arcly-stream 0.1.7

An open-extensible live-media streaming kernel: lock-free zero-copy frame fan-out, instant-start GOP cache, a pluggable multi-protocol ingestion layer (RTMP, RTSP, SRT, WHIP/WHEP shipped), and a feature-gated pure-Rust media plane (MPEG-TS/HLS/fMP4) — runtime, config, and metrics free.
Documentation
//! Codec bitstream helpers — turning wire bytes into engine metadata and
//! [`MediaFrame`](crate::MediaFrame) classification.
//!
//! Gated behind the per-codec features (`codec-h264`, `codec-h265`, `codec-av1`,
//! `codec-vp9`, `codec-vvc`, `codec-aac`; `codec` = H.264 + AAC; `codecs-all` =
//! everything). Every parser is pure Rust with **no native dependencies**.
//!
//! All codecs share the [`CodecParser`] contract: extract [`VideoParams`] from a
//! config header, and classify random-access points so the kernel's GOP cache,
//! segmenter, eviction, and recorder — which key only on
//! [`MediaFrame::is_keyframe`](crate::MediaFrame::is_keyframe) — work unchanged.
//!
//! ```
//! # #[cfg(feature = "codec-h265")] {
//! use arcly_stream::codec::dispatch;
//! use arcly_stream::CodecId;
//!
//! let access_unit: &[u8] = &[/* … Annex-B HEVC bytes … */];
//! // Codec-erased dispatch by CodecId (used at the ingest boundary):
//! let is_rap = dispatch::is_random_access_point(CodecId::H265, access_unit);
//! # let _ = is_rap;
//! # }
//! ```

mod bitreader;
pub mod parser;

#[cfg(test)]
pub(crate) mod testutil;

pub use parser::{CodecConfig, CodecParser, VideoParams};

#[cfg(feature = "_nal")]
pub mod nal;

#[cfg(feature = "codec-h264")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-h264")))]
pub mod h264;

#[cfg(feature = "codec-h265")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-h265")))]
pub mod h265;

#[cfg(feature = "codec-vvc")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-vvc")))]
pub mod vvc;

#[cfg(feature = "codec-av1")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-av1")))]
pub mod av1;

#[cfg(feature = "codec-av1")]
pub(crate) mod obu;

#[cfg(feature = "codec-vp9")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-vp9")))]
pub mod vp9;

#[cfg(feature = "codec-aac")]
#[cfg_attr(docsrs, doc(cfg(feature = "codec-aac")))]
pub mod aac;

// ── Back-compat flat re-exports (pre-split public surface) ──────────────────
#[cfg(feature = "codec-aac")]
pub use aac::{parse_adts, AdtsHeader};
#[cfg(feature = "codec-h264")]
pub use h264::{annexb_to_avcc, avcc_to_annexb, iter_nals_annexb, parse_sps, SpsInfo};

/// Codec-erased dispatch over [`CodecId`](crate::CodecId).
///
/// Protocol handlers call these to classify an access unit without naming a
/// concrete parser. Each returns a conservative default (`false` / `None`) for a
/// codec whose feature is not enabled.
pub mod dispatch {
    use super::*;
    use crate::CodecId;

    /// Extract [`VideoParams`] from a config access unit for `codec`.
    #[allow(unused_variables)]
    pub fn parse_config(codec: CodecId, data: &[u8]) -> Option<VideoParams> {
        match codec {
            #[cfg(feature = "codec-h264")]
            CodecId::H264 => <h264::H264 as CodecParser>::parse_config(data),
            #[cfg(feature = "codec-h265")]
            CodecId::H265 => <h265::H265 as CodecParser>::parse_config(data),
            #[cfg(feature = "codec-vvc")]
            CodecId::VVC => <vvc::Vvc as CodecParser>::parse_config(data),
            #[cfg(feature = "codec-av1")]
            CodecId::AV1 => <av1::Av1 as CodecParser>::parse_config(data),
            #[cfg(feature = "codec-vp9")]
            CodecId::VP9 => <vp9::Vp9 as CodecParser>::parse_config(data),
            _ => None,
        }
    }

    /// Whether `data` is a random-access point under `codec`.
    #[allow(unused_variables)]
    pub fn is_random_access_point(codec: CodecId, data: &[u8]) -> bool {
        match codec {
            #[cfg(feature = "codec-h264")]
            CodecId::H264 => <h264::H264 as CodecParser>::is_random_access_point(data),
            #[cfg(feature = "codec-h265")]
            CodecId::H265 => <h265::H265 as CodecParser>::is_random_access_point(data),
            #[cfg(feature = "codec-vvc")]
            CodecId::VVC => <vvc::Vvc as CodecParser>::is_random_access_point(data),
            #[cfg(feature = "codec-av1")]
            CodecId::AV1 => <av1::Av1 as CodecParser>::is_random_access_point(data),
            #[cfg(feature = "codec-vp9")]
            CodecId::VP9 => <vp9::Vp9 as CodecParser>::is_random_access_point(data),
            _ => false,
        }
    }

    /// Extract the codec's parameter-set NAL units (H.264 SPS/PPS, H.265
    /// VPS/SPS/PPS) from an Annex-B access unit, re-framed Annex-B with 4-byte
    /// start codes — i.e. a config record suitable for `parse_config` /
    /// `Fmp4Muxer::init_segment`. Returns `None` for codecs whose config is not a
    /// NAL parameter set, or when the access unit carries none.
    ///
    /// This lets in-band ingests (MPEG-TS over UDP/SRT, RTSP) synthesize the
    /// CONFIG access unit that container formats like FLV deliver out-of-band, so
    /// every downstream packager gets a decoder config regardless of ingest.
    #[allow(unused_variables)]
    pub fn parameter_sets(codec: CodecId, annexb: &[u8]) -> Option<bytes::Bytes> {
        use crate::codec::nal::iter_nals;
        let is_param_set = |nal: &[u8]| -> bool {
            let Some(&b0) = nal.first() else { return false };
            match codec {
                #[cfg(feature = "codec-h264")]
                CodecId::H264 => matches!(b0 & 0x1f, h264::NAL_SPS | h264::NAL_PPS),
                #[cfg(feature = "codec-h265")]
                CodecId::H265 => {
                    matches!(
                        (b0 >> 1) & 0x3f,
                        h265::NAL_VPS | h265::NAL_SPS | h265::NAL_PPS
                    )
                }
                _ => false,
            }
        };
        let mut out = Vec::new();
        for nal in iter_nals(annexb) {
            if is_param_set(nal) {
                out.extend_from_slice(&[0, 0, 0, 1]);
                out.extend_from_slice(nal);
            }
        }
        (!out.is_empty()).then(|| bytes::Bytes::from(out))
    }

    /// The HLS `CODECS` attribute for `codec` given parsed `params`.
    #[allow(unused_variables)]
    pub fn hls_codec_string(codec: CodecId, params: &VideoParams) -> Option<String> {
        match codec {
            #[cfg(feature = "codec-h264")]
            CodecId::H264 => Some(<h264::H264 as CodecParser>::hls_codec_string(params)),
            #[cfg(feature = "codec-h265")]
            CodecId::H265 => Some(<h265::H265 as CodecParser>::hls_codec_string(params)),
            #[cfg(feature = "codec-vvc")]
            CodecId::VVC => Some(<vvc::Vvc as CodecParser>::hls_codec_string(params)),
            #[cfg(feature = "codec-av1")]
            CodecId::AV1 => Some(<av1::Av1 as CodecParser>::hls_codec_string(params)),
            #[cfg(feature = "codec-vp9")]
            CodecId::VP9 => Some(<vp9::Vp9 as CodecParser>::hls_codec_string(params)),
            _ => None,
        }
    }
}