1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-06-02 13:39:54 +00:00

Add Mp4Writer and examples/mp4copy

This commit is contained in:
Ian Jun 2020-08-04 22:53:02 +09:00
parent 82f9e2580c
commit 72e8861ac2
8 changed files with 392 additions and 91 deletions

View file

@ -4,7 +4,7 @@ use std::io::prelude::*;
use std::io::{self, BufReader, BufWriter};
use std::path::Path;
use mp4::{AacConfig, AvcConfig, MediaConfig, MediaType, Result, TrackConfig};
use mp4::{AacConfig, AvcConfig, MediaConfig, MediaType, Result, TrackConfig, Mp4Config};
fn main() {
let args: Vec<String> = env::args().collect();
@ -28,11 +28,14 @@ fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
let writer = BufWriter::new(dst_file);
let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?;
let mut mp4_writer = mp4::Mp4Writer::write_header(
let mut mp4_writer = mp4::Mp4Writer::write_start(
writer,
mp4_reader.major_brand(),
mp4_reader.minor_version(),
mp4_reader.compatible_brands(),
&Mp4Config {
major_brand: mp4_reader.major_brand().clone(),
minor_version: mp4_reader.minor_version(),
compatible_brands: mp4_reader.compatible_brands().to_vec(),
timescale: mp4_reader.timescale(),
},
)?;
// TODO interleaving
@ -71,11 +74,11 @@ fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
let sample_id = six + 1;
let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap();
mp4_writer.write_sample(track_id, &sample)?;
println!("copy {}:({})", sample_id, sample);
// println!("copy {}:({})", sample_id, sample);
}
}
mp4_writer.write_tail()?;
mp4_writer.write_end()?;
Ok(())
}

View file

@ -42,7 +42,7 @@ impl Mp4Box for Mp4aBox {
}
fn box_size(&self) -> u64 {
HEADER_SIZE + 8 + 74 + self.esds.box_size()
HEADER_SIZE + 28 + self.esds.box_size()
}
}
@ -124,7 +124,7 @@ impl Mp4Box for EsdsBox {
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE
HEADER_SIZE + HEADER_EXT_SIZE + ESDescriptor::desc_size() as u64
}
}
@ -153,6 +153,8 @@ impl<W: Write> WriteBox<&mut W> for EsdsBox {
write_box_header_ext(writer, self.version, self.flags)?;
self.es_desc.write_desc(writer)?;
Ok(size)
}
}
@ -188,7 +190,7 @@ fn read_desc<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
writer.write_u8(tag)?;
if size > 0x0FFFFFFF {
if size as u64 > std::u32::MAX as u64 {
return Err(Error::InvalidData("invalid descriptor length range"));
}
@ -263,7 +265,14 @@ impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
impl<W: Write> WriteDesc<&mut W> for ESDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
write_desc(writer, Self::desc_tag(), size-1)?;
writer.write_u16::<BigEndian>(self.es_id)?;
writer.write_u8(0)?;
self.dec_config.write_desc(writer)?;
self.sl_config.write_desc(writer)?;
Ok(size)
}
}
@ -315,7 +324,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
let object_type_indication = reader.read_u8()?;
let byte_a = reader.read_u8()?;
let stream_type = byte_a & 0xFC;
let stream_type = (byte_a & 0xFC) >> 2;
let up_stream = byte_a & 0x02;
let buffer_size_db = reader.read_u24::<BigEndian>()?;
let max_bitrate = reader.read_u32::<BigEndian>()?;
@ -323,10 +332,9 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
let dec_specific = DecoderSpecificDescriptor::read_desc(reader)?;
// XXX skip_read
for _ in DecoderConfigDescriptor::desc_size()..size - 1 {
reader.read_u8()?;
}
// XXX skip_size
let skip_size = size - 1 - DecoderConfigDescriptor::desc_size();
skip_read(reader, skip_size as i64)?;
Ok(DecoderConfigDescriptor {
object_type_indication,
@ -343,7 +351,16 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
impl<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
write_desc(writer, Self::desc_tag(), size-1)?;
writer.write_u8(self.object_type_indication)?;
writer.write_u8(self.stream_type << 2 + self.up_stream & 0x02)?;
writer.write_u24::<BigEndian>(self.buffer_size_db)?;
writer.write_u32::<BigEndian>(self.max_bitrate)?;
writer.write_u32::<BigEndian>(self.avg_bitrate)?;
self.dec_specific.write_desc(writer)?;
Ok(size)
}
}
@ -400,7 +417,11 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
impl<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
write_desc(writer, Self::desc_tag(), size-1)?;
writer.write_u8((self.profile << 3) + (self.freq_index >> 1))?;
writer.write_u8((self.freq_index << 7) + (self.chan_conf << 3))?;
Ok(size)
}
}
@ -441,7 +462,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
impl<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
write_desc(writer, Self::desc_tag(), size-1)?;
writer.write_u8(0)?; // pre-defined
Ok(size)
}

View file

@ -8,6 +8,7 @@ pub struct StszBox {
pub version: u8,
pub flags: u32,
pub sample_size: u32,
pub sample_count: u32,
pub sample_sizes: Vec<u32>,
}
@ -43,6 +44,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
version,
flags,
sample_size,
sample_count,
sample_sizes,
})
}
@ -56,7 +58,7 @@ impl<W: Write> WriteBox<&mut W> for StszBox {
write_box_header_ext(writer, self.version, self.flags)?;
writer.write_u32::<BigEndian>(self.sample_size)?;
writer.write_u32::<BigEndian>(self.sample_sizes.len() as u32)?;
writer.write_u32::<BigEndian>(self.sample_count)?;
if self.sample_size == 0 {
for sample_number in self.sample_sizes.iter() {
writer.write_u32::<BigEndian>(*sample_number)?;
@ -79,6 +81,7 @@ mod tests {
version: 0,
flags: 0,
sample_size: 1165,
sample_count: 12,
sample_sizes: vec![],
};
let mut buf = Vec::new();
@ -100,6 +103,7 @@ mod tests {
version: 0,
flags: 0,
sample_size: 0,
sample_count: 9,
sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
};
let mut buf = Vec::new();

View file

@ -15,4 +15,4 @@ mod reader;
pub use reader::Mp4Reader;
mod writer;
pub use writer::Mp4Writer;
pub use writer::{Mp4Config, Mp4Writer};

View file

@ -124,7 +124,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
return Err(Error::TrakNotFound(track_id));
}
if let Some(track) = self.tracks.get(track_id as usize - 1) {
if let Some(ref track) = self.tracks.get(track_id as usize - 1) {
track.read_sample(&mut self.reader, sample_id)
} else {
Err(Error::TrakNotFound(track_id))

View file

@ -1,10 +1,12 @@
use bytes::BytesMut;
use std::convert::TryFrom;
use std::io::{Read, Seek, SeekFrom};
use std::io::{Read, Seek, SeekFrom, Write};
use std::time::Duration;
use std::cmp;
use crate::atoms::trak::TrakBox;
use crate::atoms::*;
use crate::atoms::{avc1::Avc1Box, mp4a::Mp4aBox, smhd::SmhdBox, vmhd::VmhdBox};
use crate::atoms::{avc1::Avc1Box, mp4a::Mp4aBox, smhd::SmhdBox, vmhd::VmhdBox, ctts::CttsBox, stts::SttsEntry, ctts::CttsEntry, stss::StssBox, stco::StcoBox, stsc::StscEntry};
use crate::*;
#[derive(Debug, Clone, PartialEq)]
@ -12,7 +14,6 @@ pub struct TrackConfig {
pub track_type: TrackType,
pub timescale: u32,
pub language: String,
pub media_conf: MediaConfig,
}
@ -53,34 +54,6 @@ pub struct Mp4Track {
}
impl Mp4Track {
pub fn new(track_id: u32, config: &TrackConfig) -> Result<Self> {
let mut trak = TrakBox::default();
trak.tkhd.track_id = track_id;
trak.mdia.mdhd.timescale = config.timescale;
trak.mdia.mdhd.language = config.language.to_owned();
trak.mdia.hdlr.handler_type = config.track_type.into();
match config.media_conf {
MediaConfig::AvcConfig(ref avc_config) => {
trak.tkhd.set_width(avc_config.width);
trak.tkhd.set_height(avc_config.height);
let vmhd = VmhdBox::default();
trak.mdia.minf.vmhd = Some(vmhd);
let avc1 = Avc1Box::new(avc_config);
trak.mdia.minf.stbl.stsd.avc1 = Some(avc1);
}
MediaConfig::AacConfig(ref aac_config) => {
let smhd = SmhdBox::default();
trak.mdia.minf.smhd = Some(smhd);
let mp4a = Mp4aBox::new(aac_config);
trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a);
}
}
Ok(Mp4Track { trak })
}
pub(crate) fn from(trak: &TrakBox) -> Self {
let trak = trak.clone();
Self { trak }
@ -193,7 +166,7 @@ impl Mp4Track {
}
pub fn sample_count(&self) -> u32 {
self.trak.mdia.minf.stbl.stsz.sample_sizes.len() as u32
self.trak.mdia.minf.stbl.stsz.sample_count
}
pub fn video_profile(&self) -> Result<AvcProfile> {
@ -287,13 +260,7 @@ impl Mp4Track {
}
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
assert!(self.trak.mdia.minf.stbl.ctts.is_some());
let ctts = if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts {
ctts
} else {
return Err(Error::BoxInStblNotFound(self.track_id(), BoxType::CttsBox));
};
let ctts = self.trak.mdia.minf.stbl.ctts.as_ref().unwrap();
let mut sample_count = 1;
for (i, entry) in ctts.entries.iter().enumerate() {
if sample_id <= sample_count + entry.sample_count - 1 {
@ -412,12 +379,12 @@ impl Mp4Track {
reader: &mut R,
sample_id: u32,
) -> Result<Option<Mp4Sample>> {
let sample_size = match self.sample_size(sample_id) {
Ok(size) => size,
let sample_offset = match self.sample_offset(sample_id) {
Ok(offset) => offset,
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
Err(err) => return Err(err),
};
let sample_offset = self.sample_offset(sample_id).unwrap(); // XXX
let sample_size = self.sample_size(sample_id).unwrap();
let mut buffer = vec![0x0u8; sample_size as usize];
reader.seek(SeekFrom::Start(sample_offset))?;
@ -436,3 +403,252 @@ impl Mp4Track {
}))
}
}
// TODO creation_time, modification_time
#[derive(Debug, Default)]
pub(crate) struct Mp4TrackWriter {
trak: TrakBox,
sample_id: u32,
fixed_sample_size: u32,
is_fixed_sample_size: bool,
chunk_samples: u32,
chunk_duration: u32,
chunk_buffer: BytesMut,
samples_per_chunk: u32,
duration_per_chunk: u32,
}
impl Mp4TrackWriter {
pub(crate) fn new(track_id: u32, config: &TrackConfig) -> Result<Self> {
let mut trak = TrakBox::default();
trak.tkhd.track_id = track_id;
trak.mdia.mdhd.timescale = config.timescale;
trak.mdia.mdhd.language = config.language.to_owned();
trak.mdia.hdlr.handler_type = config.track_type.into();
// XXX largesize
trak.mdia.minf.stbl.stco = Some(StcoBox::default());
match config.media_conf {
MediaConfig::AvcConfig(ref avc_config) => {
trak.tkhd.set_width(avc_config.width);
trak.tkhd.set_height(avc_config.height);
let vmhd = VmhdBox::default();
trak.mdia.minf.vmhd = Some(vmhd);
let avc1 = Avc1Box::new(avc_config);
trak.mdia.minf.stbl.stsd.avc1 = Some(avc1);
}
MediaConfig::AacConfig(ref aac_config) => {
let smhd = SmhdBox::default();
trak.mdia.minf.smhd = Some(smhd);
let mp4a = Mp4aBox::new(aac_config);
trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a);
}
}
Ok(Mp4TrackWriter {
trak,
chunk_buffer: BytesMut::new(),
sample_id: 1,
duration_per_chunk: config.timescale, // 1 second
..Self::default()
})
}
fn update_sample_sizes(&mut self, size: u32) {
if self.trak.mdia.minf.stbl.stsz.sample_count == 0 {
if size == 0 {
self.trak.mdia.minf.stbl.stsz.sample_size = 0;
self.is_fixed_sample_size = false;
} else {
self.fixed_sample_size = size;
self.is_fixed_sample_size = true;
}
} else {
assert!(self.trak.mdia.minf.stbl.stsz.sample_count > 0);
if !self.is_fixed_sample_size || self.fixed_sample_size != size {
if self.trak.mdia.minf.stbl.stsz.sample_size > 0 {
self.trak.mdia.minf.stbl.stsz.sample_size = 0;
for _ in 0..self.trak.mdia.minf.stbl.stsz.sample_count {
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(self.fixed_sample_size);
}
}
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size);
}
}
self.trak.mdia.minf.stbl.stsz.sample_count += 1;
}
fn update_sample_times(&mut self, dur: u32) {
if let Some(ref mut entry) = self.trak.mdia.minf.stbl.stts.entries.last_mut() {
if entry.sample_delta == dur {
entry.sample_count += 1;
return;
}
}
let entry = SttsEntry {
sample_count: 1,
sample_delta: dur,
};
self.trak.mdia.minf.stbl.stts.entries.push(entry);
}
fn update_rendering_offsets(&mut self, offset: i32) {
let ctts = if let Some(ref mut ctts) = self.trak.mdia.minf.stbl.ctts {
ctts
} else {
if offset == 0 {
return;
}
let mut ctts = CttsBox::default();
if self.sample_id > 1 {
let entry = CttsEntry {
sample_count: self.sample_id - 1,
sample_offset: 0,
};
ctts.entries.push(entry);
}
self.trak.mdia.minf.stbl.ctts = Some(ctts);
self.trak.mdia.minf.stbl.ctts.as_mut().unwrap()
};
if let Some(ref mut entry) = ctts.entries.last_mut() {
if entry.sample_offset == offset {
entry.sample_count += 1;
return;
}
}
let entry = CttsEntry {
sample_count: 1,
sample_offset: offset,
};
ctts.entries.push(entry);
}
fn update_sync_samples(&mut self, is_sync: bool) {
if let Some(ref mut stss) = self.trak.mdia.minf.stbl.stss {
stss.entries.push(self.sample_id);
} else {
if is_sync {
return;
}
let mut stss = StssBox::default();
for i in 1..=self.trak.mdia.minf.stbl.stsz.sample_count {
stss.entries.push(i);
}
self.trak.mdia.minf.stbl.stss = Some(stss);
};
}
fn is_chunk_full(&self) -> bool {
if self.samples_per_chunk > 0 {
self.chunk_samples >= self.samples_per_chunk
} else {
self.chunk_duration >= self.duration_per_chunk
}
}
fn update_durations(&mut self, dur: u32, movie_timescale: u32) {
self.trak.mdia.mdhd.duration += dur as u64;
self.trak.tkhd.duration += dur as u64 * movie_timescale as u64 / self.trak.mdia.mdhd.timescale as u64;
}
pub(crate) fn write_sample<W: Write + Seek>(
&mut self,
writer: &mut W,
sample: &Mp4Sample,
movie_timescale: u32,
) -> Result<u64> {
self.chunk_buffer.extend_from_slice(&sample.bytes);
self.chunk_samples += 1;
self.chunk_duration += sample.duration;
self.update_sample_sizes(sample.bytes.len() as u32);
self.update_sample_times(sample.duration);
self.update_rendering_offsets(sample.rendering_offset);
self.update_sync_samples(sample.is_sync);
if self.is_chunk_full() {
self.write_chunk(writer)?;
}
self.update_durations(sample.duration, movie_timescale);
self.sample_id += 1;
Ok(self.trak.tkhd.duration)
}
// XXX largesize
fn chunk_count(&self) -> u32 {
let stco = self.trak.mdia.minf.stbl.stco.as_ref().unwrap();
stco.entries.len() as u32
}
fn update_sample_to_chunk(&mut self, chunk_id: u32) {
if let Some(ref entry) = self.trak.mdia.minf.stbl.stsc.entries.last() {
if entry.samples_per_chunk == self.chunk_samples {
return;
}
}
let entry = StscEntry {
first_chunk: chunk_id,
samples_per_chunk: self.chunk_samples,
sample_description_index: 1,
first_sample: self.sample_id - self.chunk_samples + 1,
};
self.trak.mdia.minf.stbl.stsc.entries.push(entry);
}
fn update_chunk_offsets(&mut self, offset: u64) {
let stco = self.trak.mdia.minf.stbl.stco.as_mut().unwrap();
stco.entries.push(offset as u32);
}
fn write_chunk<W: Write + Seek>(&mut self, writer: &mut W) -> Result<()> {
if self.chunk_buffer.is_empty() {
return Ok(());
}
let chunk_offset = writer.seek(SeekFrom::Current(0))?;
writer.write_all(&self.chunk_buffer)?;
self.update_sample_to_chunk(self.chunk_count() + 1);
self.update_chunk_offsets(chunk_offset);
self.chunk_buffer.clear();
self.chunk_samples = 0;
self.chunk_duration = 0;
Ok(())
}
fn max_sample_size(&self) -> u32 {
if self.trak.mdia.minf.stbl.stsz.sample_size > 0 {
self.trak.mdia.minf.stbl.stsz.sample_size
} else {
let mut max_size = 0;
for sample_size in self.trak.mdia.minf.stbl.stsz.sample_sizes.iter() {
max_size = cmp::max(max_size, *sample_size);
}
max_size
}
}
pub(crate) fn write_end<W: Write + Seek>(&mut self, writer: &mut W) -> Result<TrakBox> {
self.write_chunk(writer)?;
let max_sample_size = self.max_sample_size();
if let Some(ref mut mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.esds.es_desc.dec_config.buffer_size_db = max_sample_size;
// TODO
// mp4a.esds.es_desc.dec_config.max_bitrate
// mp4a.esds.es_desc.dec_config.avg_bitrate
}
Ok(self.trak.clone())
}
}

View file

@ -1,26 +1,33 @@
use byteorder::{BigEndian, WriteBytesExt};
use std::io::{Seek, SeekFrom, Write};
use crate::atoms::*;
use crate::track::Mp4TrackWriter;
use crate::*;
#[derive(Debug, Clone, PartialEq)]
pub struct Mp4Config {
pub major_brand: FourCC,
pub minor_version: u32,
pub compatible_brands: Vec<FourCC>,
pub timescale: u32,
}
#[derive(Debug)]
pub struct Mp4Writer<W> {
writer: W,
tracks: Vec<Mp4Track>,
tracks: Vec<Mp4TrackWriter>,
mdat_pos: u64,
timescale: u32,
duration: u64,
}
impl<W: Write + Seek> Mp4Writer<W> {
pub fn write_header(
mut writer: W,
major_brand: &FourCC,
minor_version: u32,
compatible_brands: &[FourCC],
) -> Result<Self> {
pub fn write_start(mut writer: W, config: &Mp4Config) -> Result<Self> {
let ftyp = FtypBox {
major_brand: major_brand.to_owned(),
minor_version,
compatible_brands: compatible_brands.to_vec(),
major_brand: config.major_brand.clone(),
minor_version: config.minor_version.clone(),
compatible_brands: config.compatible_brands.clone(),
};
ftyp.write_box(&mut writer)?;
@ -29,25 +36,61 @@ impl<W: Write + Seek> Mp4Writer<W> {
BoxHeader::new(BoxType::MdatBox, HEADER_SIZE).write(&mut writer)?;
let tracks = Vec::new();
Ok(Self {
writer,
tracks,
mdat_pos,
})
let timescale = config.timescale;
let duration = 0;
Ok(Self {writer, tracks, mdat_pos, timescale, duration})
}
pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> {
let track_id = self.tracks.len() as u32 + 1;
let track = Mp4Track::new(track_id, config)?;
let track = Mp4TrackWriter::new(track_id, config)?;
self.tracks.push(track);
Ok(())
}
pub fn write_tail(&mut self) -> Result<()> {
fn update_durations(&mut self, track_dur: u64) {
if track_dur > self.duration {
self.duration = track_dur;
}
}
pub fn write_sample(&mut self, track_id: u32, sample: &Mp4Sample) -> Result<()> {
if track_id == 0 {
return Err(Error::TrakNotFound(track_id));
}
let track_dur = if let Some(ref mut track) = self.tracks.get_mut(track_id as usize - 1) {
track.write_sample(&mut self.writer, sample, self.timescale)?
} else {
return Err(Error::TrakNotFound(track_id));
};
self.update_durations(track_dur);
Ok(())
}
pub fn write_sample(&mut self, _track_id: u32, _sample: &Mp4Sample) -> Result<()> {
fn update_mdat_size(&mut self) -> Result<()> {
let mdat_end = self.writer.seek(SeekFrom::Current(0))?;
let mdat_size = mdat_end - self.mdat_pos;
assert!(mdat_size < std::u32::MAX as u64);
self.writer.seek(SeekFrom::Start(self.mdat_pos))?;
self.writer.write_u32::<BigEndian>(mdat_size as u32)?;
self.writer.seek(SeekFrom::Start(mdat_end))?;
Ok(())
}
pub fn write_end(&mut self) -> Result<()> {
let mut moov = MoovBox::default();
for track in self.tracks.iter_mut() {
moov.traks.push(track.write_end(&mut self.writer)?);
}
self.update_mdat_size()?;
moov.mvhd.timescale = self.timescale;
moov.mvhd.duration = self.duration;
moov.write_box(&mut self.writer)?;
Ok(())
}
}

View file

@ -34,14 +34,28 @@ fn test_read_mp4() {
assert_eq!(mp4.tracks().len(), 2);
let sample_count = mp4.sample_count(1).unwrap();
assert_eq!(sample_count, 0);
assert_eq!(sample_count, 1);
let sample_1_1 = mp4.read_sample(1, 1).unwrap().unwrap();
assert_eq!(sample_1_1.bytes.len(), 751);
assert_eq!(
sample_1_1,
mp4::Mp4Sample {
start_time: 0,
duration: 512,
rendering_offset: 0,
is_sync: true,
bytes: mp4::Bytes::from(vec![0x0u8; 751]),
}
);
let eos = mp4.read_sample(1, 2).unwrap();
assert!(eos.is_none());
let sample_count = mp4.sample_count(2).unwrap();
assert_eq!(sample_count, 3);
let sample1 = mp4.read_sample(2, 1).unwrap().unwrap();
assert_eq!(sample1.bytes.len(), 179);
let sample_2_1 = mp4.read_sample(2, 1).unwrap().unwrap();
assert_eq!(sample_2_1.bytes.len(), 179);
assert_eq!(
sample1,
sample_2_1,
mp4::Mp4Sample {
start_time: 0,
duration: 1024,
@ -51,9 +65,9 @@ fn test_read_mp4() {
}
);
let sample2 = mp4.read_sample(2, 2).unwrap().unwrap();
let sample_2_2 = mp4.read_sample(2, 2).unwrap().unwrap();
assert_eq!(
sample2,
sample_2_2,
mp4::Mp4Sample {
start_time: 1024,
duration: 1024,
@ -63,9 +77,9 @@ fn test_read_mp4() {
}
);
let sample3 = mp4.read_sample(2, 3).unwrap().unwrap();
let sample_2_3 = mp4.read_sample(2, 3).unwrap().unwrap();
assert_eq!(
sample3,
sample_2_3,
mp4::Mp4Sample {
start_time: 2048,
duration: 896,
@ -87,7 +101,7 @@ fn test_read_mp4() {
assert_eq!(track1.width(), 320);
assert_eq!(track1.height(), 240);
assert_eq!(track1.bitrate(), 0); // XXX
assert_eq!(track1.frame_rate().to_integer(), 0); // XXX
assert_eq!(track1.frame_rate().to_integer(), 25); // XXX
// track #2
let track2 = mp4.tracks().get(1).unwrap();