Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2013 The Chromium 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 | |
| 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()), | |
|
DaleCurtis
2013/03/26 19:23:01
Instead of storing these four variables, you could
| |
| 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 DCHECK(manager_); | |
| 96 DCHECK(output_channels_ > 0); | |
| 97 | |
| 98 // Must have at least one input or output. If there are both they must be the | |
| 99 // same. | |
| 100 unsigned int input_channels = params.input_channels(); | |
|
DaleCurtis
2013/03/26 19:23:01
Just leave this as int for now. Long term security
| |
| 101 | |
| 102 if (input_channels) { | |
| 103 // A unified stream for input and output. | |
| 104 DCHECK(output_channels_ == input_channels); | |
| 105 stream_direction_ = CRAS_STREAM_UNIFIED; | |
| 106 input_bus_ = AudioBus::Create(input_channels, | |
| 107 params.frames_per_buffer()); | |
| 108 } | |
| 109 | |
| 110 output_bus_ = AudioBus::Create(params); | |
| 111 } | |
| 112 | |
| 113 CrasUnifiedStream::~CrasUnifiedStream() { | |
| 114 DCHECK(!is_playing_); | |
| 115 } | |
| 116 | |
| 117 bool CrasUnifiedStream::Open() { | |
| 118 // Sanity check input values. | |
| 119 if (frame_rate_ <= 0) { | |
| 120 LOG(WARNING) << "Unsupported audio frequency."; | |
| 121 return false; | |
| 122 } | |
| 123 | |
| 124 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { | |
| 125 LOG(WARNING) << "Unsupported pcm format"; | |
| 126 return false; | |
| 127 } | |
| 128 | |
| 129 // Create the client and connect to the CRAS server. | |
| 130 if (cras_client_create(&client_)) { | |
| 131 LOG(WARNING) << "Couldn't create CRAS client.\n"; | |
| 132 client_ = NULL; | |
| 133 return false; | |
| 134 } | |
| 135 | |
| 136 if (cras_client_connect(client_)) { | |
| 137 LOG(WARNING) << "Couldn't connect CRAS client.\n"; | |
| 138 cras_client_destroy(client_); | |
| 139 client_ = NULL; | |
| 140 return false; | |
| 141 } | |
| 142 | |
| 143 // Then start running the client. | |
| 144 if (cras_client_run_thread(client_)) { | |
| 145 LOG(WARNING) << "Couldn't run CRAS client.\n"; | |
| 146 cras_client_destroy(client_); | |
| 147 client_ = NULL; | |
| 148 return false; | |
| 149 } | |
| 150 | |
| 151 return true; | |
| 152 } | |
| 153 | |
| 154 void CrasUnifiedStream::Close() { | |
| 155 if (client_) { | |
| 156 cras_client_stop(client_); | |
| 157 cras_client_destroy(client_); | |
| 158 client_ = NULL; | |
| 159 } | |
| 160 | |
| 161 // Signal to the manager that we're closed and can be removed. | |
| 162 // Should be last call in the method as it deletes "this". | |
| 163 manager_->ReleaseOutputStream(this); | |
| 164 } | |
| 165 | |
| 166 void CrasUnifiedStream::Start(AudioSourceCallback* callback) { | |
| 167 CHECK(callback); | |
| 168 source_callback_ = callback; | |
| 169 | |
| 170 // Only start if we can enter the playing state. | |
| 171 if (is_playing_) | |
| 172 return; | |
| 173 | |
| 174 // Prepare |audio_format| and |stream_params| for the stream we | |
| 175 // will create. | |
| 176 cras_audio_format* audio_format = cras_audio_format_create( | |
| 177 pcm_format_, | |
| 178 frame_rate_, | |
| 179 output_channels_); | |
| 180 if (audio_format == NULL) { | |
|
DaleCurtis
2013/03/26 19:23:01
if !audio_format?
| |
| 181 LOG(WARNING) << "Error setting up audio parameters."; | |
| 182 callback->OnError(this, -ENOMEM); | |
| 183 return; | |
| 184 } | |
| 185 | |
| 186 cras_stream_params* stream_params = cras_client_unified_params_create( | |
| 187 stream_direction_, | |
| 188 samples_per_packet_, | |
| 189 CRAS_STREAM_TYPE_DEFAULT, | |
| 190 0, | |
| 191 this, | |
| 192 CrasUnifiedStream::UnifiedCallback, | |
| 193 CrasUnifiedStream::StreamError, | |
| 194 audio_format); | |
| 195 if (stream_params == NULL) { | |
|
DaleCurtis
2013/03/26 19:23:01
Ditto.
| |
| 196 LOG(WARNING) << "Error setting up stream parameters."; | |
| 197 callback->OnError(this, -ENOMEM); | |
| 198 cras_audio_format_destroy(audio_format); | |
| 199 return; | |
| 200 } | |
| 201 | |
| 202 // Before starting the stream, save the number of bytes in a frame for use in | |
| 203 // the callback. | |
| 204 bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format); | |
| 205 | |
| 206 // Adding the stream will start the audio callbacks requesting data. | |
| 207 int err = cras_client_add_stream(client_, &stream_id_, stream_params); | |
| 208 if (err < 0) { | |
| 209 LOG(WARNING) << "Failed to add the stream"; | |
| 210 callback->OnError(this, err); | |
|
DaleCurtis
2013/03/26 19:23:01
Interface has changed to remove err.
| |
| 211 cras_audio_format_destroy(audio_format); | |
| 212 cras_client_stream_params_destroy(stream_params); | |
| 213 return; | |
| 214 } | |
| 215 | |
| 216 // Set initial volume. | |
| 217 cras_client_set_stream_volume(client_, stream_id_, volume_); | |
| 218 | |
| 219 // Done with config params. | |
| 220 cras_audio_format_destroy(audio_format); | |
| 221 cras_client_stream_params_destroy(stream_params); | |
| 222 | |
| 223 is_playing_ = true; | |
| 224 } | |
| 225 | |
| 226 void CrasUnifiedStream::Stop() { | |
| 227 if (!client_) | |
| 228 return; | |
| 229 | |
| 230 // Removing the stream from the client stops audio. | |
| 231 cras_client_rm_stream(client_, stream_id_); | |
| 232 | |
| 233 is_playing_ = false; | |
| 234 } | |
| 235 | |
| 236 void CrasUnifiedStream::SetVolume(double volume) { | |
| 237 if (!client_) | |
| 238 return; | |
| 239 volume_ = static_cast<float>(volume); | |
| 240 cras_client_set_stream_volume(client_, stream_id_, volume_); | |
| 241 } | |
| 242 | |
| 243 void CrasUnifiedStream::GetVolume(double* volume) { | |
| 244 *volume = volume_; | |
| 245 } | |
| 246 | |
| 247 unsigned int CrasUnifiedStream::GetBytesLatency( | |
|
DaleCurtis
2013/03/26 19:23:01
If unsigned types are necessary, using size_t inst
| |
| 248 const struct timespec& latency_ts) { | |
| 249 uint32 latency_usec; | |
| 250 | |
| 251 // Treat negative latency (if we are too slow to render) as 0. | |
| 252 if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) { | |
| 253 latency_usec = 0; | |
| 254 } else { | |
| 255 latency_usec = (latency_ts.tv_sec * base::Time::kMicrosecondsPerSecond) + | |
| 256 latency_ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond; | |
| 257 } | |
| 258 | |
| 259 double frames_latency = | |
| 260 latency_usec * frame_rate_ / base::Time::kMicrosecondsPerSecond; | |
| 261 | |
| 262 return static_cast<unsigned int>(frames_latency * bytes_per_frame_); | |
| 263 } | |
| 264 | |
| 265 // Static callback asking for samples. | |
| 266 int CrasUnifiedStream::UnifiedCallback(cras_client* client, | |
| 267 cras_stream_id_t stream_id, | |
| 268 uint8* input_samples, | |
| 269 uint8* output_samples, | |
| 270 unsigned int frames, | |
| 271 const timespec* input_ts, | |
| 272 const timespec* output_ts, | |
| 273 void* arg) { | |
| 274 CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg); | |
| 275 return me->DispatchCalback(frames, | |
| 276 input_samples, | |
| 277 output_samples, | |
| 278 input_ts, | |
| 279 output_ts); | |
| 280 } | |
| 281 | |
| 282 // Static callback for stream errors. | |
| 283 int CrasUnifiedStream::StreamError(cras_client* client, | |
| 284 cras_stream_id_t stream_id, | |
| 285 int err, | |
| 286 void* arg) { | |
| 287 CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg); | |
| 288 me->NotifyStreamError(err); | |
| 289 return 0; | |
| 290 } | |
| 291 | |
| 292 // Calls the appropriate rendering function for this type of stream. | |
| 293 uint32 CrasUnifiedStream::DispatchCalback(size_t frames, | |
| 294 uint8* input_samples, | |
| 295 uint8* output_samples, | |
| 296 const timespec* input_ts, | |
| 297 const timespec* output_ts) { | |
| 298 switch (stream_direction_) { | |
| 299 case CRAS_STREAM_OUTPUT: | |
| 300 return WriteAudio(frames, output_samples, output_ts); | |
| 301 case CRAS_STREAM_INPUT: | |
| 302 NOTREACHED() << "CrasUnifiedStream doesn't support input streams."; | |
| 303 return 0; | |
| 304 case CRAS_STREAM_UNIFIED: | |
| 305 return ReadWriteAudio(frames, input_samples, output_samples, | |
| 306 input_ts, output_ts); | |
| 307 } | |
| 308 | |
| 309 return 0; | |
| 310 } | |
| 311 | |
| 312 // Note these are run from a real time thread, so don't waste cycles here. | |
| 313 uint32 CrasUnifiedStream::ReadWriteAudio(size_t frames, | |
| 314 uint8* input_samples, | |
| 315 uint8* output_samples, | |
| 316 const timespec* input_ts, | |
| 317 const timespec* output_ts) { | |
| 318 DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); | |
| 319 DCHECK(source_callback_); | |
| 320 | |
| 321 unsigned int bytes_per_sample = bytes_per_frame_ / output_channels_; | |
| 322 input_bus_->FromInterleaved(input_samples, frames, bytes_per_sample); | |
| 323 | |
| 324 // Determine latency and pass that on to the source. We have the capture time | |
| 325 // of the first input sample and the playback time of the next audio sample | |
| 326 // passed from the audio server, add them together for total latency. | |
| 327 unsigned int total_delay_bytes; | |
| 328 timespec latency_ts = {0, 0}; | |
| 329 cras_client_calc_capture_latency(input_ts, &latency_ts); | |
| 330 total_delay_bytes = GetBytesLatency(latency_ts); | |
| 331 cras_client_calc_playback_latency(output_ts, &latency_ts); | |
| 332 total_delay_bytes += GetBytesLatency(latency_ts); | |
| 333 | |
| 334 int frames_filled = source_callback_->OnMoreIOData( | |
| 335 input_bus_.get(), | |
| 336 output_bus_.get(), | |
| 337 AudioBuffersState(0, total_delay_bytes)); | |
| 338 | |
| 339 output_bus_->ToInterleaved(frames_filled, bytes_per_sample, output_samples); | |
| 340 | |
| 341 return frames_filled; | |
| 342 } | |
| 343 | |
| 344 uint32 CrasUnifiedStream::WriteAudio(size_t frames, | |
| 345 uint8* buffer, | |
| 346 const timespec* sample_ts) { | |
| 347 DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); | |
| 348 | |
| 349 // Determine latency and pass that on to the source. | |
| 350 timespec latency_ts = {0, 0}; | |
| 351 cras_client_calc_playback_latency(sample_ts, &latency_ts); | |
| 352 | |
| 353 int frames_filled = source_callback_->OnMoreData( | |
| 354 output_bus_.get(), AudioBuffersState(0, GetBytesLatency(latency_ts))); | |
| 355 | |
| 356 // Note: If this ever changes to output raw float the data must be clipped and | |
| 357 // sanitized since it may come from an untrusted source such as NaCl. | |
| 358 output_bus_->ToInterleaved( | |
| 359 frames_filled, bytes_per_frame_ / output_channels_, buffer); | |
| 360 | |
| 361 return frames_filled; | |
| 362 } | |
| 363 | |
| 364 void CrasUnifiedStream::NotifyStreamError(int err) { | |
| 365 // This will remove the stream from the client. | |
| 366 if (source_callback_) | |
| 367 source_callback_->OnError(this, err); | |
| 368 } | |
| 369 | |
| 370 } // namespace media | |
| OLD | NEW |