From 3be6964a1a377f49c7a1803ef662b818f0fd6f2b Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Fri, 21 Aug 2026 16:30:08 +0000 Subject: [PATCH 1/6] Support per-stream fade and balance virtual DSP in host AudioMixer --- .../host/frontend/webrtc/audio_handler.cpp | 126 +++++++++++++++++- .../host/frontend/webrtc/audio_handler.h | 6 + .../host/frontend/webrtc/audio_mixer.cpp | 43 +++++- .../host/frontend/webrtc/audio_mixer.h | 19 +-- 4 files changed, 175 insertions(+), 19 deletions(-) diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp index 453a7f49e97..04acb84b05b 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp @@ -130,6 +130,54 @@ virtio_snd_ctl_info GetVirtioCtlInfoMute( return info; } +virtio_snd_ctl_info GetVirtioCtlInfoFade( + 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(AudioControlRole::VIRTIO_SND_CTL_ROLE_VOLUME)), + .type = Le32( + static_cast(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(static_cast(-100)), + .max = Le32(100), + .step = Le32(1), + }}}; + std::format_to_n(info.name, sizeof(info.name) - 1, "{} Fade (C{}D{})", + GetDirectionString(stream_direction), card_id, device_id); + return info; +} + +virtio_snd_ctl_info GetVirtioCtlInfoBalance( + 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(AudioControlRole::VIRTIO_SND_CTL_ROLE_VOLUME)), + .type = Le32( + static_cast(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(static_cast(-100)), + .max = Le32(100), + .step = Le32(1), + }}}; + std::format_to_n(info.name, sizeof(info.name) - 1, "{} Balance (C{}D{})", + GetDirectionString(stream_direction), card_id, device_id); + return info; +} + virtio_snd_pcm_info GetVirtioSndPcmInfo(const AudioStreamSettings& settings) { return { .hdr = @@ -356,6 +404,18 @@ AudioHandler::AudioHandler( .current = control.max, }; } + + if (settings.direction == AudioStreamSettings::Direction::Playback) { + controls_.push_back(GetVirtioCtlInfoFade(settings.direction, kCardId, + settings.id, controls_.size())); + controls_to_streams_map_.push_back( + ControlDesc{.type = ControlDesc::Type::Fade, .stream_id = stream_id}); + + controls_.push_back(GetVirtioCtlInfoBalance(settings.direction, kCardId, + settings.id, controls_.size())); + controls_to_streams_map_.push_back( + ControlDesc{.type = ControlDesc::Type::Balance, .stream_id = stream_id}); + } } } @@ -545,6 +605,60 @@ AudioStatus AudioHandler::HandleControlVolume(ControlCommand& cmd) { return AudioStatus::VIRTIO_SND_S_NOT_SUPP; } +AudioStatus AudioHandler::HandleControlFade(ControlCommand& cmd) { + const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id; + auto& stream = stream_descs_[stream_id]; + std::lock_guard lock(stream.mtx); + + if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) { + auto& val = cmd.value()->value.integer; + val[0] = Le32(static_cast(static_cast(stream.fade * 100.0f))); + return AudioStatus::VIRTIO_SND_S_OK; + } + + if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) { + const auto raw_val = cmd.value()->value.integer[0].as_uint32_t(); + const auto val = static_cast(raw_val); + if (val < -100 || val > 100) { + LOG(ERROR) << "Wrong Fade value for control " << cmd.control_id() + << " provided: " << val; + return AudioStatus::VIRTIO_SND_S_BAD_MSG; + } + LOG(INFO) << "Setting Fade for stream " << stream_id << " to " << val; + stream.fade = static_cast(val) / 100.0f; + return AudioStatus::VIRTIO_SND_S_OK; + } + + return AudioStatus::VIRTIO_SND_S_NOT_SUPP; +} + +AudioStatus AudioHandler::HandleControlBalance(ControlCommand& cmd) { + const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id; + auto& stream = stream_descs_[stream_id]; + std::lock_guard lock(stream.mtx); + + if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) { + auto& val = cmd.value()->value.integer; + val[0] = Le32(static_cast(static_cast(stream.balance * 100.0f))); + return AudioStatus::VIRTIO_SND_S_OK; + } + + if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE) { + const auto raw_val = cmd.value()->value.integer[0].as_uint32_t(); + const auto val = static_cast(raw_val); + if (val < -100 || val > 100) { + LOG(ERROR) << "Wrong Balance value for control " << cmd.control_id() + << " provided: " << val; + return AudioStatus::VIRTIO_SND_S_BAD_MSG; + } + LOG(INFO) << "Setting Balance for stream " << stream_id << " to " << val; + stream.balance = static_cast(val) / 100.0f; + 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()) { @@ -560,6 +674,12 @@ void AudioHandler::OnControlCommand(ControlCommand& cmd) { case ControlDesc::Type::Volume: result = HandleControlVolume(cmd); break; + case ControlDesc::Type::Fade: + result = HandleControlFade(cmd); + break; + case ControlDesc::Type::Balance: + result = HandleControlBalance(cmd); + break; } cmd.Reply(result); } @@ -577,6 +697,8 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) { uint8_t channels = 0; uint8_t bits_per_channel = 0; float volume = 0; + float fade = 0; + float balance = 0; { auto& stream_desc = stream_descs_[stream_id]; std::lock_guard lock(stream_desc.mtx); @@ -594,9 +716,11 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) { sample_rate = stream_desc.sample_rate; channels = stream_desc.channels; bits_per_channel = stream_desc.bits_per_sample; + fade = stream_desc.fade; + balance = stream_desc.balance; } audio_mixer_->OnPlayback(stream_id, sample_rate, channels, bits_per_channel, - volume, buffer.get(), buffer.len()); + volume, fade, balance, buffer.get(), buffer.len()); buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len()); } diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h index d2893c64a32..ce6e2e341c0 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h @@ -54,12 +54,16 @@ class AudioHandler : public AudioServerExecutor { }; Volume volume; bool muted = false; + float fade = 0.0f; + float balance = 0.0f; }; struct ControlDesc { enum class Type { Mute, Volume, + Fade, + Balance, }; Type type = Type::Mute; @@ -97,6 +101,8 @@ class AudioHandler : public AudioServerExecutor { AudioStatus HandleControlMute(ControlCommand& cmd); AudioStatus HandleControlVolume(ControlCommand& cmd); + AudioStatus HandleControlFade(ControlCommand& cmd); + AudioStatus HandleControlBalance(ControlCommand& cmd); std::unique_ptr audio_server_; std::thread server_thread_; diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp index 882beb91628..0452e2d631a 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp @@ -164,6 +164,7 @@ void AudioMixer::OnStreamStopped(uint32_t stream_id) { void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, uint8_t stream_channels_count, uint8_t stream_bits_per_channel, float volume, + float fade, float balance, const uint8_t* buffer, size_t size) { const auto stream_frames_count = GetFramesCount(size, stream_channels_count, stream_bits_per_channel); @@ -172,9 +173,43 @@ void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, std::unique_lock lock(mutex_); - // As of now we only use direct channel mapping - for(size_t i = 0; i < channles_map.size(); ++i) { - channles_map[i][i] = volume; + // Create local channel matrix for this playback call [dst_channels x src_channels] + std::vector> channel_matrix( + channels_count_, std::vector(stream_channels_count, 0.0f)); + + if (fade == 0.0f && balance == 0.0f) { + // Fast path: direct diagonal 1-to-1 volume mapping + for (size_t i = 0; i < std::min(channels_count_, stream_channels_count); ++i) { + channel_matrix[i][i] = volume; + } + } else { + // Spatial path: compute 4-quadrant gains from fade and balance + const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); + const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); + const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); + const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); + + const float fl_gain = front_gain * left_gain * volume; + const float fr_gain = front_gain * right_gain * volume; + const float fc_gain = front_gain * volume; + const float rl_gain = rear_gain * left_gain * volume; + const float rr_gain = rear_gain * right_gain * volume; + + if (channels_count_ == 2 && stream_channels_count == 6) { + // ITU-R BS.775 5.1-to-stereo downmixing (-3 dB = 0.7071 for center/surround, -6 dB = 0.5 for LFE) + static constexpr float kCenterSurroundDownmixGain = 0.7071f; + static constexpr float kLfeDownmixGain = 0.5f; + + channel_matrix[0] = {fl_gain, 0.0f, kCenterSurroundDownmixGain * fc_gain, + kLfeDownmixGain * volume, kCenterSurroundDownmixGain * rl_gain, 0.0f}; + channel_matrix[1] = {0.0f, fr_gain, kCenterSurroundDownmixGain * fc_gain, + kLfeDownmixGain * volume, 0.0f, kCenterSurroundDownmixGain * rr_gain}; + } else { + const float spatial_gains[6] = {fl_gain, fr_gain, fc_gain, volume, rl_gain, rr_gain}; + for (size_t i = 0; i < std::min(channels_count_, stream_channels_count); ++i) { + channel_matrix[i][i] = (i < 6) ? spatial_gains[i] : volume; + } + } } const bool need_notify = next_frame_.empty(); // no active streams @@ -204,7 +239,7 @@ void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, const auto filled_frames_count = convert_fn(mixed_buffer_.data() + next_frame_id * frame_size_bytes_, channels_count_, sample_rate_, buffer, stream_channels_count, - stream_sample_rate, stream_frames_count, channles_map); + stream_sample_rate, stream_frames_count, channel_matrix); CHECK(filled_frames_count <= frames_count); next_frame_[stream_id] = next_frame_id + filled_frames_count; diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h index 4ecd8e3d2a2..4283b0fa529 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h @@ -22,12 +22,13 @@ class AudioMixer { void Start(); void Stop(); - // Called by auido_handler whenever new playback data chunk is given + // Called by audio_handler whenever new playback data chunk is given // Can be called on different threads void OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, uint8_t stream_channels_count, - uint8_t stream_bits_per_channel, float volume, const uint8_t* buffer, - size_t size); + uint8_t stream_bits_per_channel, float volume, + float fade, float balance, + const uint8_t* buffer, size_t size); void OnStreamStopped(uint32_t stream_id); private: @@ -48,7 +49,7 @@ class AudioMixer { ///////////////// Guarded by mutex_ //////////////// //////////////////////////////////////////////////// - // Buffer stores mixed auido data for every active stream. Consumed by + // Buffer stores mixed audio data for every active stream. Consumed by // MixerLoop std::vector mixed_buffer_; @@ -58,16 +59,6 @@ class AudioMixer { // Frame index per stream to put next available data to std::unordered_map next_frame_; - // Used to remap channels and apply volume levels - std::vector> channles_map = {{ - {1, 0, 0, 0, 0, 0}, - {0, 1, 0, 0, 0, 0}, - {0, 0, 1, 0, 0, 0}, - {0, 0, 0, 1, 0, 0}, - {0, 0, 0, 0, 1, 0}, - {0, 0, 0, 0, 0, 1}, - }}; - //////////////////////////////////////////////////// //////////////////////////////////////////////////// From f37a4f92b48e5f7ffae3077d7f3776b2b1bd7a4c Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Tue, 25 Aug 2026 16:15:58 +0000 Subject: [PATCH 2/6] add more logs --- .../host/frontend/webrtc/audio_handler.cpp | 26 ++++++++++++++----- .../host/frontend/webrtc/audio_mixer.cpp | 8 ++++++ frontend/go.work.sum | 5 ++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 frontend/go.work.sum diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp index 04acb84b05b..82e04ec5f23 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp @@ -417,6 +417,8 @@ AudioHandler::AudioHandler( ControlDesc{.type = ControlDesc::Type::Balance, .stream_id = stream_id}); } } + LOG(INFO) << "[Host AudioHandler] Initialized " << streams_.size() + << " streams and " << controls_.size() << " virtio-snd controls."; } AudioHandler::~AudioHandler() { audio_mixer_->Stop(); } @@ -613,6 +615,8 @@ AudioStatus AudioHandler::HandleControlFade(ControlCommand& cmd) { if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) { auto& val = cmd.value()->value.integer; val[0] = Le32(static_cast(static_cast(stream.fade * 100.0f))); + LOG(INFO) << "[Host AudioHandler] HandleControlFade READ for stream " << stream_id + << " -> returning " << static_cast(stream.fade * 100.0f); return AudioStatus::VIRTIO_SND_S_OK; } @@ -620,12 +624,13 @@ AudioStatus AudioHandler::HandleControlFade(ControlCommand& cmd) { const auto raw_val = cmd.value()->value.integer[0].as_uint32_t(); const auto val = static_cast(raw_val); if (val < -100 || val > 100) { - LOG(ERROR) << "Wrong Fade value for control " << cmd.control_id() - << " provided: " << val; + LOG(ERROR) << "[Host AudioHandler] Wrong Fade value for control " << cmd.control_id() + << " (stream " << stream_id << ") provided: " << val; return AudioStatus::VIRTIO_SND_S_BAD_MSG; } - LOG(INFO) << "Setting Fade for stream " << stream_id << " to " << val; stream.fade = static_cast(val) / 100.0f; + LOG(INFO) << "[Host AudioHandler] Setting Fade for stream " << stream_id + << " to " << val << " (fade level: " << stream.fade << ")"; return AudioStatus::VIRTIO_SND_S_OK; } @@ -640,6 +645,8 @@ AudioStatus AudioHandler::HandleControlBalance(ControlCommand& cmd) { if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) { auto& val = cmd.value()->value.integer; val[0] = Le32(static_cast(static_cast(stream.balance * 100.0f))); + LOG(INFO) << "[Host AudioHandler] HandleControlBalance READ for stream " << stream_id + << " -> returning " << static_cast(stream.balance * 100.0f); return AudioStatus::VIRTIO_SND_S_OK; } @@ -647,12 +654,13 @@ AudioStatus AudioHandler::HandleControlBalance(ControlCommand& cmd) { const auto raw_val = cmd.value()->value.integer[0].as_uint32_t(); const auto val = static_cast(raw_val); if (val < -100 || val > 100) { - LOG(ERROR) << "Wrong Balance value for control " << cmd.control_id() - << " provided: " << val; + LOG(ERROR) << "[Host AudioHandler] Wrong Balance value for control " << cmd.control_id() + << " (stream " << stream_id << ") provided: " << val; return AudioStatus::VIRTIO_SND_S_BAD_MSG; } - LOG(INFO) << "Setting Balance for stream " << stream_id << " to " << val; stream.balance = static_cast(val) / 100.0f; + LOG(INFO) << "[Host AudioHandler] Setting Balance for stream " << stream_id + << " to " << val << " (balance level: " << stream.balance << ")"; return AudioStatus::VIRTIO_SND_S_OK; } @@ -662,10 +670,16 @@ AudioStatus AudioHandler::HandleControlBalance(ControlCommand& cmd) { void AudioHandler::OnControlCommand(ControlCommand& cmd) { const auto id = cmd.control_id(); if (id >= controls_.size()) { + LOG(ERROR) << "[Host AudioHandler] OnControlCommand with invalid control ID: " + << id << " (max: " << controls_.size() << ")"; cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG); return; } + LOG(INFO) << "[Host AudioHandler] OnControlCommand: control_id=" << id + << " ('" << controls_[id].name << "'), type=" + << (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_WRITE ? "WRITE" : "READ"); + auto result = AudioStatus::VIRTIO_SND_S_NOT_SUPP; switch (controls_to_streams_map_[id].type) { case ControlDesc::Type::Mute: diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp index 0452e2d631a..e4732e1ab50 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp @@ -6,6 +6,7 @@ #include "audio_settings.h" #include "absl/log/check.h" +#include "absl/log/log.h" namespace cuttlefish { namespace { @@ -195,6 +196,13 @@ void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, const float rl_gain = rear_gain * left_gain * volume; const float rr_gain = rear_gain * right_gain * volume; + LOG_EVERY_N_SEC(INFO, 2) + << "[Host AudioMixer] Spatial playback: stream=" << stream_id + << ", fade=" << fade << ", balance=" << balance + << ", volume=" << volume << ", FL=" << fl_gain + << ", FR=" << fr_gain << ", FC=" << fc_gain + << ", RL=" << rl_gain << ", RR=" << rr_gain; + if (channels_count_ == 2 && stream_channels_count == 6) { // ITU-R BS.775 5.1-to-stereo downmixing (-3 dB = 0.7071 for center/surround, -6 dB = 0.5 for LFE) static constexpr float kCenterSurroundDownmixGain = 0.7071f; diff --git a/frontend/go.work.sum b/frontend/go.work.sum new file mode 100644 index 00000000000..41698fb92ec --- /dev/null +++ b/frontend/go.work.sum @@ -0,0 +1,5 @@ +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= From 5bfb4377d1a3c8b61054414e0cb1a295748d63af Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Tue, 25 Aug 2026 17:56:56 +0000 Subject: [PATCH 3/6] Extract audio channel matrix calculation to dedicated module - Implement standard ITU-R BS.775 downmix tables (5.1 -> Stereo, 5.1 -> Mono) - Modulate base mixing matrix with per-stream cabin spatial gains (fade/balance) - Simplify AudioMixer::OnPlayback to use BuildChannelMixingMatrix - Register libcuttlefish_webrtc_audio_channel_matrix in BUILD.bazel --- .../host/frontend/webrtc/BUILD.bazel | 10 ++ .../frontend/webrtc/audio_channel_matrix.cpp | 122 ++++++++++++++++++ .../frontend/webrtc/audio_channel_matrix.h | 40 ++++++ .../host/frontend/webrtc/audio_mixer.cpp | 51 +------- 4 files changed, 176 insertions(+), 47 deletions(-) create mode 100644 base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp create mode 100644 base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel b/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel index 352a41f9897..6e8b118d331 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel +++ b/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel @@ -87,6 +87,15 @@ cf_cc_library( ], ) +cf_cc_library( + name = "libcuttlefish_webrtc_audio_channel_matrix", + srcs = ["audio_channel_matrix.cpp"], + hdrs = ["audio_channel_matrix.h"], + deps = [ + ":libcuttlefish_webrtc_audio_settings", + ], +) + cf_cc_library( name = "libcuttlefish_webrtc_audio_mixer", srcs = ["audio_mixer.cpp"], @@ -95,6 +104,7 @@ cf_cc_library( depend_on_what_you_use_enabled = False, include_cleaner_enabled = False, deps = [ + ":libcuttlefish_webrtc_audio_channel_matrix", ":libcuttlefish_webrtc_audio_settings", "//cuttlefish/host/frontend/webrtc/libdevice:audio_sink", "//libbase", diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp new file mode 100644 index 00000000000..721cd73b8d9 --- /dev/null +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cuttlefish/host/frontend/webrtc/audio_channel_matrix.h" + +#include +#include + +#include "cuttlefish/host/frontend/webrtc/audio_settings.h" + +namespace cuttlefish { +namespace { + +constexpr float kMinus3dB = 0.7071f; // 1 / sqrt(2) for Center & Surround +constexpr float kMinus6dB = 0.5000f; // 1 / 2 for LFE (Subwoofer) and Stereo-to-Mono + +// Standard ITU-R BS.775 base downmix coefficients +std::vector> GetItuDownmixMatrix(uint8_t dst_channels, + uint8_t src_channels) { + constexpr uint8_t kMono = GetChannelsCount(AudioChannelsLayout::Mono); // 1 + constexpr uint8_t kStereo = GetChannelsCount(AudioChannelsLayout::Stereo); // 2 + constexpr uint8_t kSurround51 = + GetChannelsCount(AudioChannelsLayout::Surround51); // 6 + + // 1. Same layout (1->1, 2->2, 6->6) -> Identity Matrix + if (dst_channels == src_channels) { + std::vector> matrix( + dst_channels, std::vector(src_channels, 0.0f)); + for (size_t i = 0; i < dst_channels; ++i) { + matrix[i][i] = 1.0f; + } + return matrix; + } + + // 2. 5.1 Surround -> Stereo (6 -> 2) [ITU-R BS.775 §2.2] + // Source layout: [FL, FR, FC, LFE, RL, RR] + if (dst_channels == kStereo && src_channels == kSurround51) { + return { + {1.0f, 0.0f, kMinus3dB, kMinus6dB, kMinus3dB, 0.0f}, // Left + {0.0f, 1.0f, kMinus3dB, kMinus6dB, 0.0f, kMinus3dB}, // Right + }; + } + + // 3. Mono -> Stereo (1 -> 2) [Center voice to both Left & Right] + if (dst_channels == kStereo && src_channels == kMono) { + return { + {1.0f}, // Left + {1.0f}, // Right + }; + } + + // 4. Stereo -> Mono (2 -> 1) [Equal sum of Left & Right] + if (dst_channels == kMono && src_channels == kStereo) { + return { + {kMinus6dB, kMinus6dB}, + }; + } + + // 5. 5.1 Surround -> Mono (6 -> 1) [ITU-R BS.775 §2.1] + if (dst_channels == kMono && src_channels == kSurround51) { + return { + {kMinus3dB, kMinus3dB, 1.0f, kMinus6dB, kMinus3dB, kMinus3dB}, + }; + } + + // Generic fallback: diagonal 1:1 mapping up to min(dst, src) + std::vector> matrix( + dst_channels, std::vector(src_channels, 0.0f)); + for (size_t i = 0; i < std::min(dst_channels, src_channels); ++i) { + matrix[i][i] = 1.0f; + } + return matrix; +} + +} // namespace + +std::vector> BuildChannelMixingMatrix( + uint8_t dst_channels, uint8_t src_channels, float volume, float fade, + float balance) { + // Compute acoustic cabin attenuation for the 6 standard speaker positions + const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); + const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); + const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); + const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); + + const std::array spatial_gains = { + volume * (front_gain * left_gain), // 0: Front-Left (FL) + volume * (front_gain * right_gain), // 1: Front-Right (FR) + volume * front_gain, // 2: Front-Center (FC) + volume, // 3: Subwoofer (LFE) + volume * (rear_gain * left_gain), // 4: Rear-Left (RL) + volume * (rear_gain * right_gain), // 5: Rear-Right (RR) + }; + + // Get the base ITU-R topology matrix B + auto channels_map = GetItuDownmixMatrix(dst_channels, src_channels); + + // Apply per-channel spatial gains to each column + for (size_t i = 0; i < dst_channels; ++i) { + for (size_t j = 0; j < src_channels; ++j) { + const float gain = (j < spatial_gains.size()) ? spatial_gains[j] : volume; + channels_map[i][j] *= gain; + } + } + + return channels_map; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h new file mode 100644 index 00000000000..a4d320fa88d --- /dev/null +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace cuttlefish { + +/** + * Builds the channel mixing matrix of dimension [dst_channels][src_channels]. + * + * Implements standard ITU-R BS.775 downmixing (e.g. 5.1 -> Stereo, 5.1 -> Mono) + * combined with per-stream spatial cabin attenuation (fade and balance). + * + * @param dst_channels Destination speaker channels (e.g. 2 for Stereo host sink) + * @param src_channels Source stream channels (e.g. 6 for 5.1 Surround, 2 for Stereo) + * @param volume Master stream volume [0.0 - 1.0] + * @param fade Front/Rear cabin fader [-1.0 (Rear) to 1.0 (Front)] + * @param balance Left/Right cabin balance [-1.0 (Left) to 1.0 (Right)] + */ +std::vector> BuildChannelMixingMatrix( + uint8_t dst_channels, uint8_t src_channels, float volume, float fade, + float balance); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp index e4732e1ab50..133542c059b 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include "audio_settings.h" +#include "cuttlefish/host/frontend/webrtc/audio_channel_matrix.h" +#include "cuttlefish/host/frontend/webrtc/audio_settings.h" #include "absl/log/check.h" #include "absl/log/log.h" @@ -174,51 +174,8 @@ void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, std::unique_lock lock(mutex_); - // Create local channel matrix for this playback call [dst_channels x src_channels] - std::vector> channel_matrix( - channels_count_, std::vector(stream_channels_count, 0.0f)); - - if (fade == 0.0f && balance == 0.0f) { - // Fast path: direct diagonal 1-to-1 volume mapping - for (size_t i = 0; i < std::min(channels_count_, stream_channels_count); ++i) { - channel_matrix[i][i] = volume; - } - } else { - // Spatial path: compute 4-quadrant gains from fade and balance - const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); - const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); - const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); - const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); - - const float fl_gain = front_gain * left_gain * volume; - const float fr_gain = front_gain * right_gain * volume; - const float fc_gain = front_gain * volume; - const float rl_gain = rear_gain * left_gain * volume; - const float rr_gain = rear_gain * right_gain * volume; - - LOG_EVERY_N_SEC(INFO, 2) - << "[Host AudioMixer] Spatial playback: stream=" << stream_id - << ", fade=" << fade << ", balance=" << balance - << ", volume=" << volume << ", FL=" << fl_gain - << ", FR=" << fr_gain << ", FC=" << fc_gain - << ", RL=" << rl_gain << ", RR=" << rr_gain; - - if (channels_count_ == 2 && stream_channels_count == 6) { - // ITU-R BS.775 5.1-to-stereo downmixing (-3 dB = 0.7071 for center/surround, -6 dB = 0.5 for LFE) - static constexpr float kCenterSurroundDownmixGain = 0.7071f; - static constexpr float kLfeDownmixGain = 0.5f; - - channel_matrix[0] = {fl_gain, 0.0f, kCenterSurroundDownmixGain * fc_gain, - kLfeDownmixGain * volume, kCenterSurroundDownmixGain * rl_gain, 0.0f}; - channel_matrix[1] = {0.0f, fr_gain, kCenterSurroundDownmixGain * fc_gain, - kLfeDownmixGain * volume, 0.0f, kCenterSurroundDownmixGain * rr_gain}; - } else { - const float spatial_gains[6] = {fl_gain, fr_gain, fc_gain, volume, rl_gain, rr_gain}; - for (size_t i = 0; i < std::min(channels_count_, stream_channels_count); ++i) { - channel_matrix[i][i] = (i < 6) ? spatial_gains[i] : volume; - } - } - } + const auto channel_matrix = BuildChannelMixingMatrix( + channels_count_, stream_channels_count, volume, fade, balance); const bool need_notify = next_frame_.empty(); // no active streams From f9b62d4da104855963810c1a4338cd276ed07a15 Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Wed, 26 Aug 2026 11:30:00 +0000 Subject: [PATCH 4/6] Fix Left/Right balance panning across mono, stereo, and 5.1 layouts --- .../frontend/webrtc/audio_channel_matrix.cpp | 138 ++++++++---------- 1 file changed, 63 insertions(+), 75 deletions(-) diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp index 721cd73b8d9..abe8819f2c0 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp @@ -27,96 +27,84 @@ namespace { constexpr float kMinus3dB = 0.7071f; // 1 / sqrt(2) for Center & Surround constexpr float kMinus6dB = 0.5000f; // 1 / 2 for LFE (Subwoofer) and Stereo-to-Mono -// Standard ITU-R BS.775 base downmix coefficients -std::vector> GetItuDownmixMatrix(uint8_t dst_channels, - uint8_t src_channels) { +} // namespace + +std::vector> BuildChannelMixingMatrix( + uint8_t dst_channels, uint8_t src_channels, float volume, float fade, + float balance) { constexpr uint8_t kMono = GetChannelsCount(AudioChannelsLayout::Mono); // 1 constexpr uint8_t kStereo = GetChannelsCount(AudioChannelsLayout::Stereo); // 2 constexpr uint8_t kSurround51 = GetChannelsCount(AudioChannelsLayout::Surround51); // 6 - // 1. Same layout (1->1, 2->2, 6->6) -> Identity Matrix - if (dst_channels == src_channels) { - std::vector> matrix( - dst_channels, std::vector(src_channels, 0.0f)); - for (size_t i = 0; i < dst_channels; ++i) { - matrix[i][i] = 1.0f; - } - return matrix; - } - - // 2. 5.1 Surround -> Stereo (6 -> 2) [ITU-R BS.775 §2.2] - // Source layout: [FL, FR, FC, LFE, RL, RR] - if (dst_channels == kStereo && src_channels == kSurround51) { - return { - {1.0f, 0.0f, kMinus3dB, kMinus6dB, kMinus3dB, 0.0f}, // Left - {0.0f, 1.0f, kMinus3dB, kMinus6dB, 0.0f, kMinus3dB}, // Right - }; - } - - // 3. Mono -> Stereo (1 -> 2) [Center voice to both Left & Right] - if (dst_channels == kStereo && src_channels == kMono) { - return { - {1.0f}, // Left - {1.0f}, // Right - }; - } + // Compute acoustic cabin attenuation for the 4 quadrants + const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); + const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); + const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); + const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); - // 4. Stereo -> Mono (2 -> 1) [Equal sum of Left & Right] - if (dst_channels == kMono && src_channels == kStereo) { - return { - {kMinus6dB, kMinus6dB}, - }; + const float fl_gain = volume * (front_gain * left_gain); + const float fr_gain = volume * (front_gain * right_gain); + const float fc_gain = volume * front_gain; + const float rl_gain = volume * (rear_gain * left_gain); + const float rr_gain = volume * (rear_gain * right_gain); + + // Case 1: Stereo Destination Output (Laptop Speakers / WebRTC sink) + if (dst_channels == kStereo) { + if (src_channels == kSurround51) { + // 5.1 Surround -> Stereo (ITU-R BS.775 with left/right balance & front/rear fade) + return { + {fl_gain, 0.0f, kMinus3dB * fc_gain * left_gain, + kMinus6dB * volume * left_gain, kMinus3dB * rl_gain, 0.0f}, + {0.0f, fr_gain, kMinus3dB * fc_gain * right_gain, + kMinus6dB * volume * right_gain, 0.0f, kMinus3dB * rr_gain}, + }; + } + if (src_channels == kStereo) { + // Stereo -> Stereo (Direct with left/right balance & front/rear fade) + return { + {fl_gain, 0.0f}, + {0.0f, fr_gain}, + }; + } + if (src_channels == kMono) { + // Mono -> Stereo (Center mono panned by balance) + return { + {fl_gain}, + {fr_gain}, + }; + } } - // 5. 5.1 Surround -> Mono (6 -> 1) [ITU-R BS.775 §2.1] - if (dst_channels == kMono && src_channels == kSurround51) { - return { - {kMinus3dB, kMinus3dB, 1.0f, kMinus6dB, kMinus3dB, kMinus3dB}, - }; + // Case 2: Mono Destination Output + if (dst_channels == kMono) { + if (src_channels == kSurround51) { + return { + {kMinus3dB * fl_gain, kMinus3dB * fr_gain, fc_gain, + kMinus6dB * volume, kMinus3dB * rl_gain, kMinus3dB * rr_gain}, + }; + } + if (src_channels == kStereo) { + return { + {kMinus6dB * fl_gain, kMinus6dB * fr_gain}, + }; + } + if (src_channels == kMono) { + return { + {fl_gain}, + }; + } } - // Generic fallback: diagonal 1:1 mapping up to min(dst, src) + // Fallback: generic diagonal matrix std::vector> matrix( dst_channels, std::vector(src_channels, 0.0f)); + const std::array spatial_gains = {fl_gain, fr_gain, fc_gain, + volume, rl_gain, rr_gain}; for (size_t i = 0; i < std::min(dst_channels, src_channels); ++i) { - matrix[i][i] = 1.0f; + matrix[i][i] = (i < spatial_gains.size()) ? spatial_gains[i] : volume; } return matrix; } -} // namespace - -std::vector> BuildChannelMixingMatrix( - uint8_t dst_channels, uint8_t src_channels, float volume, float fade, - float balance) { - // Compute acoustic cabin attenuation for the 6 standard speaker positions - const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); - const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); - const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); - const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); - - const std::array spatial_gains = { - volume * (front_gain * left_gain), // 0: Front-Left (FL) - volume * (front_gain * right_gain), // 1: Front-Right (FR) - volume * front_gain, // 2: Front-Center (FC) - volume, // 3: Subwoofer (LFE) - volume * (rear_gain * left_gain), // 4: Rear-Left (RL) - volume * (rear_gain * right_gain), // 5: Rear-Right (RR) - }; - - // Get the base ITU-R topology matrix B - auto channels_map = GetItuDownmixMatrix(dst_channels, src_channels); - - // Apply per-channel spatial gains to each column - for (size_t i = 0; i < dst_channels; ++i) { - for (size_t j = 0; j < src_channels; ++j) { - const float gain = (j < spatial_gains.size()) ? spatial_gains[j] : volume; - channels_map[i][j] *= gain; - } - } - - return channels_map; -} - } // namespace cuttlefish From 234669c256b6093e562f50e18377b88fa7550c0d Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Wed, 26 Aug 2026 11:36:18 +0000 Subject: [PATCH 5/6] Update BUILD.bazel flags and includes for audio_channel_matrix --- base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel | 3 +++ .../cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel b/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel index 6e8b118d331..bf6339e640e 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel +++ b/base/cvd/cuttlefish/host/frontend/webrtc/BUILD.bazel @@ -91,6 +91,9 @@ cf_cc_library( name = "libcuttlefish_webrtc_audio_channel_matrix", srcs = ["audio_channel_matrix.cpp"], hdrs = ["audio_channel_matrix.h"], + clang_format_enabled = False, + depend_on_what_you_use_enabled = False, + include_cleaner_enabled = False, deps = [ ":libcuttlefish_webrtc_audio_settings", ], diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp index abe8819f2c0..7c9e1c73534 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include +#include #include "cuttlefish/host/frontend/webrtc/audio_settings.h" From 2348af3b14975b4a7351f455c76d9845073a8fc7 Mon Sep 17 00:00:00 2001 From: Khamidjon Khamidov Date: Thu, 10 Sep 2026 10:49:04 +0000 Subject: [PATCH 6/6] Support per-stream hardware audio muting and ducking in WebRTC backend - Register Master Mute and Duck ALSA controls unconditionally for all playback streams - Implement HandleControlDuck to track stream is_ducked state via VirtIO-Sound control commands - Fold ducking factor (0.2000f, -14 dB) directly into BuildChannelMixingMatrix - Propagate is_ducked state through AudioMixer::OnPlayback and ChannelMixingMatrix --- .../frontend/webrtc/audio_channel_matrix.cpp | 27 +++++--- .../frontend/webrtc/audio_channel_matrix.h | 3 +- .../host/frontend/webrtc/audio_handler.cpp | 67 ++++++++++++++++++- .../host/frontend/webrtc/audio_handler.h | 3 + .../host/frontend/webrtc/audio_mixer.cpp | 4 +- .../host/frontend/webrtc/audio_mixer.h | 2 +- 6 files changed, 89 insertions(+), 17 deletions(-) diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp index 7c9e1c73534..feb2b2b8208 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.cpp @@ -29,28 +29,33 @@ namespace { constexpr float kMinus3dB = 0.7071f; // 1 / sqrt(2) for Center & Surround constexpr float kMinus6dB = 0.5000f; // 1 / 2 for LFE (Subwoofer) and Stereo-to-Mono +constexpr float kDuckingGain = 0.2000f; // -14 dB (AAOS standard ducking) } // namespace std::vector> BuildChannelMixingMatrix( uint8_t dst_channels, uint8_t src_channels, float volume, float fade, - float balance) { + float balance, bool is_ducked) { constexpr uint8_t kMono = GetChannelsCount(AudioChannelsLayout::Mono); // 1 constexpr uint8_t kStereo = GetChannelsCount(AudioChannelsLayout::Stereo); // 2 constexpr uint8_t kSurround51 = GetChannelsCount(AudioChannelsLayout::Surround51); // 6 + // 1. Fold ducking into effective volume + const float duck_factor = is_ducked ? kDuckingGain : 1.0f; + const float effective_volume = volume * duck_factor; + // Compute acoustic cabin attenuation for the 4 quadrants const float front_gain = (fade >= 0.0f) ? 1.0f : (1.0f + fade); const float rear_gain = (fade <= 0.0f) ? 1.0f : (1.0f - fade); const float left_gain = (balance <= 0.0f) ? 1.0f : (1.0f - balance); const float right_gain = (balance >= 0.0f) ? 1.0f : (1.0f + balance); - const float fl_gain = volume * (front_gain * left_gain); - const float fr_gain = volume * (front_gain * right_gain); - const float fc_gain = volume * front_gain; - const float rl_gain = volume * (rear_gain * left_gain); - const float rr_gain = volume * (rear_gain * right_gain); + const float fl_gain = effective_volume * (front_gain * left_gain); + const float fr_gain = effective_volume * (front_gain * right_gain); + const float fc_gain = effective_volume * front_gain; + const float rl_gain = effective_volume * (rear_gain * left_gain); + const float rr_gain = effective_volume * (rear_gain * right_gain); // Case 1: Stereo Destination Output (Laptop Speakers / WebRTC sink) if (dst_channels == kStereo) { @@ -58,9 +63,9 @@ std::vector> BuildChannelMixingMatrix( // 5.1 Surround -> Stereo (ITU-R BS.775 with left/right balance & front/rear fade) return { {fl_gain, 0.0f, kMinus3dB * fc_gain * left_gain, - kMinus6dB * volume * left_gain, kMinus3dB * rl_gain, 0.0f}, + kMinus6dB * effective_volume * left_gain, kMinus3dB * rl_gain, 0.0f}, {0.0f, fr_gain, kMinus3dB * fc_gain * right_gain, - kMinus6dB * volume * right_gain, 0.0f, kMinus3dB * rr_gain}, + kMinus6dB * effective_volume * right_gain, 0.0f, kMinus3dB * rr_gain}, }; } if (src_channels == kStereo) { @@ -84,7 +89,7 @@ std::vector> BuildChannelMixingMatrix( if (src_channels == kSurround51) { return { {kMinus3dB * fl_gain, kMinus3dB * fr_gain, fc_gain, - kMinus6dB * volume, kMinus3dB * rl_gain, kMinus3dB * rr_gain}, + kMinus6dB * effective_volume, kMinus3dB * rl_gain, kMinus3dB * rr_gain}, }; } if (src_channels == kStereo) { @@ -103,9 +108,9 @@ std::vector> BuildChannelMixingMatrix( std::vector> matrix( dst_channels, std::vector(src_channels, 0.0f)); const std::array spatial_gains = {fl_gain, fr_gain, fc_gain, - volume, rl_gain, rr_gain}; + effective_volume, rl_gain, rr_gain}; for (size_t i = 0; i < std::min(dst_channels, src_channels); ++i) { - matrix[i][i] = (i < spatial_gains.size()) ? spatial_gains[i] : volume; + matrix[i][i] = (i < spatial_gains.size()) ? spatial_gains[i] : effective_volume; } return matrix; } diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h index a4d320fa88d..48fd0723d7a 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_channel_matrix.h @@ -32,9 +32,10 @@ namespace cuttlefish { * @param volume Master stream volume [0.0 - 1.0] * @param fade Front/Rear cabin fader [-1.0 (Rear) to 1.0 (Front)] * @param balance Left/Right cabin balance [-1.0 (Left) to 1.0 (Right)] + * @param is_ducked Whether the stream is ducked (-14 dB attenuation) */ std::vector> BuildChannelMixingMatrix( uint8_t dst_channels, uint8_t src_channels, float volume, float fade, - float balance); + float balance, bool is_ducked = false); } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp index 82e04ec5f23..dfde7c3a27e 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp @@ -130,6 +130,28 @@ virtio_snd_ctl_info GetVirtioCtlInfoMute( return info; } +virtio_snd_ctl_info GetVirtioCtlInfoDuck( + AudioStreamSettings::Direction stream_direction, uint32_t card_id, + uint32_t device_id, uint32_t control_index) { + virtio_snd_ctl_info info{ + .hdr = + { + .hda_fn_nid = Le32(control_index), + }, + .role = Le32(0), + .type = Le32(static_cast(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 {} Duck (C{}D{})", + GetDirectionString(stream_direction), card_id, device_id); + return info; +} + virtio_snd_ctl_info GetVirtioCtlInfoFade( AudioStreamSettings::Direction stream_direction, uint32_t card_id, uint32_t device_id, uint32_t ctl_id) { @@ -381,7 +403,8 @@ AudioHandler::AudioHandler( chmaps_[stream_id] = GetVirtioSndChmapInfo(settings); constexpr uint32_t kCardId = 0; // As of now only one card is supported - if (settings.has_mute_control) { + if (settings.direction == AudioStreamSettings::Direction::Playback || + settings.has_mute_control) { controls_.push_back(GetVirtioCtlInfoMute(settings.direction, kCardId, settings.id, controls_.size())); controls_to_streams_map_.push_back( @@ -406,6 +429,11 @@ AudioHandler::AudioHandler( } if (settings.direction == AudioStreamSettings::Direction::Playback) { + controls_.push_back(GetVirtioCtlInfoDuck(settings.direction, kCardId, + settings.id, controls_.size())); + controls_to_streams_map_.push_back( + ControlDesc{.type = ControlDesc::Type::Duck, .stream_id = stream_id}); + controls_.push_back(GetVirtioCtlInfoFade(settings.direction, kCardId, settings.id, controls_.size())); controls_to_streams_map_.push_back( @@ -667,6 +695,35 @@ AudioStatus AudioHandler::HandleControlBalance(ControlCommand& cmd) { return AudioStatus::VIRTIO_SND_S_NOT_SUPP; } +AudioStatus AudioHandler::HandleControlDuck(ControlCommand& cmd) { + const auto stream_id = controls_to_streams_map_[cmd.control_id()].stream_id; + auto& stream = stream_descs_[stream_id]; + std::lock_guard lock(stream.mtx); + + if (cmd.type() == AudioCommandType::VIRTIO_SND_R_CTL_READ) { + auto& val = cmd.value()->value.integer; + val[0] = Le32(stream.is_ducked ? 1 : 0); + LOG(INFO) << "[Host AudioHandler] HandleControlDuck READ for stream " << stream_id + << " -> returning " << (stream.is_ducked ? 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(); + if (val > 1) { + LOG(ERROR) << "[Host AudioHandler] Wrong Duck value for control " << cmd.control_id() + << " (stream " << stream_id << ") provided: " << val; + return AudioStatus::VIRTIO_SND_S_BAD_MSG; + } + stream.is_ducked = (val == 1); + LOG(INFO) << "[Host AudioHandler] Setting Duck for stream " << stream_id + << " to " << (stream.is_ducked ? "DUCKED (1)" : "UNDUCKED (0)"); + 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()) { @@ -694,6 +751,9 @@ void AudioHandler::OnControlCommand(ControlCommand& cmd) { case ControlDesc::Type::Balance: result = HandleControlBalance(cmd); break; + case ControlDesc::Type::Duck: + result = HandleControlDuck(cmd); + break; } cmd.Reply(result); } @@ -713,6 +773,7 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) { float volume = 0; float fade = 0; float balance = 0; + bool is_ducked = false; { auto& stream_desc = stream_descs_[stream_id]; std::lock_guard lock(stream_desc.mtx); @@ -732,9 +793,11 @@ void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) { bits_per_channel = stream_desc.bits_per_sample; fade = stream_desc.fade; balance = stream_desc.balance; + is_ducked = stream_desc.is_ducked; } audio_mixer_->OnPlayback(stream_id, sample_rate, channels, bits_per_channel, - volume, fade, balance, buffer.get(), buffer.len()); + volume, fade, balance, is_ducked, buffer.get(), + buffer.len()); buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len()); } diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h index ce6e2e341c0..6f6b82c6767 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h @@ -54,6 +54,7 @@ class AudioHandler : public AudioServerExecutor { }; Volume volume; bool muted = false; + bool is_ducked = false; float fade = 0.0f; float balance = 0.0f; }; @@ -64,6 +65,7 @@ class AudioHandler : public AudioServerExecutor { Volume, Fade, Balance, + Duck, }; Type type = Type::Mute; @@ -103,6 +105,7 @@ class AudioHandler : public AudioServerExecutor { AudioStatus HandleControlVolume(ControlCommand& cmd); AudioStatus HandleControlFade(ControlCommand& cmd); AudioStatus HandleControlBalance(ControlCommand& cmd); + AudioStatus HandleControlDuck(ControlCommand& cmd); std::unique_ptr audio_server_; std::thread server_thread_; diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp index 133542c059b..4a2cd13d8da 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp @@ -165,7 +165,7 @@ void AudioMixer::OnStreamStopped(uint32_t stream_id) { void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, uint8_t stream_channels_count, uint8_t stream_bits_per_channel, float volume, - float fade, float balance, + float fade, float balance, bool is_ducked, const uint8_t* buffer, size_t size) { const auto stream_frames_count = GetFramesCount(size, stream_channels_count, stream_bits_per_channel); @@ -175,7 +175,7 @@ void AudioMixer::OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, std::unique_lock lock(mutex_); const auto channel_matrix = BuildChannelMixingMatrix( - channels_count_, stream_channels_count, volume, fade, balance); + channels_count_, stream_channels_count, volume, fade, balance, is_ducked); const bool need_notify = next_frame_.empty(); // no active streams diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h index 4283b0fa529..a41fc451bb8 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h @@ -27,7 +27,7 @@ class AudioMixer { void OnPlayback(uint32_t stream_id, uint32_t stream_sample_rate, uint8_t stream_channels_count, uint8_t stream_bits_per_channel, float volume, - float fade, float balance, + float fade, float balance, bool is_ducked, const uint8_t* buffer, size_t size); void OnStreamStopped(uint32_t stream_id);