use std::io::Write;
use std::num::NonZeroU64;
use std::ptr::NonNull;
use crate::ffi;
use crate::ffi::mux::{ResultCode, TrackNum};
use super::{
AudioCodecId, AudioTrack, ColorPrimaries, ColorRange, ColorSubsampling, Error, HdrMetadata,
MasteringDisplayMetadata, MatrixCoefficients, SegmentMode, TransferCharacteristics,
VideoCodecId, VideoTrack, writer::Writer,
};
struct OwnedSegmentPtr {
segment: ffi::mux::SegmentNonNullPtr,
}
impl OwnedSegmentPtr {
const unsafe fn new(segment: ffi::mux::SegmentNonNullPtr) -> Self {
Self { segment }
}
const fn as_ptr(&self) -> ffi::mux::SegmentMutPtr {
self.segment.as_ptr()
}
}
impl Drop for OwnedSegmentPtr {
fn drop(&mut self) {
unsafe {
ffi::mux::delete_segment(self.segment.as_ptr());
}
}
}
pub struct SegmentBuilder<W: Write> {
segment: OwnedSegmentPtr,
writer: Writer<W>,
}
impl<W: Write> SegmentBuilder<W> {
#[inline]
pub fn new(writer: Writer<W>) -> Result<Self, Error> {
let segment = unsafe { ffi::mux::new_segment() };
let segment = NonNull::new(segment)
.map(|ptr| unsafe { OwnedSegmentPtr::new(ptr) })
.ok_or(Error::Unknown)?;
let result = unsafe { ffi::mux::initialize_segment(segment.as_ptr(), writer.mkv_writer()) };
Error::check_code(result)?;
Ok(Self { segment, writer })
}
#[inline]
pub fn set_writing_app(self, app_name: &str) -> Result<Self, Error> {
let name = std::ffi::CString::new(app_name).map_err(|_| Error::BadParam)?;
unsafe {
ffi::mux::mux_set_writing_app(self.segment.as_ptr(), name.as_ptr());
}
Ok(self)
}
#[inline]
pub fn set_mode(self, mode: SegmentMode) -> Result<Self, Error> {
let mode_value = match mode {
SegmentMode::Live => ffi::mux::SEGMENT_MODE_LIVE,
SegmentMode::File => ffi::mux::SEGMENT_MODE_FILE,
};
let result = unsafe { ffi::mux::segment_set_mode(self.segment.as_ptr(), mode_value) };
Error::check_code(result)?;
Ok(self)
}
pub fn add_video_track(
self,
width: u32,
height: u32,
codec: VideoCodecId,
desired_track_num: Option<TrackNum>,
) -> Result<(Self, VideoTrack), Error> {
let mut track_num_out: TrackNum = 0;
if desired_track_num == Some(0) {
return Err(Error::BadParam);
}
let width: i32 = try_as_i32(width)?;
let height: i32 = try_as_i32(height)?;
if width == 0 || height == 0 {
return Err(Error::BadParam);
}
let requested_track_num: i32 = try_as_i32(desired_track_num.unwrap_or(0))?;
let result = unsafe {
ffi::mux::segment_add_video_track(
self.segment.as_ptr(),
width,
height,
requested_track_num,
codec.get_id(),
&raw mut track_num_out,
)
};
Error::check_code(result)?;
let track_num_out = NonZeroU64::new(track_num_out).ok_or(Error::Unknown)?;
if let Some(desired) = desired_track_num
&& desired != track_num_out.get() {
return Err(Error::Unknown);
}
Ok((self, VideoTrack(track_num_out)))
}
pub fn add_audio_track(
self,
sample_rate: u32,
channels: u32,
codec: AudioCodecId,
desired_track_num: Option<TrackNum>,
) -> Result<(Self, AudioTrack), Error> {
let mut track_num_out: TrackNum = 0;
if desired_track_num == Some(0) {
return Err(Error::BadParam);
}
let sample_rate: i32 = try_as_i32(sample_rate)?;
let channels: i32 = try_as_i32(channels)?;
if sample_rate == 0 || channels == 0 {
return Err(Error::BadParam);
}
let requested_track_num: i32 = try_as_i32(desired_track_num.unwrap_or(0))?;
let result = unsafe {
ffi::mux::segment_add_audio_track(
self.segment.as_ptr(),
sample_rate,
channels,
requested_track_num,
codec.get_id(),
&raw mut track_num_out,
)
};
Error::check_code(result)?;
let track_num_out = NonZeroU64::new(track_num_out).ok_or(Error::Unknown)?;
if let Some(desired) = desired_track_num
&& desired != track_num_out.get() {
return Err(Error::Unknown);
}
Ok((self, AudioTrack(track_num_out)))
}
#[inline]
pub fn set_codec_private(self, track: impl Into<TrackNum>, data: &[u8]) -> Result<Self, Error> {
unsafe {
let len: i32 = data.len().try_into().map_err(|_| Error::BadParam)?;
let result = ffi::mux::segment_set_codec_private(
self.segment.as_ptr(),
track.into(),
data.as_ptr(),
len,
);
Error::check_code(result)?;
Ok(self)
}
}
#[inline]
pub fn set_color(
self,
track: VideoTrack,
bit_depth: u8,
subsampling: ColorSubsampling,
color_range: ColorRange,
) -> Result<Self, Error> {
let color_range = match color_range {
ColorRange::Unspecified => 0,
ColorRange::Broadcast => 1,
ColorRange::Full => 2,
};
let result = unsafe {
ffi::mux::mux_set_color(
self.segment.as_ptr(),
track.into(),
bit_depth,
subsampling.chroma_horizontal,
subsampling.chroma_vertical,
color_range,
)
};
Error::check_code(result)?;
Ok(self)
}
#[inline]
pub fn set_transfer_characteristics(
self,
track: VideoTrack,
transfer: TransferCharacteristics,
) -> Result<Self, Error> {
let result = unsafe {
ffi::mux::mux_set_transfer_characteristics(
self.segment.as_ptr(),
track.into(),
transfer as u64,
)
};
Error::check_code(result)?;
Ok(self)
}
#[inline]
pub fn set_primaries(
self,
track: VideoTrack,
primaries: ColorPrimaries,
) -> Result<Self, Error> {
let result = unsafe {
ffi::mux::mux_set_primaries(self.segment.as_ptr(), track.into(), primaries as u64)
};
Error::check_code(result)?;
Ok(self)
}
#[inline]
pub fn set_matrix_coefficients(
self,
track: VideoTrack,
matrix: MatrixCoefficients,
) -> Result<Self, Error> {
let result = unsafe {
ffi::mux::mux_set_matrix_coefficients(
self.segment.as_ptr(),
track.into(),
matrix as u64,
)
};
Error::check_code(result)?;
Ok(self)
}
#[inline]
pub fn set_hdr_metadata(
self,
track: VideoTrack,
metadata: &HdrMetadata,
) -> Result<Self, Error> {
let result = unsafe {
ffi::mux::mux_set_max_cll(self.segment.as_ptr(), track.into(), metadata.max_cll)
};
Error::check_code(result)?;
let result = unsafe {
ffi::mux::mux_set_max_fall(self.segment.as_ptr(), track.into(), metadata.max_fall)
};
Error::check_code(result)?;
if let Some(mastering) = &metadata.mastering_metadata {
let result = unsafe {
ffi::mux::mux_set_mastering_metadata(
self.segment.as_ptr(),
track.into(),
mastering.luminance_max,
mastering.luminance_min,
mastering.primaries.red.x,
mastering.primaries.red.y,
mastering.primaries.green.x,
mastering.primaries.green.y,
mastering.primaries.blue.x,
mastering.primaries.blue.y,
mastering.white_point.x,
mastering.white_point.y,
)
};
Error::check_code(result)?;
}
Ok(self)
}
#[inline]
pub fn set_mastering_metadata(
self,
track: VideoTrack,
metadata: &MasteringDisplayMetadata,
) -> Result<Self, Error> {
let result = unsafe {
ffi::mux::mux_set_mastering_metadata(
self.segment.as_ptr(),
track.into(),
metadata.luminance_max,
metadata.luminance_min,
metadata.primaries.red.x,
metadata.primaries.red.y,
metadata.primaries.green.x,
metadata.primaries.green.y,
metadata.primaries.blue.x,
metadata.primaries.blue.y,
metadata.white_point.x,
metadata.white_point.y,
)
};
Error::check_code(result)?;
Ok(self)
}
#[must_use]
#[inline]
pub fn build(self) -> Segment<W> {
let Self { segment, writer } = self;
Segment { ffi: segment, writer }
}
}
impl<W: Write> std::fmt::Debug for SegmentBuilder<W> {
#[cold]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(std::any::type_name::<Self>())
}
}
pub struct Segment<W: Write> {
ffi: OwnedSegmentPtr,
writer: Writer<W>,
}
unsafe impl Send for OwnedSegmentPtr {}
impl<W: Write> Segment<W> {
#[inline]
pub fn add_frame(
&mut self,
track: impl Into<TrackNum>,
data: &[u8],
timestamp_ns: u64,
keyframe: bool,
) -> Result<(), Error> {
let result = unsafe {
ffi::mux::segment_add_frame(
self.ffi.as_ptr(),
track.into(),
data.as_ptr(),
data.len(),
timestamp_ns,
keyframe,
)
};
Error::check_code(result)
}
#[inline]
pub fn finalize(self, duration: Option<u64>) -> Result<Writer<W>, Writer<W>> {
let Self { ffi, writer } = self;
let result = unsafe { ffi::mux::finalize_segment(ffi.as_ptr(), duration.unwrap_or(0)) };
match result {
ResultCode::Ok => Ok(writer),
_ => Err(writer),
}
}
}
impl<W: Write> std::fmt::Debug for Segment<W> {
#[cold]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(std::any::type_name::<Self>())
}
}
fn try_as_i32(x: impl TryInto<i32>) -> Result<i32, Error> {
x.try_into().map_err(|_| Error::BadParam)
}
#[cfg(test)]
mod tests {
use crate::mux::Writer;
use super::*;
use std::io::Cursor;
fn make_segment_builder() -> SegmentBuilder<Cursor<Vec<u8>>> {
let output = Vec::new();
let writer = Writer::new(Cursor::new(output));
SegmentBuilder::new(writer).expect("Segment builder should create OK")
}
#[test]
fn bad_track_number() {
let builder = make_segment_builder();
let video_track = builder.add_video_track(420, 420, VideoCodecId::VP8, Some(123456));
assert!(video_track.is_err());
}
#[test]
fn overlapping_track_number_same_type() {
let builder = make_segment_builder();
let Ok((builder, _)) = builder.add_video_track(420, 420, VideoCodecId::VP8, Some(123))
else {
panic!("First video track unexpectedly failed")
};
let video_track2 = builder.add_video_track(420, 420, VideoCodecId::VP8, Some(123));
assert!(video_track2.is_err());
}
#[test]
fn overlapping_track_number_different_type() {
let builder = make_segment_builder();
let (builder, vid) = builder.add_video_track(420, 420, VideoCodecId::VP8, Some(123))
.expect("First video track failed");
let builder = builder
.set_transfer_characteristics(vid, TransferCharacteristics::Bt709).unwrap()
.set_primaries(vid, ColorPrimaries::Bt709).unwrap();
let audio_track = builder.add_audio_track(420, 420, AudioCodecId::Opus, Some(123));
assert!(audio_track.is_err());
}
#[test]
fn set_mode_live() {
let builder = make_segment_builder();
let result = builder.set_mode(super::SegmentMode::Live);
assert!(result.is_ok());
}
#[test]
fn set_mode_file() {
let builder = make_segment_builder();
let result = builder.set_mode(super::SegmentMode::File);
assert!(result.is_ok());
}
}