Bug 1289438: [ogg] P1. Always seek to the closest keyframe. r?brion
MozReview-Commit-ID: 1IGFgU1GFz5
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -193,16 +193,23 @@ ogg_packet* OggCodecState::PacketOut() {
ogg_packet* OggCodecState::PacketPeek() {
if (mPackets.IsEmpty()) {
return nullptr;
}
return mPackets.PeekFront();
}
+void OggCodecState::PushFront(OggPacketQueue &&aOther)
+{
+ while (!aOther.IsEmpty()) {
+ mPackets.PushFront(aOther.Pop());
+ }
+}
+
RefPtr<MediaRawData> OggCodecState::PacketOutAsMediaRawData()
{
ogg_packet* packet = PacketOut();
if (!packet) {
return nullptr;
}
NS_ASSERTION(!IsHeader(packet), "PacketOutAsMediaRawData can only be called on non-header packets");
--- a/dom/media/ogg/OggCodecState.h
+++ b/dom/media/ogg/OggCodecState.h
@@ -62,16 +62,17 @@ class OggPacketDeallocator : public nsDe
class OggPacketQueue : private nsDeque {
public:
OggPacketQueue() : nsDeque(new OggPacketDeallocator()) {}
~OggPacketQueue() { Erase(); }
bool IsEmpty() { return nsDeque::GetSize() == 0; }
void Append(ogg_packet* aPacket);
ogg_packet* PopFront() { return static_cast<ogg_packet*>(nsDeque::PopFront()); }
ogg_packet* PeekFront() { return static_cast<ogg_packet*>(nsDeque::PeekFront()); }
+ ogg_packet* Pop() { return static_cast<ogg_packet*>(nsDeque::Pop()); }
void PushFront(ogg_packet* aPacket) { nsDeque::PushFront(aPacket); }
void Erase() { nsDeque::Erase(); }
};
// Encapsulates the data required for decoding an ogg bitstream and for
// converting granulepos to timestamps.
class OggCodecState {
public:
@@ -171,16 +172,19 @@ public:
// OggCodecState::ReleasePacket(). The packet will have a valid granulepos.
ogg_packet* PacketOut();
// Returns the next raw packet in the stream, or nullptr if there are no more
// packets buffered in the packet queue, without consuming it.
// The packet will have a valid granulepos.
ogg_packet* PacketPeek();
+ // Moves all raw packets from aOther to the front of the current packet queue.
+ void PushFront(OggPacketQueue&& aOther);
+
// Releases the memory used by a cloned packet. Every packet returned by
// PacketOut() must be free'd using this function.
static void ReleasePacket(ogg_packet* aPacket);
// Returns the next packet in the stream as a MediaRawData, or nullptr
// if there are no more packets buffered in the packet queue. More packets
// can be buffered by inserting one or more pages into the stream by calling
// PageIn(). The packet will have a valid granulepos.
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -1164,39 +1164,56 @@ OggDemuxer::SeekInternal(TrackInfo::Trac
// search over the whole media, using the known buffered ranges to
// reduce the search space.
res = SeekInUnbuffered(aType, target, startTime, endTime, ranges);
NS_ENSURE_SUCCESS(res,res);
}
}
}
- if (aType == TrackInfo::kVideoTrack) {
- // Demux forwards until we find the next keyframe. This is required,
- // as although the seek should finish on a page containing a keyframe,
- // there may be non-keyframes in the page before the keyframe.
- // When doing fastSeek we display the first frame after the seek, so
- // we need to advance the decode to the keyframe otherwise we'll get
- // visual artifacts in the first frame output after the seek.
- while (true) {
- DemuxUntilPacketAvailable(aType, mTheoraState);
- ogg_packet* packet = mTheoraState->PacketPeek();
- if (packet == nullptr) {
- OGG_DEBUG("End of Theora stream reached before keyframe found in indexed seek");
- break;
- }
- if (mTheoraState->IsKeyframe(packet)) {
- OGG_DEBUG("Theora keyframe found after seek");
- break;
- }
+ // Demux forwards until we find the first keyframe prior the target.
+ // there may be non-keyframes in the page before the keyframe.
+ // Additionally, we may have seeked to the first page referenced by the
+ // page index which may be quite far off the target.
+ // When doing fastSeek we display the first frame after the seek, so
+ // we need to advance the decode to the keyframe otherwise we'll get
+ // visual artifacts in the first frame output after the seek.
+ OggCodecState* state = GetTrackCodecState(aType);
+ OggPacketQueue tempPackets;
+ bool foundKeyframe = false;
+ while (true) {
+ DemuxUntilPacketAvailable(aType, state);
+ ogg_packet* packet = state->PacketPeek();
+ if (packet == nullptr) {
+ OGG_DEBUG("End of stream reached before keyframe found in indexed seek");
+ break;
+ }
+ int64_t startTstamp = state->PacketStartTime(packet);
+ if (foundKeyframe && startTstamp > adjustedTarget) {
+ break;
+ }
+ if (state->IsKeyframe(packet)) {
+ OGG_DEBUG("keyframe found after seeking at %lld", startTstamp);
+ tempPackets.Erase();
+ foundKeyframe = true;
+ }
+ if (foundKeyframe && startTstamp == adjustedTarget) {
+ break;
+ }
+ ogg_packet* releaseMe = state->PacketOut();
+ if (foundKeyframe) {
+ tempPackets.Append(releaseMe);
+ } else {
// Discard video packets before the first keyframe.
- ogg_packet* releaseMe = mTheoraState->PacketOut();
OggCodecState::ReleasePacket(releaseMe);
}
}
+ // Re-add all packet into the codec state in order.
+ state->PushFront(Move(tempPackets));
+
return NS_OK;
}
OggDemuxer::IndexedSeekResult
OggDemuxer::RollbackIndexedSeek(TrackInfo::TrackType aType, int64_t aOffset)
{
if (mSkeletonState) {
mSkeletonState->Deactivate();