diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc index 9f916d74877..5103714f973 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc @@ -841,6 +841,10 @@ Result 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( diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/proto/guest_config.proto b/base/cvd/cuttlefish/host/commands/assemble_cvd/proto/guest_config.proto index f826bbfd460..fb588bf603d 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/proto/guest_config.proto +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/proto/guest_config.proto @@ -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; @@ -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 { diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp index 85dc5ab8387..408bd473bc3 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.cpp @@ -17,13 +17,12 @@ #include "cuttlefish/host/frontend/webrtc/audio_handler.h" #include -#include -#include +#include #include +#include +#include #include -#include "absl/log/check.h" -#include "absl/log/log.h" #include "cuttlefish/host/frontend/webrtc/audio_mixer.h" @@ -50,8 +49,7 @@ virtio_snd_chmap_info GetVirtioSndChmapInfo( const AudioStreamSettings& settings) { const static std::unordered_map> 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}}, @@ -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 = @@ -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, + }; + } } } @@ -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]() { @@ -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 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 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 @@ -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 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; } @@ -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]; { @@ -448,6 +621,7 @@ 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 @@ -455,24 +629,24 @@ void AudioHandler::OnCaptureBuffer(RxBuffer buffer) { 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); @@ -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) { + 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(rx_buffer), bytes_read / 2, + volume); + break; + case 4: + apply_volume(reinterpret_cast(rx_buffer), bytes_read / 4, + volume); + break; + } + } + 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 929d7aaa201..156a2b28557 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_handler.h @@ -16,8 +16,11 @@ #pragma once +#include +#include #include #include +#include #include #include @@ -37,6 +40,30 @@ class AudioHandler : public AudioServerExecutor { uint8_t bits_per_sample = 0; uint8_t channels = 0; bool active = false; + + // Controls + struct Volume { + uint32_t min = 0; + uint32_t max = 1; + uint32_t current = max; + + float GetCurrentVolumeLevel() { + return static_cast(current - min) / + static_cast(max - min); + }; + }; + Volume volume; + bool muted = false; + }; + + struct ControlDesc { + enum class Type { + Mute, + Volume, + }; + + Type type = Type::Mute; + size_t stream_id = 0; }; public: @@ -58,6 +85,8 @@ class AudioHandler : public AudioServerExecutor { void StopStream(StreamControlCommand& cmd) override; void ChmapsInfo(ChmapInfoCommand& cmd) override; void JacksInfo(JackInfoCommand& cmd) override; + void ControlsInfo(ControlInfoCommand& cmd) override; + void OnControlCommand(ControlCommand& cmd) override; void OnPlaybackBuffer(TxBuffer buffer) override; void OnCaptureBuffer(RxBuffer buffer) override; @@ -72,6 +101,8 @@ class AudioHandler : public AudioServerExecutor { std::vector streams_; std::vector stream_descs_ = {}; std::vector chmaps_; + std::vector controls_; + std::vector controls_to_streams_map_; std::unique_ptr audio_mixer_; }; } // 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 21a30cb56f7..a37bb4a4123 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.cpp @@ -3,8 +3,9 @@ #include #include #include +#include "audio_settings.h" -#include "absl/log/check.h" +#include namespace cuttlefish { namespace { @@ -57,7 +58,8 @@ size_t ConvertAudioStream(void* dst, uint8_t dst_channels, uint32_t dst_rate, const void* src, uint8_t src_channels, uint32_t src_rate, size_t src_frames_count, const std::vector>& channel_map) { - constexpr uint8_t kMaxChannelsCount = 6; + constexpr uint8_t kMaxChannelsCount = + GetChannelsCount(AudioChannelsLayout::Surround51); CHECK(src_channels <= kMaxChannelsCount); CHECK(channel_map.size() >= dst_channels); @@ -117,7 +119,7 @@ using ConvertAudioStreamFn = size_t(void*, uint8_t, uint32_t, const void*, * way to look up the correct conversion function based on the source and * destination audio formats. * 0-byte and 3-bytes samples are not supported. - * */ + */ constexpr std::array, 5> kConvertAudioStreamFunctionMap = { {{nullptr, nullptr, nullptr, nullptr, nullptr}, @@ -161,23 +163,20 @@ 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, + uint8_t stream_bits_per_channel, float volume, const uint8_t* buffer, size_t size) { - static const std::vector> kDirectMap{{ - {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}, - }}; - const auto stream_frames_count = GetFramesCount(size, stream_channels_count, stream_bits_per_channel); const auto frames_count = GetFrameCountAfterResampling( sample_rate_, stream_sample_rate, stream_frames_count); 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; + } + const bool need_notify = next_frame_.empty(); // no active streams const auto it = next_frame_.find(stream_id); @@ -205,7 +204,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, kDirectMap); + stream_sample_rate, stream_frames_count, channles_map); 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 5f5220fa101..4ecd8e3d2a2 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_mixer.h @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include #include "cuttlefish/host/frontend/webrtc/libdevice/audio_sink.h" #include "cuttlefish/host/frontend/webrtc/audio_settings.h" @@ -25,9 +25,9 @@ class AudioMixer { // Called by auido_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, const uint8_t* buffer, - size_t size); + uint8_t stream_channels_count, + uint8_t stream_bits_per_channel, float volume, const uint8_t* buffer, + size_t size); void OnStreamStopped(uint32_t stream_id); private: @@ -48,15 +48,26 @@ class AudioMixer { ///////////////// Guarded by mutex_ //////////////// //////////////////////////////////////////////////// - // Buffer stores mixed auido data for every active stream. Consumed by MixerLoop + // Buffer stores mixed auido data for every active stream. Consumed by + // MixerLoop std::vector mixed_buffer_; // Index of the last frame with available (i.e. not yet played) audio data size_t last_active_frame_ = 0; - // Frame index per stream to put next avaiable data to + // 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}, + }}; + //////////////////////////////////////////////////// //////////////////////////////////////////////////// diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/audio_settings.h b/base/cvd/cuttlefish/host/frontend/webrtc/audio_settings.h index 5ee07b7fe6f..e4406160857 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/audio_settings.h +++ b/base/cvd/cuttlefish/host/frontend/webrtc/audio_settings.h @@ -32,11 +32,19 @@ struct AudioStreamSettings { Capture, }; + struct VolumeControl { + uint32_t min = 0; + uint32_t max = 100; + uint32_t step = 1; + }; + uint8_t id = 0; AudioChannelsLayout channels_layout = AudioChannelsLayout::Stereo; Direction direction = Direction::Playback; -}; + bool has_mute_control = false; + std::optional master_volume_control; +}; struct AudioMixerSettings { AudioChannelsLayout channels_layout = AudioChannelsLayout::Stereo; diff --git a/base/cvd/cuttlefish/host/frontend/webrtc/main.cpp b/base/cvd/cuttlefish/host/frontend/webrtc/main.cpp index e34eaf54a39..dd5e05af25e 100644 --- a/base/cvd/cuttlefish/host/frontend/webrtc/main.cpp +++ b/base/cvd/cuttlefish/host/frontend/webrtc/main.cpp @@ -205,6 +205,31 @@ std::shared_ptr SetupAudio( {SampleRate::Audio_SampleRate_RATE_64000, 64000}, }; + static const auto parse_stream_settings = + [](const ::cuttlefish::config::Audio_PCMDevice_Stream& stream, + AudioStreamSettings::Direction direction) + -> cuttlefish::AudioStreamSettings { + const auto id = stream.id(); + CHECK(id <= std::numeric_limits::max()); + cuttlefish::AudioStreamSettings settings = { + .id = static_cast(id), + .channels_layout = kChannelLayoutMap.at(stream.channel_layout()), + .direction = direction}; + if (stream.has_controls()) { + const auto& controls = stream.controls(); + settings.has_mute_control = controls.mute_control_enabled(); + if (controls.has_volume_control()) { + const auto& volume = controls.volume_control(); + settings.master_volume_control = {{ + .min = volume.min(), + .max = volume.max(), + .step = volume.step(), + }}; + } + } + return settings; + }; + if (!instance.enable_audio()) { return nullptr; } @@ -214,13 +239,22 @@ std::shared_ptr SetupAudio( const auto audio_settings = instance.audio_settings(); if (!audio_settings.has_value()) { const auto output_streams_count = instance.audio_output_streams_count(); - streams.push_back({.id = 0, - .channels_layout = AudioChannelsLayout::Stereo, - .direction = AudioStreamSettings::Direction::Capture}); + streams.push_back({ + .id = 0, + .channels_layout = AudioChannelsLayout::Stereo, + .direction = AudioStreamSettings::Direction::Capture, + .has_mute_control = true, + }); for (auto i = 0; i < output_streams_count; ++i) { streams.push_back({.id = static_cast(i), .channels_layout = AudioChannelsLayout::Stereo, - .direction = AudioStreamSettings::Direction::Playback}); + .direction = AudioStreamSettings::Direction::Playback, + .has_mute_control = true, + .master_volume_control = {{ + .min = 0, + .max = 10, + .step = 1, + }}}); } } else { CHECK(!audio_settings->pcm_devices().empty()); @@ -229,20 +263,12 @@ std::shared_ptr SetupAudio( } const auto& pcm = audio_settings->pcm_devices()[0]; for (const auto& stream : pcm.playback_streams()) { - const auto id = stream.id(); - CHECK(id <= std::numeric_limits::max()); - streams.push_back( - {.id = static_cast(id), - .channels_layout = kChannelLayoutMap.at(stream.channel_layout()), - .direction = AudioStreamSettings::Direction::Playback}); + streams.push_back(parse_stream_settings( + stream, AudioStreamSettings::Direction::Playback)); } for (const auto& stream : pcm.capture_streams()) { - const auto id = stream.id(); - CHECK(id <= std::numeric_limits::max()); - streams.push_back( - {.id = static_cast(id), - .channels_layout = kChannelLayoutMap.at(stream.channel_layout()), - .direction = AudioStreamSettings::Direction::Capture}); + streams.push_back(parse_stream_settings( + stream, AudioStreamSettings::Direction::Capture)); } if (pcm.has_mixer()) { const auto& mixer = pcm.mixer(); diff --git a/base/cvd/cuttlefish/host/libs/audio_connector/commands.cpp b/base/cvd/cuttlefish/host/libs/audio_connector/commands.cpp index cc8339c562f..b253b129749 100644 --- a/base/cvd/cuttlefish/host/libs/audio_connector/commands.cpp +++ b/base/cvd/cuttlefish/host/libs/audio_connector/commands.cpp @@ -75,6 +75,24 @@ void ChmapInfoCommand::Reply(AudioStatus status, } } +ControlInfoCommand::ControlInfoCommand(uint32_t start_id, size_t count, + virtio_snd_ctl_info* ctl_info) + : InfoCommand(AudioCommandType::VIRTIO_SND_R_CTL_INFO, start_id, count, + ctl_info) {} + +void ControlInfoCommand::Reply( + AudioStatus status, std::span reply) { + MarkReplied(status); + if (status != AudioStatus::VIRTIO_SND_S_OK) { + return; + } + CHECK(reply.size() == count()) + << "Returned unmatching info count: " << reply.size() << " vs " + << count(); + + std::copy(reply.begin(), reply.end(), info_reply()); +} + StreamInfoCommand::StreamInfoCommand(uint32_t start_id, size_t count, virtio_snd_pcm_info* pcm_info) : InfoCommand(AudioCommandType::VIRTIO_SND_R_PCM_INFO, start_id, count, @@ -124,4 +142,10 @@ StreamSetParamsCommand::StreamSetParamsCommand( format_(format), rate_(rate) {} +ControlCommand::ControlCommand(AudioCommandType type, uint32_t control_id, + virtio_snd_ctl_value* value) + : AudioCommand(type), control_id_(control_id), value_(value) {} + +void ControlCommand::Reply(AudioStatus status) { MarkReplied(status); } + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/audio_connector/commands.h b/base/cvd/cuttlefish/host/libs/audio_connector/commands.h index 39c825e0cc8..f3b9dcdc030 100644 --- a/base/cvd/cuttlefish/host/libs/audio_connector/commands.h +++ b/base/cvd/cuttlefish/host/libs/audio_connector/commands.h @@ -16,6 +16,7 @@ #include +#include #include #include "cuttlefish/host/libs/audio_connector/shm_layout.h" @@ -78,6 +79,14 @@ class JackInfoCommand : public InfoCommand { const std::vector& reply); }; +class ControlInfoCommand : public InfoCommand { + public: + ControlInfoCommand(uint32_t start_id, size_t count, + virtio_snd_ctl_info* ctl_info); + + void Reply(AudioStatus status, std::span reply); +}; + class StreamInfoCommand : public InfoCommand { public: StreamInfoCommand(uint32_t start_id, size_t count, @@ -122,4 +131,19 @@ struct StreamSetParamsCommand : public StreamControlCommand { const uint8_t rate_; }; +struct ControlCommand : public AudioCommand { + public: + ControlCommand(AudioCommandType type, uint32_t control_id, + virtio_snd_ctl_value* value); + + uint32_t control_id() const { return control_id_; } + virtio_snd_ctl_value* value() { return value_; } + + void Reply(AudioStatus status); + + private: + const uint32_t control_id_; + virtio_snd_ctl_value* value_; +}; + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/audio_connector/server.cpp b/base/cvd/cuttlefish/host/libs/audio_connector/server.cpp index 0ead049d20f..796a2dce404 100644 --- a/base/cvd/cuttlefish/host/libs/audio_connector/server.cpp +++ b/base/cvd/cuttlefish/host/libs/audio_connector/server.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -99,7 +100,7 @@ std::function SendStatusCallback( } // namespace std::unique_ptr AudioServer::AcceptClient( - uint32_t num_streams, uint32_t num_jacks, uint32_t num_chmaps, + uint32_t num_streams, uint32_t num_jacks, uint32_t num_chmaps, uint32_t num_controls, size_t tx_shm_len, size_t rx_shm_len) { auto conn_fd = SharedFD::Accept(*server_socket_, nullptr, 0); if (!conn_fd->IsOpen()) { @@ -107,13 +108,13 @@ std::unique_ptr AudioServer::AcceptClient( return nullptr; } return AudioClientConnection::Create(conn_fd, num_streams, num_jacks, - num_chmaps, tx_shm_len, rx_shm_len); + num_chmaps, num_controls, tx_shm_len, rx_shm_len); } /* static */ std::unique_ptr AudioClientConnection::Create( SharedFD client_socket, uint32_t num_streams, uint32_t num_jacks, - uint32_t num_chmaps, size_t tx_shm_len, size_t rx_shm_len) { + uint32_t num_chmaps, uint32_t num_controls, size_t tx_shm_len, size_t rx_shm_len) { SharedFD event_socket, event_pair; SharedFD tx_socket, tx_pair; SharedFD rx_socket, rx_pair; @@ -143,6 +144,7 @@ std::unique_ptr AudioClientConnection::Create( .jacks = num_jacks, .streams = num_streams, .chmaps = num_chmaps, + .controls = num_controls, }; auto sent = client_socket->SendFileDescriptors( @@ -160,10 +162,9 @@ std::unique_ptr AudioClientConnection::Create( } bool AudioClientConnection::ReceiveCommands(AudioServerExecutor& executor) { - // The largest msg the client will send is 24 bytes long, using uint64_t - // guarantees it's aligned to 64 bits. - uint64_t recv_buffer[3]; - auto recv_size = + constexpr auto kBufSize = sizeof(virtio_snd_ctl_hdr) + sizeof(virtio_snd_ctl_value); + alignas(uint64_t) uint8_t recv_buffer[kBufSize]; + const auto recv_size = ReceiveMsg(control_socket_, &recv_buffer, sizeof(recv_buffer)); if (recv_size <= 0) { return false; @@ -298,6 +299,47 @@ bool AudioClientConnection::ReceiveCommands(AudioServerExecutor& executor) { case AudioCommandType::VIRTIO_SND_R_JACK_REMAP: LOG(ERROR) << "Unsupported command type: " << cmd_hdr->code.as_uint32_t(); return CmdReply(AudioStatus::VIRTIO_SND_S_NOT_SUPP); + case AudioCommandType::VIRTIO_SND_R_CTL_INFO: { + if (recv_size < sizeof(virtio_snd_query_info)) { + LOG(ERROR) << "Received QUERY_INFO message is too small: " << recv_size; + return false; + } + const auto& query_info = *reinterpret_cast(recv_buffer); + const auto info_count = query_info.count.as_uint32_t(); + const auto start_id = query_info.start_id.as_uint32_t(); + std::vector reply(info_count); + ControlInfoCommand cmd(start_id, info_count, reply.data()); + VLOG(0) << "VIRTIO_SND_R_CTL_INFO: start_id=" << start_id + << ", count=" << info_count; + + executor.ControlsInfo(cmd); + return CmdReply(cmd.status(), reply.data(), reply.size() * sizeof(virtio_snd_ctl_info)); + } + case AudioCommandType::VIRTIO_SND_R_CTL_READ: { + if (recv_size < sizeof(virtio_snd_ctl_hdr)) { + LOG(ERROR) << "Received CTL_READ message is too small: " << recv_size; + return false; + } + const auto& ctl_hdr = *reinterpret_cast(recv_buffer); + virtio_snd_ctl_value reply_value = {}; + ControlCommand cmd(AudioCommandType::VIRTIO_SND_R_CTL_READ, + ctl_hdr.control_id.as_uint32_t(), &reply_value); + executor.OnControlCommand(cmd); + return CmdReply(cmd.status(), &reply_value, sizeof(reply_value)); + } + case AudioCommandType::VIRTIO_SND_R_CTL_WRITE: { + constexpr auto kHdrSize = sizeof(virtio_snd_ctl_hdr); + if (recv_size < sizeof(virtio_snd_ctl_value) + kHdrSize) { + LOG(ERROR) << "Received CTL_WRITE message is too small: " << recv_size; + return false; + } + const auto& ctl_hdr = *reinterpret_cast(recv_buffer); + const auto ctl_value = + reinterpret_cast(&recv_buffer[kHdrSize]); + ControlCommand cmd(AudioCommandType::VIRTIO_SND_R_CTL_WRITE, ctl_hdr.control_id.as_uint32_t(), ctl_value); + executor.OnControlCommand(cmd); + return CmdReply(cmd.status()); + } default: LOG(ERROR) << "Unknown command type: " << cmd_hdr->code.as_uint32_t(); return CmdReply(AudioStatus::VIRTIO_SND_S_BAD_MSG); diff --git a/base/cvd/cuttlefish/host/libs/audio_connector/server.h b/base/cvd/cuttlefish/host/libs/audio_connector/server.h index 96a3c0f9d53..3f20902ba42 100644 --- a/base/cvd/cuttlefish/host/libs/audio_connector/server.h +++ b/base/cvd/cuttlefish/host/libs/audio_connector/server.h @@ -42,6 +42,8 @@ class AudioServerExecutor { virtual void StopStream(StreamControlCommand& cmd) = 0; virtual void ChmapsInfo(ChmapInfoCommand& cmd) = 0; virtual void JacksInfo(JackInfoCommand& cmd) = 0; + virtual void ControlsInfo(ControlInfoCommand& cmd) = 0; + virtual void OnControlCommand(ControlCommand& cmd) = 0; // Implementations must call buffer.SendStatus() before destroying the buffer // to notify the other side of the release of the buffer. Failure to do so @@ -54,7 +56,7 @@ class AudioClientConnection { public: static std::unique_ptr Create( SharedFD client_socket, uint32_t num_streams, uint32_t num_jacks, - uint32_t num_chmaps, size_t tx_shm_len, size_t rx_shm_len); + uint32_t num_chmaps, uint32_t num_controls, size_t tx_shm_len, size_t rx_shm_len); AudioClientConnection() = delete; AudioClientConnection(const AudioClientConnection&) = delete; @@ -104,6 +106,7 @@ class AudioServer { std::unique_ptr AcceptClient(uint32_t num_streams, uint32_t num_jacks, uint32_t num_chmaps, + uint32_t num_controls, size_t tx_shm_len, size_t rx_shm_len); diff --git a/base/cvd/cuttlefish/host/libs/audio_connector/shm_layout.h b/base/cvd/cuttlefish/host/libs/audio_connector/shm_layout.h index 7ef4bd581de..f6243307912 100644 --- a/base/cvd/cuttlefish/host/libs/audio_connector/shm_layout.h +++ b/base/cvd/cuttlefish/host/libs/audio_connector/shm_layout.h @@ -35,6 +35,15 @@ enum class AudioCommandType : uint32_t { /* channel map control request types */ VIRTIO_SND_R_CHMAP_INFO = 0x0200, + + /* control element request types */ + VIRTIO_SND_R_CTL_INFO = 0x0300, + VIRTIO_SND_R_CTL_ENUM_ITEMS, + VIRTIO_SND_R_CTL_READ, + VIRTIO_SND_R_CTL_WRITE, + VIRTIO_SND_R_CTL_TLV_READ, + VIRTIO_SND_R_CTL_TLV_WRITE, + VIRTIO_SND_R_CTL_TLV_COMMAND, }; enum class AudioStatus : uint32_t { @@ -217,14 +226,96 @@ struct virtio_snd_pcm_status { Le32 latency_bytes; }; -// Update this value when the msg layouts change -const uint32_t VIOS_VERSION = 2; +enum AudioControlRole { + VIRTIO_SND_CTL_ROLE_UNDEFINED = 0, + VIRTIO_SND_CTL_ROLE_VOLUME, + VIRTIO_SND_CTL_ROLE_MUTE, + VIRTIO_SND_CTL_ROLE_GAIN, +}; + +enum AudioControlType { + VIRTIO_SND_CTL_TYPE_BOOLEAN = 0, + VIRTIO_SND_CTL_TYPE_INTEGER, + VIRTIO_SND_CTL_TYPE_INTEGER64, + VIRTIO_SND_CTL_TYPE_ENUMERATED, + VIRTIO_SND_CTL_TYPE_BYTES, + VIRTIO_SND_CTL_TYPE_IEC958, +}; + +enum AudioControlAccess { + VIRTIO_SND_CTL_ACCESS_READ = 0, + VIRTIO_SND_CTL_ACCESS_WRITE, + VIRTIO_SND_CTL_ACCESS_VOLATILE, + VIRTIO_SND_CTL_ACCESS_INACTIVE, + VIRTIO_SND_CTL_ACCESS_TLV_READ, + VIRTIO_SND_CTL_ACCESS_TLV_WRITE, + VIRTIO_SND_CTL_ACCESS_TLV_COMMAND +}; + +struct virtio_snd_ctl_info { + struct virtio_snd_info hdr; + Le32 role; + Le32 type; + Le32 access; // 1 << VIRTIO_SND_CTL_ACCESS_XXX + Le32 count; + Le32 index; + uint8_t name[44]; + union { + struct { + Le32 min; + Le32 max; + Le32 step; + } integer; + struct { + Le64 min; + Le64 max; + Le64 step; + } integer64; + struct { + Le32 items; + } enumerated; + } value; +}; + +struct virtio_snd_ctl_hdr { + struct virtio_snd_hdr hdr; + Le32 control_id; +}; + +struct virtio_snd_ctl_value { + union { + struct { + Le32 value[128]; + } integer; + struct { + Le64 value[64]; + } integer64; + struct { + Le32 item[128]; + } enumerated; + struct { + uint8_t data[512]; + } bytes; + struct { + uint8_t data[16]; + } iec958; + } value; +}; + +struct virtio_snd_ctl_event { + struct virtio_snd_hdr hdr; + Le16 control_id; + Le16 mask; +}; + +const uint32_t VIOS_VERSION = 3; struct VioSConfig { uint32_t version; uint32_t jacks; uint32_t streams; uint32_t chmaps; + uint32_t controls; }; struct IoTransferMsg { @@ -248,6 +339,9 @@ ASSERT_VALID_MSG_TYPE(virtio_snd_chmap_info, 24); ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_info, 32); ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_set_params, 24); ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_hdr, 8); +ASSERT_VALID_MSG_TYPE(virtio_snd_ctl_info, 96); +ASSERT_VALID_MSG_TYPE(virtio_snd_ctl_hdr, 8); +ASSERT_VALID_MSG_TYPE(virtio_snd_ctl_value, 512); ASSERT_VALID_MSG_TYPE(IoTransferMsg, 12); ASSERT_VALID_MSG_TYPE(IoStatusMsg, 16); #undef ASSERT_VALID_MSG_TYPE