1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-06-11 09:29:21 +00:00

Add initial Mp4Writer

This commit is contained in:
Ian Jun 2020-08-04 17:40:47 +09:00
parent 9a311f3524
commit 8a7d2ae813
8 changed files with 210 additions and 39 deletions

View file

@ -1,10 +1,10 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::io::{self, BufReader, BufWriter};
use std::path::Path;
use mp4::Result;
use mp4::{Result, MediaType, AvcConfig, AacConfig, MediaConfig, TrackConfig};
fn main() {
let args: Vec<String> = env::args().collect();
@ -19,22 +19,64 @@ fn main() {
}
}
fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
let src_file = File::open(src_filename)?;
let size = src_file.metadata()?.len();
let reader = BufReader::new(src_file);
let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?;
let dst_file = File::create(dst_filename)?;
let writer = BufWriter::new(dst_file);
for tix in 0..mp4.tracks().len() {
let track_id = tix as u32 + 1;
let sample_count = mp4.sample_count(track_id)?;
let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?;
let mut mp4_writer = mp4::Mp4Writer::write_header(writer, mp4_reader.major_brand(), mp4_reader.minor_version(), mp4_reader.compatible_brands())?;
// TODO interleaving
for track_idx in 0..mp4_reader.tracks().len() {
if let Some(ref track) = mp4_reader.tracks().get(track_idx) {
let media_conf = match track.media_type()? {
MediaType::H264 => {
MediaConfig::AvcConfig(
AvcConfig {
width: track.width(),
height: track.height(),
seq_param_set: track.sequence_parameter_set()?.to_vec(),
pic_param_set: track.picture_parameter_set()?.to_vec(),
})
}
MediaType::AAC => {
MediaConfig::AacConfig(
AacConfig {
bitrate: track.bitrate(),
profile: track.audio_profile()?,
freq_index: track.sample_freq_index()?,
chan_conf: track.channel_config()?,
})
}
};
let track_conf = TrackConfig {
track_type: track.track_type()?,
timescale: track.timescale(),
language: track.language().to_string(),
media_conf,
};
mp4_writer.add_track(&track_conf)?;
} else {
unreachable!()
}
let track_id = track_idx as u32 + 1;
let sample_count = mp4_reader.sample_count(track_id)?;
for six in 0..sample_count {
let sample_id = six + 1;
let sample = mp4.read_sample(track_id, sample_id)?.unwrap();
println!("sample_id: {}, {}", sample_id, sample);
let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap();
mp4_writer.write_sample(track_id, &sample)?;
println!("copy {}:({})", sample_id, sample);
}
}
mp4_writer.write_tail()?;
Ok(())
}

View file

@ -72,18 +72,13 @@ fn video_info(track: &Mp4Track) -> Result<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),
};
Ok(format!(
"{} ({}) ({:?}), {} Hz, {}, {} kb/s",
track.media_type()?,
track.audio_profile()?,
track.box_type()?,
track.sample_rate(),
ch,
track.sample_freq_index()?.freq(),
track.channel_config()?,
track.bitrate() / 1000
))
}

View file

@ -32,7 +32,7 @@ pub(crate) mod vmhd;
pub use ftyp::FtypBox;
pub use moov::MoovBox;
const HEADER_SIZE: u64 = 8;
pub const HEADER_SIZE: u64 = 8;
// const HEADER_LARGE_SIZE: u64 = 16;
pub const HEADER_EXT_SIZE: u64 = 4;
@ -118,7 +118,7 @@ pub struct BoxHeader {
}
impl BoxHeader {
fn new(name: BoxType, size: u64) -> Self {
pub fn new(name: BoxType, size: u64) -> Self {
Self { name, size }
}
@ -154,7 +154,7 @@ impl BoxHeader {
}
}
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
if self.size > u32::MAX as u64 {
writer.write_u32::<BigEndian>(1)?;
writer.write_u32::<BigEndian>(self.name.into())?;

View file

@ -1,15 +1,18 @@
mod error;
pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;
mod types;
pub use types::*;
mod atoms;
mod track;
pub use track::{Mp4Track, TrackConfig};
mod reader;
pub use reader::Mp4Reader;
mod track;
pub use track::Mp4Track;
pub type Result<T> = std::result::Result<T, Error>;
mod writer;
pub use writer::Mp4Writer;

View file

@ -9,7 +9,6 @@ use crate::*;
#[derive(Debug, Clone, PartialEq)]
pub struct TrackConfig {
pub track_id: u32,
pub track_type: TrackType,
pub timescale: u32,
pub language: String,
@ -17,17 +16,52 @@ pub struct TrackConfig {
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::AacConfig(aac_conf) => {
Self::from(aac_conf)
}
}
}
}
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<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),
}
}
}
#[derive(Debug)]
pub struct Mp4Track {
trak: TrakBox,
}
impl Mp4Track {
pub fn new(config: TrackConfig) -> Result<Self> {
pub fn new(track_id: u32, config: &TrackConfig) -> Result<Self> {
let mut trak = TrakBox::default();
trak.tkhd.track_id = config.track_id;
trak.tkhd.track_id = track_id;
trak.mdia.mdhd.timescale = config.timescale;
trak.mdia.mdhd.language = config.language;
trak.mdia.mdhd.language = config.language.to_owned();
trak.mdia.hdlr.handler_type = config.track_type.into();
match config.media_conf {
MediaConfig::AvcConfig(ref avc_config) => {
@ -40,7 +74,7 @@ impl Mp4Track {
let avc1 = Avc1Box::new(avc_config);
trak.mdia.minf.stbl.stsd.avc1 = Some(avc1);
}
MediaConfig::AaaConfig(ref aac_config ) => {
MediaConfig::AacConfig(ref aac_config ) => {
let smhd = SmhdBox::default();
trak.mdia.minf.smhd = Some(smhd);
@ -118,19 +152,19 @@ impl Mp4Track {
}
}
pub fn sample_rate(&self) -> u32 {
pub fn sample_freq_index(&self) -> Result<SampleFreqIndex> {
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.samplerate.value() as u32
SampleFreqIndex::try_from(mp4a.esds.es_desc.dec_config.dec_specific.freq_index)
} else {
0 // XXX
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
}
}
pub fn channel_count(&self) -> u16 {
pub fn channel_config(&self) -> Result<ChannelConfig> {
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
mp4a.channelcount
ChannelConfig::try_from(mp4a.esds.es_desc.dec_config.dec_specific.chan_conf)
} else {
0 // XXX
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
}
}
@ -174,6 +208,28 @@ 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) {
Some(ref 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(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
match avc1.avcc.picture_parameter_sets.get(0) {
Some(ref 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 {
AudioObjectType::try_from(mp4a.esds.es_desc.dec_config.dec_specific.profile)

View file

@ -429,7 +429,22 @@ impl TryFrom<u8> for ChannelConfig {
}
}
#[derive(Debug, PartialEq, Clone)]
impl fmt::Display for ChannelConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
ChannelConfig::Mono => "mono",
ChannelConfig::Stereo => "stereo",
ChannelConfig::Three => "three",
ChannelConfig::Four => "four",
ChannelConfig::Five => "five",
ChannelConfig::FiveOne => "five.one",
ChannelConfig::SevenOne => "seven.one",
};
write!(f, "{}", s)
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct AvcConfig {
pub width: u16,
pub height: u16,
@ -445,10 +460,21 @@ pub struct AacConfig {
pub chan_conf: ChannelConfig,
}
impl Default for AacConfig {
fn default() -> Self {
Self {
bitrate: 0,
profile: AudioObjectType::AacLowComplexity,
freq_index: SampleFreqIndex::Freq48000,
chan_conf: ChannelConfig::Stereo,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum MediaConfig {
AvcConfig(AvcConfig),
AaaConfig(AacConfig),
AacConfig(AacConfig),
}
#[derive(Debug)]

48
src/writer.rs Normal file
View file

@ -0,0 +1,48 @@
use std::io::{Write, Seek, SeekFrom};
use crate::atoms::*;
use crate::*;
#[derive(Debug)]
pub struct Mp4Writer<W> {
writer: W,
tracks: Vec<Mp4Track>,
mdat_pos: u64,
}
impl<W: Write + Seek> Mp4Writer<W> {
pub fn write_header(mut writer: W, major_brand: &FourCC, minor_version: u32, compatible_brands: &[FourCC]) -> Result<Self> {
let ftyp = FtypBox {
major_brand: major_brand.to_owned(),
minor_version,
compatible_brands: compatible_brands.to_vec(),
};
ftyp.write_box(&mut writer)?;
// TODO largesize
let mdat_pos = writer.seek(SeekFrom::Current(0))?;
BoxHeader::new(BoxType::MdatBox, HEADER_SIZE).write(&mut writer)?;
let tracks = Vec::new();
Ok(Self {
writer,
tracks,
mdat_pos,
})
}
pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> {
let track_id = self.tracks.len() as u32 + 1;
let track = Mp4Track::new(track_id, config)?;
self.tracks.push(track);
Ok(())
}
pub fn write_tail(&mut self) -> Result<()> {
Ok(())
}
pub fn write_sample(&mut self, _track_id: u32, _sample: &Mp4Sample) -> Result<()> {
Ok(())
}
}

View file

@ -1,4 +1,4 @@
use mp4::{MediaType, TrackType, AvcProfile, AudioObjectType};
use mp4::{MediaType, TrackType, AvcProfile, AudioObjectType, SampleFreqIndex, ChannelConfig};
use std::fs::File;
use std::io::BufReader;
@ -94,6 +94,7 @@ fn test_read_mp4() {
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
assert_eq!(track2.sample_freq_index().unwrap(), SampleFreqIndex::Freq48000);
assert_eq!(track2.channel_config().unwrap(), ChannelConfig::Mono);
assert_eq!(track2.bitrate(), 67695);
}