Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpc-hc/FFmpeg.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoronomatopellan <onomatopellan@gmail.com>2015-03-24 22:00:34 +0300
committerHendrik Leppkes <h.leppkes@gmail.com>2017-08-04 20:18:07 +0300
commit7b3164e5767bc2609cbf825deae9d17b25b462d7 (patch)
tree30b6bf82824cd4523f312cb98170adb3b7f290f6
parente6283dc52b78f1a4091da13da039a1f0a4db9101 (diff)
MatroskaParser: show pre-existing subtitles when seeking
Based on a patch by John Peebles <johnpeeb@gmail.com> Signed-off-by: Hendrik Leppkes <h.leppkes@gmail.com>
-rw-r--r--libavformat/MatroskaParser.c215
1 files changed, 210 insertions, 5 deletions
diff --git a/libavformat/MatroskaParser.c b/libavformat/MatroskaParser.c
index 5165021249..364c20ccc0 100644
--- a/libavformat/MatroskaParser.c
+++ b/libavformat/MatroskaParser.c
@@ -471,6 +471,32 @@ static struct QueueEntry *QGet(struct Queue *q) {
return qe;
}
+// allocate and initialize an array of Queues, one for each track
+static struct Queue* QsStructAlloc(MatroskaFile *mf) {
+ struct Queue *q = mf->cache->memalloc(mf->cache, mf->nTracks * sizeof(struct Queue));
+ if (q == NULL)
+ errorjmp(mf, "Ouf of memory");
+ memset(q, 0, mf->nTracks * sizeof(struct Queue));
+
+ return q;
+}
+
+// dump the contents of src into the head of each dest queue such that the
+// old tail of src[i] points to the old head of dest[i], the new head of dest[i]
+// is the old head of src[i], and the src queues are now empty.
+static void QsDumpHead(struct Queue *destination, struct Queue *source, int n) {
+ for (int i = 0; i < n; ++i) {
+ struct Queue *dest = &destination[i];
+ struct Queue *src = &source[i];
+ if (!src->tail) // do nothing if src queue empty
+ continue;
+ src->tail->next = dest->head; // link up queues
+ dest->head = src->head; // set heads and tails properly
+ src->head = NULL;
+ src->tail = NULL;
+ }
+}
+
static struct QueueEntry *QAlloc(MatroskaFile *mf) {
struct QueueEntry *qe,**qep;
if (mf->QFreeList == NULL) {
@@ -505,6 +531,15 @@ static inline void QFree(MatroskaFile *mf,struct QueueEntry *qe) {
mf->QFreeList = qe;
}
+// deallocatee an array of Queues, one for each track
+static void QsStructFree(MatroskaFile *mf, struct Queue *q) {
+ for (int i = 0; i < mf->nTracks; ++i) {
+ while (q[i].head)
+ QFree(mf, QGet(&q[i]));
+ }
+ mf->cache->memfree(mf->cache, q);
+}
+
// fill the buffer at current position
static void fillbuf(MatroskaFile *mf) {
int rd;
@@ -1618,6 +1653,7 @@ static void parseCues(MatroskaFile *mf,ulonglong toplen) {
cc.Block = 0;
cc.Duration = 0;
cc.RelativePosition = 0;
+ cc.Track = 0;
memcpy(&jb,&mf->jb,sizeof(jb));
@@ -1635,6 +1671,14 @@ static void parseCues(MatroskaFile *mf,ulonglong toplen) {
cc.Time = readUInt(mf,(unsigned)len);
break;
case 0xb7: // CueTrackPositions
+
+ // reset out everything but CueTime
+ cc.Position = 0;
+ cc.Block = 0;
+ cc.Duration = 0;
+ cc.RelativePosition = 0;
+ cc.Track = 0;
+
FOREACH(mf,len)
case 0xf7: // CueTrack
v = readUInt(mf,(unsigned)len);
@@ -2910,6 +2954,135 @@ static void DeleteChapter(MatroskaFile *mf,struct Chapter *ch) {
mf->cache->memfree(mf->cache,ch->Children);
}
+// Precondition: we are currently right at the start of a Block
+// or SimpleBlock or BlockGroup element and the Queues are all empty.
+//
+// Parse the block at relative position cueRelativePosition within the
+// cluster at absolute offset clusterOffset and return a pointer to a QueueEntry
+// storing its data. (This only parses the block and not sibling
+// elements so the start timecode will equal the end timecode.)
+// If we are not right at the start of something resembling a block,
+// returns NULL. The caller is responsible for ensuring eventual disposal
+// of the QueueEntry via the QFree function. This function gurantees mf queues
+// are empty after completing.
+//
+// The returned QueueEntry may have garbage Start and End values
+static struct QueueEntry* seekAndReadLoneBlock(MatroskaFile *mf, ulonglong clusterOffset, ulonglong cueRelativePosition) {
+ jmp_buf jb;
+ int ebmlID;
+ ulonglong toplen;
+ int nTracks = mf->nTracks;
+ struct QueueEntry *ret = NULL;
+ ulonglong clusterRelZero;
+
+ memcpy(&jb, &mf->jb, sizeof(jb));
+ if (setjmp(mf->jb) != 0)
+ goto out;
+
+ seek(mf, clusterOffset + 4); // seek to right after the ebml id of the cluster
+ readSizeUnspec(mf); // move to right after the end of the encoding of the cluster size (relative position 0)
+ clusterRelZero = filepos(mf); // absolute offset corresponding to relative position 0 within the cluster
+ seek(mf, clusterRelZero + cueRelativePosition); // seek to start of BlockGroup/Block/SimpleBlock
+
+ // the spec is unclear on whether CueRelativePositoin should be the position of a Block's BlockGroup
+ // or the Block itself, so we handle both possibilities.
+ ebmlID = readID(mf);
+ if (ebmlID == 0xa0 || ebmlID == 0xa1 || ebmlID == 0xa3) { // BlockGroup or Block or SimpleBlock
+ toplen = readSize(mf);
+
+ if (ebmlID == 0xa0) { // BlockGroup
+ parseBlockGroup(mf, toplen, 0, 0); // parses block into a queue in mf
+ } else {
+ parseBlockGroup(mf, toplen, 0, 1); // parses block into a queue in mf
+ }
+
+ // find the block and return it
+ for (int i = 0; i < nTracks; ++i) {
+ ret = QGet(&mf->Queues[i]);
+ if (ret)
+ break;
+ }
+ }
+
+out:
+ // if for some reason a file tried to use lacing
+ // for subtitle blocks, we might have a nonempty queue.
+ EmptyQueues(mf);
+ memcpy(&mf->jb, &jb, sizeof(jb));
+ return ret;
+}
+
+// get index in mf->Tracks corresponding to trackNum
+// returns -1 if track number is invalid
+static int TrackNumToIndex(MatroskaFile *mf, unsigned char trackNum) {
+ int nTracks = mf->nTracks;
+ for (int i = 0; i < nTracks; ++i)
+ if (mf->Tracks[i]->Number == trackNum)
+ return i;
+
+ return -1;
+}
+
+// returns the index of the next cue at index >= startIdx that corresponds
+// to a pre-existing subtitle at timecode timecode. If no such cue exists,
+// returns -1. All cues corresponding to indices returned by this function are
+// guaranteed to have valid track numbers.
+//
+static int NextPESubtitleIdx(MatroskaFile *mf, ulonglong timecode, int startIdx) {
+ int nCues = mf->nCues;
+ Cue cue;
+
+ for (int i = startIdx; i < nCues; ++i) {
+ cue = mf->Cues[i];
+
+ if (cue.Time > timecode) {
+ break;
+ } else {
+ int trackIndex = TrackNumToIndex(mf, cue.Track);
+
+ if (trackIndex >= 0 && mf->Tracks[trackIndex]->Type == TT_SUB && !(mf->trackMask & (ULL(1) << trackIndex))
+ && cue.Duration && cue.RelativePosition && cue.Time + cue.Duration >= timecode) {
+
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static void GetSubtitlePreroll(MatroskaFile *mf, ulonglong timecode, struct Queue *subPreQueues) {
+ struct QueueEntry *qe;
+ Cue cue;
+ ulonglong prevPosition = 0, prevRelativePosition = 0;
+
+ EmptyQueues(mf);
+
+ // for each cue that overlaps with timecode in a subtitle track, add it to the corresponding
+ // queue in subPreQueues
+ for (int i = NextPESubtitleIdx(mf, timecode, 0); i != -1; i = NextPESubtitleIdx(mf, timecode, i+1)) {
+ cue = mf->Cues[i];
+
+ // skip to next cue if we are going to read same block as previous
+ if (cue.Position == prevPosition && cue.RelativePosition == prevRelativePosition)
+ continue;
+
+ // read the block contents into a QueueEntry and insert it
+ qe = seekAndReadLoneBlock(mf, mf->pSegment + cue.Position, cue.RelativePosition);
+
+ if (qe) {
+ int trackIndex = TrackNumToIndex(mf, cue.Track);
+ qe->Start = mul3(mf->Tracks[trackIndex]->TimecodeScale, cue.Time);
+ qe->End = qe->Start + cue.Duration;
+ QPut(&subPreQueues[trackIndex], qe);
+ }
+
+ // save present Position and RelativePosition for future comparisons
+ prevPosition = cue.Position;
+ prevRelativePosition = cue.RelativePosition;
+ }
+}
+
///////////////////////////////////////////////////////////////////////////
// public interface
MatroskaFile *mkv_OpenEx(InputStream *io,
@@ -3121,6 +3294,7 @@ void mkv_Seek(MatroskaFile *mf,ulonglong timecode,unsigned flags) {
unsigned n,z;
ulonglong mask,m_kftime[MAX_TRACKS];
unsigned char m_seendf[MAX_TRACKS];
+ struct Queue *subPreQueues = NULL;
if (mf->flags & MKVF_AVOID_SEEKS)
return;
@@ -3145,12 +3319,16 @@ void mkv_Seek(MatroskaFile *mf,ulonglong timecode,unsigned flags) {
i = 0;
j = mf->nCues - 1;
+ // get pre-existing subtitles that should be displayed at timecode
+ subPreQueues = QsStructAlloc(mf);
+ GetSubtitlePreroll(mf, timecode, subPreQueues);
+
for (;;) {
if (i>j) {
j = j>=0 ? j : 0;
if (setjmp(mf->jb)!=0)
- return;
+ goto dealloc;
mkv_SetTrackMask(mf,mf->trackMask);
@@ -3173,7 +3351,7 @@ void mkv_Seek(MatroskaFile *mf,ulonglong timecode,unsigned flags) {
for (;;) {
if ((ret = fillQueues(mf,0)) < 0 || ret == RBRESYNC)
- return;
+ goto dealloc;
// drain queues until we get to the required timecode
for (n=0;n<mf->nTracks;++n) {
@@ -3238,7 +3416,7 @@ again:;
for (mask=mf->trackMask;;) {
if ((ret = fillQueues(mf,mask)) < 0 || ret == RBRESYNC)
- return;
+ goto dealloc;
// drain queues until we get to the required timecode
for (n=0;n<mf->nTracks;++n) {
@@ -3255,8 +3433,31 @@ again:;
++z;
}
- if (z==mf->nTracks)
- return;
+ if (z==mf->nTracks) {
+ for (int i = 0; i<mf->nTracks; ++i) {
+ if (subPreQueues[i].head) { // if the subPreQueues are not empty
+ // remove any subtitles from queues that are duplicates of stuff in subPreQueues
+ if (mf->Tracks[i]->Type == TT_SUB)
+ while (mf->Queues[i].head && mf->Queues[i].head->Start <= timecode)
+ QFree(mf, QGet(&mf->Queues[i]));
+
+ // from subPreQueues, filter out any subtitle blocks that we'll see later on in the file
+ // (prevents the occasional case of having a subtitle displayed twice)
+ ulonglong fp = filepos(mf);
+
+ struct QueueEntry *qe;
+ struct Queue tmpQ = { .head = NULL, .tail = NULL };
+ while (qe = subPreQueues[i].head)
+ if (qe->Position < fp)
+ QPut(&tmpQ, QGet(&subPreQueues[i]));
+ else
+ QFree(mf, QGet(&subPreQueues[i]));
+ subPreQueues[i] = tmpQ;
+ }
+ }
+ QsDumpHead(mf->Queues, subPreQueues, mf->nTracks); // add pre-existing subtitles to the queues
+ goto dealloc;
+ }
}
}
@@ -3267,6 +3468,10 @@ again:;
else
i = m+1;
}
+
+dealloc:
+ if (subPreQueues)
+ QsStructFree(mf, subPreQueues);
}
void mkv_SkipToKeyframe(MatroskaFile *mf) {