blob: 670820dc3a6ad0f446d429cd97b4e3a7484f5c67 [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_manager_base.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_output_dispatcher_impl.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/audio_output_resampler.h"
#include "media/audio/fake_audio_input_stream.h"
#include "media/audio/fake_audio_output_stream.h"
#include "media/base/media_switches.h"
namespace media {
namespace {
const int kStreamCloseDelaySeconds = 5;
// Default maximum number of output streams that can be open simultaneously
// for all platforms.
const int kDefaultMaxOutputStreams = 32;
// Default maximum number of input streams that can be open simultaneously
// for all platforms.
const int kDefaultMaxInputStreams = 32;
const int kMaxInputChannels = 3;
} // namespace
struct AudioManagerBase::DispatcherParams {
DispatcherParams(const AudioParameters& input,
const AudioParameters& output,
const std::string& output_device_id)
: input_params(input),
output_params(output),
output_device_id(output_device_id) {}
~DispatcherParams() {}
const AudioParameters input_params;
const AudioParameters output_params;
const std::string output_device_id;
std::unique_ptr<AudioOutputDispatcher> dispatcher;
private:
DISALLOW_COPY_AND_ASSIGN(DispatcherParams);
};
class AudioManagerBase::CompareByParams {
public:
explicit CompareByParams(const DispatcherParams* dispatcher)
: dispatcher_(dispatcher) {}
bool operator()(DispatcherParams* dispatcher_in) const {
// We will reuse the existing dispatcher when:
// 1) Unified IO is not used, input_params and output_params of the
// existing dispatcher are the same as the requested dispatcher.
// 2) Unified IO is used, input_params and output_params of the existing
// dispatcher are the same as the request dispatcher.
return (dispatcher_->input_params.Equals(dispatcher_in->input_params) &&
dispatcher_->output_params.Equals(dispatcher_in->output_params) &&
dispatcher_->output_device_id == dispatcher_in->output_device_id);
}
private:
const DispatcherParams* dispatcher_;
};
AudioManagerBase::AudioManagerBase(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner,
AudioLogFactory* audio_log_factory)
: AudioManager(std::move(task_runner), std::move(worker_task_runner)),
max_num_output_streams_(kDefaultMaxOutputStreams),
max_num_input_streams_(kDefaultMaxInputStreams),
num_output_streams_(0),
// TODO(dalecurtis): Switch this to an base::ObserverListThreadSafe, so we
// don't
// block the UI thread when swapping devices.
output_listeners_(
base::ObserverList<AudioDeviceListener>::NOTIFY_EXISTING_ONLY),
audio_log_factory_(audio_log_factory) {}
AudioManagerBase::~AudioManagerBase() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
// All the output streams should have been deleted.
CHECK_EQ(0, num_output_streams_);
// All the input streams should have been deleted.
CHECK(input_streams_.empty());
}
base::string16 AudioManagerBase::GetAudioInputDeviceModel() {
return base::string16();
}
void AudioManagerBase::GetAudioInputDeviceDescriptions(
AudioDeviceDescriptions* device_descriptions) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceNames device_names;
GetAudioInputDeviceNames(&device_names);
for (const media::AudioDeviceName& name : device_names) {
device_descriptions->emplace_back(name.device_name, name.unique_id,
GetGroupIDInput(name.unique_id));
}
}
void AudioManagerBase::GetAudioOutputDeviceDescriptions(
AudioDeviceDescriptions* device_descriptions) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceNames device_names;
GetAudioOutputDeviceNames(&device_names);
for (const media::AudioDeviceName& name : device_names) {
device_descriptions->emplace_back(name.device_name, name.unique_id,
GetGroupIDOutput(name.unique_id));
}
}
AudioOutputStream* AudioManagerBase::MakeAudioOutputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
if (!params.IsValid()) {
DLOG(ERROR) << "Audio parameters are invalid";
return NULL;
}
// Limit the number of audio streams opened. This is to prevent using
// excessive resources for a large number of audio streams. More
// importantly it prevents instability on certain systems.
// See bug: http://crbug.com/30242.
if (num_output_streams_ >= max_num_output_streams_) {
DLOG(ERROR) << "Number of opened output audio streams "
<< num_output_streams_
<< " exceed the max allowed number "
<< max_num_output_streams_;
return NULL;
}
AudioOutputStream* stream;
switch (params.format()) {
case AudioParameters::AUDIO_PCM_LINEAR:
DCHECK(AudioDeviceDescription::IsDefaultDevice(device_id))
<< "AUDIO_PCM_LINEAR supports only the default device.";
stream = MakeLinearOutputStream(params, log_callback);
break;
case AudioParameters::AUDIO_PCM_LOW_LATENCY:
stream = MakeLowLatencyOutputStream(params, device_id, log_callback);
break;
case AudioParameters::AUDIO_BITSTREAM_AC3:
case AudioParameters::AUDIO_BITSTREAM_EAC3:
// TODO(tsunghung): create passthrough output stream.
NOTREACHED();
stream = nullptr;
break;
case AudioParameters::AUDIO_FAKE:
stream = FakeAudioOutputStream::MakeFakeStream(this, params);
break;
default:
stream = NULL;
break;
}
if (stream) {
++num_output_streams_;
}
return stream;
}
AudioInputStream* AudioManagerBase::MakeAudioInputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
if (!params.IsValid() || (params.channels() > kMaxInputChannels) ||
device_id.empty()) {
DLOG(ERROR) << "Audio parameters are invalid for device " << device_id;
return NULL;
}
if (input_stream_count() >= max_num_input_streams_) {
DLOG(ERROR) << "Number of opened input audio streams "
<< input_stream_count() << " exceed the max allowed number "
<< max_num_input_streams_;
return NULL;
}
DVLOG(2) << "Creating a new AudioInputStream with buffer size = "
<< params.frames_per_buffer();
AudioInputStream* stream;
switch (params.format()) {
case AudioParameters::AUDIO_PCM_LINEAR:
stream = MakeLinearInputStream(params, device_id, log_callback);
break;
case AudioParameters::AUDIO_PCM_LOW_LATENCY:
stream = MakeLowLatencyInputStream(params, device_id, log_callback);
break;
case AudioParameters::AUDIO_FAKE:
stream = FakeAudioInputStream::MakeFakeStream(this, params);
break;
default:
stream = NULL;
break;
}
if (stream) {
input_streams_.insert(stream);
}
return stream;
}
AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
const AudioParameters& params,
const std::string& device_id) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
// If the caller supplied an empty device id to select the default device,
// we fetch the actual device id of the default device so that the lookup
// will find the correct device regardless of whether it was opened as
// "default" or via the specific id.
// NOTE: Implementations that don't yet support opening non-default output
// devices may return an empty string from GetDefaultOutputDeviceID().
std::string output_device_id =
AudioDeviceDescription::IsDefaultDevice(device_id)
? GetDefaultOutputDeviceID()
: device_id;
// If we're not using AudioOutputResampler our output parameters are the same
// as our input parameters.
AudioParameters output_params = params;
if (params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
output_params =
GetPreferredOutputStreamParameters(output_device_id, params);
// Ensure we only pass on valid output parameters.
if (!output_params.IsValid()) {
// We've received invalid audio output parameters, so switch to a mock
// output device based on the input parameters. This may happen if the OS
// provided us junk values for the hardware configuration.
LOG(ERROR) << "Invalid audio output parameters received; using fake "
<< "audio path. Channels: " << output_params.channels() << ", "
<< "Sample Rate: " << output_params.sample_rate() << ", "
<< "Bits Per Sample: " << output_params.bits_per_sample()
<< ", Frames Per Buffer: "
<< output_params.frames_per_buffer();
// Tell the AudioManager to create a fake output device.
output_params = params;
output_params.set_format(AudioParameters::AUDIO_FAKE);
} else if (params.effects() != output_params.effects()) {
// Turn off effects that weren't requested.
output_params.set_effects(params.effects() & output_params.effects());
}
}
DispatcherParams* dispatcher_params =
new DispatcherParams(params, output_params, output_device_id);
AudioOutputDispatchers::iterator it =
std::find_if(output_dispatchers_.begin(), output_dispatchers_.end(),
CompareByParams(dispatcher_params));
if (it != output_dispatchers_.end()) {
delete dispatcher_params;
return (*it)->dispatcher->CreateStreamProxy();
}
const base::TimeDelta kCloseDelay =
base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
std::unique_ptr<AudioOutputDispatcher> dispatcher;
if (output_params.format() != AudioParameters::AUDIO_FAKE) {
dispatcher = base::MakeUnique<AudioOutputResampler>(
this, params, output_params, output_device_id, kCloseDelay);
} else {
dispatcher = base::MakeUnique<AudioOutputDispatcherImpl>(
this, output_params, output_device_id, kCloseDelay);
}
dispatcher_params->dispatcher = std::move(dispatcher);
output_dispatchers_.push_back(dispatcher_params);
return dispatcher_params->dispatcher->CreateStreamProxy();
}
void AudioManagerBase::ShowAudioInputSettings() {
}
void AudioManagerBase::GetAudioInputDeviceNames(
AudioDeviceNames* device_names) {
}
void AudioManagerBase::GetAudioOutputDeviceNames(
AudioDeviceNames* device_names) {
}
void AudioManagerBase::ReleaseOutputStream(AudioOutputStream* stream) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK(stream);
CHECK_GT(num_output_streams_, 0);
// TODO(xians) : Have a clearer destruction path for the AudioOutputStream.
// For example, pass the ownership to AudioManager so it can delete the
// streams.
--num_output_streams_;
delete stream;
}
void AudioManagerBase::ReleaseInputStream(AudioInputStream* stream) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK(stream);
// TODO(xians) : Have a clearer destruction path for the AudioInputStream.
CHECK_EQ(1u, input_streams_.erase(stream));
delete stream;
}
void AudioManagerBase::Shutdown() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
// Close all output streams.
output_dispatchers_.clear();
#if defined(OS_MACOSX)
// On mac, AudioManager runs on the main thread, loop for which stops
// processing task queue at this point. So even if tasks to close the
// streams are enqueued, they would not run leading to CHECKs getting hit
// in the destructor about open streams. Close them explicitly here.
// crbug.com/608049.
for (auto iter = input_streams_.begin(); iter != input_streams_.end();) {
// Note: Closing the stream will invalidate the iterator.
// Increment the iterator before closing the stream.
AudioInputStream* stream = *iter++;
stream->Close();
}
CHECK(input_streams_.empty());
#endif // OS_MACOSX
}
void AudioManagerBase::AddOutputDeviceChangeListener(
AudioDeviceListener* listener) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
output_listeners_.AddObserver(listener);
}
void AudioManagerBase::RemoveOutputDeviceChangeListener(
AudioDeviceListener* listener) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
output_listeners_.RemoveObserver(listener);
}
void AudioManagerBase::NotifyAllOutputDeviceChangeListeners() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DVLOG(1) << "Firing OnDeviceChange() notifications.";
for (auto& observer : output_listeners_)
observer.OnDeviceChange();
}
AudioParameters AudioManagerBase::GetDefaultOutputStreamParameters() {
return GetPreferredOutputStreamParameters(GetDefaultOutputDeviceID(),
AudioParameters());
}
AudioParameters AudioManagerBase::GetOutputStreamParameters(
const std::string& device_id) {
return GetPreferredOutputStreamParameters(device_id,
AudioParameters());
}
AudioParameters AudioManagerBase::GetInputStreamParameters(
const std::string& device_id) {
NOTREACHED();
return AudioParameters();
}
std::string AudioManagerBase::GetAssociatedOutputDeviceID(
const std::string& input_device_id) {
return "";
}
std::string AudioManagerBase::GetGroupIDOutput(
const std::string& output_device_id) {
if (output_device_id == AudioDeviceDescription::kDefaultDeviceId) {
std::string real_device_id = GetDefaultOutputDeviceID();
if (!real_device_id.empty()) {
return real_device_id;
}
}
return output_device_id;
}
std::string AudioManagerBase::GetGroupIDInput(
const std::string& input_device_id) {
std::string output_device_id = GetAssociatedOutputDeviceID(input_device_id);
if (output_device_id.empty()) {
// Some characters are added to avoid accidentally
// giving the input the same group id as an output.
return input_device_id + "input";
}
return GetGroupIDOutput(output_device_id);
}
std::string AudioManagerBase::GetDefaultOutputDeviceID() {
return "";
}
// static
int AudioManagerBase::GetUserBufferSize() {
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
int buffer_size = 0;
std::string buffer_size_str(cmd_line->GetSwitchValueASCII(
switches::kAudioBufferSize));
if (base::StringToInt(buffer_size_str, &buffer_size) && buffer_size > 0)
return buffer_size;
return 0;
}
std::unique_ptr<AudioLog> AudioManagerBase::CreateAudioLog(
AudioLogFactory::AudioComponent component) {
return audio_log_factory_->CreateAudioLog(component);
}
void AudioManagerBase::SetMaxStreamCountForTesting(int max_input,
int max_output) {
max_num_output_streams_ = max_output;
max_num_input_streams_ = max_input;
}
} // namespace media