| Index: media/audio/fake_audio_consumer.cc
|
| diff --git a/media/audio/fake_audio_consumer.cc b/media/audio/fake_audio_consumer.cc
|
| index 5232170c437466ce67709334512a0dd26c9c1b10..72b47d7eeef753a930ccc5b78e4351f60fa3dd3a 100644
|
| --- a/media/audio/fake_audio_consumer.cc
|
| +++ b/media/audio/fake_audio_consumer.cc
|
| @@ -6,50 +6,139 @@
|
|
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| +#include "base/cancelable_callback.h"
|
| #include "base/logging.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| #include "base/message_loop.h"
|
| #include "base/message_loop/message_loop_proxy.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "base/threading/thread_checker.h"
|
| +#include "base/time.h"
|
| +#include "media/audio/audio_parameters.h"
|
| #include "media/base/audio_bus.h"
|
|
|
| namespace media {
|
|
|
| +class FakeAudioConsumer::Worker {
|
| + public:
|
| + Worker(const scoped_refptr<base::MessageLoopProxy>& worker_loop,
|
| + const AudioParameters& params);
|
| +
|
| + void Start(const ReadCB& read_cb);
|
| + void Stop();
|
| +
|
| + // Destroys |this| once all outstanding references from tasks in |worker_loop|
|
| + // are gone.
|
| + void DeleteSoon();
|
| +
|
| + private:
|
| + // Initialize and start regular calls to DoRead() on the worker thread.
|
| + void DoStart();
|
| +
|
| + // Cancel any delayed callbacks to DoRead() in the worker loop's queue.
|
| + void DoCancel();
|
| +
|
| + // Task that regularly calls |read_cb_| according to the playback rate as
|
| + // determined by the audio parameters given during construction. Runs on
|
| + // the worker loop.
|
| + void DoRead();
|
| +
|
| + const scoped_refptr<base::MessageLoopProxy> worker_loop_;
|
| + const scoped_ptr<AudioBus> audio_bus_;
|
| + const base::TimeDelta buffer_duration_;
|
| +
|
| + // Held while mutating or running |read_cb_|. This mechanism ensures ReadCB
|
| + // will not be invoked once the Stop() method returns.
|
| + base::Lock read_cb_lock_;
|
| +
|
| + ReadCB read_cb_;
|
| + base::TimeTicks next_read_time_;
|
| +
|
| + // Used to cancel any delayed tasks still inside the worker loop's queue.
|
| + base::CancelableClosure read_task_cb_;
|
| +
|
| + base::ThreadChecker thread_checker_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Worker);
|
| +};
|
| +
|
| FakeAudioConsumer::FakeAudioConsumer(
|
| - const scoped_refptr<base::MessageLoopProxy>& message_loop,
|
| + const scoped_refptr<base::MessageLoopProxy>& worker_loop,
|
| + const AudioParameters& params)
|
| + : worker_(new Worker(worker_loop, params)) {
|
| +}
|
| +
|
| +FakeAudioConsumer::~FakeAudioConsumer() {
|
| + worker_->DeleteSoon();
|
| +}
|
| +
|
| +void FakeAudioConsumer::Start(const ReadCB& read_cb) {
|
| + worker_->Start(read_cb);
|
| +}
|
| +
|
| +void FakeAudioConsumer::Stop() {
|
| + worker_->Stop();
|
| +}
|
| +
|
| +FakeAudioConsumer::Worker::Worker(
|
| + const scoped_refptr<base::MessageLoopProxy>& worker_loop,
|
| const AudioParameters& params)
|
| - : message_loop_(message_loop),
|
| + : worker_loop_(worker_loop),
|
| audio_bus_(AudioBus::Create(params)),
|
| buffer_duration_(base::TimeDelta::FromMicroseconds(
|
| params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
|
| static_cast<float>(params.sample_rate()))) {
|
| audio_bus_->Zero();
|
| -}
|
|
|
| -FakeAudioConsumer::~FakeAudioConsumer() {
|
| - DCHECK(read_cb_.is_null());
|
| + // Worker can be constructed on any thread, but will DCHECK that its
|
| + // Start/Stop methods are called from the same thread.
|
| + thread_checker_.DetachFromThread();
|
| }
|
|
|
| -void FakeAudioConsumer::Start(const ReadCB& read_cb) {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - DCHECK(read_cb_.is_null());
|
| +void FakeAudioConsumer::Worker::Start(const ReadCB& read_cb) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| DCHECK(!read_cb.is_null());
|
| - read_cb_ = read_cb;
|
| + {
|
| + base::AutoLock scoped_lock(read_cb_lock_);
|
| + DCHECK(read_cb_.is_null());
|
| + read_cb_ = read_cb;
|
| + }
|
| + worker_loop_->PostTask(FROM_HERE,
|
| + base::Bind(&Worker::DoStart, base::Unretained(this)));
|
| +}
|
| +
|
| +void FakeAudioConsumer::Worker::DoStart() {
|
| + DCHECK(worker_loop_->BelongsToCurrentThread());
|
| next_read_time_ = base::TimeTicks::Now();
|
| - read_task_cb_.Reset(base::Bind(
|
| - &FakeAudioConsumer::DoRead, base::Unretained(this)));
|
| - message_loop_->PostTask(FROM_HERE, read_task_cb_.callback());
|
| + read_task_cb_.Reset(base::Bind(&Worker::DoRead, base::Unretained(this)));
|
| + read_task_cb_.callback().Run();
|
| }
|
|
|
| -void FakeAudioConsumer::Stop() {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - read_cb_.Reset();
|
| +void FakeAudioConsumer::Worker::Stop() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + // Note: It's important to post the DoCancel() task before resetting
|
| + // |read_cb_|. See DeleteSoon().
|
| + worker_loop_->PostTask(FROM_HERE,
|
| + base::Bind(&Worker::DoCancel, base::Unretained(this)));
|
| + {
|
| + base::AutoLock scoped_lock(read_cb_lock_);
|
| + read_cb_.Reset();
|
| + }
|
| +}
|
| +
|
| +void FakeAudioConsumer::Worker::DoCancel() {
|
| + DCHECK(worker_loop_->BelongsToCurrentThread());
|
| read_task_cb_.Cancel();
|
| }
|
|
|
| -void FakeAudioConsumer::DoRead() {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - DCHECK(!read_cb_.is_null());
|
| +void FakeAudioConsumer::Worker::DoRead() {
|
| + DCHECK(worker_loop_->BelongsToCurrentThread());
|
|
|
| - read_cb_.Run(audio_bus_.get());
|
| + {
|
| + base::AutoLock scoped_lock(read_cb_lock_);
|
| + if (!read_cb_.is_null())
|
| + read_cb_.Run(audio_bus_.get());
|
| + }
|
|
|
| // Need to account for time spent here due to the cost of |read_cb_| as well
|
| // as the imprecision of PostDelayedTask().
|
| @@ -61,7 +150,19 @@ void FakeAudioConsumer::DoRead() {
|
| delay += buffer_duration_ * (-delay / buffer_duration_ + 1);
|
| next_read_time_ = now + delay;
|
|
|
| - message_loop_->PostDelayedTask(FROM_HERE, read_task_cb_.callback(), delay);
|
| + worker_loop_->PostDelayedTask(FROM_HERE, read_task_cb_.callback(), delay);
|
| +}
|
| +
|
| +void FakeAudioConsumer::Worker::DeleteSoon() {
|
| + DCHECK(read_cb_.is_null());
|
| +
|
| + // At this point, |read_cb_| has been cleared and therefore the DoCancel()
|
| + // task has already been posted to |worker_loop_|. Attempt to post a delete
|
| + // task that will run after all other queued tasks have run (and the delayed
|
| + // task has been canceled). If |worker_loop_| is already stopped, simply
|
| + // delete |this| immediately.
|
| + if (!worker_loop_->DeleteSoon(FROM_HERE, this))
|
| + delete this;
|
| }
|
|
|
| } // namespace media
|
|
|