Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |