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 "content/browser/renderer_host/media/web_contents_audio_input_stream.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/threading/thread_checker.h"
14#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
15#include "content/browser/renderer_host/media/web_contents_capture_util.h"
16#include "content/browser/renderer_host/media/web_contents_tracker.h"
17#include "content/public/browser/browser_thread.h"
18#include "media/audio/virtual_audio_input_stream.h"
19#include "media/audio/virtual_audio_output_stream.h"
20
21namespace content {
22
23class WebContentsAudioInputStream::Impl
24    : public base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>,
25      public AudioMirroringManager::MirroringDestination {
26 public:
27  // Takes ownership of |mixer_stream|.  The rest outlive this instance.
28  Impl(int render_process_id, int render_view_id,
29       AudioMirroringManager* mirroring_manager,
30       const scoped_refptr<WebContentsTracker>& tracker,
31       media::VirtualAudioInputStream* mixer_stream);
32
33  // Open underlying VirtualAudioInputStream and start tracker.
34  bool Open();
35
36  // Start the underlying VirtualAudioInputStream and instruct
37  // AudioMirroringManager to begin a mirroring session.
38  void Start(AudioInputCallback* callback);
39
40  // Stop the underlying VirtualAudioInputStream and instruct
41  // AudioMirroringManager to shutdown a mirroring session.
42  void Stop();
43
44  // Close the underlying VirtualAudioInputStream and stop the tracker.
45  void Close();
46
47  // Accessor to underlying VirtualAudioInputStream.
48  media::VirtualAudioInputStream* mixer_stream() const {
49    return mixer_stream_.get();
50  }
51
52 private:
53  friend class base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>;
54
55  enum State {
56    CONSTRUCTED,
57    OPENED,
58    MIRRORING,
59    CLOSED
60  };
61
62  virtual ~Impl();
63
64  // Returns true if the mirroring target has been permanently lost.
65  bool IsTargetLost() const;
66
67  // Notifies the consumer callback that the stream is now dead.
68  void ReportError();
69
70  // Start/Stop mirroring by posting a call to AudioMirroringManager on the IO
71  // BrowserThread.
72  void StartMirroring();
73  void StopMirroring();
74
75  // AudioMirroringManager::MirroringDestination implementation
76  virtual media::AudioOutputStream* AddInput(
77      const media::AudioParameters& params) OVERRIDE;
78
79  // Callback which is run when |stream| is closed.  Deletes |stream|.
80  void ReleaseInput(media::VirtualAudioOutputStream* stream);
81
82  // Called by WebContentsTracker when the target of the audio mirroring has
83  // changed.
84  void OnTargetChanged(int render_process_id, int render_view_id);
85
86  // Injected dependencies.
87  AudioMirroringManager* const mirroring_manager_;
88  const scoped_refptr<WebContentsTracker> tracker_;
89  // The AudioInputStream implementation that handles the audio conversion and
90  // mixing details.
91  const scoped_ptr<media::VirtualAudioInputStream> mixer_stream_;
92
93  State state_;
94
95  // Current audio mirroring target.
96  int target_render_process_id_;
97  int target_render_view_id_;
98
99  // Current callback used to consume the resulting mixed audio data.
100  AudioInputCallback* callback_;
101
102  base::ThreadChecker thread_checker_;
103
104  DISALLOW_COPY_AND_ASSIGN(Impl);
105};
106
107WebContentsAudioInputStream::Impl::Impl(
108    int render_process_id, int render_view_id,
109    AudioMirroringManager* mirroring_manager,
110    const scoped_refptr<WebContentsTracker>& tracker,
111    media::VirtualAudioInputStream* mixer_stream)
112    : mirroring_manager_(mirroring_manager),
113      tracker_(tracker), mixer_stream_(mixer_stream), state_(CONSTRUCTED),
114      target_render_process_id_(render_process_id),
115      target_render_view_id_(render_view_id),
116      callback_(NULL) {
117  DCHECK(mirroring_manager_);
118  DCHECK(tracker_.get());
119  DCHECK(mixer_stream_.get());
120
121  // WAIS::Impl can be constructed on any thread, but will DCHECK that all
122  // its methods from here on are called from the same thread.
123  thread_checker_.DetachFromThread();
124}
125
126WebContentsAudioInputStream::Impl::~Impl() {
127  DCHECK(state_ == CONSTRUCTED || state_ == CLOSED);
128}
129
130bool WebContentsAudioInputStream::Impl::Open() {
131  DCHECK(thread_checker_.CalledOnValidThread());
132
133  DCHECK_EQ(CONSTRUCTED, state_) << "Illegal to Open more than once.";
134
135  if (!mixer_stream_->Open())
136    return false;
137
138  state_ = OPENED;
139
140  tracker_->Start(
141      target_render_process_id_, target_render_view_id_,
142      base::Bind(&Impl::OnTargetChanged, this));
143
144  return true;
145}
146
147void WebContentsAudioInputStream::Impl::Start(AudioInputCallback* callback) {
148  DCHECK(thread_checker_.CalledOnValidThread());
149  DCHECK(callback);
150
151  if (state_ != OPENED)
152    return;
153
154  callback_ = callback;
155  if (IsTargetLost()) {
156    ReportError();
157    callback_ = NULL;
158    return;
159  }
160
161  state_ = MIRRORING;
162  mixer_stream_->Start(callback);
163
164  StartMirroring();
165}
166
167void WebContentsAudioInputStream::Impl::Stop() {
168  DCHECK(thread_checker_.CalledOnValidThread());
169
170  if (state_ != MIRRORING)
171    return;
172
173  state_ = OPENED;
174
175  mixer_stream_->Stop();
176  callback_ = NULL;
177
178  if (!IsTargetLost())
179    StopMirroring();
180}
181
182void WebContentsAudioInputStream::Impl::Close() {
183  DCHECK(thread_checker_.CalledOnValidThread());
184
185  Stop();
186
187  if (state_ == OPENED) {
188    state_ = CONSTRUCTED;
189    tracker_->Stop();
190    mixer_stream_->Close();
191  }
192
193  DCHECK_EQ(CONSTRUCTED, state_);
194  state_ = CLOSED;
195}
196
197bool WebContentsAudioInputStream::Impl::IsTargetLost() const {
198  DCHECK(thread_checker_.CalledOnValidThread());
199
200  return target_render_process_id_ <= 0 || target_render_view_id_ <= 0;
201}
202
203void WebContentsAudioInputStream::Impl::ReportError() {
204  DCHECK(thread_checker_.CalledOnValidThread());
205
206  // TODO(miu): Need clean-up of AudioInputCallback interface in a future
207  // change, since its only implementation ignores the first argument entirely
208  callback_->OnError(NULL);
209}
210
211void WebContentsAudioInputStream::Impl::StartMirroring() {
212  DCHECK(thread_checker_.CalledOnValidThread());
213
214  BrowserThread::PostTask(
215      BrowserThread::IO,
216      FROM_HERE,
217      base::Bind(&AudioMirroringManager::StartMirroring,
218                 base::Unretained(mirroring_manager_),
219                 target_render_process_id_, target_render_view_id_,
220                 make_scoped_refptr(this)));
221}
222
223void WebContentsAudioInputStream::Impl::StopMirroring() {
224  DCHECK(thread_checker_.CalledOnValidThread());
225
226  BrowserThread::PostTask(
227      BrowserThread::IO,
228      FROM_HERE,
229      base::Bind(&AudioMirroringManager::StopMirroring,
230                 base::Unretained(mirroring_manager_),
231                 target_render_process_id_, target_render_view_id_,
232                 make_scoped_refptr(this)));
233}
234
235media::AudioOutputStream* WebContentsAudioInputStream::Impl::AddInput(
236    const media::AudioParameters& params) {
237  // Note: The closure created here holds a reference to "this," which will
238  // guarantee the VirtualAudioInputStream (mixer_stream_) outlives the
239  // VirtualAudioOutputStream.
240  return new media::VirtualAudioOutputStream(
241      params,
242      mixer_stream_.get(),
243      base::Bind(&Impl::ReleaseInput, this));
244}
245
246void WebContentsAudioInputStream::Impl::ReleaseInput(
247    media::VirtualAudioOutputStream* stream) {
248  delete stream;
249}
250
251void WebContentsAudioInputStream::Impl::OnTargetChanged(int render_process_id,
252                                                        int render_view_id) {
253  DCHECK(thread_checker_.CalledOnValidThread());
254
255  if (target_render_process_id_ == render_process_id &&
256      target_render_view_id_ == render_view_id) {
257    return;
258  }
259
260  DVLOG(1) << "Target RenderView has changed from "
261           << target_render_process_id_ << ':' << target_render_view_id_
262           << " to " << render_process_id << ':' << render_view_id;
263
264  if (state_ == MIRRORING)
265    StopMirroring();
266
267  target_render_process_id_ = render_process_id;
268  target_render_view_id_ = render_view_id;
269
270  if (state_ == MIRRORING) {
271    if (IsTargetLost()) {
272      ReportError();
273      Stop();
274    } else {
275      StartMirroring();
276    }
277  }
278}
279
280// static
281WebContentsAudioInputStream* WebContentsAudioInputStream::Create(
282    const std::string& device_id,
283    const media::AudioParameters& params,
284    const scoped_refptr<base::MessageLoopProxy>& worker_loop,
285    AudioMirroringManager* audio_mirroring_manager) {
286  int render_process_id;
287  int render_view_id;
288  if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
289          device_id, &render_process_id, &render_view_id)) {
290    return NULL;
291  }
292
293  return new WebContentsAudioInputStream(
294      render_process_id, render_view_id,
295      audio_mirroring_manager,
296      new WebContentsTracker(),
297      new media::VirtualAudioInputStream(
298          params, worker_loop,
299          media::VirtualAudioInputStream::AfterCloseCallback()));
300}
301
302WebContentsAudioInputStream::WebContentsAudioInputStream(
303    int render_process_id, int render_view_id,
304    AudioMirroringManager* mirroring_manager,
305    const scoped_refptr<WebContentsTracker>& tracker,
306    media::VirtualAudioInputStream* mixer_stream)
307    : impl_(new Impl(render_process_id, render_view_id,
308                     mirroring_manager, tracker, mixer_stream)) {}
309
310WebContentsAudioInputStream::~WebContentsAudioInputStream() {}
311
312bool WebContentsAudioInputStream::Open() {
313  return impl_->Open();
314}
315
316void WebContentsAudioInputStream::Start(AudioInputCallback* callback) {
317  impl_->Start(callback);
318}
319
320void WebContentsAudioInputStream::Stop() {
321  impl_->Stop();
322}
323
324void WebContentsAudioInputStream::Close() {
325  impl_->Close();
326  delete this;
327}
328
329double WebContentsAudioInputStream::GetMaxVolume() {
330  return impl_->mixer_stream()->GetMaxVolume();
331}
332
333void WebContentsAudioInputStream::SetVolume(double volume) {
334  impl_->mixer_stream()->SetVolume(volume);
335}
336
337double WebContentsAudioInputStream::GetVolume() {
338  return impl_->mixer_stream()->GetVolume();
339}
340
341void WebContentsAudioInputStream::SetAutomaticGainControl(bool enabled) {
342  impl_->mixer_stream()->SetAutomaticGainControl(enabled);
343}
344
345bool WebContentsAudioInputStream::GetAutomaticGainControl() {
346  return impl_->mixer_stream()->GetAutomaticGainControl();
347}
348
349}  // namespace content
350