Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: media/audio/cras/cras_unified.cc

Issue 11959018: Add a unified audio I/O backend for ChromeOS. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fixed unittest too Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
no longer working on chromium 2013/03/21 15:38:27 ditto
dgreid 2013/03/21 17:18:44 Done.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/audio/cras/cras_unified.h"
6
7 #include <cras_client.h>
8
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "media/audio/audio_util.h"
12 #include "media/audio/cras/audio_manager_cras.h"
13 #include "media/audio/linux/alsa_util.h"
14
15 namespace media {
16
17 // Overview of operation:
18 // 1) An object of CrasUnifiedStream is created by the AudioManager
19 // factory: audio_man->MakeAudioStream().
20 // 2) Next some thread will call Open(), at that point a client is created and
21 // configured for the correct format and sample rate.
22 // 3) Then Start(source) is called and a stream is added to the CRAS client
23 // which will create its own thread that periodically calls the source for more
24 // data as buffers are being consumed.
25 // 4) When finished Stop() is called, which is handled by stopping the stream.
26 // 5) Finally Close() is called. It cleans up and notifies the audio manager,
27 // which likely will destroy this object.
28 //
29 // For output-only streams, a unified stream is created with 0 input channels.
30 //
31 // Simplified data flow for unified streams:
32 //
33 // +-------------+ +------------------+
34 // | CRAS Server | | Chrome Client |
35 // +------+------+ Add Stream +---------+--------+
36 // |<----------------------------------|
37 // | |
38 // | buffer_frames captured to shm |
39 // |---------------------------------->|
40 // | | UnifiedCallback()
41 // | | ReadWriteAudio()
42 // | |
43 // | buffer_frames written to shm |
44 // |<----------------------------------|
45 // | |
46 // ... Repeats for each block. ...
47 // | |
48 // | |
49 // | Remove stream |
50 // |<----------------------------------|
51 // | |
52 //
53 // Simplified data flow for output only streams:
54 //
55 // +-------------+ +------------------+
56 // | CRAS Server | | Chrome Client |
57 // +------+------+ Add Stream +---------+--------+
58 // |<----------------------------------|
59 // | |
60 // | Near out of samples, request more |
61 // |---------------------------------->|
62 // | | UnifiedCallback()
63 // | | WriteAudio()
64 // | |
65 // | buffer_frames written to shm |
66 // |<----------------------------------|
67 // | |
68 // ... Repeats for each block. ...
69 // | |
70 // | |
71 // | Remove stream |
72 // |<----------------------------------|
73 // | |
74 //
75 // For Unified streams the Chrome client is notified whenever buffer_frames have
76 // been captured. For Output streams the client is notified a few milliseconds
77 // before the hardware buffer underruns and fills the buffer with another block
78 // of audio.
79
80 CrasUnifiedStream::CrasUnifiedStream(const AudioParameters& params,
81 AudioManagerCras* manager)
82 : client_(NULL),
83 stream_id_(0),
84 samples_per_packet_(params.frames_per_buffer()),
85 bytes_per_frame_(0),
86 frame_rate_(params.sample_rate()),
87 output_channels_(params.channels()),
88 pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())),
89 is_playing_(false),
90 volume_(1.0),
91 manager_(manager),
92 source_callback_(NULL),
93 input_bus_(NULL),
94 output_bus_(NULL) {
95 unsigned int input_channels = params.input_channels();
no longer working on chromium 2013/03/21 15:38:27 Curiously, why do we need output_channels to be a
dgreid 2013/03/21 17:18:44 If there are input channels they will always be th
dgreid 2013/03/21 17:18:44 If there are input channels there will be the same
96
97 // We must have a manager.
no longer working on chromium 2013/03/21 15:38:27 nit, the comment tells nothing, please remove
dgreid 2013/03/21 17:18:44 Done.
98 DCHECK(manager_);
no longer working on chromium 2013/03/21 15:38:27 move the DCHECK to the top of the function.
dgreid 2013/03/21 17:18:44 Done.
99 // Must have at least one input or output. If there are both they must be the
100 // same.
101 DCHECK(output_channels_ > 0 || input_channels > 0);
102 if (output_channels_ > 0 && input_channels > 0)
103 DCHECK(output_channels_ == input_channels);
104
105 if (input_channels)
106 input_bus_ = AudioBus::Create(input_channels,
107 params.frames_per_buffer());
108 if (output_channels_)
109 output_bus_ = AudioBus::Create(params);
110
111 if (output_channels_ && input_channels)
112 stream_direction_ = CRAS_STREAM_UNIFIED;
113 else if (output_channels_)
114 stream_direction_ = CRAS_STREAM_OUTPUT;
115 else
116 NOTREACHED() << "Input only streams not supported by CrasUnifiedStream.";
no longer working on chromium 2013/03/21 15:38:27 move this section up to line 101, and combine with
dgreid 2013/03/21 17:18:44 I got rid of it and simlified the checks above, th
117 }
118
119 CrasUnifiedStream::~CrasUnifiedStream() {
120 DCHECK(!is_playing_);
121 }
122
123 bool CrasUnifiedStream::Open() {
124 // Sanity check input values.
125 if (frame_rate_ <= 0) {
126 LOG(WARNING) << "Unsupported audio frequency.";
127 return false;
128 }
129
130 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) {
131 LOG(WARNING) << "Unsupported pcm format";
132 return false;
133 }
134
135 // Create the client and connect to the CRAS server.
136 int err = cras_client_create(&client_);
no longer working on chromium 2013/03/21 15:38:27 why do you need variable err? You can simply do i
dgreid 2013/03/21 17:18:44 Done.
137 if (err < 0) {
138 LOG(WARNING) << "Couldn't create CRAS client.\n";
139 client_ = NULL;
140 return false;
141 }
142
143 err = cras_client_connect(client_);
144 if (err) {
145 LOG(WARNING) << "Couldn't connect CRAS client.\n";
146 cras_client_destroy(client_);
147 client_ = NULL;
148 return false;
149 }
150
151 // Then start running the client.
152 err = cras_client_run_thread(client_);
153 if (err) {
154 LOG(WARNING) << "Couldn't run CRAS client.\n";
155 cras_client_destroy(client_);
156 client_ = NULL;
157 return false;
158 }
159
160 return true;
161 }
162
163 void CrasUnifiedStream::Close() {
164 if (client_) {
165 cras_client_stop(client_);
166 cras_client_destroy(client_);
167 client_ = NULL;
168 }
169
170 // Signal to the manager that we're closed and can be removed.
171 // Should be last call in the method as it deletes "this".
172 manager_->ReleaseOutputStream(this);
173 }
174
175 void CrasUnifiedStream::Start(AudioSourceCallback* callback) {
176 CHECK(callback);
177 source_callback_ = callback;
178
179 // Only start if we can enter the playing state.
180 if (is_playing_)
181 return;
182
183 // Prepare |audio_format| and |stream_params| for the stream we
184 // will create.
185 cras_audio_format* audio_format = cras_audio_format_create(
186 pcm_format_,
187 frame_rate_,
188 output_channels_);
189 if (audio_format == NULL) {
190 LOG(WARNING) << "Error setting up audio parameters.";
191 callback->OnError(this, -ENOMEM);
192 return;
193 }
194
195 cras_stream_params* stream_params = cras_client_unified_params_create(
196 stream_direction_,
197 samples_per_packet_,
198 CRAS_STREAM_TYPE_DEFAULT,
199 0,
200 this,
201 CrasUnifiedStream::UnifiedCallback,
202 CrasUnifiedStream::StreamError,
203 audio_format);
204 if (stream_params == NULL) {
205 LOG(WARNING) << "Error setting up stream parameters.";
206 callback->OnError(this, -ENOMEM);
207 cras_audio_format_destroy(audio_format);
208 return;
209 }
210
211 // Before starting the stream, save the number of bytes in a frame for use in
212 // the callback.
213 bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format);
214
215 // Adding the stream will start the audio callbacks requesting data.
216 int err = cras_client_add_stream(client_, &stream_id_, stream_params);
217 if (err < 0) {
218 LOG(WARNING) << "Failed to add the stream";
219 callback->OnError(this, err);
220 cras_audio_format_destroy(audio_format);
221 cras_client_stream_params_destroy(stream_params);
222 return;
223 }
224
225 // Set initial volume.
226 cras_client_set_stream_volume(client_, stream_id_, volume_);
227
228 // Done with config params.
229 cras_audio_format_destroy(audio_format);
230 cras_client_stream_params_destroy(stream_params);
231
232 is_playing_ = true;
233 }
234
235 void CrasUnifiedStream::Stop() {
236 if (!client_)
237 return;
238
239 // Removing the stream from the client stops audio.
240 cras_client_rm_stream(client_, stream_id_);
241
242 is_playing_ = false;
243 }
244
245 void CrasUnifiedStream::SetVolume(double volume) {
246 if (!client_)
247 return;
248 volume_ = static_cast<float>(volume);
249 cras_client_set_stream_volume(client_, stream_id_, volume_);
250 }
251
252 void CrasUnifiedStream::GetVolume(double* volume) {
253 *volume = volume_;
254 }
255
256 unsigned int CrasUnifiedStream::GetBytesLatency(
257 const struct timespec& latency_ts) {
258 uint32 latency_usec;
259
260 // Treat negative latency (if we are too slow to render) as 0.
261 if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0)
no longer working on chromium 2013/03/21 15:38:27 nit, add {} since else case has more than 1 line.
dgreid 2013/03/21 17:18:44 Done.
262 latency_usec = 0;
263 else
264 latency_usec = (latency_ts.tv_sec * base::Time::kMicrosecondsPerSecond) +
265 latency_ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond;
266
267 double frames_latency =
268 latency_usec * frame_rate_ / base::Time::kMicrosecondsPerSecond;
269
270 return static_cast<unsigned int>(frames_latency * bytes_per_frame_);
271 }
272
273 // Static callback asking for samples.
274 int CrasUnifiedStream::UnifiedCallback(cras_client* client,
275 cras_stream_id_t stream_id,
276 uint8* input_samples,
277 uint8* output_samples,
278 unsigned int frames,
279 const timespec* input_ts,
280 const timespec* output_ts,
281 void* arg) {
282 CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg);
283 return me->DispatchCalback(frames,
284 input_samples,
285 output_samples,
286 input_ts,
287 output_ts);
288 }
289
290 // Static callback for stream errors.
291 int CrasUnifiedStream::StreamError(cras_client* client,
292 cras_stream_id_t stream_id,
293 int err,
294 void* arg) {
295 CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg);
296 me->NotifyStreamError(err);
297 return 0;
298 }
299
300 // Calls the appropriate rendering function for this type of stream.
301 uint32 CrasUnifiedStream::DispatchCalback(size_t frames,
302 uint8* input_samples,
303 uint8* output_samples,
304 const timespec* input_ts,
305 const timespec* output_ts) {
306 switch (stream_direction_) {
307 case CRAS_STREAM_OUTPUT:
308 return WriteAudio(frames, output_samples, output_ts);
309 case CRAS_STREAM_INPUT:
310 NOTREACHED() << "CrasUnifiedStream doesn't support input streams.";
311 return 0;
312 case CRAS_STREAM_UNIFIED:
313 return ReadWriteAudio(frames, input_samples, output_samples,
314 input_ts, output_ts);
315 }
316
317 return 0;
318 }
319
320 // Note these are run from a real time thread, so don't waste cycles here.
321 uint32 CrasUnifiedStream::ReadWriteAudio(size_t frames,
322 uint8* input_samples,
323 uint8* output_samples,
324 const timespec* input_ts,
325 const timespec* output_ts) {
326 unsigned int bytes_per_sample = bytes_per_frame_ / output_channels_;
327
328 DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames()));
329 DCHECK(source_callback_);
330
331 input_bus_->FromInterleaved(input_samples, frames, bytes_per_sample);
332
333 // Determine latency and pass that on to the source. We have the capture time
334 // of the first input sample and the playback time of the next audio sample
335 // passed from the audio server, add them together for total latency.
336 unsigned int total_delay_bytes;
337 timespec latency_ts = {0, 0};
338 cras_client_calc_capture_latency(input_ts, &latency_ts);
339 total_delay_bytes = GetBytesLatency(latency_ts);
340 cras_client_calc_playback_latency(output_ts, &latency_ts);
341 total_delay_bytes += GetBytesLatency(latency_ts);
342
343 int frames_filled = source_callback_->OnMoreIOData(
344 input_bus_.get(),
345 output_bus_.get(),
346 AudioBuffersState(0, total_delay_bytes));
347
348 output_bus_->ToInterleaved(frames_filled, bytes_per_sample, output_samples);
349
350 return frames_filled;
351 }
352
353 uint32 CrasUnifiedStream::WriteAudio(size_t frames,
354 uint8* buffer,
355 const timespec* sample_ts) {
356 timespec latency_ts = {0, 0};
no longer working on chromium 2013/03/21 15:38:27 nit, move the declaration to the place where it is
dgreid 2013/03/21 17:18:44 Done.
dgreid 2013/03/21 17:18:44 Done.
357
358 DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames()));
359
360 // Determine latency and pass that on to the source.
361 cras_client_calc_playback_latency(sample_ts, &latency_ts);
362
363 int frames_filled = source_callback_->OnMoreData(
364 output_bus_.get(), AudioBuffersState(0, GetBytesLatency(latency_ts)));
365
366 // Note: If this ever changes to output raw float the data must be clipped and
367 // sanitized since it may come from an untrusted source such as NaCl.
368 output_bus_->ToInterleaved(
369 frames_filled, bytes_per_frame_ / output_channels_, buffer);
370
371 return frames_filled;
372 }
373
374 void CrasUnifiedStream::NotifyStreamError(int err) {
375 // This will remove the stream from the client.
376 if (source_callback_)
377 source_callback_->OnError(this, err);
378 }
379
380 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698