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:
parent
9a311f3524
commit
8a7d2ae813
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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())?;
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -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;
|
||||
|
|
78
src/track.rs
78
src/track.rs
|
@ -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)
|
||||
|
|
30
src/types.rs
30
src/types.rs
|
@ -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
48
src/writer.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue