OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // THREAD SAFETY | 5 // THREAD SAFETY |
6 // | 6 // |
7 // AlsaPcmOutputStream object is *not* thread-safe and should only be used | 7 // AlsaPcmOutputStream object is *not* thread-safe and should only be used |
8 // from the audio thread. We DCHECK on this assumption whenever we can. | 8 // from the audio thread. We DCHECK on this assumption whenever we can. |
9 // | 9 // |
10 // SEMANTICS OF Close() | 10 // SEMANTICS OF Close() |
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
199 alsa_buffer_frames_(0), | 199 alsa_buffer_frames_(0), |
200 stop_stream_(false), | 200 stop_stream_(false), |
201 wrapper_(wrapper), | 201 wrapper_(wrapper), |
202 manager_(manager), | 202 manager_(manager), |
203 playback_handle_(NULL), | 203 playback_handle_(NULL), |
204 frames_per_packet_(packet_size_ / bytes_per_frame_), | 204 frames_per_packet_(packet_size_ / bytes_per_frame_), |
205 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | 205 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
206 state_(kCreated), | 206 state_(kCreated), |
207 volume_(1.0f), | 207 volume_(1.0f), |
208 source_callback_(NULL) { | 208 source_callback_(NULL) { |
209 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 209 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
210 | 210 |
211 // Sanity check input values. | 211 // Sanity check input values. |
212 if ((params.sample_rate > kAlsaMaxSampleRate) || (params.sample_rate <= 0)) { | 212 if ((params.sample_rate > kAlsaMaxSampleRate) || (params.sample_rate <= 0)) { |
213 LOG(WARNING) << "Unsupported audio frequency."; | 213 LOG(WARNING) << "Unsupported audio frequency."; |
214 TransitionTo(kInError); | 214 TransitionTo(kInError); |
215 } | 215 } |
216 | 216 |
217 if (AudioParameters::AUDIO_PCM_LINEAR != params.format && | 217 if (AudioParameters::AUDIO_PCM_LINEAR != params.format && |
218 AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format) { | 218 AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format) { |
219 LOG(WARNING) << "Unsupported audio format"; | 219 LOG(WARNING) << "Unsupported audio format"; |
220 TransitionTo(kInError); | 220 TransitionTo(kInError); |
221 } | 221 } |
222 | 222 |
223 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { | 223 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { |
224 LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample; | 224 LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample; |
225 TransitionTo(kInError); | 225 TransitionTo(kInError); |
226 } | 226 } |
227 } | 227 } |
228 | 228 |
229 AlsaPcmOutputStream::~AlsaPcmOutputStream() { | 229 AlsaPcmOutputStream::~AlsaPcmOutputStream() { |
230 InternalState current_state = state(); | 230 InternalState current_state = state(); |
231 DCHECK(current_state == kCreated || | 231 DCHECK(current_state == kCreated || |
232 current_state == kIsClosed || | 232 current_state == kIsClosed || |
233 current_state == kInError); | 233 current_state == kInError); |
234 DCHECK(!playback_handle_); | 234 DCHECK(!playback_handle_); |
235 } | 235 } |
236 | 236 |
237 bool AlsaPcmOutputStream::Open() { | 237 bool AlsaPcmOutputStream::Open() { |
238 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 238 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
239 | 239 |
240 if (state() == kInError) | 240 if (state() == kInError) |
241 return false; | 241 return false; |
242 | 242 |
243 if (!CanTransitionTo(kIsOpened)) { | 243 if (!CanTransitionTo(kIsOpened)) { |
244 NOTREACHED() << "Invalid state: " << state(); | 244 NOTREACHED() << "Invalid state: " << state(); |
245 return false; | 245 return false; |
246 } | 246 } |
247 | 247 |
248 // We do not need to check if the transition was successful because | 248 // We do not need to check if the transition was successful because |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
286 alsa_buffer_frames_ = frames_per_packet_ * 2; | 286 alsa_buffer_frames_ = frames_per_packet_ * 2; |
287 } else { | 287 } else { |
288 alsa_buffer_frames_ = buffer_size; | 288 alsa_buffer_frames_ = buffer_size; |
289 } | 289 } |
290 } | 290 } |
291 | 291 |
292 return true; | 292 return true; |
293 } | 293 } |
294 | 294 |
295 void AlsaPcmOutputStream::Close() { | 295 void AlsaPcmOutputStream::Close() { |
296 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 296 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
297 | 297 |
298 // Sanity check that the transition occurs correctly. It is safe to | 298 // Sanity check that the transition occurs correctly. It is safe to |
299 // continue anyways because all operations for closing are idempotent. | 299 // continue anyways because all operations for closing are idempotent. |
300 if (TransitionTo(kIsClosed) != kIsClosed) { | 300 if (TransitionTo(kIsClosed) != kIsClosed) { |
301 NOTREACHED() << "Unable to transition Closed."; | 301 NOTREACHED() << "Unable to transition Closed."; |
302 } else { | 302 } else { |
303 // Shutdown the audio device. | 303 // Shutdown the audio device. |
304 if (playback_handle_ && | 304 if (playback_handle_ && |
305 alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { | 305 alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { |
306 LOG(WARNING) << "Unable to close audio device. Leaking handle."; | 306 LOG(WARNING) << "Unable to close audio device. Leaking handle."; |
307 } | 307 } |
308 playback_handle_ = NULL; | 308 playback_handle_ = NULL; |
309 | 309 |
310 // Release the buffer. | 310 // Release the buffer. |
311 buffer_.reset(); | 311 buffer_.reset(); |
312 | 312 |
313 // Signal anything that might already be scheduled to stop. | 313 // Signal anything that might already be scheduled to stop. |
314 stop_stream_ = true; // Not necessary in production, but unit tests | 314 stop_stream_ = true; // Not necessary in production, but unit tests |
315 // uses the flag to verify that stream was closed. | 315 // uses the flag to verify that stream was closed. |
316 weak_factory_.InvalidateWeakPtrs(); | 316 weak_factory_.InvalidateWeakPtrs(); |
317 | 317 |
318 // Signal to the manager that we're closed and can be removed. | 318 // Signal to the manager that we're closed and can be removed. |
319 // Should be last call in the method as it deletes "this". | 319 // Should be last call in the method as it deletes "this". |
320 manager_->ReleaseOutputStream(this); | 320 manager_->ReleaseOutputStream(this); |
321 } | 321 } |
322 } | 322 } |
323 | 323 |
324 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { | 324 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { |
325 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 325 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
326 | 326 |
327 CHECK(callback); | 327 CHECK(callback); |
328 | 328 |
329 if (stop_stream_) | 329 if (stop_stream_) |
330 return; | 330 return; |
331 | 331 |
332 set_source_callback(callback); | 332 set_source_callback(callback); |
333 | 333 |
334 // Only post the task if we can enter the playing state. | 334 // Only post the task if we can enter the playing state. |
335 if (TransitionTo(kIsPlaying) == kIsPlaying) { | 335 if (TransitionTo(kIsPlaying) == kIsPlaying) { |
(...skipping 18 matching lines...) Expand all Loading... |
354 stop_stream_ = true; | 354 stop_stream_ = true; |
355 } | 355 } |
356 } | 356 } |
357 | 357 |
358 if (!stop_stream_) | 358 if (!stop_stream_) |
359 ScheduleNextWrite(false); | 359 ScheduleNextWrite(false); |
360 } | 360 } |
361 } | 361 } |
362 | 362 |
363 void AlsaPcmOutputStream::Stop() { | 363 void AlsaPcmOutputStream::Stop() { |
364 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 364 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
365 | 365 |
366 // Reset the callback, so that it is not called anymore. | 366 // Reset the callback, so that it is not called anymore. |
367 set_source_callback(NULL); | 367 set_source_callback(NULL); |
368 | 368 |
369 TransitionTo(kIsStopped); | 369 TransitionTo(kIsStopped); |
370 } | 370 } |
371 | 371 |
372 void AlsaPcmOutputStream::SetVolume(double volume) { | 372 void AlsaPcmOutputStream::SetVolume(double volume) { |
373 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 373 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
374 | 374 |
375 volume_ = static_cast<float>(volume); | 375 volume_ = static_cast<float>(volume); |
376 } | 376 } |
377 | 377 |
378 void AlsaPcmOutputStream::GetVolume(double* volume) { | 378 void AlsaPcmOutputStream::GetVolume(double* volume) { |
379 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 379 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
380 | 380 |
381 *volume = volume_; | 381 *volume = volume_; |
382 } | 382 } |
383 | 383 |
384 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { | 384 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { |
385 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 385 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
386 | 386 |
387 // If stopped, simulate a 0-length packet. | 387 // If stopped, simulate a 0-length packet. |
388 if (stop_stream_) { | 388 if (stop_stream_) { |
389 buffer_->Clear(); | 389 buffer_->Clear(); |
390 *source_exhausted = true; | 390 *source_exhausted = true; |
391 return; | 391 return; |
392 } | 392 } |
393 | 393 |
394 *source_exhausted = false; | 394 *source_exhausted = false; |
395 | 395 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 packet->SetDataSize(packet_size); | 466 packet->SetDataSize(packet_size); |
467 // Add the packet to the buffer. | 467 // Add the packet to the buffer. |
468 buffer_->Append(packet); | 468 buffer_->Append(packet); |
469 } else { | 469 } else { |
470 *source_exhausted = true; | 470 *source_exhausted = true; |
471 } | 471 } |
472 } | 472 } |
473 } | 473 } |
474 | 474 |
475 void AlsaPcmOutputStream::WritePacket() { | 475 void AlsaPcmOutputStream::WritePacket() { |
476 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 476 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
477 | 477 |
478 // If the device is in error, just eat the bytes. | 478 // If the device is in error, just eat the bytes. |
479 if (stop_stream_) { | 479 if (stop_stream_) { |
480 buffer_->Clear(); | 480 buffer_->Clear(); |
481 return; | 481 return; |
482 } | 482 } |
483 | 483 |
484 if (state() == kIsStopped) | 484 if (state() == kIsStopped) |
485 return; | 485 return; |
486 | 486 |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
530 // This ensures that shorter sounds will still play. | 530 // This ensures that shorter sounds will still play. |
531 if (playback_handle_ && | 531 if (playback_handle_ && |
532 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && | 532 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && |
533 GetCurrentDelay() > 0) { | 533 GetCurrentDelay() > 0) { |
534 wrapper_->PcmStart(playback_handle_); | 534 wrapper_->PcmStart(playback_handle_); |
535 } | 535 } |
536 } | 536 } |
537 } | 537 } |
538 | 538 |
539 void AlsaPcmOutputStream::WriteTask() { | 539 void AlsaPcmOutputStream::WriteTask() { |
540 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 540 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
541 | 541 |
542 if (stop_stream_) | 542 if (stop_stream_) |
543 return; | 543 return; |
544 | 544 |
545 if (state() == kIsStopped) | 545 if (state() == kIsStopped) |
546 return; | 546 return; |
547 | 547 |
548 bool source_exhausted; | 548 bool source_exhausted; |
549 BufferPacket(&source_exhausted); | 549 BufferPacket(&source_exhausted); |
550 WritePacket(); | 550 WritePacket(); |
551 | 551 |
552 ScheduleNextWrite(source_exhausted); | 552 ScheduleNextWrite(source_exhausted); |
553 } | 553 } |
554 | 554 |
555 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { | 555 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { |
556 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 556 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
557 | 557 |
558 if (stop_stream_) | 558 if (stop_stream_) |
559 return; | 559 return; |
560 | 560 |
561 uint32 frames_avail_wanted = alsa_buffer_frames_ / 2; | 561 uint32 frames_avail_wanted = alsa_buffer_frames_ / 2; |
562 uint32 available_frames = GetAvailableFrames(); | 562 uint32 available_frames = GetAvailableFrames(); |
563 uint32 frames_in_buffer = buffer_->forward_bytes() / bytes_per_output_frame_; | 563 uint32 frames_in_buffer = buffer_->forward_bytes() / bytes_per_output_frame_; |
564 | 564 |
565 // Next write is initially scheduled for the moment when half of a packet | 565 // Next write is initially scheduled for the moment when half of a packet |
566 // has been played out. | 566 // has been played out. |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
600 if (state() == kIsPlaying) { | 600 if (state() == kIsPlaying) { |
601 if (next_fill_time_ms == 0) { | 601 if (next_fill_time_ms == 0) { |
602 manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind( | 602 manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind( |
603 &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr())); | 603 &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr())); |
604 } else { | 604 } else { |
605 // TODO(ajwong): Measure the reliability of the delay interval. Use | 605 // TODO(ajwong): Measure the reliability of the delay interval. Use |
606 // base/metrics/histogram.h. | 606 // base/metrics/histogram.h. |
607 manager_->GetMessageLoop()->PostDelayedTask(FROM_HERE, | 607 manager_->GetMessageLoop()->PostDelayedTask(FROM_HERE, |
608 base::Bind(&AlsaPcmOutputStream::WriteTask, | 608 base::Bind(&AlsaPcmOutputStream::WriteTask, |
609 weak_factory_.GetWeakPtr()), | 609 weak_factory_.GetWeakPtr()), |
610 base::TimeDelta::FromMilliseconds(next_fill_time_ms)); | 610 next_fill_time_ms); |
611 } | 611 } |
612 } | 612 } |
613 } | 613 } |
614 | 614 |
615 uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames, uint32 sample_rate) { | 615 uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames, uint32 sample_rate) { |
616 return frames * base::Time::kMicrosecondsPerSecond / sample_rate; | 616 return frames * base::Time::kMicrosecondsPerSecond / sample_rate; |
617 } | 617 } |
618 | 618 |
619 uint32 AlsaPcmOutputStream::FramesToMillis(uint32 frames, uint32 sample_rate) { | 619 uint32 AlsaPcmOutputStream::FramesToMillis(uint32 frames, uint32 sample_rate) { |
620 return frames * base::Time::kMillisecondsPerSecond / sample_rate; | 620 return frames * base::Time::kMillisecondsPerSecond / sample_rate; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
688 | 688 |
689 // snd_pcm_delay() may not work in the beginning of the stream. In this case | 689 // snd_pcm_delay() may not work in the beginning of the stream. In this case |
690 // return delay of data we know currently is in the ALSA's buffer. | 690 // return delay of data we know currently is in the ALSA's buffer. |
691 if (delay < 0) | 691 if (delay < 0) |
692 delay = alsa_buffer_frames_ - GetAvailableFrames(); | 692 delay = alsa_buffer_frames_ - GetAvailableFrames(); |
693 | 693 |
694 return delay; | 694 return delay; |
695 } | 695 } |
696 | 696 |
697 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { | 697 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { |
698 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 698 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
699 | 699 |
700 if (stop_stream_) | 700 if (stop_stream_) |
701 return 0; | 701 return 0; |
702 | 702 |
703 // Find the number of frames queued in the sound device. | 703 // Find the number of frames queued in the sound device. |
704 snd_pcm_sframes_t available_frames = | 704 snd_pcm_sframes_t available_frames = |
705 wrapper_->PcmAvailUpdate(playback_handle_); | 705 wrapper_->PcmAvailUpdate(playback_handle_); |
706 if (available_frames < 0) { | 706 if (available_frames < 0) { |
707 available_frames = wrapper_->PcmRecover(playback_handle_, | 707 available_frames = wrapper_->PcmRecover(playback_handle_, |
708 available_frames, | 708 available_frames, |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
802 return to == kIsClosed || to == kInError; | 802 return to == kIsClosed || to == kInError; |
803 | 803 |
804 case kIsClosed: | 804 case kIsClosed: |
805 default: | 805 default: |
806 return false; | 806 return false; |
807 } | 807 } |
808 } | 808 } |
809 | 809 |
810 AlsaPcmOutputStream::InternalState | 810 AlsaPcmOutputStream::InternalState |
811 AlsaPcmOutputStream::TransitionTo(InternalState to) { | 811 AlsaPcmOutputStream::TransitionTo(InternalState to) { |
812 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 812 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
813 | 813 |
814 if (!CanTransitionTo(to)) { | 814 if (!CanTransitionTo(to)) { |
815 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; | 815 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; |
816 state_ = kInError; | 816 state_ = kInError; |
817 } else { | 817 } else { |
818 state_ = to; | 818 state_ = to; |
819 } | 819 } |
820 return state_; | 820 return state_; |
821 } | 821 } |
822 | 822 |
(...skipping 13 matching lines...) Expand all Loading... |
836 } | 836 } |
837 | 837 |
838 void AlsaPcmOutputStream::RunErrorCallback(int code) { | 838 void AlsaPcmOutputStream::RunErrorCallback(int code) { |
839 if (source_callback_) | 839 if (source_callback_) |
840 source_callback_->OnError(this, code); | 840 source_callback_->OnError(this, code); |
841 } | 841 } |
842 | 842 |
843 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to | 843 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to |
844 // release ownership of the currently registered callback. | 844 // release ownership of the currently registered callback. |
845 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { | 845 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { |
846 DCHECK_EQ(MessageLoop::current(), manager_->GetMessageLoop()); | 846 DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); |
847 source_callback_ = callback; | 847 source_callback_ = callback; |
848 } | 848 } |
OLD | NEW |