moq/
api.rs

1use crate::{ffi, Error, State};
2
3use std::ffi::c_char;
4use std::ffi::c_void;
5use std::str::FromStr;
6
7use tracing::Level;
8
9/// Information about a video rendition in the catalog.
10#[repr(C)]
11#[allow(non_camel_case_types)]
12pub struct moq_video_config {
13	/// The name of the track, NOT NULL terminated.
14	pub name: *const c_char,
15	pub name_len: usize,
16
17	/// The codec of the track, NOT NULL terminated
18	pub codec: *const c_char,
19	pub codec_len: usize,
20
21	/// The description of the track, or NULL if not used.
22	/// This is codec specific, for example H264:
23	///   - NULL: annex.b encoded
24	///   - Non-NULL: AVCC encoded
25	pub description: *const u8,
26	pub description_len: usize,
27
28	/// The encoded width/height of the media, or NULL if not available
29	pub coded_width: *const u32,
30	pub coded_height: *const u32,
31}
32
33/// Information about an audio rendition in the catalog.
34#[repr(C)]
35#[allow(non_camel_case_types)]
36pub struct moq_audio_config {
37	/// The name of the track, NOT NULL terminated
38	pub name: *const c_char,
39	pub name_len: usize,
40
41	/// The codec of the track, NOT NULL terminated
42	pub codec: *const c_char,
43	pub codec_len: usize,
44
45	/// The description of the track, or NULL if not used.
46	pub description: *const u8,
47	pub description_len: usize,
48
49	/// The sample rate of the track in Hz
50	pub sample_rate: u32,
51
52	/// The number of channels in the track
53	pub channel_count: u32,
54}
55
56/// Information about a frame of media.
57#[repr(C)]
58#[allow(non_camel_case_types)]
59pub struct moq_frame {
60	/// The payload of the frame, or NULL/0 if the stream has ended
61	pub payload: *const u8,
62	pub payload_size: usize,
63
64	/// The presentation timestamp of the frame in microseconds
65	pub timestamp_us: u64,
66
67	/// Whether the frame is a keyframe, aka the start of a new group.
68	pub keyframe: bool,
69}
70
71/// Information about a broadcast announced by an origin.
72#[repr(C)]
73#[allow(non_camel_case_types)]
74pub struct moq_announced {
75	/// The path of the broadcast, NOT NULL terminated
76	pub path: *const c_char,
77	pub path_len: usize,
78
79	/// Whether the broadcast is active or has ended
80	/// This MUST toggle between true and false over the lifetime of the broadcast
81	pub active: bool,
82}
83
84/// Initialize the library with a log level.
85///
86/// This should be called before any other functions.
87/// The log_level is a string: "error", "warn", "info", "debug", "trace"
88///
89/// Returns a zero on success, or a negative code on failure.
90///
91/// # Safety
92/// - The caller must ensure that level is a valid pointer to level_len bytes of data.
93#[no_mangle]
94pub unsafe extern "C" fn moq_log_level(level: *const c_char, level_len: usize) -> i32 {
95	ffi::enter(move || {
96		match unsafe { ffi::parse_str(level, level_len)? } {
97			"" => moq_native::Log::default(),
98			level => moq_native::Log {
99				level: Level::from_str(level)?,
100			},
101		}
102		.init();
103
104		Ok(())
105	})
106}
107
108/// Start establishing a connection to a MoQ server.
109///
110/// Takes origin handles, which are used for publishing and consuming broadcasts respectively.
111/// - Any broadcasts in `origin_publish` will be announced to the server.
112/// - Any broadcasts announced by the server will be available in `origin_consume`.
113/// - If an origin handle is 0, that functionality is completely disabled.
114///
115/// This may be called multiple times to connect to different servers.
116/// Origins can be shared across sessions, useful for fanout or relaying.
117///
118/// Returns a non-zero handle to the session on success, or a negative code on (immediate) failure.
119/// You should call [moq_session_close], even on error, to free up resources.
120///
121/// The callback is called on success (status 0) and later when closed (status non-zero).
122///
123/// # Safety
124/// - The caller must ensure that url is a valid pointer to url_len bytes of data.
125/// - The caller must ensure that `on_status` is valid until [moq_session_close] is called.
126#[no_mangle]
127pub unsafe extern "C" fn moq_session_connect(
128	url: *const c_char,
129	url_len: usize,
130	origin_publish: u32,
131	origin_consume: u32,
132	on_status: Option<extern "C" fn(user_data: *mut c_void, code: i32)>,
133	user_data: *mut c_void,
134) -> i32 {
135	ffi::enter(move || {
136		let url = ffi::parse_url(url, url_len)?;
137
138		let mut state = State::lock();
139		let publish = ffi::parse_id_optional(origin_publish)?
140			.map(|id| state.origin.get(id))
141			.transpose()?
142			.map(|origin: &moq_lite::OriginProducer| origin.consume());
143		let consume = ffi::parse_id_optional(origin_consume)?
144			.map(|id| state.origin.get(id))
145			.transpose()?
146			.cloned();
147
148		let on_status = ffi::OnStatus::new(user_data, on_status);
149		state.session.connect(url, publish, consume, on_status)
150	})
151}
152
153/// Close a connection to a MoQ server.
154///
155/// Returns a zero on success, or a negative code on failure.
156///
157/// The [moq_session_connect] `on_status` callback will be called with [Error::Closed].
158#[no_mangle]
159pub extern "C" fn moq_session_close(session: u32) -> i32 {
160	ffi::enter(move || {
161		let session = ffi::parse_id(session)?;
162		State::lock().session.close(session)
163	})
164}
165
166/// Create an origin for publishing broadcasts.
167///
168/// Origins contain any number of broadcasts addressed by path.
169/// The same broadcast can be published to multiple origins under different paths.
170///
171/// [moq_origin_announced] can be used to discover broadcasts published to this origin.
172/// This is extremely useful for discovering what is available on the server to [moq_origin_consume].
173///
174/// Returns a non-zero handle to the origin on success.
175#[no_mangle]
176pub extern "C" fn moq_origin_create() -> i32 {
177	ffi::enter(move || State::lock().origin.create())
178}
179
180/// Publish a broadcast to an origin.
181///
182/// The broadcast will be announced to any origin consumers, such as over the network.
183///
184/// Returns a zero on success, or a negative code on failure.
185///
186/// # Safety
187/// - The caller must ensure that path is a valid pointer to path_len bytes of data.
188#[no_mangle]
189pub unsafe extern "C" fn moq_origin_publish(origin: u32, path: *const c_char, path_len: usize, broadcast: u32) -> i32 {
190	ffi::enter(move || {
191		let origin = ffi::parse_id(origin)?;
192		let path = unsafe { ffi::parse_str(path, path_len)? };
193		let broadcast = ffi::parse_id(broadcast)?;
194
195		let mut state = State::lock();
196		let broadcast = state.publish.get(broadcast)?.consume();
197		state.origin.publish(origin, path, broadcast)
198	})
199}
200
201/// Learn about all broadcasts published to an origin.
202///
203/// The callback is called with an announced ID when a new broadcast is published.
204///
205/// - [moq_origin_announced_info] is used to query information about the broadcast.
206/// - [moq_origin_announced_close] is used to stop receiving announcements.
207///
208/// Returns a non-zero handle on success, or a negative code on failure.
209///
210/// # Safety
211/// - The caller must ensure that `on_announce` is valid until [moq_origin_announced_close] is called.
212#[no_mangle]
213pub unsafe extern "C" fn moq_origin_announced(
214	origin: u32,
215	on_announce: Option<extern "C" fn(user_data: *mut c_void, announced: i32)>,
216	user_data: *mut c_void,
217) -> i32 {
218	ffi::enter(move || {
219		let origin = ffi::parse_id(origin)?;
220		let on_announce = ffi::OnStatus::new(user_data, on_announce);
221		State::lock().origin.announced(origin, on_announce)
222	})
223}
224
225/// Query information about a broadcast discovered by [moq_origin_announced].
226///
227/// The destination is filled with the broadcast information.
228///
229/// Returns a zero on success, or a negative code on failure.
230///
231/// # Safety
232/// - The caller must ensure that `dst` is a valid pointer to a [moq_announced] struct.
233#[no_mangle]
234pub unsafe extern "C" fn moq_origin_announced_info(announced: u32, dst: *mut moq_announced) -> i32 {
235	ffi::enter(move || {
236		let announced = ffi::parse_id(announced)?;
237		let dst = dst.as_mut().ok_or(Error::InvalidPointer)?;
238		State::lock().origin.announced_info(announced, dst)
239	})
240}
241
242/// Stop receiving announcements for broadcasts published to an origin.
243///
244/// Returns a zero on success, or a negative code on failure.
245#[no_mangle]
246pub extern "C" fn moq_origin_announced_close(announced: u32) -> i32 {
247	ffi::enter(move || {
248		let announced = ffi::parse_id(announced)?;
249		State::lock().origin.announced_close(announced)
250	})
251}
252
253/// Consume a broadcast from an origin by path.
254///
255/// Returns a non-zero handle to the broadcast on success, or a negative code on failure.
256///
257/// # Safety
258/// - The caller must ensure that path is a valid pointer to path_len bytes of data.
259#[no_mangle]
260pub unsafe extern "C" fn moq_origin_consume(origin: u32, path: *const c_char, path_len: usize) -> i32 {
261	ffi::enter(move || {
262		let origin = ffi::parse_id(origin)?;
263		let path = unsafe { ffi::parse_str(path, path_len)? };
264
265		let mut state = State::lock();
266		let broadcast = state.origin.consume(origin, path)?;
267		Ok(state.consume.start(broadcast.into()))
268	})
269}
270
271/// Close an origin and clean up its resources.
272///
273/// Returns a zero on success, or a negative code on failure.
274#[no_mangle]
275pub extern "C" fn moq_origin_close(origin: u32) -> i32 {
276	ffi::enter(move || {
277		let origin = ffi::parse_id(origin)?;
278		State::lock().origin.close(origin)
279	})
280}
281
282/// Create a new broadcast for publishing media tracks.
283///
284/// Returns a non-zero handle to the broadcast on success, or a negative code on failure.
285#[no_mangle]
286pub extern "C" fn moq_publish_create() -> i32 {
287	ffi::enter(move || State::lock().publish.create())
288}
289
290/// Close a broadcast and clean up its resources.
291///
292/// Returns a zero on success, or a negative code on failure.
293#[no_mangle]
294pub extern "C" fn moq_publish_close(broadcast: u32) -> i32 {
295	ffi::enter(move || {
296		let broadcast = ffi::parse_id(broadcast)?;
297		State::lock().publish.close(broadcast)
298	})
299}
300
301/// Create a new media track for a broadcast
302///
303/// All frames in [moq_publish_media_frame] must be written in decode order.
304/// The `format` controls the encoding, both of `init` and frame payloads.
305///
306/// Returns a non-zero handle to the track on success, or a negative code on failure.
307///
308/// # Safety
309/// - The caller must ensure that format is a valid pointer to format_len bytes of data.
310/// - The caller must ensure that init is a valid pointer to init_size bytes of data.
311#[no_mangle]
312pub unsafe extern "C" fn moq_publish_media_ordered(
313	broadcast: u32,
314	format: *const c_char,
315	format_len: usize,
316	init: *const u8,
317	init_size: usize,
318) -> i32 {
319	ffi::enter(move || {
320		let broadcast = ffi::parse_id(broadcast)?;
321		let format = unsafe { ffi::parse_str(format, format_len)? };
322		let init = unsafe { ffi::parse_slice(init, init_size)? };
323
324		State::lock().publish.media_ordered(broadcast, format, init)
325	})
326}
327
328/// Remove a track from a broadcast.
329///
330/// Returns a zero on success, or a negative code on failure.
331#[no_mangle]
332pub extern "C" fn moq_publish_media_close(export: u32) -> i32 {
333	ffi::enter(move || {
334		let export = ffi::parse_id(export)?;
335		State::lock().publish.media_close(export)
336	})
337}
338
339/// Write data to a track.
340///
341/// The encoding of `data` depends on the track `format`.
342/// The timestamp is in microseconds.
343///
344/// Returns a zero on success, or a negative code on failure.
345///
346/// # Safety
347/// - The caller must ensure that payload is a valid pointer to payload_size bytes of data.
348#[no_mangle]
349pub unsafe extern "C" fn moq_publish_media_frame(
350	media: u32,
351	payload: *const u8,
352	payload_size: usize,
353	timestamp_us: u64,
354) -> i32 {
355	ffi::enter(move || {
356		let media = ffi::parse_id(media)?;
357		let payload = unsafe { ffi::parse_slice(payload, payload_size)? };
358		let timestamp = hang::Timestamp::from_micros(timestamp_us)?;
359		State::lock().publish.media_frame(media, payload, timestamp)
360	})
361}
362
363/// Create a catalog consumer for a broadcast.
364///
365/// The callback is called with a catalog ID when a new catalog is available.
366/// The catalog ID can be used to query video/audio track information.
367///
368/// Returns a non-zero handle on success, or a negative code on failure.
369///
370/// # Safety
371/// - The caller must ensure that `on_catalog` is valid until [moq_consume_catalog_close] is called.
372#[no_mangle]
373pub unsafe extern "C" fn moq_consume_catalog(
374	broadcast: u32,
375	on_catalog: Option<extern "C" fn(user_data: *mut c_void, catalog: i32)>,
376	user_data: *mut c_void,
377) -> i32 {
378	ffi::enter(move || {
379		let broadcast = ffi::parse_id(broadcast)?;
380		let on_catalog = ffi::OnStatus::new(user_data, on_catalog);
381		State::lock().consume.catalog(broadcast, on_catalog)
382	})
383}
384
385/// Close a catalog consumer and clean up its resources.
386///
387/// Returns a zero on success, or a negative code on failure.
388#[no_mangle]
389pub extern "C" fn moq_consume_catalog_close(catalog: u32) -> i32 {
390	ffi::enter(move || {
391		let catalog = ffi::parse_id(catalog)?;
392		State::lock().consume.catalog_close(catalog)
393	})
394}
395
396/// Query information about a video track in a catalog.
397///
398/// The destination is filled with the video track information.
399///
400/// Returns a zero on success, or a negative code on failure.
401///
402/// # Safety
403/// - The caller must ensure that `dst` is a valid pointer to a [moq_video_config] struct.
404/// - The caller must ensure that `dst` is not used after [moq_consume_catalog_close] is called.
405#[no_mangle]
406pub unsafe extern "C" fn moq_consume_video_config(catalog: u32, index: u32, dst: *mut moq_video_config) -> i32 {
407	ffi::enter(move || {
408		let catalog = ffi::parse_id(catalog)?;
409		let index = index as usize;
410		let dst = dst.as_mut().ok_or(Error::InvalidPointer)?;
411		State::lock().consume.video_config(catalog, index, dst)
412	})
413}
414
415/// Query information about an audio track in a catalog.
416///
417/// The destination is filled with the audio track information.
418///
419/// Returns a zero on success, or a negative code on failure.
420///
421/// # Safety
422/// - The caller must ensure that `dst` is a valid pointer to a [moq_audio_config] struct.
423/// - The caller must ensure that `dst` is not used after [moq_consume_catalog_close] is called.
424#[no_mangle]
425pub unsafe extern "C" fn moq_consume_audio_config(catalog: u32, index: u32, dst: *mut moq_audio_config) -> i32 {
426	ffi::enter(move || {
427		let catalog = ffi::parse_id(catalog)?;
428		let index = index as usize;
429		let dst = dst.as_mut().ok_or(Error::InvalidPointer)?;
430		State::lock().consume.audio_config(catalog, index, dst)
431	})
432}
433
434/// Consume a video track from a broadcast, delivering frames in order.
435///
436/// - `max_latency_ms` controls the maximum amount of buffering allowed before skipping a GoP.
437/// - `on_frame` is called with a frame ID when a new frame is available.
438///
439/// Returns a non-zero handle to the track on success, or a negative code on failure.
440///
441/// # Safety
442/// - The caller must ensure that `on_frame` is valid until [moq_consume_video_track_close] is called.
443#[no_mangle]
444pub unsafe extern "C" fn moq_consume_video_ordered(
445	broadcast: u32,
446	index: u32,
447	max_latency_ms: u64,
448	on_frame: Option<extern "C" fn(user_data: *mut c_void, frame: i32)>,
449	user_data: *mut c_void,
450) -> i32 {
451	ffi::enter(move || {
452		let broadcast = ffi::parse_id(broadcast)?;
453		let index = index as usize;
454		let max_latency = std::time::Duration::from_millis(max_latency_ms);
455		let on_frame = ffi::OnStatus::new(user_data, on_frame);
456		State::lock()
457			.consume
458			.video_ordered(broadcast, index, max_latency, on_frame)
459	})
460}
461
462/// Close a video track consumer and clean up its resources.
463///
464/// Returns a zero on success, or a negative code on failure.
465#[no_mangle]
466pub extern "C" fn moq_consume_video_close(track: u32) -> i32 {
467	ffi::enter(move || {
468		let track = ffi::parse_id(track)?;
469		State::lock().consume.video_close(track)
470	})
471}
472
473/// Consume an audio track from a broadcast, emitting the frames in order.
474///
475/// The callback is called with a frame ID when a new frame is available.
476/// The `max_latency_ms` parameter controls how long to wait before skipping frames.
477///
478/// Returns a non-zero handle to the track on success, or a negative code on failure.
479///
480/// # Safety
481/// - The caller must ensure that `on_frame` is valid until [moq_consume_audio_close] is called.
482#[no_mangle]
483pub unsafe extern "C" fn moq_consume_audio_ordered(
484	broadcast: u32,
485	index: u32,
486	max_latency_ms: u64,
487	on_frame: Option<extern "C" fn(user_data: *mut c_void, frame: i32)>,
488	user_data: *mut c_void,
489) -> i32 {
490	ffi::enter(move || {
491		let broadcast = ffi::parse_id(broadcast)?;
492		let index = index as usize;
493		let max_latency = std::time::Duration::from_millis(max_latency_ms);
494		let on_frame = ffi::OnStatus::new(user_data, on_frame);
495		State::lock()
496			.consume
497			.audio_ordered(broadcast, index, max_latency, on_frame)
498	})
499}
500
501/// Close an audio track consumer and clean up its resources.
502///
503/// Returns a zero on success, or a negative code on failure.
504#[no_mangle]
505pub extern "C" fn moq_consume_audio_close(track: u32) -> i32 {
506	ffi::enter(move || {
507		let track = ffi::parse_id(track)?;
508		State::lock().consume.audio_close(track)
509	})
510}
511
512/// Get a chunk of a frame's payload.
513///
514/// Frames may be split into multiple chunks. Call this multiple times with increasing
515/// index values to get all chunks. The destination is filled with the frame chunk information.
516///
517/// Returns a zero on success, or a negative code on failure.
518///
519/// # Safety
520/// - The caller must ensure that `dst` is a valid pointer to a [moq_frame] struct.
521#[no_mangle]
522pub unsafe extern "C" fn moq_consume_frame_chunk(frame: u32, index: u32, dst: *mut moq_frame) -> i32 {
523	ffi::enter(move || {
524		let frame = ffi::parse_id(frame)?;
525		let index = index as usize;
526		let dst = dst.as_mut().ok_or(Error::InvalidPointer)?;
527		State::lock().consume.frame_chunk(frame, index, dst)
528	})
529}
530
531/// Close a frame and clean up its resources.
532///
533/// Returns a zero on success, or a negative code on failure.
534#[no_mangle]
535pub extern "C" fn moq_consume_frame_close(frame: u32) -> i32 {
536	ffi::enter(move || {
537		let frame = ffi::parse_id(frame)?;
538		State::lock().consume.frame_close(frame)
539	})
540}
541
542/// Close a broadcast consumer and clean up its resources.
543///
544/// Returns a zero on success, or a negative code on failure.
545#[no_mangle]
546pub extern "C" fn moq_consume_close(consume: u32) -> i32 {
547	ffi::enter(move || {
548		let consume = ffi::parse_id(consume)?;
549		State::lock().consume.close(consume)
550	})
551}