OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 24 matching lines...) Expand all Loading... |
35 #include "media/audio/alsa/alsa_output.h" | 35 #include "media/audio/alsa/alsa_output.h" |
36 | 36 |
37 #include <stddef.h> | 37 #include <stddef.h> |
38 | 38 |
39 #include <algorithm> | 39 #include <algorithm> |
40 | 40 |
41 #include "base/bind.h" | 41 #include "base/bind.h" |
42 #include "base/logging.h" | 42 #include "base/logging.h" |
43 #include "base/memory/free_deleter.h" | 43 #include "base/memory/free_deleter.h" |
44 #include "base/stl_util.h" | 44 #include "base/stl_util.h" |
| 45 #include "base/threading/thread_task_runner_handle.h" |
45 #include "base/trace_event/trace_event.h" | 46 #include "base/trace_event/trace_event.h" |
46 #include "media/audio/alsa/alsa_util.h" | 47 #include "media/audio/alsa/alsa_util.h" |
47 #include "media/audio/alsa/alsa_wrapper.h" | 48 #include "media/audio/alsa/alsa_wrapper.h" |
48 #include "media/audio/alsa/audio_manager_alsa.h" | 49 #include "media/audio/alsa/audio_manager_alsa.h" |
49 #include "media/base/channel_mixer.h" | 50 #include "media/base/channel_mixer.h" |
50 #include "media/base/data_buffer.h" | 51 #include "media/base/data_buffer.h" |
51 #include "media/base/seekable_buffer.h" | 52 #include "media/base/seekable_buffer.h" |
52 | 53 |
53 namespace media { | 54 namespace media { |
54 | 55 |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 bytes_per_frame_(params.GetBytesPerFrame()), | 150 bytes_per_frame_(params.GetBytesPerFrame()), |
150 packet_size_(params.GetBytesPerBuffer()), | 151 packet_size_(params.GetBytesPerBuffer()), |
151 latency_(std::max( | 152 latency_(std::max( |
152 base::TimeDelta::FromMicroseconds(kMinLatencyMicros), | 153 base::TimeDelta::FromMicroseconds(kMinLatencyMicros), |
153 FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), | 154 FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), |
154 bytes_per_output_frame_(bytes_per_frame_), | 155 bytes_per_output_frame_(bytes_per_frame_), |
155 alsa_buffer_frames_(0), | 156 alsa_buffer_frames_(0), |
156 stop_stream_(false), | 157 stop_stream_(false), |
157 wrapper_(wrapper), | 158 wrapper_(wrapper), |
158 manager_(manager), | 159 manager_(manager), |
159 message_loop_(base::MessageLoop::current()), | 160 task_runner_(base::ThreadTaskRunnerHandle::Get()), |
160 playback_handle_(NULL), | 161 playback_handle_(NULL), |
161 frames_per_packet_(packet_size_ / bytes_per_frame_), | 162 frames_per_packet_(packet_size_ / bytes_per_frame_), |
162 state_(kCreated), | 163 state_(kCreated), |
163 volume_(1.0f), | 164 volume_(1.0f), |
164 source_callback_(NULL), | 165 source_callback_(NULL), |
165 audio_bus_(AudioBus::Create(params)), | 166 audio_bus_(AudioBus::Create(params)), |
166 weak_factory_(this) { | 167 weak_factory_(this) { |
167 DCHECK(manager_->GetTaskRunner()->BelongsToCurrentThread()); | 168 DCHECK(manager_->GetTaskRunner()->BelongsToCurrentThread()); |
168 DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); | 169 DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); |
169 | 170 |
(...skipping 11 matching lines...) Expand all Loading... |
181 | 182 |
182 AlsaPcmOutputStream::~AlsaPcmOutputStream() { | 183 AlsaPcmOutputStream::~AlsaPcmOutputStream() { |
183 InternalState current_state = state(); | 184 InternalState current_state = state(); |
184 DCHECK(current_state == kCreated || | 185 DCHECK(current_state == kCreated || |
185 current_state == kIsClosed || | 186 current_state == kIsClosed || |
186 current_state == kInError); | 187 current_state == kInError); |
187 DCHECK(!playback_handle_); | 188 DCHECK(!playback_handle_); |
188 } | 189 } |
189 | 190 |
190 bool AlsaPcmOutputStream::Open() { | 191 bool AlsaPcmOutputStream::Open() { |
191 DCHECK(IsOnAudioThread()); | 192 DCHECK(CalledOnValidThread()); |
192 | 193 |
193 if (state() == kInError) | 194 if (state() == kInError) |
194 return false; | 195 return false; |
195 | 196 |
196 if (!CanTransitionTo(kIsOpened)) { | 197 if (!CanTransitionTo(kIsOpened)) { |
197 NOTREACHED() << "Invalid state: " << state(); | 198 NOTREACHED() << "Invalid state: " << state(); |
198 return false; | 199 return false; |
199 } | 200 } |
200 | 201 |
201 // We do not need to check if the transition was successful because | 202 // 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... |
239 // Buffer size is at least twice of packet size. | 240 // Buffer size is at least twice of packet size. |
240 alsa_buffer_frames_ = frames_per_packet_ * 2; | 241 alsa_buffer_frames_ = frames_per_packet_ * 2; |
241 } else { | 242 } else { |
242 alsa_buffer_frames_ = buffer_size; | 243 alsa_buffer_frames_ = buffer_size; |
243 } | 244 } |
244 | 245 |
245 return true; | 246 return true; |
246 } | 247 } |
247 | 248 |
248 void AlsaPcmOutputStream::Close() { | 249 void AlsaPcmOutputStream::Close() { |
249 DCHECK(IsOnAudioThread()); | 250 DCHECK(CalledOnValidThread()); |
250 | 251 |
251 if (state() != kIsClosed) | 252 if (state() != kIsClosed) |
252 TransitionTo(kIsClosed); | 253 TransitionTo(kIsClosed); |
253 | 254 |
254 // Shutdown the audio device. | 255 // Shutdown the audio device. |
255 if (playback_handle_) { | 256 if (playback_handle_) { |
256 if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { | 257 if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { |
257 LOG(WARNING) << "Unable to close audio device. Leaking handle."; | 258 LOG(WARNING) << "Unable to close audio device. Leaking handle."; |
258 } | 259 } |
259 playback_handle_ = NULL; | 260 playback_handle_ = NULL; |
260 | 261 |
261 // Release the buffer. | 262 // Release the buffer. |
262 buffer_.reset(); | 263 buffer_.reset(); |
263 | 264 |
264 // Signal anything that might already be scheduled to stop. | 265 // Signal anything that might already be scheduled to stop. |
265 stop_stream_ = true; // Not necessary in production, but unit tests | 266 stop_stream_ = true; // Not necessary in production, but unit tests |
266 // uses the flag to verify that stream was closed. | 267 // uses the flag to verify that stream was closed. |
267 } | 268 } |
268 | 269 |
269 weak_factory_.InvalidateWeakPtrs(); | 270 weak_factory_.InvalidateWeakPtrs(); |
270 | 271 |
271 // Signal to the manager that we're closed and can be removed. | 272 // Signal to the manager that we're closed and can be removed. |
272 // Should be last call in the method as it deletes "this". | 273 // Should be last call in the method as it deletes "this". |
273 manager_->ReleaseOutputStream(this); | 274 manager_->ReleaseOutputStream(this); |
274 } | 275 } |
275 | 276 |
276 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { | 277 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { |
277 DCHECK(IsOnAudioThread()); | 278 DCHECK(CalledOnValidThread()); |
278 | 279 |
279 CHECK(callback); | 280 CHECK(callback); |
280 | 281 |
281 if (stop_stream_) | 282 if (stop_stream_) |
282 return; | 283 return; |
283 | 284 |
284 // Only post the task if we can enter the playing state. | 285 // Only post the task if we can enter the playing state. |
285 if (TransitionTo(kIsPlaying) != kIsPlaying) | 286 if (TransitionTo(kIsPlaying) != kIsPlaying) |
286 return; | 287 return; |
287 | 288 |
(...skipping 28 matching lines...) Expand all Loading... |
316 memset(silent_packet->writable_data(), 0, silent_packet->data_size()); | 317 memset(silent_packet->writable_data(), 0, silent_packet->data_size()); |
317 buffer_->Append(silent_packet); | 318 buffer_->Append(silent_packet); |
318 WritePacket(); | 319 WritePacket(); |
319 | 320 |
320 // Start the callback chain. | 321 // Start the callback chain. |
321 set_source_callback(callback); | 322 set_source_callback(callback); |
322 WriteTask(); | 323 WriteTask(); |
323 } | 324 } |
324 | 325 |
325 void AlsaPcmOutputStream::Stop() { | 326 void AlsaPcmOutputStream::Stop() { |
326 DCHECK(IsOnAudioThread()); | 327 DCHECK(CalledOnValidThread()); |
327 | 328 |
328 // Reset the callback, so that it is not called anymore. | 329 // Reset the callback, so that it is not called anymore. |
329 set_source_callback(NULL); | 330 set_source_callback(NULL); |
330 weak_factory_.InvalidateWeakPtrs(); | 331 weak_factory_.InvalidateWeakPtrs(); |
331 | 332 |
332 TransitionTo(kIsStopped); | 333 TransitionTo(kIsStopped); |
333 } | 334 } |
334 | 335 |
335 void AlsaPcmOutputStream::SetVolume(double volume) { | 336 void AlsaPcmOutputStream::SetVolume(double volume) { |
336 DCHECK(IsOnAudioThread()); | 337 DCHECK(CalledOnValidThread()); |
337 | 338 |
338 volume_ = static_cast<float>(volume); | 339 volume_ = static_cast<float>(volume); |
339 } | 340 } |
340 | 341 |
341 void AlsaPcmOutputStream::GetVolume(double* volume) { | 342 void AlsaPcmOutputStream::GetVolume(double* volume) { |
342 DCHECK(IsOnAudioThread()); | 343 DCHECK(CalledOnValidThread()); |
343 | 344 |
344 *volume = volume_; | 345 *volume = volume_; |
345 } | 346 } |
346 | 347 |
347 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { | 348 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { |
348 DCHECK(IsOnAudioThread()); | 349 DCHECK(CalledOnValidThread()); |
349 | 350 |
350 // If stopped, simulate a 0-length packet. | 351 // If stopped, simulate a 0-length packet. |
351 if (stop_stream_) { | 352 if (stop_stream_) { |
352 buffer_->Clear(); | 353 buffer_->Clear(); |
353 *source_exhausted = true; | 354 *source_exhausted = true; |
354 return; | 355 return; |
355 } | 356 } |
356 | 357 |
357 *source_exhausted = false; | 358 *source_exhausted = false; |
358 | 359 |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
412 packet->set_data_size(packet_size); | 413 packet->set_data_size(packet_size); |
413 // Add the packet to the buffer. | 414 // Add the packet to the buffer. |
414 buffer_->Append(packet); | 415 buffer_->Append(packet); |
415 } else { | 416 } else { |
416 *source_exhausted = true; | 417 *source_exhausted = true; |
417 } | 418 } |
418 } | 419 } |
419 } | 420 } |
420 | 421 |
421 void AlsaPcmOutputStream::WritePacket() { | 422 void AlsaPcmOutputStream::WritePacket() { |
422 DCHECK(IsOnAudioThread()); | 423 DCHECK(CalledOnValidThread()); |
423 | 424 |
424 // If the device is in error, just eat the bytes. | 425 // If the device is in error, just eat the bytes. |
425 if (stop_stream_) { | 426 if (stop_stream_) { |
426 buffer_->Clear(); | 427 buffer_->Clear(); |
427 return; | 428 return; |
428 } | 429 } |
429 | 430 |
430 if (state() != kIsPlaying) | 431 if (state() != kIsPlaying) |
431 return; | 432 return; |
432 | 433 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
471 // This ensures that shorter sounds will still play. | 472 // This ensures that shorter sounds will still play. |
472 if (playback_handle_ && | 473 if (playback_handle_ && |
473 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && | 474 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && |
474 GetCurrentDelay() > 0) { | 475 GetCurrentDelay() > 0) { |
475 wrapper_->PcmStart(playback_handle_); | 476 wrapper_->PcmStart(playback_handle_); |
476 } | 477 } |
477 } | 478 } |
478 } | 479 } |
479 | 480 |
480 void AlsaPcmOutputStream::WriteTask() { | 481 void AlsaPcmOutputStream::WriteTask() { |
481 DCHECK(IsOnAudioThread()); | 482 DCHECK(CalledOnValidThread()); |
482 | 483 |
483 if (stop_stream_) | 484 if (stop_stream_) |
484 return; | 485 return; |
485 | 486 |
486 if (state() == kIsStopped) | 487 if (state() == kIsStopped) |
487 return; | 488 return; |
488 | 489 |
489 bool source_exhausted; | 490 bool source_exhausted; |
490 BufferPacket(&source_exhausted); | 491 BufferPacket(&source_exhausted); |
491 WritePacket(); | 492 WritePacket(); |
492 | 493 |
493 ScheduleNextWrite(source_exhausted); | 494 ScheduleNextWrite(source_exhausted); |
494 } | 495 } |
495 | 496 |
496 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { | 497 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { |
497 DCHECK(IsOnAudioThread()); | 498 DCHECK(CalledOnValidThread()); |
498 | 499 |
499 if (stop_stream_ || state() != kIsPlaying) | 500 if (stop_stream_ || state() != kIsPlaying) |
500 return; | 501 return; |
501 | 502 |
502 const uint32_t kTargetFramesAvailable = alsa_buffer_frames_ / 2; | 503 const uint32_t kTargetFramesAvailable = alsa_buffer_frames_ / 2; |
503 uint32_t available_frames = GetAvailableFrames(); | 504 uint32_t available_frames = GetAvailableFrames(); |
504 | 505 |
505 base::TimeDelta next_fill_time; | 506 base::TimeDelta next_fill_time; |
506 if (buffer_->forward_bytes() && available_frames) { | 507 if (buffer_->forward_bytes() && available_frames) { |
507 // If we've got data available and ALSA has room, deliver it immediately. | 508 // If we've got data available and ALSA has room, deliver it immediately. |
(...skipping 12 matching lines...) Expand all Loading... |
520 } else if (!source_exhausted) { | 521 } else if (!source_exhausted) { |
521 // The sound card has |kTargetFramesAvailable| or more frames available. | 522 // The sound card has |kTargetFramesAvailable| or more frames available. |
522 // Invoke the next write immediately to avoid underrun. | 523 // Invoke the next write immediately to avoid underrun. |
523 next_fill_time = base::TimeDelta(); | 524 next_fill_time = base::TimeDelta(); |
524 } else { | 525 } else { |
525 // The sound card has frames available, but our source is exhausted, so | 526 // The sound card has frames available, but our source is exhausted, so |
526 // avoid busy looping by delaying a bit. | 527 // avoid busy looping by delaying a bit. |
527 next_fill_time = base::TimeDelta::FromMilliseconds(10); | 528 next_fill_time = base::TimeDelta::FromMilliseconds(10); |
528 } | 529 } |
529 | 530 |
530 message_loop_->PostDelayedTask(FROM_HERE, base::Bind( | 531 task_runner_->PostDelayedTask( |
531 &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), | 532 FROM_HERE, |
| 533 base::Bind(&AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), |
532 next_fill_time); | 534 next_fill_time); |
533 } | 535 } |
534 | 536 |
535 // static | 537 // static |
536 base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, | 538 base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, |
537 double sample_rate) { | 539 double sample_rate) { |
538 return base::TimeDelta::FromMicroseconds( | 540 return base::TimeDelta::FromMicroseconds( |
539 frames * base::Time::kMicrosecondsPerSecond / sample_rate); | 541 frames * base::Time::kMicrosecondsPerSecond / sample_rate); |
540 } | 542 } |
541 | 543 |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
620 } | 622 } |
621 | 623 |
622 if (delay < 0) { | 624 if (delay < 0) { |
623 delay = 0; | 625 delay = 0; |
624 } | 626 } |
625 | 627 |
626 return delay; | 628 return delay; |
627 } | 629 } |
628 | 630 |
629 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { | 631 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { |
630 DCHECK(IsOnAudioThread()); | 632 DCHECK(CalledOnValidThread()); |
631 | 633 |
632 if (stop_stream_) | 634 if (stop_stream_) |
633 return 0; | 635 return 0; |
634 | 636 |
635 // Find the number of frames queued in the sound device. | 637 // Find the number of frames queued in the sound device. |
636 snd_pcm_sframes_t available_frames = | 638 snd_pcm_sframes_t available_frames = |
637 wrapper_->PcmAvailUpdate(playback_handle_); | 639 wrapper_->PcmAvailUpdate(playback_handle_); |
638 if (available_frames < 0) { | 640 if (available_frames < 0) { |
639 available_frames = wrapper_->PcmRecover(playback_handle_, | 641 available_frames = wrapper_->PcmRecover(playback_handle_, |
640 available_frames, | 642 available_frames, |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
753 return to == kIsClosed || to == kInError; | 755 return to == kIsClosed || to == kInError; |
754 | 756 |
755 case kIsClosed: | 757 case kIsClosed: |
756 default: | 758 default: |
757 return false; | 759 return false; |
758 } | 760 } |
759 } | 761 } |
760 | 762 |
761 AlsaPcmOutputStream::InternalState | 763 AlsaPcmOutputStream::InternalState |
762 AlsaPcmOutputStream::TransitionTo(InternalState to) { | 764 AlsaPcmOutputStream::TransitionTo(InternalState to) { |
763 DCHECK(IsOnAudioThread()); | 765 DCHECK(CalledOnValidThread()); |
764 | 766 |
765 if (!CanTransitionTo(to)) { | 767 if (!CanTransitionTo(to)) { |
766 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; | 768 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; |
767 state_ = kInError; | 769 state_ = kInError; |
768 } else { | 770 } else { |
769 state_ = to; | 771 state_ = to; |
770 } | 772 } |
771 return state_; | 773 return state_; |
772 } | 774 } |
773 | 775 |
774 AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { | 776 AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { |
775 return state_; | 777 return state_; |
776 } | 778 } |
777 | 779 |
778 bool AlsaPcmOutputStream::IsOnAudioThread() const { | |
779 return message_loop_ && message_loop_ == base::MessageLoop::current(); | |
780 } | |
781 | |
782 int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, | 780 int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, |
783 uint32_t total_bytes_delay) { | 781 uint32_t total_bytes_delay) { |
784 TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); | 782 TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); |
785 | 783 |
786 if (source_callback_) | 784 if (source_callback_) |
787 return source_callback_->OnMoreData(audio_bus, total_bytes_delay, 0); | 785 return source_callback_->OnMoreData(audio_bus, total_bytes_delay, 0); |
788 | 786 |
789 return 0; | 787 return 0; |
790 } | 788 } |
791 | 789 |
792 void AlsaPcmOutputStream::RunErrorCallback(int code) { | 790 void AlsaPcmOutputStream::RunErrorCallback(int code) { |
793 if (source_callback_) | 791 if (source_callback_) |
794 source_callback_->OnError(this); | 792 source_callback_->OnError(this); |
795 } | 793 } |
796 | 794 |
797 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to | 795 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to |
798 // release ownership of the currently registered callback. | 796 // release ownership of the currently registered callback. |
799 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { | 797 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { |
800 DCHECK(IsOnAudioThread()); | 798 DCHECK(CalledOnValidThread()); |
801 source_callback_ = callback; | 799 source_callback_ = callback; |
802 } | 800 } |
803 | 801 |
804 } // namespace media | 802 } // namespace media |
OLD | NEW |