diff --git a/libwebrtc/src/native/apm.rs b/libwebrtc/src/native/apm.rs index 7f36405e2..2ee2c8edc 100644 --- a/libwebrtc/src/native/apm.rs +++ b/libwebrtc/src/native/apm.rs @@ -13,6 +13,7 @@ // limitations under the License. use cxx::UniquePtr; +use std::path::Path; use webrtc_sys::apm::ffi as sys_apm; use crate::{RtcError, RtcErrorType}; @@ -110,4 +111,37 @@ impl AudioProcessingModule { }) } } + + /// Creates and attaches an AEC dump for recording debugging information. + pub fn create_and_attach_aec_dump( + &mut self, + file_path: impl AsRef, + max_log_size_bytes: Option, + ) -> Result<(), RtcError> { + let Some(file_path) = file_path.as_ref().to_str() else { + Err(RtcError { + error_type: RtcErrorType::Internal, + message: "Invalid file path".to_string(), + })? + }; + let max_size = max_log_size_bytes.unwrap_or(-1); + + if self.sys_handle.pin_mut().create_and_attach_aec_dump(file_path, max_size) { + Ok(()) + } else { + Err(RtcError { + error_type: RtcErrorType::Internal, + message: "Failed to create and attach AEC dump".to_string(), + }) + } + } + + /// Ends an in-progress AEC dump. + /// + /// If no AEC dump was created with [`create_and_attach_aec_dump`], this + /// method has no effect. + /// + pub fn detach_aec_dump(&mut self) { + self.sys_handle.pin_mut().detach_aec_dump(); + } } diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 6aac005ab..6f4d6dd57 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -54,9 +54,9 @@ message NewAudioSourceRequest { } message NewAudioSourceResponse { required OwnedAudioSource source = 1; } -// Push a frame to an AudioSource +// Push a frame to an AudioSource // The data provided must be available as long as the client receive the callback. -message CaptureAudioFrameRequest { +message CaptureAudioFrameRequest { required uint64 source_handle = 1; required AudioFrameBufferInfo buffer = 2; } @@ -137,6 +137,21 @@ message ApmSetStreamDelayResponse { optional string error = 1; } +message ApmAecDumpCreateAndAttachRequest { + required uint64 apm_handle = 1; + required string file_path = 2; + optional int64 max_log_size_bytes = 3; +} + +message ApmAecDumpCreateAndAttachResponse { + optional string error = 1; +} + +message ApmAecDumpDetachRequest { + required uint64 apm_handle = 1; +} + +message ApmAecDumpDetachResponse {} // New resampler using SoX (much better quality) @@ -154,7 +169,7 @@ message NewSoxResamplerResponse { OwnedSoxResampler resampler = 1; string error = 2; } - + } message PushSoxResamplerRequest { @@ -240,7 +255,7 @@ message OwnedAudioStream { message AudioStreamEvent { required uint64 stream_handle = 1; - oneof message { + oneof message { AudioFrameReceived frame_received = 2; AudioStreamEOS eos = 3; } diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 7e58123d4..b01b2fa8a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -148,7 +148,10 @@ message FfiRequest { TextStreamWriterWriteRequest text_stream_write = 65; TextStreamWriterCloseRequest text_stream_close = 66; - // NEXT_ID: 67 + ApmAecDumpCreateAndAttachRequest apm_aec_dump_create_and_attach = 67; + ApmAecDumpDetachRequest apm_aec_dump_detach = 68; + + // NEXT_ID: 69 } } @@ -243,7 +246,10 @@ message FfiResponse { TextStreamWriterWriteResponse text_stream_write = 64; TextStreamWriterCloseResponse text_stream_close = 65; - // NEXT_ID: 66 + ApmAecDumpCreateAndAttachResponse apm_aec_dump_create_and_attach = 66; + ApmAecDumpDetachResponse apm_aec_dump_detach = 67; + + // NEXT_ID: 68 } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index be0e5c1bc..822cd6b90 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -4131,7 +4131,7 @@ pub struct NewAudioSourceResponse { #[prost(message, required, tag="1")] pub source: OwnedAudioSource, } -/// Push a frame to an AudioSource +/// Push a frame to an AudioSource /// The data provided must be available as long as the client receive the callback. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4273,6 +4273,32 @@ pub struct ApmSetStreamDelayResponse { #[prost(string, optional, tag="1")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmAecDumpCreateAndAttachRequest { + #[prost(uint64, required, tag="1")] + pub apm_handle: u64, + #[prost(string, required, tag="2")] + pub file_path: ::prost::alloc::string::String, + #[prost(int64, optional, tag="3")] + pub max_log_size_bytes: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmAecDumpCreateAndAttachResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmAecDumpDetachRequest { + #[prost(uint64, required, tag="1")] + pub apm_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmAecDumpDetachResponse { +} // New resampler using SoX (much better quality) #[allow(clippy::derive_partial_eq_without_eq)] @@ -4804,7 +4830,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4952,13 +4978,17 @@ pub mod ffi_request { TextStreamWrite(super::TextStreamWriterWriteRequest), #[prost(message, tag="66")] TextStreamClose(super::TextStreamWriterCloseRequest), + #[prost(message, tag="67")] + ApmAecDumpCreateAndAttach(super::ApmAecDumpCreateAndAttachRequest), + #[prost(message, tag="68")] + ApmAecDumpDetach(super::ApmAecDumpDetachRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -5104,6 +5134,10 @@ pub mod ffi_response { TextStreamWrite(super::TextStreamWriterWriteResponse), #[prost(message, tag="65")] TextStreamClose(super::TextStreamWriterCloseResponse), + #[prost(message, tag="66")] + ApmAecDumpCreateAndAttach(super::ApmAecDumpCreateAndAttachResponse), + #[prost(message, tag="67")] + ApmAecDumpDetach(super::ApmAecDumpDetachResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index c45dba855..61b6215c8 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -958,6 +958,36 @@ fn on_apm_set_stream_delay( Ok(proto::ApmSetStreamDelayResponse { error: None }) } +fn on_apm_aec_dump_create_and_attach( + server: &'static FfiServer, + request: proto::ApmAecDumpCreateAndAttachRequest, +) -> FfiResult { + let aec = server + .retrieve_handle::>>(request.apm_handle)? + .clone(); + + let mut aec = aec.lock(); + + if let Err(e) = aec.create_and_attach_aec_dump(&request.file_path, request.max_log_size_bytes) { + return Ok(proto::ApmAecDumpCreateAndAttachResponse { error: Some(e.to_string()) }); + } + + Ok(proto::ApmAecDumpCreateAndAttachResponse { error: None }) +} + +fn on_apm_aec_dump_detach( + server: &'static FfiServer, + request: proto::ApmAecDumpDetachRequest, +) -> FfiResult { + let aec = server + .retrieve_handle::>>(request.apm_handle)? + .clone(); + + let mut aec = aec.lock(); + aec.detach_aec_dump(); + Ok(proto::ApmAecDumpDetachResponse {}) +} + fn on_perform_rpc( server: &'static FfiServer, request: proto::PerformRpcRequest, @@ -1311,6 +1341,14 @@ pub fn handle_request( server, request, )?) } + proto::ffi_request::Message::ApmAecDumpCreateAndAttach(request) => { + proto::ffi_response::Message::ApmAecDumpCreateAndAttach( + on_apm_aec_dump_create_and_attach(server, request)?, + ) + } + proto::ffi_request::Message::ApmAecDumpDetach(request) => { + proto::ffi_response::Message::ApmAecDumpDetach(on_apm_aec_dump_detach(server, request)?) + } proto::ffi_request::Message::PerformRpc(request) => { proto::ffi_response::Message::PerformRpc(on_perform_rpc(server, request)?) } diff --git a/webrtc-sys/include/livekit/apm.h b/webrtc-sys/include/livekit/apm.h index 5f0e17eca..695a09f43 100644 --- a/webrtc-sys/include/livekit/apm.h +++ b/webrtc-sys/include/livekit/apm.h @@ -19,10 +19,13 @@ #include #include "api/scoped_refptr.h" +#include "api/task_queue/task_queue_base.h" #include "api/video_codecs/video_decoder_factory.h" #include "api/video_codecs/video_encoder_factory.h" #include "modules/audio_processing/aec3/echo_canceller3.h" #include "modules/audio_processing/audio_buffer.h" +#include "livekit/global_task_queue.h" +#include "rust/cxx.h" namespace livekit { @@ -62,8 +65,14 @@ class AudioProcessingModule { int set_stream_delay_ms(int delay_ms); + bool create_and_attach_aec_dump(rust::Str file_name, + int64_t max_log_size_bytes); + + void detach_aec_dump(); + private: rtc::scoped_refptr apm_; + std::unique_ptr aec_dump_queue_; }; std::unique_ptr create_apm( diff --git a/webrtc-sys/src/apm.cpp b/webrtc-sys/src/apm.cpp index 0c6bcd1ec..58a8d4f92 100644 --- a/webrtc-sys/src/apm.cpp +++ b/webrtc-sys/src/apm.cpp @@ -50,4 +50,21 @@ std::unique_ptr create_apm( return std::make_unique(config); } +bool AudioProcessingModule::create_and_attach_aec_dump(rust::Str file_name, + int64_t max_log_size_bytes) { + if (!aec_dump_queue_) { + aec_dump_queue_ = GetGlobalTaskQueueFactory()->CreateTaskQueue( + "aec-dump", webrtc::TaskQueueFactory::Priority::LOW); + } + return apm_->CreateAndAttachAecDump( + absl::string_view(file_name.data(), file_name.size()), + max_log_size_bytes, + aec_dump_queue_.get()); +} + +void AudioProcessingModule::detach_aec_dump() { + apm_->DetachAecDump(); + aec_dump_queue_.reset(); +} + } // namespace livekit diff --git a/webrtc-sys/src/apm.rs b/webrtc-sys/src/apm.rs index 30c804ee1..e625c0317 100644 --- a/webrtc-sys/src/apm.rs +++ b/webrtc-sys/src/apm.rs @@ -43,6 +43,14 @@ pub mod ffi { fn set_stream_delay_ms(self: Pin<&mut AudioProcessingModule>, delay: i32) -> i32; + fn create_and_attach_aec_dump( + self: Pin<&mut AudioProcessingModule>, + file_name: &str, + max_log_size_bytes: i64, + ) -> bool; + + fn detach_aec_dump(self: Pin<&mut AudioProcessingModule>); + fn create_apm( echo_canceller_enabled: bool, gain_controller_enabled: bool,