diff --git a/ChangeLog b/ChangeLog index e3950e3a0..e0fc914f7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2012-02-06 Moritz Bunkus + + * 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 * mkvmerge: new feature: Implemented support for treating several diff --git a/src/input/r_qtmp4.cpp b/src/input/r_qtmp4.cpp index 3e7b58dc8..5f2868d87 100644 --- a/src/input/r_qtmp4.cpp +++ b/src/input/r_qtmp4.cpp @@ -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)) diff --git a/src/input/r_qtmp4.h b/src/input/r_qtmp4.h index 08dbcc87a..bd0229868 100644 --- a/src/input/r_qtmp4.h +++ b/src/input/r_qtmp4.h @@ -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 &entries); virtual void process_chapter_entries(int level, std::vector &entries); virtual void detect_interleaving(); + + virtual std::string read_string_atom(qt_atom_t atom, size_t num_skipped); }; #endif // __R_QTMP4_H diff --git a/tests/results.txt b/tests/results.txt index 53fd17233..a6e19240e 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -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 diff --git a/tests/test-334mp4_audio_encoder_delay.rb b/tests/test-334mp4_audio_encoder_delay.rb new file mode 100644 index 000000000..eec039255 --- /dev/null +++ b/tests/test-334mp4_audio_encoder_delay.rb @@ -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"