From e72a2307c6487bc04f10c81489d2ffc21e2dea0d Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Tue, 10 Feb 2015 13:13:27 +0100 Subject: [PATCH] TrueHD reader: treat embedded AC3 data as its own track Part of an implementation of #1107. --- ChangeLog | 7 ++ src/common/truehd.cpp | 14 ++-- src/common/truehd.h | 3 + src/input/packet_converter.h | 5 ++ src/input/r_truehd.cpp | 81 ++++++++++++++++--- src/input/r_truehd.h | 9 ++- .../truehd_ac3_splitting_packet_converter.cpp | 68 ++++++++++++++++ .../truehd_ac3_splitting_packet_converter.h | 41 ++++++++++ src/output/p_truehd.cpp | 13 ++- src/output/p_truehd.h | 3 +- tests/results.txt | 2 +- tests/test-460truehd.rb | 5 ++ 12 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 src/input/truehd_ac3_splitting_packet_converter.cpp create mode 100644 src/input/truehd_ac3_splitting_packet_converter.h diff --git a/ChangeLog b/ChangeLog index f4293458f..b8ae40cc5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2015-02-10 Moritz Bunkus + + * mkvmerge: new feature: mkvmerge will now recognize TrueHD+AC3 + files as consisting of two tracks. Instead of always dropping the + AC3 part the user can simply select which tracks to + keep. Part of the implementation of #1107. + 2015-02-08 Moritz Bunkus * Released v7.6.0. diff --git a/src/common/truehd.cpp b/src/common/truehd.cpp index e5e5fe67a..1b9ccb46d 100644 --- a/src/common/truehd.cpp +++ b/src/common/truehd.cpp @@ -13,7 +13,6 @@ #include "common/common_pch.h" -#include "common/ac3.h" #include "common/endian.h" #include "common/memory.h" #include "common/truehd.h" @@ -96,15 +95,14 @@ truehd_parser_c::parse(bool end_of_stream) { frame->m_channels = mlp_channels[data[offset + 11] & 0x1f]; } - } else if (get_uint16_be(&data[offset]) == 0x0b77) { - ac3::frame_c ac3_frame; - if (ac3_frame.decode_header(&data[offset], size - offset)) { - if (((size - offset) < ac3_frame.m_bytes) && !end_of_stream) + } else if (get_uint16_be(&data[offset]) == AC3_SYNC_WORD) { + if (frame->m_ac3_header.decode_header(&data[offset], size - offset)) { + if (((size - offset) < frame->m_ac3_header.m_bytes) && !end_of_stream) break; - if (((size - offset) >= ac3_frame.m_bytes) && verify_ac3_checksum(&data[offset], size - offset)) { + if (((size - offset) >= frame->m_ac3_header.m_bytes) && verify_ac3_checksum(&data[offset], size - offset)) { frame->m_type = truehd_frame_t::ac3; - frame->m_size = ac3_frame.m_bytes; + frame->m_size = frame->m_ac3_header.m_bytes; } } } @@ -170,7 +168,7 @@ truehd_parser_c::resync(unsigned int offset) { for (offset = offset + 4; (offset + 4) < size; ++offset) { uint32_t sync_word = get_uint32_be(&data[offset]); - if ((TRUEHD_SYNC_WORD == sync_word) || (MLP_SYNC_WORD == sync_word)) { + if ((TRUEHD_SYNC_WORD == sync_word) || (MLP_SYNC_WORD == sync_word) || (AC3_SYNC_WORD == get_uint16_be(&data[offset - 4]))) { m_sync_state = state_synced; return offset - 4; } diff --git a/src/common/truehd.h b/src/common/truehd.h index 5741157e0..2cd550a6b 100644 --- a/src/common/truehd.h +++ b/src/common/truehd.h @@ -18,6 +18,7 @@ #include +#include "common/ac3.h" #include "common/byte_buffer.h" #define TRUEHD_SYNC_WORD 0xf8726fba @@ -41,6 +42,8 @@ struct truehd_frame_t { int m_channels; int m_samples_per_frame; + ac3::frame_c m_ac3_header; + memory_cptr m_data; truehd_frame_t() diff --git a/src/input/packet_converter.h b/src/input/packet_converter.h index cb3dc59f2..f7344d148 100644 --- a/src/input/packet_converter.h +++ b/src/input/packet_converter.h @@ -32,6 +32,11 @@ public: virtual ~packet_converter_c() {} virtual bool convert(packet_cptr const &packet) = 0; + virtual void flush() {} + + virtual void set_packetizer(generic_packetizer_c *ptzr) { + m_ptzr = ptzr; + } }; typedef std::shared_ptr packet_converter_cptr; diff --git a/src/input/r_truehd.cpp b/src/input/r_truehd.cpp index 6584357da..9a5f980ab 100644 --- a/src/input/r_truehd.cpp +++ b/src/input/r_truehd.cpp @@ -19,6 +19,7 @@ #include "common/mm_io_x.h" #include "input/r_truehd.h" #include "merge/input_x.h" +#include "output/p_ac3.h" #include "output/p_truehd.h" #define TRUEHD_READ_SIZE (1024 * 1024) @@ -40,6 +41,7 @@ truehd_reader_c::truehd_reader_c(const track_info_c &ti, const mm_io_cptr &in) : generic_reader_c(ti, in) , m_chunk(memory_c::alloc(TRUEHD_READ_SIZE)) + , m_ac3_ptzr{-1} { } @@ -63,7 +65,30 @@ truehd_reader_c::read_headers() { truehd_parser_c parser; parser.add_data(m_chunk->get_buffer(), init_read_len); - m_header = parser.get_next_frame(); + + + auto found_truehd = false; + auto found_ac3 = false; + + while (parser.frame_available() && (!found_truehd || !found_ac3)) { + auto frame = parser.get_next_frame(); + + if (frame->is_ac3()) { + if (!found_ac3) { + found_ac3 = true; + m_ac3_header = frame->m_ac3_header; + } + + continue; + } + + if (!frame->is_sync()) + continue; + + m_header = frame; + found_truehd = true; + } + m_ti.m_id = 0; // ID for this track. show_demuxer_info(); @@ -77,31 +102,65 @@ truehd_reader_c::~truehd_reader_c() { } void -truehd_reader_c::create_packetizer(int64_t) { - if (!demuxing_requested('a', 0) || (NPTZR() != 0)) +truehd_reader_c::add_available_track_ids() { + add_available_track_id(0); + if (m_ac3_header.m_valid) + add_available_track_id(1); +} + +void +truehd_reader_c::create_packetizers() { + create_packetizer(0); + create_packetizer(1); +} + +void +truehd_reader_c::create_packetizer(int64_t tid) { + if (!demuxing_requested('a', tid)) return; - add_packetizer(new truehd_packetizer_c(this, m_ti, m_header->m_codec, m_header->m_sampling_rate, m_header->m_channels)); - show_packetizer_info(0, PTZR0); + if (0 == tid) { + m_truehd_ptzr = add_packetizer(new truehd_packetizer_c(this, m_ti, m_header->m_codec, m_header->m_sampling_rate, m_header->m_channels)); + show_packetizer_info(0, PTZR(m_truehd_ptzr)); + m_converter.set_packetizer(PTZR(m_truehd_ptzr)); + + } else if ((1 == tid) && m_ac3_header.m_valid) { + m_ac3_ptzr = add_packetizer(new ac3_packetizer_c(this, m_ti, m_ac3_header.m_sample_rate, m_ac3_header.m_channels, m_ac3_header.m_bs_id, true)); + show_packetizer_info(m_ti.m_id, PTZR(m_ac3_ptzr)); + m_converter.set_ac3_packetizer(PTZR(m_ac3_ptzr)); + + } } file_status_e truehd_reader_c::read(generic_packetizer_c *, bool) { - int64_t remaining_bytes = m_size - m_in->getFilePointer(); - int64_t read_len = std::min((int64_t)TRUEHD_READ_SIZE, remaining_bytes); - int num_read = m_in->read(m_chunk->get_buffer(), read_len); + auto remaining_bytes = m_size - m_in->getFilePointer(); + auto read_len = std::min(TRUEHD_READ_SIZE, remaining_bytes); - if (0 <= num_read) - PTZR0->process(new packet_t(new memory_c(m_chunk->get_buffer(), num_read, false))); + if (0 >= read_len) { + m_converter.flush(); + return FILE_STATUS_DONE; + } - return (0 >= (remaining_bytes - num_read)) ? flush_packetizers() : FILE_STATUS_MOREDATA; + auto num_read = m_in->read(m_chunk->get_buffer(), read_len); + + if (0 < num_read) + m_converter.convert(std::make_shared(new memory_c(m_chunk->get_buffer(), num_read, false))); + + if (num_read == read_len) + return FILE_STATUS_MOREDATA; + + m_converter.flush(); + return FILE_STATUS_DONE; } void truehd_reader_c::identify() { id_result_container(); id_result_track(0, ID_RESULT_TRACK_AUDIO, codec_c::get_name(m_header->is_truehd() ? codec_c::A_TRUEHD : codec_c::A_MLP, m_header->is_truehd() ? "TrueHD" : "MLP")); + if (m_ac3_header.m_valid) + id_result_track(1, ID_RESULT_TRACK_AUDIO, codec_c::get_name(codec_c::A_AC3, "AC3")); } bool diff --git a/src/input/r_truehd.h b/src/input/r_truehd.h index dc9037945..3cd414b63 100644 --- a/src/input/r_truehd.h +++ b/src/input/r_truehd.h @@ -20,13 +20,18 @@ #include "common/error.h" #include "common/mm_io.h" +#include "input/truehd_ac3_splitting_packet_converter.h" #include "merge/generic_reader.h" +#include "common/ac3.h" #include "common/truehd.h" class truehd_reader_c: public generic_reader_c { private: memory_cptr m_chunk; truehd_frame_cptr m_header; + int m_truehd_ptzr, m_ac3_ptzr; + ac3::frame_c m_ac3_header; + truehd_ac3_splitting_packet_converter_c m_converter; public: truehd_reader_c(const track_info_c &ti, const mm_io_cptr &in); @@ -39,7 +44,9 @@ public: virtual void read_headers(); virtual file_status_e read(generic_packetizer_c *ptzr, bool force = false); virtual void identify(); - virtual void create_packetizer(int64_t id); + virtual void create_packetizers(); + virtual void create_packetizer(int64_t tid); + virtual void add_available_track_ids(); virtual bool is_providing_timecodes() const { return false; } diff --git a/src/input/truehd_ac3_splitting_packet_converter.cpp b/src/input/truehd_ac3_splitting_packet_converter.cpp new file mode 100644 index 000000000..591528c4d --- /dev/null +++ b/src/input/truehd_ac3_splitting_packet_converter.cpp @@ -0,0 +1,68 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + Distributed under the GPL v2 + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html + + Splitting TrueHD/AC3 into to packetizers + + Written by Moritz Bunkus . +*/ + +#include "common/common_pch.h" + +#include "input/truehd_ac3_splitting_packet_converter.h" +#include "merge/generic_packetizer.h" +#include "output/p_truehd.h" + +truehd_ac3_splitting_packet_converter_c::truehd_ac3_splitting_packet_converter_c(generic_packetizer_c *truehd_ptzr, + generic_packetizer_c *ac3_ptzr) + : packet_converter_c{truehd_ptzr} + , m_ac3_ptzr{ac3_ptzr} +{ +} + +bool +truehd_ac3_splitting_packet_converter_c::convert(packet_cptr const &packet) { + m_parser.add_data(packet->data->get_buffer(), packet->data->get_size()); + + process_frames(packet->timecode); + + return true; +} + +void +truehd_ac3_splitting_packet_converter_c::set_ac3_packetizer(generic_packetizer_c *ac3_ptzr) { + m_ac3_ptzr = ac3_ptzr; +} + +void +truehd_ac3_splitting_packet_converter_c::flush() { + m_parser.parse(true); + + process_frames(); + + if (m_ptzr) + m_ptzr->flush(); + + if (m_ac3_ptzr) + m_ac3_ptzr->flush(); +} + +void +truehd_ac3_splitting_packet_converter_c::process_frames(int64_t timecode) { + while (m_parser.frame_available()) { + auto frame = m_parser.get_next_frame(); + + if (frame->is_ac3()) { + if (m_ac3_ptzr) { + auto packet = std::make_shared(frame->m_data, timecode); + m_ac3_ptzr->process(packet); + } + + } else if (m_ptzr) + static_cast(m_ptzr)->process_framed(frame); + } +} diff --git a/src/input/truehd_ac3_splitting_packet_converter.h b/src/input/truehd_ac3_splitting_packet_converter.h new file mode 100644 index 000000000..e08a9a802 --- /dev/null +++ b/src/input/truehd_ac3_splitting_packet_converter.h @@ -0,0 +1,41 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + Distributed under the GPL v2 + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html + + class definitions for the TrueHD/AC3 splitting converter + + Written by Moritz Bunkus . +*/ + +#ifndef MTX_INPUT_TRUEHD_AC3_SPLITTING_PACKET_CONVERTER_H +#define MTX_INPUT_TRUEHD_AC3_SPLITTING_PACKET_CONVERTER_H + +#include "common/common_pch.h" + +#include "common/truehd.h" +#include "input/packet_converter.h" + +class truehd_ac3_splitting_packet_converter_c: public packet_converter_c { +protected: + truehd_parser_c m_parser; + generic_packetizer_c *m_ac3_ptzr; + +public: + truehd_ac3_splitting_packet_converter_c(generic_packetizer_c *truehd_ptzr = nullptr, generic_packetizer_c *ac3_ptzr = nullptr); + virtual ~truehd_ac3_splitting_packet_converter_c() {}; + + virtual bool convert(packet_cptr const &packet); + virtual void set_ac3_packetizer(generic_packetizer_c *ac3_ptzr); + virtual void flush(); + +protected: + virtual void process_frames(int64_t timecode = -1); +}; + +typedef std::shared_ptr truehd_ac3_splitting_packet_converter_cptr; + +#endif // MTX_INPUT_TRUEHD_AC3_SPLITTING_PACKET_CONVERTER_H diff --git a/src/output/p_truehd.cpp b/src/output/p_truehd.cpp index 40ef7ea2c..499c62a7d 100644 --- a/src/output/p_truehd.cpp +++ b/src/output/p_truehd.cpp @@ -58,6 +58,17 @@ truehd_packetizer_c::set_headers() { m_track_entry->EnableLacing(false); } +void +truehd_packetizer_c::process_framed(truehd_frame_cptr const &frame) { + if (frame->is_sync()) { + adjust_header_values(frame); + flush(); + } + + if (!frame->is_ac3()) + m_frames.push_back(frame); +} + int truehd_packetizer_c::process(packet_cptr packet) { m_parser.add_data(packet->data->get_buffer(), packet->data->get_size()); @@ -84,7 +95,7 @@ truehd_packetizer_c::handle_frames() { } void -truehd_packetizer_c::adjust_header_values(truehd_frame_cptr &frame) { +truehd_packetizer_c::adjust_header_values(truehd_frame_cptr const &frame) { if (!m_first_frame) return; diff --git a/src/output/p_truehd.h b/src/output/p_truehd.h index 08c715605..436a88e36 100644 --- a/src/output/p_truehd.h +++ b/src/output/p_truehd.h @@ -35,6 +35,7 @@ public: virtual ~truehd_packetizer_c(); virtual int process(packet_cptr packet); + virtual void process_framed(truehd_frame_cptr const &frame); virtual void handle_frames(); virtual void set_headers(); @@ -45,7 +46,7 @@ public: virtual connection_result_e can_connect_to(generic_packetizer_c *src, std::string &error_message); protected: - virtual void adjust_header_values(truehd_frame_cptr &frame); + virtual void adjust_header_values(truehd_frame_cptr const &frame); virtual void flush_impl(); virtual void flush_frames(); diff --git a/tests/results.txt b/tests/results.txt index b5cfbd242..225fb1b85 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -305,4 +305,4 @@ T_456tta:4b92a305f440778a3128685cefd4bd5d-a3f2a14c15e709027c05445ac91b0659:passe T_457mpeg_ts_all_pmts_with_crc_errors:0fefd1b95985d5f5deea17d20e05f6e9:passed:20150104-133628:1.586689715 T_458pcm_big_endian_in_matroska:c83ea37c1a80696b342859467d863f2a:passed:20150202-193826:0.080955865 T_459append_chapters_same_uid_with_sub_chapters:656362feddd9e89d28df6078361bf9d0:passed:20150202-214722:0.072671062 -T_460truehd:08e418b5bc96a1c746a14a70ddf8f2b1-8a5f8851733848f5505361d043ae8eb8:passed:20150210-130114:3.848084902 +T_460truehd:08e418b5bc96a1c746a14a70ddf8f2b1-08e418b5bc96a1c746a14a70ddf8f2b1-dd6d54ed6acbe10803bb910551b45ed9-8a5f8851733848f5505361d043ae8eb8-b2089ce04a1239127d8a3a0ccb864346-dd6d54ed6acbe10803bb910551b45ed9:passed:20150210-130114:10.654546371 diff --git a/tests/test-460truehd.rb b/tests/test-460truehd.rb index de87a370c..286d8d8aa 100755 --- a/tests/test-460truehd.rb +++ b/tests/test-460truehd.rb @@ -3,5 +3,10 @@ # T_460truehd describe "mkvmerge / TrueHD" +test_merge "data/truehd/blueplanet.thd" test_merge "data/truehd/blueplanet.thd", :args => "-a 0" + +test_merge "data/truehd/blueplanet.thd+ac3" test_merge "data/truehd/blueplanet.thd+ac3", :args => "-a 0" +test_merge "data/truehd/blueplanet.thd+ac3", :args => "-a 1" +test_merge "data/truehd/blueplanet.thd+ac3", :args => "-a 0,1"