use std::collections::HashMap;
use std::io::{Read, Write};
use std::path::Path;
use super::codec::VideoDecoder as _;
use super::error::VideoError;
use super::frame::Rgb8Frame;
#[derive(Debug, Clone)]
pub struct VideoMeta {
pub width: u32,
pub height: u32,
pub frame_count: u32,
pub fps: f32,
pub properties: HashMap<String, String>,
}
pub struct RawVideoReader {
pub meta: VideoMeta,
data: Vec<u8>,
frame_offset: usize,
current_frame: u32,
}
const MAGIC: &[u8; 8] = b"RCVVIDEO";
impl RawVideoReader {
pub fn open(path: &Path) -> Result<Self, VideoError> {
let mut file = std::fs::File::open(path)
.map_err(|e| VideoError::Source(format!("{}: {e}", path.display())))?;
let file_size = file.metadata().map(|m| m.len()).unwrap_or(0);
if file_size > 2 * 1024 * 1024 * 1024 {
return Err(VideoError::Source("raw video file too large (>2GB)".into()));
}
let mut data = Vec::new();
file.read_to_end(&mut data)
.map_err(|e| VideoError::Source(e.to_string()))?;
if data.len() < 24 || &data[..8] != MAGIC {
return Err(VideoError::Source("invalid raw video file header".into()));
}
let width = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let height = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let frame_count = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
let fps = f32::from_le_bytes([data[20], data[21], data[22], data[23]]);
Ok(Self {
meta: VideoMeta {
width,
height,
frame_count,
fps,
properties: HashMap::new(),
},
data,
frame_offset: 24,
current_frame: 0,
})
}
pub fn next_frame(&mut self) -> Option<Rgb8Frame> {
if self.current_frame >= self.meta.frame_count {
return None;
}
let frame_size = (self.meta.width as usize)
.checked_mul(self.meta.height as usize)
.and_then(|x| x.checked_mul(3))?;
let start = self
.frame_offset
.checked_add((self.current_frame as usize).checked_mul(frame_size)?)?;
let end = start + frame_size;
if end > self.data.len() {
return None;
}
self.current_frame += 1;
Rgb8Frame::from_bytes(
self.current_frame as u64 - 1,
0,
self.meta.width as usize,
self.meta.height as usize,
bytes::Bytes::copy_from_slice(&self.data[start..end]),
)
.ok()
}
pub fn seek_start(&mut self) {
self.current_frame = 0;
}
pub fn frame_count(&self) -> u32 {
self.meta.frame_count
}
}
pub struct RawVideoWriter {
width: u32,
height: u32,
fps: f32,
frames: Vec<Vec<u8>>,
}
impl RawVideoWriter {
pub fn new(width: u32, height: u32, fps: f32) -> Self {
Self {
width,
height,
fps,
frames: Vec::new(),
}
}
pub fn push_frame(&mut self, rgb8_data: &[u8]) -> Result<(), VideoError> {
let expected = self.width as usize * self.height as usize * 3;
if rgb8_data.len() != expected {
return Err(VideoError::Source(format!(
"frame size mismatch: expected {expected}, got {}",
rgb8_data.len()
)));
}
self.frames.push(rgb8_data.to_vec());
Ok(())
}
pub fn save(&self, path: &Path) -> Result<(), VideoError> {
let mut file = std::fs::File::create(path)
.map_err(|e| VideoError::Source(format!("{}: {e}", path.display())))?;
let wr = |f: &mut std::fs::File, d: &[u8]| -> Result<(), VideoError> {
f.write_all(d)
.map_err(|e| VideoError::Source(e.to_string()))
};
wr(&mut file, MAGIC)?;
wr(&mut file, &self.width.to_le_bytes())?;
wr(&mut file, &self.height.to_le_bytes())?;
wr(&mut file, &(self.frames.len() as u32).to_le_bytes())?;
wr(&mut file, &self.fps.to_le_bytes())?;
for frame in &self.frames {
wr(&mut file, frame)?;
}
Ok(())
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}
pub struct ImageSequenceReader {
pub width: usize,
pub height: usize,
paths: Vec<std::path::PathBuf>,
current: usize,
}
impl ImageSequenceReader {
pub fn from_paths(paths: Vec<std::path::PathBuf>) -> Self {
Self {
width: 0,
height: 0,
paths,
current: 0,
}
}
pub fn frame_count(&self) -> usize {
self.paths.len()
}
pub fn seek_start(&mut self) {
self.current = 0;
}
pub fn next_path(&mut self) -> Option<&Path> {
if self.current >= self.paths.len() {
return None;
}
let path = &self.paths[self.current];
self.current += 1;
Some(path)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mp4Codec {
H264,
Hevc,
}
enum Mp4Decoder {
H264(super::h264_decoder::H264Decoder),
Hevc(super::hevc_decoder::HevcDecoder),
Hw(super::hw_decode::HwVideoDecoder),
}
pub struct Mp4VideoReader {
decoder: Mp4Decoder,
codec: Mp4Codec,
file: Option<std::fs::File>,
audio: Option<super::audio::AudioTrackInfo>,
param_nals_h264: Vec<super::codec::NalUnit>,
param_nals_hevc: Vec<Vec<u8>>,
sample_table: Vec<(u64, u32)>,
nal_length_size: usize,
current_sample: usize,
params_fed: bool,
hw_init_au: Option<Vec<u8>>,
hw_sample_idx: usize,
}
impl Mp4VideoReader {
pub fn open(path: &Path) -> Result<Self, VideoError> {
use std::io::{Read as _, Seek, SeekFrom};
let mut file = std::fs::File::open(path)
.map_err(|e| VideoError::Source(format!("{}: {e}", path.display())))?;
let file_size = file.metadata().map(|m| m.len()).unwrap_or(0);
let mut moov_data: Vec<u8> = Vec::new();
let mut pos = 0u64;
let mut header_buf = [0u8; 8];
#[allow(unused_assignments)]
let mut detected_codec = Mp4Codec::H264;
while pos < file_size {
file.seek(SeekFrom::Start(pos))
.map_err(|e| VideoError::Source(format!("seek: {e}")))?;
if file.read_exact(&mut header_buf).is_err() {
break;
}
let box_size =
u32::from_be_bytes([header_buf[0], header_buf[1], header_buf[2], header_buf[3]])
as u64;
let box_type = &header_buf[4..8];
let real_size = if box_size == 1 {
let mut ext = [0u8; 8];
let _ = file.read_exact(&mut ext);
u64::from_be_bytes(ext)
} else if box_size == 0 {
file_size - pos
} else {
box_size
};
if box_type == b"moov" {
let content_size = (real_size - 8) as usize;
moov_data.resize(content_size, 0);
let _ = file.read_exact(&mut moov_data);
break;
}
pos += real_size.max(8);
}
if moov_data.is_empty() {
return Err(VideoError::ContainerParse("no moov box found".into()));
}
detected_codec = if moov_data.windows(4).any(|w| w == b"hvc1" || w == b"hev1") {
Mp4Codec::Hevc
} else {
Mp4Codec::H264
};
let nal_length_size = find_nal_length_size(&moov_data, detected_codec);
let sample_table = build_sample_table(&moov_data)?;
if sample_table.is_empty() {
return Err(VideoError::ContainerParse("no video samples found".into()));
}
let mut param_nals_h264 = Vec::new();
let mut param_nals_hevc = Vec::new();
match detected_codec {
Mp4Codec::Hevc => {
param_nals_hevc = extract_hvcc_nals(&moov_data);
}
Mp4Codec::H264 => {
param_nals_h264 = extract_avcc_nals(&moov_data);
}
}
let decoder = match detected_codec {
Mp4Codec::H264 => Mp4Decoder::H264(super::h264_decoder::H264Decoder::new()),
Mp4Codec::Hevc => Mp4Decoder::Hevc(super::hevc_decoder::HevcDecoder::new()),
};
let audio = find_audio_trak(&moov_data).and_then(parse_mp4_audio_info);
Ok(Self {
decoder,
codec: detected_codec,
file: Some(file),
audio,
param_nals_h264,
param_nals_hevc,
sample_table,
nal_length_size,
current_sample: 0,
params_fed: false,
hw_init_au: None,
hw_sample_idx: 0,
})
}
pub fn open_hw(path: &std::path::Path) -> Result<Self, VideoError> {
let mut reader = Self::open(path)?;
let vc = match reader.codec {
Mp4Codec::H264 => crate::VideoCodec::H264,
Mp4Codec::Hevc => crate::VideoCodec::H265,
};
let hw = super::hw_decode::HwVideoDecoder::new(vc)?;
if hw.is_hardware() {
let mut init_au = Vec::new();
match reader.codec {
Mp4Codec::H264 => {
for nal in &reader.param_nals_h264 {
init_au.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
init_au.extend_from_slice(&nal.data);
}
}
Mp4Codec::Hevc => {
for nal in &reader.param_nals_hevc {
init_au.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
init_au.extend_from_slice(nal);
}
}
}
reader.hw_init_au = Some(init_au);
reader.decoder = Mp4Decoder::Hw(hw);
}
Ok(reader)
}
pub fn hw_backend(&self) -> super::hw_decode::HwBackend {
match &self.decoder {
Mp4Decoder::Hw(hw) => hw.backend(),
_ => super::hw_decode::HwBackend::Software,
}
}
fn read_sample(&mut self, idx: usize) -> Result<Vec<u8>, VideoError> {
use std::io::{Read as _, Seek, SeekFrom};
let (offset, size) = self.sample_table[idx];
let file = self
.file
.as_mut()
.ok_or_else(|| VideoError::Source("file handle closed".into()))?;
file.seek(SeekFrom::Start(offset))
.map_err(|e| VideoError::Source(format!("seek: {e}")))?;
let mut buf = vec![0u8; size as usize];
file.read_exact(&mut buf)
.map_err(|e| VideoError::Source(format!("read sample: {e}")))?;
Ok(buf)
}
pub fn next_frame(&mut self) -> Result<Option<super::codec::DecodedFrame>, VideoError> {
if matches!(self.decoder, Mp4Decoder::Hw(_)) {
while self.hw_sample_idx < self.sample_table.len() {
let idx = self.hw_sample_idx;
self.hw_sample_idx += 1;
let sample_data = self.read_sample(idx)?;
let nals = parse_length_prefixed_nals(&sample_data, self.nal_length_size);
let mut au = if idx == 0 {
self.hw_init_au.take().unwrap_or_default()
} else {
Vec::new()
};
for nal_data in nals {
au.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
au.extend_from_slice(&nal_data);
}
if !au.is_empty()
&& let Mp4Decoder::Hw(ref mut hw) = self.decoder
&& let Some(frame) = hw.decode(&au, 0)?
{
return Ok(Some(frame));
}
}
return Ok(None);
}
if !self.params_fed {
self.params_fed = true;
match self.codec {
Mp4Codec::H264 => {
let Mp4Decoder::H264(ref mut dec) = self.decoder else {
return Ok(None);
};
for nal in &self.param_nals_h264 {
let _ = dec.process_nal(nal);
}
}
Mp4Codec::Hevc => {
let Mp4Decoder::Hevc(ref mut dec) = self.decoder else {
return Ok(None);
};
for nal in &self.param_nals_hevc {
let _ = dec.decode_nal(nal);
}
}
}
}
while self.current_sample < self.sample_table.len() {
let sample_data = self.read_sample(self.current_sample)?;
self.current_sample += 1;
let nals = parse_length_prefixed_nals(&sample_data, self.nal_length_size);
for nal_data in nals {
if nal_data.is_empty() {
continue;
}
match self.codec {
Mp4Codec::H264 => {
let Mp4Decoder::H264(ref mut dec) = self.decoder else {
continue;
};
let header = nal_data[0];
let nal = super::codec::NalUnit {
nal_type: super::codec::NalUnitType::from_byte(header),
nal_ref_idc: (header >> 5) & 3,
data: nal_data,
};
if let Some(frame) = dec.process_nal(&nal)? {
return Ok(Some(frame));
}
}
Mp4Codec::Hevc => {
let Mp4Decoder::Hevc(ref mut dec) = self.decoder else {
continue;
};
if let Some(frame) = dec.decode_nal(&nal_data)? {
return Ok(Some(frame));
}
}
}
}
}
Ok(None)
}
pub fn next_frame_luma_only(
&mut self,
) -> Result<Option<super::codec::DecodedFrame>, VideoError> {
if let Mp4Decoder::Hevc(ref mut dec) = self.decoder {
dec.skip_rgb = true;
}
self.next_frame()
}
pub fn seek_start(&mut self) {
self.current_sample = 0;
self.hw_sample_idx = 0;
self.params_fed = false;
match self.codec {
Mp4Codec::H264 => {
self.decoder = Mp4Decoder::H264(super::h264_decoder::H264Decoder::new());
}
Mp4Codec::Hevc => {
self.decoder = Mp4Decoder::Hevc(super::hevc_decoder::HevcDecoder::new());
}
}
}
pub fn nal_count(&self) -> usize {
self.sample_table.len()
}
pub fn codec(&self) -> super::codec::VideoCodec {
match self.codec {
Mp4Codec::H264 => super::codec::VideoCodec::H264,
Mp4Codec::Hevc => super::codec::VideoCodec::H265,
}
}
pub fn audio_info(&self) -> Option<&super::audio::AudioTrackInfo> {
self.audio.as_ref()
}
}
fn find_box_data<'a>(data: &'a [u8], tag: &[u8; 4]) -> Option<&'a [u8]> {
let mut i = 0;
while i + 8 <= data.len() {
let sz = u32::from_be_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]) as usize;
if sz < 8 || i + sz > data.len() {
break;
}
if &data[i + 4..i + 8] == tag {
return Some(&data[i + 8..i + sz]);
}
let box_tag = &data[i + 4..i + 8];
if (box_tag == b"trak" || box_tag == b"mdia" || box_tag == b"minf" || box_tag == b"stbl")
&& let Some(found) = find_box_data(&data[i + 8..i + sz], tag)
{
return Some(found);
}
i += sz;
}
None
}
fn find_video_trak(moov_data: &[u8]) -> Option<&[u8]> {
let mut i = 0;
while i + 8 <= moov_data.len() {
let sz = u32::from_be_bytes([
moov_data[i],
moov_data[i + 1],
moov_data[i + 2],
moov_data[i + 3],
]) as usize;
if sz < 8 || i + sz > moov_data.len() {
break;
}
if &moov_data[i + 4..i + 8] == b"trak" {
let trak_data = &moov_data[i + 8..i + sz];
if find_box_data(trak_data, b"vmhd").is_some() {
return Some(trak_data);
}
}
i += sz;
}
None
}
fn find_audio_trak(moov_data: &[u8]) -> Option<&[u8]> {
let mut i = 0;
while i + 8 <= moov_data.len() {
let sz = u32::from_be_bytes([
moov_data[i],
moov_data[i + 1],
moov_data[i + 2],
moov_data[i + 3],
]) as usize;
if sz < 8 || i + sz > moov_data.len() {
break;
}
if &moov_data[i + 4..i + 8] == b"trak" {
let trak_data = &moov_data[i + 8..i + sz];
if find_box_data(trak_data, b"smhd").is_some() {
return Some(trak_data);
}
}
i += sz;
}
None
}
fn parse_mp4_audio_info(trak_data: &[u8]) -> Option<super::audio::AudioTrackInfo> {
let stsd = find_box_data(trak_data, b"stsd")?;
if stsd.len() < 8 {
return None;
}
let entry_data = &stsd[8..];
let mut codec = super::audio::AudioCodec::Unknown;
let mut sample_rate = 0u32;
let mut channels = 0u16;
for i in 0..entry_data.len().saturating_sub(32) {
let tag = &entry_data[i..i + 4];
if tag == b"mp4a" || tag == b"alac" || tag == b"Opus" || tag == b"fLaC" {
codec = super::audio::audio_codec_from_mp4(tag.try_into().unwrap_or(&[0; 4]));
let base = i + 4; if base + 28 <= entry_data.len() {
channels = u16::from_be_bytes([entry_data[base + 16], entry_data[base + 17]]);
let sr_fixed = u32::from_be_bytes([
entry_data[base + 24],
entry_data[base + 25],
entry_data[base + 26],
entry_data[base + 27],
]);
sample_rate = sr_fixed >> 16;
}
break;
}
}
if codec == super::audio::AudioCodec::Unknown {
return None;
}
Some(super::audio::AudioTrackInfo {
codec,
sample_rate,
channels,
bits_per_sample: 0,
duration_ms: 0,
codec_private: Vec::new(),
})
}
fn find_nal_length_size(moov_data: &[u8], codec: Mp4Codec) -> usize {
let tag = match codec {
Mp4Codec::H264 => b"avcC",
Mp4Codec::Hevc => b"hvcC",
};
for i in 0..moov_data.len().saturating_sub(8) {
if &moov_data[i..i + 4] == tag {
let config = &moov_data[i + 4..];
if config.len() >= 5 {
return match codec {
Mp4Codec::H264 => (config[4] & 0x03) as usize + 1,
Mp4Codec::Hevc => (config[21] & 0x03) as usize + 1,
};
}
}
}
4 }
fn parse_stco(box_data: &[u8]) -> Vec<u64> {
if box_data.len() < 8 {
return Vec::new();
}
let count = u32::from_be_bytes([box_data[4], box_data[5], box_data[6], box_data[7]]) as usize;
let mut offsets = Vec::with_capacity(count);
for j in 0..count {
let pos = 8 + j * 4;
if pos + 4 > box_data.len() {
break;
}
offsets.push(u32::from_be_bytes([
box_data[pos],
box_data[pos + 1],
box_data[pos + 2],
box_data[pos + 3],
]) as u64);
}
offsets
}
fn parse_co64(box_data: &[u8]) -> Vec<u64> {
if box_data.len() < 8 {
return Vec::new();
}
let count = u32::from_be_bytes([box_data[4], box_data[5], box_data[6], box_data[7]]) as usize;
let mut offsets = Vec::with_capacity(count);
for j in 0..count {
let pos = 8 + j * 8;
if pos + 8 > box_data.len() {
break;
}
offsets.push(u64::from_be_bytes([
box_data[pos],
box_data[pos + 1],
box_data[pos + 2],
box_data[pos + 3],
box_data[pos + 4],
box_data[pos + 5],
box_data[pos + 6],
box_data[pos + 7],
]));
}
offsets
}
fn parse_stsz(box_data: &[u8]) -> Vec<u32> {
if box_data.len() < 12 {
return Vec::new();
}
let default_size = u32::from_be_bytes([box_data[4], box_data[5], box_data[6], box_data[7]]);
let count = u32::from_be_bytes([box_data[8], box_data[9], box_data[10], box_data[11]]) as usize;
if default_size > 0 {
return vec![default_size; count];
}
let mut sizes = Vec::with_capacity(count);
for j in 0..count {
let pos = 12 + j * 4;
if pos + 4 > box_data.len() {
break;
}
sizes.push(u32::from_be_bytes([
box_data[pos],
box_data[pos + 1],
box_data[pos + 2],
box_data[pos + 3],
]));
}
sizes
}
fn parse_stsc(box_data: &[u8]) -> Vec<(u32, u32, u32)> {
if box_data.len() < 8 {
return Vec::new();
}
let count = u32::from_be_bytes([box_data[4], box_data[5], box_data[6], box_data[7]]) as usize;
let mut entries = Vec::with_capacity(count);
for j in 0..count {
let pos = 8 + j * 12;
if pos + 12 > box_data.len() {
break;
}
let first_chunk = u32::from_be_bytes([
box_data[pos],
box_data[pos + 1],
box_data[pos + 2],
box_data[pos + 3],
]);
let spc = u32::from_be_bytes([
box_data[pos + 4],
box_data[pos + 5],
box_data[pos + 6],
box_data[pos + 7],
]);
let sdi = u32::from_be_bytes([
box_data[pos + 8],
box_data[pos + 9],
box_data[pos + 10],
box_data[pos + 11],
]);
entries.push((first_chunk, spc, sdi));
}
entries
}
fn build_sample_table(moov_data: &[u8]) -> Result<Vec<(u64, u32)>, VideoError> {
let video_trak = find_video_trak(moov_data)
.ok_or_else(|| VideoError::ContainerParse("no video trak found".into()))?;
let chunk_offsets = if let Some(co64_data) = find_box_data(video_trak, b"co64") {
parse_co64(co64_data)
} else if let Some(stco_data) = find_box_data(video_trak, b"stco") {
parse_stco(stco_data)
} else {
return Err(VideoError::ContainerParse("no stco/co64".into()));
};
let sample_sizes = if let Some(stsz_data) = find_box_data(video_trak, b"stsz") {
parse_stsz(stsz_data)
} else {
return Err(VideoError::ContainerParse("no stsz".into()));
};
let stsc_entries = if let Some(stsc_data) = find_box_data(video_trak, b"stsc") {
parse_stsc(stsc_data)
} else {
vec![(1, 1, 1)]
};
let num_samples = sample_sizes.len();
let num_chunks = chunk_offsets.len();
let mut table = Vec::with_capacity(num_samples);
let mut sample_idx = 0usize;
for chunk_idx in 0..num_chunks {
let chunk_num = chunk_idx as u32 + 1;
let mut samples_in_chunk = 1u32;
for entry in stsc_entries.iter().rev() {
if chunk_num >= entry.0 {
samples_in_chunk = entry.1;
break;
}
}
let mut offset = chunk_offsets[chunk_idx];
for _ in 0..samples_in_chunk {
if sample_idx >= num_samples {
break;
}
let sz = sample_sizes[sample_idx];
table.push((offset, sz));
offset += sz as u64;
sample_idx += 1;
}
}
Ok(table)
}
fn parse_length_prefixed_nals(sample: &[u8], nal_length_size: usize) -> Vec<Vec<u8>> {
let mut nals = Vec::new();
let mut pos = 0;
while pos + nal_length_size <= sample.len() {
let nal_len = match nal_length_size {
1 => sample[pos] as usize,
2 => u16::from_be_bytes([sample[pos], sample[pos + 1]]) as usize,
4 => u32::from_be_bytes([
sample[pos],
sample[pos + 1],
sample[pos + 2],
sample[pos + 3],
]) as usize,
_ => break,
};
pos += nal_length_size;
if nal_len == 0 || pos + nal_len > sample.len() {
break;
}
nals.push(sample[pos..pos + nal_len].to_vec());
pos += nal_len;
}
nals
}
fn extract_hvcc_nals(moov_data: &[u8]) -> Vec<Vec<u8>> {
let mut nals = Vec::new();
for i in 0..moov_data.len().saturating_sub(4) {
if &moov_data[i..i + 4] == b"hvcC" {
let config_start = i + 4;
if config_start + 23 > moov_data.len() {
break;
}
let num_arrays = moov_data[config_start + 22];
let mut pos = config_start + 23;
for _ in 0..num_arrays {
if pos + 3 > moov_data.len() {
break;
}
let _array_completeness_and_type = moov_data[pos];
pos += 1;
let num_nalus = u16::from_be_bytes([moov_data[pos], moov_data[pos + 1]]) as usize;
pos += 2;
for _ in 0..num_nalus {
if pos + 2 > moov_data.len() {
break;
}
let nal_len = u16::from_be_bytes([moov_data[pos], moov_data[pos + 1]]) as usize;
pos += 2;
if pos + nal_len > moov_data.len() {
break;
}
nals.push(moov_data[pos..pos + nal_len].to_vec());
pos += nal_len;
}
}
break;
}
}
nals
}
pub(crate) fn extract_avcc_nals(moov_data: &[u8]) -> Vec<super::codec::NalUnit> {
let mut nals = Vec::new();
for i in 0..moov_data.len().saturating_sub(4) {
if &moov_data[i..i + 4] == b"avcC" {
let config_start = i + 4;
if config_start + 6 > moov_data.len() {
break;
}
let num_sps = (moov_data[config_start + 5] & 0x1F) as usize;
let mut pos = config_start + 6;
for _ in 0..num_sps {
if pos + 2 > moov_data.len() {
break;
}
let sps_len = u16::from_be_bytes([moov_data[pos], moov_data[pos + 1]]) as usize;
pos += 2;
if pos + sps_len > moov_data.len() || sps_len == 0 {
break;
}
let header = moov_data[pos];
nals.push(super::codec::NalUnit {
nal_type: super::codec::NalUnitType::Sps,
nal_ref_idc: (header >> 5) & 3,
data: moov_data[pos..pos + sps_len].to_vec(),
});
pos += sps_len;
}
if pos >= moov_data.len() {
break;
}
let num_pps = moov_data[pos] as usize;
pos += 1;
for _ in 0..num_pps {
if pos + 2 > moov_data.len() {
break;
}
let pps_len = u16::from_be_bytes([moov_data[pos], moov_data[pos + 1]]) as usize;
pos += 2;
if pos + pps_len > moov_data.len() || pps_len == 0 {
break;
}
let header = moov_data[pos];
nals.push(super::codec::NalUnit {
nal_type: super::codec::NalUnitType::Pps,
nal_ref_idc: (header >> 5) & 3,
data: moov_data[pos..pos + pps_len].to_vec(),
});
pos += pps_len;
}
break;
}
}
nals
}