| // 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/strings/string_number_conversions.h" |
| #include "build/build_config.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 { |
| |
| static const int kStreamCloseDelaySeconds = 5; |
| |
| // Default maximum number of output streams that can be open simultaneously |
| // for all platforms. |
| static const int kDefaultMaxOutputStreams = 16; |
| |
| // Default maximum number of input streams that can be open simultaneously |
| // for all platforms. |
| static const int kDefaultMaxInputStreams = 16; |
| |
| static const int kMaxInputChannels = 3; |
| |
| const char AudioManagerBase::kDefaultDeviceName[] = "Default"; |
| const char AudioManagerBase::kDefaultDeviceId[] = "default"; |
| const char AudioManagerBase::kLoopbackInputDeviceId[] = "loopback"; |
| |
| 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; |
| scoped_refptr<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 == dispatcher_in->input_params && |
| dispatcher_->output_params == dispatcher_in->output_params && |
| dispatcher_->output_device_id == dispatcher_in->output_device_id); |
| } |
| |
| private: |
| const DispatcherParams* dispatcher_; |
| }; |
| |
| AudioManagerBase::AudioManagerBase(AudioLogFactory* audio_log_factory) |
| : max_num_output_streams_(kDefaultMaxOutputStreams), |
| max_num_input_streams_(kDefaultMaxInputStreams), |
| num_output_streams_(0), |
| num_input_streams_(0), |
| // TODO(dalecurtis): Switch this to an ObserverListThreadSafe, so we don't |
| // block the UI thread when swapping devices. |
| output_listeners_( |
| ObserverList<AudioDeviceListener>::NOTIFY_EXISTING_ONLY), |
| audio_thread_("AudioThread"), |
| audio_log_factory_(audio_log_factory) { |
| #if defined(OS_WIN) |
| audio_thread_.init_com_with_mta(true); |
| #elif defined(OS_MACOSX) |
| // CoreAudio calls must occur on the main thread of the process, which in our |
| // case is sadly the browser UI thread. Failure to execute calls on the right |
| // thread leads to crashes and odd behavior. See http://crbug.com/158170. |
| // TODO(dalecurtis): We should require the message loop to be passed in. |
| if (base::MessageLoopForUI::IsCurrent()) { |
| task_runner_ = base::MessageLoopProxy::current(); |
| return; |
| } |
| #endif |
| |
| CHECK(audio_thread_.Start()); |
| task_runner_ = audio_thread_.message_loop_proxy(); |
| } |
| |
| AudioManagerBase::~AudioManagerBase() { |
| // The platform specific AudioManager implementation must have already |
| // stopped the audio thread. Otherwise, we may destroy audio streams before |
| // stopping the thread, resulting an unexpected behavior. |
| // This way we make sure activities of the audio streams are all stopped |
| // before we destroy them. |
| CHECK(!audio_thread_.IsRunning()); |
| // All the output streams should have been deleted. |
| DCHECK_EQ(0, num_output_streams_); |
| // All the input streams should have been deleted. |
| DCHECK_EQ(0, num_input_streams_); |
| } |
| |
| base::string16 AudioManagerBase::GetAudioInputDeviceModel() { |
| return base::string16(); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> AudioManagerBase::GetTaskRunner() { |
| return task_runner_; |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| AudioManagerBase::GetWorkerTaskRunner() { |
| // Lazily start the worker thread. |
| if (!audio_thread_.IsRunning()) |
| CHECK(audio_thread_.Start()); |
| |
| return audio_thread_.message_loop_proxy(); |
| } |
| |
| AudioOutputStream* AudioManagerBase::MakeAudioOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id) { |
| // TODO(miu): Fix ~50 call points across several unit test modules to call |
| // this method on the audio thread, then uncomment the following: |
| // DCHECK(task_runner_->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(device_id.empty()) |
| << "AUDIO_PCM_LINEAR supports only the default device."; |
| stream = MakeLinearOutputStream(params); |
| break; |
| case AudioParameters::AUDIO_PCM_LOW_LATENCY: |
| stream = MakeLowLatencyOutputStream(params, device_id); |
| 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) { |
| // TODO(miu): Fix ~20 call points across several unit test modules to call |
| // this method on the audio thread, then uncomment the following: |
| // DCHECK(task_runner_->BelongsToCurrentThread()); |
| |
| if (!params.IsValid() || (params.channels() > kMaxInputChannels) || |
| device_id.empty()) { |
| DLOG(ERROR) << "Audio parameters are invalid for device " << device_id; |
| return NULL; |
| } |
| |
| if (num_input_streams_ >= max_num_input_streams_) { |
| DLOG(ERROR) << "Number of opened input audio streams " |
| << num_input_streams_ |
| << " exceed the max allowed number " << max_num_input_streams_; |
| return NULL; |
| } |
| |
| AudioInputStream* stream; |
| switch (params.format()) { |
| case AudioParameters::AUDIO_PCM_LINEAR: |
| stream = MakeLinearInputStream(params, device_id); |
| break; |
| case AudioParameters::AUDIO_PCM_LOW_LATENCY: |
| stream = MakeLowLatencyInputStream(params, device_id); |
| break; |
| case AudioParameters::AUDIO_FAKE: |
| stream = FakeAudioInputStream::MakeFakeStream(this, params); |
| break; |
| default: |
| stream = NULL; |
| break; |
| } |
| |
| if (stream) { |
| ++num_input_streams_; |
| } |
| |
| return stream; |
| } |
| |
| AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy( |
| const AudioParameters& params, |
| const std::string& device_id) { |
| DCHECK(task_runner_->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 = device_id.empty() ? |
| 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 = AudioParameters( |
| AudioParameters::AUDIO_FAKE, params.channel_layout(), |
| params.sample_rate(), params.bits_per_sample(), |
| params.frames_per_buffer()); |
| } |
| } |
| |
| 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 new AudioOutputProxy((*it)->dispatcher.get()); |
| } |
| |
| const base::TimeDelta kCloseDelay = |
| base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds); |
| scoped_refptr<AudioOutputDispatcher> dispatcher; |
| if (output_params.format() != AudioParameters::AUDIO_FAKE) { |
| dispatcher = new AudioOutputResampler(this, params, output_params, |
| output_device_id, |
| kCloseDelay); |
| } else { |
| dispatcher = new AudioOutputDispatcherImpl(this, output_params, |
| output_device_id, |
| kCloseDelay); |
| } |
| |
| dispatcher_params->dispatcher = dispatcher; |
| output_dispatchers_.push_back(dispatcher_params); |
| return new AudioOutputProxy(dispatcher.get()); |
| } |
| |
| void AudioManagerBase::ShowAudioInputSettings() { |
| } |
| |
| void AudioManagerBase::GetAudioInputDeviceNames( |
| AudioDeviceNames* device_names) { |
| } |
| |
| void AudioManagerBase::GetAudioOutputDeviceNames( |
| AudioDeviceNames* device_names) { |
| } |
| |
| void AudioManagerBase::ReleaseOutputStream(AudioOutputStream* stream) { |
| DCHECK(stream); |
| // 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) { |
| DCHECK(stream); |
| // TODO(xians) : Have a clearer destruction path for the AudioInputStream. |
| --num_input_streams_; |
| delete stream; |
| } |
| |
| void AudioManagerBase::Shutdown() { |
| // Only true when we're sharing the UI message loop with the browser. The UI |
| // loop is no longer running at this time and browser destruction is imminent. |
| if (task_runner_->BelongsToCurrentThread()) { |
| ShutdownOnAudioThread(); |
| } else { |
| task_runner_->PostTask(FROM_HERE, base::Bind( |
| &AudioManagerBase::ShutdownOnAudioThread, base::Unretained(this))); |
| } |
| |
| // Stop() will wait for any posted messages to be processed first. |
| audio_thread_.Stop(); |
| } |
| |
| void AudioManagerBase::ShutdownOnAudioThread() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| while (!output_dispatchers_.empty()) { |
| output_dispatchers_.back()->dispatcher->Shutdown(); |
| output_dispatchers_.pop_back(); |
| } |
| } |
| |
| void AudioManagerBase::AddOutputDeviceChangeListener( |
| AudioDeviceListener* listener) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| output_listeners_.AddObserver(listener); |
| } |
| |
| void AudioManagerBase::RemoveOutputDeviceChangeListener( |
| AudioDeviceListener* listener) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| output_listeners_.RemoveObserver(listener); |
| } |
| |
| void AudioManagerBase::NotifyAllOutputDeviceChangeListeners() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| DVLOG(1) << "Firing OnDeviceChange() notifications."; |
| FOR_EACH_OBSERVER(AudioDeviceListener, output_listeners_, 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::GetDefaultOutputDeviceID() { |
| return ""; |
| } |
| |
| int AudioManagerBase::GetUserBufferSize() { |
| const CommandLine* cmd_line = 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; |
| } |
| |
| scoped_ptr<AudioLog> AudioManagerBase::CreateAudioLog( |
| AudioLogFactory::AudioComponent component) { |
| return audio_log_factory_->CreateAudioLog(component); |
| } |
| |
| } // namespace media |