MP4 reader: re-derive MP3 parameters if track headers are invalid

This commit is contained in:
Moritz Bunkus 2015-02-23 19:04:16 +01:00
parent 3f83e0c533
commit 5c45673d37
5 changed files with 91 additions and 24 deletions

View File

@ -1,3 +1,10 @@
2015-02-23 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: bug fix: If the MP4 track headers for MP3 tracks
contain invalid values (number of channels is 0 or the sampling
rate is 0) then mkvmerge will re-derive these parameters from the
MP3 bitstream instead of ignoring that track.
2015-02-18 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: bug fix: Matroska reader: track-specific tags weren't

View File

@ -30,6 +30,7 @@
#include "common/endian.h"
#include "common/hacks.h"
#include "common/iso639.h"
#include "common/mp3.h"
#include "common/strings/formatting.h"
#include "common/strings/parsing.h"
#include "input/r_qtmp4.h"
@ -131,6 +132,7 @@ qtmp4_reader_c::qtmp4_reader_c(const track_info_c &ti,
, m_fragment_implicit_offset{}
, m_fragment{}
, m_track_for_fragment{}
, m_timecodes_calculated{}
, m_debug_chapters{ "qtmp4|qtmp4_full|qtmp4_chapters"}
, m_debug_headers{ "qtmp4|qtmp4_full|qtmp4_headers"}
, m_debug_tables{ "qtmp4_full|qtmp4_tables"}
@ -305,12 +307,15 @@ qtmp4_reader_c::verify_track_parameters_and_update_indexes() {
else if (dmx->is_unknown())
continue;
dmx->ok = dmx->update_tables(m_time_scale);
dmx->ok = dmx->update_tables();
}
}
void
qtmp4_reader_c::calculate_timecodes() {
if (m_timecodes_calculated)
return;
int64_t min_timecode = 0;
for (auto &dmx : m_demuxers) {
@ -325,6 +330,8 @@ qtmp4_reader_c::calculate_timecodes() {
for (auto &dmx : m_demuxers)
dmx->build_index();
m_timecodes_calculated = true;
}
void
@ -582,7 +589,7 @@ qtmp4_reader_c::handle_moov_atom(qt_atom_t parent,
handle_mvex_atom(atom.to_parent(), level + 1);
else if (atom.fourcc == "trak") {
qtmp4_demuxer_cptr new_dmx(new qtmp4_demuxer_c);
auto new_dmx = std::make_shared<qtmp4_demuxer_c>(*this);
new_dmx->id = m_demuxers.size();
handle_trak_atom(new_dmx, atom.to_parent(), level + 1);
@ -1435,9 +1442,9 @@ qtmp4_reader_c::create_bitmap_info_header(qtmp4_demuxer_cptr &dmx,
bool
qtmp4_reader_c::create_audio_packetizer_ac3(qtmp4_demuxer_cptr &dmx) {
memory_cptr buf = memory_c::alloc(64);
auto buf = dmx->read_first_bytes(64);
if (!dmx->read_first_bytes(buf, 64, m_in) || (-1 == dmx->m_ac3_header.find_in(buf))) {
if (!buf || (-1 == dmx->m_ac3_header.find_in(buf))) {
mxwarn_tid(m_ti.m_fname, dmx->id, Y("No AC3 header found in first frame; track will be skipped.\n"));
dmx->ok = false;
@ -1462,9 +1469,9 @@ qtmp4_reader_c::create_audio_packetizer_alac(qtmp4_demuxer_cptr &dmx) {
bool
qtmp4_reader_c::create_audio_packetizer_dts(qtmp4_demuxer_cptr &dmx) {
auto const bytes_to_read = 8192u;
auto buf = memory_c::alloc(bytes_to_read);
auto buf = dmx->read_first_bytes(bytes_to_read);
if (!dmx->read_first_bytes(buf, bytes_to_read, m_in) || (-1 == find_dts_header(buf->get_buffer(), bytes_to_read, &dmx->m_dts_header, false))) {
if (!buf || (-1 == find_dts_header(buf->get_buffer(), bytes_to_read, &dmx->m_dts_header, false))) {
mxwarn_tid(m_ti.m_fname, dmx->id, Y("No DTS header found in first frames; track will be skipped.\n"));
dmx->ok = false;
@ -1995,7 +2002,10 @@ qtmp4_demuxer_c::min_timecode()
}
bool
qtmp4_demuxer_c::update_tables(int64_t global_m_time_scale) {
qtmp4_demuxer_c::update_tables() {
if (m_tables_updated)
return true;
uint64_t last = chunk_table.size();
if (!last)
@ -2043,6 +2053,8 @@ qtmp4_demuxer_c::update_tables(int64_t global_m_time_scale) {
else
mxerror(Y("Quicktime/MP4 reader: Constant samplesize & variable duration not yet supported. Contact the author if you have such a sample file.\n"));
m_tables_updated = true;
return true;
}
@ -2086,16 +2098,19 @@ qtmp4_demuxer_c::update_tables(int64_t global_m_time_scale) {
mxdebug(boost::format(" %1%: pts %2% size %3% pos %4%\n") % i++ % sample.pts % sample.size % sample.pos);
}
update_editlist_table(global_m_time_scale);
update_editlist_table();
m_tables_updated = true;
return true;
}
void
qtmp4_demuxer_c::update_editlist_table(int64_t global_time_scale) {
qtmp4_demuxer_c::update_editlist_table() {
if (editlist_table.empty())
return;
auto global_time_scale = m_reader.m_time_scale;
auto simple_editlist_type = 0u;
int64_t raw_offset = 0;
auto offset_in_global_time_scale = false;
@ -2240,10 +2255,14 @@ qtmp4_demuxer_c::build_index_chunk_mode() {
}
}
bool
qtmp4_demuxer_c::read_first_bytes(memory_cptr &buf,
int num_bytes,
mm_io_cptr in) {
memory_cptr
qtmp4_demuxer_c::read_first_bytes(int num_bytes) {
if (!update_tables())
return memory_cptr{};
m_reader.calculate_timecodes();
auto buf = memory_c::alloc(num_bytes);
size_t buf_pos = 0;
size_t idx_pos = 0;
@ -2251,16 +2270,16 @@ qtmp4_demuxer_c::read_first_bytes(memory_cptr &buf,
qt_index_t &index = m_index[idx_pos];
uint64_t num_bytes_to_read = std::min((int64_t)num_bytes, index.size);
in->setFilePointer(index.file_pos);
if (in->read(buf->get_buffer() + buf_pos, num_bytes_to_read) < num_bytes_to_read)
return false;
m_reader.m_in->setFilePointer(index.file_pos);
if (m_reader.m_in->read(buf->get_buffer() + buf_pos, num_bytes_to_read) < num_bytes_to_read)
return memory_cptr{};
num_bytes -= num_bytes_to_read;
buf_pos += num_bytes_to_read;
++idx_pos;
}
return 0 == num_bytes;
return 0 == num_bytes ? buf : memory_cptr{};
}
bool
@ -2687,8 +2706,28 @@ qtmp4_demuxer_c::parse_esds_atom(mm_mem_io_c &memio,
return true;
}
void
qtmp4_demuxer_c::derive_track_params_from_mp3_audio_bitstream() {
auto buf = read_first_bytes(64);
if (!buf)
return;
mp3_header_t header;
auto offset = find_mp3_header(buf->get_buffer(), buf->get_size());
if ((-1 == offset) || !decode_mp3_header(&buf->get_buffer()[offset], &header))
return;
a_channels = header.channels;
a_samplerate = header.sampling_frequency;
}
bool
qtmp4_demuxer_c::verify_audio_parameters() {
if ((0 == a_channels) || (0.0 == a_samplerate)) {
if (codec.is(codec_c::A_MP3))
derive_track_params_from_mp3_audio_bitstream();
}
if ((0 == a_channels) || (0.0 == a_samplerate)) {
mxwarn(boost::format(Y("Quicktime/MP4 reader: Track %1% is missing some data. Broken header atoms?\n")) % id);
return false;

View File

@ -207,8 +207,12 @@ struct qt_fragment_t {
{}
};
class qtmp4_reader_c;
struct qtmp4_demuxer_c {
bool ok;
qtmp4_reader_c &m_reader;
bool ok, m_tables_updated;
char type;
uint32_t id, container_id;
@ -261,8 +265,10 @@ struct qtmp4_demuxer_c {
debugging_option_c m_debug_tables, m_debug_fps, m_debug_headers, m_debug_editlists;
qtmp4_demuxer_c()
: ok{false}
qtmp4_demuxer_c(qtmp4_reader_c &reader)
: m_reader{reader}
, ok{}
, m_tables_updated{}
, type{'?'}
, id{0}
, container_id{0}
@ -305,12 +311,12 @@ struct qtmp4_demuxer_c {
void calculate_timecodes();
void adjust_timecodes(int64_t delta);
bool update_tables(int64_t global_time_scale);
void update_editlist_table(int64_t global_time_scale);
bool update_tables();
void update_editlist_table();
void build_index();
bool read_first_bytes(memory_cptr &buf, int num_bytes, mm_io_cptr in);
memory_cptr read_first_bytes(int num_bytes);
bool is_audio() const;
bool is_video() const;
@ -339,6 +345,8 @@ struct qtmp4_demuxer_c {
bool verify_subtitles_parameters();
bool verify_vobsub_subtitles_parameters();
void derive_track_params_from_mp3_audio_bitstream();
int64_t min_timecode() const;
void determine_codec();
@ -413,7 +421,7 @@ private:
std::unordered_map<unsigned int, bool> m_chapter_track_ids;
std::unordered_map<unsigned int, qt_track_defaults_t> m_track_defaults;
uint32_t m_time_scale;
int64_t m_time_scale;
fourcc_c m_compression_algorithm;
int m_main_dmx;
@ -423,8 +431,12 @@ private:
qt_fragment_t *m_fragment;
qtmp4_demuxer_c *m_track_for_fragment;
bool m_timecodes_calculated;
debugging_option_c m_debug_chapters, m_debug_headers, m_debug_tables, m_debug_interleaving, m_debug_resync;
friend class qtmp4_demuxer_c;
public:
qtmp4_reader_c(const track_info_c &ti, const mm_io_cptr &in);
virtual ~qtmp4_reader_c();

View File

@ -309,3 +309,4 @@ T_460truehd:33f8c0f013c71529281179cf8669c567-33f8c0f013c71529281179cf8669c567-ab
T_461truehd_from_mpeg_ts:c1ee4dc0746b9a3bea90ab49dd65e6a1-29f5c91656812569fb5e1fdbbbf85c39-c7272bed949872038798042ebbd31a2e:passed:20150212-134650:22.377720277
T_462dtshd_reduce_to_core:c284b0e29c3b7040e14b89a7e4790ce1-1e8b4d2eac574607bbaa54e4b8eee9e0-1e8b4d2eac574607bbaa54e4b8eee9e0:passed:20150212-223839:1.367529862
T_463a_ms_acm_with_track_tags:7766fe047ed88b8561caa4fba7c40ea6-45aa68321f4c3785d2e38ddbfd7cd850:passed:20150218-142924:0.139369636
T_464mp4_mp3_track_sampling_rate_0:51ec17a6dcfae5d8ac0f5fcf44e31eb6-b2c1dca03505c75c694c0de113a450f6:passed:20150223-190257:0.939161518

View File

@ -0,0 +1,8 @@
#!/usr/bin/ruby -w
describe "mkvmerge / MP3 in MP4, sample rate == 0 in track headers"
file = "data/mp4/mp3-samplerate-0.mp4"
test_identify file
test_merge file, :exit_code => :warning