Chromium Code Reviews| 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 #include "media/audio/sounds/audio_stream_handler.h" | 5 #include "media/audio/sounds/audio_stream_handler.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/cancelable_callback.h" | |
| 9 #include "base/logging.h" | 10 #include "base/logging.h" |
| 10 #include "base/message_loop/message_loop_proxy.h" | 11 #include "base/message_loop/message_loop_proxy.h" |
| 12 #include "base/time/time.h" | |
| 11 #include "media/audio/audio_manager.h" | 13 #include "media/audio/audio_manager.h" |
| 12 #include "media/audio/audio_manager_base.h" | 14 #include "media/audio/audio_manager_base.h" |
| 13 #include "media/base/channel_layout.h" | 15 #include "media/base/channel_layout.h" |
| 14 | 16 |
| 15 namespace media { | 17 namespace media { |
| 16 | 18 |
| 17 namespace { | 19 namespace { |
| 18 | 20 |
| 19 // Volume percent. | 21 // Volume percent. |
| 20 const double kOutputVolumePercent = 0.8; | 22 const double kOutputVolumePercent = 0.8; |
| 21 | 23 |
| 22 // The number of frames each OnMoreData() call will request. | 24 // Delay between end of sound and call to AudioOutputStream::Stop(). |
| 23 const int kDefaultFrameCount = 1024; | 25 const int kStopStreamDelaySec = 3; |
| 24 | 26 |
| 25 AudioStreamHandler::TestObserver* g_observer_for_testing = NULL; | 27 AudioStreamHandler::TestObserver* g_observer_for_testing = NULL; |
| 26 AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL; | 28 AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL; |
| 27 | 29 |
| 28 } // namespace | 30 } // namespace |
| 29 | 31 |
| 30 class AudioStreamHandler::AudioStreamContainer | 32 class AudioStreamHandler::AudioStreamContainer |
| 31 : public AudioOutputStream::AudioSourceCallback { | 33 : public AudioOutputStream::AudioSourceCallback { |
| 32 public: | 34 public: |
| 33 AudioStreamContainer(const WavAudioHandler& wav_audio, | 35 AudioStreamContainer(const WavAudioHandler& wav_audio) |
| 34 const AudioParameters& params) | |
| 35 : stream_(NULL), | 36 : stream_(NULL), |
| 36 wav_audio_(wav_audio), | 37 wav_audio_(wav_audio), |
| 37 params_(params), | 38 cursor_(0), |
| 38 cursor_(0) { | 39 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,
| |
| 39 } | 40 is_stream_idle_(true), |
| 41 schedule_replay_request_for_testing_(0) {} | |
| 40 | 42 |
| 41 virtual ~AudioStreamContainer() { | 43 virtual ~AudioStreamContainer() { |
| 42 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); | 44 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| 45 stop_stream_closure_.Cancel(); | |
| 46 StopAndCloseStream(); | |
| 43 } | 47 } |
| 44 | 48 |
| 45 void Play() { | 49 void Play() { |
| 46 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); | 50 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| 47 | 51 |
| 48 if (!stream_) { | 52 if (!stream_) { |
| 49 stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy(params_, | 53 stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy( |
| 50 std::string(), | 54 wav_audio_.params(), std::string(), std::string()); |
| 51 std::string()); | |
| 52 if (!stream_ || !stream_->Open()) { | 55 if (!stream_ || !stream_->Open()) { |
| 53 LOG(ERROR) << "Failed to open an output stream."; | 56 LOG(ERROR) << "Failed to open an output stream."; |
| 54 return; | 57 return; |
| 55 } | 58 } |
| 59 cursor_ = 0; | |
| 56 stream_->SetVolume(kOutputVolumePercent); | 60 stream_->SetVolume(kOutputVolumePercent); |
| 61 } else if (is_stream_idle_) { | |
| 62 // If stream exists and idle, just stop the stream and start again. | |
| 63 cursor_ = 0; | |
| 64 StopStream(); | |
| 57 } else { | 65 } else { |
| 58 // TODO (ygorshenin@): implement smart stream rewind. | 66 // Stream exists and not idle, so let the current sound to |
| 59 stream_->Stop(); | 67 // finish playing. If less than |wav_audio_.duration()| / 2 |
| 68 // until the end (including all replay requests), then replay is | |
| 69 // sheduled. The main purpose of this strategy is to smooth | |
| 70 // sound reproduction when multiple requests are made, for | |
| 71 // instance, when user presses volume(up|down) button for a long | |
| 72 // time. | |
| 73 if (ReplayRequestCouldBeScheduled()) | |
| 74 replay_scheduled_ = true; | |
| 75 return; | |
| 60 } | 76 } |
| 61 | 77 |
| 62 cursor_ = 0; | 78 last_play_start_time_ = base::TimeTicks::Now(); |
| 79 replay_scheduled_ = false; | |
| 80 is_stream_idle_ = false; | |
| 63 if (g_audio_source_for_testing) | 81 if (g_audio_source_for_testing) |
| 64 stream_->Start(g_audio_source_for_testing); | 82 stream_->Start(g_audio_source_for_testing); |
| 65 else | 83 else |
| 66 stream_->Start(this); | 84 stream_->Start(this); |
| 67 | 85 |
| 68 if (g_observer_for_testing) | 86 if (g_observer_for_testing) |
| 69 g_observer_for_testing->OnPlay(); | 87 g_observer_for_testing->OnPlay(); |
| 70 } | 88 } |
| 71 | 89 |
| 72 void Stop() { | 90 void Stop() { |
| 73 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); | 91 DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread()); |
| 74 if (!stream_) | 92 StopStream(); |
| 75 return; | |
| 76 stream_->Stop(); | |
| 77 stream_->Close(); | |
| 78 stream_ = NULL; | |
| 79 | |
| 80 if (g_observer_for_testing) | |
| 81 g_observer_for_testing->OnStop(cursor_); | |
| 82 } | 93 } |
| 83 | 94 |
| 84 private: | 95 private: |
| 96 friend class AudioStreamHandler; | |
| 97 | |
| 85 // AudioOutputStream::AudioSourceCallback overrides: | 98 // AudioOutputStream::AudioSourceCallback overrides: |
| 86 // Following methods could be called from *ANY* thread. | 99 // Following methods could be called from *ANY* thread. |
| 87 virtual int OnMoreData(AudioBus* dest, | 100 virtual int OnMoreData(AudioBus* dest, |
| 88 AudioBuffersState /* state */) OVERRIDE { | 101 AudioBuffersState /* state */) OVERRIDE { |
| 102 if (is_stream_idle_) | |
| 103 return 0; | |
| 104 | |
| 89 size_t bytes_written = 0; | 105 size_t bytes_written = 0; |
| 90 if (wav_audio_.AtEnd(cursor_) || | 106 if (wav_audio_.AtEnd(cursor_) || |
| 91 !wav_audio_.CopyTo(dest, cursor_, &bytes_written)) { | 107 !wav_audio_.CopyTo(dest, cursor_, &bytes_written)) { |
| 92 AudioManager::Get()->GetMessageLoop()->PostTask( | 108 // If there are replay requests, |cursor_| is reset and will |
| 93 FROM_HERE, | 109 // continue to play from the start. Otherwise, stream will be |
| 94 base::Bind(&AudioStreamContainer::Stop, base::Unretained(this))); | 110 // stopped (not closed!) in |kStopStreamDelaySec| seconds, |
| 111 // unless new Play() requests arrive during this time period. | |
| 112 if (replay_scheduled_) { | |
| 113 cursor_ = 0; | |
| 114 last_play_start_time_ = base::TimeTicks::Now(); | |
| 115 replay_scheduled_ = false; | |
| 116 if (g_observer_for_testing) | |
| 117 g_observer_for_testing->OnReplay(); | |
| 118 } else { | |
| 119 is_stream_idle_ = true; | |
| 120 stop_stream_closure_.Reset(base::Bind(&AudioStreamContainer::StopStream, | |
| 121 base::Unretained(this))); | |
| 122 AudioManager::Get()->GetMessageLoop()->PostDelayedTask( | |
| 123 FROM_HERE, | |
| 124 stop_stream_closure_.callback(), | |
| 125 base::TimeDelta::FromSeconds(kStopStreamDelaySec)); | |
| 126 } | |
| 95 return 0; | 127 return 0; |
| 96 } | 128 } |
| 97 cursor_ += bytes_written; | 129 cursor_ += bytes_written; |
| 98 | 130 |
| 99 return dest->frames(); | 131 return dest->frames(); |
| 100 } | 132 } |
| 101 | 133 |
| 102 virtual int OnMoreIOData(AudioBus* /* source */, | 134 virtual int OnMoreIOData(AudioBus* /* source */, |
| 103 AudioBus* dest, | 135 AudioBus* dest, |
| 104 AudioBuffersState state) OVERRIDE { | 136 AudioBuffersState state) OVERRIDE { |
| 105 return OnMoreData(dest, state); | 137 return OnMoreData(dest, state); |
| 106 } | 138 } |
| 107 | 139 |
| 108 virtual void OnError(AudioOutputStream* /* stream */) OVERRIDE { | 140 virtual void OnError(AudioOutputStream* /* stream */) OVERRIDE { |
| 109 LOG(ERROR) << "Error during system sound reproduction."; | 141 LOG(ERROR) << "Error during system sound reproduction."; |
| 110 } | 142 } |
| 111 | 143 |
| 144 void StopStream() { | |
| 145 stop_stream_closure_.Cancel(); | |
| 146 replay_scheduled_ = false; | |
| 147 if (stream_) | |
| 148 stream_->Stop(); | |
| 149 if (g_observer_for_testing) | |
| 150 g_observer_for_testing->OnStop(cursor_); | |
| 151 } | |
| 152 | |
| 153 void StopAndCloseStream() { | |
| 154 StopStream(); | |
| 155 if (stream_) { | |
| 156 stream_->Close(); | |
| 157 stream_ = NULL; | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 bool ReplayRequestCouldBeScheduled() { | |
| 162 if (schedule_replay_request_for_testing_) { | |
| 163 --schedule_replay_request_for_testing_; | |
| 164 return true; | |
| 165 } | |
| 166 const base::TimeDelta delta = | |
| 167 base::TimeTicks::Now() - last_play_start_time_; | |
| 168 return 2 * delta > wav_audio_.duration() && !replay_scheduled_; | |
| 169 } | |
| 170 | |
| 171 void AllowReplayOnceForTesting() { | |
| 172 ++schedule_replay_request_for_testing_; | |
| 173 } | |
| 174 | |
| 112 AudioOutputStream* stream_; | 175 AudioOutputStream* stream_; |
| 113 | 176 |
| 114 const WavAudioHandler wav_audio_; | 177 WavAudioHandler wav_audio_; |
| 115 const AudioParameters params_; | |
| 116 | 178 |
| 117 size_t cursor_; | 179 size_t cursor_; |
| 118 | 180 |
| 181 // True if replay of the current sound is scheduled. | |
| 182 bool replay_scheduled_; | |
| 183 | |
| 184 base::CancelableClosure stop_stream_closure_; | |
| 185 bool is_stream_idle_; | |
| 186 | |
| 187 // Last time sound started to play from the start. | |
| 188 base::TimeTicks last_play_start_time_; | |
| 189 | |
| 190 int schedule_replay_request_for_testing_; | |
| 191 | |
| 119 DISALLOW_COPY_AND_ASSIGN(AudioStreamContainer); | 192 DISALLOW_COPY_AND_ASSIGN(AudioStreamContainer); |
| 120 }; | 193 }; |
| 121 | 194 |
| 122 AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data) | 195 AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data) |
| 123 : wav_audio_(wav_data), | 196 : wav_audio_(wav_data), |
| 124 initialized_(false) { | 197 initialized_(false) { |
| 125 AudioManager* manager = AudioManager::Get(); | 198 AudioManager* manager = AudioManager::Get(); |
| 126 if (!manager) { | 199 if (!manager) { |
| 127 LOG(ERROR) << "Can't get access to audio manager."; | 200 LOG(ERROR) << "Can't get access to audio manager."; |
| 128 return; | 201 return; |
| 129 } | 202 } |
| 130 AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY, | 203 if (!wav_audio_.params().IsValid()) { |
| 131 GuessChannelLayout(wav_audio_.num_channels()), | |
| 132 wav_audio_.sample_rate(), | |
| 133 wav_audio_.bits_per_sample(), | |
| 134 kDefaultFrameCount); | |
| 135 if (!params.IsValid()) { | |
| 136 LOG(ERROR) << "Audio params are invalid."; | 204 LOG(ERROR) << "Audio params are invalid."; |
| 137 return; | 205 return; |
| 138 } | 206 } |
| 139 stream_.reset(new AudioStreamContainer(wav_audio_, params)); | 207 stream_.reset(new AudioStreamContainer(wav_audio_)); |
| 140 initialized_ = true; | 208 initialized_ = true; |
| 141 } | 209 } |
| 142 | 210 |
| 143 AudioStreamHandler::~AudioStreamHandler() { | 211 AudioStreamHandler::~AudioStreamHandler() { |
| 144 DCHECK(CalledOnValidThread()); | 212 DCHECK(CalledOnValidThread()); |
| 145 AudioManager::Get()->GetMessageLoop()->PostTask( | 213 AudioManager::Get()->GetMessageLoop()->PostTask( |
| 146 FROM_HERE, | 214 FROM_HERE, |
| 147 base::Bind(&AudioStreamContainer::Stop, base::Unretained(stream_.get()))); | 215 base::Bind(&AudioStreamContainer::Stop, base::Unretained(stream_.get()))); |
| 148 AudioManager::Get()->GetMessageLoop()->DeleteSoon(FROM_HERE, | 216 AudioManager::Get()->GetMessageLoop()->DeleteSoon(FROM_HERE, |
| 149 stream_.release()); | 217 stream_.release()); |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 178 void AudioStreamHandler::SetObserverForTesting(TestObserver* observer) { | 246 void AudioStreamHandler::SetObserverForTesting(TestObserver* observer) { |
| 179 g_observer_for_testing = observer; | 247 g_observer_for_testing = observer; |
| 180 } | 248 } |
| 181 | 249 |
| 182 // static | 250 // static |
| 183 void AudioStreamHandler::SetAudioSourceForTesting( | 251 void AudioStreamHandler::SetAudioSourceForTesting( |
| 184 AudioOutputStream::AudioSourceCallback* source) { | 252 AudioOutputStream::AudioSourceCallback* source) { |
| 185 g_audio_source_for_testing = source; | 253 g_audio_source_for_testing = source; |
| 186 } | 254 } |
| 187 | 255 |
| 256 void AudioStreamHandler::AllowReplayOnceForTesting() { | |
| 257 AudioManager::Get()->GetMessageLoop()->PostTask( | |
| 258 FROM_HERE, | |
| 259 base::Bind(&AudioStreamContainer::AllowReplayOnceForTesting, | |
| 260 base::Unretained(stream_.get()))); | |
| 261 } | |
| 262 | |
| 188 } // namespace media | 263 } // namespace media |
| OLD | NEW |