MPEG-2 parser: re-write timestamping code not to rely on exact sequence number progression

The old code was calculating an expected sequence number. As soon as
that sequence number was matched with frames, all previous frames would
be timestamped.

This method is problematic if the actual sequence numbers don't follow
the expected progression.

The new code is much simpler. As soon as an I frame is encountered (or
at the end of the file) the queued frames are first sorted by their
sequence number, then timestamped, and lastly sorted back into their
decoding order.

This fixes #1162 and most likely others (see #1145, #1099 et. al.).
This commit is contained in:
Moritz Bunkus 2015-04-11 14:33:45 +02:00
parent de9470bae2
commit 8696db7e11
6 changed files with 69 additions and 93 deletions

View File

@ -1,5 +1,11 @@
2015-04-11 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: bug fix: MPEG-1/2 parser: fixed a long-standing issue
that prevented mkvmerge from recognizing certain MPEG-1/2 video
tracks and files if the frame's sequence numbers didn't follow a
certain expected pattern. Fixes #1162 and probably others like
#1145 or #1099.
* MKVToolNix GUI: merge tool enhancement: Implemented adding and
append files and adding files as additional parts via drag & drop
from external applications.

View File

@ -602,19 +602,26 @@ mpeg_ps_reader_c::new_stream_v_mpeg_1_2(mpeg_ps_id_t id,
bool found_i_frame = false;
bool found_non_b_frame = false;
bool flushed = false;
MPEG2SequenceHeader seq_hdr;
while ( (MPV_PARSER_STATE_EOS != state)
&& (MPV_PARSER_STATE_ERROR != state)
&& (PS_PROBE_SIZE >= m_in->getFilePointer())) {
if (!find_next_packet_for_id(id, PS_PROBE_SIZE))
if (find_next_packet_for_id(id, PS_PROBE_SIZE)) {
auto packet = parse_packet(id);
if (!packet)
break;
m2v_parser->WriteData(packet.m_buffer->get_buffer(), packet.m_length);
} else if (flushed)
break;
auto packet = parse_packet(id);
if (!packet)
break;
m2v_parser->WriteData(packet.m_buffer->get_buffer(), packet.m_length);
else {
m2v_parser->SetEOS();
flushed = true;
}
state = m2v_parser->GetState();

View File

@ -64,6 +64,7 @@ void M2VParser::SetEOS(){
c = mpgBuf->ReadChunk();
if(c) chunks.push_back(c);
FillQueues();
TimestampWaitingFrames();
m_eos = true;
}
@ -117,7 +118,6 @@ M2VParser::M2VParser()
notReachedFirstGOP = true;
previousTimecode = 0;
previousDuration = 0;
queueTime = 0;
waitExpectedTime = 0;
probing = false;
b_frame_warning_printed = false;
@ -205,12 +205,15 @@ MPEG2ParserState_e M2VParser::GetState(){
void M2VParser::FlushWaitQueue(){
waitSecondField = false;
while(!waitQueue.empty()){
delete waitQueue.front();
waitQueue.pop();
if (!m_timecodes.empty())
m_timecodes.pop_front();
for (auto const &frame : waitQueue)
delete frame;
while (!buffers.empty()) {
delete buffers.front();
buffers.pop();
}
waitQueue.clear();
m_timecodes.clear();
}
void M2VParser::StampFrame(MPEGFrame* frame){
@ -248,7 +251,8 @@ void M2VParser::UpdateFrame(MPEGFrame* frame){
int32_t M2VParser::OrderFrame(MPEGFrame* frame){
MPEGFrame *p = frame;
bool flushQueue = false;
// mxinfo(boost::format("picStr %1% frame type %2% qt tc %3%\n") % p->timecode % static_cast<int>(p->pictureStructure) % p->frameType);
if (waitSecondField && (p->pictureStructure == MPEG2_PICTURE_TYPE_FRAME)){
auto error = Y("Unexpected picture frame after single field frame. Fix the MPEG2 video stream before attempting to multiplex it.\n");
@ -257,86 +261,38 @@ int32_t M2VParser::OrderFrame(MPEGFrame* frame){
mxerror(error);
}
/* While the refs of a frame are set in SetFrameRef, the actual final timecodes are possibly derived later.
* Based on display order, a dependent frame (below: "DEP") can be located after or before a ref frame (below: "REF").
* This leads to the following behaviour:
*
* ***** case "after" => REF takes branch 1 below *****
* - REF: ShoveRef/StampFrame set both frame pointer and timecode at M2VParser::refs.
* - DEP: SetFrameRef already derives the timecode.
*
* ***** case "before" => REF takes branch 2 below *****
* - REF: ShoveRef sets the frame pointer at M2VParser::refs.
* - DEP: SetFrameRef derives that pointer in the first place.
* Later, while queue flushing:
* - REF: StampFrame sets the timecode.
* - DEP: UpdateFrame derives the timecode via frame pointer.
* The queue frames are in decoding order, therefore DEP's UpdateFrame happens before REF's StampFrame.
* In addition, tmpForwardQueue ensures that the pointed to ref frame is still available.
*/
SetFrameRef(p);
ShoveRef(p);
/*
* Process incoming frames (being in decoding order) and ensure correct stamping (in display order) - output again happens in decoding order.
* - branch 1: not waiting; forward frame, which has consecutive sequence number
* - branch 2: not waiting; queue frame, which has preceding frames (regarding display order) + start waiting for that intermediate frames
* - branch 3: waiting: queue frame (if last intermediate frame, finish waiting by invoking flush)
* - branch 4: waiting; queue frame - sequence number beyond expected range (this can only occur at defective streams!)
*/
if ((p->timecode == queueTime) && waitQueue.empty()) { // branch 1
if ((p->pictureStructure == MPEG2_PICTURE_TYPE_FRAME) || waitSecondField)
queueTime++;
StampFrame(p);
UpdateFrame(p);
buffers.push(p);
} else if ((p->timecode > queueTime) && waitQueue.empty()) { // branch 2
waitExpectedTime = p->timecode - 1;
waitQueue.push(p);
} else if (p->timecode <= waitExpectedTime) { // branch 3
if ((p->timecode == waitExpectedTime) && ((p->pictureStructure == MPEG2_PICTURE_TYPE_FRAME) || waitSecondField))
flushQueue = true;
StampFrame(p);
waitQueue.push(p);
} else // branch 4
waitQueue.push(p);
if (('I' == p->frameType) && !waitQueue.empty())
TimestampWaitingFrames();
if(flushQueue){
int i,l;
waitSecondField = false;
l = waitQueue.size();
for(i=0;i<l;i++){
p = waitQueue.front();
waitQueue.pop();
if(!p->stamped) {
StampFrame(p);
}
UpdateFrame(p);
// temporarily cache all frames, to ensure that all needed ref frames are still available while UpdateFrame (see above)
tmpForwardQueue.push(p);
}
for (i = 0; i < l; i++) {
p = tmpForwardQueue.front();
tmpForwardQueue.pop();
if((p->pictureStructure == MPEG2_PICTURE_TYPE_FRAME) || waitSecondField){
queueTime++;
}
if(p->pictureStructure != MPEG2_PICTURE_TYPE_FRAME)
waitSecondField = !waitSecondField;
buffers.push(p);
}
waitSecondField = false;
}else if (p->pictureStructure != MPEG2_PICTURE_TYPE_FRAME){
waitSecondField = !waitSecondField;
}
waitQueue.push_back(p);
return 0;
}
void
M2VParser::TimestampWaitingFrames() {
// mxinfo(boost::format(" flushing %1%\n") % waitQueue.size());
for (std::size_t idx = 0, numFrames = waitQueue.size(); idx < numFrames; ++idx)
waitQueue[idx]->decodingOrder = idx;
brng::sort(waitQueue, [](MPEGFrame *a, MPEGFrame *b) { return a->timecode < b->timecode; });
for (auto const &frame : waitQueue)
StampFrame(frame);
for (auto const &frame : waitQueue)
UpdateFrame(frame);
brng::sort(waitQueue, [](MPEGFrame *a, MPEGFrame *b) { return a->decodingOrder < b->decodingOrder; });
for (auto const &frame : waitQueue)
buffers.push(frame);
waitQueue.clear();
}
int32_t M2VParser::PrepareFrame(MPEGChunk* chunk, MediaTime timecode, MPEG2PictureHeader picHdr){
MPEGFrame* outBuf;
bool bCopy = true;

View File

@ -61,6 +61,7 @@ public:
MediaTime duration;
char frameType;
MediaTime timecode; // before stamping: sequence number; after stamping: real timecode
unsigned int decodingOrder;
MediaTime refs[2];
MPEGFrameRef tmpRefs[2];
bool stamped;
@ -78,8 +79,7 @@ public:
class M2VParser {
private:
std::vector<MPEGChunk*> chunks; //Hold the chunks until we can order them
std::queue<MPEGFrame*> waitQueue; //Holds unstamped buffers until we can stamp them.
std::queue<MPEGFrame*> tmpForwardQueue; //Temporarily holds stamped buffers until we can forward them.
std::vector<MPEGFrame*> waitQueue; //Holds unstamped buffers until we can stamp them.
std::queue<MPEGFrame*> buffers; //Holds stamped buffers until they are requested.
MediaTime previousTimecode;
MediaTime previousDuration;
@ -87,7 +87,6 @@ private:
MPEGChunk* seqHdrChunk, *gopChunk;
MPEG2SequenceHeader m_seqHdr; //current sequence header
MPEG2GOPHeader m_gopHdr; //current GOP header
MediaTime queueTime;
MediaTime waitExpectedTime;
bool waitSecondField;
bool probing;
@ -168,6 +167,8 @@ public:
void AddTimecode(int64_t timecode);
void SetThrowOnError(bool doThrow);
void TimestampWaitingFrames();
};

View File

@ -94,7 +94,7 @@ T_244iconv_missing_character:b0cea35ff7be939dc13b0e373657695a:passed:20081004-21
T_245srt_timecode_formats:ce80ae14afb44f507cfe4226944d1266-ce80ae14afb44f507cfe4226944d1266:passed:20081202-141604:0.255023139
T_246theora_pixel_aspect_ratio:2067abd2dc972e76ec7b1080927cbe8e:passed:20081205-174857:0.061985381
T_247attachment_selection:04e4c3afcde00ea879132481a68d1478-9c6b5c11674833d484f9a3465c42f4bd-6e3afba12988ca0d66b15096002dcaa3-04a48cc0da545825357249eace746de8+7aba76631fef5d8be295411dd12ccc9e:passed:20090228-191612:0.113510024
T_248mpeg2:c11e561110cebaa7268be5c7991ccbf3-00db43954883d06d2805a81d277ecc10:passed:20090531-132819:8.128150931
T_248mpeg2:326006565ffe36597085992dccbe19ac-00db43954883d06d2805a81d277ecc10:passed:20090531-132819:8.128150931
T_249mpeg2_no_codecprivate:9a338fc21ac14764bc63cbdde68d7f2b:passed:20090531-132821:1.45695982
T_250tag_selection:9df21986c4523e8d83533391e9d3a3dd-d1b4cc27362894ef70176a4e42ce88b7-59ee8324b57552b1b63b7c8a6866efae-0411dd08be9cc75d7ac861782af4150b-ee207fb3cb5933299d2bc80919c2d7f4-ee207fb3cb5933299d2bc80919c2d7f4-09c92b6792730887038da4c7e25ebc9f:passed:20090531-205640:1.287172749
T_251vc1_truehd_eac3_from_evo:74614cfa6328f70b12b2327277f5f61e:passed:20090606-220945:1.173449174
@ -171,7 +171,7 @@ T_322propedit_track_headers:785209f2dc35ad6177bea2ca6e43198f-3e9ff1255235f31945c
T_323propedit_segment_info:785209f2dc35ad6177bea2ca6e43198f-0baf1cb46e7d365580b51aa4452551d6-d6cd21681b8544aa730744f128d46fd7:passed:20111203-152845:0.425794709
T_324propedit_chapters:785209f2dc35ad6177bea2ca6e43198f-a9255d40de93e2731aaead0a746e582f-493859725ffbe8bf65cfd397e26a7c90-ae788bbd0580dd01d10672a46e3be84d-9ff842a7f02fe26ef95313b626be83b3-b6aafbfe2bc4902f3187031a71730f8d-3057bf142672a2c86657c64f4e4d189a-48614f8c72edb5d3e23115e1998f5997-8594e3734741654285cc5ce5e48f3e29-d41d8cd98f00b204e9800998ecf8427e:passed:20111203-154502:0.736303091
T_325propedit_tags:785209f2dc35ad6177bea2ca6e43198f-26ad4ab0491d76d9fb6f57b4a4b35400-2d6bc3c519e65853430cd53e64d46386-52322241c33de4f7b56ede7a4764f72f-2d6bc3c519e65853430cd53e64d46386-52322241c33de4f7b56ede7a4764f72f-ddb1f5475eca068a3e4b6ad8df1dea7e-591a65deb231cb0150a4da224d5f3415-e657d1255b0b7a1a1610bda7460dc318-438f0a8ce757e03b77c084bd593cb196-e657d1255b0b7a1a1610bda7460dc318-438f0a8ce757e03b77c084bd593cb196-6f60e83c7d351a25ad6af4545ce3dff6-5a8f05d63ffe9d53046abf40ec30e42a-e126ac5e6a97297f4ee14cfd648e1e33-d41d8cd98f00b204e9800998ecf8427e:passed:20111203-160727:1.314514241
T_326mpeg_ps_mpeg_audio_layer4:92c7507e54caf041d729c2e9e97c775f:passed:20111207-224511:1.505513342
T_326mpeg_ps_mpeg_audio_layer4:fca2d489ec0cd9ade511f3145187464b:passed:20111207-224511:1.505513342
T_327vp8_frame_type:da02f5873c366315b4594e34a04966e8:passed:20111207-233304:0.089263543
T_328dts_detected_as_ac3:6cadc1ac331dda8e80eae3abfeca1358:passed:20111229-192324:0.090561076
T_329X_timecodes_v2:dadc36ce79c1c4b281f8f1f865746598-049cdc2d9226fac8c61d193d803bfc1f-3720aac3f16b66ec3308ffa7bf913c6e-6469e2522a4b48b7b20bae93f5d9086d-1ff091abfcb0938d6ac7fd0495e899b3-049cdc2d9226fac8c61d193d803bfc1f-d172a9340cbf2802690479e396879d1e-bf76c5886cc7c18cc7e6ee796c3406b4-b3f9d126c31505c22f292a1d2bdffba2-4bd97467fac0ac0b561d68b8b15a79dd:passed:20120105-202451:1.376047868
@ -206,7 +206,7 @@ T_357segment_info:c734542adcdeca270db3b6e41fd85ffc-61d4730547bcd79e9a692caa4c214
T_358usf:13cc323a8e690b4e1c236010938e1ee3:passed:20120329-142144:0.051754089
T_359split_parts:bc2ff718d54847937b9f6b4f2e38036d+49341e4669faff0225236af4eda8eb09+ok-5efda882c230b3ff5a044af4148f8d1c+ok-b1619e39ba9194cf2ef3187991f6ed18+ok-497448adf872f4050f5e34d9aacf5fa0+4f25451d573042cfdff4809a28638e93+782db07154db9054ff9f0ef3bf1235c6+f129ab41e298b6358fc0197c92c3da5f+91120826bea5d0462090d54416a571b1+ad267ff44bf21e0acee2a0dc009908d6+1586ba9b356c823bd58566c58e635889+ok-32b1d647cec6dc843b31f3030daf9ee7+ok-a72d22cdc1ab54c605930259a9953aff+ok:passed:20120331-133448:2.321768368
T_360X_chapters_hex_format:87a60c81c05fb0a153a2e041485ae2cb-3853793b0d88fc10efadb146ca948833:passed:20120404-152038:0.047282116
T_361file_concatenation:68b5608e9ff344d493fdcbbdc24b821b-35c3e616631e24ef8df17c8cfb0b35d0-35c3e616631e24ef8df17c8cfb0b35d0-98fd5ee77c40d1de6244c7afca3cfa15-98fd5ee77c40d1de6244c7afca3cfa15-98fd5ee77c40d1de6244c7afca3cfa15-2b0cde47d9cff1d464c78f2e158582f1-2b64e3421c5553b1dcd8675f69c0b6cc-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-09ffbb6cd3ce4f4947f1f2e782be6cdd-d7039c2d63d417c29d4ba7bf623563e5-80e456901895bd509cc8f2f6ed10d587-80e456901895bd509cc8f2f6ed10d587:passed:20120406-144928:18.646532342
T_361file_concatenation:68b5608e9ff344d493fdcbbdc24b821b-ed5c6fbaedc5f24c2a8321c306baff41-ed5c6fbaedc5f24c2a8321c306baff41-98fd5ee77c40d1de6244c7afca3cfa15-98fd5ee77c40d1de6244c7afca3cfa15-98fd5ee77c40d1de6244c7afca3cfa15-2b0cde47d9cff1d464c78f2e158582f1-2b64e3421c5553b1dcd8675f69c0b6cc-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-740607972d7bb60c595835efdb9d880d-09ffbb6cd3ce4f4947f1f2e782be6cdd-d7039c2d63d417c29d4ba7bf623563e5-80e456901895bd509cc8f2f6ed10d587-80e456901895bd509cc8f2f6ed10d587:passed:20120406-144928:18.646532342
T_362xtr_avc:abc9dd7b4579a2e14783271d1d64d486-49ae33bdb1e43de90886bc2ac6410c35:passed:20120416-153515:1.811589633
T_363srt_colon_decimal_separator:d75be97f27797c8b3fc62e5d39a8d7ea:passed:20120520-180625:0.032379535
T_364qtmp4_track_with_empty_chunkmap_table:f7837ed142ed9a5cca5d2fbc5788d597:passed:20120605-223925:0.168392001
@ -234,7 +234,7 @@ T_385split_parts_frames:bc2ff718d54847937b9f6b4f2e38036d+49341e4669faff0225236af
T_386flv_vp6f:06bccf045351fc4946cb24c4990d396c:passed:20130121-223417:0.187881106
T_387mp4_free_invalid_size:6a338e059717d57b89efa683031ee3c6:passed:20130216-003333:0.04107827
T_388split_parts_and_chapters:8d2a65e53a0d3c8ed6050fc8d4679538-7d1a25c6a3c78c7f1469d1dcf637af4f-0e101a08e2be05f0247b09e93635b1de:passed:20130216-203522:0.690018077
T_389mpeg1_in_ps_misdetected_as_avc:276be21585918359788a0bf47dbf8502:passed:20130224-132738:1.205560654
T_389mpeg1_in_ps_misdetected_as_avc:566aeaa72d47e30589f3227555bf24d3:passed:20130224-132738:1.205560654
T_390timecode_info_on_resync:ok+ok:passed:20130318-185621:0.115461517
T_391fix_bitstream_frame_rate:6d71f661ca5682143eddaf0842435952:passed:20130329-114522:0.460370742
T_392avi_audio_chunk_size_0:77f8ab45d16202e5e0a4bdabc7f616f0:passed:20130331-134751:0.893777097
@ -283,7 +283,7 @@ T_434mkvpropedit_no_track_uid:99631d7c0f79faf45a37696dae506b21-ab7b3ff004b7ed5a1
T_435mp4_edit_list_duration_uses_global_time_scale:eba0b6ddc51c05e4a97f39a3bb350b01:passed:20140905-183027:0.096674624
T_436extract_ssa_extradata_after_events:8be4c4af0a2d65826071aee718ec44e8:passed:20140906-090602:0.054263072
T_437ac3_from_avi_with_garbage:dd5b3c1593cdccac26d2b8e8c6694893:passed:20140908-144311:0.075599921
T_438pcm_in_vob:09b8c5d3ca0f0224f3ccc9f0ca4f5930:passed:20140917-213731:1.216717009
T_438pcm_in_vob:f39c9585f8fe45d89073a4531dc69d9d:passed:20140917-213731:1.216717009
T_439pcm_in_m2ts:e508ff1185855b9454c4b15ca6278790-fae839a729cd17480f7f88d6736df0e4:passed:20140917-222633:4.736872644
T_440chapter_display_language_default_value:6dd844972d790ee741c58f5de5ff1555:passed:20140929-142306:0.021103552
T_441mkvmerge_mp4_big_endian_pcm:ddd27c5fa93e1e4fe610707e8b050365:passed:20141104-190420:0.278181073
@ -335,3 +335,4 @@ T_486m2ts_eac3_with_extension_in_own_packet:265d8436497eb50f49b24805a08cd39a:pas
T_487matroska_version_and_read_version_with_opus:4+2-4+2-4+2-4+1-4+2-4+1-4+1-4+1:passed:20150329-213811:0.670610463
T_488hevc_conformance_window_with_cropping:71d84f56384c2d25fe55477766b7604d:passed:20150329-220212:0.705828455
T_489dts_es:6f9fb5e7a5144060ce4b19ee146bb03a-0770ee503180e4d8cfd57335e684921d-99ccde4d9e7b1cd0ff711edfe9cdad1e:passed:20150403-115948:1.604769748
T_490sequence_numbers_no_0_in_first_gop:10a0cc2f79acd8cb520661b45721e652:new:20150411-142423:0.832402022

View File

@ -0,0 +1,5 @@
#!/usr/bin/ruby -w
# T_490sequence_numbers_no_0_in_first_gop
describe "mkvmerge / MPEG-2 ES without a sequence number == 0 in the first GOP"
test_merge "data/mpeg12/sequence_numbers_no_0_in_first_gop.m2v"