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:
parent
82f9e2580c
commit
72e8861ac2
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -15,4 +15,4 @@ mod reader;
|
|||
pub use reader::Mp4Reader;
|
||||
|
||||
mod writer;
|
||||
pub use writer::Mp4Writer;
|
||||
pub use writer::{Mp4Config, Mp4Writer};
|
||||
|
|
|
@ -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))
|
||||
|
|
300
src/track.rs
300
src/track.rs
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
32
tests/lib.rs
32
tests/lib.rs
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue