From 2e02c96599f1cca3686cd8136f31d94524eb6ccb Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Fri, 12 Sep 2008 17:04:11 +0000 Subject: [PATCH] Added support for extracting Theora video tracks into Ogg files. Fix for bug 298. --- ChangeLog | 3 ++ src/extract/xtr_base.cpp | 2 + src/extract/xtr_ogg.cpp | 104 ++++++++++++++++++++++++++++++++++++++- src/extract/xtr_ogg.h | 26 ++++++++-- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 61e14dce1..17a798b1a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2008-09-12 Moritz Bunkus + * mkvextract: new feature: Added support for extracting Theora + video tracks into Ogg files. + * mkvmerge: bug fix: Fixed the frame type (key or non-key frame) detection for Theora tracks. diff --git a/src/extract/xtr_base.cpp b/src/extract/xtr_base.cpp index ddc0d4cee..ba7286a16 100644 --- a/src/extract/xtr_base.cpp +++ b/src/extract/xtr_base.cpp @@ -169,6 +169,8 @@ xtr_base_c::create_extractor(const string &new_codec_id, else if ((new_codec_id == MKV_V_MPEG1) || (new_codec_id == MKV_V_MPEG2)) return new xtr_mpeg1_2_video_c(new_codec_id, new_tid, tspec); + else if (new_codec_id == MKV_V_THEORA) + return new xtr_oggtheora_c(new_codec_id, new_tid, tspec); // Subtitle formats else if ((new_codec_id == MKV_S_TEXTUTF8) || diff --git a/src/extract/xtr_ogg.cpp b/src/extract/xtr_ogg.cpp index 3e04f692a..1899daec4 100644 --- a/src/extract/xtr_ogg.cpp +++ b/src/extract/xtr_ogg.cpp @@ -99,6 +99,10 @@ xtr_oggbase_c::handle_frame(memory_cptr &frame, previous_end = timecode + duration; } +xtr_oggbase_c::~xtr_oggbase_c() { + ogg_stream_clear(&os); +} + void xtr_oggbase_c::finish_file() { ogg_packet op; @@ -117,7 +121,6 @@ xtr_oggbase_c::finish_file() { op.granulepos = previous_end * sfreq / 1000000000; ogg_stream_packetin(&os, &op); flush_pages(); - ogg_stream_clear(&os); } void @@ -392,3 +395,102 @@ xtr_oggkate_c::handle_frame(memory_cptr &frame, packetno++; } + +// ------------------------------------------------------------------------ + +xtr_oggtheora_c::xtr_oggtheora_c(const string &_codec_id, + int64_t _tid, + track_spec_t &tspec) + : xtr_oggbase_c(_codec_id, _tid, tspec) + , keyframe_number(0) + , non_keyframe_number(-1) +{ +} + +void +xtr_oggtheora_c::create_file(xtr_base_c *_master, + KaxTrackEntry &track) { + KaxCodecPrivate *priv = FINDFIRST(&track, KaxCodecPrivate); + if (NULL == priv) + mxerror("Track " LLD " with the CodecID '%s' is missing the \"codec private\" element and cannot be extracted.\n", tid, codec_id.c_str()); + + init_content_decoder(track); + memory_cptr mpriv = decode_codec_private(priv); + + vector header_packets; + try { + header_packets = unlace_memory_xiph(mpriv); + + if (header_packets.empty()) + throw false; + + theora_parse_identification_header(header_packets[0]->get(), header_packets[0]->get_size(), theora); + + } catch (...) { + mxerror("Track " LLD " with the CodecID '%s' does not contain valid headers.\n", tid, codec_id.c_str()); + } + + xtr_oggbase_c::create_file(_master, track); + + ogg_packet op; + for (packetno = 0; packetno < header_packets.size(); packetno++) { + // Handle all the header packets: ID header, comments, etc + op.b_o_s = (0 == packetno ? 1 : 0); + op.e_o_s = 0; + op.packetno = packetno; + op.packet = header_packets[packetno]->get(); + op.bytes = header_packets[packetno]->get_size(); + op.granulepos = 0; + ogg_stream_packetin(&os, &op); + + if (0 == packetno) /* ID header must be alone on a separate page */ + flush_pages(); + } + + /* flush at last header, data must start on a new page */ + flush_pages(); +} + +void +xtr_oggtheora_c::handle_frame(memory_cptr &frame, + KaxBlockAdditions *additions, + int64_t timecode, + int64_t duration, + int64_t bref, + int64_t fref, + bool keyframe, + bool discardable, + bool references_valid) { + content_decoder.reverse(frame, CONTENT_ENCODING_SCOPE_BLOCK); + + if (frame->get_size() && (0x00 == (frame->get()[0] & 0x40))) { + keyframe = true; + keyframe_number += non_keyframe_number + 1; + non_keyframe_number = 0; + + } else + non_keyframe_number += 1; + + ogg_packet op; + + op.b_o_s = 0; + op.e_o_s = 0; + op.packetno = packetno; + op.packet = frame->get(); + op.bytes = frame->get_size(); + op.granulepos = (keyframe_number << theora.kfgshift) | (non_keyframe_number & ((1 << theora.kfgshift) - 1)); + + ogg_stream_packetin(&os, &op); + write_pages(); + + packetno++; + +// mxinfo("Theora kfgshift %d granulepos 0x%08x %08x keyframe_number " LLD " non_keyframe_number " LLD "%s size %d\n", +// theora.kfgshift, (uint32_t)(op.granulepos >> 32), (uint32_t)(op.granulepos & 0xffffffff), keyframe_number, non_keyframe_number, keyframe ? " key" : "", +// frame->get_size()); +} + +void +xtr_oggtheora_c::finish_file() { + flush_pages(); +} diff --git a/src/extract/xtr_ogg.h b/src/extract/xtr_ogg.h index 456a8afaa..77ba2e0e6 100644 --- a/src/extract/xtr_ogg.h +++ b/src/extract/xtr_ogg.h @@ -20,6 +20,7 @@ #include #include "kate_common.h" +#include "theora_common.h" #include "xtr_base.h" class xtr_flac_c: public xtr_base_c { @@ -39,12 +40,11 @@ public: public: xtr_oggbase_c(const string &_codec_id, int64_t _tid, track_spec_t &tspec); + virtual ~xtr_oggbase_c(); virtual void create_file(xtr_base_c *_master, KaxTrackEntry &track); - virtual void handle_frame(memory_cptr &frame, KaxBlockAdditions *additions, - int64_t timecode, int64_t duration, int64_t bref, - int64_t fref, bool keyframe, bool discardable, - bool references_valid); + virtual void handle_frame(memory_cptr &frame, KaxBlockAdditions *additions, int64_t timecode, int64_t duration, int64_t bref, + int64_t fref, bool keyframe, bool discardable, bool references_valid); virtual void finish_file(); virtual void write_pages(); @@ -89,4 +89,22 @@ private: kate_identification_header_t kate_id_header; }; +class xtr_oggtheora_c: public xtr_oggbase_c { +public: + xtr_oggtheora_c(const string &_codec_id, int64_t _tid, track_spec_t &tspec); + + virtual void create_file(xtr_base_c *_master, KaxTrackEntry &track); + virtual void handle_frame(memory_cptr &frame, KaxBlockAdditions *additions, int64_t timecode, int64_t duration, int64_t bref, + int64_t fref, bool keyframe, bool discardable, bool references_valid); + virtual void finish_file(); + + virtual const char *get_container_name() { + return "Ogg (Theora in Ogg)"; + }; + +private: + theora_identification_header_t theora; + int64_t keyframe_number, non_keyframe_number; +}; + #endif