AudioServer: Add support for volume control

This commit is contained in:
Bananymous 2026-04-02 15:21:38 +03:00
parent 5647cf24d2
commit bf4831f468
6 changed files with 100 additions and 9 deletions

View File

@ -145,7 +145,7 @@ namespace LibAudio
LibAudio::Packet packet { LibAudio::Packet packet {
.type = LibAudio::Packet::Notify, .type = LibAudio::Packet::Notify,
.parameter = 0, .parameter = {},
}; };
send(m_server_fd, &packet, sizeof(packet), 0); send(m_server_fd, &packet, sizeof(packet), 0);

View File

@ -40,6 +40,14 @@ namespace LibAudio
SetPin, // parameter: pin number SetPin, // parameter: pin number
// response: nothing // response: nothing
// set the active pin of the current device // set the active pin of the current device
GetVolume, // parameter: ignored
// response: 10 * volume percentage (uint32_t)
// get the volume of the current device
SetVolume, // parameter: 10 * volume percentage (uint32_t)
// response: nothing
// set the volume of the current device
} type; } type;
uint64_t parameter; uint64_t parameter;

View File

@ -98,12 +98,53 @@ bool AudioServer::on_client_packet(int fd, LibAudio::Packet packet)
return false; return false;
} }
reset_kernel_buffer(); reset_kernel_buffer();
if (uint32_t pin = packet.parameter; ioctl(device().fd, SND_SET_PIN, &pin) != 0) if (ioctl(device().fd, SND_SET_PIN, &packet.parameter) != 0)
dwarnln("Failed to set pin {}: {}", packet.parameter, strerror(errno)); dwarnln("Failed to set pin {}: {}", packet.parameter, strerror(errno));
else else
{
device().current_pin = packet.parameter; device().current_pin = packet.parameter;
// NOTE: different pins have different volume controls
if (ioctl(device().fd, SND_GET_VOLUME_INFO, &device().volume) != 0)
dwarnln("Failed to query new volume: {}", strerror(errno));
}
update(); update();
break; break;
case LibAudio::Packet::GetVolume:
{
// NOTE: maybe we should map [0->100%] -> [min, 0] instead?
const auto& volume = device().volume;
if (volume.min_mdB == volume.max_mdB)
response = 1000;
else
{
const double min_dB = volume.min_mdB / 1000.0;
const double max_dB = volume.max_mdB / 1000.0;
const double dB = volume.mdB / 1000.0;
const double percent = (BAN::Math::pow(10.0, (dB - min_dB) / (max_dB - min_dB)) - 1.0) / 9.0;
response = BAN::Math::round(percent * 1000.0);
}
break;
}
case LibAudio::Packet::SetVolume:
{
// NOTE: maybe we should map [0->100%] -> [min, 0] instead?
const auto& volume = device().volume;
const double min_dB = volume.min_mdB / 1000.0;
const double max_dB = volume.max_mdB / 1000.0;
const double percent = packet.parameter / 1000.0;
const double dB = min_dB + (max_dB - min_dB) * BAN::Math::log10(1.0 + 9.0 * percent);
const int32_t mdB = BAN::Math::round(dB * 1000.0);
if (ioctl(device().fd, SND_SET_VOLUME_MDB, &mdB) != 0)
dwarnln("Failed to set volume: {}", strerror(errno));
else if (ioctl(device().fd, SND_GET_VOLUME_INFO, &device().volume) != 0)
dwarnln("Failed to query new volume: {}", strerror(errno));
break;
}
default: default:
dwarnln("unknown packet type {}", static_cast<uint8_t>(packet.type)); dwarnln("unknown packet type {}", static_cast<uint8_t>(packet.type));
return false; return false;
@ -278,7 +319,7 @@ void AudioServer::send_samples()
for (size_t i = 0; i < samples_to_send; i++) for (size_t i = 0; i < samples_to_send; i++)
{ {
sample_buffer[i] = BAN::Math::clamp<sample_t>( sample_buffer[i] = BAN::Math::clamp<sample_t>(
0.2 * m_samples[m_samples_sent + i] * BAN::numeric_limits<kernel_sample_t>::max(), m_samples[m_samples_sent + i] * BAN::numeric_limits<kernel_sample_t>::max(),
BAN::numeric_limits<kernel_sample_t>::min(), BAN::numeric_limits<kernel_sample_t>::min(),
BAN::numeric_limits<kernel_sample_t>::max() BAN::numeric_limits<kernel_sample_t>::max()
); );

View File

@ -8,6 +8,8 @@
#include <LibAudio/Audio.h> #include <LibAudio/Audio.h>
#include <LibAudio/Protocol.h> #include <LibAudio/Protocol.h>
#include <sys/ioctl.h>
struct AudioDevice struct AudioDevice
{ {
int fd; int fd;
@ -15,6 +17,7 @@ struct AudioDevice
uint32_t sample_rate; uint32_t sample_rate;
uint32_t total_pins; uint32_t total_pins;
uint32_t current_pin; uint32_t current_pin;
snd_volume_info volume;
}; };
class AudioServer class AudioServer

View File

@ -68,6 +68,8 @@ static BAN::Optional<AudioDevice> initialize_audio_device(int fd)
return {}; return {};
if (ioctl(fd, SND_GET_PIN, &result.current_pin) != 0) if (ioctl(fd, SND_GET_PIN, &result.current_pin) != 0)
return {}; return {};
if (ioctl(fd, SND_GET_VOLUME_INFO, &result.volume) != 0)
return {};
return result; return result;
} }

View File

@ -74,17 +74,20 @@ static uint32_t send_request(int fd, LibAudio::Packet packet, bool wait_response
static void list_devices(int fd) static void list_devices(int fd)
{ {
const uint32_t current_device = send_request(fd, { .type = LibAudio::Packet::GetDevice, .parameter = 0 }, true); const uint32_t current_device = send_request(fd, { .type = LibAudio::Packet::GetDevice, .parameter = {} }, true);
const uint32_t current_pin = send_request(fd, { .type = LibAudio::Packet::GetPin, .parameter = 0 }, true); const uint32_t current_pin = send_request(fd, { .type = LibAudio::Packet::GetPin, .parameter = {} }, true);
const uint32_t total_devices = send_request(fd, { .type = LibAudio::Packet::QueryDevices, .parameter = 0 }, true); const uint32_t total_devices = send_request(fd, { .type = LibAudio::Packet::QueryDevices, .parameter = {} }, true);
for (uint32_t dev = 0; dev < total_devices; dev++) for (uint32_t dev = 0; dev < total_devices; dev++)
{ {
const uint32_t total_pins = send_request(fd, { .type = LibAudio::Packet::QueryPins, .parameter = dev }, true); const uint32_t total_pins = send_request(fd, { .type = LibAudio::Packet::QueryPins, .parameter = dev }, true);
printf("Device %" PRIu32 "", dev); printf("Device %" PRIu32 "", dev);
if (dev == current_device) if (dev == current_device)
printf(" (current)"); {
const uint32_t volume = send_request(fd, { .type = LibAudio::Packet::GetVolume, .parameter = {} }, true);
printf(" (current) %" PRIu32 "%%", volume / 10);
}
printf("\n"); printf("\n");
for (uint32_t pin = 0; pin < total_pins; pin++) for (uint32_t pin = 0; pin < total_pins; pin++)
@ -103,16 +106,20 @@ int main(int argc, char** argv)
BAN::Optional<uint32_t> device; BAN::Optional<uint32_t> device;
BAN::Optional<uint32_t> pin; BAN::Optional<uint32_t> pin;
BAN::Optional<uint32_t> volume;
BAN::Optional<bool> volume_rel; // no value: absolute, false positive, true negative
for (;;) for (;;)
{ {
static option long_options[] { static option long_options[] {
{ "list", no_argument, nullptr, 'l' }, { "list", no_argument, nullptr, 'l' },
{ "device", required_argument, nullptr, 'd' }, { "device", required_argument, nullptr, 'd' },
{ "pin", required_argument, nullptr, 'p' }, { "pin", required_argument, nullptr, 'p' },
{ "volume", required_argument, nullptr, 'v' },
{ "help", no_argument, nullptr, 'h' }, { "help", no_argument, nullptr, 'h' },
}; };
int ch = getopt_long(argc, argv, "ld:p:h", long_options, nullptr); int ch = getopt_long(argc, argv, "ld:p:v:h", long_options, nullptr);
if (ch == -1) if (ch == -1)
break; break;
@ -125,6 +132,7 @@ int main(int argc, char** argv)
fprintf(stderr, " -l, --list list devices and their pins\n"); fprintf(stderr, " -l, --list list devices and their pins\n");
fprintf(stderr, " -d, --device N set device index N as the current one\n"); fprintf(stderr, " -d, --device N set device index N as the current one\n");
fprintf(stderr, " -p, --pin N set pin N as the current one\n"); fprintf(stderr, " -p, --pin N set pin N as the current one\n");
fprintf(stderr, " -v, --volume N set volume to N%%. if + or - is given, volume is relative to the current volume\n");
fprintf(stderr, " -h, --help show this message and exit\n"); fprintf(stderr, " -h, --help show this message and exit\n");
return 0; return 0;
case 'l': case 'l':
@ -136,6 +144,11 @@ int main(int argc, char** argv)
case 'p': case 'p':
pin = parse_u32_or_exit(optarg); pin = parse_u32_or_exit(optarg);
break; break;
case 'v':
if (optarg[0] == '-' || optarg[0] == '+')
volume_rel = (optarg[0] == '-');
volume = parse_u32_or_exit(optarg + volume_rel.has_value());
break;
case '?': case '?':
fprintf(stderr, "invalid option %c\n", optopt); fprintf(stderr, "invalid option %c\n", optopt);
fprintf(stderr, "see '%s --help' for usage\n", argv[0]); fprintf(stderr, "see '%s --help' for usage\n", argv[0]);
@ -143,7 +156,7 @@ int main(int argc, char** argv)
} }
} }
if (!device.has_value() && !pin.has_value()) if (!device.has_value() && !pin.has_value() && !volume.has_value())
list = true; list = true;
const int fd = get_server_fd(); const int fd = get_server_fd();
@ -156,6 +169,30 @@ int main(int argc, char** argv)
if (pin.has_value()) if (pin.has_value())
send_request(fd, { .type = LibAudio::Packet::SetPin, .parameter = pin.value() }, false); send_request(fd, { .type = LibAudio::Packet::SetPin, .parameter = pin.value() }, false);
if (volume.has_value())
{
const uint32_t volume10 = volume.value() * 10;
uint32_t param;
if (!volume_rel.has_value())
param = volume10;
else
{
const uint32_t current = send_request(fd, { .type = LibAudio::Packet::GetVolume, .parameter = {} }, true);
if (!volume_rel.value())
param = current + volume10;
else
{
if (current > volume10)
param = current - volume10;
else
param = 0;
}
}
send_request(fd, { .type = LibAudio::Packet::SetVolume, .parameter = param }, false);
}
if (list) if (list)
list_devices(fd); list_devices(fd);