Skip to content

Commit a1cd65b

Browse files
committed
webrtc: Add Sound volume and mute controls support
This change implements volume and mute controls for the WebRTC audio backend in Cuttlefish. These controls allow the guest to dynamically adjust playback and capture audio levels via Virtio Sound control messages. - AudioMixer: Apply playback volume scaling during audio stream resampling by modifying the channel mix map. - Implement read/write logic for Virtio audio volume/mute control commands. - Guest Config: Allow enabling of Virtio Sound controls per stream.
1 parent 0e49030 commit a1cd65b

File tree

13 files changed

+567
-83
lines changed

13 files changed

+567
-83
lines changed

base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,11 @@ Result<CuttlefishConfig> InitializeCuttlefishConfiguration(
840840

841841
instance.set_audio_output_streams_count(
842842
guest_configs[instance_index].output_audio_streams_count);
843+
const std::optional<::cuttlefish::config::Audio>& audio_settings =
844+
guest_configs[instance_index].audio_settings;
845+
if (audio_settings.has_value()) {
846+
instance.set_audio_settings(audio_settings.value());
847+
}
843848

844849
// jcardsim
845850
instance.set_enable_jcard_simulator(

base/cvd/cuttlefish/host/commands/assemble_cvd/proto/guest_config.proto

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,21 @@ message Audio {
7575
}
7676

7777
message PCMDevice {
78+
79+
message Controls {
80+
message Volume {
81+
uint32 min = 1;
82+
uint32 max = 2;
83+
uint32 step = 3;
84+
}
85+
bool mute_control_enabled = 1;
86+
Volume volume_control = 2;
87+
}
88+
7889
message Stream {
7990
uint32 id = 1;
8091
ChannelLayout channel_layout = 2;
92+
Controls controls = 3;
8193
}
8294
repeated Stream capture_streams = 1;
8395
repeated Stream playback_streams = 2;
@@ -138,7 +150,7 @@ enum DeviceType {
138150
message Lights {}
139151

140152
// GuestConfigFile represents the configuration for a Cuttlefish guest device,
141-
// typically parsed from an cuttlefish-guest-config.txtpb file. It defines the
153+
// typically parsed from a cuttlefish-guest-config.txtpb file. It defines the
142154
// device's hardware capabilities, software features, and virtualization
143155
// settings.
144156
message GuestConfigFile {

base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp

Lines changed: 215 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
#include "cuttlefish/host/frontend/webrtc/audio_handler.h"
1818

1919
#include <algorithm>
20-
#include <array>
21-
#include <chrono>
20+
#include <cstdint>
2221
#include <cstring>
22+
#include <format>
2323

24-
#include <rtc_base/time_utils.h>
2524
#include "absl/log/check.h"
2625
#include "absl/log/log.h"
2726

27+
#include <rtc_base/time_utils.h>
28+
2829
#include "cuttlefish/host/frontend/webrtc/audio_mixer.h"
2930

3031
namespace cuttlefish {
@@ -50,8 +51,7 @@ virtio_snd_chmap_info GetVirtioSndChmapInfo(
5051
const AudioStreamSettings& settings) {
5152
const static std::unordered_map<AudioChannelsLayout, std::vector<uint8_t>>
5253
kChannelPositions = {
53-
{AudioChannelsLayout::Mono,
54-
{AudioChannelMap::VIRTIO_SND_CHMAP_MONO}},
54+
{AudioChannelsLayout::Mono, {AudioChannelMap::VIRTIO_SND_CHMAP_MONO}},
5555
{AudioChannelsLayout::Stereo,
5656
{AudioChannelMap::VIRTIO_SND_CHMAP_FL,
5757
AudioChannelMap::VIRTIO_SND_CHMAP_FR}},
@@ -77,6 +77,59 @@ virtio_snd_chmap_info GetVirtioSndChmapInfo(
7777
return info;
7878
}
7979

80+
inline constexpr const char* GetDirectionString(
81+
AudioStreamSettings::Direction direction) {
82+
switch (direction) {
83+
case AudioStreamSettings::Direction::Capture:
84+
return "Capture";
85+
case AudioStreamSettings::Direction::Playback:
86+
return "Playback";
87+
}
88+
}
89+
90+
virtio_snd_ctl_info GetVirtioCtlInfoVolume(
91+
const AudioStreamSettings::VolumeControl& settings,
92+
AudioStreamSettings::Direction stream_direction, uint32_t card_id,
93+
uint32_t device_id, uint32_t ctl_id) {
94+
virtio_snd_ctl_info info = {
95+
.hdr = {.hda_fn_nid = Le32(ctl_id)},
96+
.role = Le32(static_cast<uint8_t>(AudioControlRole::VIRTIO_SND_CTL_ROLE_VOLUME)),
97+
.type = Le32(static_cast<uint8_t>(AudioControlType::VIRTIO_SND_CTL_TYPE_INTEGER)),
98+
.access = Le32((1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_READ) |
99+
(1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_WRITE)),
100+
.count = Le32(1),
101+
.index = Le32(0),
102+
.name = {},
103+
.value = {.integer = {
104+
.min = Le32(settings.min),
105+
.max = Le32(settings.max),
106+
.step = Le32(settings.step),
107+
}}};
108+
std::format_to_n(info.name, sizeof(info.name) - 1,
109+
"Master {} Volume (C{}D{})",
110+
GetDirectionString(stream_direction), card_id, device_id);
111+
return info;
112+
}
113+
114+
virtio_snd_ctl_info GetVirtioCtlInfoMute(
115+
AudioStreamSettings::Direction stream_direction, uint32_t card_id,
116+
uint32_t device_id, uint32_t ctl_id) {
117+
virtio_snd_ctl_info info = {
118+
.hdr = {.hda_fn_nid = Le32(ctl_id)},
119+
.role = Le32(static_cast<uint8_t>(AudioControlRole::VIRTIO_SND_CTL_ROLE_MUTE)),
120+
.type = Le32(static_cast<uint8_t>(AudioControlType::VIRTIO_SND_CTL_TYPE_BOOLEAN)),
121+
.access = Le32((1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_READ) |
122+
(1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_WRITE)),
123+
.count = Le32(1),
124+
.index = Le32(0),
125+
.name = {},
126+
.value = {} // Ignored when VIRTIO_SND_CTL_TYPE_BOOLEAN
127+
};
128+
std::format_to_n(info.name, sizeof(info.name) - 1, "Master {} Mute (C{}D{})",
129+
GetDirectionString(stream_direction), card_id, device_id);
130+
return info;
131+
}
132+
80133
virtio_snd_pcm_info GetVirtioSndPcmInfo(const AudioStreamSettings& settings) {
81134
return {
82135
.hdr =
@@ -278,6 +331,31 @@ AudioHandler::AudioHandler(
278331
: 0);
279332
streams_[stream_id] = GetVirtioSndPcmInfo(settings);
280333
chmaps_[stream_id] = GetVirtioSndChmapInfo(settings);
334+
335+
constexpr uint32_t kCardId = 0; // As of now only one card is supported
336+
if (settings.has_mute_control) {
337+
controls_.push_back(GetVirtioCtlInfoMute(settings.direction, kCardId,
338+
settings.id, controls_.size()));
339+
controls_to_streams_map_.push_back(
340+
ControlDesc{.type = ControlDesc::Type::Mute, .stream_id = stream_id});
341+
}
342+
if (settings.master_volume_control.has_value()) {
343+
const auto& control = settings.master_volume_control.value();
344+
CHECK(control.max > control.min)
345+
<< "Volume control 'min' value must be lower than 'max'.";
346+
CHECK((control.max - control.min) % control.step == 0)
347+
<< "Volume control 'step' value must divide the total volume range "
348+
"evenly.";
349+
controls_.push_back(GetVirtioCtlInfoVolume(
350+
control, settings.direction, kCardId, settings.id, controls_.size()));
351+
controls_to_streams_map_.push_back(ControlDesc{
352+
.type = ControlDesc::Type::Volume, .stream_id = stream_id});
353+
stream_descs_[stream_id].volume = {
354+
.min = control.min,
355+
.max = control.max,
356+
.current = control.max,
357+
};
358+
}
281359
}
282360
}
283361

@@ -291,8 +369,8 @@ void AudioHandler::Start() {
291369
[[noreturn]] void AudioHandler::Loop() {
292370
for (;;) {
293371
auto audio_client = audio_server_->AcceptClient(
294-
streams_.size(), NUM_JACKS, chmaps_.size(), 262144 /* tx_shm_len */,
295-
262144 /* rx_shm_len */);
372+
streams_.size(), NUM_JACKS, chmaps_.size(), controls_.size(),
373+
262144 /* tx_shm_len */, 262144 /* rx_shm_len */);
296374
CHECK(audio_client) << "Failed to create audio client connection instance";
297375

298376
std::thread playback_thread([this, &audio_client]() {
@@ -404,26 +482,111 @@ void AudioHandler::JacksInfo(JackInfoCommand& cmd) {
404482
cmd.Reply(AudioStatus::VIRTIO_SND_S_OK, jack_info);
405483
}
406484

485+
void AudioHandler::ControlsInfo(ControlInfoCommand& cmd) {
486+
if (cmd.start_id() + cmd.count() > controls_.size()) {
487+
cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG, {});
488+
return;
489+
}
490+
491+
cmd.Reply(AudioStatus::VIRTIO_SND_S_OK,
492+
{controls_.cbegin() + cmd.start_id(), cmd.count()});
493+
}
494+
495+
AudioStatus AudioHandler::HandleControlMute(ControlCommand& cmd) {
496+
const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id;
497+
auto& stream = stream_descs_[stream_id];
498+
std::lock_guard<std::mutex> lock(stream.mtx);
499+
500+
if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
501+
auto& val = cmd.value()->value.integer;
502+
val[0] = Le32(stream.muted ? 1 : 0);
503+
return AudioStatus::VIRTIO_SND_S_OK;
504+
}
505+
506+
if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) {
507+
const auto val = cmd.value()->value.integer[0].as_uint32_t();
508+
// For VIRTIO_SND_CTL_TYPE_BOOLEAN, the guest driver (virtio_snd)
509+
// inherently knows that the control acts as a toggle. It implicitly
510+
// enforces a minimum of 0 and a maximum of 1.
511+
if (val > 1) {
512+
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
513+
}
514+
515+
stream.muted = val == 1;
516+
return AudioStatus::VIRTIO_SND_S_OK;
517+
}
518+
519+
return AudioStatus::VIRTIO_SND_S_NOT_SUPP;
520+
}
521+
522+
AudioStatus AudioHandler::HandleControlVolume(ControlCommand& cmd) {
523+
const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id;
524+
auto& stream = stream_descs_[stream_id];
525+
std::lock_guard<std::mutex> lock(stream.mtx);
526+
527+
if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
528+
auto& val = cmd.value()->value.integer;
529+
val[0] = Le32(stream.volume.current);
530+
return AudioStatus::VIRTIO_SND_S_OK;
531+
}
532+
533+
if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) {
534+
const auto val = cmd.value()->value.integer[0].as_uint32_t();
535+
if (val < stream.volume.min || val > stream.volume.max) {
536+
VLOG(0) << "Wrongs volume value for control " << cmd.control_id()
537+
<< " provided: " << val;
538+
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
539+
}
540+
VLOG(0) << "Setting volume for stream " << stream_id << " to " << val;
541+
stream.volume.current = val;
542+
return AudioStatus::VIRTIO_SND_S_OK;
543+
}
544+
545+
return AudioStatus::VIRTIO_SND_S_NOT_SUPP;
546+
}
547+
548+
void AudioHandler::OnControlCommand(ControlCommand& cmd) {
549+
const auto id = cmd.control_id();
550+
if (id >= controls_.size()) {
551+
cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG);
552+
return;
553+
}
554+
555+
auto result = AudioStatus::VIRTIO_SND_S_NOT_SUPP;
556+
switch (controls_to_streams_map_[id].type) {
557+
case ControlDesc::Type::Mute:
558+
result = HandleControlMute(cmd);
559+
break;
560+
case ControlDesc::Type::Volume:
561+
result = HandleControlVolume(cmd);
562+
break;
563+
}
564+
cmd.Reply(result);
565+
}
566+
407567
void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
408568
const auto stream_id = buffer.stream_id();
409569
// Invalid or capture streams shouldn't send tx buffers
410570
if (stream_id >= streams_.size() || IsCapture(stream_id)) {
411-
LOG(ERROR) << "Invalid or capture streams have sent tx buffers";
571+
VLOG(0) << "Invalid or capture streams have sent tx buffers";
412572
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_BAD_MSG, 0, 0);
413573
return;
414574
}
415575

416576
uint32_t sample_rate = 0;
417577
uint8_t channels = 0;
418578
uint8_t bits_per_channel = 0;
579+
float volume = 0;
419580
{
420581
auto& stream_desc = stream_descs_[stream_id];
421582
std::lock_guard<std::mutex> lock(stream_desc.mtx);
422583

584+
volume = stream_desc.volume.GetCurrentVolumeLevel();
585+
423586
// A buffer may be received for an inactive stream if we were slow to
424587
// process it and the other side stopped the stream. Quietly ignore it in
425588
// that case
426-
if (!stream_desc.active) {
589+
if (!stream_desc.active || stream_desc.muted || volume == 0) {
427590
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
428591
return;
429592
}
@@ -433,11 +596,17 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
433596
bits_per_channel = stream_desc.bits_per_sample;
434597
}
435598
audio_mixer_->OnPlayback(stream_id, sample_rate, channels, bits_per_channel,
436-
buffer.get(), buffer.len());
599+
volume, buffer.get(), buffer.len());
437600
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
438601
}
439602

440603
void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
604+
const auto rx_buffer = buffer.get();
605+
size_t bytes_read = 0;
606+
float volume = 0;
607+
uint32_t bytes_per_sample = 0;
608+
bool is_muted_by_control = false;
609+
441610
auto stream_id = buffer.stream_id();
442611
auto& stream_desc = stream_descs_[stream_id];
443612
{
@@ -448,31 +617,32 @@ void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
448617
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_BAD_MSG, 0, 0);
449618
return;
450619
}
620+
451621
// A buffer may be received for an inactive stream if we were slow to
452622
// process it and the other side stopped the stream. Quietly ignore it in
453623
// that case
454624
if (!stream_desc.active) {
455625
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
456626
return;
457627
}
458-
const auto bytes_per_sample = stream_desc.bits_per_sample / 8;
628+
volume = stream_desc.volume.GetCurrentVolumeLevel();
629+
is_muted_by_control = stream_desc.muted;
630+
bytes_per_sample = stream_desc.bits_per_sample / 8;
459631
const auto samples_per_channel = stream_desc.sample_rate / 100;
460632
const auto bytes_per_request =
461633
samples_per_channel * bytes_per_sample * stream_desc.channels;
634+
635+
// Fill remaining data from previous iteration
462636
auto& holding_buffer = stream_descs_[stream_id].holding_buffer;
463-
size_t bytes_read = 0;
464-
const auto rx_buffer = buffer.get();
465-
if (!holding_buffer.empty()) {
466-
// Fill remaining data from previous iteration
467-
bytes_read = holding_buffer.size();
468-
std::copy(holding_buffer.cbegin(), holding_buffer.cend(), rx_buffer);
469-
holding_buffer.clear();
470-
}
637+
bytes_read = holding_buffer.size();
638+
std::copy(holding_buffer.cbegin(), holding_buffer.cend(), rx_buffer);
639+
holding_buffer.clear();
640+
471641
bool muted = false;
472642
while (buffer.len() - bytes_read >= bytes_per_request) {
473643
// Skip the holding buffer in as many reads as possible to avoid the extra
474644
// copies
475-
auto write_pos = rx_buffer + bytes_read;
645+
const auto write_pos = rx_buffer + bytes_read;
476646
auto res = audio_source_->GetMoreAudioData(
477647
write_pos, bytes_per_sample, samples_per_channel,
478648
stream_desc.channels, stream_desc.sample_rate, muted);
@@ -521,6 +691,31 @@ void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
521691
}
522692
}
523693
}
694+
695+
if (is_muted_by_control) {
696+
memset(rx_buffer, 0, bytes_read);
697+
} else if (volume < 1.) {
698+
static const auto apply_volume = [](auto* data, size_t size_bytes,
699+
float volume) {
700+
for (auto& val : std::span{data, size_bytes / sizeof(*data)}) {
701+
val *= volume;
702+
}
703+
};
704+
switch (bytes_per_sample) {
705+
case 1:
706+
apply_volume(rx_buffer, bytes_read, volume);
707+
break;
708+
case 2:
709+
apply_volume(reinterpret_cast<uint16_t*>(rx_buffer), bytes_read,
710+
volume);
711+
break;
712+
case 4:
713+
apply_volume(reinterpret_cast<uint32_t*>(rx_buffer), bytes_read,
714+
volume);
715+
break;
716+
}
717+
}
718+
524719
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
525720
}
526721

0 commit comments

Comments
 (0)