/* * Copyright 2026 LiveKit * * 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 "livekit/platform_audio.h" #include #include "ffi.pb.h" #include "ffi_client.h" namespace livekit { struct PlatformAudioState { FfiHandle handle; }; namespace { std::uint64_t requireHandle(const std::shared_ptr& state) { if (!state || !state->handle) { throw PlatformAudioError("PlatformAudio has no valid FFI handle"); } return static_cast(state->handle.get()); } AudioDeviceInfo fromProto(const proto::AudioDeviceInfo& device) { AudioDeviceInfo out; out.index = device.index(); out.name = device.name(); out.id = device.has_guid() ? device.guid() : std::string(); return out; } std::vector convertDevices(const google::protobuf::RepeatedPtrField& devices) { std::vector out; out.reserve(static_cast(devices.size())); for (const auto& device : devices) { out.push_back(fromProto(device)); } return out; } proto::AudioSourceOptions toProto(const PlatformAudioOptions& options) { proto::AudioSourceOptions out; out.set_echo_cancellation(options.echo_cancellation); out.set_noise_suppression(options.noise_suppression); out.set_auto_gain_control(options.auto_gain_control); out.set_prefer_hardware(options.prefer_hardware); return out; } proto::GetAudioDevicesResponse getAudioDevices(const std::shared_ptr& state) { proto::FfiRequest req; req.mutable_get_audio_devices()->set_platform_audio_handle(requireHandle(state)); const proto::FfiResponse resp = FfiClient::instance().sendRequest(req); if (!resp.has_get_audio_devices()) { throw PlatformAudioError("FfiResponse missing get_audio_devices"); } const auto& devices = resp.get_audio_devices(); if (devices.has_error() && !devices.error().empty()) { throw PlatformAudioError(devices.error()); } return devices; } } // namespace PlatformAudioSource::PlatformAudioSource(FfiHandle handle, std::shared_ptr platform_audio) noexcept : handle_(std::move(handle)), platform_audio_(std::move(platform_audio)) {} PlatformAudio::PlatformAudio() { proto::FfiRequest req; req.mutable_new_platform_audio(); const proto::FfiResponse resp = FfiClient::instance().sendRequest(req); if (!resp.has_new_platform_audio()) { throw PlatformAudioError("Failed to construct PlatformAudio: FfiResponse missing new_platform_audio"); } const auto& platform_audio = resp.new_platform_audio(); if (platform_audio.has_error()) { throw PlatformAudioError(platform_audio.error()); } if (!platform_audio.has_platform_audio()) { throw PlatformAudioError("Failed to construct PlatformAudio: NewPlatformAudioResponse missing platform_audio"); } const auto& owned = platform_audio.platform_audio(); state_ = std::make_shared(); state_->handle = FfiHandle(static_cast(owned.handle().id())); } std::int32_t PlatformAudio::recordingDeviceCount() const { return static_cast(getAudioDevices(state_).recording_devices_size()); } std::int32_t PlatformAudio::playoutDeviceCount() const { return static_cast(getAudioDevices(state_).playout_devices_size()); } std::vector PlatformAudio::recordingDevices() const { return convertDevices(getAudioDevices(state_).recording_devices()); } std::vector PlatformAudio::playoutDevices() const { return convertDevices(getAudioDevices(state_).playout_devices()); } void PlatformAudio::setRecordingDevice(const std::string& device_id) const { proto::FfiRequest req; auto* msg = req.mutable_set_recording_device(); msg->set_platform_audio_handle(requireHandle(state_)); msg->set_device_id(device_id); const proto::FfiResponse resp = FfiClient::instance().sendRequest(req); if (!resp.has_set_recording_device()) { throw PlatformAudioError("FfiResponse missing set_recording_device"); } const auto& set_device = resp.set_recording_device(); if (set_device.has_error() && !set_device.error().empty()) { throw PlatformAudioError(set_device.error()); } } void PlatformAudio::setPlayoutDevice(const std::string& device_id) const { proto::FfiRequest req; auto* msg = req.mutable_set_playout_device(); msg->set_platform_audio_handle(requireHandle(state_)); msg->set_device_id(device_id); const proto::FfiResponse resp = FfiClient::instance().sendRequest(req); if (!resp.has_set_playout_device()) { throw PlatformAudioError("FfiResponse missing set_playout_device"); } const auto& set_device = resp.set_playout_device(); if (set_device.has_error() && !set_device.error().empty()) { throw PlatformAudioError(set_device.error()); } } std::shared_ptr PlatformAudio::createAudioSource(const PlatformAudioOptions& options) const { proto::FfiRequest req; auto* msg = req.mutable_new_audio_source(); msg->set_type(proto::AudioSourceType::AUDIO_SOURCE_PLATFORM); msg->set_platform_audio_handle(requireHandle(state_)); *msg->mutable_options() = toProto(options); const proto::FfiResponse resp = FfiClient::instance().sendRequest(req); if (!resp.has_new_audio_source()) { throw PlatformAudioError("FfiResponse missing new_audio_source"); } const auto& new_source = resp.new_audio_source(); if (!new_source.has_source() || !new_source.source().has_handle()) { throw PlatformAudioError("NewAudioSourceResponse missing source handle"); } const auto& source = new_source.source(); const std::uint64_t handle_id = source.handle().id(); if (handle_id == 0) { throw PlatformAudioError("NewAudioSourceResponse returned an invalid (null) source handle"); } FfiHandle handle(static_cast(handle_id)); return std::shared_ptr(new PlatformAudioSource(std::move(handle), state_)); } } // namespace livekit