| 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..d82829189a80fde0da24aa28d01611d821ee290a 100644
|
| --- a/media/audio/sounds/audio_stream_handler.cc
|
| +++ b/media/audio/sounds/audio_stream_handler.cc
|
| @@ -6,10 +6,13 @@
|
|
|
| #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/audio/sounds/wav_reader.h"
|
| #include "media/base/channel_layout.h"
|
|
|
| namespace media {
|
| @@ -22,6 +25,9 @@ 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 +36,55 @@ AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL;
|
| class AudioStreamHandler::AudioStreamContainer
|
| : public AudioOutputStream::AudioSourceCallback {
|
| public:
|
| - AudioStreamContainer(const WavAudioHandler& wav_audio,
|
| + AudioStreamContainer(const WavParser& wav_parser,
|
| const AudioParameters& params)
|
| : stream_(NULL),
|
| - wav_audio_(wav_audio),
|
| + wav_reader_(new WavReaderImpl(wav_parser)),
|
| + duration_(wav_parser.duration()),
|
| params_(params),
|
| - cursor_(0) {
|
| - }
|
| + cursor_(0),
|
| + num_replay_requests_(0),
|
| + 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(
|
| + 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_parser_.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())
|
| + ++num_replay_requests_;
|
| + return;
|
| }
|
|
|
| - cursor_ = 0;
|
| + last_play_start_time_ = base::TimeTicks::Now();
|
| + num_replay_requests_ = 0;
|
| + is_stream_idle_ = false;
|
| if (g_audio_source_for_testing)
|
| stream_->Start(g_audio_source_for_testing);
|
| else
|
| @@ -71,27 +96,40 @@ 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 (wav_reader_->AtEnd(cursor_) ||
|
| + !wav_reader_->CopyTo(dest, cursor_, &bytes_written)) {
|
| + // 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 (num_replay_requests_) {
|
| + cursor_ = 0;
|
| + --num_replay_requests_;
|
| + 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,18 +147,65 @@ class AudioStreamHandler::AudioStreamContainer
|
| LOG(ERROR) << "Error during system sound reproduction.";
|
| }
|
|
|
| + void StopStream() {
|
| + stop_stream_closure_.Cancel();
|
| + num_replay_requests_ = 0;
|
| + 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;
|
| + }
|
| + return 2 * (base::TimeTicks::Now() - last_play_start_time_) >
|
| + (2 * num_replay_requests_ + 1) * duration_;
|
| + }
|
| +
|
| + void SetWavReaderForTesting(WavReader* reader) {
|
| + wav_reader_.reset(reader);
|
| + }
|
| +
|
| + void AllowReplayOnceForTesting() {
|
| + ++schedule_replay_request_for_testing_;
|
| + }
|
| +
|
| AudioOutputStream* stream_;
|
|
|
| - const WavAudioHandler wav_audio_;
|
| + scoped_ptr<WavReader> wav_reader_;
|
| + const base::TimeDelta duration_;
|
| const AudioParameters params_;
|
|
|
| size_t cursor_;
|
|
|
| + // Number of pending play requests, which should be processed after
|
| + // the current request.
|
| + int num_replay_requests_;
|
| +
|
| + 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);
|
| };
|
|
|
| AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data)
|
| - : wav_audio_(wav_data),
|
| + : wav_parser_(wav_data),
|
| initialized_(false) {
|
| AudioManager* manager = AudioManager::Get();
|
| if (!manager) {
|
| @@ -128,15 +213,15 @@ AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data)
|
| return;
|
| }
|
| AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
|
| - GuessChannelLayout(wav_audio_.num_channels()),
|
| - wav_audio_.sample_rate(),
|
| - wav_audio_.bits_per_sample(),
|
| + GuessChannelLayout(wav_parser_.num_channels()),
|
| + wav_parser_.sample_rate(),
|
| + wav_parser_.bits_per_sample(),
|
| kDefaultFrameCount);
|
| if (!params.IsValid()) {
|
| LOG(ERROR) << "Audio params are invalid.";
|
| return;
|
| }
|
| - stream_.reset(new AudioStreamContainer(wav_audio_, params));
|
| + stream_.reset(new AudioStreamContainer(wav_parser_, params));
|
| initialized_ = true;
|
| }
|
|
|
| @@ -185,4 +270,18 @@ void AudioStreamHandler::SetAudioSourceForTesting(
|
| g_audio_source_for_testing = source;
|
| }
|
|
|
| +void AudioStreamHandler::SetWavReaderForTesting(WavReader* reader) {
|
| + AudioManager::Get()->GetMessageLoop()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&AudioStreamContainer::SetWavReaderForTesting,
|
| + base::Unretained(stream_.get()), reader));
|
| +}
|
| +
|
| +void AudioStreamHandler::AllowReplayOnceForTesting() {
|
| + AudioManager::Get()->GetMessageLoop()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&AudioStreamContainer::AllowReplayOnceForTesting,
|
| + base::Unretained(stream_.get())));
|
| +}
|
| +
|
| } // namespace media
|
|
|