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