WAV reader: implement Wave64 support

Implements #2042.
This commit is contained in:
Moritz Bunkus 2017-07-21 22:17:04 +02:00
parent 5594270cd9
commit 8e270adcef
6 changed files with 215 additions and 88 deletions

View File

@ -18,6 +18,7 @@
timestamps. Implements #1887.
* mkvmerge: AAC: implemented support for AAC with 960 samples per
frame. Implements #2031.
* mkvmerge: WAV reader: added support for Wave64 files. Implements #2042.
## Bug fixes

View File

@ -21,6 +21,7 @@
#include "common/dts_parser.h"
#include "common/endian.h"
#include "common/id_info.h"
#include "common/mm_io_x.h"
#include "common/strings/formatting.h"
#include "input/r_wav.h"
#include "input/wav_ac3acm_demuxer.h"
@ -40,69 +41,109 @@ wav_demuxer_c::wav_demuxer_c(wav_reader_c *reader,
// ----------------------------------------------------------
int
wav_reader_c::probe_file(mm_io_c *in,
uint64_t size) {
wave_header wheader;
static unsigned char const s_wave64_guid_riff[16] = {
'r', 'i', 'f', 'f',
0x2e, 0x91, 0xcf, 0x11, 0xa5, 0xd6, 0x28, 0xdb, 0x04, 0xc1, 0x00, 0x00
};
if (sizeof(wave_header) > size)
return 0;
try {
in->setFilePointer(0, seek_beginning);
if (in->read(&wheader.riff, sizeof(wheader.riff)) != sizeof(wheader.riff))
return 0;
in->setFilePointer(0, seek_beginning);
} catch (...) {
return 0;
}
static unsigned char const s_wave64_guid_wave[16] = {
'w', 'a', 'v', 'e',
0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a
};
if (strncmp((char *)wheader.riff.id, "RIFF", 4) ||
strncmp((char *)wheader.riff.wave_id, "WAVE", 4))
return 0;
static unsigned char const s_wave64_guid_fmt [16] = {
'f', 'm', 't', ' ',
0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a
};
return 1;
}
static unsigned char const s_wave64_guid_fact[16] = {
'f', 'a', 'c', 't',
0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a
};
static unsigned char const s_wave64_guid_data[16] = {
'd', 'a', 't', 'a',
0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a
};
static unsigned char const s_wave64_guid_summarylist[16] = {
0xbc, 0x94, 0x5f, 0x92,
0x5a, 0x52, 0xd2, 0x11, 0x86, 0xdc, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a
};
wav_reader_c::wav_reader_c(const track_info_c &ti,
const mm_io_cptr &in)
: generic_reader_c(ti, in)
, m_bytes_in_data_chunks(0)
, m_remaining_bytes_in_current_data_chunk(0)
, m_cur_data_chunk_idx(0)
: generic_reader_c{ti, in}
, m_type{type_e::unknown}
, m_bytes_in_data_chunks{}
, m_remaining_bytes_in_current_data_chunk{}
{
}
wav_reader_c::~wav_reader_c() {
}
wav_reader_c::type_e
wav_reader_c::determine_type(mm_io_c &in,
uint64_t size) {
if (sizeof(wave64_header_t) > size)
return type_e::unknown;
try {
wave_header wheader;
wave64_header_t w64_header;
in.setFilePointer(0, seek_beginning);
if (in.read(&w64_header, sizeof(w64_header)) != sizeof(w64_header))
return type_e::unknown;
in.setFilePointer(0, seek_beginning);
std::memcpy(&wheader.riff, &w64_header, sizeof(wheader.riff));
if ( !std::memcmp(&wheader.riff.id, "RIFF", 4)
&& !std::memcmp(&wheader.riff.wave_id, "WAVE", 4))
return type_e::wave;
if ( !std::memcmp(w64_header.riff.guid, s_wave64_guid_riff, 16)
&& !std::memcmp(w64_header.wave_guid, s_wave64_guid_wave, 16))
return type_e::wave64;
} catch (mtx::mm_io::exception &) {
return type_e::unknown;
}
return type_e::unknown;
}
int
wav_reader_c::probe_file(mm_io_c *in,
uint64_t size) {
return determine_type(*in, size) != type_e::unknown;
}
void
wav_reader_c::read_headers() {
if (!wav_reader_c::probe_file(m_in.get(), m_size))
m_type = determine_type(*m_in, m_size);
if (m_type == type_e::unknown)
throw mtx::input::invalid_format_x();
parse_file();
create_demuxer();
}
wav_reader_c::~wav_reader_c() {
}
void
wav_reader_c::parse_file() {
int chunk_idx;
wav_reader_c::parse_fmt_chunk() {
auto chunk_idx = find_chunk("fmt ");
if (m_in->read(&m_wheader.riff, sizeof(m_wheader.riff)) != sizeof(m_wheader.riff))
if (!chunk_idx)
throw mtx::input::header_parsing_x();
scan_chunks();
if ((chunk_idx = find_chunk("fmt ")) == -1)
throw mtx::input::header_parsing_x();
m_in->setFilePointer(m_chunks[chunk_idx].pos, seek_beginning);
try {
if (m_in->read(&m_wheader.format, sizeof(m_wheader.format)) != sizeof(m_wheader.format))
throw false;
m_in->setFilePointer(m_chunks[*chunk_idx].pos, seek_beginning);
if (static_cast<uint64_t>(m_chunks[chunk_idx].len) >= sizeof(alWAVEFORMATEXTENSIBLE)) {
if (static_cast<uint64_t>(m_chunks[*chunk_idx].len) >= sizeof(alWAVEFORMATEXTENSIBLE)) {
alWAVEFORMATEXTENSIBLE format;
if (m_in->read(&format, sizeof(format)) != sizeof(format))
throw false;
@ -121,37 +162,47 @@ wav_reader_c::parse_file() {
} catch (...) {
throw mtx::input::header_parsing_x();
}
}
if ((m_cur_data_chunk_idx = find_chunk("data", 0, false)) == -1)
void
wav_reader_c::parse_file() {
scan_chunks();
parse_fmt_chunk();
m_cur_data_chunk_idx = find_chunk("data", 0, false);
if (!m_cur_data_chunk_idx)
throw mtx::input::header_parsing_x();
if (debugging_c::requested("wav_reader|wav_reader_headers"))
dump_headers();
m_in->setFilePointer(m_chunks[m_cur_data_chunk_idx].pos + sizeof(struct chunk_struct), seek_beginning);
m_in->setFilePointer(m_chunks[*m_cur_data_chunk_idx].pos, seek_beginning);
m_remaining_bytes_in_current_data_chunk = m_chunks[m_cur_data_chunk_idx].len;
m_remaining_bytes_in_current_data_chunk = m_chunks[*m_cur_data_chunk_idx].len;
}
void
wav_reader_c::dump_headers() {
mxinfo(boost::format("File '%1%' wave_header dump\n"
" riff:\n"
" id: %2%%3%%4%%5%\n"
" len: %6%\n"
" wave_id: %7%%8%%9%%10%\n"
" common:\n"
" wFormatTag: %|11$04x|\n"
" wChannels: %12%\n"
" dwSamplesPerSec: %13%\n"
" dwAvgBytesPerSec: %14%\n"
" wBlockAlign: %15%\n"
" wBitsPerSample: %16%\n"
" actual format_tag: %17%\n")
% m_ti.m_fname
% char(m_wheader.riff.id[0]) % char(m_wheader.riff.id[1]) % char(m_wheader.riff.id[2]) % char(m_wheader.riff.id[3])
% get_uint32_le(&m_wheader.riff.len)
% char(m_wheader.riff.wave_id[0]) % char(m_wheader.riff.wave_id[1]) % char(m_wheader.riff.wave_id[2]) % char(m_wheader.riff.wave_id[3])
mxinfo(boost::format("File '%1%' header dump (mode: %2%)\n") % m_ti.m_fname % (m_type == type_e::wave ? "WAV" : "Wave64"));
if (m_type == type_e::wave)
mxinfo(boost::format(" riff:\n"
" id: %1%%2%%3%%4%\n"
" len: %5%\n"
" wave_id: %6%%7%%8%%9%\n")
% char(m_wheader.riff.id[0]) % char(m_wheader.riff.id[1]) % char(m_wheader.riff.id[2]) % char(m_wheader.riff.id[3])
% get_uint32_le(&m_wheader.riff.len)
% char(m_wheader.riff.wave_id[0]) % char(m_wheader.riff.wave_id[1]) % char(m_wheader.riff.wave_id[2]) % char(m_wheader.riff.wave_id[3]));
mxinfo(boost::format(" common:\n"
" wFormatTag: %|1$04x|\n"
" wChannels: %2%\n"
" dwSamplesPerSec: %3%\n"
" dwAvgBytesPerSec: %4%\n"
" wBlockAlign: %5%\n"
" wBitsPerSample: %6%\n"
" actual format_tag: %7%\n")
% get_uint16_le(&m_wheader.common.wFormatTag)
% get_uint16_le(&m_wheader.common.wChannels)
% get_uint32_le(&m_wheader.common.dwSamplesPerSec)
@ -217,14 +268,14 @@ wav_reader_c::read(generic_packetizer_c *,
m_remaining_bytes_in_current_data_chunk -= num_read;
if (!m_remaining_bytes_in_current_data_chunk) {
m_cur_data_chunk_idx = find_chunk("data", m_cur_data_chunk_idx + 1, false);
m_cur_data_chunk_idx = find_chunk("data", *m_cur_data_chunk_idx + 1, false);
if (-1 == m_cur_data_chunk_idx)
if (!m_cur_data_chunk_idx)
return flush_packetizers();
m_in->setFilePointer(m_chunks[m_cur_data_chunk_idx].pos + sizeof(struct chunk_struct), seek_beginning);
m_in->setFilePointer(m_chunks[*m_cur_data_chunk_idx].pos, seek_beginning);
m_remaining_bytes_in_current_data_chunk = m_chunks[m_cur_data_chunk_idx].len;
m_remaining_bytes_in_current_data_chunk = m_chunks[*m_cur_data_chunk_idx].len;
}
return FILE_STATUS_MOREDATA;
@ -232,37 +283,50 @@ wav_reader_c::read(generic_packetizer_c *,
void
wav_reader_c::scan_chunks() {
if (m_type == type_e::wave)
scan_chunks_wave();
else
scan_chunks_wave64();
}
void
wav_reader_c::scan_chunks_wave() {
m_in->setFilePointer(0);
if (m_in->read(&m_wheader.riff, sizeof(m_wheader.riff)) != sizeof(m_wheader.riff))
throw mtx::input::header_parsing_x();
wav_chunk_t new_chunk;
bool debug_chunks = debugging_c::requested("wav_reader|wav_reader_chunks");
try {
int64_t file_size = m_in->get_size();
auto file_size = m_in->get_size();
char id[4];
while (true) {
new_chunk.pos = m_in->getFilePointer();
new_chunk.pos = m_in->getFilePointer() + 8;
if (m_in->read(new_chunk.id, 4) != 4)
if (m_in->read(id, 4) != 4)
return;
new_chunk.id = memory_c::clone(id, 4);
new_chunk.len = m_in->read_uint32_le();
if (debug_chunks)
mxinfo(boost::format("wav_reader_c::scan_chunks() new chunk at %1% type %2% length %3%\n")
% new_chunk.pos % get_displayable_string(new_chunk.id, 4) % new_chunk.len);
mxdebug_if(debug_chunks, boost::format("wav_reader_c::scan_chunks() new chunk at %1% type %2% length %3%\n") % new_chunk.pos % get_displayable_string(id, 4) % new_chunk.len);
if (!strncasecmp(new_chunk.id, "data", 4))
if (!strncasecmp(id, "data", 4))
m_bytes_in_data_chunks += new_chunk.len;
else if (!m_chunks.empty() && !strncasecmp(m_chunks.back().id, "data", 4) && (file_size > 0x100000000ll)) {
else if ( !m_chunks.empty()
&& !strncasecmp(reinterpret_cast<char *>(m_chunks.back().id->get_buffer()), "data", 4)
&& (file_size > 0x100000000ll)) {
wav_chunk_t &previous_chunk = m_chunks.back();
int64_t this_chunk_len = file_size - previous_chunk.pos - sizeof(struct chunk_struct);
int64_t this_chunk_len = file_size - previous_chunk.pos;
m_bytes_in_data_chunks -= previous_chunk.len;
m_bytes_in_data_chunks += this_chunk_len;
previous_chunk.len = this_chunk_len;
if (debug_chunks)
mxinfo(boost::format("wav_reader_c::scan_chunks() hugh data chunk with wrong length at %1%; re-calculated from file size; new length %2%\n")
% previous_chunk.pos % previous_chunk.len);
mxdebug_if(debug_chunks, boost::format("wav_reader_c::scan_chunks() hugh data chunk with wrong length at %1%; re-calculated from file size; new length %2%\n") % previous_chunk.pos % previous_chunk.len);
break;
}
@ -275,17 +339,46 @@ wav_reader_c::scan_chunks() {
}
}
int
void
wav_reader_c::scan_chunks_wave64() {
wav_chunk_t new_chunk;
bool debug_chunks = debugging_c::requested("wav_reader|wav_reader_chunks");
m_in->setFilePointer(sizeof(wave64_header_t));
try {
while (true) {
new_chunk.pos = m_in->getFilePointer() + sizeof(wave64_chunk_t);
new_chunk.id = m_in->read(16);
new_chunk.len = m_in->read_uint64_le();
if (!new_chunk.id || (new_chunk.len < sizeof(wave64_chunk_t)))
return;
new_chunk.len -= sizeof(wave64_chunk_t);
mxdebug_if(debug_chunks, boost::format("wav_reader_c::scan_chunks() new chunk at %1% type %2% length %3%\n") % new_chunk.pos % get_displayable_string(reinterpret_cast<char *>(new_chunk.id->get_buffer()), 4) % new_chunk.len);
if (!strncasecmp(reinterpret_cast<char *>(new_chunk.id->get_buffer()), "data", 4))
m_bytes_in_data_chunks += new_chunk.len;
m_chunks.push_back(new_chunk);
m_in->setFilePointer(new_chunk.len, seek_current);
}
} catch (...) {
}
}
boost::optional<std::size_t>
wav_reader_c::find_chunk(const char *id,
int start_idx,
bool allow_empty) {
size_t idx;
for (idx = start_idx; idx < m_chunks.size(); ++idx)
if (!strncasecmp(m_chunks[idx].id, id, 4) && (allow_empty || m_chunks[idx].len))
for (std::size_t idx = start_idx, end = m_chunks.size(); idx < end; ++idx)
if (!strncasecmp(reinterpret_cast<char *>(m_chunks[idx].id->get_buffer()), id, 4) && (allow_empty || m_chunks[idx].len))
return idx;
return -1;
return {};
}
void

View File

@ -54,18 +54,35 @@ public:
using wav_demuxer_cptr = std::shared_ptr<wav_demuxer_c>;
struct wav_chunk_t {
int64_t pos;
char id[4];
int64_t len;
uint64_t pos, len;
memory_cptr id;
};
struct wave64_chunk_t {
unsigned char guid[16];
uint64_t size;
};
struct wave64_header_t {
wave64_chunk_t riff;
unsigned char wave_guid[16];
};
class wav_reader_c: public generic_reader_c {
public:
enum class type_e {
unknown,
wave,
wave64,
};
private:
type_e m_type;
struct wave_header m_wheader;
int64_t m_bytes_in_data_chunks, m_remaining_bytes_in_current_data_chunk;
std::vector<wav_chunk_t> m_chunks;
int m_cur_data_chunk_idx;
boost::optional<std::size_t> m_cur_data_chunk_idx;
wav_demuxer_cptr m_demuxer;
@ -90,12 +107,19 @@ public:
static int probe_file(mm_io_c *in, uint64_t size);
protected:
static type_e determine_type(mm_io_c &in, uint64_t size);
boost::optional<std::size_t> find_chunk(const char *id, int start_idx = 0, bool allow_empty = true);
void scan_chunks();
int find_chunk(const char *id, int start_idx = 0, bool allow_empty = true);
void scan_chunks_wave();
void scan_chunks_wave64();
void dump_headers();
void parse_file();
void parse_fmt_chunk();
void create_demuxer();
};

View File

@ -35,7 +35,7 @@ bool
wav_dts_demuxer_c::probe(mm_io_cptr &io) {
io->save_pos();
auto read_buf = memory_c::alloc(DTS_READ_SIZE);
read_buf->set_size(io->read(read_buf, DTS_READ_SIZE));
read_buf->set_size(io->read(read_buf->get_buffer(), DTS_READ_SIZE));
io->restore_pos();
if (!m_parser.detect(*read_buf, 5))

View File

@ -452,3 +452,4 @@ T_603mpeg_ps_ac3_not_enough_data_in_first_packet:1+189+128+AC_3/E_AC_3-2+189+129
T_604append_only_one_video_track_with_codec_private:994b07e01a07bdfa5d571ac0edac9ff0:passed:20170624-105837:0.144927484
T_605h264_changing_sps_pps_wrong_frame_order_and_timestamps:1af409bd8266567525c54a0799de44cc:passed:20170704-211727:0.912982996
T_606aac_960_samples_per_frame:69b0ad71348f27421ec3a2fbba9ed33d-a4bcfeaa69074c2ecf5d672c5a21284a:passed:20170720-215449:0.065007779
T_607wave64:567b45caf96e2914012453a72227e4df-a2b74f962f91921d05bf1b6d65d4350e:passed:20170721-221321:0.021307011

8
tests/test-607wave64.rb Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/ruby -w
files = %w{w64-pcm16.w64 w64-pcm32.w64}
# T_607wave64
describe "mkvmerge / Wave64"
files.each { |file| test_merge "data/wav/#{file}" }