blob: 2d844f67ef768564c1dd5d5f723fd66d02623d32 [file] [log] [blame]
// Copyright 2023 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/fidl.h>
#include <fidl/fuchsia.hardware.audio/cpp/natural_ostream.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/unsafe.h>
#include <lib/fit/defer.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <zircon/status.h>
#include <deque>
#include <filesystem>
#include <sstream>
#include <utility>
#include <fbl/unique_fd.h>
constexpr char kCodecClassDir[] = "/dev/class/codec";
// LINT.IfChange
constexpr char kUsageSummary[] = R"""(
Audio hardware codec driver control.
Usage:
audio-codec-ctl [-d|--device <device>] f[ormats]
audio-codec-ctl [-d|--device <device>] b[ridgeable]
audio-codec-ctl [-d|--device <device>] r[eset]
audio-codec-ctl [-d|--device <device>] m[ode_bridged] true|false
audio-codec-ctl [-d|--device <device>] d[ai] <number_of_channels>
<channels_to_use_bitmask> pdm|upcm|spcm|fpcm none|i2s|left-stereo|right-stereo|1tdm|2tdm|3tdm
<frame_rate> <bits_per_slot> <bits_per_sample>
audio-codec-ctl [-d|--device <device>] start
audio-codec-ctl [-d|--device <device>] stop
audio-codec-ctl [-d|--device <device>] p[lug_state]
audio-codec-ctl [-d|--device <device>] e[lements]
audio-codec-ctl [-d|--device <device>] t[opologies]
audio-codec-ctl [-d|--device <device>] w[atch] <id>
audio-codec-ctl [-d|--device <device>] set <id> [start|stop] [bypass] [gain <gain>]
[vendor <hex> <hex> ...]
audio-codec-ctl [-h|--help]
)""";
constexpr char kUsageDetails[] = R"""(
Audio hardware codec driver control on <device> (full path specified e.g. /dev/class/codec/123 or
only the devfs node name specified e.g. 123) or unspecified (picks the first device in
/dev/class/codec).
Commands:
f[ormats] : Retrieves the DAI formats supported by the codec.
i[nfo] : Retrieves textual information about the codec.
c[apabilities_plug_detect] : Retrieves Plug Detect Capabilities.
r[eset] : Resets the codec.
d[ai] <number_of_channels> : Sets the DAI format to be used in the codec interface.
<number_of_channels>: Number of channels.
<channels_to_use_bitmask>: Sets which channels are active via a bitmask. The least significant
bit corresponds to channel index 0.
pdm: Pulse Density Modulation samples.
upcm: Signed Linear Pulse Code Modulation samples at the host endianness.
spcm: Unsigned Linear Pulse Code Modulation samples at the host endianness.
fpcm: Floating point samples IEEE-754 encoded.
none: No frame format as in samples without a frame sync like PDM.
i2s: Format as specified in the I2S specification.
left-stereo: Left justified, 2 channels.
right-stereo: Right justified, 2 channels.
1tdm: Left justified, variable number of channels, data starts at frame sync changes from low to
high clocked out at the rising edge of sclk. The frame sync must stay high for exactly 1
clock cycle.
2tdm: Left justified, variable number of channels, data starts one clock cycle after the frame
sync changes from low to high clocked out at the rising edge of sclk. The frame sync must
stay high for exactly 1 clock cycle.
3tdm: Left justified, variable number of channels, data starts two clock cycles after the frame
sync changes from low to high clocked out at the rising edge of sclk. The frame sync must
stay high for exactly 1 clock cycle.
<frame_rate>: The frame rate for all samples.
<bits_per_slot>: The bits per slot for all channels.
<bits_per_sample>: The bits per sample for all samples. Must be smaller than bits per channel for
samples to fit.
start : Start/Re-start the codec operation.
stop : Stops the codec operation.
p[lug_state] : Get the plug detect state.
e[lements] : Returns a vector of supported processing elements.
t[opologies] : Returns a vector of supported topologies.
w[atch] <id> : Get a processing element state.
set <id> [start|stop] [bypass] [gain <gain>] [vendor <hex> <hex> ...] : Controls a processing
element.
<id>: Processing element id.
start: Process element started state.
stop: Process element stopped state.
bypass: Process element bypassed state.
<gain>: Current gain in GainType format reported in the supported processing elements vector.
<hex>: Vendor specific raw byte to feed to the processing element in hex format.
Examples:
Retrieves the DAI formats supported:
$ audio-codec-ctl f
Executing on device /dev/class/codec/209
[ fuchsia_hardware_audio::DaiSupportedFormats{ number_of_channels = [ 2, 4, ], sample_formats = [ fuchsia_hardware_audio::DaiSampleFormat::kPcmSigned, ], frame_formats = [ fuchsia_hardware_audio::DaiFrameFormat::frame_format_standard(fuchsia_hardware_audio::DaiFrameFormatStandard::kI2S), fuchsia_hardware_audio::DaiFrameFormat::frame_format_standard(fuchsia_hardware_audio::DaiFrameFormatStandard::k1tdm), ], frame_rates = [ 48000, 96000, ], bits_per_slot = [ 16, 32, ], bits_per_sample = [ 16, 32, ], }, ]
Retrieves textual information:
$ audio-codec-ctl i
Executing on device /dev/class/codec/706
fuchsia_hardware_audio::CodecInfo{ unique_id = "", manufacturer = "Texas Instruments", product_name = "TAS5825m", }
Retrieves Plug Detect Capabilities:
$ audio-codec-ctl c
Executing on device: /dev/class/codec/706
fuchsia_hardware_audio::PlugDetectCapabilities::kHardwired
Resets the codec:
$ audio-codec-ctl r
Executing on device: /dev/class/codec/706
Reset done
Sets the DAI format to be used in the codec interface:
$ audio-codec-ctl d 2 1 s i 48000 16 32
Setting DAI format:
fuchsia_hardware_audio::DaiFormat{ number_of_channels = 2, channels_to_use_bitmask = 1, sample_format = fuchsia_hardware_audio::DaiSampleFormat::kPcmSigned, frame_format = fuchsia_hardware_audio::DaiFrameFormat::frame_format_standard(fuchsia_hardware_audio::DaiFrameFormatStandard::kI2S), frame_rate = 48000, bits_per_slot = 16, bits_per_sample = 32, }
Executing on device: /dev/class/codec/706
Start/Re-start the codec operation:
$ audio-codec-ctl start
Executing on device: /dev/class/codec/706
Start done
Stops the codec operation:
$ audio-codec-ctl stop
Executing on device: /dev/class/codec/706
Stop done
Get the plug detect state:
$ audio-codec-ctl p
Executing on device: /dev/class/codec/706
fuchsia_hardware_audio::PlugState{ plugged = true, plug_state_time = 1167863520, }
Returns a vector of supported processing elements:
$ audio-codec-ctl e
Executing on device: /dev/class/codec/706
[ fuchsia_hardware_audio_signalprocessing::Element{ id = 1, type = fuchsia_hardware_audio_signalprocessing::ElementType::kGain, type_specific = fuchsia_hardware_audio_signalprocessing::TypeSpecificElement::gain(fuchsia_hardware_audio_signalprocessing::Gain{ type = fuchsia_hardware_audio_signalprocessing::GainType::kDecibels, min_gain = -63.5, max_gain = 0, min_gain_step = 0.5, }), }, fuchsia_hardware_audio_signalprocessing::Element{ id = 2, type = fuchsia_hardware_audio_signalprocessing::ElementType::kMute, }, ]
Returns a vector of supported topologies.
$ audio-codec-ctl t
Executing on device: /dev/class/codec/706
[ fuchsia_hardware_audio_signalprocessing::Topology{ id = 1, processing_elements_edge_pairs = [ fuchsia_hardware_audio_signalprocessing::EdgePair{ processing_element_id_from = 1, processing_element_id_to = 2, }, fuchsia_hardware_audio_signalprocessing::EdgePair{ processing_element_id_from = 2, processing_element_id_to = 3, }, ], }, ]
Get a processing element state.
$ audio-codec-ctl w 1
Executing on device: /dev/class/codec/706
fuchsia_hardware_audio_signalprocessing::ElementState{ type_specific = fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::gain(fuchsia_hardware_audio_signalprocessing::GainElementState{ gain = 0, }), started = true, }
Controls a processing element.
$ audio-codec-ctl set 1 start gain 1.23 vendor 0x12 0x98
Setting element state:
fuchsia_hardware_audio_signalprocessing::SignalProcessingSetElementStateRequest{ processing_element_id = 1, state = fuchsia_hardware_audio_signalprocessing::ElementState{ type_specific = fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::gain(fuchsia_hardware_audio_signalprocessing::GainElementState{ gain = 1.23, }), started = true, vendor_specific_data = [ 18, 152, ], }, }
Executing on device: /dev/class/codec/706
Specify device:
$ audio-codec-ctl -d 706 p
Executing on device: /dev/class/codec/706
fuchsia_hardware_audio::PlugState{ plugged = true, plug_state_time = 1167863520, }
$ audio-codec-ctl -d 123 p
Executing on device /dev/class/codec/123
watch plug state failed: FIDL operation failed due to peer closed, status: ZX_ERR_PEER_CLOSED (-24)
$ audio-codec-ctl -d /dev/class/codec/706 p
Executing on device: /dev/class/codec/706
fuchsia_hardware_audio::PlugState{ plugged = true, plug_state_time = 1167863520, }
)""";
// LINT.ThenChange(//docs/reference/tools/hardware/audio-codec-ctl.md)
template <typename T>
std::string ToString(const T& value) {
std::ostringstream buf;
buf << value;
return buf.str();
}
template <typename T>
std::string FidlString(const T& value) {
return ToString(fidl::ostream::Formatted<T>(value));
}
void ShowUsage(bool show_details) {
std::cout << kUsageSummary;
if (!show_details) {
std::cout << std::endl << "Use `audio-codec-ctl --help` to see full help text" << std::endl;
return;
}
std::cout << kUsageDetails;
}
fidl::SyncClient<fuchsia_hardware_audio::Codec> GetCodecClient(std::string path) {
if (!path.size()) {
for (const auto& entry : std::filesystem::directory_iterator(kCodecClassDir)) {
path = entry.path().string();
break;
}
}
std::cout << "Executing on device " << path << std::endl;
zx::result connector = component::Connect<fuchsia_hardware_audio::CodecConnector>(path);
if (connector.is_error()) {
std::cerr << "could not connect to:" << path << " status:" << connector.status_string();
return {};
}
fidl::SyncClient connector_client(std::move(connector.value()));
auto [local, remote] = fidl::Endpoints<fuchsia_hardware_audio::Codec>::Create();
auto connect_ret = connector_client->Connect(std::move(remote));
ZX_ASSERT(connect_ret.is_ok());
return fidl::SyncClient<fuchsia_hardware_audio::Codec>(std::move(local));
}
fidl::SyncClient<fuchsia_hardware_audio_signalprocessing::SignalProcessing> GetSignalClient(
std::string path) {
auto [local, remote] =
fidl::Endpoints<fuchsia_hardware_audio_signalprocessing::SignalProcessing>::Create();
auto connect_ret = GetCodecClient(std::move(path))->SignalProcessingConnect(std::move(remote));
ZX_ASSERT(connect_ret.is_ok());
return fidl::SyncClient<fuchsia_hardware_audio_signalprocessing::SignalProcessing>(
std::move(local));
}
int main(int argc, char** argv) {
printf(
"WARNING: audio-codec-ctl is deprecated. Please use `ffx audio device`\n"
"to get and set device properties. For more information,\n"
"run `ffx audio device --help`,\n");
std::string path = {};
std::deque<std::string> args(argv + 1, argv + argc);
if (args.size() == 0) { // Must have a parameter.
ShowUsage(false);
return 0;
}
if (!args.front().compare(0, 2, "-d") || !args.front().compare(0, 3, "--d")) {
args.pop_front();
if (args.size() == 0) { // Must have a <device> if -d or --d
ShowUsage(false);
return 0;
}
// Allows using only the devfs node number, for instance "123" instead of
// "/dev/class/codec/123".
path = args.front();
args.pop_front();
int id = -1;
if (sscanf(path.c_str(), "%u", &id) == 1) {
path = std::string(kCodecClassDir) + "/" + path;
}
} else if (!args.front().compare(0, 2, "-h") || !args.front().compare(0, 3, "--h")) {
ShowUsage(true);
return 0;
}
if (args.size() == 0) { // Must have a command.
ShowUsage(false);
return 0;
}
std::string_view cmd(args.front());
args.pop_front();
switch (cmd[0]) {
case 'f': {
auto result = GetCodecClient(path)->GetDaiFormats();
if (result.is_error()) {
std::cerr << "get DAI formats failed: " << result.error_value().FormatDescription()
<< std::endl;
return -1;
}
std::cout << FidlString(result->formats()) << std::endl;
return 0;
}
case 'd': {
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
uint32_t number_of_channels = 0;
if (sscanf(args.front().c_str(), "%u", &number_of_channels) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
uint64_t channels_to_use_bitmask = 0;
if (sscanf(args.front().c_str(), "%lx", &channels_to_use_bitmask) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
fuchsia_hardware_audio::DaiSampleFormat sample_format = {};
switch (args.front()[0]) {
case 'p':
sample_format = fuchsia_hardware_audio::DaiSampleFormat::kPdm;
break;
case 'u':
sample_format = fuchsia_hardware_audio::DaiSampleFormat::kPcmUnsigned;
break;
case 's':
default:
sample_format = fuchsia_hardware_audio::DaiSampleFormat::kPcmSigned;
break;
case 'f':
sample_format = fuchsia_hardware_audio::DaiSampleFormat::kPcmFloat;
break;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
auto frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard({});
switch (args.front()[0]) {
case 'n':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kNone);
break;
case 'i':
default:
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kI2S);
break;
case 'l':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kStereoLeft);
break;
case 'r':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kStereoRight);
break;
case '1':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kTdm1);
break;
case '2':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kTdm2);
break;
case '3':
frame_format = fuchsia_hardware_audio::DaiFrameFormat::WithFrameFormatStandard(
fuchsia_hardware_audio::DaiFrameFormatStandard::kTdm3);
break;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
uint32_t frame_rate = 0;
if (sscanf(args.front().c_str(), "%u", &frame_rate) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
uint32_t bits_per_slot = 0;
if (sscanf(args.front().c_str(), "%u", &bits_per_slot) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
if (args.size() == 0) {
ShowUsage(false);
return -1;
}
uint32_t bits_per_sample = 0;
if (sscanf(args.front().c_str(), "%u", &bits_per_sample) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
fuchsia_hardware_audio::DaiFormat format(
number_of_channels, channels_to_use_bitmask, sample_format, std::move(frame_format),
frame_rate, static_cast<uint8_t>(bits_per_slot), static_cast<uint8_t>(bits_per_sample));
std::cout << "Setting DAI format:" << std::endl;
std::cout << FidlString(format) << std::endl;
auto result = GetCodecClient(path)->SetDaiFormat(std::move(format));
if (!result.is_ok()) {
std::cerr << "set DAI format failed: " << result.error_value().FormatDescription()
<< std::endl;
return -1;
}
return 0;
}
case 'r': {
auto result = GetCodecClient(path)->Reset();
if (!result.is_ok()) {
std::cerr << "reset failed: " << result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << "Reset done" << std::endl;
return 0;
}
case 's':
if (cmd == "start") {
auto result = GetCodecClient(path)->Start();
if (!result.is_ok()) {
std::cerr << "start failed: " << result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << "Start done" << std::endl;
return 0;
} else if (cmd == "stop") {
auto result = GetCodecClient(path)->Stop();
if (!result.is_ok()) {
std::cerr << "stop failed: " << result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << "Stop done" << std::endl;
return 0;
} else if (cmd == "set") {
if (args.size() == 0) { // Must have an id.
ShowUsage(false);
return -1;
}
uint64_t id = 0;
if (sscanf(args.front().c_str(), "%lu", &id) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
fuchsia_hardware_audio_signalprocessing::SettableElementState state;
if (args.size() > 0) {
if (args.front() == "start") {
args.pop_front();
state.started(true);
state.bypassed(false);
} else if (args.front() == "stop") {
args.pop_front();
state.started(false);
}
}
if (args.size() > 0) {
if (args.front() == "bypass") {
args.pop_front();
state.bypassed(true);
}
}
if (args.size() > 0) {
if (args.front() == "gain") {
args.pop_front();
if (args.size() > 0) {
fuchsia_hardware_audio_signalprocessing::GainElementState gain_state;
float gain = std::stof(args.front());
args.pop_front();
gain_state.gain(gain);
state.type_specific(
fuchsia_hardware_audio_signalprocessing::SettableTypeSpecificElementState::
WithGain(std::move(gain_state)));
} else {
std::cerr << "set processing element state failed: no gain specified" << std::endl;
return -1;
}
}
}
if (args.size() > 0) {
if (args.front() == "latency") {
std::cerr << "set processing element state failed: cannot specify latency (deprecated)"
<< std::endl;
return -1;
}
}
if (args.size() > 0) {
if (args.front() == "vendor") {
args.pop_front();
std::vector<uint8_t> bytes;
while (args.size() > 0) {
uint32_t hex = 0;
if (sscanf(args.front().c_str(), "%x", &hex) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
bytes.push_back(static_cast<uint8_t>(hex));
}
state.vendor_specific_data(std::move(bytes));
}
}
if (args.size() > 0) { // Error if we have unparsed parameters.
ShowUsage(false);
return -1;
}
fuchsia_hardware_audio_signalprocessing::SignalProcessingSetElementStateRequest request(
id, std::move(state));
std::cout << "Setting element state:" << std::endl;
std::cout << FidlString(request) << std::endl;
auto result = GetSignalClient(path)->SetElementState(request);
if (result.is_error()) {
std::cerr << "set processing element state failed: "
<< result.error_value().FormatDescription() << std::endl;
return -1;
}
return 0;
}
break;
case 'p': {
auto result = GetCodecClient(path)->WatchPlugState();
if (!result.is_ok()) {
std::cerr << "watch plug state failed: " << result.error_value().FormatDescription()
<< std::endl;
return -1;
}
std::cout << FidlString(result->plug_state()) << std::endl;
return 0;
}
case 'e': {
auto result = GetSignalClient(path)->GetElements();
if (result.is_error()) {
std::cerr << "get signal processing elements failed: "
<< result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << FidlString(result->processing_elements()) << std::endl;
return 0;
}
case 't': {
auto result = GetSignalClient(path)->GetTopologies();
if (result.is_error()) {
std::cerr << "get signal processing topologies failed: "
<< result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << FidlString(result->topologies()) << std::endl;
return 0;
}
case 'w': {
if (args.size() == 0) { // Must have an id.
ShowUsage(false);
return -1;
}
uint64_t id = 0;
if (sscanf(args.front().c_str(), "%lu", &id) != 1) {
ShowUsage(false);
return -1;
}
args.pop_front();
auto result = GetSignalClient(path)->WatchElementState(id);
if (result.is_error()) {
std::cerr << "watch processing element state failed: "
<< result.error_value().FormatDescription() << std::endl;
return -1;
}
std::cout << FidlString(result->state()) << std::endl;
return 0;
}
default:
ShowUsage(false);
return -1;
}
ShowUsage(false);
return 0;
}