Parse and apply audio encoder delay information in MP4 files

Fix for bug 715.
This commit is contained in:
Moritz Bunkus 2012-02-06 10:06:52 +01:00
parent 371ca103bd
commit 0ffdd3a6bf
5 changed files with 127 additions and 0 deletions

View File

@ -1,3 +1,9 @@
2012-02-06 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: new feature: mkvmerge will parse and apply the audio
encoder delay in MP4 files that contain said information in the
format that iTunes writes it. Fix for bug 715.
2012-02-02 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: new feature: Implemented support for treating several

View File

@ -32,6 +32,7 @@
#include "common/iso639.h"
#include "common/matroska.h"
#include "common/strings/formatting.h"
#include "common/strings/parsing.h"
#include "input/r_qtmp4.h"
#include "merge/output_control.h"
#include "output/p_aac.h"
@ -110,6 +111,7 @@ qtmp4_reader_c::qtmp4_reader_c(const track_info_c &ti,
, m_time_scale(1)
, m_compression_algorithm(0)
, m_main_dmx(-1)
, m_audio_encoder_delay_samples(0)
, m_debug_chapters( debugging_requested("qtmp4") || debugging_requested("qtmp4_full") || debugging_requested("qtmp4_chapters"))
, m_debug_headers( debugging_requested("qtmp4") || debugging_requested("qtmp4_full") || debugging_requested("qtmp4_headers"))
, m_debug_tables( debugging_requested("qtmp4_full") || debugging_requested("qtmp4_tables"))
@ -344,6 +346,15 @@ qtmp4_reader_c::calculate_timecodes() {
dmx->build_index();
}
void
qtmp4_reader_c::handle_audio_encoder_delay(qtmp4_demuxer_cptr &dmx) {
if ((0 == m_audio_encoder_delay_samples) || (0 == dmx->a_samplerate) || (-1 == dmx->ptzr))
return;
PTZR(dmx->ptzr)->m_ti.m_tcsync.displacement -= (m_audio_encoder_delay_samples * 1000000000ll) / dmx->a_samplerate;
m_audio_encoder_delay_samples = 0;
}
void
qtmp4_reader_c::parse_video_header_priv_atoms(qtmp4_demuxer_cptr &dmx,
unsigned char *mem,
@ -735,6 +746,9 @@ qtmp4_reader_c::handle_udta_atom(qt_atom_t parent,
if (FOURCC('c', 'h', 'p', 'l') == atom.fourcc)
handle_chpl_atom(atom.to_parent(), level + 1);
else if (FOURCC('m', 'e', 't', 'a') == atom.fourcc)
handle_meta_atom(atom.to_parent(), level + 1);
skip_atom();
parent.size -= atom.size;
}
@ -772,6 +786,94 @@ qtmp4_reader_c::handle_chpl_atom(qt_atom_t,
process_chapter_entries(level, entries);
}
void
qtmp4_reader_c::handle_meta_atom(qt_atom_t parent,
int level) {
m_in->skip(1 + 3); // version & flags
while (8 <= parent.size) {
qt_atom_t atom = read_atom();
print_basic_atom_info();
if (FOURCC('i', 'l', 's', 't') == atom.fourcc)
handle_ilst_atom(atom.to_parent(), level + 1);
skip_atom();
parent.size -= atom.size;
}
}
void
qtmp4_reader_c::handle_ilst_atom(qt_atom_t parent,
int level) {
while (8 <= parent.size) {
qt_atom_t atom = read_atom();
print_basic_atom_info();
if (FOURCC('-', '-', '-', '-') == atom.fourcc)
handle_4dashes_atom(atom.to_parent(), level + 1);
skip_atom();
parent.size -= atom.size;
}
}
std::string
qtmp4_reader_c::read_string_atom(qt_atom_t atom,
size_t num_skipped) {
if ((num_skipped + atom.hsize) > atom.size)
return "";
std::string string;
size_t length = atom.size - atom.hsize - num_skipped;
m_in->skip(num_skipped);
m_in->read(string, length);
return string;
}
void
qtmp4_reader_c::handle_4dashes_atom(qt_atom_t parent,
int level) {
std::string name, mean, data;
while (8 <= parent.size) {
qt_atom_t atom = read_atom();
print_basic_atom_info();
if (FOURCC('n', 'a', 'm', 'e') == atom.fourcc)
name = read_string_atom(atom, 4);
else if (FOURCC('m', 'e', 'a', 'n') == atom.fourcc)
mean = read_string_atom(atom, 4);
else if (FOURCC('d', 'a', 't', 'a') == atom.fourcc)
data = read_string_atom(atom, 8);
skip_atom();
parent.size -= atom.size;
}
mxdebug_if(m_debug_headers, boost::format("'----' content: name=%1% mean=%2% data=%3%\n") % name % mean % data);
if (name == "iTunSMPB")
parse_itunsmpb(data);
}
void
qtmp4_reader_c::parse_itunsmpb(std::string data) {
data = boost::regex_replace(data, boost::regex("[^\\da-fA-F]+", boost::regex::perl), "");
if (16 > data.length())
return;
try {
m_audio_encoder_delay_samples = from_hex(data.substr(8, 8));
} catch (std::bad_cast &) {
}
}
void
qtmp4_reader_c::read_chapter_track() {
if (m_ti.m_no_chapters || (NULL != m_chapters) || !m_chapter_dmx.is_set())
@ -1593,6 +1695,8 @@ qtmp4_reader_c::create_packetizer(int64_t tid) {
else
create_audio_packetizer_passthrough(dmx);
handle_audio_encoder_delay(dmx);
}
if (packetizer_ok && (-1 == m_main_dmx))

View File

@ -280,6 +280,8 @@ private:
uint32_t m_time_scale, m_compression_algorithm;
int m_main_dmx;
unsigned int m_audio_encoder_delay_samples;
bool m_debug_chapters, m_debug_headers, m_debug_tables, m_debug_interleaving;
public:
@ -308,6 +310,7 @@ protected:
virtual void parse_audio_header_priv_atoms(qtmp4_demuxer_cptr &dmx, unsigned char *mem, size_t size, int level);
virtual bool parse_esds_atom(mm_mem_io_c &memio, qtmp4_demuxer_cptr &dmx, int level);
virtual uint32_t read_esds_descr_len(mm_mem_io_c &memio);
virtual void parse_itunsmpb(std::string data);
virtual void handle_cmov_atom(qt_atom_t parent, int level);
virtual void handle_cmvd_atom(qt_atom_t parent, int level);
@ -321,6 +324,9 @@ protected:
virtual void handle_mvhd_atom(qt_atom_t parent, int level);
virtual void handle_udta_atom(qt_atom_t parent, int level);
virtual void handle_chpl_atom(qt_atom_t parent, int level);
virtual void handle_meta_atom(qt_atom_t parent, int level);
virtual void handle_ilst_atom(qt_atom_t parent, int level);
virtual void handle_4dashes_atom(qt_atom_t parent, int level);
virtual void handle_stbl_atom(qtmp4_demuxer_cptr &new_dmx, qt_atom_t parent, int level);
virtual void handle_stco_atom(qtmp4_demuxer_cptr &new_dmx, qt_atom_t parent, int level);
virtual void handle_co64_atom(qtmp4_demuxer_cptr &new_dmx, qt_atom_t parent, int level);
@ -349,12 +355,16 @@ protected:
virtual void create_video_packetizer_standard(qtmp4_demuxer_cptr &dmx);
virtual void create_video_packetizer_svq1(qtmp4_demuxer_cptr &dmx);
virtual void handle_audio_encoder_delay(qtmp4_demuxer_cptr &dmx);
virtual std::string decode_and_verify_language(uint16_t coded_language);
virtual void read_chapter_track();
virtual void recode_chapter_entries(std::vector<qtmp4_chapter_entry_t> &entries);
virtual void process_chapter_entries(int level, std::vector<qtmp4_chapter_entry_t> &entries);
virtual void detect_interleaving();
virtual std::string read_string_atom(qt_atom_t atom, size_t num_skipped);
};
#endif // __R_QTMP4_H

View File

@ -179,3 +179,4 @@ T_330dts_detection:38c941b579418e6c874950f4c55f84ce:passed:20120107-210130:1.227
T_331read_buffer_underflow:3bdec07b9e45cafe2c35561e7f8ad2db:passed:20120125-232902:0.407400904
T_332eac3_misdetected_as_avc:b4e0ac5954dbf57e4ad941f01c7de462:passed:20120131-145550:0.452730804
T_333wavpack_with_correction:c561a04a67042b048f7059a80d1c366d-e33897409384ca5fd8ed5e497f8c3883+37f802510b43b2ab4bd7d8356a7ac606-ok:passed:20120131-164845:0.299865433
T_334mp4_audio_encoder_delay:2afe7b533c9f1ed35168e60aaca20dff:passed:20120206-100443:0.195628019

View File

@ -0,0 +1,6 @@
#!/usr/bin/ruby -w
# T_334mp4_audio_encoder_delay
describe "mkvmerge / MP4 files & audio encoder delay information"
test_merge "data/mp4/aac_encoder_delay_sample.m4a"