Chromium Code Reviews| Index: media/audio/sounds/audio_stream_handler.cc |
| diff --git a/media/audio/sounds/audio_stream_handler.cc b/media/audio/sounds/audio_stream_handler.cc |
| index 08608ac4187aa0863b777e5e42701f2b7a327f69..08dbdcd89162e4c76add16b95e14b1c951ac2878 100644 |
| --- a/media/audio/sounds/audio_stream_handler.cc |
| +++ b/media/audio/sounds/audio_stream_handler.cc |
| @@ -6,8 +6,10 @@ |
| #include <string> |
| +#include "base/cancelable_callback.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop_proxy.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" |
| @@ -19,8 +21,8 @@ namespace { |
| // Volume percent. |
| const double kOutputVolumePercent = 0.8; |
| -// The number of frames each OnMoreData() call will request. |
| -const int kDefaultFrameCount = 1024; |
| +// Delay between end of sound and call to AudioOutputStream::Stop(). |
| +const int kStopStreamDelaySec = 3; |
| AudioStreamHandler::TestObserver* g_observer_for_testing = NULL; |
| AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL; |
| @@ -30,36 +32,52 @@ AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL; |
| class AudioStreamHandler::AudioStreamContainer |
| : public AudioOutputStream::AudioSourceCallback { |
| public: |
| - AudioStreamContainer(const WavAudioHandler& wav_audio, |
| - const AudioParameters& params) |
| + AudioStreamContainer(const WavAudioHandler& wav_audio) |
| : stream_(NULL), |
| wav_audio_(wav_audio), |
| - params_(params), |
| - cursor_(0) { |
| - } |
| + cursor_(0), |
| + replay_scheduled_(false), |
|
DaleCurtis
2013/12/18 21:20:21
I think instead of all this replay_scheduled_ and
ygorshenin1
2013/12/19 15:42:29
* added a lock around internal state;
* is_stream_
DaleCurtis
2013/12/19 19:44:08
I'm only suggesting the timer is used to Stop() th
ygorshenin1
2013/12/20 09:05:05
You don't need to press a key 5 times per second,
|
| + is_stream_idle_(true), |
| + schedule_replay_request_for_testing_(0) {} |
| virtual ~AudioStreamContainer() { |
| DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| + stop_stream_closure_.Cancel(); |
| + StopAndCloseStream(); |
| } |
| void Play() { |
| DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| if (!stream_) { |
| - stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy(params_, |
| - std::string(), |
| - std::string()); |
| + stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy( |
| + wav_audio_.params(), std::string(), std::string()); |
| if (!stream_ || !stream_->Open()) { |
| LOG(ERROR) << "Failed to open an output stream."; |
| return; |
| } |
| + cursor_ = 0; |
| stream_->SetVolume(kOutputVolumePercent); |
| + } else if (is_stream_idle_) { |
| + // If stream exists and idle, just stop the stream and start again. |
| + cursor_ = 0; |
| + StopStream(); |
| } else { |
| - // TODO (ygorshenin@): implement smart stream rewind. |
| - stream_->Stop(); |
| + // Stream exists and not idle, so let the current sound to |
| + // finish playing. If less than |wav_audio_.duration()| / 2 |
| + // until the end (including all replay requests), then replay is |
| + // sheduled. The main purpose of this strategy is to smooth |
| + // sound reproduction when multiple requests are made, for |
| + // instance, when user presses volume(up|down) button for a long |
| + // time. |
| + if (ReplayRequestCouldBeScheduled()) |
| + replay_scheduled_ = true; |
| + return; |
| } |
| - cursor_ = 0; |
| + last_play_start_time_ = base::TimeTicks::Now(); |
| + replay_scheduled_ = false; |
| + is_stream_idle_ = false; |
| if (g_audio_source_for_testing) |
| stream_->Start(g_audio_source_for_testing); |
| else |
| @@ -71,27 +89,41 @@ class AudioStreamHandler::AudioStreamContainer |
| void Stop() { |
| DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| - if (!stream_) |
| - return; |
| - stream_->Stop(); |
| - stream_->Close(); |
| - stream_ = NULL; |
| - |
| - if (g_observer_for_testing) |
| - g_observer_for_testing->OnStop(cursor_); |
| + StopStream(); |
| } |
| private: |
| + friend class AudioStreamHandler; |
| + |
| // AudioOutputStream::AudioSourceCallback overrides: |
| // Following methods could be called from *ANY* thread. |
| virtual int OnMoreData(AudioBus* dest, |
| AudioBuffersState /* state */) OVERRIDE { |
| + if (is_stream_idle_) |
| + return 0; |
| + |
| size_t bytes_written = 0; |
| if (wav_audio_.AtEnd(cursor_) || |
| !wav_audio_.CopyTo(dest, cursor_, &bytes_written)) { |
| - AudioManager::Get()->GetMessageLoop()->PostTask( |
| - FROM_HERE, |
| - base::Bind(&AudioStreamContainer::Stop, base::Unretained(this))); |
| + // If there are replay requests, |cursor_| is reset and will |
| + // continue to play from the start. Otherwise, stream will be |
| + // stopped (not closed!) in |kStopStreamDelaySec| seconds, |
| + // unless new Play() requests arrive during this time period. |
| + if (replay_scheduled_) { |
| + cursor_ = 0; |
| + last_play_start_time_ = base::TimeTicks::Now(); |
| + replay_scheduled_ = false; |
| + if (g_observer_for_testing) |
| + g_observer_for_testing->OnReplay(); |
| + } else { |
| + is_stream_idle_ = true; |
| + stop_stream_closure_.Reset(base::Bind(&AudioStreamContainer::StopStream, |
| + base::Unretained(this))); |
| + AudioManager::Get()->GetMessageLoop()->PostDelayedTask( |
| + FROM_HERE, |
| + stop_stream_closure_.callback(), |
| + base::TimeDelta::FromSeconds(kStopStreamDelaySec)); |
| + } |
| return 0; |
| } |
| cursor_ += bytes_written; |
| @@ -109,13 +141,54 @@ class AudioStreamHandler::AudioStreamContainer |
| LOG(ERROR) << "Error during system sound reproduction."; |
| } |
| + void StopStream() { |
| + stop_stream_closure_.Cancel(); |
| + replay_scheduled_ = false; |
| + if (stream_) |
| + stream_->Stop(); |
| + if (g_observer_for_testing) |
| + g_observer_for_testing->OnStop(cursor_); |
| + } |
| + |
| + void StopAndCloseStream() { |
| + StopStream(); |
| + if (stream_) { |
| + stream_->Close(); |
| + stream_ = NULL; |
| + } |
| + } |
| + |
| + bool ReplayRequestCouldBeScheduled() { |
| + if (schedule_replay_request_for_testing_) { |
| + --schedule_replay_request_for_testing_; |
| + return true; |
| + } |
| + const base::TimeDelta delta = |
| + base::TimeTicks::Now() - last_play_start_time_; |
| + return 2 * delta > wav_audio_.duration() && !replay_scheduled_; |
| + } |
| + |
| + void AllowReplayOnceForTesting() { |
| + ++schedule_replay_request_for_testing_; |
| + } |
| + |
| AudioOutputStream* stream_; |
| - const WavAudioHandler wav_audio_; |
| - const AudioParameters params_; |
| + WavAudioHandler wav_audio_; |
| size_t cursor_; |
| + // True if replay of the current sound is scheduled. |
| + bool replay_scheduled_; |
| + |
| + base::CancelableClosure stop_stream_closure_; |
| + bool is_stream_idle_; |
| + |
| + // Last time sound started to play from the start. |
| + base::TimeTicks last_play_start_time_; |
| + |
| + int schedule_replay_request_for_testing_; |
| + |
| DISALLOW_COPY_AND_ASSIGN(AudioStreamContainer); |
| }; |
| @@ -127,16 +200,11 @@ AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data) |
| LOG(ERROR) << "Can't get access to audio manager."; |
| return; |
| } |
| - AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| - GuessChannelLayout(wav_audio_.num_channels()), |
| - wav_audio_.sample_rate(), |
| - wav_audio_.bits_per_sample(), |
| - kDefaultFrameCount); |
| - if (!params.IsValid()) { |
| + if (!wav_audio_.params().IsValid()) { |
| LOG(ERROR) << "Audio params are invalid."; |
| return; |
| } |
| - stream_.reset(new AudioStreamContainer(wav_audio_, params)); |
| + stream_.reset(new AudioStreamContainer(wav_audio_)); |
| initialized_ = true; |
| } |
| @@ -185,4 +253,11 @@ void AudioStreamHandler::SetAudioSourceForTesting( |
| g_audio_source_for_testing = source; |
| } |
| +void AudioStreamHandler::AllowReplayOnceForTesting() { |
| + AudioManager::Get()->GetMessageLoop()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AudioStreamContainer::AllowReplayOnceForTesting, |
| + base::Unretained(stream_.get()))); |
| +} |
| + |
| } // namespace media |