mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-06-02 13:39:54 +00:00
a1cd3dd4e5
According to MPEG-4 part 15, sections 5.4.2.1.2 and 5.4.4 (or the whole 5.4 section in general), `avc1` and `avc3` have identical syntax and only differ in semantics. Since the change is trivial, there is no reason not to support both.
941 lines
32 KiB
Rust
941 lines
32 KiB
Rust
use bytes::BytesMut;
|
|
use std::cmp;
|
|
use std::convert::TryFrom;
|
|
use std::io::{Read, Seek, SeekFrom, Write};
|
|
use std::time::Duration;
|
|
|
|
use crate::mp4box::traf::TrafBox;
|
|
use crate::mp4box::trak::TrakBox;
|
|
use crate::mp4box::trun::TrunBox;
|
|
use crate::mp4box::{
|
|
avc::Avc1Box, co64::Co64Box, ctts::CttsBox, ctts::CttsEntry, hev1::Hev1Box, mp4a::Mp4aBox,
|
|
smhd::SmhdBox, stco::StcoBox, stsc::StscEntry, stss::StssBox, stts::SttsEntry, tx3g::Tx3gBox,
|
|
vmhd::VmhdBox, vp09::Vp09Box,
|
|
};
|
|
use crate::*;
|
|
|
|
use self::avc::Avc1Or3Inner;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct TrackConfig {
|
|
pub track_type: TrackType,
|
|
pub timescale: u32,
|
|
pub language: String,
|
|
pub media_conf: MediaConfig,
|
|
}
|
|
|
|
impl From<MediaConfig> for TrackConfig {
|
|
fn from(media_conf: MediaConfig) -> Self {
|
|
match media_conf {
|
|
MediaConfig::AvcConfig(avc_conf) => Self::from(avc_conf),
|
|
MediaConfig::HevcConfig(hevc_conf) => Self::from(hevc_conf),
|
|
MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf),
|
|
MediaConfig::TtxtConfig(ttxt_conf) => Self::from(ttxt_conf),
|
|
MediaConfig::Vp9Config(vp9_config) => Self::from(vp9_config),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AvcConfig> for TrackConfig {
|
|
fn from(avc_conf: AvcConfig) -> Self {
|
|
Self {
|
|
track_type: TrackType::Video,
|
|
timescale: 1000, // XXX
|
|
language: String::from("und"), // XXX
|
|
media_conf: MediaConfig::AvcConfig(avc_conf),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<HevcConfig> for TrackConfig {
|
|
fn from(hevc_conf: HevcConfig) -> Self {
|
|
Self {
|
|
track_type: TrackType::Video,
|
|
timescale: 1000, // XXX
|
|
language: String::from("und"), // XXX
|
|
media_conf: MediaConfig::HevcConfig(hevc_conf),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AacConfig> for TrackConfig {
|
|
fn from(aac_conf: AacConfig) -> Self {
|
|
Self {
|
|
track_type: TrackType::Audio,
|
|
timescale: 1000, // XXX
|
|
language: String::from("und"), // XXX
|
|
media_conf: MediaConfig::AacConfig(aac_conf),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<TtxtConfig> for TrackConfig {
|
|
fn from(txtt_conf: TtxtConfig) -> Self {
|
|
Self {
|
|
track_type: TrackType::Subtitle,
|
|
timescale: 1000, // XXX
|
|
language: String::from("und"), // XXX
|
|
media_conf: MediaConfig::TtxtConfig(txtt_conf),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Vp9Config> for TrackConfig {
|
|
fn from(vp9_conf: Vp9Config) -> Self {
|
|
Self {
|
|
track_type: TrackType::Video,
|
|
timescale: 1000, // XXX
|
|
language: String::from("und"), // XXX
|
|
media_conf: MediaConfig::Vp9Config(vp9_conf),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Mp4Track {
|
|
pub trak: TrakBox,
|
|
pub trafs: Vec<TrafBox>,
|
|
pub moof_offsets: Vec<u64>,
|
|
|
|
// Fragmented Tracks Defaults.
|
|
pub default_sample_duration: u32,
|
|
}
|
|
|
|
impl Mp4Track {
|
|
pub(crate) fn from(trak: &TrakBox) -> Self {
|
|
let trak = trak.clone();
|
|
Self {
|
|
trak,
|
|
trafs: Vec::new(),
|
|
moof_offsets: Vec::new(),
|
|
default_sample_duration: 0,
|
|
}
|
|
}
|
|
|
|
pub fn track_id(&self) -> u32 {
|
|
self.trak.tkhd.track_id
|
|
}
|
|
|
|
pub fn track_type(&self) -> Result<TrackType> {
|
|
TrackType::try_from(&self.trak.mdia.hdlr.handler_type)
|
|
}
|
|
|
|
pub fn media_type(&self) -> Result<MediaType> {
|
|
if self.avc1_or_3_inner().is_some() {
|
|
Ok(MediaType::H264)
|
|
} else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() {
|
|
Ok(MediaType::H265)
|
|
} else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() {
|
|
Ok(MediaType::VP9)
|
|
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
|
Ok(MediaType::AAC)
|
|
} else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
|
Ok(MediaType::TTXT)
|
|
} else {
|
|
Err(Error::InvalidData("unsupported media type"))
|
|
}
|
|
}
|
|
|
|
pub fn box_type(&self) -> Result<FourCC> {
|
|
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
|
Ok(FourCC::from(BoxType::Avc1Box))
|
|
} else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() {
|
|
Ok(FourCC::from(BoxType::Hev1Box))
|
|
} else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() {
|
|
Ok(FourCC::from(BoxType::Vp09Box))
|
|
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
|
Ok(FourCC::from(BoxType::Mp4aBox))
|
|
} else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
|
Ok(FourCC::from(BoxType::Tx3gBox))
|
|
} else {
|
|
Err(Error::InvalidData("unsupported sample entry box"))
|
|
}
|
|
}
|
|
|
|
pub fn avc1_or_3_inner(&self) -> Option<&Avc1Or3Inner> {
|
|
self.trak
|
|
.mdia
|
|
.minf
|
|
.stbl
|
|
.stsd
|
|
.avc1
|
|
.as_ref()
|
|
.map(|avc1| &avc1.inner)
|
|
.or(self
|
|
.trak
|
|
.mdia
|
|
.minf
|
|
.stbl
|
|
.stsd
|
|
.avc3
|
|
.as_ref()
|
|
.map(|avc3| &avc3.inner))
|
|
}
|
|
|
|
pub fn width(&self) -> u16 {
|
|
if let Some(avc) = self.avc1_or_3_inner() {
|
|
avc.width
|
|
} else {
|
|
self.trak.tkhd.width.value()
|
|
}
|
|
}
|
|
|
|
pub fn height(&self) -> u16 {
|
|
if let Some(avc) = self.avc1_or_3_inner() {
|
|
avc.height
|
|
} else {
|
|
self.trak.tkhd.height.value()
|
|
}
|
|
}
|
|
|
|
pub fn frame_rate(&self) -> f64 {
|
|
let dur = self.duration();
|
|
if dur.is_zero() {
|
|
0.0
|
|
} else {
|
|
self.sample_count() as f64 / dur.as_secs_f64()
|
|
}
|
|
}
|
|
|
|
pub fn sample_freq_index(&self) -> Result<SampleFreqIndex> {
|
|
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
|
if let Some(ref esds) = mp4a.esds {
|
|
SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index)
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
|
}
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
|
}
|
|
}
|
|
|
|
pub fn channel_config(&self) -> Result<ChannelConfig> {
|
|
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
|
if let Some(ref esds) = mp4a.esds {
|
|
ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf)
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
|
}
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
|
}
|
|
}
|
|
|
|
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 {
|
|
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
|
if let Some(ref esds) = mp4a.esds {
|
|
esds.es_desc.dec_config.avg_bitrate
|
|
} else {
|
|
0
|
|
}
|
|
// mp4a.esds.es_desc.dec_config.avg_bitrate
|
|
} else {
|
|
let dur = self.duration();
|
|
if dur.is_zero() {
|
|
0
|
|
} else {
|
|
let bitrate = self.total_sample_size() as f64 * 8.0 / dur.as_secs_f64();
|
|
bitrate as u32
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sample_count(&self) -> u32 {
|
|
if !self.trafs.is_empty() {
|
|
let mut sample_count = 0u32;
|
|
for traf in self.trafs.iter() {
|
|
if let Some(ref trun) = traf.trun {
|
|
sample_count = sample_count
|
|
.checked_add(trun.sample_count)
|
|
.expect("attempt to sum trun sample_count with overflow");
|
|
}
|
|
}
|
|
sample_count
|
|
} else {
|
|
self.trak.mdia.minf.stbl.stsz.sample_count
|
|
}
|
|
}
|
|
|
|
pub fn video_profile(&self) -> Result<AvcProfile> {
|
|
if let Some(avc) = self.avc1_or_3_inner() {
|
|
AvcProfile::try_from((
|
|
avc.avcc.avc_profile_indication,
|
|
avc.avcc.profile_compatibility,
|
|
))
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
|
}
|
|
}
|
|
|
|
pub fn sequence_parameter_set(&self) -> Result<&[u8]> {
|
|
if let Some(avc) = self.avc1_or_3_inner() {
|
|
match avc.avcc.sequence_parameter_sets.first() {
|
|
Some(nal) => Ok(nal.bytes.as_ref()),
|
|
None => Err(Error::EntryInStblNotFound(
|
|
self.track_id(),
|
|
BoxType::AvcCBox,
|
|
0,
|
|
)),
|
|
}
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
|
}
|
|
}
|
|
|
|
pub fn picture_parameter_set(&self) -> Result<&[u8]> {
|
|
if let Some(avc) = self.avc1_or_3_inner() {
|
|
match avc.avcc.picture_parameter_sets.first() {
|
|
Some(nal) => Ok(nal.bytes.as_ref()),
|
|
None => Err(Error::EntryInStblNotFound(
|
|
self.track_id(),
|
|
BoxType::AvcCBox,
|
|
0,
|
|
)),
|
|
}
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
|
}
|
|
}
|
|
|
|
pub fn audio_profile(&self) -> Result<AudioObjectType> {
|
|
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
|
if let Some(ref esds) = mp4a.esds {
|
|
AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile)
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
|
}
|
|
} else {
|
|
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
|
}
|
|
}
|
|
|
|
fn stsc_index(&self, sample_id: u32) -> Result<usize> {
|
|
if self.trak.mdia.minf.stbl.stsc.entries.is_empty() {
|
|
return Err(Error::InvalidData("no stsc entries"));
|
|
}
|
|
for (i, entry) in self.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() {
|
|
if sample_id < entry.first_sample {
|
|
return if i == 0 {
|
|
Err(Error::InvalidData("sample not found"))
|
|
} else {
|
|
Ok(i - 1)
|
|
};
|
|
}
|
|
}
|
|
Ok(self.trak.mdia.minf.stbl.stsc.entries.len() - 1)
|
|
}
|
|
|
|
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
|
if self.trak.mdia.minf.stbl.stco.is_none() && self.trak.mdia.minf.stbl.co64.is_none() {
|
|
return Err(Error::InvalidData("must have either stco or co64 boxes"));
|
|
}
|
|
if let Some(ref stco) = self.trak.mdia.minf.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) = self.trak.mdia.minf.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,
|
|
));
|
|
}
|
|
}
|
|
Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box))
|
|
}
|
|
|
|
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
|
let ctts = self.trak.mdia.minf.stbl.ctts.as_ref().unwrap();
|
|
let mut sample_count: u32 = 1;
|
|
for (i, entry) in ctts.entries.iter().enumerate() {
|
|
let next_sample_count =
|
|
sample_count
|
|
.checked_add(entry.sample_count)
|
|
.ok_or(Error::InvalidData(
|
|
"attempt to sum ctts entries sample_count with overflow",
|
|
))?;
|
|
if sample_id < next_sample_count {
|
|
return Ok((i, sample_count));
|
|
}
|
|
sample_count = next_sample_count;
|
|
}
|
|
|
|
Err(Error::EntryInStblNotFound(
|
|
self.track_id(),
|
|
BoxType::CttsBox,
|
|
sample_id,
|
|
))
|
|
}
|
|
|
|
/// return `(traf_idx, sample_idx_in_trun)`
|
|
fn find_traf_idx_and_sample_idx(&self, sample_id: u32) -> Option<(usize, usize)> {
|
|
let global_idx = sample_id - 1;
|
|
let mut offset = 0;
|
|
for traf_idx in 0..self.trafs.len() {
|
|
if let Some(trun) = &self.trafs[traf_idx].trun {
|
|
let sample_count = trun.sample_count;
|
|
if sample_count > (global_idx - offset) {
|
|
return Some((traf_idx, (global_idx - offset) as _));
|
|
}
|
|
offset = offset
|
|
.checked_add(sample_count)
|
|
.expect("attempt to sum trun sample_count with overflow");
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
|
if !self.trafs.is_empty() {
|
|
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
|
if let Some(size) = self.trafs[traf_idx]
|
|
.trun
|
|
.as_ref()
|
|
.unwrap()
|
|
.sample_sizes
|
|
.get(sample_idx)
|
|
{
|
|
Ok(*size)
|
|
} else {
|
|
Err(Error::EntryInTrunNotFound(
|
|
self.track_id(),
|
|
BoxType::TrunBox,
|
|
sample_id,
|
|
))
|
|
}
|
|
} else {
|
|
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
|
}
|
|
} else {
|
|
let stsz = &self.trak.mdia.minf.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 {
|
|
Err(Error::EntryInStblNotFound(
|
|
self.track_id(),
|
|
BoxType::StszBox,
|
|
sample_id,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn total_sample_size(&self) -> u64 {
|
|
let stsz = &self.trak.mdia.minf.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
|
|
}
|
|
}
|
|
|
|
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
|
if !self.trafs.is_empty() {
|
|
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
|
let mut sample_offset = self.trafs[traf_idx]
|
|
.tfhd
|
|
.base_data_offset
|
|
.unwrap_or(self.moof_offsets[traf_idx]);
|
|
|
|
if let Some(data_offset) = self.trafs[traf_idx]
|
|
.trun
|
|
.as_ref()
|
|
.and_then(|trun| trun.data_offset)
|
|
{
|
|
sample_offset = sample_offset.checked_add_signed(data_offset as i64).ok_or(
|
|
Error::InvalidData("attempt to calculate trun sample offset with overflow"),
|
|
)?;
|
|
}
|
|
|
|
let first_sample_in_trun = sample_id - sample_idx as u32;
|
|
for i in first_sample_in_trun..sample_id {
|
|
sample_offset = sample_offset
|
|
.checked_add(self.sample_size(i)? as u64)
|
|
.ok_or(Error::InvalidData(
|
|
"attempt to calculate trun entry sample offset with overflow",
|
|
))?;
|
|
}
|
|
|
|
Ok(sample_offset)
|
|
} else {
|
|
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
|
}
|
|
} else {
|
|
let stsc_index = self.stsc_index(sample_id)?;
|
|
|
|
let stsc = &self.trak.mdia.minf.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 = sample_id
|
|
.checked_sub(first_sample)
|
|
.map(|n| n / samples_per_chunk)
|
|
.and_then(|n| n.checked_add(first_chunk))
|
|
.ok_or(Error::InvalidData(
|
|
"attempt to calculate stsc chunk_id with overflow",
|
|
))?;
|
|
|
|
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)> {
|
|
if !self.trafs.is_empty() {
|
|
let mut base_start_time = 0;
|
|
let mut default_sample_duration = self.default_sample_duration;
|
|
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
|
let traf = &self.trafs[traf_idx];
|
|
if let Some(tfdt) = &traf.tfdt {
|
|
base_start_time = tfdt.base_media_decode_time;
|
|
}
|
|
if let Some(duration) = traf.tfhd.default_sample_duration {
|
|
default_sample_duration = duration;
|
|
}
|
|
if let Some(trun) = &traf.trun {
|
|
if TrunBox::FLAG_SAMPLE_DURATION & trun.flags != 0 {
|
|
let mut start_offset = 0u64;
|
|
for duration in &trun.sample_durations[..sample_idx] {
|
|
start_offset = start_offset.checked_add(*duration as u64).ok_or(
|
|
Error::InvalidData("attempt to sum sample durations with overflow"),
|
|
)?;
|
|
}
|
|
let duration = trun.sample_durations[sample_idx];
|
|
return Ok((base_start_time + start_offset, duration));
|
|
}
|
|
}
|
|
}
|
|
let start_offset = ((sample_id - 1) * default_sample_duration) as u64;
|
|
Ok((base_start_time + start_offset, default_sample_duration))
|
|
} else {
|
|
let stts = &self.trak.mdia.minf.stbl.stts;
|
|
|
|
let mut sample_count: u32 = 1;
|
|
let mut elapsed = 0;
|
|
|
|
for entry in stts.entries.iter() {
|
|
let new_sample_count =
|
|
sample_count
|
|
.checked_add(entry.sample_count)
|
|
.ok_or(Error::InvalidData(
|
|
"attempt to sum stts entries sample_count with overflow",
|
|
))?;
|
|
if sample_id < new_sample_count {
|
|
let start_time =
|
|
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
|
|
return Ok((start_time, entry.sample_delta));
|
|
}
|
|
|
|
sample_count = new_sample_count;
|
|
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
|
|
}
|
|
|
|
Err(Error::EntryInStblNotFound(
|
|
self.track_id(),
|
|
BoxType::SttsBox,
|
|
sample_id,
|
|
))
|
|
}
|
|
}
|
|
|
|
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
|
if !self.trafs.is_empty() {
|
|
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
|
if let Some(cts) = self.trafs[traf_idx]
|
|
.trun
|
|
.as_ref()
|
|
.and_then(|trun| trun.sample_cts.get(sample_idx))
|
|
{
|
|
return *cts as i32;
|
|
}
|
|
}
|
|
} else if let Some(ref ctts) = self.trak.mdia.minf.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 {
|
|
if !self.trafs.is_empty() {
|
|
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
|
|
return sample_id == 1 || sample_id % sample_sizes_count == 0;
|
|
}
|
|
|
|
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
|
|
stss.entries.binary_search(&sample_id).is_ok()
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
pub(crate) fn read_sample<R: Read + Seek>(
|
|
&self,
|
|
reader: &mut R,
|
|
sample_id: u32,
|
|
) -> Result<Option<Mp4Sample>> {
|
|
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_size = match self.sample_size(sample_id) {
|
|
Ok(size) => size,
|
|
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
|
|
Err(err) => return Err(err),
|
|
};
|
|
|
|
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),
|
|
}))
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
trak.mdia.minf.stbl.co64 = Some(Co64Box::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::HevcConfig(ref hevc_config) => {
|
|
trak.tkhd.set_width(hevc_config.width);
|
|
trak.tkhd.set_height(hevc_config.height);
|
|
|
|
let vmhd = VmhdBox::default();
|
|
trak.mdia.minf.vmhd = Some(vmhd);
|
|
|
|
let hev1 = Hev1Box::new(hevc_config);
|
|
trak.mdia.minf.stbl.stsd.hev1 = Some(hev1);
|
|
}
|
|
MediaConfig::Vp9Config(ref config) => {
|
|
trak.tkhd.set_width(config.width);
|
|
trak.tkhd.set_height(config.height);
|
|
|
|
trak.mdia.minf.stbl.stsd.vp09 = Some(Vp09Box::new(config));
|
|
}
|
|
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);
|
|
}
|
|
MediaConfig::TtxtConfig(ref _ttxt_config) => {
|
|
let tx3g = Tx3gBox::default();
|
|
trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g);
|
|
}
|
|
}
|
|
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;
|
|
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(0);
|
|
} else {
|
|
self.trak.mdia.minf.stbl.stsz.sample_size = size;
|
|
self.fixed_sample_size = size;
|
|
self.is_fixed_sample_size = true;
|
|
}
|
|
} else if self.is_fixed_sample_size {
|
|
if self.fixed_sample_size != size {
|
|
self.is_fixed_sample_size = false;
|
|
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);
|
|
}
|
|
} else {
|
|
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 {
|
|
if !is_sync {
|
|
return;
|
|
}
|
|
|
|
stss.entries.push(self.sample_id);
|
|
} else {
|
|
if !is_sync {
|
|
return;
|
|
}
|
|
|
|
// Create the stts box if not found and push the entry.
|
|
let mut stss = StssBox::default();
|
|
stss.entries.push(self.sample_id);
|
|
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;
|
|
if self.trak.mdia.mdhd.duration > (u32::MAX as u64) {
|
|
self.trak.mdia.mdhd.version = 1
|
|
}
|
|
self.trak.tkhd.duration +=
|
|
dur as u64 * movie_timescale as u64 / self.trak.mdia.mdhd.timescale as u64;
|
|
if self.trak.tkhd.duration > (u32::MAX as u64) {
|
|
self.trak.tkhd.version = 1
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fn chunk_count(&self) -> u32 {
|
|
let co64 = self.trak.mdia.minf.stbl.co64.as_ref().unwrap();
|
|
co64.entries.len() as u32
|
|
}
|
|
|
|
fn update_sample_to_chunk(&mut self, chunk_id: u32) {
|
|
if let Some(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 co64 = self.trak.mdia.minf.stbl.co64.as_mut().unwrap();
|
|
co64.entries.push(offset);
|
|
}
|
|
|
|
fn write_chunk<W: Write + Seek>(&mut self, writer: &mut W) -> Result<()> {
|
|
if self.chunk_buffer.is_empty() {
|
|
return Ok(());
|
|
}
|
|
let chunk_offset = writer.stream_position()?;
|
|
|
|
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 {
|
|
if let Some(ref mut esds) = mp4a.esds {
|
|
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
|
|
}
|
|
if let Ok(stco) = StcoBox::try_from(self.trak.mdia.minf.stbl.co64.as_ref().unwrap()) {
|
|
self.trak.mdia.minf.stbl.stco = Some(stco);
|
|
self.trak.mdia.minf.stbl.co64 = None;
|
|
}
|
|
|
|
Ok(self.trak.clone())
|
|
}
|
|
}
|