Skip to main content

fswtch/
media.rs

1use std::{
2    ffi::{CStr, c_void},
3    marker::PhantomData,
4    panic::{AssertUnwindSafe, catch_unwind},
5    ptr::NonNull,
6    slice,
7};
8
9use crate::{Result, Session, StaticCStr, SwitchError, log_error, status_to_result, sys};
10
11macro_rules! call_ffi {
12    ($call:expr) => {{
13        // SAFETY: The caller documents the FreeSWITCH ABI preconditions at each call site.
14        unsafe { $call }
15    }};
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub enum MediaBugAction {
20    Continue,
21    Stop,
22}
23
24impl MediaBugAction {
25    fn as_switch_bool(self) -> sys::switch_bool_t {
26        match self {
27            Self::Continue => sys::switch_bool_t_SWITCH_TRUE,
28            Self::Stop => sys::switch_bool_t_SWITCH_FALSE,
29        }
30    }
31}
32
33#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34pub struct MediaBugFlags(pub sys::switch_media_bug_flag_t);
35
36impl MediaBugFlags {
37    pub const BOTH: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_BOTH);
38    pub const READ_STREAM: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_READ_STREAM);
39    pub const WRITE_STREAM: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_WRITE_STREAM);
40    pub const WRITE_REPLACE: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_WRITE_REPLACE);
41    pub const READ_REPLACE: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_READ_REPLACE);
42    pub const READ_PING: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_READ_PING);
43    pub const STEREO: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_STEREO);
44    pub const ANSWER_REQUIRED: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_ANSWER_REQ);
45    pub const BRIDGE_REQUIRED: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_BRIDGE_REQ);
46    pub const THREAD_LOCK: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_THREAD_LOCK);
47    pub const PRUNE: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_PRUNE);
48    pub const NO_PAUSE: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_NO_PAUSE);
49    pub const STEREO_SWAP: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_STEREO_SWAP);
50    pub const LOCK: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_LOCK);
51    pub const TAP_NATIVE_READ: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_TAP_NATIVE_READ);
52    pub const TAP_NATIVE_WRITE: Self =
53        Self(sys::switch_media_bug_flag_enum_t_SMBF_TAP_NATIVE_WRITE);
54    pub const ONE_ONLY: Self = Self(sys::switch_media_bug_flag_enum_t_SMBF_ONE_ONLY);
55    pub const READ_TEXT_STREAM: Self =
56        Self(sys::switch_media_bug_flag_enum_t_SMBF_READ_TEXT_STREAM);
57
58    pub const fn bits(self) -> sys::switch_media_bug_flag_t {
59        self.0
60    }
61}
62
63impl std::ops::BitOr for MediaBugFlags {
64    type Output = Self;
65
66    fn bitor(self, rhs: Self) -> Self::Output {
67        Self(self.0 | rhs.0)
68    }
69}
70
71impl std::ops::BitOrAssign for MediaBugFlags {
72    fn bitor_assign(&mut self, rhs: Self) {
73        self.0 |= rhs.0;
74    }
75}
76
77#[derive(Debug, Copy, Clone)]
78pub struct MediaBugConfig {
79    pub function: &'static CStr,
80    pub target: &'static CStr,
81    pub flags: MediaBugFlags,
82    pub stop_time: sys::time_t,
83}
84
85impl MediaBugConfig {
86    pub fn new(
87        function: impl StaticCStr,
88        target: impl StaticCStr,
89        flags: MediaBugFlags,
90    ) -> Result<Self> {
91        Ok(Self {
92            function: function.into_static_cstr()?,
93            target: target.into_static_cstr()?,
94            flags,
95            stop_time: 0,
96        })
97    }
98
99    pub const fn stop_time(mut self, stop_time: sys::time_t) -> Self {
100        self.stop_time = stop_time;
101        self
102    }
103}
104
105#[derive(Debug, Copy, Clone)]
106pub struct MediaBug {
107    raw: NonNull<sys::switch_media_bug_t>,
108}
109
110impl MediaBug {
111    pub fn as_ptr(self) -> *mut sys::switch_media_bug_t {
112        self.raw.as_ptr()
113    }
114}
115
116pub trait MediaBugHandler: 'static {
117    fn on_init(&mut self, _ctx: &mut MediaBugContext<'_>) -> MediaBugAction {
118        MediaBugAction::Continue
119    }
120
121    fn on_read(
122        &mut self,
123        _ctx: &mut MediaBugContext<'_>,
124        _frame: MediaFrame<'_>,
125    ) -> MediaBugAction {
126        MediaBugAction::Continue
127    }
128
129    fn on_write(
130        &mut self,
131        _ctx: &mut MediaBugContext<'_>,
132        _frame: MediaFrame<'_>,
133    ) -> MediaBugAction {
134        MediaBugAction::Continue
135    }
136
137    fn on_read_replace(
138        &mut self,
139        _ctx: &mut MediaBugContext<'_>,
140        _frame: MediaFrameMut<'_>,
141    ) -> MediaBugAction {
142        MediaBugAction::Continue
143    }
144
145    fn on_write_replace(
146        &mut self,
147        _ctx: &mut MediaBugContext<'_>,
148        _frame: MediaFrameMut<'_>,
149    ) -> MediaBugAction {
150        MediaBugAction::Continue
151    }
152
153    fn on_close(&mut self, _ctx: &mut MediaBugContext<'_>) {}
154}
155
156/// Attaches a FreeSWITCH media bug and owns `handler` until FreeSWITCH closes the bug.
157///
158pub fn attach_media_bug<H>(session: Session, config: MediaBugConfig, handler: H) -> Result<MediaBug>
159where
160    H: MediaBugHandler,
161{
162    let state = Box::into_raw(Box::new(MediaBugState { handler }));
163    let mut bug = std::ptr::null_mut();
164
165    // SAFETY: `session` is live and `state` remains allocated until close or failure cleanup.
166    let status = call_ffi!(add_media_bug::<H>(session, config, state.cast(), &mut bug));
167
168    if status != crate::SUCCESS {
169        // SAFETY: FreeSWITCH did not take ownership on failure.
170        call_ffi!(drop(Box::from_raw(state)));
171        return Err(SwitchError(status));
172    }
173
174    let Some(raw) = NonNull::new(bug) else {
175        // SAFETY: FreeSWITCH did not return a bug handle, so there is no close callback that can
176        // reclaim the state.
177        call_ffi!(drop(Box::from_raw(state)));
178        return Err(SwitchError(crate::GENERR));
179    };
180    Ok(MediaBug { raw })
181}
182
183/// # Safety
184///
185/// `session` must be live, `user_data` must remain valid until FreeSWITCH closes the bug, and
186/// `bug` must be writable output storage.
187// SAFETY: The caller must provide a live session, owned user data, and writable bug output storage.
188unsafe fn add_media_bug<H>(
189    session: Session,
190    config: MediaBugConfig,
191    user_data: *mut c_void,
192    bug: &mut *mut sys::switch_media_bug_t,
193) -> sys::switch_status_t
194where
195    H: MediaBugHandler,
196{
197    let add = sys::switch_core_media_bug_add;
198    call_ffi!(add(
199        session.as_ptr(),
200        config.function.as_ptr(),
201        config.target.as_ptr(),
202        Some(media_bug_trampoline::<H>),
203        user_data,
204        config.stop_time,
205        config.flags.bits(),
206        bug,
207    ))
208}
209
210pub struct MediaBugContext<'a> {
211    raw: NonNull<sys::switch_media_bug_t>,
212    _lifetime: PhantomData<&'a mut sys::switch_media_bug_t>,
213}
214
215impl<'a> MediaBugContext<'a> {
216    /// Wraps a media bug pointer for the duration of a FreeSWITCH media bug callback.
217    ///
218    /// # Safety
219    ///
220    /// `raw` must point to a live media bug and must remain valid for `'a`.
221    pub unsafe fn from_raw(raw: *mut sys::switch_media_bug_t) -> Option<Self> {
222        NonNull::new(raw).map(|raw| Self {
223            raw,
224            _lifetime: PhantomData,
225        })
226    }
227
228    pub fn as_ptr(&self) -> *mut sys::switch_media_bug_t {
229        self.raw.as_ptr()
230    }
231
232    pub fn session(&self) -> Option<NonNull<sys::switch_core_session_t>> {
233        // SAFETY: `self.raw` is live for this callback.
234        NonNull::new(call_ffi!(sys::switch_core_media_bug_get_session(
235            self.raw.as_ptr()
236        )))
237    }
238
239    pub fn native_read_frame(&self) -> Option<MediaFrame<'_>> {
240        // SAFETY: `self.raw` is live for this callback.
241        call_ffi!(MediaFrame::from_raw(
242            sys::switch_core_media_bug_get_native_read_frame(self.raw.as_ptr())
243        ))
244    }
245
246    pub fn native_write_frame(&self) -> Option<MediaFrame<'_>> {
247        // SAFETY: `self.raw` is live for this callback.
248        call_ffi!(MediaFrame::from_raw(
249            sys::switch_core_media_bug_get_native_write_frame(self.raw.as_ptr())
250        ))
251    }
252
253    pub fn read_replace_frame(&mut self) -> Option<MediaFrameMut<'_>> {
254        // SAFETY: `self.raw` is live for this callback.
255        call_ffi!(MediaFrameMut::from_raw(
256            sys::switch_core_media_bug_get_read_replace_frame(self.raw.as_ptr())
257        ))
258    }
259
260    pub fn write_replace_frame(&mut self) -> Option<MediaFrameMut<'_>> {
261        // SAFETY: `self.raw` is live for this callback.
262        call_ffi!(MediaFrameMut::from_raw(
263            sys::switch_core_media_bug_get_write_replace_frame(self.raw.as_ptr())
264        ))
265    }
266
267    pub fn set_read_replace_frame(&mut self, frame: &mut MediaFrameMut<'_>) {
268        // SAFETY: Both pointers are live for this callback.
269        call_ffi!(sys::switch_core_media_bug_set_read_replace_frame(
270            self.raw.as_ptr(),
271            frame.as_ptr()
272        ));
273    }
274
275    pub fn set_write_replace_frame(&mut self, frame: &mut MediaFrameMut<'_>) {
276        // SAFETY: Both pointers are live for this callback.
277        call_ffi!(sys::switch_core_media_bug_set_write_replace_frame(
278            self.raw.as_ptr(),
279            frame.as_ptr()
280        ));
281    }
282
283    pub fn flush(&mut self) {
284        // SAFETY: `self.raw` is live for this callback.
285        call_ffi!(sys::switch_core_media_bug_flush(self.raw.as_ptr()));
286    }
287
288    pub fn read_into(&mut self, frame: &mut sys::switch_frame_t, fill: bool) -> Result<()> {
289        let fill = if fill {
290            sys::switch_bool_t_SWITCH_TRUE
291        } else {
292            sys::switch_bool_t_SWITCH_FALSE
293        };
294        // SAFETY: `self.raw` is live and `frame` is caller-provided writable frame storage.
295        let status = call_ffi!(sys::switch_core_media_bug_read(
296            self.raw.as_ptr(),
297            frame,
298            fill
299        ));
300        status_to_result(status)
301    }
302}
303
304#[derive(Copy, Clone)]
305pub struct MediaFrame<'a> {
306    raw: NonNull<sys::switch_frame_t>,
307    _lifetime: PhantomData<&'a sys::switch_frame_t>,
308}
309
310impl<'a> MediaFrame<'a> {
311    /// Wraps a frame pointer for the duration of a FreeSWITCH callback.
312    ///
313    /// # Safety
314    ///
315    /// `raw` must point to a live FreeSWITCH frame and must remain valid for `'a`.
316    pub unsafe fn from_raw(raw: *mut sys::switch_frame_t) -> Option<Self> {
317        NonNull::new(raw).map(|raw| Self {
318            raw,
319            _lifetime: PhantomData,
320        })
321    }
322
323    pub fn as_ptr(self) -> *mut sys::switch_frame_t {
324        self.raw.as_ptr()
325    }
326
327    pub fn data_len(self) -> usize {
328        // SAFETY: `self.raw` is live for this frame wrapper.
329        call_ffi!(self.raw.as_ref().datalen as usize)
330    }
331
332    pub fn samples(self) -> u32 {
333        // SAFETY: `self.raw` is live for this frame wrapper.
334        call_ffi!(self.raw.as_ref().samples)
335    }
336
337    pub fn rate(self) -> u32 {
338        // SAFETY: `self.raw` is live for this frame wrapper.
339        call_ffi!(self.raw.as_ref().rate)
340    }
341
342    pub fn channels(self) -> u32 {
343        // SAFETY: `self.raw` is live for this frame wrapper.
344        call_ffi!(self.raw.as_ref().channels)
345    }
346
347    pub fn bytes(self) -> &'a [u8] {
348        // SAFETY: `self.raw` is live and FreeSWITCH keeps `data` valid for `datalen` bytes during
349        // the callback. Null data with zero length is represented as an empty slice.
350        call_ffi!({
351            let frame = self.raw.as_ref();
352            if frame.data.is_null() || frame.datalen == 0 {
353                &[]
354            } else {
355                slice::from_raw_parts(frame.data.cast::<u8>(), frame.datalen as usize)
356            }
357        })
358    }
359
360    pub fn pcm_i16(self) -> Option<&'a [i16]> {
361        let bytes = self.bytes();
362        if !bytes.len().is_multiple_of(std::mem::size_of::<i16>())
363            || !(bytes.as_ptr() as usize).is_multiple_of(std::mem::align_of::<i16>())
364        {
365            return None;
366        }
367
368        // SAFETY: Length and alignment were checked above.
369        Some(call_ffi!(slice::from_raw_parts(
370            bytes.as_ptr().cast::<i16>(),
371            bytes.len() / size_of::<i16>()
372        )))
373    }
374}
375
376pub struct MediaFrameMut<'a> {
377    raw: NonNull<sys::switch_frame_t>,
378    _lifetime: PhantomData<&'a mut sys::switch_frame_t>,
379}
380
381impl<'a> MediaFrameMut<'a> {
382    /// Wraps a mutable frame pointer for the duration of a FreeSWITCH callback.
383    ///
384    /// # Safety
385    ///
386    /// `raw` must point to a live FreeSWITCH frame that is safe to mutate for `'a`.
387    pub unsafe fn from_raw(raw: *mut sys::switch_frame_t) -> Option<Self> {
388        NonNull::new(raw).map(|raw| Self {
389            raw,
390            _lifetime: PhantomData,
391        })
392    }
393
394    pub fn as_ptr(&mut self) -> *mut sys::switch_frame_t {
395        self.raw.as_ptr()
396    }
397
398    pub fn as_frame(&self) -> MediaFrame<'_> {
399        MediaFrame {
400            raw: self.raw,
401            _lifetime: PhantomData,
402        }
403    }
404
405    pub fn bytes_mut(&mut self) -> &mut [u8] {
406        // SAFETY: `self.raw` is live and uniquely borrowed through this mutable frame wrapper.
407        call_ffi!({
408            let frame = self.raw.as_ref();
409            if frame.data.is_null() || frame.datalen == 0 {
410                &mut []
411            } else {
412                slice::from_raw_parts_mut(frame.data.cast::<u8>(), frame.datalen as usize)
413            }
414        })
415    }
416
417    pub fn pcm_i16_mut(&mut self) -> Option<&mut [i16]> {
418        let bytes = self.bytes_mut();
419        if !bytes.len().is_multiple_of(std::mem::size_of::<i16>())
420            || !(bytes.as_ptr() as usize).is_multiple_of(std::mem::align_of::<i16>())
421        {
422            return None;
423        }
424
425        // SAFETY: Length and alignment were checked above, and `bytes` is uniquely borrowed.
426        Some(call_ffi!(slice::from_raw_parts_mut(
427            bytes.as_mut_ptr().cast::<i16>(),
428            bytes.len() / size_of::<i16>(),
429        )))
430    }
431}
432
433struct MediaBugState<H> {
434    handler: H,
435}
436
437/// # Safety
438///
439/// FreeSWITCH must call this with the `bug` and `user_data` pair supplied when the media bug was
440/// registered. `user_data` must be the boxed `MediaBugState<H>` allocated by `attach_media_bug`;
441/// FreeSWITCH must invoke CLOSE at most once for that pointer.
442// SAFETY: FreeSWITCH must pass the same bug/user_data pair registered by `attach_media_bug`.
443unsafe extern "C" fn media_bug_trampoline<H>(
444    bug: *mut sys::switch_media_bug_t,
445    user_data: *mut c_void,
446    callback_type: sys::switch_abc_type_t,
447) -> sys::switch_bool_t
448where
449    H: MediaBugHandler,
450{
451    if user_data.is_null() {
452        return sys::switch_bool_t_SWITCH_TRUE;
453    }
454
455    // SAFETY: FreeSWITCH passes a live media bug pointer for the callback duration.
456    let Some(mut ctx) = (call_ffi!(MediaBugContext::from_raw(bug))) else {
457        return sys::switch_bool_t_SWITCH_TRUE;
458    };
459
460    if callback_type == sys::switch_abc_type_t_SWITCH_ABC_TYPE_CLOSE {
461        return close_media_bug::<H>(user_data, &mut ctx);
462    }
463
464    // SAFETY: `user_data` is the `MediaBugState<H>` pointer passed to `switch_core_media_bug_add`.
465    let state = call_ffi!(&mut *user_data.cast::<MediaBugState<H>>());
466    let dispatch = MediaBugDispatch {
467        bug,
468        state,
469        ctx: &mut ctx,
470    };
471    let result = catch_unwind(AssertUnwindSafe(|| dispatch.run(callback_type)));
472
473    callback_result(result)
474}
475
476fn close_media_bug<H>(user_data: *mut c_void, ctx: &mut MediaBugContext<'_>) -> sys::switch_bool_t
477where
478    H: MediaBugHandler,
479{
480    // SAFETY: Close is the terminal callback for the pointer passed to FreeSWITCH.
481    let mut state = call_ffi!(Box::from_raw(user_data.cast::<MediaBugState<H>>()));
482    let result = catch_unwind(AssertUnwindSafe(|| {
483        state.handler.on_close(ctx);
484        MediaBugAction::Continue
485    }));
486    callback_result(result)
487}
488
489struct MediaBugDispatch<'a, H> {
490    bug: *mut sys::switch_media_bug_t,
491    state: &'a mut MediaBugState<H>,
492    ctx: &'a mut MediaBugContext<'a>,
493}
494
495impl<H> MediaBugDispatch<'_, H>
496where
497    H: MediaBugHandler,
498{
499    fn run(self, callback_type: sys::switch_abc_type_t) -> MediaBugAction {
500        match callback_type {
501            sys::switch_abc_type_t_SWITCH_ABC_TYPE_INIT => self.state.handler.on_init(self.ctx),
502            sys::switch_abc_type_t_SWITCH_ABC_TYPE_READ => self.read(),
503            sys::switch_abc_type_t_SWITCH_ABC_TYPE_WRITE => self.write(),
504            sys::switch_abc_type_t_SWITCH_ABC_TYPE_READ_REPLACE => self.read_replace(),
505            sys::switch_abc_type_t_SWITCH_ABC_TYPE_WRITE_REPLACE => self.write_replace(),
506            _ => MediaBugAction::Continue,
507        }
508    }
509
510    fn read(self) -> MediaBugAction {
511        // SAFETY: `bug` is live for the callback duration.
512        let frame = call_ffi!(MediaFrame::from_raw(
513            sys::switch_core_media_bug_get_native_read_frame(self.bug)
514        ));
515        frame.map_or(MediaBugAction::Continue, |frame| {
516            self.state.handler.on_read(self.ctx, frame)
517        })
518    }
519
520    fn write(self) -> MediaBugAction {
521        // SAFETY: `bug` is live for the callback duration.
522        let frame = call_ffi!(MediaFrame::from_raw(
523            sys::switch_core_media_bug_get_native_write_frame(self.bug)
524        ));
525        frame.map_or(MediaBugAction::Continue, |frame| {
526            self.state.handler.on_write(self.ctx, frame)
527        })
528    }
529
530    fn read_replace(self) -> MediaBugAction {
531        // SAFETY: `bug` is live for the callback duration.
532        let frame = call_ffi!(MediaFrameMut::from_raw(
533            sys::switch_core_media_bug_get_read_replace_frame(self.bug)
534        ));
535        frame.map_or(MediaBugAction::Continue, |frame| {
536            self.state.handler.on_read_replace(self.ctx, frame)
537        })
538    }
539
540    fn write_replace(self) -> MediaBugAction {
541        // SAFETY: `bug` is live for the callback duration.
542        let frame = call_ffi!(MediaFrameMut::from_raw(
543            sys::switch_core_media_bug_get_write_replace_frame(self.bug)
544        ));
545        frame.map_or(MediaBugAction::Continue, |frame| {
546            self.state.handler.on_write_replace(self.ctx, frame)
547        })
548    }
549}
550
551fn callback_result(result: std::thread::Result<MediaBugAction>) -> sys::switch_bool_t {
552    match result {
553        Ok(action) => action.as_switch_bool(),
554        Err(_) => {
555            log_error("media_bug", "media bug callback panicked");
556            sys::switch_bool_t_SWITCH_FALSE
557        }
558    }
559}