| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/audio/sounds/audio_stream_handler.h" |
| |
| #include <string> |
| |
| #include "base/cancelable_callback.h" |
| #include "base/logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/lock.h" |
| #include "base/time/time.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/audio_manager_base.h" |
| #include "media/base/channel_layout.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Volume percent. |
| const double kOutputVolumePercent = 0.8; |
| |
| // The number of frames each OnMoreData() call will request. |
| const int kDefaultFrameCount = 1024; |
| |
| // Keep alive timeout for audio stream. |
| const int kKeepAliveMs = 1500; |
| |
| AudioStreamHandler::TestObserver* g_observer_for_testing = NULL; |
| AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL; |
| |
| } // namespace |
| |
| class AudioStreamHandler::AudioStreamContainer |
| : public AudioOutputStream::AudioSourceCallback { |
| public: |
| AudioStreamContainer(const WavAudioHandler& wav_audio) |
| : started_(false), |
| stream_(NULL), |
| cursor_(0), |
| delayed_stop_posted_(false), |
| wav_audio_(wav_audio) {} |
| |
| virtual ~AudioStreamContainer() { |
| DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); |
| } |
| |
| void Play() { |
| DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); |
| |
| if (!stream_) { |
| const AudioParameters& p = wav_audio_.params(); |
| const AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| p.channel_layout(), |
| p.sample_rate(), |
| p.bits_per_sample(), |
| kDefaultFrameCount); |
| stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy(params, |
| std::string()); |
| if (!stream_ || !stream_->Open()) { |
| LOG(ERROR) << "Failed to open an output stream."; |
| return; |
| } |
| stream_->SetVolume(kOutputVolumePercent); |
| } |
| |
| { |
| base::AutoLock al(state_lock_); |
| |
| delayed_stop_posted_ = false; |
| stop_closure_.Reset(base::Bind(&AudioStreamContainer::StopStream, |
| base::Unretained(this))); |
| |
| if (started_) { |
| if (wav_audio_.AtEnd(cursor_)) |
| cursor_ = 0; |
| return; |
| } |
| |
| cursor_ = 0; |
| } |
| |
| started_ = true; |
| if (g_audio_source_for_testing) |
| stream_->Start(g_audio_source_for_testing); |
| else |
| stream_->Start(this); |
| |
| if (g_observer_for_testing) |
| g_observer_for_testing->OnPlay(); |
| } |
| |
| void Stop() { |
| DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); |
| StopStream(); |
| if (stream_) |
| stream_->Close(); |
| stream_ = NULL; |
| stop_closure_.Cancel(); |
| } |
| |
| private: |
| // AudioOutputStream::AudioSourceCallback overrides: |
| // Following methods could be called from *ANY* thread. |
| virtual int OnMoreData(AudioBus* dest, |
| AudioBuffersState /* state */) OVERRIDE { |
| base::AutoLock al(state_lock_); |
| size_t bytes_written = 0; |
| |
| if (wav_audio_.AtEnd(cursor_) || |
| !wav_audio_.CopyTo(dest, cursor_, &bytes_written)) { |
| if (delayed_stop_posted_) |
| return 0; |
| delayed_stop_posted_ = true; |
| AudioManager::Get()->GetTaskRunner()->PostDelayedTask( |
| FROM_HERE, |
| stop_closure_.callback(), |
| base::TimeDelta::FromMilliseconds(kKeepAliveMs)); |
| return 0; |
| } |
| cursor_ += bytes_written; |
| return dest->frames(); |
| } |
| |
| virtual void OnError(AudioOutputStream* /* stream */) OVERRIDE { |
| LOG(ERROR) << "Error during system sound reproduction."; |
| } |
| |
| void StopStream() { |
| DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); |
| |
| if (stream_ && started_) { |
| // Do not hold the |state_lock_| while stopping the output stream. |
| stream_->Stop(); |
| if (g_observer_for_testing) |
| g_observer_for_testing->OnStop(cursor_); |
| } |
| |
| started_ = false; |
| } |
| |
| // Must only be accessed on the AudioManager::GetTaskRunner() thread. |
| bool started_; |
| AudioOutputStream* stream_; |
| |
| // All variables below must be accessed under |state_lock_| when |started_|. |
| base::Lock state_lock_; |
| size_t cursor_; |
| bool delayed_stop_posted_; |
| const WavAudioHandler wav_audio_; |
| base::CancelableClosure stop_closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AudioStreamContainer); |
| }; |
| |
| AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data) |
| : wav_audio_(wav_data), |
| initialized_(false) { |
| AudioManager* manager = AudioManager::Get(); |
| if (!manager) { |
| LOG(ERROR) << "Can't get access to audio manager."; |
| return; |
| } |
| if (!wav_audio_.params().IsValid()) { |
| LOG(ERROR) << "Audio params are invalid."; |
| return; |
| } |
| stream_.reset(new AudioStreamContainer(wav_audio_)); |
| initialized_ = true; |
| } |
| |
| AudioStreamHandler::~AudioStreamHandler() { |
| DCHECK(CalledOnValidThread()); |
| AudioManager::Get()->GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&AudioStreamContainer::Stop, base::Unretained(stream_.get()))); |
| AudioManager::Get()->GetTaskRunner()->DeleteSoon(FROM_HERE, |
| stream_.release()); |
| } |
| |
| bool AudioStreamHandler::IsInitialized() const { |
| DCHECK(CalledOnValidThread()); |
| return initialized_; |
| } |
| |
| bool AudioStreamHandler::Play() { |
| DCHECK(CalledOnValidThread()); |
| |
| if (!IsInitialized()) |
| return false; |
| |
| AudioManager::Get()->GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&AudioStreamContainer::Play), |
| base::Unretained(stream_.get()))); |
| return true; |
| } |
| |
| void AudioStreamHandler::Stop() { |
| DCHECK(CalledOnValidThread()); |
| AudioManager::Get()->GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&AudioStreamContainer::Stop, base::Unretained(stream_.get()))); |
| } |
| |
| // static |
| void AudioStreamHandler::SetObserverForTesting(TestObserver* observer) { |
| g_observer_for_testing = observer; |
| } |
| |
| // static |
| void AudioStreamHandler::SetAudioSourceForTesting( |
| AudioOutputStream::AudioSourceCallback* source) { |
| g_audio_source_for_testing = source; |
| } |
| |
| } // namespace media |