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/media/capture/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/media/capture/audio_mirroring_manager.h"
15#include "content/browser/media/capture/web_contents_capture_util.h"
16#include "content/browser/media/capture/web_contents_tracker.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/render_frame_host.h"
19#include "content/public/browser/web_contents.h"
20#include "media/audio/virtual_audio_input_stream.h"
21#include "media/audio/virtual_audio_output_stream.h"
22#include "media/base/bind_to_current_loop.h"
23
24namespace content {
25
26class WebContentsAudioInputStream::Impl
27    : public base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>,
28      public AudioMirroringManager::MirroringDestination {
29 public:
30  // Takes ownership of |mixer_stream|.  The rest outlive this instance.
31  Impl(int render_process_id, int main_render_frame_id,
32       AudioMirroringManager* mirroring_manager,
33       const scoped_refptr<WebContentsTracker>& tracker,
34       media::VirtualAudioInputStream* mixer_stream);
35
36  // Open underlying VirtualAudioInputStream and start tracker.
37  bool Open();
38
39  // Start the underlying VirtualAudioInputStream and instruct
40  // AudioMirroringManager to begin a mirroring session.
41  void Start(AudioInputCallback* callback);
42
43  // Stop the underlying VirtualAudioInputStream and instruct
44  // AudioMirroringManager to shutdown a mirroring session.
45  void Stop();
46
47  // Close the underlying VirtualAudioInputStream and stop the tracker.
48  void Close();
49
50  // Accessor to underlying VirtualAudioInputStream.
51  media::VirtualAudioInputStream* mixer_stream() const {
52    return mixer_stream_.get();
53  }
54
55 private:
56  friend class base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>;
57
58  typedef AudioMirroringManager::SourceFrameRef SourceFrameRef;
59
60  enum State {
61    CONSTRUCTED,
62    OPENED,
63    MIRRORING,
64    CLOSED
65  };
66
67  virtual ~Impl();
68
69  // Notifies the consumer callback that the stream is now dead.
70  void ReportError();
71
72  // (Re-)Start/Stop mirroring by posting a call to AudioMirroringManager on the
73  // IO BrowserThread.
74  void StartMirroring();
75  void StopMirroring();
76
77  // Invoked on the UI thread to make sure WebContents muting is turned off for
78  // successful audio capture.
79  void UnmuteWebContentsAudio();
80
81  // AudioMirroringManager::MirroringDestination implementation
82  virtual void QueryForMatches(
83      const std::set<SourceFrameRef>& candidates,
84      const MatchesCallback& results_callback) OVERRIDE;
85  void QueryForMatchesOnUIThread(const std::set<SourceFrameRef>& candidates,
86                                 const MatchesCallback& results_callback);
87  virtual media::AudioOutputStream* AddInput(
88      const media::AudioParameters& params) OVERRIDE;
89
90  // Callback which is run when |stream| is closed.  Deletes |stream|.
91  void ReleaseInput(media::VirtualAudioOutputStream* stream);
92
93  // Called by WebContentsTracker when the target of the audio mirroring has
94  // changed.
95  void OnTargetChanged(RenderWidgetHost* target);
96
97  // Injected dependencies.
98  const int initial_render_process_id_;
99  const int initial_main_render_frame_id_;
100  AudioMirroringManager* const mirroring_manager_;
101  const scoped_refptr<WebContentsTracker> tracker_;
102  // The AudioInputStream implementation that handles the audio conversion and
103  // mixing details.
104  const scoped_ptr<media::VirtualAudioInputStream> mixer_stream_;
105
106  State state_;
107
108  // Set to true if |tracker_| reports a NULL target, which indicates the target
109  // is permanently lost.
110  bool is_target_lost_;
111
112  // Current callback used to consume the resulting mixed audio data.
113  AudioInputCallback* callback_;
114
115  base::ThreadChecker thread_checker_;
116
117  DISALLOW_COPY_AND_ASSIGN(Impl);
118};
119
120WebContentsAudioInputStream::Impl::Impl(
121    int render_process_id, int main_render_frame_id,
122    AudioMirroringManager* mirroring_manager,
123    const scoped_refptr<WebContentsTracker>& tracker,
124    media::VirtualAudioInputStream* mixer_stream)
125    : initial_render_process_id_(render_process_id),
126      initial_main_render_frame_id_(main_render_frame_id),
127      mirroring_manager_(mirroring_manager),
128      tracker_(tracker),
129      mixer_stream_(mixer_stream),
130      state_(CONSTRUCTED),
131      is_target_lost_(false),
132      callback_(NULL) {
133  DCHECK(mirroring_manager_);
134  DCHECK(tracker_.get());
135  DCHECK(mixer_stream_.get());
136
137  // WAIS::Impl can be constructed on any thread, but will DCHECK that all
138  // its methods from here on are called from the same thread.
139  thread_checker_.DetachFromThread();
140}
141
142WebContentsAudioInputStream::Impl::~Impl() {
143  DCHECK(state_ == CONSTRUCTED || state_ == CLOSED);
144}
145
146bool WebContentsAudioInputStream::Impl::Open() {
147  DCHECK(thread_checker_.CalledOnValidThread());
148
149  DCHECK_EQ(CONSTRUCTED, state_) << "Illegal to Open more than once.";
150
151  if (!mixer_stream_->Open())
152    return false;
153
154  state_ = OPENED;
155
156  tracker_->Start(
157      initial_render_process_id_, initial_main_render_frame_id_,
158      base::Bind(&Impl::OnTargetChanged, this));
159
160  return true;
161}
162
163void WebContentsAudioInputStream::Impl::Start(AudioInputCallback* callback) {
164  DCHECK(thread_checker_.CalledOnValidThread());
165  DCHECK(callback);
166
167  if (state_ != OPENED)
168    return;
169
170  callback_ = callback;
171  if (is_target_lost_) {
172    ReportError();
173    callback_ = NULL;
174    return;
175  }
176
177  state_ = MIRRORING;
178  mixer_stream_->Start(callback);
179
180  StartMirroring();
181
182  // WebContents audio muting is implemented as audio capture to nowhere.
183  // Unmuting will stop that audio capture, allowing AudioMirroringManager to
184  // divert audio capture to here.
185  BrowserThread::PostTask(
186      BrowserThread::UI,
187      FROM_HERE,
188      base::Bind(&Impl::UnmuteWebContentsAudio, this));
189}
190
191void WebContentsAudioInputStream::Impl::Stop() {
192  DCHECK(thread_checker_.CalledOnValidThread());
193
194  if (state_ != MIRRORING)
195    return;
196
197  state_ = OPENED;
198
199  mixer_stream_->Stop();
200  callback_ = NULL;
201
202  StopMirroring();
203}
204
205void WebContentsAudioInputStream::Impl::Close() {
206  DCHECK(thread_checker_.CalledOnValidThread());
207
208  Stop();
209
210  if (state_ == OPENED) {
211    state_ = CONSTRUCTED;
212    tracker_->Stop();
213    mixer_stream_->Close();
214  }
215
216  DCHECK_EQ(CONSTRUCTED, state_);
217  state_ = CLOSED;
218}
219
220void WebContentsAudioInputStream::Impl::ReportError() {
221  DCHECK(thread_checker_.CalledOnValidThread());
222
223  // TODO(miu): Need clean-up of AudioInputCallback interface in a future
224  // change, since its only implementation ignores the first argument entirely
225  callback_->OnError(NULL);
226}
227
228void WebContentsAudioInputStream::Impl::StartMirroring() {
229  DCHECK(thread_checker_.CalledOnValidThread());
230
231  BrowserThread::PostTask(
232      BrowserThread::IO,
233      FROM_HERE,
234      base::Bind(&AudioMirroringManager::StartMirroring,
235                 base::Unretained(mirroring_manager_),
236                 make_scoped_refptr(this)));
237}
238
239void WebContentsAudioInputStream::Impl::StopMirroring() {
240  DCHECK(thread_checker_.CalledOnValidThread());
241
242  BrowserThread::PostTask(
243      BrowserThread::IO,
244      FROM_HERE,
245      base::Bind(&AudioMirroringManager::StopMirroring,
246                 base::Unretained(mirroring_manager_),
247                 make_scoped_refptr(this)));
248}
249
250void WebContentsAudioInputStream::Impl::UnmuteWebContentsAudio() {
251  DCHECK_CURRENTLY_ON(BrowserThread::UI);
252
253  WebContents* const contents = tracker_->web_contents();
254  if (contents)
255    contents->SetAudioMuted(false);
256}
257
258void WebContentsAudioInputStream::Impl::QueryForMatches(
259    const std::set<SourceFrameRef>& candidates,
260    const MatchesCallback& results_callback) {
261  BrowserThread::PostTask(
262      BrowserThread::UI,
263      FROM_HERE,
264      base::Bind(&Impl::QueryForMatchesOnUIThread,
265                 this,
266                 candidates,
267                 media::BindToCurrentLoop(results_callback)));
268}
269
270void WebContentsAudioInputStream::Impl::QueryForMatchesOnUIThread(
271    const std::set<SourceFrameRef>& candidates,
272    const MatchesCallback& results_callback) {
273  DCHECK_CURRENTLY_ON(BrowserThread::UI);
274
275  std::set<SourceFrameRef> matches;
276  WebContents* const contents = tracker_->web_contents();
277  if (contents) {
278    // Add each ID to |matches| if it maps to a RenderFrameHost that maps to the
279    // currently-tracked WebContents.
280    for (std::set<SourceFrameRef>::const_iterator i = candidates.begin();
281         i != candidates.end(); ++i) {
282      WebContents* const contents_containing_frame =
283          WebContents::FromRenderFrameHost(
284              RenderFrameHost::FromID(i->first, i->second));
285      if (contents_containing_frame == contents)
286        matches.insert(*i);
287    }
288  }
289
290  results_callback.Run(matches);
291}
292
293media::AudioOutputStream* WebContentsAudioInputStream::Impl::AddInput(
294    const media::AudioParameters& params) {
295  // Note: The closure created here holds a reference to "this," which will
296  // guarantee the VirtualAudioInputStream (mixer_stream_) outlives the
297  // VirtualAudioOutputStream.
298  return new media::VirtualAudioOutputStream(
299      params,
300      mixer_stream_.get(),
301      base::Bind(&Impl::ReleaseInput, this));
302}
303
304void WebContentsAudioInputStream::Impl::ReleaseInput(
305    media::VirtualAudioOutputStream* stream) {
306  delete stream;
307}
308
309void WebContentsAudioInputStream::Impl::OnTargetChanged(
310    RenderWidgetHost* target) {
311  DCHECK(thread_checker_.CalledOnValidThread());
312
313  is_target_lost_ = !target;
314
315  if (state_ == MIRRORING) {
316    if (is_target_lost_) {
317      ReportError();
318      Stop();
319    } else {
320      StartMirroring();
321    }
322  }
323}
324
325// static
326WebContentsAudioInputStream* WebContentsAudioInputStream::Create(
327    const std::string& device_id,
328    const media::AudioParameters& params,
329    const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
330    AudioMirroringManager* audio_mirroring_manager) {
331  int render_process_id;
332  int main_render_frame_id;
333  if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
334          device_id, &render_process_id, &main_render_frame_id)) {
335    return NULL;
336  }
337
338  return new WebContentsAudioInputStream(
339      render_process_id, main_render_frame_id,
340      audio_mirroring_manager,
341      new WebContentsTracker(false),
342      new media::VirtualAudioInputStream(
343          params, worker_task_runner,
344          media::VirtualAudioInputStream::AfterCloseCallback()));
345}
346
347WebContentsAudioInputStream::WebContentsAudioInputStream(
348    int render_process_id, int main_render_frame_id,
349    AudioMirroringManager* mirroring_manager,
350    const scoped_refptr<WebContentsTracker>& tracker,
351    media::VirtualAudioInputStream* mixer_stream)
352    : impl_(new Impl(render_process_id, main_render_frame_id,
353                     mirroring_manager, tracker, mixer_stream)) {}
354
355WebContentsAudioInputStream::~WebContentsAudioInputStream() {}
356
357bool WebContentsAudioInputStream::Open() {
358  return impl_->Open();
359}
360
361void WebContentsAudioInputStream::Start(AudioInputCallback* callback) {
362  impl_->Start(callback);
363}
364
365void WebContentsAudioInputStream::Stop() {
366  impl_->Stop();
367}
368
369void WebContentsAudioInputStream::Close() {
370  impl_->Close();
371  delete this;
372}
373
374double WebContentsAudioInputStream::GetMaxVolume() {
375  return impl_->mixer_stream()->GetMaxVolume();
376}
377
378void WebContentsAudioInputStream::SetVolume(double volume) {
379  impl_->mixer_stream()->SetVolume(volume);
380}
381
382double WebContentsAudioInputStream::GetVolume() {
383  return impl_->mixer_stream()->GetVolume();
384}
385
386void WebContentsAudioInputStream::SetAutomaticGainControl(bool enabled) {
387  impl_->mixer_stream()->SetAutomaticGainControl(enabled);
388}
389
390bool WebContentsAudioInputStream::GetAutomaticGainControl() {
391  return impl_->mixer_stream()->GetAutomaticGainControl();
392}
393
394bool WebContentsAudioInputStream::IsMuted() {
395  return false;
396}
397
398}  // namespace content
399