Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,10 @@ Result<CuttlefishConfig> InitializeCuttlefishConfiguration(

instance.set_audio_output_streams_count(
guest_configs[instance_index].output_audio_streams_count);
const auto& audio_settings = guest_configs[instance_index].audio_settings;
if (audio_settings.has_value()) {
instance.set_audio_settings(audio_settings.value());
}

// jcardsim
instance.set_enable_jcard_simulator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,21 @@ message Audio {
}

message PCMDevice {

message Controls {
message Volume {
uint32 min = 1;
uint32 max = 2;
uint32 step = 3;
}
bool mute_control_enabled = 1;
Volume volume_control = 2;
}

message Stream {
uint32 id = 1;
ChannelLayout channel_layout = 2;
Controls controls = 3;
}
repeated Stream capture_streams = 1;
repeated Stream playback_streams = 2;
Expand Down Expand Up @@ -120,7 +132,7 @@ enum DeviceType {
}

// GuestConfigFile represents the configuration for a Cuttlefish guest device,
// typically parsed from an cuttlefish-guest-config.txtpb file. It defines the
// typically parsed from a cuttlefish-guest-config.txtpb file. It defines the
// device's hardware capabilities, software features, and virtualization
// settings.
message GuestConfigFile {
Expand Down
238 changes: 218 additions & 20 deletions base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@
#include "cuttlefish/host/frontend/webrtc/audio_handler.h"

#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <format>

#include <android-base/logging.h>
#include <rtc_base/time_utils.h>
#include "absl/log/check.h"
#include "absl/log/log.h"

#include "cuttlefish/host/frontend/webrtc/audio_mixer.h"

Expand All @@ -50,8 +49,7 @@ virtio_snd_chmap_info GetVirtioSndChmapInfo(
const AudioStreamSettings& settings) {
const static std::unordered_map<AudioChannelsLayout, std::vector<uint8_t>>
kChannelPositions = {
{AudioChannelsLayout::Mono,
{AudioChannelMap::VIRTIO_SND_CHMAP_MONO}},
{AudioChannelsLayout::Mono, {AudioChannelMap::VIRTIO_SND_CHMAP_MONO}},
{AudioChannelsLayout::Stereo,
{AudioChannelMap::VIRTIO_SND_CHMAP_FL,
AudioChannelMap::VIRTIO_SND_CHMAP_FR}},
Expand All @@ -77,6 +75,59 @@ virtio_snd_chmap_info GetVirtioSndChmapInfo(
return info;
}

inline constexpr const char* GetDirectionString(
AudioStreamSettings::Direction direction) {
switch (direction) {
case AudioStreamSettings::Direction::Capture:
return "Capture";
case AudioStreamSettings::Direction::Playback:
return "Playback";
}
}

virtio_snd_ctl_info GetVirtioCtlInfoVolume(
const AudioStreamSettings::VolumeControl& settings,
AudioStreamSettings::Direction stream_direction, uint32_t card_id,
uint32_t device_id, uint32_t ctl_id) {
virtio_snd_ctl_info info = {
.hdr = {.hda_fn_nid = Le32(ctl_id)},
.role = Le32(AudioControlRole::VIRTIO_SND_CTL_ROLE_VOLUME),
.type = Le32(AudioControlType::VIRTIO_SND_CTL_TYPE_INTEGER),
.access = Le32((1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_READ) |
(1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_WRITE)),
.count = Le32(1),
.index = Le32(0),
.name = {},
.value = {.integer = {
.min = Le32(settings.min),
.max = Le32(settings.max),
.step = Le32(settings.step),
}}};
std::format_to_n(info.name, sizeof(info.name) - 1,
"Master {} Volume (C{}D{})",
GetDirectionString(stream_direction), card_id, device_id);
return info;
}

virtio_snd_ctl_info GetVirtioCtlInfoMute(
AudioStreamSettings::Direction stream_direction, uint32_t card_id,
uint32_t device_id, uint32_t ctl_id) {
virtio_snd_ctl_info info = {
.hdr = {.hda_fn_nid = Le32(ctl_id)},
.role = Le32(AudioControlRole::VIRTIO_SND_CTL_ROLE_MUTE),
.type = Le32(AudioControlType::VIRTIO_SND_CTL_TYPE_BOOLEAN),
.access = Le32((1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_READ) |
(1 << AudioControlAccess::VIRTIO_SND_CTL_ACCESS_WRITE)),
.count = Le32(1),
.index = Le32(0),
.name = {},
.value = {} // Ignored when VIRTIO_SND_CTL_TYPE_BOOLEAN
};
std::format_to_n(info.name, sizeof(info.name) - 1, "Master {} Mute (C{}D{})",
GetDirectionString(stream_direction), card_id, device_id);
return info;
}

virtio_snd_pcm_info GetVirtioSndPcmInfo(const AudioStreamSettings& settings) {
return {
.hdr =
Expand Down Expand Up @@ -278,6 +329,31 @@ AudioHandler::AudioHandler(
: 0);
streams_[stream_id] = GetVirtioSndPcmInfo(settings);
chmaps_[stream_id] = GetVirtioSndChmapInfo(settings);

constexpr uint32_t kCardId = 0; // As of now only one card is supported
if (settings.has_mute_control) {
controls_.push_back(GetVirtioCtlInfoMute(settings.direction, kCardId,
settings.id, controls_.size()));
controls_to_streams_map_.push_back(
ControlDesc{.type = ControlDesc::Type::Mute, .stream_id = stream_id});
}
if (settings.master_volume_control.has_value()) {
const auto& control = settings.master_volume_control.value();
CHECK(control.max > control.min)
<< "Volume control 'min' value must be lower than 'max'.";
CHECK((control.max - control.min) % control.step == 0)
<< "Volume control 'step' value must divide the total volume range "
"evenly.";
controls_.push_back(GetVirtioCtlInfoVolume(
control, settings.direction, kCardId, settings.id, controls_.size()));
controls_to_streams_map_.push_back(ControlDesc{
.type = ControlDesc::Type::Volume, .stream_id = stream_id});
stream_descs_[stream_id].volume = {
.min = control.min,
.max = control.max,
.current = control.max,
};
}
}
}

Expand All @@ -291,8 +367,8 @@ void AudioHandler::Start() {
[[noreturn]] void AudioHandler::Loop() {
for (;;) {
auto audio_client = audio_server_->AcceptClient(
streams_.size(), NUM_JACKS, chmaps_.size(), 262144 /* tx_shm_len */,
262144 /* rx_shm_len */);
streams_.size(), NUM_JACKS, chmaps_.size(), controls_.size(),
262144 /* tx_shm_len */, 262144 /* rx_shm_len */);
CHECK(audio_client) << "Failed to create audio client connection instance";

std::thread playback_thread([this, &audio_client]() {
Expand Down Expand Up @@ -404,6 +480,94 @@ void AudioHandler::JacksInfo(JackInfoCommand& cmd) {
cmd.Reply(AudioStatus::VIRTIO_SND_S_OK, jack_info);
}

void AudioHandler::ControlsInfo(ControlInfoCommand& cmd) {
LOG(DEBUG) << "AudioHandler::ControlsInfo: start_id=" << cmd.start_id()
<< ", count=" << cmd.count();

if (cmd.start_id() + cmd.count() > controls_.size()) {
cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG, {});
return;
}

cmd.Reply(AudioStatus::VIRTIO_SND_S_OK,
{controls_.cbegin() + cmd.start_id(), cmd.count()});
}

void AudioHandler::OnControlCommand(ControlCommand& cmd) {
const auto id = cmd.control_id();
if (id >= controls_.size()) {
cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG);
return;
}

LOG(DEBUG) << "AudioHandler::OnControlCommand: control_id=" << id;

const auto stream_id = controls_to_streams_map_[id].stream_id;
auto& stream = stream_descs_[stream_id];

const auto control_mute = [stream_id, &stream, &cmd]() -> AudioStatus {
std::lock_guard<std::mutex> lock(stream.mtx);

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
auto& val = cmd.value()->value.integer;
val.value[0] = Le32(stream.muted ? 1 : 0);
return AudioStatus::VIRTIO_SND_S_OK;
}

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) {
const auto val = cmd.value()->value.integer.value[0].as_uint32_t();
// For VIRTIO_SND_CTL_TYPE_BOOLEAN, the guest driver (virtio_snd)
// inherently knows that the control acts as a toggle. It implicitly
// enforces a minimum of 0 and a maximum of 1.
if (val > 1) {
LOG(ERROR) << "Wrongs boolean value for control " << cmd.control_id()
<< " provided: " << val;
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
}
LOG(DEBUG) << "Setting mute for stream " << stream_id << " to " << val;
stream.muted = val == 1;
return AudioStatus::VIRTIO_SND_S_OK;
}

return AudioStatus::VIRTIO_SND_S_NOT_SUPP;
};

const auto control_volume = [stream_id, &stream, &cmd]() -> AudioStatus {
std::lock_guard<std::mutex> lock(stream.mtx);

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
auto& val = cmd.value()->value.integer;
val.value[0] = Le32(stream.volume.current);
return AudioStatus::VIRTIO_SND_S_OK;
}

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) {
const auto val = cmd.value()->value.integer.value[0].as_uint32_t();
if (val < stream.volume.min || val > stream.volume.max) {
LOG(ERROR) << "Wrongs volume value for control " << cmd.control_id()
<< " provided: " << val;
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
}
LOG(DEBUG) << "Setting volume for stream " << stream_id << " to " << val;
stream.volume.current = val;
return AudioStatus::VIRTIO_SND_S_OK;
}

return AudioStatus::VIRTIO_SND_S_NOT_SUPP;
};

auto result = AudioStatus::VIRTIO_SND_S_NOT_SUPP;
switch (controls_to_streams_map_[id].type) {
case ControlDesc::Type::Mute:
result = control_mute();
break;
case ControlDesc::Type::Volume:
result = control_volume();
break;
}
cmd.Reply(result);
}

void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
const auto stream_id = buffer.stream_id();
// Invalid or capture streams shouldn't send tx buffers
Expand All @@ -416,14 +580,17 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
uint32_t sample_rate = 0;
uint8_t channels = 0;
uint8_t bits_per_channel = 0;
float volume = 0;
{
auto& stream_desc = stream_descs_[stream_id];
std::lock_guard<std::mutex> lock(stream_desc.mtx);

volume = stream_desc.volume.GetCurrentVolumeLevel();

// A buffer may be received for an inactive stream if we were slow to
// process it and the other side stopped the stream. Quietly ignore it in
// that case
if (!stream_desc.active) {
if (!stream_desc.active || stream_desc.muted || volume == 0) {
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
return;
}
Expand All @@ -433,11 +600,17 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
bits_per_channel = stream_desc.bits_per_sample;
}
audio_mixer_->OnPlayback(stream_id, sample_rate, channels, bits_per_channel,
buffer.get(), buffer.len());
volume, buffer.get(), buffer.len());
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
}

void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
const auto rx_buffer = buffer.get();
size_t bytes_read = 0;
float volume = 0;
uint32_t bytes_per_sample = 0;
bool is_muted_by_control = false;

auto stream_id = buffer.stream_id();
auto& stream_desc = stream_descs_[stream_id];
{
Expand All @@ -448,31 +621,32 @@ void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_BAD_MSG, 0, 0);
return;
}

// A buffer may be received for an inactive stream if we were slow to
// process it and the other side stopped the stream. Quietly ignore it in
// that case
if (!stream_desc.active) {
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
return;
}
const auto bytes_per_sample = stream_desc.bits_per_sample / 8;
volume = stream_desc.volume.GetCurrentVolumeLevel();
is_muted_by_control = stream_desc.muted;
bytes_per_sample = stream_desc.bits_per_sample / 8;
const auto samples_per_channel = stream_desc.sample_rate / 100;
const auto bytes_per_request =
samples_per_channel * bytes_per_sample * stream_desc.channels;

// Fill remaining data from previous iteration
auto& holding_buffer = stream_descs_[stream_id].holding_buffer;
size_t bytes_read = 0;
const auto rx_buffer = buffer.get();
if (!holding_buffer.empty()) {
// Fill remaining data from previous iteration
bytes_read = holding_buffer.size();
std::copy(holding_buffer.cbegin(), holding_buffer.cend(), rx_buffer);
holding_buffer.clear();
}
bytes_read = holding_buffer.size();
std::copy(holding_buffer.cbegin(), holding_buffer.cend(), rx_buffer);
holding_buffer.clear();

bool muted = false;
while (buffer.len() - bytes_read >= bytes_per_request) {
// Skip the holding buffer in as many reads as possible to avoid the extra
// copies
auto write_pos = rx_buffer + bytes_read;
const auto write_pos = rx_buffer + bytes_read;
auto res = audio_source_->GetMoreAudioData(
write_pos, bytes_per_sample, samples_per_channel,
stream_desc.channels, stream_desc.sample_rate, muted);
Expand Down Expand Up @@ -521,6 +695,30 @@ void AudioHandler::OnCaptureBuffer(RxBuffer buffer) {
}
}
}

if (is_muted_by_control) {
memset(rx_buffer, 0, bytes_read);
} else if (volume < 1.){
static const auto apply_volume = [](auto* data, size_t size, float volume) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a template function instead of a lambda, and you could avoid some repetition by accepting the size in bytes as parameter and dividing by the size of the template parameter. Something like this:

template<typename T>
void apply_volume(T* data, size_bytes, float volume) {
  for (T& val: std::span(data, size_bytes / sizeof(T))) {
    val *= volume;
  }
}

for (auto& val : std::span{data, size}) {
val *= volume;
}
};
switch (bytes_per_sample) {
case 1:
apply_volume(rx_buffer, bytes_read, volume);
break;
case 2:
apply_volume(reinterpret_cast<uint16_t*>(rx_buffer), bytes_read / 2,
volume);
break;
case 4:
apply_volume(reinterpret_cast<uint32_t*>(rx_buffer), bytes_read / 4,
volume);
break;
}
}

buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
}

Expand Down
Loading
Loading