TrueHD reader: treat embedded AC3 data as its own track

Part of an implementation of #1107.
This commit is contained in:
Moritz Bunkus 2015-02-10 13:13:27 +01:00
parent ac58b5547e
commit e72a2307c6
12 changed files with 228 additions and 23 deletions

View File

@ -1,3 +1,10 @@
2015-02-10 Moritz Bunkus <moritz@bunkus.org>
* 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 <moritz@bunkus.org>
* Released v7.6.0.

View File

@ -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;
}

View File

@ -18,6 +18,7 @@
#include <deque>
#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()

View File

@ -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_c> packet_converter_cptr;

View File

@ -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<int64_t>(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<packet_t>(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

View File

@ -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;
}

View File

@ -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 <moritz@bunkus.org>.
*/
#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<packet_t>(frame->m_data, timecode);
m_ac3_ptzr->process(packet);
}
} else if (m_ptzr)
static_cast<truehd_packetizer_c *>(m_ptzr)->process_framed(frame);
}
}

View File

@ -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 <moritz@bunkus.org>.
*/
#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_c> truehd_ac3_splitting_packet_converter_cptr;
#endif // MTX_INPUT_TRUEHD_AC3_SPLITTING_PACKET_CONVERTER_H

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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"