use oxideav_core::{CodecId, CodecParameters, Error, MediaType, Result};
use crate::stream_format::{write_bitmap_info_header, write_waveformatex};
pub fn video_codec_id(fourcc: &[u8; 4]) -> CodecId {
let upper = uppercase4(fourcc);
let name = match &upper {
b"MJPG" | b"AVRN" | b"LJPG" | b"JPGL" => "mjpeg",
b"FFV1" => "ffv1",
b"XVID" | b"DIVX" | b"DX50" | b"MP4V" | b"FMP4" | b"3IV2" | b"M4S2" | b"MP4S" | b"DIVF"
| b"BLZ0" | b"DX40" | b"RMP4" | b"SMP4" | b"UMP4" | b"WV1F" | b"XVIX" | b"DXGM" => {
"mpeg4video"
}
b"DIV3" | b"DIV4" | b"DIV5" | b"DIV6" | b"MP43" | b"MPG3" | b"AP41" => "msmpeg4v3",
b"MP42" => "msmpeg4v2",
b"MP41" | b"MPG4" => "msmpeg4v1",
b"H264" | b"AVC1" | b"X264" | b"VSSH" | b"DAVC" | b"PAVC" | b"AVC2" | b"AVC3"
| b"AI5Q" | b"AI55" | b"AI15" | b"AI13" | b"AI12" | b"AI1Q" | b"AI5P" | b"AI53" => "h264",
b"HEVC" | b"H265" | b"HVC1" | b"HEV1" | b"X265" | b"DXHE" => "h265",
b"H263" | b"U263" | b"M263" | b"ILVR" | b"VX1K" | b"VIV1" | b"X263" | b"T263"
| b"S263" | b"L263" => "h263",
b"MPG1" | b"MPEG" => "mpeg1video",
b"MPG2" | b"MP2V" | b"EM2V" | b"HDV1" | b"HDV2" | b"HDV3" | b"HDV4" | b"HDV5"
| b"HDV6" | b"HDV7" | b"HDV8" | b"HDV9" => "mpeg2video",
b"VP80" | b"VP8 " => "vp8",
b"VP90" | b"VP9 " => "vp9",
b"AV01" => "av1",
b"THEO" => "theora",
b"HFYU" => "huffyuv",
b"FFVH" => "ffvhuff",
b"UTVS" | b"ULRG" | b"ULRA" | b"ULY0" | b"ULY2" | b"ULY4" | b"ULH0" | b"ULH2"
| b"ULH4" => "utvideo",
b"APCH" | b"APCN" | b"APCS" | b"APCO" | b"AP4H" | b"AP4X" => "prores",
b"DVSD" | b"DV25" | b"DV50" | b"DVC " | b"DVCP" | b"DVHD" | b"DVH1" => "dv",
b"WMV1" => "wmv1",
b"WMV2" => "wmv2",
b"WMV3" => "wmv3",
b"WVC1" | b"WMVA" => "vc1",
b"CVID" => "cinepak",
b"IV31" | b"IV32" => "indeo3",
b"IV41" | b"IV42" => "indeo4",
b"IV50" => "indeo5",
b"SVQ1" => "svq1",
b"SVQ3" => "svq3",
b"FLV1" => "flv1",
b"VP60" | b"VP61" | b"VP62" | b"VP6F" | b"VP6A" => "vp6",
[0, 0, 0, 0] | b"DIB " | b"RGB " | b"RAW " => "rgb24",
b"YUY2" | b"YUYV" | b"V422" | b"YUNV" => "yuyv422",
b"UYVY" | b"Y422" | b"UYNV" => "uyvy422",
b"NV12" => "nv12",
b"NV21" => "nv21",
b"I420" | b"IYUV" | b"YV12" => "yuv420p",
b"Y41P" => "yuv411p",
b"V410" => "yuv444p10le",
b"V210" => "v210",
b"R210" | b"R10K" => "r210",
other => {
let s = std::str::from_utf8(other).unwrap_or("????");
return CodecId::new(format!("avi:{s}"));
}
};
CodecId::new(name)
}
pub fn audio_codec_id(format_tag: u16) -> CodecId {
audio_codec_id_full(format_tag, 0)
}
pub fn audio_codec_id_full(format_tag: u16, bits_per_sample: u16) -> CodecId {
let name = match format_tag {
0x0001 => match bits_per_sample {
0 | 16 => "pcm_s16le",
8 => "pcm_u8",
24 => "pcm_s24le",
32 => "pcm_s32le",
_ => "pcm_s16le",
},
0x0002 => "adpcm_ms",
0x0003 => match bits_per_sample {
64 => "pcm_f64le",
_ => "pcm_f32le",
},
0x0006 => "pcm_alaw",
0x0007 => "pcm_mulaw",
0x0011 => "adpcm_ima_wav",
0x0020 => "adpcm_yamaha",
0x0014 => "g723_1",
0x0031 | 0x0032 => "gsm_ms",
0x0028 => "g722",
0x0050 => "mp2",
0x0055 => "mp3",
0x0061 => "adpcm_ima_dk4",
0x0062 => "adpcm_ima_dk3",
0x00FF | 0x706D | 0x4143 | 0xA106 => "aac",
0x0160 => "wmav1",
0x0161 => "wmav2",
0x0162 => "wmapro",
0x0163 => "wmalossless",
0x2000 | 0x6AC3 => "ac3",
0x2001 => "eac3",
0x2002 | 0x0008 => "dts",
0x4F70 | 0x704F | 0x7075 => "opus",
0x674F | 0x6750 | 0x6751 | 0x676F | 0x6770 | 0x6771 => "vorbis",
0xF1AC => "flac",
0xEAC3 => "eac3",
other => return CodecId::new(format!("avi:tag_{other:04x}")),
};
CodecId::new(name)
}
pub(crate) struct StrfEntry {
pub chunk_suffix: [u8; 2],
pub handler_fourcc: [u8; 4],
pub strf: Vec<u8>,
pub strh_type: [u8; 4],
pub sample_size: u32,
pub scale: u32,
pub rate: u32,
}
pub(crate) fn build_strf(params: &CodecParameters) -> Result<StrfEntry> {
match params.codec_id.as_str() {
"mjpeg" => mjpeg_entry(params),
"ffv1" => ffv1_entry(params),
"mpeg4video" => video_entry(params, *b"XVID", 24),
"h264" => video_entry(params, *b"H264", 24),
"h265" => video_entry(params, *b"HEVC", 24),
"h263" => video_entry(params, *b"H263", 24),
"mpeg1video" => video_entry(params, *b"MPG1", 24),
"mpeg2video" => video_entry(params, *b"MPG2", 24),
"vp8" => video_entry(params, *b"VP80", 24),
"vp9" => video_entry(params, *b"VP90", 24),
"av1" => video_entry(params, *b"AV01", 24),
"rgb24" => rgb24_entry(params),
"pcm_u8" => pcm_int_entry(params, 0x0001, 8),
"pcm_s16le" => pcm_int_entry(params, 0x0001, 16),
"pcm_s24le" => pcm_int_entry(params, 0x0001, 24),
"pcm_s32le" => pcm_int_entry(params, 0x0001, 32),
"pcm_f32le" => pcm_int_entry(params, 0x0003, 32),
"pcm_f64le" => pcm_int_entry(params, 0x0003, 64),
"pcm_alaw" => pcm_companded_entry(params, 0x0006),
"pcm_mulaw" => pcm_companded_entry(params, 0x0007),
"mp2" => mp_audio_entry(params, 0x0050),
"mp3" => mp_audio_entry(params, 0x0055),
"aac" => aac_entry(params),
"ac3" => compressed_audio_entry(params, 0x2000),
"eac3" => compressed_audio_entry(params, 0x2001),
"flac" => compressed_audio_entry(params, 0xF1AC),
other => Err(Error::unsupported(format!(
"avi muxer: no packaging for codec {other}"
))),
}
}
fn mjpeg_entry(params: &CodecParameters) -> Result<StrfEntry> {
if params.media_type != MediaType::Video {
return Err(Error::invalid("avi muxer: mjpeg must be video"));
}
let width = params
.width
.ok_or_else(|| Error::invalid("avi muxer: mjpeg requires width"))?;
let height = params
.height
.ok_or_else(|| Error::invalid("avi muxer: mjpeg requires height"))?;
let strf = write_bitmap_info_header(width, height, *b"MJPG", 24, ¶ms.extradata);
let (scale, rate) = video_scale_rate(params);
Ok(StrfEntry {
chunk_suffix: *b"dc",
handler_fourcc: *b"MJPG",
strf,
strh_type: *b"vids",
sample_size: 0,
scale,
rate,
})
}
fn ffv1_entry(params: &CodecParameters) -> Result<StrfEntry> {
if params.media_type != MediaType::Video {
return Err(Error::invalid("avi muxer: ffv1 must be video"));
}
let width = params
.width
.ok_or_else(|| Error::invalid("avi muxer: ffv1 requires width"))?;
let height = params
.height
.ok_or_else(|| Error::invalid("avi muxer: ffv1 requires height"))?;
let strf = write_bitmap_info_header(width, height, *b"FFV1", 24, ¶ms.extradata);
let (scale, rate) = video_scale_rate(params);
Ok(StrfEntry {
chunk_suffix: *b"dc",
handler_fourcc: *b"FFV1",
strf,
strh_type: *b"vids",
sample_size: 0,
scale,
rate,
})
}
fn video_entry(
params: &CodecParameters,
fourcc: [u8; 4],
bit_count: u16,
) -> Result<StrfEntry> {
if params.media_type != MediaType::Video {
return Err(Error::invalid("avi muxer: video codec on non-video stream"));
}
let width = params
.width
.ok_or_else(|| Error::invalid("avi muxer: video stream missing width"))?;
let height = params
.height
.ok_or_else(|| Error::invalid("avi muxer: video stream missing height"))?;
let strf = write_bitmap_info_header(width, height, fourcc, bit_count, ¶ms.extradata);
let (scale, rate) = video_scale_rate(params);
Ok(StrfEntry {
chunk_suffix: *b"dc",
handler_fourcc: fourcc,
strf,
strh_type: *b"vids",
sample_size: 0,
scale,
rate,
})
}
fn rgb24_entry(params: &CodecParameters) -> Result<StrfEntry> {
if params.media_type != MediaType::Video {
return Err(Error::invalid("avi muxer: rgb24 must be video"));
}
let width = params
.width
.ok_or_else(|| Error::invalid("avi muxer: rgb24 requires width"))?;
let height = params
.height
.ok_or_else(|| Error::invalid("avi muxer: rgb24 requires height"))?;
let strf = write_bitmap_info_header(width, height, [0, 0, 0, 0], 24, ¶ms.extradata);
let (scale, rate) = video_scale_rate(params);
Ok(StrfEntry {
chunk_suffix: *b"db",
handler_fourcc: [0, 0, 0, 0],
strf,
strh_type: *b"vids",
sample_size: 0,
scale,
rate,
})
}
fn pcm_int_entry(
params: &CodecParameters,
format_tag: u16,
bits_per_sample: u16,
) -> Result<StrfEntry> {
if params.media_type != MediaType::Audio {
return Err(Error::invalid("avi muxer: pcm must be audio"));
}
let channels = params
.channels
.ok_or_else(|| Error::invalid("avi muxer: pcm requires channels"))?;
let sample_rate = params
.sample_rate
.ok_or_else(|| Error::invalid("avi muxer: pcm requires sample_rate"))?;
let block_align = channels * (bits_per_sample / 8);
let avg_bytes_per_sec = sample_rate * block_align as u32;
let strf = write_waveformatex(
format_tag,
channels,
sample_rate,
avg_bytes_per_sec,
block_align,
bits_per_sample,
&[],
);
Ok(StrfEntry {
chunk_suffix: *b"wb",
handler_fourcc: *b"\0\0\0\0",
strf,
strh_type: *b"auds",
sample_size: block_align as u32,
scale: 1,
rate: sample_rate,
})
}
fn pcm_companded_entry(params: &CodecParameters, format_tag: u16) -> Result<StrfEntry> {
if params.media_type != MediaType::Audio {
return Err(Error::invalid("avi muxer: companded pcm must be audio"));
}
let channels = params
.channels
.ok_or_else(|| Error::invalid("avi muxer: pcm requires channels"))?;
let sample_rate = params
.sample_rate
.ok_or_else(|| Error::invalid("avi muxer: pcm requires sample_rate"))?;
let block_align = channels; let avg_bytes_per_sec = sample_rate * block_align as u32;
let strf = write_waveformatex(
format_tag,
channels,
sample_rate,
avg_bytes_per_sec,
block_align,
8,
&[],
);
Ok(StrfEntry {
chunk_suffix: *b"wb",
handler_fourcc: *b"\0\0\0\0",
strf,
strh_type: *b"auds",
sample_size: block_align as u32,
scale: 1,
rate: sample_rate,
})
}
fn mp_audio_entry(params: &CodecParameters, format_tag: u16) -> Result<StrfEntry> {
compressed_audio_entry(params, format_tag)
}
fn aac_entry(params: &CodecParameters) -> Result<StrfEntry> {
compressed_audio_entry(params, 0x00FF)
}
fn compressed_audio_entry(params: &CodecParameters, format_tag: u16) -> Result<StrfEntry> {
if params.media_type != MediaType::Audio {
return Err(Error::invalid("avi muxer: audio codec on non-audio stream"));
}
let channels = params
.channels
.ok_or_else(|| Error::invalid("avi muxer: audio stream missing channels"))?;
let sample_rate = params
.sample_rate
.ok_or_else(|| Error::invalid("avi muxer: audio stream missing sample_rate"))?;
let avg_bytes_per_sec = params.bit_rate.map(|b| (b / 8) as u32).unwrap_or(0);
let block_align: u16 = 1;
let strf = write_waveformatex(
format_tag,
channels,
sample_rate,
avg_bytes_per_sec,
block_align,
0,
¶ms.extradata,
);
Ok(StrfEntry {
chunk_suffix: *b"wb",
handler_fourcc: *b"\0\0\0\0",
strf,
strh_type: *b"auds",
sample_size: 0,
scale: 1,
rate: sample_rate,
})
}
fn video_scale_rate(params: &CodecParameters) -> (u32, u32) {
if let Some(fr) = params.frame_rate {
let num = fr.num.max(1) as u32;
let den = fr.den.max(1) as u32;
return (den, num);
}
(1, 25) }
fn uppercase4(s: &[u8; 4]) -> [u8; 4] {
let mut out = *s;
for b in out.iter_mut() {
if b.is_ascii_lowercase() {
*b -= 32;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn video_mapping() {
assert_eq!(video_codec_id(b"MJPG").as_str(), "mjpeg");
assert_eq!(video_codec_id(b"mjpg").as_str(), "mjpeg");
assert_eq!(video_codec_id(b"FFV1").as_str(), "ffv1");
assert_eq!(video_codec_id(&[0, 0, 0, 0]).as_str(), "rgb24");
assert_eq!(video_codec_id(b"XVID").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"xvid").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"DIVX").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"divx").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"DX50").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"MP4V").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"FMP4").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"fmp4").as_str(), "mpeg4video");
assert_eq!(video_codec_id(b"H263").as_str(), "h263");
assert_eq!(video_codec_id(b"h263").as_str(), "h263");
assert_eq!(video_codec_id(b"U263").as_str(), "h263");
assert_eq!(video_codec_id(b"M263").as_str(), "h263");
assert_eq!(video_codec_id(b"MPG1").as_str(), "mpeg1video");
assert_eq!(video_codec_id(b"mpg1").as_str(), "mpeg1video");
assert_eq!(video_codec_id(b"MPEG").as_str(), "mpeg1video");
}
#[test]
fn video_mapping_extended() {
assert_eq!(video_codec_id(b"H264").as_str(), "h264");
assert_eq!(video_codec_id(b"avc1").as_str(), "h264");
assert_eq!(video_codec_id(b"HEVC").as_str(), "h265");
assert_eq!(video_codec_id(b"hev1").as_str(), "h265");
assert_eq!(video_codec_id(b"VP80").as_str(), "vp8");
assert_eq!(video_codec_id(b"VP90").as_str(), "vp9");
assert_eq!(video_codec_id(b"AV01").as_str(), "av1");
assert_eq!(video_codec_id(b"MPG2").as_str(), "mpeg2video");
assert_eq!(video_codec_id(b"mp2v").as_str(), "mpeg2video");
assert_eq!(video_codec_id(b"YUY2").as_str(), "yuyv422");
assert_eq!(video_codec_id(b"UYVY").as_str(), "uyvy422");
assert_eq!(video_codec_id(b"NV12").as_str(), "nv12");
assert_eq!(video_codec_id(b"I420").as_str(), "yuv420p");
assert_eq!(video_codec_id(b"YV12").as_str(), "yuv420p");
assert_eq!(video_codec_id(b"DVSD").as_str(), "dv");
assert_eq!(video_codec_id(b"APCH").as_str(), "prores");
assert_eq!(video_codec_id(b"WMV3").as_str(), "wmv3");
assert_eq!(video_codec_id(b"WVC1").as_str(), "vc1");
assert_eq!(video_codec_id(b"CVID").as_str(), "cinepak");
assert_eq!(video_codec_id(b"IV41").as_str(), "indeo4");
assert_eq!(video_codec_id(b"SVQ3").as_str(), "svq3");
assert_eq!(video_codec_id(b"FLV1").as_str(), "flv1");
}
#[test]
fn audio_mapping() {
assert_eq!(audio_codec_id(0x0001).as_str(), "pcm_s16le");
assert_eq!(audio_codec_id(0x0055).as_str(), "mp3");
}
#[test]
fn audio_mapping_extended() {
assert_eq!(audio_codec_id_full(0x0001, 8).as_str(), "pcm_u8");
assert_eq!(audio_codec_id_full(0x0001, 16).as_str(), "pcm_s16le");
assert_eq!(audio_codec_id_full(0x0001, 24).as_str(), "pcm_s24le");
assert_eq!(audio_codec_id_full(0x0001, 32).as_str(), "pcm_s32le");
assert_eq!(audio_codec_id_full(0x0003, 32).as_str(), "pcm_f32le");
assert_eq!(audio_codec_id_full(0x0003, 64).as_str(), "pcm_f64le");
assert_eq!(audio_codec_id(0x0006).as_str(), "pcm_alaw");
assert_eq!(audio_codec_id(0x0007).as_str(), "pcm_mulaw");
assert_eq!(audio_codec_id(0x0002).as_str(), "adpcm_ms");
assert_eq!(audio_codec_id(0x0011).as_str(), "adpcm_ima_wav");
assert_eq!(audio_codec_id(0x0050).as_str(), "mp2");
assert_eq!(audio_codec_id(0x0055).as_str(), "mp3");
assert_eq!(audio_codec_id(0x00FF).as_str(), "aac");
assert_eq!(audio_codec_id(0x2000).as_str(), "ac3");
assert_eq!(audio_codec_id(0x2001).as_str(), "eac3");
assert_eq!(audio_codec_id(0x0160).as_str(), "wmav1");
assert_eq!(audio_codec_id(0x0161).as_str(), "wmav2");
assert_eq!(audio_codec_id(0xABCD).as_str(), "avi:tag_abcd");
}
#[test]
fn unsupported_codec() {
let p = CodecParameters::audio(CodecId::new("opus"));
assert!(build_strf(&p).is_err());
}
#[test]
fn mux_pcm_variants() {
use oxideav_core::Rational;
for id in &[
"pcm_u8",
"pcm_s16le",
"pcm_s24le",
"pcm_s32le",
"pcm_f32le",
"pcm_f64le",
"pcm_alaw",
"pcm_mulaw",
] {
let mut p = CodecParameters::audio(CodecId::new(*id));
p.channels = Some(2);
p.sample_rate = Some(48_000);
let e = build_strf(&p).expect(id);
assert_eq!(&e.strh_type, b"auds", "{id}");
assert!(e.rate == 48_000, "{id}");
}
for id in &["mpeg4video", "h264", "h265", "h263", "vp8", "vp9", "av1"] {
let mut p = CodecParameters::video(CodecId::new(*id));
p.width = Some(320);
p.height = Some(240);
p.frame_rate = Some(Rational::new(25, 1));
let e = build_strf(&p).expect(id);
assert_eq!(&e.strh_type, b"vids", "{id}");
assert_eq!(&e.chunk_suffix, b"dc", "{id}");
}
}
}