| // 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 <memory> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "build/buildflag.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" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| #include "base/logging.h" |
| #include "build/chromeos_buildflags.h" |
| #include "media/audio/audio_input_stream_data_interceptor.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 = 16; |
| |
| // Default maximum number of input streams that can be open simultaneously |
| // for all platforms. |
| const int kMaxInputStreams = 16; |
| |
| const int kMaxInputChannels = 3; |
| |
| // Helper function to pass as callback when the audio debug recording is not |
| // enabled. |
| std::unique_ptr<AudioDebugRecorder> GetNullptrAudioDebugRecorder( |
| const AudioParameters& params) { |
| return nullptr; |
| } |
| |
| // This enum must match the numbering for AudioOutputProxyStreamFormat in |
| // enums.xml. Do not reorder or remove items, only add new items before |
| // STREAM_FORMAT_MAX. |
| enum StreamFormat { |
| STREAM_FORMAT_BITSTREAM = 0, |
| STREAM_FORMAT_PCM_LINEAR = 1, |
| STREAM_FORMAT_PCM_LOW_LATENCY = 2, |
| STREAM_FORMAT_PCM_LOW_LATENCY_FALLBACK_TO_FAKE = 3, |
| STREAM_FORMAT_FAKE = 4, |
| STREAM_FORMAT_MAX = 4, |
| }; |
| |
| PRINTF_FORMAT(2, 3) |
| void SendLogMessage(const AudioManagerBase::LogCallback& callback, |
| const char* format, |
| ...) { |
| if (callback.is_null()) |
| return; |
| va_list args; |
| va_start(args, format); |
| callback.Run("AMB::" + base::StringPrintV(format, args)); |
| va_end(args); |
| } |
| |
| } // 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() = default; |
| |
| 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()( |
| const std::unique_ptr<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(std::unique_ptr<AudioThread> audio_thread, |
| AudioLogFactory* audio_log_factory) |
| : AudioManager(std::move(audio_thread)), |
| max_num_output_streams_(kDefaultMaxOutputStreams), |
| 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::ObserverListPolicy::EXISTING_ONLY), |
| audio_log_factory_(audio_log_factory) {} |
| |
| AudioManagerBase::~AudioManagerBase() { |
| // 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()); |
| } |
| |
| void AudioManagerBase::GetAudioInputDeviceDescriptions( |
| AudioDeviceDescriptions* device_descriptions) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| GetAudioDeviceDescriptions(device_descriptions, |
| &AudioManagerBase::GetAudioInputDeviceNames, |
| &AudioManagerBase::GetDefaultInputDeviceID, |
| &AudioManagerBase::GetCommunicationsInputDeviceID, |
| &AudioManagerBase::GetGroupIDInput); |
| } |
| |
| void AudioManagerBase::GetAudioOutputDeviceDescriptions( |
| AudioDeviceDescriptions* device_descriptions) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| GetAudioDeviceDescriptions(device_descriptions, |
| &AudioManagerBase::GetAudioOutputDeviceNames, |
| &AudioManagerBase::GetDefaultOutputDeviceID, |
| &AudioManagerBase::GetCommunicationsOutputDeviceID, |
| &AudioManagerBase::GetGroupIDOutput); |
| } |
| |
| void AudioManagerBase::GetAudioDeviceDescriptions( |
| AudioDeviceDescriptions* device_descriptions, |
| void (AudioManagerBase::*get_device_names)(AudioDeviceNames*), |
| std::string (AudioManagerBase::*get_default_device_id)(), |
| std::string (AudioManagerBase::*get_communications_device_id)(), |
| std::string (AudioManagerBase::*get_group_id)(const std::string&)) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| AudioDeviceNames device_names; |
| (this->*get_device_names)(&device_names); |
| std::string real_default_device_id = (this->*get_default_device_id)(); |
| std::string real_communications_device_id = |
| (this->*get_communications_device_id)(); |
| std::string real_default_name; |
| std::string real_communications_name; |
| |
| // Find the names for the real devices that are mapped to the default and |
| // communications devices. |
| for (const auto& name : device_names) { |
| if (name.unique_id == real_default_device_id) |
| real_default_name = name.device_name; |
| if (name.unique_id == real_communications_device_id) |
| real_communications_name = name.device_name; |
| } |
| |
| for (auto& name : device_names) { |
| if (AudioDeviceDescription::IsDefaultDevice(name.unique_id)) |
| name.device_name = real_default_name; |
| else if (AudioDeviceDescription::IsCommunicationsDevice(name.unique_id)) |
| name.device_name = real_communications_name; |
| std::string group_id = (this->*get_group_id)(name.unique_id); |
| device_descriptions->emplace_back(std::move(name.device_name), |
| std::move(name.unique_id), |
| std::move(group_id)); |
| } |
| } |
| |
| AudioOutputStream* AudioManagerBase::MakeAudioOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| DCHECK(params.IsValid()); |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kFailAudioStreamCreation)) { |
| return nullptr; |
| } |
| |
| SendLogMessage(log_callback, "%s({device_id=%s}, {params=[%s]})", __func__, |
| device_id.c_str(), params.AsHumanReadableString().c_str()); |
| |
| // 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_) { |
| LOG(ERROR) << "Number of opened output audio streams " |
| << num_output_streams_ << " exceed the max allowed number " |
| << max_num_output_streams_; |
| return nullptr; |
| } |
| |
| 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: |
| stream = MakeBitstreamOutputStream(params, device_id, log_callback); |
| break; |
| case AudioParameters::AUDIO_FAKE: |
| stream = FakeAudioOutputStream::MakeFakeStream(this, params); |
| break; |
| default: |
| stream = nullptr; |
| break; |
| } |
| |
| if (stream) { |
| ++num_output_streams_; |
| SendLogMessage(log_callback, "%s => (number of streams=%d)", __func__, |
| output_stream_count()); |
| } |
| |
| return stream; |
| } |
| |
| AudioOutputStream* AudioManagerBase::MakeBitstreamOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| return nullptr; |
| } |
| |
| AudioInputStream* AudioManagerBase::MakeAudioInputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kFailAudioStreamCreation)) { |
| return nullptr; |
| } |
| |
| SendLogMessage(log_callback, "%s({device_id=%s}, {params=[%s]})", __func__, |
| device_id.c_str(), params.AsHumanReadableString().c_str()); |
| |
| if (!params.IsValid() || (params.channels() > kMaxInputChannels) || |
| device_id.empty()) { |
| DLOG(ERROR) << "Audio parameters are invalid for device " << device_id |
| << ", params: " << params.AsHumanReadableString(); |
| return nullptr; |
| } |
| |
| if (input_stream_count() >= kMaxInputStreams) { |
| LOG(ERROR) << "Number of opened input audio streams " |
| << input_stream_count() << " exceed the max allowed number " |
| << kMaxInputStreams; |
| return nullptr; |
| } |
| |
| 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 = nullptr; |
| break; |
| } |
| |
| if (stream) { |
| input_streams_.insert(stream); |
| if (!log_callback.is_null()) { |
| SendLogMessage(log_callback, "%s => (number of streams=%d)", __func__, |
| input_stream_count()); |
| } |
| |
| if (!params.IsBitstreamFormat() && debug_recording_manager_) { |
| // Using unretained for |debug_recording_manager_| is safe since it |
| // outlives the audio thread, on which streams are operated. |
| // Note: The AudioInputStreamDataInterceptor takes ownership of the |
| // created stream and cleans it up when it is Close()d, transparently to |
| // the user of the stream. I the case where the audio manager closes the |
| // stream (Mac), this will result in a dangling pointer. |
| stream = new AudioInputStreamDataInterceptor( |
| base::BindRepeating( |
| &AudioDebugRecordingManager::RegisterDebugRecordingSource, |
| base::Unretained(debug_recording_manager_.get()), |
| AudioDebugRecordingStreamType::kInput, params), |
| stream); |
| } |
| } |
| |
| return stream; |
| } |
| |
| AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy( |
| const AudioParameters& params, |
| const std::string& device_id) { |
| CHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| DCHECK(params.IsValid()); |
| absl::optional<StreamFormat> uma_stream_format; |
| |
| // 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) |
| ? |
| #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) |
| // On ChromeOS, it is expected that, if the default device is given, |
| // no specific device ID should be used since the actual output device |
| // should change dynamically if the system default device changes. |
| // See http://crbug.com/750614. |
| std::string() |
| #else |
| GetDefaultOutputDeviceID() |
| #endif |
| : device_id; |
| |
| // If we're not using AudioOutputResampler our output parameters are the same |
| // as our input parameters. |
| AudioParameters output_params = params; |
| |
| // If audio has been disabled force usage of a fake audio stream. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableAudioOutput)) { |
| output_params.set_format(AudioParameters::AUDIO_FAKE); |
| } |
| |
| if (params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY && |
| output_params.format() != AudioParameters::AUDIO_FAKE) { |
| output_params = |
| GetPreferredOutputStreamParameters(output_device_id, params); |
| |
| // Ensure we only pass on valid output parameters. |
| if (output_params.IsValid()) { |
| if (params.effects() & AudioParameters::MULTIZONE) { |
| // Never turn off the multizone effect even if it is not preferred. |
| output_params.set_effects(output_params.effects() | |
| AudioParameters::MULTIZONE); |
| } |
| if (params.effects() != output_params.effects()) { |
| // Turn off effects that weren't requested. |
| output_params.set_effects(params.effects() & output_params.effects()); |
| } |
| |
| uma_stream_format = STREAM_FORMAT_PCM_LOW_LATENCY; |
| } else { |
| // 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: " << output_params.AsHumanReadableString(); |
| |
| // Tell the AudioManager to create a fake output device. |
| output_params = params; |
| output_params.set_format(AudioParameters::AUDIO_FAKE); |
| uma_stream_format = STREAM_FORMAT_PCM_LOW_LATENCY_FALLBACK_TO_FAKE; |
| } |
| |
| output_params.set_latency_tag(params.latency_tag()); |
| } else { |
| switch (output_params.format()) { |
| case AudioParameters::AUDIO_PCM_LINEAR: |
| uma_stream_format = STREAM_FORMAT_PCM_LINEAR; |
| break; |
| case AudioParameters::AUDIO_FAKE: |
| uma_stream_format = STREAM_FORMAT_FAKE; |
| break; |
| default: |
| if (output_params.IsBitstreamFormat()) |
| uma_stream_format = STREAM_FORMAT_BITSTREAM; |
| else |
| NOTREACHED(); |
| } |
| } |
| |
| if (uma_stream_format) { |
| UMA_HISTOGRAM_ENUMERATION("Media.AudioOutputStreamProxy.StreamFormat", |
| *uma_stream_format, STREAM_FORMAT_MAX + 1); |
| } else { |
| NOTREACHED(); |
| } |
| |
| std::unique_ptr<DispatcherParams> dispatcher_params = |
| std::make_unique<DispatcherParams>(params, output_params, |
| output_device_id); |
| |
| auto it = std::find_if(output_dispatchers_.begin(), output_dispatchers_.end(), |
| CompareByParams(dispatcher_params.get())); |
| if (it != output_dispatchers_.end()) |
| return (*it)->dispatcher->CreateStreamProxy(); |
| |
| const base::TimeDelta kCloseDelay = base::Seconds(kStreamCloseDelaySeconds); |
| std::unique_ptr<AudioOutputDispatcher> dispatcher; |
| if (output_params.format() != AudioParameters::AUDIO_FAKE && |
| !output_params.IsBitstreamFormat()) { |
| // Using unretained for |debug_recording_manager_| is safe since it |
| // outlives the dispatchers (cleared in ShutdownOnAudioThread()). |
| dispatcher = std::make_unique<AudioOutputResampler>( |
| this, params, output_params, output_device_id, kCloseDelay, |
| debug_recording_manager_ |
| ? base::BindRepeating( |
| &AudioDebugRecordingManager::RegisterDebugRecordingSource, |
| base::Unretained(debug_recording_manager_.get()), |
| AudioDebugRecordingStreamType::kOutput) |
| : base::BindRepeating(&GetNullptrAudioDebugRecorder)); |
| } else { |
| dispatcher = std::make_unique<AudioOutputDispatcherImpl>( |
| this, output_params, output_device_id, kCloseDelay); |
| } |
| |
| dispatcher_params->dispatcher = std::move(dispatcher); |
| output_dispatchers_.push_back(std::move(dispatcher_params)); |
| return output_dispatchers_.back()->dispatcher->CreateStreamProxy(); |
| } |
| |
| 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::ShutdownOnAudioThread() { |
| DCHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| |
| // Close all output streams. |
| output_dispatchers_.clear(); |
| } |
| |
| 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(); |
| } |
| |
| 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; |
| } else if (output_device_id == |
| AudioDeviceDescription::kCommunicationsDeviceId) { |
| std::string real_device_id = GetCommunicationsOutputDeviceID(); |
| if (!real_device_id.empty()) |
| return real_device_id; |
| } |
| return output_device_id; |
| } |
| |
| std::string AudioManagerBase::GetGroupIDInput( |
| const std::string& input_device_id) { |
| const std::string& real_input_device_id = |
| input_device_id == AudioDeviceDescription::kDefaultDeviceId |
| ? GetDefaultInputDeviceID() |
| : input_device_id == AudioDeviceDescription::kCommunicationsDeviceId |
| ? GetCommunicationsInputDeviceID() |
| : input_device_id; |
| std::string output_device_id = |
| GetAssociatedOutputDeviceID(real_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 real_input_device_id + "input"; |
| } |
| return GetGroupIDOutput(output_device_id); |
| } |
| |
| void AudioManagerBase::CloseAllInputStreams() { |
| 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()); |
| } |
| |
| std::string AudioManagerBase::GetDefaultInputDeviceID() { |
| return std::string(); |
| } |
| |
| std::string AudioManagerBase::GetDefaultOutputDeviceID() { |
| return std::string(); |
| } |
| |
| std::string AudioManagerBase::GetCommunicationsInputDeviceID() { |
| return std::string(); |
| } |
| |
| std::string AudioManagerBase::GetCommunicationsOutputDeviceID() { |
| return std::string(); |
| } |
| |
| // 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, |
| int component_id) { |
| return audio_log_factory_->CreateAudioLog(component, component_id); |
| } |
| |
| void AudioManagerBase::InitializeDebugRecording() { |
| if (!GetTaskRunner()->BelongsToCurrentThread()) { |
| // AudioManager is deleted on the audio thread, so it's safe to post |
| // unretained. |
| GetTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&AudioManagerBase::InitializeDebugRecording, |
| base::Unretained(this))); |
| return; |
| } |
| |
| DCHECK(!debug_recording_manager_); |
| debug_recording_manager_ = CreateAudioDebugRecordingManager(GetTaskRunner()); |
| } |
| |
| std::unique_ptr<AudioDebugRecordingManager> |
| AudioManagerBase::CreateAudioDebugRecordingManager( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| return std::make_unique<AudioDebugRecordingManager>(std::move(task_runner)); |
| } |
| |
| AudioDebugRecordingManager* AudioManagerBase::GetAudioDebugRecordingManager() { |
| DCHECK(GetTaskRunner()->BelongsToCurrentThread()); |
| return debug_recording_manager_.get(); |
| } |
| |
| } // namespace media |