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/audio_mirroring_manager.h"
6
7#include "content/public/browser/browser_thread.h"
8
9namespace content {
10
11namespace {
12
13// Debug utility to make sure methods of AudioMirroringManager are not invoked
14// more than once in a single call stack.  In release builds, this compiles to
15// nothing and gets completely optimized out.
16class ReentrancyGuard {
17 public:
18#ifdef NDEBUG
19  ReentrancyGuard() {}
20  ~ReentrancyGuard() {}
21#else
22  ReentrancyGuard() {
23    DCHECK(!inside_a_method_);
24    inside_a_method_ = true;
25  }
26  ~ReentrancyGuard() {
27    inside_a_method_ = false;
28  }
29
30  static bool inside_a_method_;  // Safe to be static, since AMM is a singleton.
31#endif
32};
33
34#ifndef NDEBUG
35bool ReentrancyGuard::inside_a_method_ = false;
36#endif
37
38}  // namespace
39
40AudioMirroringManager::AudioMirroringManager() {}
41
42AudioMirroringManager::~AudioMirroringManager() {
43  DCHECK(diverters_.empty());
44  DCHECK(sessions_.empty());
45}
46
47void AudioMirroringManager::AddDiverter(
48    int render_process_id, int render_view_id, Diverter* diverter) {
49  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
50  ReentrancyGuard guard;
51  DCHECK(diverter);
52
53  // DCHECK(diverter not already in diverters_ under any key)
54#ifndef NDEBUG
55  for (DiverterMap::const_iterator it = diverters_.begin();
56       it != diverters_.end(); ++it) {
57    DCHECK_NE(diverter, it->second);
58  }
59#endif
60
61  // Add the diverter to the set of active diverters.
62  const Target target(render_process_id, render_view_id);
63  diverters_.insert(std::make_pair(target, diverter));
64
65  // If a mirroring session is active, start diverting the audio stream
66  // immediately.
67  SessionMap::iterator session_it = sessions_.find(target);
68  if (session_it != sessions_.end()) {
69    diverter->StartDiverting(
70        session_it->second->AddInput(diverter->GetAudioParameters()));
71  }
72}
73
74void AudioMirroringManager::RemoveDiverter(
75    int render_process_id, int render_view_id, Diverter* diverter) {
76  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
77  ReentrancyGuard guard;
78
79  // Stop diverting the audio stream if a mirroring session is active.
80  const Target target(render_process_id, render_view_id);
81  SessionMap::iterator session_it = sessions_.find(target);
82  if (session_it != sessions_.end())
83    diverter->StopDiverting();
84
85  // Remove the diverter from the set of active diverters.
86  for (DiverterMap::iterator it = diverters_.lower_bound(target);
87       it != diverters_.end() && it->first == target; ++it) {
88    if (it->second == diverter) {
89      diverters_.erase(it);
90      break;
91    }
92  }
93}
94
95void AudioMirroringManager::StartMirroring(
96    int render_process_id, int render_view_id,
97    MirroringDestination* destination) {
98  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
99  ReentrancyGuard guard;
100  DCHECK(destination);
101
102  // Insert an entry into the set of active mirroring sessions.  If a mirroring
103  // session is already active for |render_process_id| + |render_view_id|,
104  // replace the entry.
105  const Target target(render_process_id, render_view_id);
106  SessionMap::iterator session_it = sessions_.find(target);
107  MirroringDestination* old_destination;
108  if (session_it == sessions_.end()) {
109    old_destination = NULL;
110    sessions_.insert(std::make_pair(target, destination));
111
112    DVLOG(1) << "Start mirroring render_process_id:render_view_id="
113             << render_process_id << ':' << render_view_id
114             << " --> MirroringDestination@" << destination;
115  } else {
116    old_destination = session_it->second;
117    session_it->second = destination;
118
119    DVLOG(1) << "Switch mirroring of render_process_id:render_view_id="
120             << render_process_id << ':' << render_view_id
121             << "  MirroringDestination@" << old_destination
122             << " --> MirroringDestination@" << destination;
123  }
124
125  // Divert audio streams coming from |target| to |destination|.  If streams
126  // were already diverted to the |old_destination|, remove them.
127  for (DiverterMap::iterator it = diverters_.lower_bound(target);
128       it != diverters_.end() && it->first == target; ++it) {
129    Diverter* const diverter = it->second;
130    if (old_destination)
131      diverter->StopDiverting();
132    diverter->StartDiverting(
133        destination->AddInput(diverter->GetAudioParameters()));
134  }
135}
136
137void AudioMirroringManager::StopMirroring(
138    int render_process_id, int render_view_id,
139    MirroringDestination* destination) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
141  ReentrancyGuard guard;
142
143  // Stop mirroring if there is an active session *and* the destination
144  // matches.
145  const Target target(render_process_id, render_view_id);
146  SessionMap::iterator session_it = sessions_.find(target);
147  if (session_it == sessions_.end() || destination != session_it->second)
148    return;
149
150  DVLOG(1) << "Stop mirroring render_process_id:render_view_id="
151           << render_process_id << ':' << render_view_id
152           << " --> MirroringDestination@" << destination;
153
154  // Stop diverting each audio stream in the mirroring session being stopped.
155  for (DiverterMap::iterator it = diverters_.lower_bound(target);
156       it != diverters_.end() && it->first == target; ++it) {
157    it->second->StopDiverting();
158  }
159
160  // Remove the entry from the set of active mirroring sessions.
161  sessions_.erase(session_it);
162}
163
164}  // namespace content
165