ndi_sdk_sys/
receiver.rs

1//! NDI Receiver
2//!
3//! <https://docs.ndi.video/all/developing-with-ndi/sdk/ndi-recv>
4
5use std::{
6    ffi::{CStr, CString},
7    fmt::Debug,
8    ptr::NonNull,
9    sync::Arc,
10    time::Duration,
11};
12
13use static_assertions::assert_impl_all;
14
15use crate::{
16    bindings::{self},
17    enums::{NDIBandwidthMode, NDIPreferredColorFormat, NDIRecvError},
18    frame::{
19        audio::AudioFrame,
20        generic::{AsFFIReadable, AsFFIWritable},
21        metadata::MetadataFrame,
22        video::VideoFrame,
23    },
24    source::NDISourceLike,
25    tally::Tally,
26    util::duration_to_ms,
27};
28
29pub use crate::enums::NDIRecvType;
30
31/// Builder for [NDIReceiver]
32#[non_exhaustive]
33#[derive(Debug, Clone)]
34pub struct NDIReceiverBuilder<Source: NDISourceLike> {
35    pub source: Option<Source>,
36    pub name: Option<CString>,
37    pub color_format: NDIPreferredColorFormat,
38    pub bandwidth: NDIBandwidthMode,
39    pub allow_fielded_video: bool,
40}
41
42impl<Source: NDISourceLike> Default for NDIReceiverBuilder<Source> {
43    fn default() -> Self {
44        Self {
45            source: None,
46            name: None,
47            color_format: NDIPreferredColorFormat::default(),
48            bandwidth: NDIBandwidthMode::default(),
49            allow_fielded_video: false,
50        }
51    }
52}
53
54impl<Source: NDISourceLike> NDIReceiverBuilder<Source> {
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Sets the source to connect to. Can be left out to select the source later.
60    pub fn source(mut self, source: Source) -> Self {
61        self.source = Some(source);
62        self
63    }
64
65    /// Sets the receiver name. This will be used in future versions of NDI.
66    pub fn name(mut self, name: &str) -> Self {
67        self.name = Some(CString::new(name).unwrap());
68        self
69    }
70
71    /// Sets the preferred color format for the receiver.
72    /// Unless you need a specific color format, always use [NDIPreferredColorFormat::Fastest]
73    pub fn color_format(mut self, color_format: NDIPreferredColorFormat) -> Self {
74        self.color_format = color_format;
75        self
76    }
77
78    /// Sets the bandwidth mode for the receiver.
79    /// Can be used to use reduced bandwidth for multi-views or metadata-only
80    pub fn bandwidth(mut self, bandwidth: NDIBandwidthMode) -> Self {
81        self.bandwidth = bandwidth;
82        self
83    }
84
85    /// Sets whether the receiver should allow fielded video.
86    /// Default is false, only progressive video will be received.
87    pub fn allow_fielded_video(mut self, allow: bool) -> Self {
88        self.allow_fielded_video = allow;
89        self
90    }
91
92    pub fn build(self) -> Result<NDIReceiver, NDIReceiverBuilderError> {
93        self.source.with_descriptor(|src_ptr| {
94            let options = bindings::NDIlib_recv_create_v3_t {
95                p_ndi_recv_name: self
96                    .name
97                    .as_ref()
98                    .map(|s| s.as_ptr())
99                    .unwrap_or(std::ptr::null()),
100
101                source_to_connect_to: if src_ptr.is_null() {
102                    bindings::NDIlib_source_t {
103                        p_ndi_name: std::ptr::null(),
104                        __bindgen_anon_1: bindings::NDIlib_source_t__bindgen_ty_1 {
105                            p_url_address: std::ptr::null(),
106                        },
107                    }
108                } else {
109                    unsafe { *src_ptr }
110                },
111                color_format: self.color_format.to_ffi(),
112                bandwidth: self.bandwidth.to_ffi(),
113                allow_video_fields: self.allow_fielded_video,
114            };
115
116            let handle = unsafe { bindings::NDIlib_recv_create_v3(&options) };
117
118            if let Some(handle) = NonNull::new(handle) {
119                Ok(NDIReceiver {
120                    handle: Arc::new(RawReceiver { handle }),
121                })
122            } else {
123                Err(NDIReceiverBuilderError::CreationFailed)
124            }
125        })
126    }
127}
128
129#[non_exhaustive]
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum NDIReceiverBuilderError {
132    CreationFailed,
133}
134
135#[derive(PartialEq, Eq)]
136pub(crate) struct RawReceiver {
137    handle: NonNull<bindings::NDIlib_recv_instance_type>,
138}
139
140impl RawReceiver {
141    pub(crate) fn raw_ptr(&self) -> bindings::NDIlib_recv_instance_t {
142        self.handle.as_ptr()
143    }
144}
145
146impl Drop for RawReceiver {
147    fn drop(&mut self) {
148        unsafe { bindings::NDIlib_recv_destroy(self.raw_ptr()) };
149    }
150}
151
152impl Debug for RawReceiver {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.debug_struct("RawReceiver")
155            .field("raw_ptr", &self.raw_ptr())
156            .finish()
157    }
158}
159
160unsafe impl Send for RawReceiver {}
161unsafe impl Sync for RawReceiver {}
162
163/// A NDI receiver that can receive frames from a source.
164///
165/// Please note that the receiver handle (from the SDK) will not be dropped until all
166/// frames that were received from it are dropped or have their buffers deallocated.[^note]
167///
168/// [^note]: The inner receiver is [Arc]ed because all frames received need to be dropped on the receiver handle and therefore need a valid reference to it
169pub struct NDIReceiver {
170    handle: Arc<RawReceiver>,
171}
172
173assert_impl_all!(NDIReceiver: Send, Sync);
174
175impl NDIReceiver {
176    /// Switches the receiver to the given source.
177    pub fn set_source(&self, source: &impl NDISourceLike) {
178        source.with_descriptor(|src_ptr| {
179            unsafe { bindings::NDIlib_recv_connect(self.handle.raw_ptr(), src_ptr) };
180        });
181    }
182
183    /// Tries to read into the given buffers and returns which of these has been written to.
184    pub fn recv(
185        &self,
186        mut video: Option<&mut VideoFrame>,
187        mut audio: Option<&mut AudioFrame>,
188        mut meta: Option<&mut MetadataFrame>,
189        timeout: Duration,
190    ) -> Result<NDIRecvType, NDIRecvError> {
191        let video_ptr = video.to_ffi_recv_frame_ptr();
192        if video_ptr.is_null() {
193            video = None; // make sure NullPtr's are consistent with the option
194        }
195
196        let audio_ptr = audio.to_ffi_recv_frame_ptr();
197        if audio_ptr.is_null() {
198            audio = None; // make sure NullPtr's are consistent with the option
199        }
200
201        let meta_ptr = meta.to_ffi_recv_frame_ptr();
202        if meta_ptr.is_null() {
203            meta = None; // make sure NullPtr's are consistent with the option
204        }
205
206        let timeout: u32 = duration_to_ms(timeout);
207
208        let recv_type = unsafe {
209            bindings::NDIlib_recv_capture_v3(
210                self.handle.raw_ptr(),
211                video_ptr,
212                audio_ptr,
213                meta_ptr,
214                timeout,
215            )
216        };
217
218        match recv_type {
219            bindings::NDIlib_frame_type_e_NDIlib_frame_type_video => {
220                video
221                    .expect(
222                        "[Fatal FFI Error] SDK indicated that a video frame was received, but there is no buffer it could have been written to",
223                    )
224                    .alloc.update_from_receiver(self.handle.clone());
225
226                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
227                {
228                    if let Some(audio) = audio {
229                        audio.assert_unwritten();
230                    }
231                    if let Some(meta) = meta {
232                        meta.assert_unwritten();
233                    }
234                }
235
236                Ok(NDIRecvType::Video)
237            }
238            bindings::NDIlib_frame_type_e_NDIlib_frame_type_audio => {
239                audio
240                    .expect(
241                        "[Fatal FFI Error] SDK indicated that an audio frame was received, but there is no buffer it could have been written to",
242                    )
243                    .alloc.update_from_receiver(self.handle.clone());
244
245                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
246                {
247                    if let Some(video) = video {
248                        video.assert_unwritten();
249                    }
250                    if let Some(meta) = meta {
251                        meta.assert_unwritten();
252                    }
253                }
254
255                Ok(NDIRecvType::Audio)
256            }
257            bindings::NDIlib_frame_type_e_NDIlib_frame_type_metadata => {
258                meta.expect(
259                    "[Fatal FFI Error] SDK indicated that a metadata frame was received, but there is no buffer it could have been written to",
260                )
261                .alloc.update_from_receiver(self.handle.clone());
262
263                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
264                {
265                    if let Some(video) = video {
266                        video.assert_unwritten();
267                    }
268                    if let Some(audio) = audio {
269                        audio.assert_unwritten();
270                    }
271                }
272
273                Ok(NDIRecvType::Metadata)
274            }
275            bindings::NDIlib_frame_type_e_NDIlib_frame_type_status_change => {
276                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
277                {
278                    if let Some(video) = video {
279                        video.assert_unwritten();
280                    }
281                    if let Some(audio) = audio {
282                        audio.assert_unwritten();
283                    }
284                    if let Some(meta) = meta {
285                        meta.assert_unwritten();
286                    }
287                }
288
289                Ok(NDIRecvType::StatusChange)
290            }
291            bindings::NDIlib_frame_type_e_NDIlib_frame_type_source_change => {
292                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
293                {
294                    if let Some(video) = video {
295                        video.assert_unwritten();
296                    }
297                    if let Some(audio) = audio {
298                        audio.assert_unwritten();
299                    }
300                    if let Some(meta) = meta {
301                        meta.assert_unwritten();
302                    }
303                }
304
305                Ok(NDIRecvType::SourceChange)
306            }
307            bindings::NDIlib_frame_type_e_NDIlib_frame_type_none => {
308                #[cfg(any(debug_assertions, feature = "strict_assertions"))]
309                {
310                    if let Some(video) = video {
311                        video.assert_unwritten();
312                    }
313                    if let Some(audio) = audio {
314                        audio.assert_unwritten();
315                    }
316                    if let Some(meta) = meta {
317                        meta.assert_unwritten();
318                    }
319                }
320
321                Ok(NDIRecvType::None)
322            }
323            #[cfg(any(debug_assertions, feature = "strict_assertions"))]
324            discriminant => {
325                eprintln!("NDI SDK returned an unknown frame type: {:?}", discriminant);
326
327                if let Some(video) = video {
328                    video.assert_unwritten();
329                }
330                if let Some(audio) = audio {
331                    audio.assert_unwritten();
332                }
333                if let Some(meta) = meta {
334                    meta.assert_unwritten();
335                }
336
337                Err(NDIRecvError::UnknownType)
338            }
339            #[cfg(not(any(debug_assertions, feature = "strict_assertions")))]
340            _ => NDIRecvType::Unknown,
341        }
342    }
343
344    unsafe fn free_string(&self, ptr: *const std::os::raw::c_char) {
345        if !ptr.is_null() {
346            unsafe { bindings::NDIlib_recv_free_string(self.handle.raw_ptr(), ptr) };
347        }
348    }
349
350    /// Sends a metadata frame over the current connection.
351    pub fn send_metadata(&self, frame: &MetadataFrame) -> Result<(), SendMetadataError> {
352        let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
353            crate::frame::generic::FFIReadablePtrError::NotReadable(desc) => {
354                SendMetadataError::NotSendable(desc)
355            }
356        })?;
357
358        let result = unsafe { bindings::NDIlib_recv_send_metadata(self.handle.raw_ptr(), ptr) };
359
360        if result {
361            Ok(())
362        } else {
363            Err(SendMetadataError::NotConnected)
364        }
365    }
366
367    /// Adds connection metadata which will be sent to every connection in the future.
368    pub fn add_connection_metadata(&self, frame: &MetadataFrame) -> Result<(), SendMetadataError> {
369        let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
370            crate::frame::generic::FFIReadablePtrError::NotReadable(desc) => {
371                SendMetadataError::NotSendable(desc)
372            }
373        })?;
374
375        unsafe { bindings::NDIlib_recv_add_connection_metadata(self.handle.raw_ptr(), ptr) };
376
377        Ok(())
378    }
379
380    /// Removes all connection metadata that was previously added.
381    pub fn clear_connection_metadata(&self) {
382        unsafe { bindings::NDIlib_recv_clear_connection_metadata(self.handle.raw_ptr()) };
383    }
384
385    /// Sets the tally status for the receiver. This will be merged from all receivers on the same
386    /// source.
387    pub fn set_tally(&self, tally: Tally) {
388        let tally = tally.to_ffi();
389
390        unsafe { bindings::NDIlib_recv_set_tally(self.handle.raw_ptr(), &tally) };
391    }
392
393    /// Returns the current number of connections to the receiver.
394    pub fn get_num_connections(&self) -> usize {
395        let num_connections =
396            unsafe { bindings::NDIlib_recv_get_no_connections(self.handle.raw_ptr()) };
397        num_connections
398            .try_into()
399            .expect("[Fatal FFI Error] NDI SDK returned a invalid number of connections")
400    }
401
402    /// Get the web control URL for the receiver.
403    pub fn get_web_control(&self) -> Option<NDIWebControlInfo<'_>> {
404        let ptr = unsafe { bindings::NDIlib_recv_get_web_control(self.handle.raw_ptr()) };
405
406        if ptr.is_null() {
407            None?;
408        }
409
410        let str = unsafe { CStr::from_ptr(ptr) };
411        Some(NDIWebControlInfo {
412            url: str,
413            recv: self,
414        })
415    }
416}
417
418#[non_exhaustive]
419#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
420pub enum SendMetadataError {
421    /// The metadata could not be sent because it is not readable
422    NotSendable(&'static str),
423    /// The metadata could not be sent because the receiver is not connected
424    NotConnected,
425}
426
427pub struct NDIWebControlInfo<'a> {
428    url: &'a CStr,
429    recv: &'a NDIReceiver,
430}
431
432impl<'a> Drop for NDIWebControlInfo<'a> {
433    fn drop(&mut self) {
434        unsafe { self.recv.free_string(self.url.as_ptr()) };
435    }
436}
437
438impl<'a> NDIWebControlInfo<'a> {
439    pub fn as_cstr(&self) -> &'a CStr {
440        self.url
441    }
442}