mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-04-16 16:54:14 +00:00
Add media configuration, profile, ...
This commit is contained in:
parent
cf4165425a
commit
9a311f3524
6 changed files with 299 additions and 70 deletions
|
@ -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
|
||||
)
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
69
src/track.rs
69
src/track.rs
|
@ -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 {
|
||||
|
|
110
src/types.rs
110
src/types.rs
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue