Skip to main content

moq/
api.rs

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