blob: 8cf3ec1f772fcf24546e4ef2b06cf96361788995 [file] [log] [blame] [edit]
// 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