blob: a75eb3c6476f8c0d80c64fbea0085362b05d866d [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/audio_mirroring_manager.h"
#include <algorithm>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"
namespace content {
namespace {
base::LazyInstance<AudioMirroringManager>::Leaky g_audio_mirroring_manager =
LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
AudioMirroringManager* AudioMirroringManager::GetInstance() {
return g_audio_mirroring_manager.Pointer();
}
AudioMirroringManager::AudioMirroringManager() {
// Only *after* construction, check that AudioMirroringManager is being
// invoked on the same single thread.
thread_checker_.DetachFromThread();
}
AudioMirroringManager::~AudioMirroringManager() {}
void AudioMirroringManager::AddDiverter(
int render_process_id, int render_frame_id, Diverter* diverter) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(diverter);
// DCHECK(diverter not already in routes_)
#ifndef NDEBUG
for (StreamRoutes::const_iterator it = routes_.begin();
it != routes_.end(); ++it) {
DCHECK_NE(diverter, it->diverter);
}
#endif
routes_.push_back(StreamRoutingState(
SourceFrameRef(render_process_id, render_frame_id),
diverter));
// Query existing destinations to see whether to immediately start diverting
// the stream.
std::set<SourceFrameRef> candidates;
candidates.insert(routes_.back().source_render_frame);
InitiateQueriesToFindNewDestination(NULL, candidates);
}
void AudioMirroringManager::RemoveDiverter(Diverter* diverter) {
DCHECK(thread_checker_.CalledOnValidThread());
// Find and remove the entry from the routing table. If the stream is being
// diverted, it is stopped.
for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
if (it->diverter == diverter) {
ChangeRoute(&(*it), NULL);
routes_.erase(it);
return;
}
}
NOTREACHED();
}
void AudioMirroringManager::StartMirroring(MirroringDestination* destination) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(destination);
// Insert an entry into the set of active mirroring sessions, if this is a
// previously-unknown destination.
if (std::find(sessions_.begin(), sessions_.end(), destination) ==
sessions_.end()) {
sessions_.push_back(destination);
}
// Query the MirroringDestination to see which of the audio streams should be
// diverted.
std::set<SourceFrameRef> candidates;
for (StreamRoutes::const_iterator it = routes_.begin(); it != routes_.end();
++it) {
if (!it->destination || it->destination == destination)
candidates.insert(it->source_render_frame);
}
if (!candidates.empty()) {
destination->QueryForMatches(
candidates,
base::Bind(&AudioMirroringManager::UpdateRoutesToDestination,
base::Unretained(this),
destination,
false));
}
}
void AudioMirroringManager::StopMirroring(MirroringDestination* destination) {
DCHECK(thread_checker_.CalledOnValidThread());
// Stop diverting each audio stream in the mirroring session being stopped.
// Each stopped stream becomes a candidate to be diverted to another
// destination.
std::set<SourceFrameRef> redivert_candidates;
for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
if (it->destination == destination) {
ChangeRoute(&(*it), NULL);
redivert_candidates.insert(it->source_render_frame);
}
}
if (!redivert_candidates.empty())
InitiateQueriesToFindNewDestination(destination, redivert_candidates);
// Remove the entry from the set of active mirroring sessions.
const Destinations::iterator dest_it =
std::find(sessions_.begin(), sessions_.end(), destination);
if (dest_it == sessions_.end()) {
NOTREACHED();
return;
}
sessions_.erase(dest_it);
}
void AudioMirroringManager::InitiateQueriesToFindNewDestination(
MirroringDestination* old_destination,
const std::set<SourceFrameRef>& candidates) {
DCHECK(thread_checker_.CalledOnValidThread());
for (Destinations::const_iterator it = sessions_.begin();
it != sessions_.end(); ++it) {
if (*it != old_destination) {
(*it)->QueryForMatches(
candidates,
base::Bind(&AudioMirroringManager::UpdateRoutesToDestination,
base::Unretained(this),
*it,
true));
}
}
}
void AudioMirroringManager::UpdateRoutesToDestination(
MirroringDestination* destination,
bool add_only,
const std::set<SourceFrameRef>& matches) {
DCHECK(thread_checker_.CalledOnValidThread());
if (std::find(sessions_.begin(), sessions_.end(), destination) ==
sessions_.end()) {
return; // Query result callback invoked after StopMirroring().
}
DVLOG(1) << (add_only ? "Add " : "Replace with ") << matches.size()
<< " routes to MirroringDestination@" << destination;
// Start/stop diverting based on |matches|. Any stopped stream becomes a
// candidate to be diverted to another destination.
std::set<SourceFrameRef> redivert_candidates;
for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
if (matches.find(it->source_render_frame) != matches.end()) {
// Only change the route if the stream is not already being diverted.
if (!it->destination)
ChangeRoute(&(*it), destination);
} else if (!add_only) {
// Only stop diverting if the stream is currently routed to |destination|.
if (it->destination == destination) {
ChangeRoute(&(*it), NULL);
redivert_candidates.insert(it->source_render_frame);
}
}
}
if (!redivert_candidates.empty())
InitiateQueriesToFindNewDestination(destination, redivert_candidates);
}
// static
void AudioMirroringManager::ChangeRoute(
StreamRoutingState* route, MirroringDestination* new_destination) {
if (route->destination == new_destination)
return; // No change.
if (route->destination) {
DVLOG(1) << "Stop diverting render_process_id:render_frame_id="
<< route->source_render_frame.first << ':'
<< route->source_render_frame.second
<< " --> MirroringDestination@" << route->destination;
route->diverter->StopDiverting();
route->destination = NULL;
}
if (new_destination) {
DVLOG(1) << "Start diverting of render_process_id:render_frame_id="
<< route->source_render_frame.first << ':'
<< route->source_render_frame.second
<< " --> MirroringDestination@" << new_destination;
route->diverter->StartDiverting(
new_destination->AddInput(route->diverter->GetAudioParameters()));
route->destination = new_destination;
}
}
AudioMirroringManager::StreamRoutingState::StreamRoutingState(
const SourceFrameRef& source_frame, Diverter* stream_diverter)
: source_render_frame(source_frame),
diverter(stream_diverter),
destination(NULL) {}
AudioMirroringManager::StreamRoutingState::~StreamRoutingState() {}
} // namespace content