Skip to content
Open
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
5 changes: 5 additions & 0 deletions base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,11 @@ Result<CuttlefishConfig> InitializeCuttlefishConfiguration(

instance.set_audio_output_streams_count(
guest_configs[instance_index].output_audio_streams_count);
const std::optional<::cuttlefish::config::Audio>& 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 @@ -75,9 +75,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 @@ -138,7 +150,7 @@ enum DeviceType {
message Lights {}

// 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
235 changes: 215 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,14 +17,15 @@
#include "cuttlefish/host/frontend/webrtc/audio_handler.h"

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

#include <rtc_base/time_utils.h>
#include "absl/log/check.h"
#include "absl/log/log.h"

#include <rtc_base/time_utils.h>

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

namespace cuttlefish {
Expand All @@ -50,8 +51,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 +77,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(static_cast<uint8_t>(AudioControlRole::VIRTIO_SND_CTL_ROLE_VOLUME)),
.type = Le32(static_cast<uint8_t>(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(static_cast<uint8_t>(AudioControlRole::VIRTIO_SND_CTL_ROLE_MUTE)),
.type = Le32(static_cast<uint8_t>(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 +331,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 +369,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,26 +482,111 @@ void AudioHandler::JacksInfo(JackInfoCommand& cmd) {
cmd.Reply(AudioStatus::VIRTIO_SND_S_OK, jack_info);
}

void AudioHandler::ControlsInfo(ControlInfoCommand& cmd) {
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()});
}

AudioStatus AudioHandler::HandleControlMute(ControlCommand& cmd) {
const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id;
auto& stream = stream_descs_[stream_id];
std::lock_guard<std::mutex> lock(stream.mtx);

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
auto& val = cmd.value()->value.integer;
val[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[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) {
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
}

stream.muted = val == 1;
return AudioStatus::VIRTIO_SND_S_OK;
}

return AudioStatus::VIRTIO_SND_S_NOT_SUPP;
}

AudioStatus AudioHandler::HandleControlVolume(ControlCommand& cmd) {
const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id;
auto& stream = stream_descs_[stream_id];
std::lock_guard<std::mutex> lock(stream.mtx);

if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) {
auto& val = cmd.value()->value.integer;
val[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[0].as_uint32_t();
if (val < stream.volume.min || val > stream.volume.max) {
VLOG(0) << "Wrongs volume value for control " << cmd.control_id()
<< " provided: " << val;
return AudioStatus::VIRTIO_SND_S_BAD_MSG;
}
VLOG(0) << "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;
}

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

auto result = AudioStatus::VIRTIO_SND_S_NOT_SUPP;
switch (controls_to_streams_map_[id].type) {
case ControlDesc::Type::Mute:
result = HandleControlMute(cmd);
break;
case ControlDesc::Type::Volume:
result = HandleControlVolume(cmd);
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
if (stream_id >= streams_.size() || IsCapture(stream_id)) {
LOG(ERROR) << "Invalid or capture streams have sent tx buffers";
VLOG(0) << "Invalid or capture streams have sent tx buffers";
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_BAD_MSG, 0, 0);
return;
}

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 +596,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 +617,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 +691,31 @@ 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_bytes,
float volume) {
for (auto& val : std::span{data, size_bytes / sizeof(*data)}) {
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,
volume);
break;
case 4:
apply_volume(reinterpret_cast<uint32_t*>(rx_buffer), bytes_read,
volume);
break;
}
}

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

Expand Down
Loading
Loading