1use 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#[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 pub fn source(mut self, source: Source) -> Self {
61 self.source = Some(source);
62 self
63 }
64
65 pub fn name(mut self, name: &str) -> Self {
67 self.name = Some(CString::new(name).unwrap());
68 self
69 }
70
71 pub fn color_format(mut self, color_format: NDIPreferredColorFormat) -> Self {
74 self.color_format = color_format;
75 self
76 }
77
78 pub fn bandwidth(mut self, bandwidth: NDIBandwidthMode) -> Self {
81 self.bandwidth = bandwidth;
82 self
83 }
84
85 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
163pub struct NDIReceiver {
170 handle: Arc<RawReceiver>,
171}
172
173assert_impl_all!(NDIReceiver: Send, Sync);
174
175impl NDIReceiver {
176 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 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; }
195
196 let audio_ptr = audio.to_ffi_recv_frame_ptr();
197 if audio_ptr.is_null() {
198 audio = None; }
200
201 let meta_ptr = meta.to_ffi_recv_frame_ptr();
202 if meta_ptr.is_null() {
203 meta = None; }
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 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 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 pub fn clear_connection_metadata(&self) {
382 unsafe { bindings::NDIlib_recv_clear_connection_metadata(self.handle.raw_ptr()) };
383 }
384
385 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 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 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 NotSendable(&'static str),
423 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}