| // Copyright 2017 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 "services/audio/public/cpp/audio_system_to_service_adapter.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/audio/audio_device_description.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "services/audio/public/mojom/constants.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| |
| namespace audio { |
| |
| namespace { |
| |
| using OnAudioParamsCallback = media::AudioSystem::OnAudioParamsCallback; |
| using OnDeviceIdCallback = media::AudioSystem::OnDeviceIdCallback; |
| using OnInputDeviceInfoCallback = media::AudioSystem::OnInputDeviceInfoCallback; |
| using OnBoolCallback = media::AudioSystem::OnBoolCallback; |
| using OnDeviceDescriptionsCallback = |
| media::AudioSystem::OnDeviceDescriptionsCallback; |
| using media::AudioParameters; |
| |
| int64_t ToTraceId(base::TimeTicks time) { |
| return (time - base::TimeTicks()).InNanoseconds(); |
| } |
| |
| std::string ParamsToString(base::Optional<AudioParameters> params) { |
| return params ? params->AsHumanReadableString() : "nullopt"; |
| } |
| |
| enum Action { |
| kGetInputStreamParameters, |
| kGetOutputStreamParameters, |
| kHasInputDevices, |
| kHasOutputDevices, |
| kGetInputDeviceDescriptions, |
| kGetOutputDeviceDescriptions, |
| kGetAssociatedOutputDeviceID, |
| kGetInputDeviceInfo |
| }; |
| |
| enum StreamType { kInput, kOutput }; |
| |
| const char* GetTraceEvent(Action action) { |
| switch (action) { |
| case kGetInputStreamParameters: |
| return "AudioSystemToServiceAdapter::GetInputStreamParameters"; |
| case kGetOutputStreamParameters: |
| return "AudioSystemToServiceAdapter::GetOutputStreamParameters"; |
| case kHasInputDevices: |
| return "AudioSystemToServiceAdapter::HasInputDevices"; |
| case kHasOutputDevices: |
| return "AudioSystemToServiceAdapter::HasOutputDevices"; |
| case kGetInputDeviceDescriptions: |
| return "AudioSystemToServiceAdapter::GetInputDeviceDescriptions"; |
| case kGetOutputDeviceDescriptions: |
| return "AudioSystemToServiceAdapter::GetOutputDeviceDescriptions"; |
| case kGetAssociatedOutputDeviceID: |
| return "AudioSystemToServiceAdapter::GetAssociatedOutputDeviceID"; |
| case kGetInputDeviceInfo: |
| return "AudioSystemToServiceAdapter::GetInputDeviceInfo"; |
| } |
| NOTREACHED(); |
| } |
| |
| void LogUMA(Action action, base::TimeTicks start_time) { |
| const base::TimeDelta duration = base::TimeTicks::Now() - start_time; |
| switch (action) { |
| case kGetInputStreamParameters: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetInputStreamParameters", |
| duration); |
| return; |
| case kGetOutputStreamParameters: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetOutputStreamParameters", |
| duration); |
| return; |
| case kHasInputDevices: |
| UMA_HISTOGRAM_TIMES("Media.AudioService.SystemInfoClient.HasInputDevices", |
| duration); |
| return; |
| case kHasOutputDevices: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.HasOutputDevices", duration); |
| return; |
| case kGetInputDeviceDescriptions: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetInputDeviceDescriptions", |
| duration); |
| return; |
| case kGetOutputDeviceDescriptions: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetOutputDeviceDescriptions", |
| duration); |
| return; |
| case kGetAssociatedOutputDeviceID: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetAssociatedOutputDeviceID", |
| duration); |
| return; |
| case kGetInputDeviceInfo: |
| UMA_HISTOGRAM_TIMES( |
| "Media.AudioService.SystemInfoClient.GetInputDeviceInfo", duration); |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| OnAudioParamsCallback WrapGetStreamParametersReply( |
| StreamType stream_type, |
| const std::string& device_id, |
| OnAudioParamsCallback on_params_callback) { |
| const Action action = (stream_type == kInput) ? kGetInputStreamParameters |
| : kGetOutputStreamParameters; |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| TRACE_EVENT_ASYNC_BEGIN1("audio", GetTraceEvent(action), |
| ToTraceId(start_time), "device id", device_id); |
| |
| return base::BindOnce( |
| [](Action action, base::TimeTicks start_time, |
| OnAudioParamsCallback on_params_callback, |
| const base::Optional<media::AudioParameters>& params) { |
| TRACE_EVENT_ASYNC_END1("audio", GetTraceEvent(action), |
| ToTraceId(start_time), "params", |
| ParamsToString(params)); |
| LogUMA(action, start_time); |
| std::move(on_params_callback).Run(params); |
| }, |
| action, start_time, std::move(on_params_callback)); |
| } |
| |
| OnBoolCallback WrapHasDevicesReply(StreamType stream_type, |
| OnBoolCallback on_has_devices_callback) { |
| const Action action = |
| (stream_type == kInput) ? kHasInputDevices : kHasOutputDevices; |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| TRACE_EVENT_ASYNC_BEGIN0("audio", GetTraceEvent(action), |
| ToTraceId(start_time)); |
| |
| return base::BindOnce( |
| [](Action action, base::TimeTicks start_time, |
| OnBoolCallback on_has_devices_callback, bool answer) { |
| TRACE_EVENT_ASYNC_END1("audio", GetTraceEvent(action), |
| ToTraceId(start_time), "answer", answer); |
| LogUMA(action, start_time); |
| std::move(on_has_devices_callback).Run(answer); |
| }, |
| action, start_time, std::move(on_has_devices_callback)); |
| } |
| |
| OnDeviceDescriptionsCallback WrapGetDeviceDescriptionsReply( |
| StreamType stream_type, |
| OnDeviceDescriptionsCallback on_descriptions_callback) { |
| const Action action = (stream_type == kInput) ? kGetInputDeviceDescriptions |
| : kGetOutputDeviceDescriptions; |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| TRACE_EVENT_ASYNC_BEGIN0("audio", GetTraceEvent(action), |
| ToTraceId(start_time)); |
| |
| return base::BindOnce( |
| [](Action action, base::TimeTicks start_time, |
| OnDeviceDescriptionsCallback on_descriptions_callback, |
| media::AudioDeviceDescriptions descriptions) { |
| TRACE_EVENT_ASYNC_END1("audio", GetTraceEvent(action), |
| ToTraceId(start_time), "device count", |
| descriptions.size()); |
| LogUMA(action, start_time); |
| std::move(on_descriptions_callback).Run(std::move(descriptions)); |
| }, |
| action, start_time, std::move(on_descriptions_callback)); |
| } |
| |
| OnDeviceIdCallback WrapGetAssociatedOutputDeviceIDReply( |
| const std::string& input_device_id, |
| OnDeviceIdCallback on_device_id_callback) { |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| TRACE_EVENT_ASYNC_BEGIN1("audio", GetTraceEvent(kGetAssociatedOutputDeviceID), |
| ToTraceId(start_time), "input_device_id", |
| input_device_id); |
| |
| return base::BindOnce( |
| [](base::TimeTicks start_time, OnDeviceIdCallback on_device_id_callback, |
| const base::Optional<std::string>& answer) { |
| TRACE_EVENT_ASYNC_END1( |
| "audio", GetTraceEvent(kGetAssociatedOutputDeviceID), |
| ToTraceId(start_time), "answer", answer.value_or("nullopt")); |
| LogUMA(kGetAssociatedOutputDeviceID, start_time); |
| std::move(on_device_id_callback).Run(answer); |
| }, |
| start_time, std::move(on_device_id_callback)); |
| } |
| |
| OnInputDeviceInfoCallback WrapGetInputDeviceInfoReply( |
| const std::string& input_device_id, |
| OnInputDeviceInfoCallback on_input_device_info_callback) { |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| TRACE_EVENT_ASYNC_BEGIN1("audio", GetTraceEvent(kGetInputDeviceInfo), |
| ToTraceId(start_time), "input_device_id", |
| input_device_id); |
| |
| return base::BindOnce( |
| [](base::TimeTicks start_time, |
| OnInputDeviceInfoCallback on_input_device_info_callback, |
| const base::Optional<AudioParameters>& params, |
| const base::Optional<std::string>& associated_output_device_id) { |
| TRACE_EVENT_ASYNC_END2( |
| "audio", GetTraceEvent(kGetInputDeviceInfo), ToTraceId(start_time), |
| "params", ParamsToString(params), "associated_output_device_id", |
| associated_output_device_id.value_or("nullopt")); |
| LogUMA(kGetInputDeviceInfo, start_time); |
| std::move(on_input_device_info_callback) |
| .Run(params, associated_output_device_id); |
| }, |
| start_time, std::move(on_input_device_info_callback)); |
| } |
| |
| } // namespace |
| |
| AudioSystemToServiceAdapter::AudioSystemToServiceAdapter( |
| std::unique_ptr<service_manager::Connector> connector, |
| base::TimeDelta disconnect_timeout) |
| : connector_(std::move(connector)) { |
| DCHECK(connector_); |
| DETACH_FROM_THREAD(thread_checker_); |
| if (disconnect_timeout > base::TimeDelta()) { |
| disconnect_timer_.emplace( |
| FROM_HERE, disconnect_timeout, this, |
| &AudioSystemToServiceAdapter::DisconnectOnTimeout); |
| } |
| } |
| |
| AudioSystemToServiceAdapter::AudioSystemToServiceAdapter( |
| std::unique_ptr<service_manager::Connector> connector) |
| : AudioSystemToServiceAdapter(std::move(connector), base::TimeDelta()) {} |
| |
| AudioSystemToServiceAdapter::~AudioSystemToServiceAdapter() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (!!system_info_) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", |
| "AudioSystemToServiceAdapter bound", this, |
| "disconnect reason", "destroyed"); |
| } |
| } |
| |
| void AudioSystemToServiceAdapter::GetInputStreamParameters( |
| const std::string& device_id, |
| OnAudioParamsCallback on_params_callback) { |
| GetSystemInfo()->GetInputStreamParameters( |
| device_id, mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapGetStreamParametersReply( |
| kInput, device_id, std::move(on_params_callback)), |
| base::nullopt)); |
| } |
| |
| void AudioSystemToServiceAdapter::GetOutputStreamParameters( |
| const std::string& device_id, |
| OnAudioParamsCallback on_params_callback) { |
| GetSystemInfo()->GetOutputStreamParameters( |
| device_id, mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapGetStreamParametersReply( |
| kOutput, device_id, std::move(on_params_callback)), |
| base::nullopt)); |
| } |
| |
| void AudioSystemToServiceAdapter::HasInputDevices( |
| OnBoolCallback on_has_devices_callback) { |
| GetSystemInfo()->HasInputDevices(mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapHasDevicesReply(kInput, std::move(on_has_devices_callback)), false)); |
| } |
| |
| void AudioSystemToServiceAdapter::HasOutputDevices( |
| OnBoolCallback on_has_devices_callback) { |
| GetSystemInfo()->HasOutputDevices(mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapHasDevicesReply(kOutput, std::move(on_has_devices_callback)), false)); |
| } |
| |
| void AudioSystemToServiceAdapter::GetDeviceDescriptions( |
| bool for_input, |
| OnDeviceDescriptionsCallback on_descriptions_callback) { |
| auto reply_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapCallbackWithDeviceNameLocalization( |
| std::move(on_descriptions_callback)), |
| media::AudioDeviceDescriptions()); |
| if (for_input) |
| GetSystemInfo()->GetInputDeviceDescriptions( |
| WrapGetDeviceDescriptionsReply(kInput, std::move(reply_callback))); |
| else |
| GetSystemInfo()->GetOutputDeviceDescriptions( |
| WrapGetDeviceDescriptionsReply(kOutput, std::move(reply_callback))); |
| } |
| |
| void AudioSystemToServiceAdapter::GetAssociatedOutputDeviceID( |
| const std::string& input_device_id, |
| OnDeviceIdCallback on_device_id_callback) { |
| GetSystemInfo()->GetAssociatedOutputDeviceID( |
| input_device_id, |
| mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapGetAssociatedOutputDeviceIDReply( |
| input_device_id, std::move(on_device_id_callback)), |
| base::nullopt)); |
| } |
| |
| void AudioSystemToServiceAdapter::GetInputDeviceInfo( |
| const std::string& input_device_id, |
| OnInputDeviceInfoCallback on_input_device_info_callback) { |
| GetSystemInfo()->GetInputDeviceInfo( |
| input_device_id, |
| mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| WrapGetInputDeviceInfoReply(input_device_id, |
| std::move(on_input_device_info_callback)), |
| base::nullopt, base::nullopt)); |
| } |
| |
| mojom::SystemInfo* AudioSystemToServiceAdapter::GetSystemInfo() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (!system_info_) { |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( |
| "audio", "AudioSystemToServiceAdapter bound", this); |
| connector_->BindInterface(mojom::kServiceName, |
| mojo::MakeRequest(&system_info_)); |
| system_info_.set_connection_error_handler( |
| base::BindOnce(&AudioSystemToServiceAdapter::OnConnectionError, |
| base::Unretained(this))); |
| DCHECK(system_info_); |
| } |
| if (disconnect_timer_) |
| disconnect_timer_->Reset(); |
| return system_info_.get(); |
| } |
| |
| void AudioSystemToServiceAdapter::DisconnectOnTimeout() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (system_info_.IsExpectingResponse()) { |
| if (disconnect_timer_) |
| disconnect_timer_->Reset(); |
| TRACE_EVENT_NESTABLE_ASYNC_INSTANT0("audio", "Timeout: expecting responce", |
| this); |
| return; |
| } |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", "AudioSystemToServiceAdapter bound", |
| this, "disconnect reason", "timeout"); |
| system_info_.reset(); |
| } |
| |
| void AudioSystemToServiceAdapter::OnConnectionError() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", "AudioSystemToServiceAdapter bound", |
| this, "disconnect reason", |
| "connection error"); |
| system_info_.reset(); |
| } |
| |
| } // namespace audio |