blob: 932be8196d782c327cfea410f59c8537e57634ff [file] [log] [blame]
// Copyright (c) 2012 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 "media/audio/audio_output_dispatcher_impl.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "media/audio/audio_logging.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_output_proxy.h"
namespace media {
AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
AudioManager* audio_manager,
const AudioParameters& params,
const std::string& output_device_id,
base::TimeDelta close_delay)
: AudioOutputDispatcher(audio_manager),
params_(params),
device_id_(output_device_id),
idle_proxies_(0),
close_timer_(FROM_HERE,
close_delay,
this,
&AudioOutputDispatcherImpl::CloseAllIdleStreams),
audio_stream_id_(0),
weak_factory_(this) {
DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
}
AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
// Stop all active streams.
for (auto& iter : proxy_to_physical_map_) {
StopPhysicalStream(iter.second);
}
// Close all idle streams immediately. The |close_timer_| will handle
// invalidating any outstanding tasks upon its destruction.
CloseAllIdleStreams();
// All idle physical streams must have been closed during shutdown.
CHECK(idle_streams_.empty());
}
AudioOutputProxy* AudioOutputDispatcherImpl::CreateStreamProxy() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
return new AudioOutputProxy(weak_factory_.GetWeakPtr());
}
bool AudioOutputDispatcherImpl::OpenStream() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
// Ensure that there is at least one open stream.
if (idle_streams_.empty() && !CreateAndOpenStream())
return false;
++idle_proxies_;
close_timer_.Reset();
return true;
}
bool AudioOutputDispatcherImpl::StartStream(
AudioOutputStream::AudioSourceCallback* callback,
AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(proxy_to_physical_map_.find(stream_proxy) ==
proxy_to_physical_map_.end());
if (idle_streams_.empty() && !CreateAndOpenStream())
return false;
AudioOutputStream* physical_stream = idle_streams_.back();
idle_streams_.pop_back();
DCHECK_GT(idle_proxies_, 0u);
--idle_proxies_;
double volume = 0;
stream_proxy->GetVolume(&volume);
physical_stream->SetVolume(volume);
DCHECK(base::ContainsKey(audio_logs_, physical_stream));
AudioLog* const audio_log = audio_logs_[physical_stream].get();
audio_log->OnSetVolume(volume);
physical_stream->Start(callback);
audio_log->OnStarted();
proxy_to_physical_map_[stream_proxy] = physical_stream;
close_timer_.Reset();
return true;
}
void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
auto it = proxy_to_physical_map_.find(stream_proxy);
DCHECK(it != proxy_to_physical_map_.end());
StopPhysicalStream(it->second);
proxy_to_physical_map_.erase(it);
++idle_proxies_;
}
void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
double volume) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
auto it = proxy_to_physical_map_.find(stream_proxy);
if (it != proxy_to_physical_map_.end()) {
AudioOutputStream* physical_stream = it->second;
physical_stream->SetVolume(volume);
DCHECK(base::ContainsKey(audio_logs_, physical_stream));
audio_logs_[physical_stream]->OnSetVolume(volume);
}
}
void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK_GT(idle_proxies_, 0u);
--idle_proxies_;
// Leave at least a single stream running until the close timer fires to help
// cycle time when streams are opened and closed repeatedly.
CloseIdleStreams(std::max(idle_proxies_, static_cast<size_t>(1)));
close_timer_.Reset();
}
bool AudioOutputDispatcherImpl::HasOutputProxies() const {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
return idle_proxies_ || !proxy_to_physical_map_.empty();
}
bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
const int stream_id = audio_stream_id_++;
std::unique_ptr<AudioLog> audio_log = audio_manager()->CreateAudioLog(
AudioLogFactory::AUDIO_OUTPUT_STREAM, stream_id);
AudioOutputStream* stream = audio_manager()->MakeAudioOutputStream(
params_, device_id_,
base::BindRepeating(&AudioLog::OnLogMessage,
base::Unretained(audio_log.get())));
if (!stream)
return false;
if (!stream->Open()) {
stream->Close();
return false;
}
audio_log->OnCreated(params_, device_id_);
audio_logs_[stream] = std::move(audio_log);
idle_streams_.push_back(stream);
return true;
}
void AudioOutputDispatcherImpl::CloseAllIdleStreams() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
CloseIdleStreams(0);
}
void AudioOutputDispatcherImpl::CloseIdleStreams(size_t keep_alive) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
if (idle_streams_.size() <= keep_alive)
return;
for (size_t i = keep_alive; i < idle_streams_.size(); ++i) {
AudioOutputStream* stream = idle_streams_[i];
stream->Close();
auto it = audio_logs_.find(stream);
DCHECK(it != audio_logs_.end());
it->second->OnClosed();
audio_logs_.erase(it);
}
idle_streams_.erase(idle_streams_.begin() + keep_alive, idle_streams_.end());
}
void AudioOutputDispatcherImpl::StopPhysicalStream(AudioOutputStream* stream) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
stream->Stop();
DCHECK(base::ContainsKey(audio_logs_, stream));
audio_logs_[stream]->OnStopped();
idle_streams_.push_back(stream);
close_timer_.Reset();
}
} // namespace media