| // Copyright 2020 The Fuchsia 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 <fidl/fuchsia.hardware.audio/cpp/wire.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/simple-codec/simple-codec-client.h> |
| #include <zircon/threads.h> |
| |
| #include <fbl/auto_lock.h> |
| |
| namespace audio { |
| |
| SimpleCodecClient::~SimpleCodecClient() { Unbind(); } |
| |
| SimpleCodecClient::SimpleCodecClient(SimpleCodecClient&& other) noexcept |
| : loop_(&kAsyncLoopConfigNeverAttachToThread), |
| created_with_dispatcher_(other.created_with_dispatcher_), |
| dispatcher_(created_with_dispatcher_ ? other.dispatcher_ : loop_.dispatcher()) { |
| other.Unbind(); |
| if (other.proto_client_.is_valid()) { |
| SetProtocol(other.proto_client_); |
| } |
| } |
| |
| zx_status_t SimpleCodecClient::SetProtocol(ddk::CodecProtocolClient proto_client) { |
| Unbind(); |
| |
| proto_client_ = proto_client; |
| if (!proto_client_.is_valid()) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| zx::channel channel_remote; |
| fidl::ClientEnd<fuchsia_hardware_audio::Codec> channel_local; |
| auto status = zx::channel::create(0, &channel_local.channel(), &channel_remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = proto_client_.Connect(std::move(channel_remote)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| std::promise<void> codec_torn_down_promise; |
| codec_torn_down_ = codec_torn_down_promise.get_future(); |
| codec_ = fidl::WireSharedClient( |
| std::move(channel_local), dispatcher_, |
| fidl::ObserveTeardown( |
| [teardown = std::move(codec_torn_down_promise)]() mutable { teardown.set_value(); })); |
| |
| if (!created_with_dispatcher_ && !thread_started_) { |
| status = loop_.StartThread("SimpleCodecClient thread"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| thread_started_ = true; |
| } |
| |
| // The first call from this client shouldn't block. |
| const auto response = codec_.sync()->WatchGainState(); |
| if (!response.ok()) { |
| return response.status(); |
| } |
| |
| auto mutable_response = response.value(); |
| // Update the stored gain state, and start a hanging get to receive further gain state changes. |
| UpdateGainState(&mutable_response); |
| |
| auto endpoints = |
| fidl::CreateEndpoints<fuchsia_hardware_audio_signalprocessing::SignalProcessing>(); |
| if (endpoints.status_value() != ZX_OK) { |
| return ZX_OK; // We allow servers not supporting signal processing. |
| } |
| signal_processing_ = |
| fidl::WireSyncClient<fuchsia_hardware_audio_signalprocessing::SignalProcessing>( |
| std::move(endpoints->client)); |
| auto result = codec_.sync()->SignalProcessingConnect(std::move(endpoints->server)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| auto pes = signal_processing_->GetElements(); |
| if (!pes.ok() || pes->is_error()) { |
| return ZX_OK; // We allow servers not supporting signal processing. |
| } |
| for (auto& pe : pes->value()->processing_elements) { |
| if (pe.type() == |
| fuchsia_hardware_audio_signalprocessing::wire::ElementType::kAutomaticGainLimiter) { |
| if (pe.has_id()) { |
| if (!agl_pe_id_.has_value()) { // Use the first PE with AGL support. |
| agl_pe_id_.emplace(pe.id()); |
| } |
| } else { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SimpleCodecClient::Reset() { return codec_.sync()->Reset().status(); } |
| |
| zx_status_t SimpleCodecClient::Stop() { return codec_.sync()->Stop().status(); } |
| |
| zx_status_t SimpleCodecClient::Start() { return codec_.sync()->Start().status(); } |
| |
| zx::status<Info> SimpleCodecClient::GetInfo() { |
| const auto result = codec_.sync()->GetInfo(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| |
| const fuchsia_hardware_audio::wire::CodecInfo& llcpp_info = result.value().info; |
| |
| Info info; |
| info.unique_id = std::string(llcpp_info.unique_id.data(), llcpp_info.unique_id.size()); |
| info.manufacturer = std::string(llcpp_info.manufacturer.data(), llcpp_info.manufacturer.size()); |
| info.product_name = std::string(llcpp_info.product_name.data(), llcpp_info.product_name.size()); |
| return zx::ok(std::move(info)); |
| } |
| |
| zx::status<bool> SimpleCodecClient::IsBridgeable() { |
| const auto result = codec_.sync()->IsBridgeable(); |
| if (result.ok()) { |
| return zx::ok(result.value().supports_bridged_mode); |
| } |
| return zx::error(result.status()); |
| } |
| |
| zx_status_t SimpleCodecClient::SetBridgedMode(bool bridged) { |
| return codec_->SetBridgedMode(bridged).status(); |
| } |
| |
| zx::status<DaiSupportedFormats> SimpleCodecClient::GetDaiFormats() { |
| auto result = codec_.sync()->GetDaiFormats(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| if (result->is_error()) { |
| return zx::error(result->error_value()); |
| } |
| |
| ZX_ASSERT(result->value()->formats.count() == 1); |
| const auto& llcpp_formats = result->value()->formats[0]; |
| |
| DaiSupportedFormats formats; |
| formats.number_of_channels = std::vector(llcpp_formats.number_of_channels.cbegin(), |
| llcpp_formats.number_of_channels.cend()); |
| for (auto& sample_format : llcpp_formats.sample_formats) { |
| formats.sample_formats.push_back(static_cast<SampleFormat>(sample_format)); |
| } |
| for (auto& frame_format : llcpp_formats.frame_formats) { |
| formats.frame_formats.push_back(static_cast<FrameFormat>(frame_format.frame_format_standard())); |
| } |
| formats.frame_rates = |
| std::vector(llcpp_formats.frame_rates.cbegin(), llcpp_formats.frame_rates.cend()); |
| formats.bits_per_slot = |
| std::vector(llcpp_formats.bits_per_slot.cbegin(), llcpp_formats.bits_per_slot.cend()); |
| formats.bits_per_sample = |
| std::vector(llcpp_formats.bits_per_sample.cbegin(), llcpp_formats.bits_per_sample.cend()); |
| |
| return zx::ok(formats); |
| } |
| |
| zx::status<CodecFormatInfo> SimpleCodecClient::SetDaiFormat(DaiFormat format) { |
| fidl::Arena allocator; |
| |
| fuchsia_hardware_audio::wire::DaiFormat format2; |
| format2.number_of_channels = format.number_of_channels; |
| format2.channels_to_use_bitmask = format.channels_to_use_bitmask; |
| format2.sample_format = |
| static_cast<fuchsia_hardware_audio::wire::DaiSampleFormat>(format.sample_format); |
| const auto standard = |
| static_cast<fuchsia_hardware_audio::wire::DaiFrameFormatStandard>(format.frame_format); |
| format2.frame_format = |
| fuchsia_hardware_audio::wire::DaiFrameFormat::WithFrameFormatStandard(standard); |
| format2.frame_rate = format.frame_rate; |
| format2.bits_per_slot = format.bits_per_slot; |
| format2.bits_per_sample = format.bits_per_sample; |
| |
| const auto ret = codec_.sync()->SetDaiFormat(format2); |
| if (!ret.ok()) { |
| return zx::error(ret.status()); |
| } |
| if (ret->is_error()) { |
| return zx::error(ret->error_value()); |
| } |
| CodecFormatInfo format_info = {}; |
| if (ret->value()->state.has_external_delay()) { |
| format_info.set_external_delay(ret->value()->state.external_delay()); |
| } |
| if (ret->value()->state.has_turn_on_delay()) { |
| format_info.set_turn_on_delay(ret->value()->state.turn_on_delay()); |
| } |
| if (ret->value()->state.has_turn_off_delay()) { |
| format_info.set_turn_off_delay(ret->value()->state.turn_off_delay()); |
| } |
| return zx::ok(std::move(format_info)); |
| } |
| |
| zx::status<GainFormat> SimpleCodecClient::GetGainFormat() { |
| const auto result = codec_.sync()->GetGainFormat(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| |
| const fuchsia_hardware_audio::wire::GainFormat& format = result.value().gain_format; |
| |
| // Only decibels in simple codec. |
| ZX_ASSERT(format.type() == fuchsia_hardware_audio::wire::GainType::kDecibels); |
| |
| // Only hardwired in simple codec. |
| return zx::ok(GainFormat{ |
| .min_gain = format.min_gain(), |
| .max_gain = format.max_gain(), |
| .gain_step = format.gain_step(), |
| .can_mute = format.can_mute(), |
| .can_agc = format.can_agc(), |
| }); |
| } |
| |
| zx::status<GainState> SimpleCodecClient::GetGainState() { |
| fbl::AutoLock lock(&gain_state_lock_); |
| return gain_state_; |
| } |
| |
| void SimpleCodecClient::SetGainState(GainState state) { |
| fidl::Arena allocator; |
| |
| fuchsia_hardware_audio::wire::GainState state2(allocator); |
| state2.set_gain_db(state.gain); |
| state2.set_muted(state.muted); |
| state2.set_agc_enabled(state.agc_enabled); |
| const auto result = codec_->SetGainState(state2); |
| if (result.ok()) { |
| fbl::AutoLock lock(&gain_state_lock_); |
| gain_state_ = zx::ok(state); |
| } |
| } |
| |
| zx_status_t SimpleCodecClient::SetAgl(bool agl_enable) { |
| fidl::Arena allocator; |
| if (!agl_pe_id_.has_value()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| fuchsia_hardware_audio_signalprocessing::wire::ElementState state(allocator); |
| state.set_enabled(agl_enable); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)signal_processing_->SetElementState(agl_pe_id_.value(), std::move(state)); |
| return ZX_OK; |
| } |
| |
| void SimpleCodecClient::UpdateGainState( |
| fidl::WireResponse<fuchsia_hardware_audio::Codec::WatchGainState>* response) { |
| const GainState state{ |
| .gain = response->gain_state.gain_db(), |
| .muted = response->gain_state.muted(), |
| .agc_enabled = response->gain_state.agc_enabled(), |
| }; |
| |
| { |
| fbl::AutoLock lock(&gain_state_lock_); |
| gain_state_ = zx::ok(state); |
| } |
| |
| codec_->WatchGainState().Then( |
| [this](fidl::WireUnownedResult<fuchsia_hardware_audio::Codec::WatchGainState>& result) { |
| if (!result.ok()) { |
| return; |
| } |
| UpdateGainState(result.Unwrap()); |
| }); |
| } |
| |
| void SimpleCodecClient::Unbind() { |
| // Wait for any pending channel operations to complete. This ensures we don't get a WatchGainState |
| // callback after the client has been freed. |
| if (codec_.is_valid()) { |
| codec_.AsyncTeardown(); |
| codec_torn_down_.wait(); |
| } |
| { |
| fbl::AutoLock lock(&gain_state_lock_); |
| gain_state_ = zx::error(ZX_ERR_BAD_STATE); |
| } |
| } |
| |
| } // namespace audio |