| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 // The object has one error state: |state_| == kInError. When |state_| == | |
| 6 // kInError, all public API functions will fail with an error (Start() will call | |
| 7 // the OnError() function on the callback immediately), or no-op themselves with | |
| 8 // the exception of Close(). Even if an error state has been entered, if Open() | |
| 9 // has previously returned successfully, Close() must be called. | |
| 10 | |
| 11 #include "media/audio/cras/cras_output.h" | |
| 12 | |
| 13 #include <cras_client.h> | |
| 14 | |
| 15 #include "base/logging.h" | |
| 16 #include "media/audio/audio_util.h" | |
| 17 #include "media/audio/cras/audio_manager_cras.h" | |
| 18 #include "media/audio/linux/alsa_util.h" | |
| 19 | |
| 20 namespace media { | |
| 21 | |
| 22 // Helps make log messages readable. | |
| 23 std::ostream& operator<<(std::ostream& os, | |
| 24 CrasOutputStream::InternalState state) { | |
| 25 switch (state) { | |
| 26 case CrasOutputStream::kInError: | |
| 27 os << "kInError"; | |
| 28 break; | |
| 29 case CrasOutputStream::kCreated: | |
| 30 os << "kCreated"; | |
| 31 break; | |
| 32 case CrasOutputStream::kIsOpened: | |
| 33 os << "kIsOpened"; | |
| 34 break; | |
| 35 case CrasOutputStream::kIsPlaying: | |
| 36 os << "kIsPlaying"; | |
| 37 break; | |
| 38 case CrasOutputStream::kIsStopped: | |
| 39 os << "kIsStopped"; | |
| 40 break; | |
| 41 case CrasOutputStream::kIsClosed: | |
| 42 os << "kIsClosed"; | |
| 43 break; | |
| 44 default: | |
| 45 os << "UnknownState"; | |
| 46 break; | |
| 47 }; | |
| 48 return os; | |
| 49 } | |
| 50 | |
| 51 // Overview of operation: | |
| 52 // 1) An object of CrasOutputStream is created by the AudioManager | |
| 53 // factory: audio_man->MakeAudioStream(). | |
| 54 // 2) Next some thread will call Open(), at that point a client is created and | |
| 55 // configured for the correct format and sample rate. | |
| 56 // 3) Then Start(source) is called and a stream is added to the CRAS client | |
| 57 // which will create its own thread that periodically calls the source for more | |
| 58 // data as buffers are being consumed. | |
| 59 // 4) When finished Stop() is called, which is handled by stopping the stream. | |
| 60 // 5) Finally Close() is called. It cleans up and notifies the audio manager, | |
| 61 // which likely will destroy this object. | |
| 62 | |
| 63 CrasOutputStream::CrasOutputStream(const AudioParameters& params, | |
| 64 AudioManagerCras* manager) | |
| 65 : client_(NULL), | |
| 66 stream_id_(0), | |
| 67 samples_per_packet_(params.frames_per_buffer()), | |
| 68 bytes_per_frame_(0), | |
| 69 frame_rate_(params.sample_rate()), | |
| 70 num_channels_(params.channels()), | |
| 71 pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), | |
| 72 state_(kCreated), | |
| 73 volume_(1.0), | |
| 74 manager_(manager), | |
| 75 source_callback_(NULL), | |
| 76 audio_bus_(AudioBus::Create(params)) { | |
| 77 // We must have a manager. | |
| 78 DCHECK(manager_); | |
| 79 | |
| 80 // Sanity check input values. | |
| 81 if (params.sample_rate() <= 0) { | |
| 82 LOG(WARNING) << "Unsupported audio frequency."; | |
| 83 TransitionTo(kInError); | |
| 84 return; | |
| 85 } | |
| 86 | |
| 87 if (AudioParameters::AUDIO_PCM_LINEAR != params.format() && | |
| 88 AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format()) { | |
| 89 LOG(WARNING) << "Unsupported audio format."; | |
| 90 TransitionTo(kInError); | |
| 91 return; | |
| 92 } | |
| 93 | |
| 94 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { | |
| 95 LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample(); | |
| 96 TransitionTo(kInError); | |
| 97 return; | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 CrasOutputStream::~CrasOutputStream() { | |
| 102 InternalState current_state = state(); | |
| 103 DCHECK(current_state == kCreated || | |
| 104 current_state == kIsClosed || | |
| 105 current_state == kInError); | |
| 106 } | |
| 107 | |
| 108 bool CrasOutputStream::Open() { | |
| 109 if (!CanTransitionTo(kIsOpened)) { | |
| 110 NOTREACHED() << "Invalid state: " << state(); | |
| 111 return false; | |
| 112 } | |
| 113 | |
| 114 // We do not need to check if the transition was successful because | |
| 115 // CanTransitionTo() was checked above, and it is assumed that this | |
| 116 // object's public API is only called on one thread so the state cannot | |
| 117 // transition out from under us. | |
| 118 TransitionTo(kIsOpened); | |
| 119 | |
| 120 // Create the client and connect to the CRAS server. | |
| 121 int err = cras_client_create(&client_); | |
| 122 if (err < 0) { | |
| 123 LOG(WARNING) << "Couldn't create CRAS client.\n"; | |
| 124 client_ = NULL; | |
| 125 TransitionTo(kInError); | |
| 126 return false; | |
| 127 } | |
| 128 err = cras_client_connect(client_); | |
| 129 if (err) { | |
| 130 LOG(WARNING) << "Couldn't connect CRAS client.\n"; | |
| 131 cras_client_destroy(client_); | |
| 132 client_ = NULL; | |
| 133 TransitionTo(kInError); | |
| 134 return false; | |
| 135 } | |
| 136 // Then start running the client. | |
| 137 err = cras_client_run_thread(client_); | |
| 138 if (err) { | |
| 139 LOG(WARNING) << "Couldn't run CRAS client.\n"; | |
| 140 cras_client_destroy(client_); | |
| 141 client_ = NULL; | |
| 142 TransitionTo(kInError); | |
| 143 return false; | |
| 144 } | |
| 145 | |
| 146 return true; | |
| 147 } | |
| 148 | |
| 149 void CrasOutputStream::Close() { | |
| 150 // Sanity Check that we can transition to closed. | |
| 151 if (TransitionTo(kIsClosed) != kIsClosed) { | |
| 152 NOTREACHED() << "Unable to transition Closed."; | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 if (client_) { | |
| 157 cras_client_stop(client_); | |
| 158 cras_client_destroy(client_); | |
| 159 client_ = NULL; | |
| 160 } | |
| 161 | |
| 162 // Signal to the manager that we're closed and can be removed. | |
| 163 // Should be last call in the method as it deletes "this". | |
| 164 manager_->ReleaseOutputStream(this); | |
| 165 } | |
| 166 | |
| 167 void CrasOutputStream::Start(AudioSourceCallback* callback) { | |
| 168 CHECK(callback); | |
| 169 source_callback_ = callback; | |
| 170 | |
| 171 // Only start if we can enter the playing state. | |
| 172 if (TransitionTo(kIsPlaying) != kIsPlaying) | |
| 173 return; | |
| 174 | |
| 175 // Prepare |audio_format| and |stream_params| for the stream we | |
| 176 // will create. | |
| 177 cras_audio_format* audio_format = cras_audio_format_create( | |
| 178 pcm_format_, | |
| 179 frame_rate_, | |
| 180 num_channels_); | |
| 181 if (audio_format == NULL) { | |
| 182 LOG(WARNING) << "Error setting up audio parameters."; | |
| 183 TransitionTo(kInError); | |
| 184 callback->OnError(this); | |
| 185 return; | |
| 186 } | |
| 187 cras_stream_params* stream_params = cras_client_stream_params_create( | |
| 188 CRAS_STREAM_OUTPUT, | |
| 189 samples_per_packet_ * 2, // Total latency. | |
| 190 samples_per_packet_ / 2, // Call back when this many left. | |
| 191 samples_per_packet_, // Call back with at least this much space. | |
| 192 CRAS_STREAM_TYPE_DEFAULT, | |
| 193 0, | |
| 194 this, | |
| 195 CrasOutputStream::PutSamples, | |
| 196 CrasOutputStream::StreamError, | |
| 197 audio_format); | |
| 198 if (stream_params == NULL) { | |
| 199 LOG(WARNING) << "Error setting up stream parameters."; | |
| 200 TransitionTo(kInError); | |
| 201 callback->OnError(this); | |
| 202 cras_audio_format_destroy(audio_format); | |
| 203 return; | |
| 204 } | |
| 205 | |
| 206 // Before starting the stream, save the number of bytes in a frame for use in | |
| 207 // the callback. | |
| 208 bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format); | |
| 209 | |
| 210 // Adding the stream will start the audio callbacks requesting data. | |
| 211 int err = cras_client_add_stream(client_, &stream_id_, stream_params); | |
| 212 if (err < 0) { | |
| 213 LOG(WARNING) << "Failed to add the stream"; | |
| 214 TransitionTo(kInError); | |
| 215 callback->OnError(this); | |
| 216 cras_audio_format_destroy(audio_format); | |
| 217 cras_client_stream_params_destroy(stream_params); | |
| 218 return; | |
| 219 } | |
| 220 | |
| 221 // Set initial volume. | |
| 222 cras_client_set_stream_volume(client_, stream_id_, volume_); | |
| 223 | |
| 224 // Done with config params. | |
| 225 cras_audio_format_destroy(audio_format); | |
| 226 cras_client_stream_params_destroy(stream_params); | |
| 227 } | |
| 228 | |
| 229 void CrasOutputStream::Stop() { | |
| 230 if (!client_) | |
| 231 return; | |
| 232 // Removing the stream from the client stops audio. | |
| 233 cras_client_rm_stream(client_, stream_id_); | |
| 234 TransitionTo(kIsStopped); | |
| 235 } | |
| 236 | |
| 237 void CrasOutputStream::SetVolume(double volume) { | |
| 238 if (!client_) | |
| 239 return; | |
| 240 volume_ = static_cast<float>(volume); | |
| 241 cras_client_set_stream_volume(client_, stream_id_, volume_); | |
| 242 } | |
| 243 | |
| 244 void CrasOutputStream::GetVolume(double* volume) { | |
| 245 *volume = volume_; | |
| 246 } | |
| 247 | |
| 248 // Static callback asking for samples. | |
| 249 int CrasOutputStream::PutSamples(cras_client* client, | |
| 250 cras_stream_id_t stream_id, | |
| 251 uint8* samples, | |
| 252 size_t frames, | |
| 253 const timespec* sample_ts, | |
| 254 void* arg) { | |
| 255 CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); | |
| 256 return me->Render(frames, samples, sample_ts); | |
| 257 } | |
| 258 | |
| 259 // Static callback for stream errors. | |
| 260 int CrasOutputStream::StreamError(cras_client* client, | |
| 261 cras_stream_id_t stream_id, | |
| 262 int err, | |
| 263 void* arg) { | |
| 264 CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); | |
| 265 me->NotifyStreamError(err); | |
| 266 return 0; | |
| 267 } | |
| 268 | |
| 269 // Note this is run from a real time thread, so don't waste cycles here. | |
| 270 uint32 CrasOutputStream::Render(size_t frames, | |
| 271 uint8* buffer, | |
| 272 const timespec* sample_ts) { | |
| 273 timespec latency_ts = {0, 0}; | |
| 274 | |
| 275 // Determine latency and pass that on to the source. | |
| 276 cras_client_calc_playback_latency(sample_ts, &latency_ts); | |
| 277 | |
| 278 // Treat negative latency (if we are too slow to render) as 0. | |
| 279 uint32 latency_usec; | |
| 280 if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) { | |
| 281 latency_usec = 0; | |
| 282 } else { | |
| 283 latency_usec = (latency_ts.tv_sec * 1000000) + | |
| 284 latency_ts.tv_nsec / 1000; | |
| 285 } | |
| 286 | |
| 287 uint32 frames_latency = latency_usec * frame_rate_ / 1000000; | |
| 288 uint32 bytes_latency = frames_latency * bytes_per_frame_; | |
| 289 DCHECK_EQ(frames, static_cast<size_t>(audio_bus_->frames())); | |
| 290 int frames_filled = source_callback_->OnMoreData( | |
| 291 audio_bus_.get(), AudioBuffersState(0, bytes_latency)); | |
| 292 // Note: If this ever changes to output raw float the data must be clipped and | |
| 293 // sanitized since it may come from an untrusted source such as NaCl. | |
| 294 audio_bus_->ToInterleaved( | |
| 295 frames_filled, bytes_per_frame_ / num_channels_, buffer); | |
| 296 return frames_filled; | |
| 297 } | |
| 298 | |
| 299 void CrasOutputStream::NotifyStreamError(int err) { | |
| 300 // This will remove the stream from the client. | |
| 301 if (state_ == kIsClosed || state_ == kInError) | |
| 302 return; // Don't care about error if we aren't using it. | |
| 303 TransitionTo(kInError); | |
| 304 if (source_callback_) | |
| 305 source_callback_->OnError(this); | |
| 306 } | |
| 307 | |
| 308 bool CrasOutputStream::CanTransitionTo(InternalState to) { | |
| 309 switch (state_) { | |
| 310 case kCreated: | |
| 311 return to == kIsOpened || to == kIsClosed || to == kInError; | |
| 312 | |
| 313 case kIsOpened: | |
| 314 return to == kIsPlaying || to == kIsStopped || | |
| 315 to == kIsClosed || to == kInError; | |
| 316 | |
| 317 case kIsPlaying: | |
| 318 return to == kIsPlaying || to == kIsStopped || | |
| 319 to == kIsClosed || to == kInError; | |
| 320 | |
| 321 case kIsStopped: | |
| 322 return to == kIsPlaying || to == kIsStopped || | |
| 323 to == kIsClosed || to == kInError; | |
| 324 | |
| 325 case kInError: | |
| 326 return to == kIsClosed || to == kInError; | |
| 327 | |
| 328 case kIsClosed: | |
| 329 return false; | |
| 330 } | |
| 331 return false; | |
| 332 } | |
| 333 | |
| 334 CrasOutputStream::InternalState | |
| 335 CrasOutputStream::TransitionTo(InternalState to) { | |
| 336 if (!CanTransitionTo(to)) { | |
| 337 state_ = kInError; | |
| 338 } else { | |
| 339 state_ = to; | |
| 340 } | |
| 341 return state_; | |
| 342 } | |
| 343 | |
| 344 CrasOutputStream::InternalState CrasOutputStream::state() { | |
| 345 return state_; | |
| 346 } | |
| 347 | |
| 348 } // namespace media | |
| OLD | NEW |