From 79e2bda369e91a74765893c87a8f23a6655d6810 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Sun, 15 Sep 2013 20:20:14 +0200 Subject: [PATCH] mkvextract: implement support for Opus --- ChangeLog | 5 ++ src/extract/tracks.cpp | 9 +++- src/extract/xtr_base.cpp | 2 + src/extract/xtr_base.h | 2 + src/extract/xtr_ogg.cpp | 80 ++++++++++++++++++++++++++++---- src/extract/xtr_ogg.h | 21 ++++++++- tests/results.txt | 1 + tests/test-404opus_extraction.rb | 7 +++ 8 files changed, 115 insertions(+), 12 deletions(-) create mode 100755 tests/test-404opus_extraction.rb diff --git a/ChangeLog b/ChangeLog index 8469c104a..ed9ecb357 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2013-09-15 Moritz Bunkus + + * mkvextract: new feature: Implemented extraction of Opus tracks + into OggOpus files. + 2013-09-14 Monty Montgomery * mkvinfo: bug fix: The track information summary enabled with diff --git a/src/extract/tracks.cpp b/src/extract/tracks.cpp index 97a294f92..f2ffc5b8f 100644 --- a/src/extract/tracks.cpp +++ b/src/extract/tracks.cpp @@ -184,9 +184,14 @@ handle_blockgroup(KaxBlockGroup &blockgroup, this_duration = duration / block->NumberFrames(); } + auto discard_padding = timecode_c::ns(0); + auto kdiscard_padding = FindChild(blockgroup); + if (kdiscard_padding) + discard_padding = timecode_c::ns(kdiscard_padding->GetValue()); + auto &data = block->GetBuffer(i); auto frame = std::make_shared(data.Buffer(), data.Size(), false); - auto f = xtr_frame_t{frame, kadditions, this_timecode, this_duration, bref, fref, false, false, true}; + auto f = xtr_frame_t{frame, kadditions, this_timecode, this_duration, bref, fref, false, false, true, discard_padding}; extractor->decode_and_handle_frame(f); max_timecode = std::max(max_timecode, this_timecode); @@ -231,7 +236,7 @@ handle_simpleblock(KaxSimpleBlock &simpleblock, auto &data = simpleblock.GetBuffer(i); auto frame = std::make_shared(data.Buffer(), data.Size(), false); - auto f = xtr_frame_t{frame, nullptr, this_timecode, this_duration, -1, -1, simpleblock.IsKeyframe(), simpleblock.IsDiscardable(), false}; + auto f = xtr_frame_t{frame, nullptr, this_timecode, this_duration, -1, -1, simpleblock.IsKeyframe(), simpleblock.IsDiscardable(), false, timecode_c::ns(0)}; extractor->decode_and_handle_frame(f); max_timecode = std::max(max_timecode, this_timecode); diff --git a/src/extract/xtr_base.cpp b/src/extract/xtr_base.cpp index 0b11fb251..4b21feecb 100644 --- a/src/extract/xtr_base.cpp +++ b/src/extract/xtr_base.cpp @@ -147,6 +147,8 @@ xtr_base_c::create_extractor(const std::string &new_codec_id, return new xtr_alac_c(new_codec_id, new_tid, tspec); else if (new_codec_id == MKV_A_VORBIS) return new xtr_oggvorbis_c(new_codec_id, new_tid, tspec); + else if (new_codec_id == MKV_A_OPUS) + return new xtr_oggopus_c(new_codec_id, new_tid, tspec); else if (balg::istarts_with(new_codec_id, "A_AAC")) return new xtr_aac_c(new_codec_id, new_tid, tspec); else if (balg::istarts_with(new_codec_id, "A_REAL/")) diff --git a/src/extract/xtr_base.h b/src/extract/xtr_base.h index 5a0cd8412..4a0759608 100644 --- a/src/extract/xtr_base.h +++ b/src/extract/xtr_base.h @@ -19,6 +19,7 @@ #include #include "common/content_decoder.h" +#include "common/timecode.h" #include "extract/mkvextract.h" using namespace libmatroska; @@ -28,6 +29,7 @@ struct xtr_frame_t { KaxBlockAdditions *additions; int64_t timecode, duration, bref, fref; bool keyframe, discardable, references_valid; + timecode_c discard_duration; }; class xtr_base_c { diff --git a/src/extract/xtr_ogg.cpp b/src/extract/xtr_ogg.cpp index 086a6bfb4..c0a2a7cff 100644 --- a/src/extract/xtr_ogg.cpp +++ b/src/extract/xtr_ogg.cpp @@ -17,8 +17,10 @@ #include "common/ebml.h" #include "common/endian.h" #include "common/hacks.h" -#include "common/random.h" #include "common/kate.h" +#include "common/opus.h" +#include "common/random.h" +#include "common/version.h" #include "extract/xtr_ogg.h" // ------------------------------------------------------------------------ @@ -67,7 +69,8 @@ xtr_oggbase_c::create_file(xtr_base_c *master, void xtr_oggbase_c::create_standard_file(xtr_base_c *master, - KaxTrackEntry &track) { + KaxTrackEntry &track, + LacingType lacing) { KaxCodecPrivate *priv = FindChild(&track); if (!priv) mxerror(boost::format(Y("Track %1% with the CodecID '%2%' is missing the \"codec private\" element and cannot be extracted.\n")) % m_tid % m_codec_id); @@ -76,11 +79,17 @@ xtr_oggbase_c::create_standard_file(xtr_base_c *master, memory_cptr mpriv = decode_codec_private(priv); std::vector header_packets; - try { - header_packets = unlace_memory_xiph(mpriv); - if (header_packets.empty()) - throw false; + try { + if (lacing == LACING_NONE) + header_packets.push_back(mpriv); + + else { + header_packets = unlace_memory_xiph(mpriv); + + if (header_packets.empty()) + throw false; + } header_packets_unlaced(header_packets); @@ -214,7 +223,7 @@ xtr_oggvorbis_c::~xtr_oggvorbis_c() { void xtr_oggvorbis_c::create_file(xtr_base_c *master, KaxTrackEntry &track) { - create_standard_file(master, track); + create_standard_file(master, track, LACING_AUTO); } void @@ -263,7 +272,7 @@ xtr_oggkate_c::xtr_oggkate_c(const std::string &codec_id, void xtr_oggkate_c::create_file(xtr_base_c *master, KaxTrackEntry &track) { - create_standard_file(master, track); + create_standard_file(master, track, LACING_AUTO); } void @@ -316,7 +325,7 @@ xtr_oggtheora_c::xtr_oggtheora_c(const std::string &codec_id, void xtr_oggtheora_c::create_file(xtr_base_c *master, KaxTrackEntry &track) { - create_standard_file(master, track); + create_standard_file(master, track, LACING_AUTO); } void @@ -341,3 +350,56 @@ xtr_oggtheora_c::finish_file() { write_queued_frame(true); flush_pages(); } + +// ------------------------------------------------------------------------ + +xtr_oggopus_c::xtr_oggopus_c(const std::string &codec_id, + int64_t tid, + track_spec_t &tspec) + : xtr_oggbase_c{codec_id, tid, tspec} + , m_position{timecode_c::ns(0)} +{ + m_debug = debugging_requested("opus|opus_extrator"); +} + +void +xtr_oggopus_c::create_file(xtr_base_c *master, + KaxTrackEntry &track) { + create_standard_file(master, track, LACING_NONE); +} + +void +xtr_oggopus_c::header_packets_unlaced(std::vector &header_packets) { + auto signature = std::string{"OpusTags"}; + auto version = std::string{"unknown encoder; extracted from Matroska with "} + get_version_info("mkvextract"); + auto ver_len = version.length(); + auto mem = memory_c::alloc(8 + 4 + ver_len + 4); + auto buffer = reinterpret_cast(mem->get_buffer()); + + signature.copy(buffer, 8); + put_uint32_le(buffer + 8, ver_len); + version.copy(buffer + 8 + 4, ver_len); + put_uint32_le(buffer + mem->get_size() - 4, 0); + + header_packets.push_back(mem); +} + +void +xtr_oggopus_c::handle_frame(xtr_frame_t &f) { + try { + auto toc = mtx::opus::toc_t::decode(f.frame); + mxdebug_if(m_debug, boost::format("Position: %1% discard_duration: %2% TOC: %3%\n") % m_position % f.discard_duration % toc); + + m_position = m_position + toc.packet_duration - f.discard_duration; + queue_frame(f.frame, m_position.to_samples(48000)); + + } catch (mtx::opus::exception &ex) { + mxdebug_if(m_debug, boost::format("Exception: %1%\n") % ex.what()); + } +} + +void +xtr_oggopus_c::finish_file() { + write_queued_frame(true); + flush_pages(); +} diff --git a/src/extract/xtr_ogg.h b/src/extract/xtr_ogg.h index d7de5046d..c0c4fea96 100644 --- a/src/extract/xtr_ogg.h +++ b/src/extract/xtr_ogg.h @@ -50,7 +50,7 @@ public: virtual void flush_pages(); protected: - virtual void create_standard_file(xtr_base_c *master, KaxTrackEntry &track); + virtual void create_standard_file(xtr_base_c *master, KaxTrackEntry &track, LacingType lacing); virtual void header_packets_unlaced(std::vector &header_packets); virtual void queue_frame(memory_cptr &frame, int64_t granulepos); @@ -114,4 +114,23 @@ protected: virtual void header_packets_unlaced(std::vector &header_packets); }; +class xtr_oggopus_c: public xtr_oggbase_c { +private: + timecode_c m_position; + +public: + xtr_oggopus_c(const std::string &codec_id, int64_t tid, track_spec_t &tspec); + + virtual void create_file(xtr_base_c *master, KaxTrackEntry &track); + virtual void handle_frame(xtr_frame_t &f); + virtual void finish_file(); + + virtual const char *get_container_name() { + return "Ogg (Opus in Ogg)"; + }; + +protected: + virtual void header_packets_unlaced(std::vector &header_packets); +}; + #endif diff --git a/tests/results.txt b/tests/results.txt index 7b723d42c..0f6a9b958 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -249,3 +249,4 @@ T_400opus_experimental:ce4f0bd0c52a9d410ce34b5751416c63:passed:20130703-213929:0 T_401opus_experimental_remux:a910a17383083f2fa75001efd175c352:passed:20130703-213932:0.06857043 T_402opus_output_order:e7d89f40e06bd81db8fa20b2ea6218b8:passed:20130705-115856:0.252820697 T_403opus_remux_final:da99246c61326c47a530f95948b088be:passed:20130705-135811:0.068533558 +T_404opus_extraction:58498a76a35cd151b1c75ad913887f00:passed:20130915-201931:0.050758351 diff --git a/tests/test-404opus_extraction.rb b/tests/test-404opus_extraction.rb new file mode 100755 index 000000000..1a358b357 --- /dev/null +++ b/tests/test-404opus_extraction.rb @@ -0,0 +1,7 @@ +#!/usr/bin/ruby -w + +describe "mkvmerge / extract Opus from MKA in final mode" +test "extraction" do + extract "data/opus/v-opus.mka", 0 => tmp + hash_tmp +end