use vacc_core::VideoCodec;
pub fn detect_codec(data: &[u8]) -> Option<VideoCodec> {
if data.len() >= 32 && &data[0..4] == b"DKIF" {
return match &data[8..12] {
b"AV01" => Some(VideoCodec::DecodeAv1),
_ => Some(VideoCodec::DecodeVp9),
};
}
if detect_av1_obu(data) {
return Some(VideoCodec::DecodeAv1);
}
for &b in data.iter().take(256) {
if b == 0 {
continue;
}
if b & 0xC0 == 0x80 {
return Some(VideoCodec::DecodeVp9);
}
break;
}
for i in 0..data.len().min(4096) {
let start = if i + 4 <= data.len() && data[i..i + 4] == [0x00, 0x00, 0x00, 0x01] {
i + 4
} else if i + 3 <= data.len() && data[i..i + 3] == [0x00, 0x00, 0x01] {
i + 3
} else {
continue;
};
if start >= data.len() {
continue;
}
let b0 = data[start];
let h265_nal_type = (b0 >> 1) & 0x3F;
if h265_nal_type == 32 || h265_nal_type == 33 || h265_nal_type == 34 {
return Some(VideoCodec::DecodeH265);
}
let h264_nal_type = b0 & 0x1F;
if h264_nal_type == 7 || h264_nal_type == 8 {
return Some(VideoCodec::DecodeH264);
}
}
None
}
fn detect_av1_obu(data: &[u8]) -> bool {
detect_av1_annexb(data) || detect_av1_low_overhead(data)
}
fn read_leb128(data: &[u8], pos: usize) -> Option<(usize, usize)> {
let mut value = 0usize;
let mut shift = 0;
let mut i = pos;
while i - pos < 8 {
let &b = data.get(i)?;
i += 1;
value = value.checked_add(((b & 0x7F) as usize) << shift)?;
if b & 0x80 == 0x00 {
return Some((value, i - pos));
}
shift += 7;
}
None
}
fn av1_obu_header(data: &[u8], pos: usize) -> Option<(u8, bool, usize)> {
let &b0 = data.get(pos)?;
if b0 & 0x80 != 0x00 {
return None; }
let obu_type = (b0 >> 3) & 0x0F;
if !(1..=8).contains(&obu_type) {
return None;
}
let has_size = b0 & 0x02 != 0x00;
Some((obu_type, has_size, pos + 1 + usize::from(b0 & 0x04 != 0x00)))
}
fn detect_av1_annexb(data: &[u8]) -> bool {
let Some((tu, n)) = read_leb128(data, 0) else { return false };
if tu == 0 {
return false;
}
let Some((fu, m)) = read_leb128(data, n) else { return false };
if fu == 0 || fu > tu {
return false;
}
let Some((len, k)) = read_leb128(data, n + m) else { return false };
if len == 0 || len > fu {
return false;
}
let Some((obu_type, _, _)) = av1_obu_header(data, n + m + k) else { return false };
obu_type == 2
}
fn detect_av1_low_overhead(data: &[u8]) -> bool {
let mut i = 0;
let mut seen_delimiter = false;
let mut seen_sequence = false;
while i < data.len() {
let (obu_type, has_size, hdr_end) = match av1_obu_header(data, i) {
Some(h) => h,
None => break,
};
if !has_size {
break;
}
let (size, n) = match read_leb128(data, hdr_end) {
Some(v) => v,
None => break,
};
seen_delimiter |= obu_type == 2;
seen_sequence |= obu_type == 1;
let end = match hdr_end.checked_add(n).and_then(|e| e.checked_add(size)) {
Some(e) => e,
None => break,
};
if end > data.len() {
break; }
i = end;
}
seen_delimiter && seen_sequence
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(name: &str) -> Vec<u8> {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/samples/");
std::fs::read(format!("{path}{name}")).unwrap()
}
#[test]
fn detects_all_codecs() {
assert_eq!(detect_codec(&sample("h264_main.h264")), Some(VideoCodec::DecodeH264));
assert_eq!(detect_codec(&sample("h265_main.h265")), Some(VideoCodec::DecodeH265));
assert_eq!(detect_codec(&sample("vp9_profile1_444.ivf")), Some(VideoCodec::DecodeVp9));
assert_eq!(detect_codec(&sample("av1_main.ivf")), Some(VideoCodec::DecodeAv1));
}
#[test]
fn rejects_unknown_data() {
assert_eq!(detect_codec(&[]), None);
assert_eq!(detect_codec(&[0u8; 64]), None);
assert_eq!(detect_codec(&[1u8, 2, 3, 4, 5]), None);
}
#[test]
fn detects_raw_av1_low_overhead() {
let stream: Vec<u8> = [
0x12, 0x00, 0x0A, 0x03, 0xAA, 0xBB, 0xCC, 0x32, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, ]
.to_vec();
assert_eq!(detect_codec(&stream), Some(VideoCodec::DecodeAv1));
assert_eq!(detect_codec(&stream[..6]), Some(VideoCodec::DecodeAv1));
}
#[test]
fn detects_raw_av1_annexb() {
let stream: Vec<u8> = [
0x06, 0x06, 0x04, 0x10, 0xAA, 0xBB, 0xCC, ]
.to_vec();
assert_eq!(detect_codec(&stream), Some(VideoCodec::DecodeAv1));
}
#[test]
fn raw_av1_probe_rejects_annexb_nal() {
let h264: Vec<u8> = [0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xC0, 0x1E].to_vec();
assert!(!detect_av1_obu(&h264));
}
}