summaryrefslogtreecommitdiff
path: root/sound_card_init/dsm/src/zero_player.rs
blob: 441f7ffa27145f4e16fb7dd8adde291fae8ece38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;

use audio_streams::SampleFormat;
use libcras::{CrasClient, CrasNodeType};
use sys_util::error;

use crate::error::{Error, Result};

/// `ZeroPlayer` provides the functionality to play zeros sample in the background thread.
#[derive(Default)]
pub struct ZeroPlayer {
    thread_info: Option<PlayZeroWorkerInfo>,
}

impl Drop for ZeroPlayer {
    fn drop(&mut self) {
        if self.thread_info.is_some() {
            if let Err(e) = self.stop() {
                error!("{}", e);
            }
        }
    }
}

impl ZeroPlayer {
    /// It takes about 400 ms to get CRAS_NODE_TYPE_INTERNAL_SPEAKER during the boot time.
    const TIMEOUT: Duration = Duration::from_millis(1000);

    /// Returns whether the ZeroPlayer is running.
    pub fn running(&self) -> bool {
        self.thread_info.is_some()
    }

    /// Starts to play zeros for at most `max_playback_time`.
    /// This function blocks and returns until playback has started for `min_playback_time`.
    /// This function must be called when self.running() returns false.
    ///
    /// # Arguments
    ///
    /// * `min_playback_time` - It blocks and returns until playback has started for
    ///                         `min_playback_time`.
    ///
    /// # Errors
    ///
    /// * If it's called when the `ZeroPlayer` is already running.
    /// * Failed to find internal speakers.
    /// * Failed to start the background thread.
    pub fn start(&mut self, min_playback_time: Duration) -> Result<()> {
        if self.running() {
            return Err(Error::ZeroPlayerIsRunning);
        }
        self.thread_info = Some(PlayZeroWorkerInfo::new(min_playback_time));
        if let Some(thread_info) = &mut self.thread_info {
            // Block until playback of zeros has started for min_playback_time or timeout.
            let (lock, cvar) = &*(thread_info.ready);
            let result = cvar.wait_timeout_while(
                lock.lock()?,
                min_playback_time + ZeroPlayer::TIMEOUT,
                |&mut is_ready| !is_ready,
            )?;
            if result.1.timed_out() {
                return Err(Error::StartPlaybackTimeout);
            }
        }
        Ok(())
    }

    /// Stops playing zeros in the background thread.
    /// This function must be called when self.running() returns true.
    ///
    /// # Errors
    ///
    /// * If it's called again when the `ZeroPlayer` is not running.
    /// * Failed to play zeros to internal speakers via CRAS client.
    /// * Failed to join the background thread.
    pub fn stop(&mut self) -> Result<()> {
        match self.thread_info.take() {
            Some(mut thread_info) => Ok(thread_info.destroy()?),
            None => Err(Error::ZeroPlayerIsNotRunning),
        }
    }
}

// Audio thread book-keeping data
struct PlayZeroWorkerInfo {
    thread: Option<JoinHandle<Result<()>>>,
    // Uses `thread_run` to notify the background thread to stop.
    thread_run: Arc<AtomicBool>,
    // The background thread uses `ready` to notify the main thread that playback
    // of zeros has started for min_playback_time.
    ready: Arc<(Mutex<bool>, Condvar)>,
}

impl Drop for PlayZeroWorkerInfo {
    fn drop(&mut self) {
        if let Err(e) = self.destroy() {
            error!("{}", e);
        }
    }
}

impl PlayZeroWorkerInfo {
    // Spawns the PlayZeroWorker.
    fn new(min_playback_time: Duration) -> Self {
        let thread_run = Arc::new(AtomicBool::new(false));
        let ready = Arc::new((Mutex::new(false), Condvar::new()));
        let mut worker = PlayZeroWorker::new(min_playback_time, thread_run.clone(), ready.clone());
        Self {
            thread: Some(thread::spawn(move || -> Result<()> {
                worker.run()?;
                Ok(())
            })),
            thread_run,
            ready,
        }
    }

    // Joins the PlayZeroWorker.
    fn destroy(&mut self) -> Result<()> {
        self.thread_run.store(false, Ordering::Relaxed);
        if let Some(handle) = self.thread.take() {
            let res = handle.join().map_err(Error::WorkerPanics)?;
            return match res {
                Err(e) => Err(e),
                Ok(_) => Ok(()),
            };
        }
        Ok(())
    }
}

struct PlayZeroWorker {
    min_playback_time: Duration,
    // Uses `thread_run` to notify the background thread to stop.
    thread_run: Arc<AtomicBool>,
    // The background thread uses `ready` to notify the main thread that playback
    // of zeros has started for min_playback_time.
    ready: Arc<(Mutex<bool>, Condvar)>,
}

impl PlayZeroWorker {
    const FRAMES_PER_BUFFER: usize = 256;
    const FRAME_RATE: u32 = 48000;
    const NUM_CHANNELS: usize = 2;
    const FORMAT: SampleFormat = SampleFormat::S16LE;

    fn new(
        min_playback_time: Duration,
        thread_run: Arc<AtomicBool>,
        ready: Arc<(Mutex<bool>, Condvar)>,
    ) -> Self {
        Self {
            min_playback_time,
            thread_run,
            ready,
        }
    }

    fn run(&mut self) -> Result<()> {
        let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
        let node = cras_client
            .output_nodes()
            .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
            .ok_or(Error::InternalSpeakerNotFound)?;
        let local_buffer =
            vec![0u8; Self::FRAMES_PER_BUFFER * Self::NUM_CHANNELS * Self::FORMAT.sample_bytes()];
        let min_playback_iterations = (Self::FRAME_RATE
            * self.min_playback_time.as_millis() as u32)
            / Self::FRAMES_PER_BUFFER as u32
            / 1000;
        let (_control, mut stream) = cras_client
            .new_pinned_playback_stream(
                node.iodev_index,
                Self::NUM_CHANNELS,
                Self::FORMAT,
                Self::FRAME_RATE,
                Self::FRAMES_PER_BUFFER,
            )
            .map_err(|e| Error::NewPlayStreamFailed(e))?;

        let mut iter = 0;
        self.thread_run.store(true, Ordering::Relaxed);
        while self.thread_run.load(Ordering::Relaxed) {
            let mut buffer = stream
                .next_playback_buffer()
                .map_err(|e| Error::NextPlaybackBufferFailed(e))?;
            let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?;

            // Notifies the main thread that playback of zeros has started for min_playback_time.
            if iter == min_playback_iterations {
                let (lock, cvar) = &*self.ready;
                let mut is_ready = lock.lock()?;
                *is_ready = true;
                cvar.notify_one();
            }
            iter += 1;
        }
        Ok(())
    }
}