blob: d4f9662ffe7571e780e670f7d6c1fc3d58ba415e [file] [log] [blame]
// Copyright 2024 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 "src/media/audio/services/device_registry/testing/fake_codec.h"
#include <fidl/fuchsia.audio.device/cpp/natural_types.h>
#include <fidl/fuchsia.hardware.audio.signalprocessing/cpp/markers.h>
#include <fidl/fuchsia.hardware.audio/cpp/natural_types.h>
#include <lib/fit/result.h>
#include <zircon/errors.h>
#include <iterator>
#include <gtest/gtest.h>
namespace media_audio {
using fuchsia_hardware_audio::Codec;
const fuchsia_hardware_audio::DaiFrameFormat FakeCodec::kDefaultFrameFormat =
fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kI2S);
const std::vector<uint32_t> FakeCodec::kDefaultNumberOfChannelsSet{
FakeCodec::kDefaultNumberOfChannels, FakeCodec::kDefaultNumberOfChannels2};
const std::vector<fuchsia_hardware_audio::DaiSampleFormat> FakeCodec::kDefaultSampleFormatsSet{
FakeCodec::kDefaultDaiSampleFormat};
const std::vector<fuchsia_hardware_audio::DaiFrameFormat> FakeCodec::kDefaultFrameFormatsSet{
FakeCodec::kDefaultFrameFormat};
const std::vector<uint32_t> FakeCodec::kDefaultFrameRatesSet{FakeCodec::kDefaultFrameRates};
const std::vector<uint8_t> FakeCodec::kDefaultBitsPerSlotSet{FakeCodec::kDefaultBitsPerSlot};
const std::vector<uint8_t> FakeCodec::kDefaultBitsPerSampleSet{FakeCodec::kDefaultBitsPerSample};
const fuchsia_hardware_audio::DaiSupportedFormats FakeCodec::kDefaultDaiFormatSet{{
.number_of_channels = FakeCodec::kDefaultNumberOfChannelsSet,
.sample_formats = FakeCodec::kDefaultSampleFormatsSet,
.frame_formats = FakeCodec::kDefaultFrameFormatsSet,
.frame_rates = FakeCodec::kDefaultFrameRatesSet,
.bits_per_slot = FakeCodec::kDefaultBitsPerSlotSet,
.bits_per_sample = FakeCodec::kDefaultBitsPerSampleSet,
}};
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats> FakeCodec::kDefaultDaiFormatSets{
FakeCodec::kDefaultDaiFormatSet};
FakeCodec::FakeCodec(zx::channel server_end, zx::channel client_end, async_dispatcher_t* dispatcher)
: dispatcher_(dispatcher),
server_end_(std::move(server_end)),
client_end_(std::move(client_end)) {
ADR_LOG_METHOD(kLogFakeCodec || kLogObjectLifetimes);
set_stream_unique_id(kDefaultUniqueInstanceId);
SetDefaultFormatSets();
}
FakeCodec::~FakeCodec() {
ADR_LOG_METHOD(kLogFakeCodec || kLogObjectLifetimes);
signal_processing_binding_.reset();
binding_.reset();
}
void on_unbind(FakeCodec* fake_codec, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_hardware_audio::Codec> server_end) {
ADR_LOG(kLogFakeCodec || kLogObjectLifetimes) << "FakeCodec disconnected";
}
fidl::ClientEnd<fuchsia_hardware_audio::Codec> FakeCodec::Enable() {
ADR_LOG_METHOD(kLogFakeCodec);
EXPECT_TRUE(server_end_.is_valid());
EXPECT_TRUE(client_end_.is_valid());
EXPECT_TRUE(dispatcher_);
EXPECT_FALSE(binding_);
binding_ = fidl::BindServer(dispatcher_, std::move(server_end_), this);
EXPECT_FALSE(server_end_.is_valid());
return std::move(client_end_);
}
void FakeCodec::DropCodec() {
ADR_LOG_METHOD(kLogFakeCodec);
health_completer_.reset();
watch_plug_state_completer_.reset();
binding_->Close(ZX_ERR_PEER_CLOSED);
}
void FakeCodec::GetHealthState(GetHealthStateCompleter::Sync& completer) {
if (responsive_) {
if (healthy_.has_value()) {
completer.Reply(fuchsia_hardware_audio::HealthState{{healthy_.value()}});
} else {
completer.Reply({});
}
} else {
health_completer_.emplace(completer.ToAsync()); // Just pend it; never respond.
}
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::SignalProcessingConnect(SignalProcessingConnectRequest& request,
SignalProcessingConnectCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
if (!supports_signalprocessing_) {
request.protocol().Close(ZX_ERR_NOT_SUPPORTED);
return;
}
// TODO(https://fxbug.dev/323270827): implement signalprocessing for Codec (topology, gain).
// Not yet implemented.
signal_processing_binding_ =
fidl::BindServer(dispatcher_,
fidl::ServerEnd<fuchsia_hardware_audio_signalprocessing::SignalProcessing>(
request.protocol().TakeChannel()),
this);
signal_processing_binding_->Close(ZX_ERR_NOT_SUPPORTED);
}
void FakeCodec::GetProperties(GetPropertiesCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
// Gather the properties and return them.
fuchsia_hardware_audio::CodecProperties codec_properties{};
if (is_input_) {
codec_properties.is_input(*is_input_);
}
if (manufacturer_) {
codec_properties.manufacturer(*manufacturer_);
}
if (product_) {
codec_properties.product(*product_);
}
if (uid_) {
codec_properties.unique_id(*uid_);
}
if (plug_detect_capabilities_) {
codec_properties.plug_detect_capabilities(*plug_detect_capabilities_);
}
completer.Reply(codec_properties);
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::GetDaiFormats(GetDaiFormatsCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
completer.Reply(fit::ok(std::vector<fuchsia_hardware_audio::DaiSupportedFormats>{
{{
.number_of_channels = number_of_channels_,
.sample_formats = sample_formats_,
.frame_formats = frame_formats_,
.frame_rates = frame_rates_,
.bits_per_slot = bits_per_slot_,
.bits_per_sample = bits_per_sample_,
}},
}));
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
bool FakeCodec::CheckDaiFormatSupported(const fuchsia_hardware_audio::DaiFormat& candidate) {
return (std::find(number_of_channels_.begin(), number_of_channels_.end(),
candidate.number_of_channels()) != std::end(number_of_channels_) &&
candidate.channels_to_use_bitmask() < (1u << candidate.number_of_channels()) &&
std::find(sample_formats_.begin(), sample_formats_.end(), candidate.sample_format()) !=
std::end(sample_formats_) &&
std::find(frame_formats_.begin(), frame_formats_.end(), candidate.frame_format()) !=
std::end(frame_formats_) &&
std::find(frame_rates_.begin(), frame_rates_.end(), candidate.frame_rate()) !=
std::end(frame_rates_) &&
std::find(bits_per_slot_.begin(), bits_per_slot_.end(), candidate.bits_per_slot()) !=
std::end(bits_per_slot_) &&
std::find(bits_per_sample_.begin(), bits_per_sample_.end(),
candidate.bits_per_sample()) != std::end(bits_per_sample_));
}
void FakeCodec::SetDaiFormat(SetDaiFormatRequest& request, SetDaiFormatCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
if (!CheckDaiFormatSupported(request.format())) {
completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED));
return;
}
selected_format_.emplace(request.format());
fuchsia_hardware_audio::CodecFormatInfo info;
if (external_delay_) {
info.external_delay(external_delay_->get());
}
if (turn_on_delay_) {
info.turn_on_delay(turn_on_delay_->get());
}
if (turn_off_delay_) {
info.turn_off_delay(turn_off_delay_->get());
}
completer.Reply(fit::ok(info));
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::Start(StartCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
EXPECT_TRUE(selected_format_);
if (!is_running_) {
mono_start_time_ = zx::clock::get_monotonic();
is_running_ = true;
}
completer.Reply(mono_start_time_.get());
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::Stop(StopCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
if (is_running_) {
mono_stop_time_ = zx::clock::get_monotonic();
is_running_ = false;
}
completer.Reply(mono_stop_time_.get());
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::Reset(ResetCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
if (is_running_) {
mono_stop_time_ = zx::clock::get_monotonic();
is_running_ = false;
}
if (selected_format_) {
selected_format_.reset();
external_delay_.reset();
turn_on_delay_.reset();
turn_off_delay_.reset();
}
// TODO(https://fxbug.dev/323270827): implement signalprocessing for Codec (topology, gain).
// Reset all signalprocessing Elements and the signalprocessing Topology.
completer.Reply();
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::WatchPlugState(WatchPlugStateCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogFakeCodec);
ASSERT_FALSE(watch_plug_state_completer_.has_value())
<< " received second WatchPlugState while first is still pending";
// If pending plug state change, clear the dirty bit and return the state with this completer.
if (plug_has_changed_) {
plug_has_changed_ = false;
completer.Reply(fuchsia_hardware_audio::PlugState{{
.plugged = plugged_,
.plug_state_time = plug_state_time_.get(),
}});
} else {
watch_plug_state_completer_ = completer.ToAsync();
}
ADR_LOG_METHOD(kLogFakeCodec) << "exit";
}
void FakeCodec::InjectPluggedAt(zx::time plug_time) {
ADR_LOG_METHOD(kLogFakeCodec) << "(plugged, " << plug_time.get() << ")";
ASSERT_TRUE(!watch_plug_state_completer_ || !plug_has_changed_)
<< "Inconsistent state: WatchPlugState is pending, but a plug change is waiting to be reported";
// Compare to previously-reported plug state: only report _changes_ that occurred _later_.
if (!plugged_ && plug_time > plug_state_time_) {
plugged_ = true;
plug_state_time_ = plug_time;
HandlePlugResponse();
}
}
void FakeCodec::InjectUnpluggedAt(zx::time plug_time) {
ADR_LOG_METHOD(kLogFakeCodec) << "(unplugged, " << plug_time.get() << ")";
ASSERT_TRUE(!watch_plug_state_completer_ || !plug_has_changed_)
<< "Inconsistent state: WatchPlugState is pending, but a plug change is waiting to be reported";
// Compare to previously-reported plug state: only report _changes_ that occurred _later_.
if (plugged_ && plug_time > plug_state_time_) {
plugged_ = false;
plug_state_time_ = plug_time;
HandlePlugResponse();
}
}
void FakeCodec::HandlePlugResponse() {
// A WatchPlugState is pending; complete it.
if (watch_plug_state_completer_) {
watch_plug_state_completer_->Reply(fuchsia_hardware_audio::PlugState{{
.plugged = plugged_,
.plug_state_time = plug_state_time_.get(),
}});
watch_plug_state_completer_.reset();
plug_has_changed_ = false;
} else { // Pend this plug change for a WatchPlugState call in the future.
plug_has_changed_ = true;
}
}
} // namespace media_audio