mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-02-22 07:56:16 +00:00
Add support for the avc3
box.
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.
This commit is contained in:
parent
35560e94f5
commit
a1cd3dd4e5
4 changed files with 264 additions and 82 deletions
|
@ -5,7 +5,7 @@ use std::io::{Read, Seek, Write};
|
|||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Avc1Box {
|
||||
pub struct Avc1Or3Inner {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
@ -20,9 +20,9 @@ pub struct Avc1Box {
|
|||
pub avcc: AvcCBox,
|
||||
}
|
||||
|
||||
impl Default for Avc1Box {
|
||||
impl Default for Avc1Or3Inner {
|
||||
fn default() -> Self {
|
||||
Avc1Box {
|
||||
Avc1Or3Inner {
|
||||
data_reference_index: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
|
@ -35,9 +35,9 @@ impl Default for Avc1Box {
|
|||
}
|
||||
}
|
||||
|
||||
impl Avc1Box {
|
||||
impl Avc1Or3Inner {
|
||||
pub fn new(config: &AvcConfig) -> Self {
|
||||
Avc1Box {
|
||||
Avc1Or3Inner {
|
||||
data_reference_index: 1,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
|
@ -49,41 +49,13 @@ impl Avc1Box {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Avc1Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + 70 + self.avcc.box_size()
|
||||
pub fn size(&self) -> u64 {
|
||||
8 + 70 + self.avcc.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Avc1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.data_reference_index, self.width, self.height, self.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
impl Avc1Or3Inner {
|
||||
fn read<R: Read + Seek>(reader: &mut R, size: u64, start: u64) -> Result<Self> {
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
@ -119,7 +91,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
return Ok(Avc1Box {
|
||||
return Ok(Avc1Or3Inner {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
|
@ -134,13 +106,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
@ -161,6 +128,140 @@ impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
|||
|
||||
self.avcc.write_box(writer)?;
|
||||
|
||||
Ok(self.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct Avc1Box {
|
||||
pub inner: Avc1Or3Inner,
|
||||
}
|
||||
|
||||
impl Avc1Box {
|
||||
pub fn new(config: &AvcConfig) -> Self {
|
||||
Avc1Box {
|
||||
inner: Avc1Or3Inner::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Avc1Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.inner.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Avc1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.inner.data_reference_index,
|
||||
self.inner.width,
|
||||
self.inner.height,
|
||||
self.inner.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let inner = Avc1Or3Inner::read(reader, size, start)?;
|
||||
|
||||
Ok(Avc1Box { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.inner.write(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct Avc3Box {
|
||||
pub inner: Avc1Or3Inner,
|
||||
}
|
||||
|
||||
impl Avc3Box {
|
||||
pub fn new(config: &AvcConfig) -> Self {
|
||||
Avc3Box {
|
||||
inner: Avc1Or3Inner::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Avc3Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.inner.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Avc3Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.inner.data_reference_index,
|
||||
self.inner.width,
|
||||
self.inner.height,
|
||||
self.inner.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Avc3Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let inner = Avc1Or3Inner::read(reader, size, start)?;
|
||||
|
||||
Ok(Avc3Box { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Avc3Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.inner.write(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
@ -315,28 +416,30 @@ mod tests {
|
|||
#[test]
|
||||
fn test_avc1() {
|
||||
let src_box = Avc1Box {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
avcc: AvcCBox {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: 100,
|
||||
profile_compatibility: 0,
|
||||
avc_level_indication: 13,
|
||||
length_size_minus_one: 3,
|
||||
sequence_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![
|
||||
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00,
|
||||
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
|
||||
],
|
||||
}],
|
||||
picture_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0],
|
||||
}],
|
||||
inner: Avc1Or3Inner {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
avcc: AvcCBox {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: 100,
|
||||
profile_compatibility: 0,
|
||||
avc_level_indication: 13,
|
||||
length_size_minus_one: 3,
|
||||
sequence_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![
|
||||
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00,
|
||||
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
|
||||
],
|
||||
}],
|
||||
picture_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0],
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
|
@ -351,4 +454,46 @@ mod tests {
|
|||
let dst_box = Avc1Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_avc3() {
|
||||
let src_box = Avc3Box {
|
||||
inner: Avc1Or3Inner {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
avcc: AvcCBox {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: 100,
|
||||
profile_compatibility: 0,
|
||||
avc_level_indication: 13,
|
||||
length_size_minus_one: 3,
|
||||
sequence_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![
|
||||
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00,
|
||||
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
|
||||
],
|
||||
}],
|
||||
picture_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0],
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Avc3Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Avc3Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
//! stbl
|
||||
//! stsd
|
||||
//! avc1
|
||||
//! avc3
|
||||
//! hev1
|
||||
//! mp4a
|
||||
//! tx3g
|
||||
|
@ -62,7 +63,7 @@ use std::io::{Read, Seek, SeekFrom, Write};
|
|||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) mod avc1;
|
||||
pub(crate) mod avc;
|
||||
pub(crate) mod co64;
|
||||
pub(crate) mod ctts;
|
||||
pub(crate) mod data;
|
||||
|
@ -106,7 +107,7 @@ pub(crate) mod vmhd;
|
|||
pub(crate) mod vp09;
|
||||
pub(crate) mod vpcc;
|
||||
|
||||
pub use avc1::Avc1Box;
|
||||
pub use avc::Avc1Box;
|
||||
pub use co64::Co64Box;
|
||||
pub use ctts::CttsBox;
|
||||
pub use data::DataBox;
|
||||
|
@ -223,6 +224,7 @@ boxtype! {
|
|||
UrlBox => 0x75726C20,
|
||||
SmhdBox => 0x736d6864,
|
||||
Avc1Box => 0x61766331,
|
||||
Avc3Box => 0x61766333,
|
||||
AvcCBox => 0x61766343,
|
||||
Hev1Box => 0x68657631,
|
||||
HvcCBox => 0x68766343,
|
||||
|
|
|
@ -4,7 +4,12 @@ use std::io::{Read, Seek, Write};
|
|||
|
||||
use crate::mp4box::vp09::Vp09Box;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox};
|
||||
use crate::mp4box::{
|
||||
avc::{Avc1Box, Avc3Box},
|
||||
hev1::Hev1Box,
|
||||
mp4a::Mp4aBox,
|
||||
tx3g::Tx3gBox,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StsdBox {
|
||||
|
@ -14,6 +19,9 @@ pub struct StsdBox {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avc1: Option<Avc1Box>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avc3: Option<Avc3Box>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hev1: Option<Hev1Box>,
|
||||
|
||||
|
@ -77,6 +85,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
reader.read_u32::<BigEndian>()?; // XXX entry_count
|
||||
|
||||
let mut avc1 = None;
|
||||
let mut avc3 = None;
|
||||
let mut hev1 = None;
|
||||
let mut vp09 = None;
|
||||
let mut mp4a = None;
|
||||
|
@ -95,6 +104,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
BoxType::Avc1Box => {
|
||||
avc1 = Some(Avc1Box::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Avc3Box => {
|
||||
avc3 = Some(Avc3Box::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Hev1Box => {
|
||||
hev1 = Some(Hev1Box::read_box(reader, s)?);
|
||||
}
|
||||
|
@ -116,6 +128,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
version,
|
||||
flags,
|
||||
avc1,
|
||||
avc3,
|
||||
hev1,
|
||||
vp09,
|
||||
mp4a,
|
||||
|
|
48
src/track.rs
48
src/track.rs
|
@ -8,12 +8,14 @@ use crate::mp4box::traf::TrafBox;
|
|||
use crate::mp4box::trak::TrakBox;
|
||||
use crate::mp4box::trun::TrunBox;
|
||||
use crate::mp4box::{
|
||||
avc1::Avc1Box, co64::Co64Box, ctts::CttsBox, ctts::CttsEntry, hev1::Hev1Box, mp4a::Mp4aBox,
|
||||
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,
|
||||
|
@ -119,7 +121,7 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn media_type(&self) -> Result<MediaType> {
|
||||
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
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)
|
||||
|
@ -150,17 +152,37 @@ impl Mp4Track {
|
|||
}
|
||||
}
|
||||
|
||||
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(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
avc1.width
|
||||
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(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
avc1.height
|
||||
if let Some(avc) = self.avc1_or_3_inner() {
|
||||
avc.height
|
||||
} else {
|
||||
self.trak.tkhd.height.value()
|
||||
}
|
||||
|
@ -249,10 +271,10 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn video_profile(&self) -> Result<AvcProfile> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
if let Some(avc) = self.avc1_or_3_inner() {
|
||||
AvcProfile::try_from((
|
||||
avc1.avcc.avc_profile_indication,
|
||||
avc1.avcc.profile_compatibility,
|
||||
avc.avcc.avc_profile_indication,
|
||||
avc.avcc.profile_compatibility,
|
||||
))
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
||||
|
@ -260,8 +282,8 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn sequence_parameter_set(&self) -> Result<&[u8]> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
match avc1.avcc.sequence_parameter_sets.get(0) {
|
||||
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(),
|
||||
|
@ -275,8 +297,8 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn picture_parameter_set(&self) -> Result<&[u8]> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
match avc1.avcc.picture_parameter_sets.get(0) {
|
||||
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(),
|
||||
|
|
Loading…
Reference in a new issue