blob: 3afd88c72524917f926a202a3397da258f169181 [file] [log] [blame]
// 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 <ddktl/fidl.h>
#include <fbl/auto_lock.h>
namespace audio {
SimpleCodecClient::~SimpleCodecClient() { Unbind(); }
zx_status_t SimpleCodecClient::SetCodec(
fidl::ClientEnd<fuchsia_hardware_audio::Codec> channel_local) {
Unbind();
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_) {
zx_status_t status = loop_.StartThread("SimpleCodecClient thread");
if (status != ZX_OK) {
return status;
}
thread_started_ = true;
}
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::WireSharedClient<fuchsia_hardware_audio_signalprocessing::SignalProcessing>(
std::move(endpoints->client), dispatcher_, fidl::ObserveTeardown([]() mutable {}));
auto result = codec_.sync()->SignalProcessingConnect(std::move(endpoints->server));
if (!result.ok()) {
return result.status();
}
auto pes = signal_processing_.sync()->GetElements();
if (!pes.ok() || pes->is_error()) {
return ZX_OK; // We allow servers not supporting signal processing.
}
GainState gain_state{
.gain = 0.0f,
.muted = false,
.agc_enabled = false,
};
GainFormat gain_format{
.min_gain = 0.0f,
.max_gain = 0.0f,
.gain_step = 0.0f,
.can_mute = false,
.can_agc = false,
};
for (auto& pe : pes->value()->processing_elements) {
if (!pe.has_id()) {
return ZX_ERR_INVALID_ARGS;
}
switch (pe.type()) {
case fuchsia_hardware_audio_signalprocessing::wire::ElementType::kGain:
if (!gain_pe_id_.has_value()) { // Use the first PE with gain support.
gain_pe_id_.emplace(pe.id());
if (pe.has_type_specific() && pe.type_specific().is_gain()) {
if (pe.type_specific().gain().has_type() &&
pe.type_specific().gain().type() !=
fuchsia_hardware_audio_signalprocessing::wire::GainType::kDecibels) {
return ZX_ERR_NOT_SUPPORTED;
}
if (pe.type_specific().gain().has_min_gain()) {
gain_format.min_gain = pe.type_specific().gain().min_gain();
}
if (pe.type_specific().gain().has_max_gain()) {
gain_format.max_gain = pe.type_specific().gain().max_gain();
}
if (pe.type_specific().gain().has_min_gain_step()) {
gain_format.gain_step = pe.type_specific().gain().min_gain_step();
}
}
// The first call from this client shouldn't block since this is a hanging-get, and the
// first calls always have new information (no information has previously been provided).
const auto response = signal_processing_.sync()->WatchElementState(gain_pe_id_.value());
if (!response.ok()) {
return response.status();
}
if (response->state.has_started() && response->state.started() &&
response->state.has_bypassed() && !response->state.bypassed() &&
response->state.has_type_specific() && response->state.type_specific().is_gain() &&
response->state.type_specific().gain().has_gain()) {
gain_state.gain = response->state.type_specific().gain().gain();
}
}
break;
case fuchsia_hardware_audio_signalprocessing::wire::ElementType::kMute:
if (!mute_pe_id_.has_value()) { // Use the first PE with mute support.
mute_pe_id_.emplace(pe.id());
// The first call from this client shouldn't block since this is a hanging-get, and the
// first calls always have new information (no information has previously been provided).
const auto response = signal_processing_.sync()->WatchElementState(mute_pe_id_.value());
if (!response.ok()) {
return response.status();
}
gain_format.can_mute = true;
gain_state.muted = response->state.has_started() && response->state.started() &&
response->state.has_bypassed() && !response->state.bypassed();
}
break;
case fuchsia_hardware_audio_signalprocessing::wire::ElementType::kAutomaticGainControl:
if (!agc_pe_id_.has_value()) { // Use the first PE with agc support.
agc_pe_id_.emplace(pe.id());
// The first call from this client shouldn't block since this is a hanging-get, and the
// first calls always have new information (no information has previously been provided).
const auto response = signal_processing_.sync()->WatchElementState(agc_pe_id_.value());
if (!response.ok()) {
return response.status();
}
gain_format.can_agc = true;
gain_state.agc_enabled = response->state.has_started() && response->state.started() &&
response->state.has_bypassed() && !response->state.bypassed();
}
break;
default:
// TODO(https://fxbug.dev/42061588): Handle default case.
break;
}
}
gain_format_ = zx::ok(gain_format);
// Update the stored gain state, and start hanging gets to receive further gain state changes.
UpdateGainAndStartHangingGet(gain_state.gain);
UpdateMuteAndStartHangingGet(gain_state.muted);
UpdateAgcAndStartHangingGet(gain_state.agc_enabled);
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::result<Info> SimpleCodecClient::GetInfo() {
const auto result = codec_.sync()->GetProperties();
if (!result.ok()) {
return zx::error(result.status());
}
const fuchsia_hardware_audio::wire::CodecProperties& properties = result.value().properties;
Info info;
if (properties.has_unique_id()) {
info.unique_id.emplace();
std::memcpy(info.unique_id->data(), properties.unique_id().data(),
fuchsia_hardware_audio::wire::kUniqueIdSize);
}
info.manufacturer =
std::string(properties.manufacturer().data(), properties.manufacturer().size());
info.product_name = std::string(properties.product().data(), properties.product().size());
return zx::ok(std::move(info));
}
zx::result<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.size() == 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::result<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::result<GainFormat> SimpleCodecClient::GetGainFormat() { return gain_format_; }
zx::result<GainState> SimpleCodecClient::GetGainState() {
fbl::AutoLock lock(&gain_state_lock_);
return gain_state_;
}
void SimpleCodecClient::SetGainState(GainState gain_state) {
fidl::Arena allocator;
if (gain_pe_id_.has_value()) {
auto gain =
fuchsia_hardware_audio_signalprocessing::wire::SettableElementState::Builder(allocator);
gain.started(true).bypassed(false);
auto gain_parameters =
fuchsia_hardware_audio_signalprocessing::wire::GainElementState::Builder(allocator);
gain_parameters.gain(gain_state.gain);
gain.type_specific(
fuchsia_hardware_audio_signalprocessing::wire::SettableTypeSpecificElementState::WithGain(
allocator, gain_parameters.Build()));
auto ret = signal_processing_.sync()->SetElementState(gain_pe_id_.value(), gain.Build());
if (!ret.ok()) {
return;
}
}
if (mute_pe_id_.has_value()) {
auto mute =
fuchsia_hardware_audio_signalprocessing::wire::SettableElementState::Builder(allocator);
mute.started(true).bypassed(!gain_state.muted);
auto ret = signal_processing_.sync()->SetElementState(mute_pe_id_.value(), mute.Build());
if (!ret.ok()) {
return;
}
}
if (agc_pe_id_.has_value()) {
auto agc =
fuchsia_hardware_audio_signalprocessing::wire::SettableElementState::Builder(allocator);
agc.started(true).bypassed(!gain_state.agc_enabled);
auto ret = signal_processing_.sync()->SetElementState(agc_pe_id_.value(), agc.Build());
if (!ret.ok()) {
return;
}
}
}
void SimpleCodecClient::UpdateGainAndStartHangingGet(float gain) {
{
fbl::AutoLock lock(&gain_state_lock_);
if (!gain_state_.is_ok()) {
gain_state_ = zx::ok(GainState{});
}
gain_state_->gain = gain;
}
if (gain_pe_id_.has_value()) {
signal_processing_->WatchElementState(gain_pe_id_.value())
.Then([this](fidl::WireUnownedResult<
fuchsia_hardware_audio_signalprocessing::SignalProcessing::WatchElementState>&
result) {
if (!result.ok()) {
return;
}
if (result->state.has_started() && result->state.started() &&
result->state.has_bypassed() && !result->state.bypassed() &&
result->state.has_type_specific() && result->state.type_specific().is_gain() &&
result->state.type_specific().gain().has_gain()) {
UpdateGainAndStartHangingGet(result->state.type_specific().gain().gain());
}
});
}
}
void SimpleCodecClient::UpdateMuteAndStartHangingGet(bool mute) {
{
fbl::AutoLock lock(&gain_state_lock_);
if (!gain_state_.is_ok()) {
gain_state_ = zx::ok(GainState{});
}
gain_state_->muted = mute;
}
if (mute_pe_id_.has_value()) {
signal_processing_->WatchElementState(mute_pe_id_.value())
.Then([this](fidl::WireUnownedResult<
fuchsia_hardware_audio_signalprocessing::SignalProcessing::WatchElementState>&
result) {
if (!result.ok()) {
return;
}
UpdateMuteAndStartHangingGet(result->state.has_started() && result->state.started() &&
result->state.has_bypassed() && !result->state.bypassed());
});
}
}
void SimpleCodecClient::UpdateAgcAndStartHangingGet(bool agc) {
{
fbl::AutoLock lock(&gain_state_lock_);
if (!gain_state_.is_ok()) {
gain_state_ = zx::ok(GainState{});
}
gain_state_->agc_enabled = agc;
}
if (agc_pe_id_.has_value()) {
signal_processing_->WatchElementState(agc_pe_id_.value())
.Then([this](fidl::WireUnownedResult<
fuchsia_hardware_audio_signalprocessing::SignalProcessing::WatchElementState>&
result) {
if (!result.ok()) {
return;
}
UpdateAgcAndStartHangingGet(result->state.has_started() && result->state.started() &&
result->state.has_bypassed() && !result->state.bypassed());
});
}
}
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