#![forbid(unsafe_code)]
use crate::types::TrackInfo;
use crate::{INLINE_TRACKS, MuxError, ids, vint};
use smallvec::SmallVec;
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, Default)]
pub struct Open;
#[derive(Debug, Clone, Copy, Default)]
pub struct Live;
pub const DEFAULT_CLUSTER_BATCH: usize = 32;
const DEFAULT_TIMECODE_SCALE: u64 = 1_000_000;
const fn fits_relative_timecode(delta: i64) -> bool {
delta >= i16::MIN as i64 && delta <= i16::MAX as i64
}
#[derive(Debug)]
pub struct Muxer<S = Open> {
tracks: SmallVec<[TrackInfo; INLINE_TRACKS]>,
timecode_scale: u64,
output: Vec<u8>,
output_consumed: usize,
cluster: Vec<u8>,
cluster_timecode: Option<i64>,
cluster_frames: usize,
batch: usize,
_state: PhantomData<S>,
}
impl Muxer<Open> {
#[must_use]
pub fn new() -> Self {
Self::with_options(DEFAULT_TIMECODE_SCALE, DEFAULT_CLUSTER_BATCH)
}
#[must_use]
pub fn with_options(timecode_scale: u64, batch: usize) -> Self {
Self {
tracks: SmallVec::new(),
timecode_scale: timecode_scale.max(1),
output: Vec::with_capacity(4 * 1024),
output_consumed: 0,
cluster: Vec::new(),
cluster_timecode: None,
cluster_frames: 0,
batch: batch.max(1),
_state: PhantomData,
}
}
#[must_use]
pub fn tracks(&self) -> &[TrackInfo] {
&self.tracks
}
pub fn add_track(&mut self, track: TrackInfo) -> Result<(), MuxError> {
if track.track_number == 0 {
return Err(MuxError::InvalidTrackNumber);
}
if self
.tracks
.iter()
.any(|t| t.track_number == track.track_number)
{
return Err(MuxError::DuplicateTrack(track.track_number));
}
self.tracks.push(track);
Ok(())
}
#[must_use]
pub fn begin(mut self) -> Muxer<Live> {
write_ebml_header(&mut self.output);
write_id(&mut self.output, ids::SEGMENT);
vint::encode_unknown_size(4, &mut self.output); write_segment_info(&mut self.output, self.timecode_scale);
write_tracks(&mut self.output, &self.tracks);
Muxer {
tracks: self.tracks,
timecode_scale: self.timecode_scale,
output: self.output,
output_consumed: self.output_consumed,
cluster: self.cluster,
cluster_timecode: self.cluster_timecode,
cluster_frames: self.cluster_frames,
batch: self.batch,
_state: PhantomData,
}
}
}
impl Default for Muxer<Open> {
fn default() -> Self {
Self::new()
}
}
impl Muxer<Live> {
#[must_use]
pub fn tracks(&self) -> &[TrackInfo] {
&self.tracks
}
#[must_use]
pub const fn timecode_scale(&self) -> u64 {
self.timecode_scale
}
pub fn push_frame(
&mut self,
track_number: u64,
timecode: i64,
is_keyframe: bool,
payload: &[u8],
) -> Result<(), MuxError> {
if !self.tracks.iter().any(|t| t.track_number == track_number) {
return Err(MuxError::UnknownTrack(track_number));
}
let needs_new_cluster = match self.cluster_timecode {
None => true,
Some(base) => {
self.cluster_frames >= self.batch || !fits_relative_timecode(timecode - base)
}
};
if needs_new_cluster {
self.close_cluster();
self.cluster_timecode = Some(timecode);
}
let base = self.cluster_timecode.unwrap_or(timecode);
write_simple_block(
&mut self.cluster,
track_number,
(timecode - base) as i16,
is_keyframe,
payload,
);
self.cluster_frames += 1;
Ok(())
}
pub fn flush(&mut self) {
self.close_cluster();
}
pub fn poll_bytes(&mut self, out: &mut Vec<u8>) -> usize {
let available = self.output.len().saturating_sub(self.output_consumed);
if available == 0 {
return 0;
}
out.extend_from_slice(&self.output[self.output_consumed..]);
self.output_consumed = self.output.len();
if self.output_consumed >= 64 * 1024 {
self.output.drain(..self.output_consumed);
self.output_consumed = 0;
}
available
}
fn close_cluster(&mut self) {
if self.cluster_timecode.is_none() || self.cluster.is_empty() {
self.cluster.clear();
self.cluster_timecode = None;
self.cluster_frames = 0;
return;
}
let base = self.cluster_timecode.unwrap_or(0);
let mut body = Vec::with_capacity(self.cluster.len() + 16);
write_uint_elem(&mut body, ids::TIMECODE, base.max(0) as u64);
body.extend_from_slice(&self.cluster);
write_id(&mut self.output, ids::CLUSTER);
vint::encode_size(body.len() as u64, &mut self.output);
self.output.extend_from_slice(&body);
self.cluster.clear();
self.cluster_timecode = None;
self.cluster_frames = 0;
}
}
fn write_id(out: &mut Vec<u8>, id: u32) {
vint::encode_id(id, out);
}
fn write_ebml_header(out: &mut Vec<u8>) {
let mut body = Vec::new();
write_uint_elem(&mut body, ids::EBML_VERSION, 1);
write_uint_elem(&mut body, ids::EBML_READ_VERSION, 1);
write_uint_elem(&mut body, ids::EBML_MAX_ID_LENGTH, 4);
write_uint_elem(&mut body, ids::EBML_MAX_SIZE_LENGTH, 8);
write_string_elem(&mut body, ids::DOC_TYPE, "webm");
write_uint_elem(&mut body, ids::DOC_TYPE_VERSION, 2);
write_uint_elem(&mut body, ids::DOC_TYPE_READ_VERSION, 2);
write_id(out, ids::EBML_HEADER);
vint::encode_size(body.len() as u64, out);
out.extend_from_slice(&body);
}
fn write_segment_info(out: &mut Vec<u8>, timecode_scale: u64) {
let mut body = Vec::new();
write_uint_elem(&mut body, ids::TIMECODE_SCALE, timecode_scale);
write_id(out, ids::SEGMENT_INFO);
vint::encode_size(body.len() as u64, out);
out.extend_from_slice(&body);
}
fn write_tracks(out: &mut Vec<u8>, tracks: &[TrackInfo]) {
if tracks.is_empty() {
return;
}
let mut body = Vec::new();
for t in tracks {
write_track_entry(&mut body, t);
}
write_id(out, ids::TRACKS);
vint::encode_size(body.len() as u64, out);
out.extend_from_slice(&body);
}
fn write_track_entry(out: &mut Vec<u8>, t: &TrackInfo) {
let mut body = Vec::new();
write_uint_elem(&mut body, ids::TRACK_NUMBER, t.track_number);
write_uint_elem(&mut body, ids::TRACK_TYPE, u64::from(t.track_type));
write_string_elem(&mut body, ids::CODEC_ID, &t.codec_id);
if t.is_video() {
let mut video = Vec::new();
write_uint_elem(&mut video, ids::PIXEL_WIDTH, u64::from(t.width));
write_uint_elem(&mut video, ids::PIXEL_HEIGHT, u64::from(t.height));
write_id(&mut body, ids::VIDEO);
vint::encode_size(video.len() as u64, &mut body);
body.extend_from_slice(&video);
} else {
let mut audio = Vec::new();
write_float_elem(&mut audio, ids::SAMPLING_FREQUENCY, t.sample_rate);
write_uint_elem(&mut audio, ids::CHANNELS, u64::from(t.channels));
write_id(&mut body, ids::AUDIO);
vint::encode_size(audio.len() as u64, &mut body);
body.extend_from_slice(&audio);
}
write_id(out, ids::TRACK_ENTRY);
vint::encode_size(body.len() as u64, out);
out.extend_from_slice(&body);
}
fn write_simple_block(
out: &mut Vec<u8>,
track_number: u64,
relative_timecode: i16,
is_keyframe: bool,
payload: &[u8],
) {
let mut body = Vec::with_capacity(payload.len() + 4);
vint::encode_size(track_number, &mut body);
body.extend_from_slice(&relative_timecode.to_be_bytes());
body.push(if is_keyframe { 0x80 } else { 0x00 });
body.extend_from_slice(payload);
write_id(out, ids::SIMPLE_BLOCK);
vint::encode_size(body.len() as u64, out);
out.extend_from_slice(&body);
}
fn write_uint_elem(out: &mut Vec<u8>, id: u32, value: u64) {
let be = value.to_be_bytes();
let first_nonzero = be.iter().position(|&b| b != 0).unwrap_or(be.len() - 1);
write_id(out, id);
vint::encode_size((be.len() - first_nonzero) as u64, out);
out.extend_from_slice(&be[first_nonzero..]);
}
fn write_float_elem(out: &mut Vec<u8>, id: u32, value: f64) {
write_id(out, id);
vint::encode_size(8, out);
out.extend_from_slice(&value.to_be_bytes());
}
fn write_string_elem(out: &mut Vec<u8>, id: u32, value: &str) {
write_id(out, id);
vint::encode_size(value.len() as u64, out);
out.extend_from_slice(value.as_bytes());
}
#[cfg(test)]
#[path = "mux_tests.rs"]
mod tests;