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

Update mp4info

This commit is contained in:
Ian Jun 2020-08-03 18:28:00 +09:00
parent 4b82165efc
commit 18bc289cab
11 changed files with 533 additions and 394 deletions

View file

@ -24,11 +24,10 @@ fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
let size = src_file.metadata()?.len();
let reader = BufReader::new(src_file);
let mut mp4 = mp4::Mp4Reader::new(reader);
mp4.read(size)?;
let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?;
for tix in 0..mp4.track_count() {
let track_id = tix + 1;
for tix in 0..mp4.tracks().len() {
let track_id = tix as u32 + 1;
let sample_count = mp4.sample_count(track_id)?;
for six in 0..sample_count {
let sample_id = six + 1;

View file

@ -4,7 +4,7 @@ use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::Path;
use mp4::{Mp4Reader, Result, TrackType};
use mp4::{Result, Mp4Track, TrackType};
fn main() {
let args: Vec<String> = env::args().collect();
@ -24,99 +24,46 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
let size = f.metadata()?.len();
let reader = BufReader::new(f);
let mut mp4 = Mp4Reader::new(reader);
mp4.read(size)?;
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
println!("Metadata:");
println!(" size: {}", mp4.size());
println!(
" brands: {:?} {:?}\n",
mp4.major_brand(),
mp4.compatible_brands()
);
println!(" size : {}", mp4.size());
println!(" major_brand : {}", mp4.major_brand());
let mut compatible_brands = String::new();
for brand in mp4.compatible_brands().iter() {
compatible_brands.push_str(&brand.to_string());
compatible_brands.push_str(",");
}
println!(" compatible_brands: {}", compatible_brands);
println!(
"Duration: {}, timescale: {}",
mp4.duration()?,
mp4.timescale()?
mp4.duration(),
mp4.timescale()
);
for track in mp4.tracks().iter() {
println!(" Track: {}", track.track_id());
}
if let Some(ref moov) = mp4.moov {
println!("Found {} Tracks", moov.traks.len());
for trak in moov.traks.iter() {
let tkhd = &trak.tkhd;
println!("Track: {:?}", tkhd.track_id);
println!(" flags: {:?}", tkhd.flags);
println!(" id: {:?}", tkhd.track_id);
println!(" duration: {:?}", tkhd.duration);
if tkhd.width != 0 && tkhd.height != 0 {
println!(" width: {:?}", tkhd.width);
println!(" height: {:?}", tkhd.height);
}
let mdia = &trak.mdia;
let hdlr = &mdia.hdlr;
let mdhd = &mdia.mdhd;
let stts = &mdia.minf.stbl.stts;
println!(
" type: {:?}",
get_handler_type(hdlr.handler_type.value.as_ref())
);
println!(" language: {:?}", mdhd.language);
println!(" media:");
println!(" sample count: {:?}", stts.entries[0].sample_count);
println!(" timescale: {:?}", mdhd.timescale);
println!(
" duration: {:?} (media timescale units)",
mdhd.duration
);
println!(
" duration: {:?} (ms)",
get_duration_ms(mdhd.duration, mdhd.timescale)
);
if get_handler_type(hdlr.handler_type.value.as_ref()) == TrackType::Video {
println!(
" frame rate: (computed): {:?}",
get_framerate(stts.entries[0].sample_count, mdhd.duration, mdhd.timescale)
);
}
}
let media_info = match track.track_type() {
TrackType::Video => video_info(track),
TrackType::Audio => audio_info(track),
_ => String::from("error")
};
println!(" Track: #{}({}) {}: {} ({:?}), {}", track.track_id(), track.language(),
track.track_type(), track.media_type(), track.box_type(), media_info);
}
Ok(())
}
fn get_handler_type(handler: &str) -> TrackType {
let mut typ: TrackType = TrackType::Unknown;
match handler {
"vide" => typ = TrackType::Video,
"soun" => typ = TrackType::Audio,
"meta" => typ = TrackType::Unknown,
_ => (),
}
return typ;
fn video_info(track: &Mp4Track) -> String {
format!("{}x{}, {} kb/s, {:.2} fps", track.width(), track.height(),
track.bitrate() / 1000, track.frame_rate())
}
fn get_duration_ms(duration: u64, timescale: u32) -> String {
let ms = (duration as f64 / timescale as f64) * 1000.0;
return format!("{:.2}", ms.floor());
fn audio_info(track: &Mp4Track) -> String {
let ch = match track.channel_count() {
1 => String::from("mono"),
2 => String::from("stereo"),
n => format!("{}-ch", n),
};
format!("{} Hz, {}, {} kb/s", track.sample_rate(), ch, track.bitrate() / 1000)
}
fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String {
let sc = (sample_count as f64) * 1000.0;
let ms = (duration as f64 / timescale as f64) * 1000.0;
return format!("{:.2}", sc / ms.floor());
}
// fn creation_time(creation_time: u64) -> u64 {
// // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
// if creation_time >= 2082844800 {
// creation_time - 2082844800
// } else {
// creation_time
// }
// }

View file

@ -1,10 +1,11 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::convert::TryInto;
use std::fmt;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::*;
pub(crate) mod avc;
pub(crate) mod avc1;
pub(crate) mod co64;
pub(crate) mod ctts;
pub(crate) mod edts;
@ -180,7 +181,8 @@ impl From<BoxType> for FourCC {
impl fmt::Debug for FourCC {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
let code: u32 = self.into();
write!(f, "{} / {:#010X}", self.value, code)
}
}

View file

@ -1,4 +1,5 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use num_rational::Ratio;
use std::io::{Read, Seek, Write};
use crate::atoms::*;
@ -6,9 +7,9 @@ use crate::atoms::*;
#[derive(Debug, Clone, PartialEq)]
pub struct Mp4aBox {
pub data_reference_index: u16,
pub channel_count: u16,
pub channelcount: u16,
pub samplesize: u16,
pub samplerate: u32,
pub samplerate: Ratio<u32>,
pub esds: EsdsBox,
}
@ -16,9 +17,9 @@ impl Default for Mp4aBox {
fn default() -> Self {
Mp4aBox {
data_reference_index: 0,
channel_count: 2,
channelcount: 2,
samplesize: 16,
samplerate: 0, // XXX
samplerate: Ratio::new_raw(48000 * 0x10000, 0x10000),
esds: EsdsBox::default(),
}
}
@ -43,10 +44,11 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
let data_reference_index = reader.read_u16::<BigEndian>()?;
reader.read_u64::<BigEndian>()?; // reserved
let channel_count = reader.read_u16::<BigEndian>()?;
let channelcount = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
let samplerate = reader.read_u32::<BigEndian>()?;
let samplerate_numer = reader.read_u32::<BigEndian>()?;
let samplerate = Ratio::new_raw(samplerate_numer, 0x10000);
let header = BoxHeader::read(reader)?;
let BoxHeader { name, size: s } = header;
@ -57,7 +59,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
Ok(Mp4aBox {
data_reference_index,
channel_count,
channelcount,
samplesize,
samplerate,
esds,
@ -78,10 +80,10 @@ impl<W: Write> WriteBox<&mut W> for Mp4aBox {
writer.write_u16::<BigEndian>(self.data_reference_index)?;
writer.write_u64::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.channel_count)?;
writer.write_u16::<BigEndian>(self.channelcount)?;
writer.write_u16::<BigEndian>(self.samplesize)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u32::<BigEndian>(self.samplerate)?;
writer.write_u32::<BigEndian>(*self.samplerate.numer())?;
self.esds.write_box(writer)?;

View file

@ -2,7 +2,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::atoms::*;
use crate::atoms::{avc::Avc1Box, mp4a::Mp4aBox};
use crate::atoms::{avc1::Avc1Box, mp4a::Mp4aBox};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StsdBox {

View file

@ -16,8 +16,8 @@ pub struct TkhdBox {
pub alternate_group: u16,
pub volume: Ratio<u16>,
pub matrix: Matrix,
pub width: u32,
pub height: u32,
pub width: Ratio<u32>,
pub height: Ratio<u32>,
}
impl Default for TkhdBox {
@ -33,8 +33,8 @@ impl Default for TkhdBox {
alternate_group: 0,
volume: Ratio::new_raw(0x0100, 0x100),
matrix: Matrix::default(),
width: 0,
height: 0,
width: Ratio::new_raw(0, 0x10000),
height: Ratio::new_raw(0, 0x10000),
}
}
}
@ -113,8 +113,10 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
w: reader.read_i32::<BigEndian>()?,
};
let width = reader.read_u32::<BigEndian>()? >> 16;
let height = reader.read_u32::<BigEndian>()? >> 16;
let width_numer = reader.read_u32::<BigEndian>()?;
let width = Ratio::new_raw(width_numer, 0x10000);
let height_numer = reader.read_u32::<BigEndian>()?;
let height = Ratio::new_raw(height_numer, 0x10000);
skip_read_to(reader, start + size)?;
@ -174,8 +176,8 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
writer.write_i32::<BigEndian>(self.matrix.y)?;
writer.write_i32::<BigEndian>(self.matrix.w)?;
writer.write_u32::<BigEndian>(self.width << 16)?;
writer.write_u32::<BigEndian>(self.height << 16)?;
writer.write_u32::<BigEndian>(*self.width.numer())?;
writer.write_u32::<BigEndian>(*self.height.numer())?;
Ok(size)
}
@ -210,8 +212,8 @@ mod tests {
y: 0,
w: 0x40000000,
},
width: 512,
height: 288,
width: Ratio::new_raw(512 * 0x10000, 0x10000),
height: Ratio::new_raw(288 * 0x10000, 0x10000),
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
@ -249,8 +251,8 @@ mod tests {
y: 0,
w: 0x40000000,
},
width: 512,
height: 288,
width: Ratio::new_raw(512 * 0x10000, 0x10000),
height: Ratio::new_raw(288 * 0x10000, 0x10000),
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();

View file

@ -1,4 +1,3 @@
use std::convert::TryInto;
use std::fmt;
pub use bytes::Bytes;
@ -11,15 +10,10 @@ mod atoms;
mod reader;
pub use reader::Mp4Reader;
pub type Result<T> = std::result::Result<T, Error>;
mod track;
pub use track::{Mp4Track, TrackType, MediaType};
#[derive(Debug, PartialEq)]
pub enum TrackType {
Audio,
Video,
Metadata,
Unknown,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Mp4Sample {
@ -53,3 +47,12 @@ impl fmt::Display for Mp4Sample {
)
}
}
pub fn creation_time(creation_time: u64) -> u64 {
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
if creation_time >= 2082844800 {
creation_time - 2082844800
} else {
creation_time
}
}

View file

@ -1,75 +1,87 @@
use std::io::{Read, Seek, SeekFrom};
use crate::atoms::*;
use crate::atoms::{mvhd::MvhdBox, stbl::StblBox, trak::TrakBox};
use crate::{Bytes, Error, Mp4Sample, Result};
use crate::{Error, Mp4Sample, Mp4Track, Result};
#[derive(Debug)]
pub struct Mp4Reader<R> {
reader: R,
pub ftyp: FtypBox,
pub moov: Option<MoovBox>,
size: u64,
ftyp: FtypBox,
moov: MoovBox,
tracks: Vec<TrackReader>,
tracks: Vec<Mp4Track>,
size: u64,
}
impl<R: Read + Seek> Mp4Reader<R> {
pub fn new(reader: R) -> Self {
Mp4Reader {
reader,
ftyp: FtypBox::default(),
moov: None,
size: 0,
tracks: Vec::new(),
}
}
pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
let start = reader.seek(SeekFrom::Current(0))?;
pub fn size(&self) -> u64 {
self.size
}
let mut ftyp = None;
let mut moov = None;
pub fn read(&mut self, size: u64) -> Result<()> {
let start = self.reader.seek(SeekFrom::Current(0))?;
let mut current = start;
while current < size {
// Get box header.
let header = BoxHeader::read(&mut self.reader)?;
let header = BoxHeader::read(&mut reader)?;
let BoxHeader { name, size: s } = header;
// Match and parse the atom boxes.
match name {
BoxType::FtypBox => {
let ftyp = FtypBox::read_box(&mut self.reader, s)?;
self.ftyp = ftyp;
ftyp = Some(FtypBox::read_box(&mut reader, s)?);
}
BoxType::FreeBox => {
skip_box(&mut self.reader, s)?;
skip_box(&mut reader, s)?;
}
BoxType::MdatBox => {
skip_box(&mut self.reader, s)?;
skip_box(&mut reader, s)?;
}
BoxType::MoovBox => {
let moov = MoovBox::read_box(&mut self.reader, s)?;
self.moov = Some(moov);
moov = Some(MoovBox::read_box(&mut reader, s)?);
}
BoxType::MoofBox => {
skip_box(&mut self.reader, s)?;
skip_box(&mut reader, s)?;
}
_ => {
// XXX warn!()
skip_box(&mut self.reader, s)?;
skip_box(&mut reader, s)?;
}
}
current = self.reader.seek(SeekFrom::Current(0))?;
current = reader.seek(SeekFrom::Current(0))?;
}
if let Some(ref moov) = self.moov {
if ftyp.is_none() {
return Err(Error::BoxNotFound(BoxType::FtypBox));
}
if moov.is_none() {
return Err(Error::BoxNotFound(BoxType::MoovBox));
}
let size = current - start;
let tracks = if let Some(ref moov) = moov {
let mut tracks = Vec::with_capacity(moov.traks.len());
for (i, trak) in moov.traks.iter().enumerate() {
self.tracks.push(TrackReader::new(i as u32 + 1, trak));
let track_id = i as u32 + 1;
assert_eq!(track_id, trak.tkhd.track_id);
tracks.push(Mp4Track::new(track_id, trak));
}
}
self.size = current - start;
Ok(())
tracks
} else {
Vec::new()
};
Ok(Mp4Reader {
reader,
ftyp: ftyp.unwrap(),
moov: moov.unwrap(),
size,
tracks,
})
}
pub fn size(&self) -> u64 {
self.size
}
pub fn major_brand(&self) -> &FourCC {
@ -84,29 +96,15 @@ impl<R: Read + Seek> Mp4Reader<R> {
&self.ftyp.compatible_brands
}
fn mvhd(&self) -> Result<&MvhdBox> {
if let Some(ref moov) = self.moov {
Ok(&moov.mvhd)
} else {
Err(Error::BoxNotFound(BoxType::VmhdBox))
}
pub fn duration(&self) -> u64 {
self.moov.mvhd.duration
}
pub fn duration(&self) -> Result<u64> {
let mvhd = self.mvhd()?;
Ok(mvhd.duration)
pub fn timescale(&self) -> u32 {
self.moov.mvhd.timescale
}
pub fn timescale(&self) -> Result<u32> {
let mvhd = self.mvhd()?;
Ok(mvhd.timescale)
}
pub fn track_count(&self) -> u32 {
self.tracks.len() as u32
}
pub fn tracks(&self) -> &[TrackReader] {
pub fn tracks(&self) -> &[Mp4Track] {
&self.tracks
}
@ -134,217 +132,3 @@ impl<R: Read + Seek> Mp4Reader<R> {
}
}
}
#[derive(Debug)]
pub struct TrackReader {
track_id: u32,
trak: TrakBox,
}
impl TrackReader {
pub(crate) fn new(track_id: u32, trak: &TrakBox) -> Self {
let trak = trak.clone();
Self { track_id, trak }
}
pub fn track_id(&self) -> u32 {
self.track_id
}
fn stbl(&self) -> &StblBox {
&self.trak.mdia.minf.stbl
}
fn stsc_index(&self, sample_id: u32) -> usize {
let stsc = &self.stbl().stsc;
for (i, entry) in stsc.entries.iter().enumerate() {
if sample_id < entry.first_sample {
assert_ne!(i, 0);
return i - 1;
}
}
assert_ne!(stsc.entries.len(), 0);
stsc.entries.len() - 1
}
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
let stbl = self.stbl();
if let Some(ref stco) = stbl.stco {
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
return Ok(*offset as u64);
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::StcoBox,
chunk_id,
));
}
} else {
if let Some(ref co64) = stbl.co64 {
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
return Ok(*offset);
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::Co64Box,
chunk_id,
));
}
}
}
assert!(stbl.stco.is_some() || stbl.co64.is_some());
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
}
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
let stbl = self.stbl();
assert!(stbl.ctts.is_some());
let ctts = if let Some(ref ctts) = stbl.ctts {
ctts
} else {
return Err(Error::BoxInStblNotFound(self.track_id, BoxType::CttsBox));
};
let mut sample_count = 1;
for (i, entry) in ctts.entries.iter().enumerate() {
if sample_id <= sample_count + entry.sample_count - 1 {
return Ok((i, sample_count));
}
sample_count += entry.sample_count;
}
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::CttsBox,
sample_id,
));
}
pub fn sample_count(&self) -> u32 {
let stsz = &self.stbl().stsz;
stsz.sample_sizes.len() as u32
}
pub fn sample_size(&self, sample_id: u32) -> Result<u32> {
let stsz = &self.stbl().stsz;
if stsz.sample_size > 0 {
return Ok(stsz.sample_size);
}
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
Ok(*size)
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::StszBox,
sample_id,
));
}
}
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
let stsc_index = self.stsc_index(sample_id);
let stsc = &self.stbl().stsc;
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
let first_chunk = stsc_entry.first_chunk;
let first_sample = stsc_entry.first_sample;
let samples_per_chunk = stsc_entry.samples_per_chunk;
let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
let chunk_offset = self.chunk_offset(chunk_id)?;
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
let mut sample_offset = 0;
for i in first_sample_in_chunk..sample_id {
sample_offset += self.sample_size(i)?;
}
Ok(chunk_offset + sample_offset as u64)
}
pub fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
let stts = &self.stbl().stts;
let mut sample_count = 1;
let mut elapsed = 0;
for entry in stts.entries.iter() {
if sample_id <= sample_count + entry.sample_count - 1 {
let start_time =
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
return Ok((start_time, entry.sample_delta));
}
sample_count += entry.sample_count;
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
}
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::SttsBox,
sample_id,
));
}
pub fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
let stbl = self.stbl();
if let Some(ref ctts) = stbl.ctts {
if let Ok((ctts_index, _)) = self.ctts_index(sample_id) {
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
return ctts_entry.sample_offset;
}
}
0
}
pub fn is_sync_sample(&self, sample_id: u32) -> bool {
let stbl = self.stbl();
if let Some(ref stss) = stbl.stss {
match stss.entries.binary_search(&sample_id) {
Ok(_) => true,
Err(_) => false,
}
} else {
true
}
}
pub fn read_sample<R: Read + Seek>(
&self,
reader: &mut R,
sample_id: u32,
) -> Result<Option<Mp4Sample>> {
let sample_size = match self.sample_size(sample_id) {
Ok(size) => size,
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
Err(err) => return Err(err),
};
let sample_offset = self.sample_offset(sample_id).unwrap(); // XXX
let mut buffer = vec![0x0u8; sample_size as usize];
reader.seek(SeekFrom::Start(sample_offset))?;
reader.read_exact(&mut buffer)?;
let (start_time, duration) = self.sample_time(sample_id).unwrap(); // XXX
let rendering_offset = self.sample_rendering_offset(sample_id);
let is_sync = self.is_sync_sample(sample_id);
Ok(Some(Mp4Sample {
start_time,
duration,
rendering_offset,
is_sync,
bytes: Bytes::from(buffer),
}))
}
}

384
src/track.rs Normal file
View file

@ -0,0 +1,384 @@
use std::fmt;
use std::time::Duration;
use std::io::{Read, Seek, SeekFrom};
use crate::atoms::*;
use crate::atoms::{trak::TrakBox, stbl::StblBox};
use crate::{Bytes, Error, Mp4Sample, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TrackType {
Video,
Audio,
Hint,
Text,
Unknown,
}
impl fmt::Display for TrackType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
TrackType::Video => "video",
TrackType::Audio => "audio",
TrackType::Hint => "hint",
TrackType::Text => "text",
TrackType::Unknown => "unknown", // XXX
};
write!(f, "{}", s)
}
}
impl From<&str> for TrackType {
fn from(handler: &str) -> TrackType {
match handler {
"vide" => TrackType::Video,
"soun" => TrackType::Audio,
"hint" => TrackType::Hint,
"text" => TrackType::Text,
_ => TrackType::Unknown,
}
}
}
impl From<&FourCC> for TrackType {
fn from(fourcc: &FourCC) -> TrackType {
TrackType::from(fourcc.value.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MediaType {
H264,
AAC,
Unknown,
}
impl fmt::Display for MediaType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
MediaType::H264 => "h264",
MediaType::AAC => "aac",
MediaType::Unknown => "unknown", // XXX
};
write!(f, "{}", s)
}
}
#[derive(Debug)]
pub struct Mp4Track {
track_id: u32,
track_type: TrackType,
media_type: MediaType,
trak: TrakBox,
}
impl Mp4Track {
pub(crate) fn new(track_id: u32, trak: &TrakBox) -> Self {
let trak = trak.clone();
let track_type = (&trak.mdia.hdlr.handler_type).into();
let media_type = if trak.mdia.minf.stbl.stsd.avc1.is_some() {
MediaType::H264
} else if trak.mdia.minf.stbl.stsd.mp4a.is_some() {
MediaType::AAC
} else {
MediaType::Unknown
};
Self { track_id, track_type, media_type, trak }
}
pub fn track_id(&self) -> u32 {
self.track_id
}
pub fn track_type(&self) -> TrackType {
self.track_type
}
pub fn media_type(&self) -> MediaType {
self.media_type
}
pub fn box_type(&self) -> FourCC {
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
FourCC::from(BoxType::Avc1Box)
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
FourCC::from(BoxType::Mp4aBox)
} else {
FourCC::from("null") // XXX
}
}
pub fn width(&self) -> u16 {
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
avc1.width
} else {
self.trak.tkhd.width.to_integer() as u16
}
}
pub fn height(&self) -> u16 {
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
avc1.height
} else {
self.trak.tkhd.height.to_integer() as u16
}
}
pub fn frame_rate(&self) -> f64 {
let dur_sec_f64 = self.duration().as_secs_f64();
if dur_sec_f64 > 0.0 {
self.sample_count() as f64 / dur_sec_f64
} else {
0.0
}
}
pub fn sample_rate(&self) -> u32 {
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.samplerate.to_integer() as u32
} else {
0 // XXX
}
}
pub fn channel_count(&self) -> u16 {
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.channelcount
} else {
0 // XXX
}
}
pub fn language(&self) -> &str {
&self.trak.mdia.mdhd.language
}
pub fn timescale(&self) -> u32 {
self.trak.mdia.mdhd.timescale
}
pub fn duration(&self) -> Duration {
Duration::from_micros(self.trak.mdia.mdhd.duration * 1_000_000
/ self.trak.mdia.mdhd.timescale as u64)
}
pub fn bitrate(&self) -> u32 {
let dur_sec = self.duration().as_secs();
if dur_sec > 0 {
let bitrate = self.total_sample_size() * 8 / dur_sec;
bitrate as u32
} else {
0
}
}
pub fn sample_count(&self) -> u32 {
let stsz = &self.stbl().stsz;
stsz.sample_sizes.len() as u32
}
fn stbl(&self) -> &StblBox {
&self.trak.mdia.minf.stbl
}
fn stsc_index(&self, sample_id: u32) -> usize {
let stsc = &self.stbl().stsc;
for (i, entry) in stsc.entries.iter().enumerate() {
if sample_id < entry.first_sample {
assert_ne!(i, 0);
return i - 1;
}
}
assert_ne!(stsc.entries.len(), 0);
stsc.entries.len() - 1
}
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
let stbl = self.stbl();
if let Some(ref stco) = stbl.stco {
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
return Ok(*offset as u64);
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::StcoBox,
chunk_id,
));
}
} else {
if let Some(ref co64) = stbl.co64 {
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
return Ok(*offset);
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::Co64Box,
chunk_id,
));
}
}
}
assert!(stbl.stco.is_some() || stbl.co64.is_some());
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
}
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
let stbl = self.stbl();
assert!(stbl.ctts.is_some());
let ctts = if let Some(ref ctts) = stbl.ctts {
ctts
} else {
return Err(Error::BoxInStblNotFound(self.track_id, BoxType::CttsBox));
};
let mut sample_count = 1;
for (i, entry) in ctts.entries.iter().enumerate() {
if sample_id <= sample_count + entry.sample_count - 1 {
return Ok((i, sample_count));
}
sample_count += entry.sample_count;
}
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::CttsBox,
sample_id,
));
}
fn sample_size(&self, sample_id: u32) -> Result<u32> {
let stsz = &self.stbl().stsz;
if stsz.sample_size > 0 {
return Ok(stsz.sample_size);
}
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
Ok(*size)
} else {
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::StszBox,
sample_id,
));
}
}
fn total_sample_size(&self) -> u64 {
let stsz = &self.stbl().stsz;
if stsz.sample_size > 0 {
stsz.sample_size as u64 * self.sample_count() as u64
} else {
let mut total_size = 0;
for size in stsz.sample_sizes.iter() {
total_size += *size as u64;
}
total_size
}
}
fn sample_offset(&self, sample_id: u32) -> Result<u64> {
let stsc_index = self.stsc_index(sample_id);
let stsc = &self.stbl().stsc;
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
let first_chunk = stsc_entry.first_chunk;
let first_sample = stsc_entry.first_sample;
let samples_per_chunk = stsc_entry.samples_per_chunk;
let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
let chunk_offset = self.chunk_offset(chunk_id)?;
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
let mut sample_offset = 0;
for i in first_sample_in_chunk..sample_id {
sample_offset += self.sample_size(i)?;
}
Ok(chunk_offset + sample_offset as u64)
}
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
let stts = &self.stbl().stts;
let mut sample_count = 1;
let mut elapsed = 0;
for entry in stts.entries.iter() {
if sample_id <= sample_count + entry.sample_count - 1 {
let start_time =
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
return Ok((start_time, entry.sample_delta));
}
sample_count += entry.sample_count;
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
}
return Err(Error::EntryInStblNotFound(
self.track_id,
BoxType::SttsBox,
sample_id,
));
}
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
let stbl = self.stbl();
if let Some(ref ctts) = stbl.ctts {
if let Ok((ctts_index, _)) = self.ctts_index(sample_id) {
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
return ctts_entry.sample_offset;
}
}
0
}
fn is_sync_sample(&self, sample_id: u32) -> bool {
let stbl = self.stbl();
if let Some(ref stss) = stbl.stss {
match stss.entries.binary_search(&sample_id) {
Ok(_) => true,
Err(_) => false,
}
} else {
true
}
}
pub(crate) fn read_sample<R: Read + Seek>(
&self,
reader: &mut R,
sample_id: u32,
) -> Result<Option<Mp4Sample>> {
let sample_size = match self.sample_size(sample_id) {
Ok(size) => size,
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
Err(err) => return Err(err),
};
let sample_offset = self.sample_offset(sample_id).unwrap(); // XXX
let mut buffer = vec![0x0u8; sample_size as usize];
reader.seek(SeekFrom::Start(sample_offset))?;
reader.read_exact(&mut buffer)?;
let (start_time, duration) = self.sample_time(sample_id).unwrap(); // XXX
let rendering_offset = self.sample_rendering_offset(sample_id);
let is_sync = self.is_sync_sample(sample_id);
Ok(Some(Mp4Sample {
start_time,
duration,
rendering_offset,
is_sync,
bytes: Bytes::from(buffer),
}))
}
}

View file

@ -1,4 +1,4 @@
use mp4;
use mp4::{TrackType, MediaType};
use std::fs::File;
use std::io::BufReader;
@ -9,8 +9,7 @@ fn test_read_mp4() {
let size = f.metadata().unwrap().len();
let reader = BufReader::new(f);
let mut mp4 = mp4::Mp4Reader::new(reader);
mp4.read(size).unwrap();
let mut mp4 = mp4::Mp4Reader::read_header(reader, size).unwrap();
assert_eq!(2591, mp4.size());
@ -30,9 +29,9 @@ fn test_read_mp4() {
assert_eq!(t, true);
}
assert_eq!(mp4.duration().unwrap(), 62);
assert_eq!(mp4.timescale().unwrap(), 1000);
assert_eq!(mp4.track_count(), 2);
assert_eq!(mp4.duration(), 62);
assert_eq!(mp4.timescale(), 1000);
assert_eq!(mp4.tracks().len(), 2);
let sample_count = mp4.sample_count(1).unwrap();
assert_eq!(sample_count, 0);
@ -78,4 +77,21 @@ fn test_read_mp4() {
let eos = mp4.read_sample(2, 4).unwrap();
assert!(eos.is_none());
// track #1
let track1 = mp4.tracks().get(0).unwrap();
assert_eq!(track1.track_id(), 1);
assert_eq!(track1.track_type(), TrackType::Video);
assert_eq!(track1.media_type(), MediaType::H264);
assert_eq!(track1.width(), 320);
assert_eq!(track1.height(), 240);
assert_eq!(track1.bitrate(), 0); // XXX
assert_eq!(track1.frame_rate(), 0.0); // XXX
// track #2
let track2 = mp4.tracks().get(1).unwrap();
assert_eq!(track2.track_type(), TrackType::Audio);
assert_eq!(track2.media_type(), MediaType::AAC);
assert_eq!(track2.sample_rate(), 48000);
assert_eq!(track2.bitrate(), 0); // XXX
}