• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 use std::time::Duration;
5 use std::{error, fmt};
6 
7 use audio_streams::{
8     shm_streams::{BufferSet, ServerRequest, ShmStream},
9     BoxError, SampleFormat, StreamDirection,
10 };
11 use cras_sys::gen::CRAS_AUDIO_MESSAGE_ID;
12 use sys_util::error;
13 
14 use crate::audio_socket::{AudioMessage, AudioSocket};
15 use crate::cras_server_socket::CrasServerSocket;
16 use crate::cras_shm::{self, CrasAudioHeader, CrasAudioShmHeaderFd};
17 
18 #[derive(Debug)]
19 pub enum Error {
20     MessageTypeError,
21     CaptureBufferTooSmall,
22 }
23 
24 impl error::Error for Error {}
25 
26 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result27     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28         match self {
29             Error::MessageTypeError => write!(f, "Message type error"),
30             Error::CaptureBufferTooSmall => write!(
31                 f,
32                 "Capture buffer too small, must have size at least 'used_size'."
33             ),
34         }
35     }
36 }
37 
38 /// An object that handles interactions with CRAS for a shm stream.
39 /// The object implements `ShmStream` and so can be used to wait for
40 /// `ServerRequest` and `BufferComplete` messages.
41 pub struct CrasShmStream<'a> {
42     stream_id: u32,
43     server_socket: CrasServerSocket,
44     audio_socket: AudioSocket,
45     direction: StreamDirection,
46     header: CrasAudioHeader<'a>,
47     frame_size: usize,
48     num_channels: usize,
49     frame_rate: u32,
50     // The index of the next buffer within SHM to set the buffer offset for.
51     next_buffer_idx: usize,
52 }
53 
54 impl<'a> CrasShmStream<'a> {
55     /// Attempt to creates a CrasShmStream with the given arguments.
56     ///
57     /// # Arguments
58     ///
59     /// * `stream_id` - The server's ID for the stream.
60     /// * `server_socket` - The socket that is connected to the server.
61     /// * `audio_socket` - The socket for audio request and audio available messages.
62     /// * `direction` - The direction of the stream, `Playback` or `Capture`.
63     /// * `num_channels` - The number of audio channels for the stream.
64     /// * `format` - The format to use for the stream's samples.
65     /// * `header_fd` - The file descriptor for the audio header shm area.
66     /// * `samples_len` - The size of the audio samples shm area.
67     ///
68     /// # Returns
69     ///
70     /// `CrasShmStream` - CRAS client stream.
71     ///
72     /// # Errors
73     ///
74     /// * If `header_fd` could not be successfully mmapped.
75     #[allow(clippy::too_many_arguments)]
try_new( stream_id: u32, server_socket: CrasServerSocket, audio_socket: AudioSocket, direction: StreamDirection, num_channels: usize, frame_rate: u32, format: SampleFormat, header_fd: CrasAudioShmHeaderFd, samples_len: usize, ) -> Result<Self, BoxError>76     pub fn try_new(
77         stream_id: u32,
78         server_socket: CrasServerSocket,
79         audio_socket: AudioSocket,
80         direction: StreamDirection,
81         num_channels: usize,
82         frame_rate: u32,
83         format: SampleFormat,
84         header_fd: CrasAudioShmHeaderFd,
85         samples_len: usize,
86     ) -> Result<Self, BoxError> {
87         let header = cras_shm::create_header(header_fd, samples_len)?;
88         Ok(Self {
89             stream_id,
90             server_socket,
91             audio_socket,
92             direction,
93             header,
94             frame_size: format.sample_bytes() * num_channels,
95             num_channels,
96             frame_rate,
97             // We have either sent zero or two offsets to the server, so we will
98             // need to update index 0 next.
99             next_buffer_idx: 0,
100         })
101     }
102 }
103 
104 impl<'a> Drop for CrasShmStream<'a> {
105     /// Send the disconnect stream message and log an error if sending fails.
drop(&mut self)106     fn drop(&mut self) {
107         if let Err(e) = self.server_socket.disconnect_stream(self.stream_id) {
108             error!("CrasShmStream::drop error: {}", e);
109         }
110     }
111 }
112 
113 impl<'a> ShmStream for CrasShmStream<'a> {
frame_size(&self) -> usize114     fn frame_size(&self) -> usize {
115         self.frame_size
116     }
117 
num_channels(&self) -> usize118     fn num_channels(&self) -> usize {
119         self.num_channels
120     }
121 
frame_rate(&self) -> u32122     fn frame_rate(&self) -> u32 {
123         self.frame_rate
124     }
125 
wait_for_next_action_with_timeout( &mut self, timeout: Duration, ) -> Result<Option<ServerRequest>, BoxError>126     fn wait_for_next_action_with_timeout(
127         &mut self,
128         timeout: Duration,
129     ) -> Result<Option<ServerRequest>, BoxError> {
130         let expected_id = match self.direction {
131             StreamDirection::Playback => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA,
132             StreamDirection::Capture => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY,
133         };
134 
135         match self
136             .audio_socket
137             .read_audio_message_with_timeout(Some(timeout))?
138         {
139             Some(AudioMessage::Success { id, frames }) if id == expected_id => {
140                 Ok(Some(ServerRequest::new(frames as usize, self)))
141             }
142             None => Ok(None),
143             _ => Err(Box::new(Error::MessageTypeError)),
144         }
145     }
146 }
147 
148 impl BufferSet for CrasShmStream<'_> {
callback(&mut self, offset: usize, frames: usize) -> Result<(), BoxError>149     fn callback(&mut self, offset: usize, frames: usize) -> Result<(), BoxError> {
150         self.header
151             .set_buffer_offset(self.next_buffer_idx, offset)?;
152         self.next_buffer_idx ^= 1;
153         let frames = frames as u32;
154 
155         match self.direction {
156             StreamDirection::Playback => {
157                 self.header.commit_written_frames(frames)?;
158 
159                 // Notify CRAS that we've made playback data available.
160                 self.audio_socket.data_ready(frames)?
161             }
162             StreamDirection::Capture => {
163                 let used_size = self.header.get_used_size();
164                 // Because CRAS doesn't know how long our buffer in shm is, we
165                 // must make sure that there are always at least buffer_size
166                 // frames available so that it doesn't write outside the buffer.
167                 if frames < (used_size / self.frame_size) as u32 {
168                     return Err(Box::new(Error::CaptureBufferTooSmall));
169                 }
170 
171                 self.header.commit_read_frames(frames)?;
172                 self.audio_socket.capture_ready(frames)?;
173             }
174         }
175 
176         Ok(())
177     }
178 
ignore(&mut self) -> Result<(), BoxError>179     fn ignore(&mut self) -> Result<(), BoxError> {
180         // We send an empty buffer for an ignored playback request since the
181         // server will not read from a 0-length buffer. We don't do anything for
182         // an ignored capture request, since we don't have a way to communicate
183         // buffer length to the server, and we don't want the server writing
184         // data to offsets within the SHM area that aren't audio buffers.
185         if self.direction == StreamDirection::Playback {
186             self.callback(0, 0)?;
187         }
188 
189         Ok(())
190     }
191 }
192