diff --git a/NEWS.md b/NEWS.md index a34c482e6..47573ea10 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/src/input/r_wav.cpp b/src/input/r_wav.cpp index 285557c0d..d6f0403cb 100644 --- a/src/input/r_wav.cpp +++ b/src/input/r_wav.cpp @@ -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(m_chunks[chunk_idx].len) >= sizeof(alWAVEFORMATEXTENSIBLE)) { + if (static_cast(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(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(new_chunk.id->get_buffer()), 4) % new_chunk.len); + + if (!strncasecmp(reinterpret_cast(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 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(m_chunks[idx].id->get_buffer()), id, 4) && (allow_empty || m_chunks[idx].len)) return idx; - return -1; + return {}; } void diff --git a/src/input/r_wav.h b/src/input/r_wav.h index be8508a2e..076be9500 100644 --- a/src/input/r_wav.h +++ b/src/input/r_wav.h @@ -54,18 +54,35 @@ public: using wav_demuxer_cptr = std::shared_ptr; 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 m_chunks; - int m_cur_data_chunk_idx; + boost::optional 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 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(); }; diff --git a/src/input/wav_dts_demuxer.cpp b/src/input/wav_dts_demuxer.cpp index bd6625df7..15a9700f0 100644 --- a/src/input/wav_dts_demuxer.cpp +++ b/src/input/wav_dts_demuxer.cpp @@ -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)) diff --git a/tests/results.txt b/tests/results.txt index 54eb83a13..4d7870594 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -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 diff --git a/tests/test-607wave64.rb b/tests/test-607wave64.rb new file mode 100755 index 000000000..e264b27d9 --- /dev/null +++ b/tests/test-607wave64.rb @@ -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}" }