| OLD | NEW | 
|    1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |    1 // Copyright (c) 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/fake_audio_consumer.h" |    5 #include "media/audio/fake_audio_consumer.h" | 
|    6  |    6  | 
|    7 #include "base/bind.h" |    7 #include "base/bind.h" | 
|    8 #include "base/bind_helpers.h" |    8 #include "base/bind_helpers.h" | 
 |    9 #include "base/cancelable_callback.h" | 
|    9 #include "base/logging.h" |   10 #include "base/logging.h" | 
 |   11 #include "base/memory/scoped_ptr.h" | 
|   10 #include "base/message_loop.h" |   12 #include "base/message_loop.h" | 
|   11 #include "base/message_loop/message_loop_proxy.h" |   13 #include "base/message_loop/message_loop_proxy.h" | 
 |   14 #include "base/synchronization/lock.h" | 
 |   15 #include "base/threading/thread_checker.h" | 
 |   16 #include "base/time.h" | 
 |   17 #include "media/audio/audio_parameters.h" | 
|   12 #include "media/base/audio_bus.h" |   18 #include "media/base/audio_bus.h" | 
|   13  |   19  | 
|   14 namespace media { |   20 namespace media { | 
|   15  |   21  | 
 |   22 class FakeAudioConsumer::Worker { | 
 |   23  public: | 
 |   24   Worker(const scoped_refptr<base::MessageLoopProxy>& worker_loop, | 
 |   25          const AudioParameters& params); | 
 |   26  | 
 |   27   void Start(const ReadCB& read_cb); | 
 |   28   void Stop(); | 
 |   29  | 
 |   30   // Destroys |this| once all outstanding references from tasks in |worker_loop| | 
 |   31   // are gone. | 
 |   32   void DeleteSoon(); | 
 |   33  | 
 |   34  private: | 
 |   35   // Initialize and start regular calls to DoRead() on the worker thread. | 
 |   36   void DoStart(); | 
 |   37  | 
 |   38   // Cancel any delayed callbacks to DoRead() in the worker loop's queue. | 
 |   39   void DoCancel(); | 
 |   40  | 
 |   41   // Task that regularly calls |read_cb_| according to the playback rate as | 
 |   42   // determined by the audio parameters given during construction.  Runs on | 
 |   43   // the worker loop. | 
 |   44   void DoRead(); | 
 |   45  | 
 |   46   const scoped_refptr<base::MessageLoopProxy> worker_loop_; | 
 |   47   const scoped_ptr<AudioBus> audio_bus_; | 
 |   48   const base::TimeDelta buffer_duration_; | 
 |   49  | 
 |   50   // Held while mutating or running |read_cb_|.  This mechanism ensures ReadCB | 
 |   51   // will not be invoked once the Stop() method returns. | 
 |   52   base::Lock read_cb_lock_; | 
 |   53  | 
 |   54   ReadCB read_cb_; | 
 |   55   base::TimeTicks next_read_time_; | 
 |   56  | 
 |   57   // Used to cancel any delayed tasks still inside the worker loop's queue. | 
 |   58   base::CancelableClosure read_task_cb_; | 
 |   59  | 
 |   60   base::ThreadChecker thread_checker_; | 
 |   61  | 
 |   62   DISALLOW_COPY_AND_ASSIGN(Worker); | 
 |   63 }; | 
 |   64  | 
|   16 FakeAudioConsumer::FakeAudioConsumer( |   65 FakeAudioConsumer::FakeAudioConsumer( | 
|   17     const scoped_refptr<base::MessageLoopProxy>& message_loop, |   66     const scoped_refptr<base::MessageLoopProxy>& worker_loop, | 
|   18     const AudioParameters& params) |   67     const AudioParameters& params) | 
|   19     : message_loop_(message_loop), |   68     : worker_(new Worker(worker_loop, params)) { | 
 |   69 } | 
 |   70  | 
 |   71 FakeAudioConsumer::~FakeAudioConsumer() { | 
 |   72   worker_->DeleteSoon(); | 
 |   73 } | 
 |   74  | 
 |   75 void FakeAudioConsumer::Start(const ReadCB& read_cb) { | 
 |   76   worker_->Start(read_cb); | 
 |   77 } | 
 |   78  | 
 |   79 void FakeAudioConsumer::Stop() { | 
 |   80   worker_->Stop(); | 
 |   81 } | 
 |   82  | 
 |   83 FakeAudioConsumer::Worker::Worker( | 
 |   84     const scoped_refptr<base::MessageLoopProxy>& worker_loop, | 
 |   85     const AudioParameters& params) | 
 |   86     : worker_loop_(worker_loop), | 
|   20       audio_bus_(AudioBus::Create(params)), |   87       audio_bus_(AudioBus::Create(params)), | 
|   21       buffer_duration_(base::TimeDelta::FromMicroseconds( |   88       buffer_duration_(base::TimeDelta::FromMicroseconds( | 
|   22           params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond / |   89           params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond / | 
|   23           static_cast<float>(params.sample_rate()))) { |   90           static_cast<float>(params.sample_rate()))) { | 
|   24   audio_bus_->Zero(); |   91   audio_bus_->Zero(); | 
 |   92  | 
 |   93   // Worker can be constructed on any thread, but will DCHECK that its | 
 |   94   // Start/Stop methods are called from the same thread. | 
 |   95   thread_checker_.DetachFromThread(); | 
|   25 } |   96 } | 
|   26  |   97  | 
|   27 FakeAudioConsumer::~FakeAudioConsumer() { |   98 void FakeAudioConsumer::Worker::Start(const ReadCB& read_cb)  { | 
|   28   DCHECK(read_cb_.is_null()); |   99   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  100   DCHECK(!read_cb.is_null()); | 
 |  101   { | 
 |  102     base::AutoLock scoped_lock(read_cb_lock_); | 
 |  103     DCHECK(read_cb_.is_null()); | 
 |  104     read_cb_ = read_cb; | 
 |  105   } | 
 |  106   worker_loop_->PostTask(FROM_HERE, | 
 |  107                          base::Bind(&Worker::DoStart, base::Unretained(this))); | 
|   29 } |  108 } | 
|   30  |  109  | 
|   31 void FakeAudioConsumer::Start(const ReadCB& read_cb)  { |  110 void FakeAudioConsumer::Worker::DoStart() { | 
|   32   DCHECK(message_loop_->BelongsToCurrentThread()); |  111   DCHECK(worker_loop_->BelongsToCurrentThread()); | 
|   33   DCHECK(read_cb_.is_null()); |  | 
|   34   DCHECK(!read_cb.is_null()); |  | 
|   35   read_cb_ = read_cb; |  | 
|   36   next_read_time_ = base::TimeTicks::Now(); |  112   next_read_time_ = base::TimeTicks::Now(); | 
|   37   read_task_cb_.Reset(base::Bind( |  113   read_task_cb_.Reset(base::Bind(&Worker::DoRead, base::Unretained(this))); | 
|   38       &FakeAudioConsumer::DoRead, base::Unretained(this))); |  114   read_task_cb_.callback().Run(); | 
|   39   message_loop_->PostTask(FROM_HERE, read_task_cb_.callback()); |  | 
|   40 } |  115 } | 
|   41  |  116  | 
|   42 void FakeAudioConsumer::Stop() { |  117 void FakeAudioConsumer::Worker::Stop() { | 
|   43   DCHECK(message_loop_->BelongsToCurrentThread()); |  118   DCHECK(thread_checker_.CalledOnValidThread()); | 
|   44   read_cb_.Reset(); |  119   // Note: It's important to post the DoCancel() task before resetting | 
 |  120   // |read_cb_|.  See DeleteSoon(). | 
 |  121   worker_loop_->PostTask(FROM_HERE, | 
 |  122                          base::Bind(&Worker::DoCancel, base::Unretained(this))); | 
 |  123   { | 
 |  124     base::AutoLock scoped_lock(read_cb_lock_); | 
 |  125     read_cb_.Reset(); | 
 |  126   } | 
 |  127 } | 
 |  128  | 
 |  129 void FakeAudioConsumer::Worker::DoCancel() { | 
 |  130   DCHECK(worker_loop_->BelongsToCurrentThread()); | 
|   45   read_task_cb_.Cancel(); |  131   read_task_cb_.Cancel(); | 
|   46 } |  132 } | 
|   47  |  133  | 
|   48 void FakeAudioConsumer::DoRead() { |  134 void FakeAudioConsumer::Worker::DoRead() { | 
|   49   DCHECK(message_loop_->BelongsToCurrentThread()); |  135   DCHECK(worker_loop_->BelongsToCurrentThread()); | 
|   50   DCHECK(!read_cb_.is_null()); |  | 
|   51  |  136  | 
|   52   read_cb_.Run(audio_bus_.get()); |  137   { | 
 |  138     base::AutoLock scoped_lock(read_cb_lock_); | 
 |  139     if (!read_cb_.is_null()) | 
 |  140       read_cb_.Run(audio_bus_.get()); | 
 |  141   } | 
|   53  |  142  | 
|   54   // Need to account for time spent here due to the cost of |read_cb_| as well |  143   // Need to account for time spent here due to the cost of |read_cb_| as well | 
|   55   // as the imprecision of PostDelayedTask(). |  144   // as the imprecision of PostDelayedTask(). | 
|   56   const base::TimeTicks now = base::TimeTicks::Now(); |  145   const base::TimeTicks now = base::TimeTicks::Now(); | 
|   57   base::TimeDelta delay = next_read_time_ + buffer_duration_ - now; |  146   base::TimeDelta delay = next_read_time_ + buffer_duration_ - now; | 
|   58  |  147  | 
|   59   // If we're behind, find the next nearest ontime interval. |  148   // If we're behind, find the next nearest ontime interval. | 
|   60   if (delay < base::TimeDelta()) |  149   if (delay < base::TimeDelta()) | 
|   61     delay += buffer_duration_ * (-delay / buffer_duration_ + 1); |  150     delay += buffer_duration_ * (-delay / buffer_duration_ + 1); | 
|   62   next_read_time_ = now + delay; |  151   next_read_time_ = now + delay; | 
|   63  |  152  | 
|   64   message_loop_->PostDelayedTask(FROM_HERE, read_task_cb_.callback(), delay); |  153   worker_loop_->PostDelayedTask(FROM_HERE, read_task_cb_.callback(), delay); | 
 |  154 } | 
 |  155  | 
 |  156 void FakeAudioConsumer::Worker::DeleteSoon() { | 
 |  157   DCHECK(read_cb_.is_null()); | 
 |  158  | 
 |  159   // At this point, |read_cb_| has been cleared and therefore the DoCancel() | 
 |  160   // task has already been posted to |worker_loop_|.  Attempt to post a delete | 
 |  161   // task that will run after all other queued tasks have run (and the delayed | 
 |  162   // task has been canceled).  If |worker_loop_| is already stopped, simply | 
 |  163   // delete |this| immediately. | 
 |  164   if (!worker_loop_->DeleteSoon(FROM_HERE, this)) | 
 |  165     delete this; | 
|   65 } |  166 } | 
|   66  |  167  | 
|   67 }  // namespace media |  168 }  // namespace media | 
| OLD | NEW |