use anyhow::{Context, Result, bail};
use codec::frame::{ColorSpace, PixelFormat, StreamInfo};
use crate::demux::AudioTrack;
use crate::streaming::{DemuxHeader, Sample, StreamingDemuxer};
use super::opendml::{locate_stream_indx, parse_ix_chunk, read_avih_total_frames,
read_dmlh_total_frames};
use super::riff::{VideoStream, ascii, find_video_stream, fourcc_to_codec,
scan_top_level_records};
pub(super) enum Backend {
Cursor(Vec<(usize, usize)>),
OpenDml {
samples: Vec<(usize, usize)>,
cursor: usize,
},
}
pub struct AviStreamingDemuxer {
data: Vec<u8>,
pub(super) header: DemuxHeader,
pub(super) backend: Backend,
prefix: [u8; 2],
next_idx: u64,
pixel_format_detected: bool,
}
pub(crate) fn demux_avi_streaming_init(data: &[u8]) -> Result<AviStreamingDemuxer> {
if data.len() < 12 || &data[..4] != b"RIFF" || &data[8..12] != b"AVI " {
bail!("not a RIFF/AVI file");
}
let owned = data.to_vec();
let mut hdrl: Option<(usize, usize)> = None;
let mut movi_lists: Vec<(usize, usize)> = Vec::new();
scan_top_level_records(&owned, &mut hdrl, &mut movi_lists);
let (hdrl_start, hdrl_end) = hdrl.context("AVI: missing hdrl LIST")?;
if movi_lists.is_empty() {
bail!("AVI: missing movi LIST");
}
let video: VideoStream = find_video_stream(&owned[hdrl_start..hdrl_end])
.context("AVI: no video stream found in hdrl")?;
let codec = fourcc_to_codec(&video.handler)
.or_else(|| fourcc_to_codec(&video.compression))
.with_context(|| {
format!(
"AVI: unsupported video fourcc {:?}/{:?}",
ascii(&video.handler),
ascii(&video.compression)
)
})?;
let stream_idx = video.stream_index;
let prefix_str = format!("{:02}", stream_idx);
let prefix_bytes = prefix_str.as_bytes();
if prefix_bytes.len() != 2 {
bail!("AVI: stream index out of range");
}
let prefix = [prefix_bytes[0], prefix_bytes[1]];
let backend =
if let Some(ix_refs) = locate_stream_indx(&owned[hdrl_start..hdrl_end], stream_idx) {
let mut samples: Vec<(usize, usize)> = Vec::new();
for (ix_off, ix_size) in ix_refs {
parse_ix_chunk(&owned, ix_off, ix_size, &prefix, &mut samples);
}
Backend::OpenDml { samples, cursor: 0 }
} else {
Backend::Cursor(movi_lists)
};
let total_frames = read_dmlh_total_frames(&owned[hdrl_start..hdrl_end])
.or_else(|| read_avih_total_frames(&owned[hdrl_start..hdrl_end]))
.unwrap_or(0);
let duration = if total_frames > 0 && video.frame_rate > 0.0 {
total_frames as f64 / video.frame_rate
} else {
0.0
};
let info = StreamInfo {
codec: codec.clone(),
width: video.width,
height: video.height,
frame_rate: video.frame_rate,
duration,
pixel_format: PixelFormat::Yuv420p,
color_space: ColorSpace::Bt709,
color_metadata: Default::default(),
total_frames,
bitrate: 0,
};
Ok(AviStreamingDemuxer {
data: owned,
header: DemuxHeader { codec, info },
backend,
prefix,
next_idx: 0,
pixel_format_detected: false,
})
}
impl StreamingDemuxer for AviStreamingDemuxer {
fn header(&self) -> &DemuxHeader {
&self.header
}
fn next_video_sample(&mut self) -> Result<Option<Sample>> {
let payload_range = match &mut self.backend {
Backend::OpenDml { samples, cursor } => {
loop {
if *cursor >= samples.len() {
return Ok(None);
}
let (off, size) = samples[*cursor];
*cursor += 1;
let end = off
.checked_add(size)
.ok_or_else(|| anyhow::anyhow!("AVI: ix## entry overflows usize"))?;
if end > self.data.len() {
continue;
}
break Some((off, end));
}
}
Backend::Cursor(walk) => {
loop {
while let Some(&(pos, end)) = walk.last() {
if pos + 8 <= end {
break;
}
walk.pop();
}
let Some(&mut (ref mut pos, end)) = walk.last_mut() else {
return Ok(None);
};
let fcc: [u8; 4] = self.data[*pos..*pos + 4].try_into()?;
let size = u32::from_le_bytes([
self.data[*pos + 4],
self.data[*pos + 5],
self.data[*pos + 6],
self.data[*pos + 7],
]) as usize;
let payload_start = *pos + 8;
let payload_end = payload_start + size;
if payload_end > end || payload_end > self.data.len() {
walk.pop();
continue;
}
*pos = payload_end + (payload_end & 1);
if &fcc == b"LIST" && payload_start + 4 <= payload_end {
let list_type: [u8; 4] =
self.data[payload_start..payload_start + 4].try_into()?;
if &list_type == b"rec " {
walk.push((payload_start + 4, payload_end));
continue;
}
continue; }
if fcc[0] != self.prefix[0] || fcc[1] != self.prefix[1] {
continue; }
let kind = fcc[3];
if kind != b'c' && kind != b'b' {
continue; }
break Some((payload_start, payload_end));
}
}
};
let Some((start, end)) = payload_range else {
return Ok(None);
};
let pts_ticks = self.next_idx as i64;
self.next_idx += 1;
let data = self.data[start..end].to_vec();
if !self.pixel_format_detected {
let detected =
codec::pixel_format::detect(&self.header.codec, std::slice::from_ref(&data));
self.header.info.pixel_format = detected;
self.pixel_format_detected = true;
}
Ok(Some(Sample {
data,
pts_ticks,
duration_ticks: 0,
}))
}
fn audio(&self) -> Option<&AudioTrack> {
None
}
}