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

Add media configuration, profile, ...

This commit is contained in:
Ian Jun 2020-08-04 15:36:01 +09:00
parent cf4165425a
commit 9a311f3524
6 changed files with 299 additions and 70 deletions

View file

@ -43,16 +43,14 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
for track in mp4.tracks().iter() {
let media_info = match track.track_type()? {
TrackType::Video => video_info(track),
TrackType::Audio => audio_info(track),
TrackType::Video => video_info(track)?,
TrackType::Audio => audio_info(track)?,
};
println!(
" Track: #{}({}) {}: {} ({:?}), {}",
" Track: #{}({}) {}: {}",
track.track_id(),
track.language(),
track.track_type()?,
track.media_type()?,
track.box_type(),
media_info
);
}
@ -60,26 +58,32 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
Ok(())
}
fn video_info(track: &Mp4Track) -> String {
format!(
"{}x{}, {} kb/s, {:.2} fps",
fn video_info(track: &Mp4Track) -> Result<String> {
Ok(format!(
"{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps",
track.media_type()?,
track.video_profile()?,
track.box_type()?,
track.width(),
track.height(),
track.bitrate() / 1000,
track.frame_rate_f64()
)
))
}
fn audio_info(track: &Mp4Track) -> String {
fn audio_info(track: &Mp4Track) -> Result<String> {
let ch = match track.channel_count() {
1 => String::from("mono"),
2 => String::from("stereo"),
n => format!("{}-ch", n),
};
format!(
"{} Hz, {}, {} kb/s",
Ok(format!(
"{} ({}) ({:?}), {} Hz, {}, {} kb/s",
track.media_type()?,
track.audio_profile()?,
track.box_type()?,
track.sample_rate(),
ch,
track.bitrate() / 1000
)
))
}

View file

@ -30,6 +30,21 @@ impl Default for Avc1Box {
}
}
impl Avc1Box {
pub fn new(config: &AvcConfig) -> Self {
Avc1Box {
data_reference_index: 1,
width: config.width,
height: config.height,
horizresolution: FixedPointU16::new(0x48),
vertresolution: FixedPointU16::new(0x48),
frame_count: 1,
depth: 0x0018,
avcc: AvcCBox::new(&config.seq_param_set, &config.pic_param_set),
}
}
}
impl Mp4Box for Avc1Box {
fn box_type() -> BoxType {
BoxType::Avc1Box
@ -126,6 +141,20 @@ pub struct AvcCBox {
pub picture_parameter_sets: Vec<NalUnit>,
}
impl AvcCBox {
pub fn new(sps: &[u8], pps: &[u8]) -> Self {
Self {
configuration_version: 1,
avc_profile_indication: sps[1],
profile_compatibility: sps[2],
avc_level_indication: sps[3],
length_size_minus_one: 0xff, // length_size = 4
sequence_parameter_sets: vec![NalUnit::from(sps)],
picture_parameter_sets: vec![NalUnit::from(pps)],
}
}
}
impl Mp4Box for AvcCBox {
fn box_type() -> BoxType {
BoxType::AvcCBox
@ -206,19 +235,27 @@ pub struct NalUnit {
pub bytes: Vec<u8>,
}
impl From<&[u8]> for NalUnit {
fn from(bytes: &[u8]) -> Self {
Self {
bytes: bytes.to_vec()
}
}
}
impl NalUnit {
pub fn size(&self) -> usize {
fn size(&self) -> usize {
2 + self.bytes.len()
}
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let length = reader.read_u16::<BigEndian>()? as usize;
let mut bytes = vec![0u8; length];
reader.read(&mut bytes)?;
Ok(NalUnit { bytes })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
writer.write_u16::<BigEndian>(self.bytes.len() as u16)?;
writer.write(&self.bytes)?;
Ok(self.size() as u64)

View file

@ -14,7 +14,7 @@ pub struct Mp4aBox {
impl Default for Mp4aBox {
fn default() -> Self {
Mp4aBox {
Self {
data_reference_index: 0,
channelcount: 2,
samplesize: 16,
@ -25,8 +25,14 @@ impl Default for Mp4aBox {
}
impl Mp4aBox {
pub fn set_samplerate(&mut self, samplerate: u32) {
self.samplerate = FixedPointU16::new_raw(samplerate);
pub fn new(config: &AacConfig) -> Self {
Self {
data_reference_index: 1,
channelcount: config.chan_conf as u16,
samplesize: 16,
samplerate: FixedPointU16::new(config.freq_index.freq() as u16),
esds: EsdsBox::new(config),
}
}
}
@ -102,6 +108,16 @@ pub struct EsdsBox {
pub es_desc: ESDescriptor,
}
impl EsdsBox {
pub fn new(config: &AacConfig) -> Self {
Self {
version: 0,
flags: 0,
es_desc: ESDescriptor::new(config),
}
}
}
impl Mp4Box for EsdsBox {
fn box_type() -> BoxType {
BoxType::EsdsBox
@ -196,15 +212,22 @@ fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ESDescriptor {
pub tag: u8,
pub size: u32,
pub es_id: u16,
pub dec_config: DecoderConfigDescriptor,
pub sl_config: SLConfigDescriptor,
}
impl ESDescriptor {
pub fn new(config: &AacConfig) -> Self {
Self {
es_id: 1,
dec_config: DecoderConfigDescriptor::new(config),
sl_config: SLConfigDescriptor::new(),
}
}
}
impl Descriptor for ESDescriptor {
fn desc_tag() -> u8 {
0x03
@ -218,7 +241,7 @@ impl Descriptor for ESDescriptor {
impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
let (tag, size) = read_desc(reader)?;
let (tag, _) = read_desc(reader)?;
if tag != Self::desc_tag() {
return Err(Error::InvalidData("ESDescriptor not found"));
}
@ -230,8 +253,6 @@ impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
let sl_config = SLConfigDescriptor::read_desc(reader)?;
Ok(ESDescriptor {
tag,
size,
es_id,
dec_config,
sl_config,
@ -241,17 +262,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> {
write_desc(writer, self.tag, self.size)?;
Ok(self.size)
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
Ok(size)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DecoderConfigDescriptor {
pub tag: u8,
pub size: u32,
pub object_type_indication: u8,
pub stream_type: u8,
pub up_stream: u8,
@ -262,6 +280,21 @@ pub struct DecoderConfigDescriptor {
pub dec_specific: DecoderSpecificDescriptor,
}
impl DecoderConfigDescriptor {
pub fn new(config: &AacConfig) -> Self {
Self {
object_type_indication: 0x40, // AAC
// 0x05 << 2
stream_type: 0,
up_stream: 0,
buffer_size_db: 0,
max_bitrate: config.bitrate * 2, // XXX
avg_bitrate: config.bitrate,
dec_specific: DecoderSpecificDescriptor::new(config),
}
}
}
impl Descriptor for DecoderConfigDescriptor {
fn desc_tag() -> u8 {
0x04
@ -296,8 +329,6 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
}
Ok(DecoderConfigDescriptor {
tag,
size,
object_type_indication,
stream_type,
up_stream,
@ -311,21 +342,29 @@ 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> {
write_desc(writer, self.tag, self.size)?;
Ok(self.size)
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
Ok(size)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DecoderSpecificDescriptor {
pub tag: u8,
pub size: u32,
pub profile: u8,
pub freq_index: u8,
pub chan_conf: u8,
}
impl DecoderSpecificDescriptor {
pub fn new(config: &AacConfig) -> Self {
Self {
profile: config.profile as u8,
freq_index: config.freq_index as u8,
chan_conf: config.chan_conf as u8,
}
}
}
impl Descriptor for DecoderSpecificDescriptor {
fn desc_tag() -> u8 {
0x05
@ -339,7 +378,7 @@ impl Descriptor for DecoderSpecificDescriptor {
impl<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
let (tag, size) = read_desc(reader)?;
let (tag, _) = read_desc(reader)?;
if tag != Self::desc_tag() {
return Err(Error::InvalidData("DecoderSpecificDescriptor not found"));
}
@ -351,8 +390,6 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
let chan_conf = (byte_b >> 3) & 0x0F;
Ok(DecoderSpecificDescriptor {
tag,
size,
profile,
freq_index,
chan_conf,
@ -362,16 +399,19 @@ 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> {
write_desc(writer, self.tag, self.size)?;
Ok(self.size)
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
Ok(size)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SLConfigDescriptor {
pub tag: u8,
pub size: u32,
pub struct SLConfigDescriptor {}
impl SLConfigDescriptor {
pub fn new() -> Self {
SLConfigDescriptor {}
}
}
impl Descriptor for SLConfigDescriptor {
@ -387,21 +427,22 @@ impl Descriptor for SLConfigDescriptor {
impl<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
let (tag, size) = read_desc(reader)?;
let (tag, _) = read_desc(reader)?;
if tag != Self::desc_tag() {
return Err(Error::InvalidData("SLConfigDescriptor not found"));
}
reader.read_u8()?; // pre-defined
Ok(SLConfigDescriptor { tag, size })
Ok(SLConfigDescriptor {})
}
}
impl<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
write_desc(writer, self.tag, self.size)?;
Ok(self.size)
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size)?;
writer.write_u8(0)?; // pre-defined
Ok(size)
}
}

View file

@ -4,14 +4,53 @@ use std::time::Duration;
use crate::atoms::trak::TrakBox;
use crate::atoms::*;
use crate::atoms::{vmhd::VmhdBox, smhd::SmhdBox, avc1::Avc1Box, mp4a::Mp4aBox};
use crate::*;
#[derive(Debug, Clone, PartialEq)]
pub struct TrackConfig {
pub track_id: u32,
pub track_type: TrackType,
pub timescale: u32,
pub language: String,
pub media_conf: MediaConfig,
}
#[derive(Debug)]
pub struct Mp4Track {
trak: TrakBox,
}
impl Mp4Track {
pub fn new(config: TrackConfig) -> Result<Self> {
let mut trak = TrakBox::default();
trak.tkhd.track_id = config.track_id;
trak.mdia.mdhd.timescale = config.timescale;
trak.mdia.mdhd.language = config.language;
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::AaaConfig(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 }
@ -110,12 +149,16 @@ impl Mp4Track {
}
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
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.esds.es_desc.dec_config.avg_bitrate
} else {
0
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
}
}
}
@ -123,6 +166,22 @@ impl Mp4Track {
self.trak.mdia.minf.stbl.stsz.sample_sizes.len() as u32
}
pub fn video_profile(&self) -> Result<AvcProfile> {
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
AvcProfile::try_from((avc1.avcc.avc_profile_indication, avc1.avcc.profile_compatibility))
} 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 {
AudioObjectType::try_from(mp4a.esds.es_desc.dec_config.dec_specific.profile)
} else {
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
}
}
fn stsc_index(&self, sample_id: u32) -> usize {
for (i, entry) in self.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() {
if sample_id < entry.first_sample {

View file

@ -164,6 +164,9 @@ impl fmt::Display for FourCC {
}
}
const DISPLAY_TYPE_VIDEO: &str = "Video";
const DISPLAY_TYPE_AUDIO: &str = "Audio";
const HANDLER_TYPE_VIDEO: &str = "vide";
const HANDLER_TYPE_AUDIO: &str = "soun";
@ -175,7 +178,10 @@ pub enum TrackType {
impl fmt::Display for TrackType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s: &str = self.into();
let s = match self {
TrackType::Video => DISPLAY_TYPE_VIDEO,
TrackType::Audio => DISPLAY_TYPE_AUDIO,
};
write!(f, "{}", s)
}
}
@ -268,6 +274,78 @@ impl Into<&str> for &MediaType {
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum AvcProfile {
AvcConstrainedBaseline, // 66 with constraint set 1
AvcBaseline, // 66,
AvcMain, // 77,
AvcExtended, // 88,
AvcHigh, // 100
// TODO Progressive High Profile, Constrained High Profile, ...
}
impl TryFrom<(u8, u8)> for AvcProfile {
type Error = Error;
fn try_from(value: (u8, u8)) -> Result<AvcProfile> {
let profile = value.0;
let constraint_set1_flag = value.1 & 0x40 >> 7;
match (profile, constraint_set1_flag) {
(66, 1) => Ok(AvcProfile::AvcConstrainedBaseline),
(66, 0) => Ok(AvcProfile::AvcBaseline),
(77, _) => Ok(AvcProfile::AvcMain),
(88, _) => Ok(AvcProfile::AvcExtended),
(100, _) => Ok(AvcProfile::AvcHigh),
_ => Err(Error::InvalidData("unsupported avc profile")),
}
}
}
impl fmt::Display for AvcProfile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let profile = match self {
AvcProfile::AvcConstrainedBaseline => "Constrained Baseline",
AvcProfile::AvcBaseline => "Baseline",
AvcProfile::AvcMain => "Main",
AvcProfile::AvcExtended => "Extended",
AvcProfile::AvcHigh => "High",
};
write!(f, "{}", profile)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum AudioObjectType {
AacMain = 1,
AacLowComplexity = 2,
AacScalableSampleRate = 3,
AacLongTermPrediction = 4,
}
impl TryFrom<u8> for AudioObjectType {
type Error = Error;
fn try_from(value: u8) -> Result<AudioObjectType> {
match value {
1 => Ok(AudioObjectType::AacMain),
2 => Ok(AudioObjectType::AacLowComplexity),
3 => Ok(AudioObjectType::AacScalableSampleRate),
4 => Ok(AudioObjectType::AacLongTermPrediction),
_ => Err(Error::InvalidData("invalid audio object type")),
}
}
}
impl fmt::Display for AudioObjectType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let type_str = match self {
AudioObjectType::AacMain => "main",
AudioObjectType::AacLowComplexity => "LC",
AudioObjectType::AacScalableSampleRate => "SSR",
AudioObjectType::AacLongTermPrediction => "LTP",
};
write!(f, "{}", type_str)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SampleFreqIndex {
Freq96000 = 0x0,
@ -351,18 +429,26 @@ impl TryFrom<u8> for ChannelConfig {
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone)]
pub struct AvcConfig {
pub width: u16,
pub height: u16,
pub seq_param_set: Vec<u8>,
pub pic_param_set: Vec<u8>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct AacConfig {
pub bitrate: u32,
pub profile: AudioObjectType,
pub freq_index: SampleFreqIndex,
pub chan_conf: ChannelConfig,
}
#[derive(Debug, PartialEq, Clone)]
pub enum MediaConfig {
AVC {
width: u16,
height: u16,
// sps: Vec<u8>,
// pps: Vec<u8>,
},
AAC {
freq_index: SampleFreqIndex,
chan_conf: ChannelConfig,
},
AvcConfig(AvcConfig),
AaaConfig(AacConfig),
}
#[derive(Debug)]

View file

@ -1,4 +1,4 @@
use mp4::{MediaType, TrackType};
use mp4::{MediaType, TrackType, AvcProfile, AudioObjectType};
use std::fs::File;
use std::io::BufReader;
@ -83,6 +83,7 @@ fn test_read_mp4() {
assert_eq!(track1.track_id(), 1);
assert_eq!(track1.track_type().unwrap(), TrackType::Video);
assert_eq!(track1.media_type().unwrap(), MediaType::H264);
assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh);
assert_eq!(track1.width(), 320);
assert_eq!(track1.height(), 240);
assert_eq!(track1.bitrate(), 0); // XXX
@ -92,6 +93,7 @@ fn test_read_mp4() {
let track2 = mp4.tracks().get(1).unwrap();
assert_eq!(track2.track_type().unwrap(), TrackType::Audio);
assert_eq!(track2.media_type().unwrap(), MediaType::AAC);
assert_eq!(track2.audio_profile().unwrap(), AudioObjectType::AacLowComplexity);
assert_eq!(track2.sample_rate(), 48000);
assert_eq!(track2.bitrate(), 0); // XXX
}