1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Copyright 2025 LiveKit, Inc.
//
// 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.
use std::sync::Arc;
use cxx::{SharedPtr, UniquePtr};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use webrtc_sys::{peer_connection_factory as sys_pcf, rtc_error as sys_err, webrtc as sys_rtc};
use crate::{
audio_source::native::NativeAudioSource,
audio_track::RtcAudioTrack,
imp::{audio_track as imp_at, peer_connection as imp_pc, video_track as imp_vt},
peer_connection::PeerConnection,
peer_connection_factory::RtcConfiguration,
rtp_parameters::RtpCapabilities,
video_source::native::NativeVideoSource,
video_track::RtcVideoTrack,
MediaType, RtcError,
};
lazy_static! {
static ref LOG_SINK: Mutex<Option<UniquePtr<sys_rtc::ffi::LogSink>>> = Default::default();
}
#[derive(Clone)]
pub struct PeerConnectionFactory {
pub(crate) sys_handle: SharedPtr<sys_pcf::ffi::PeerConnectionFactory>,
}
impl Default for PeerConnectionFactory {
fn default() -> Self {
let mut log_sink = LOG_SINK.lock();
if log_sink.is_none() {
*log_sink = Some(sys_rtc::ffi::new_log_sink(|msg, _| {
let msg = msg.strip_suffix("\r\n").or(msg.strip_suffix('\n')).unwrap_or(&msg);
log::debug!(target: "libwebrtc", "{}", msg);
}));
}
let sys_handle = sys_pcf::ffi::create_peer_connection_factory();
Self { sys_handle }
}
}
impl PeerConnectionFactory {
pub fn create_peer_connection(
&self,
config: RtcConfiguration,
) -> Result<PeerConnection, RtcError> {
let observer = Arc::new(imp_pc::PeerObserver::default());
let res = self.sys_handle.create_peer_connection(
config.into(),
Box::new(sys_pcf::PeerConnectionObserverWrapper::new(observer.clone())),
);
match res {
Ok(sys_handle) => Ok(PeerConnection {
handle: imp_pc::PeerConnection::configure(sys_handle, observer),
}),
Err(e) => Err(unsafe { sys_err::ffi::RtcError::from(e.what()).into() }),
}
}
pub fn create_video_track(&self, label: &str, source: NativeVideoSource) -> RtcVideoTrack {
RtcVideoTrack {
handle: imp_vt::RtcVideoTrack::new(
self.sys_handle.create_video_track(label.to_string(), source.handle.sys_handle()),
),
}
}
pub fn create_audio_track(&self, label: &str, source: NativeAudioSource) -> RtcAudioTrack {
RtcAudioTrack {
handle: imp_at::RtcAudioTrack {
sys_handle: self
.sys_handle
.create_audio_track(label.to_string(), source.handle.sys_handle()),
},
}
}
/// Create an audio track that uses the Platform ADM for capture.
///
/// This requires that `enable_platform_adm()` was called first.
/// The track will capture audio from the selected recording device.
pub fn create_device_audio_track(&self, label: &str) -> RtcAudioTrack {
RtcAudioTrack {
handle: imp_at::RtcAudioTrack {
sys_handle: self.sys_handle.create_device_audio_track(label.to_string()),
},
}
}
pub fn get_rtp_sender_capabilities(&self, media_type: MediaType) -> RtpCapabilities {
self.sys_handle.rtp_sender_capabilities(media_type.into()).into()
}
pub fn get_rtp_receiver_capabilities(&self, media_type: MediaType) -> RtpCapabilities {
self.sys_handle.rtp_receiver_capabilities(media_type.into()).into()
}
// ===== Device Management Methods =====
/// Get the number of playout (output) devices
pub fn playout_devices(&self) -> i16 {
self.sys_handle.audio_device().playout_devices()
}
/// Get the number of recording (input) devices
pub fn recording_devices(&self) -> i16 {
self.sys_handle.audio_device().recording_devices()
}
/// Get the name of a playout device by index
pub fn playout_device_name(&self, index: u16) -> String {
self.sys_handle.audio_device().playout_device_name(index)
}
/// Get the name of a recording device by index
pub fn recording_device_name(&self, index: u16) -> String {
self.sys_handle.audio_device().recording_device_name(index)
}
/// Get the GUID of a playout device by index
/// The GUID is a platform-specific unique identifier that is stable across device hot-plug events.
pub fn playout_device_guid(&self, index: u16) -> String {
self.sys_handle.audio_device().playout_device_guid(index)
}
/// Get the GUID of a recording device by index
/// The GUID is a platform-specific unique identifier that is stable across device hot-plug events.
pub fn recording_device_guid(&self, index: u16) -> String {
self.sys_handle.audio_device().recording_device_guid(index)
}
/// Set the playout device by index
pub fn set_playout_device(&self, index: u16) -> bool {
self.sys_handle.audio_device().set_playout_device(index)
}
/// Set the recording device by index
pub fn set_recording_device(&self, index: u16) -> bool {
self.sys_handle.audio_device().set_recording_device(index)
}
/// Set the playout device by GUID
/// This is preferred over index as GUIDs are stable across device hot-plug events.
pub fn set_playout_device_by_guid(&self, guid: &str) -> bool {
self.sys_handle.audio_device().set_playout_device_by_guid(guid.to_string())
}
/// Set the recording device by GUID
/// This is preferred over index as GUIDs are stable across device hot-plug events.
pub fn set_recording_device_by_guid(&self, guid: &str) -> bool {
self.sys_handle.audio_device().set_recording_device_by_guid(guid.to_string())
}
/// Stop recording (clears initialized state, allowing device switch)
pub fn stop_recording(&self) -> bool {
self.sys_handle.audio_device().stop_recording()
}
/// Initialize recording
pub fn init_recording(&self) -> bool {
self.sys_handle.audio_device().init_recording()
}
/// Start recording
pub fn start_recording(&self) -> bool {
self.sys_handle.audio_device().start_recording()
}
/// Check if recording is initialized
pub fn recording_is_initialized(&self) -> bool {
self.sys_handle.audio_device().recording_is_initialized()
}
/// Stop playout (clears initialized state, allowing device switch)
pub fn stop_playout(&self) -> bool {
self.sys_handle.audio_device().stop_playout()
}
/// Initialize playout
pub fn init_playout(&self) -> bool {
self.sys_handle.audio_device().init_playout()
}
/// Start playout
pub fn start_playout(&self) -> bool {
self.sys_handle.audio_device().start_playout()
}
/// Check if playout is initialized
pub fn playout_is_initialized(&self) -> bool {
self.sys_handle.audio_device().playout_is_initialized()
}
// ===== Built-in Audio Processing Methods =====
// These control hardware AEC/AGC/NS on platforms that support it (iOS, some Android)
/// Check if built-in (hardware) AEC is available on this device.
///
/// Returns true on iOS (VPIO) and some Android devices.
/// Returns false on desktop platforms (macOS, Windows, Linux).
pub fn builtin_aec_is_available(&self) -> bool {
self.sys_handle.audio_device().builtin_aec_is_available()
}
/// Check if built-in (hardware) AGC is available on this device.
///
/// Returns true on iOS (VPIO) and some Android devices.
/// Returns false on desktop platforms (macOS, Windows, Linux).
pub fn builtin_agc_is_available(&self) -> bool {
self.sys_handle.audio_device().builtin_agc_is_available()
}
/// Check if built-in (hardware) NS is available on this device.
///
/// Returns true on iOS (VPIO) and some Android devices.
/// Returns false on desktop platforms (macOS, Windows, Linux).
pub fn builtin_ns_is_available(&self) -> bool {
self.sys_handle.audio_device().builtin_ns_is_available()
}
/// Enable or disable built-in (hardware) AEC.
///
/// When disabled on platforms that support it, WebRTC's software AEC
/// will be used instead.
pub fn enable_builtin_aec(&self, enable: bool) -> bool {
self.sys_handle.audio_device().enable_builtin_aec(enable)
}
/// Enable or disable built-in (hardware) AGC.
///
/// When disabled on platforms that support it, WebRTC's software AGC
/// will be used instead.
pub fn enable_builtin_agc(&self, enable: bool) -> bool {
self.sys_handle.audio_device().enable_builtin_agc(enable)
}
/// Enable or disable built-in (hardware) NS.
///
/// When disabled on platforms that support it, WebRTC's software NS
/// will be used instead.
pub fn enable_builtin_ns(&self, enable: bool) -> bool {
self.sys_handle.audio_device().enable_builtin_ns(enable)
}
/// Control whether ADM recording (microphone) is enabled.
///
/// When disabled, WebRTC's calls to InitRecording/StartRecording will be no-ops.
/// Use this when only using NativeAudioSource (no microphone capture needed).
/// This prevents the microphone from interfering with the audio pipeline.
pub fn set_adm_recording_enabled(&self, enabled: bool) {
self.sys_handle.audio_device().set_adm_recording_enabled(enabled)
}
/// Check if ADM recording (microphone) is enabled.
pub fn adm_recording_enabled(&self) -> bool {
self.sys_handle.audio_device().adm_recording_enabled()
}
/// Control whether ADM playout (speakers) is enabled.
///
/// When disabled (default), playout uses synthetic mode - remote audio is
/// delivered via FFI callbacks to the application (e.g., Unity AudioSource).
/// When enabled, remote audio plays through the platform speakers with AEC.
pub fn set_adm_playout_enabled(&self, enabled: bool) {
self.sys_handle.audio_device().set_adm_playout_enabled(enabled)
}
/// Check if ADM playout (speakers) is enabled.
pub fn adm_playout_enabled(&self) -> bool {
self.sys_handle.audio_device().adm_playout_enabled()
}
// ===== Platform ADM Lifecycle Management =====
/// Acquires a reference to the Platform ADM.
///
/// On first call, creates and initializes the Platform ADM. On subsequent
/// calls, just increments the reference count.
///
/// Returns true if Platform ADM is ready for use, false if initialization failed.
pub fn acquire_platform_adm(&self) -> bool {
self.sys_handle.audio_device().acquire_platform_adm()
}
/// Releases a reference to the Platform ADM.
///
/// When the reference count reaches zero, the Platform ADM is terminated
/// and the proxy returns to synthetic mode.
pub fn release_platform_adm(&self) {
self.sys_handle.audio_device().release_platform_adm()
}
/// Returns the current reference count for the Platform ADM.
pub fn platform_adm_ref_count(&self) -> i32 {
self.sys_handle.audio_device().platform_adm_ref_count()
}
/// Returns true if Platform ADM is currently active (ref_count > 0).
pub fn is_platform_adm_active(&self) -> bool {
self.sys_handle.audio_device().is_platform_adm_active()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_MUTEX: Mutex<()> = Mutex::new(());
#[tokio::test]
async fn test_peer_connection_factory_and_audio_device_controller_bridge() {
let _guard = TEST_MUTEX.lock().expect("test mutex poisoned");
let _ = env_logger::builder().is_test(true).try_init();
let factory = PeerConnectionFactory::default();
let source = NativeVideoSource::default();
let _track = factory.create_video_track("test", source);
let recording_count = factory.recording_devices();
let playout_count = factory.playout_devices();
assert!(recording_count >= 0);
assert!(playout_count >= 0);
let initial_recording = factory.adm_recording_enabled();
factory.set_adm_recording_enabled(!initial_recording);
assert_eq!(factory.adm_recording_enabled(), !initial_recording);
factory.set_adm_recording_enabled(initial_recording);
}
}