Bug 1257107: discard decoded data if its pts is smaller than seek time. r=jya
MozReview-Commit-ID: 70rbBsor8lp
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -268,17 +268,18 @@ typedef AlignedBuffer<AudioDataValue> Al
class MediaData {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaData)
enum Type {
AUDIO_DATA = 0,
VIDEO_DATA,
- RAW_DATA
+ RAW_DATA,
+ NULL_DATA
};
MediaData(Type aType,
int64_t aOffset,
int64_t aTimestamp,
int64_t aDuration,
uint32_t aFrames)
: mType(aType)
@@ -349,16 +350,27 @@ protected:
, mKeyframe(false)
, mDiscontinuity(false)
{}
virtual ~MediaData() {}
};
+// NullData is for decoder generating a sample which doesn't need to be
+// rendered.
+class NullData : public MediaData {
+public:
+ NullData(int64_t aOffset, int64_t aTime, int64_t aDuration)
+ : MediaData(NULL_DATA, aOffset, aTime, aDuration, 0)
+ {}
+
+ static const Type sType = NULL_DATA;
+};
+
// Holds chunk a decoded audio frames.
class AudioData : public MediaData {
public:
AudioData(int64_t aOffset,
int64_t aTime,
int64_t aDuration,
uint32_t aFrames,
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -457,16 +457,17 @@ MediaFormatReader::EnsureDecoderInitiali
decoder.mInitPromise.Begin(decoder.mDecoder->Init()
->Then(OwnerThread(), __func__,
[self] (TrackType aTrack) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mInitPromise.Complete();
decoder.mDecoderInitialized = true;
MonitorAutoLock mon(decoder.mMonitor);
decoder.mDescription = decoder.mDecoder->GetDescriptionName();
+ self->SetVideoDecodeThreshold();
self->ScheduleUpdate(aTrack);
},
[self, aTrack] (MediaDataDecoder::DecoderFailureReason aResult) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mInitPromise.Complete();
decoder.ShutdownDecoder();
self->NotifyError(aTrack);
}));
@@ -1033,16 +1034,17 @@ MediaFormatReader::InternalSeek(TrackTyp
decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().Time())
->Then(OwnerThread(), __func__,
[self, aTrack] (media::TimeUnit aTime) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mSeekRequest.Complete();
MOZ_ASSERT(decoder.mTimeThreshold,
"Seek promise must be disconnected when timethreshold is reset");
decoder.mTimeThreshold.ref().mHasSeeked = true;
+ self->SetVideoDecodeThreshold();
self->NotifyDecodingRequested(aTrack);
},
[self, aTrack] (DemuxerFailureReason aResult) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mSeekRequest.Complete();
switch (aResult) {
case DemuxerFailureReason::WAITING_FOR_DATA:
self->NotifyWaitingForData(aTrack);
@@ -1145,16 +1147,22 @@ MediaFormatReader::Update(TrackType aTra
media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
target.Time().ToSeconds(),
output->mKeyframe);
decoder.mOutput.RemoveElementAt(0);
decoder.mSizeOfQueue -= 1;
}
}
+ while (decoder.mOutput.Length() && decoder.mOutput[0]->mType == MediaData::NULL_DATA) {
+ LOGV("Dropping null data. Time: %lld", decoder.mOutput[0]->mTime);
+ decoder.mOutput.RemoveElementAt(0);
+ decoder.mSizeOfQueue -= 1;
+ }
+
if (decoder.HasPromise()) {
needOutput = true;
if (decoder.mOutput.Length()) {
RefPtr<MediaData> output = decoder.mOutput[0];
decoder.mOutput.RemoveElementAt(0);
decoder.mSizeOfQueue -= 1;
decoder.mLastSampleTime =
Some(TimeInterval(TimeUnit::FromMicroseconds(output->mTime),
@@ -1264,16 +1272,17 @@ MediaFormatReader::Update(TrackType aTra
HandleDemuxedSamples(aTrack, a);
}
void
MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
{
auto& decoder = GetDecoderData(aTrack);
MOZ_ASSERT(decoder.HasPromise());
+ MOZ_DIAGNOSTIC_ASSERT(aData->mType != MediaData::NULL_DATA);
if (decoder.mDiscontinuity) {
LOGV("Setting discontinuity flag");
decoder.mDiscontinuity = false;
aData->mDiscontinuity = true;
}
LOG("Resolved data promise for %s [%lld, %lld]", TrackTypeToStr(aTrack),
aData->mTime, aData->GetEndTime());
@@ -1716,31 +1725,71 @@ MediaFormatReader::DoVideoSeek()
void
MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("Video seeked to %lld", aTime.ToMicroseconds());
mVideo.mSeekRequest.Complete();
+ SetVideoDecodeThreshold();
+
if (HasAudio() && !mOriginalSeekTarget.IsVideoOnly()) {
MOZ_ASSERT(mPendingSeekTime.isSome());
if (mOriginalSeekTarget.IsFast()) {
// We are performing a fast seek. We need to seek audio to where the
// video seeked to, to ensure proper A/V sync once playback resume.
mPendingSeekTime = Some(aTime);
}
DoAudioSeek();
} else {
mPendingSeekTime.reset();
mSeekPromise.Resolve(aTime, __func__);
}
}
void
+MediaFormatReader::SetVideoDecodeThreshold()
+{
+ MOZ_ASSERT(OnTaskQueue());
+
+ if (!HasVideo() || !mVideo.mDecoder) {
+ return;
+ }
+
+ if (!mVideo.mTimeThreshold && !IsSeeking()) {
+ return;
+ }
+
+ TimeUnit threshold;
+ if (mVideo.mTimeThreshold) {
+ // For internalSeek.
+ threshold = mVideo.mTimeThreshold.ref().Time();
+ } else if (IsSeeking()) {
+ // If IsSeeking() is true, then video seek must have completed already.
+ TimeUnit keyframe;
+ if (NS_FAILED(mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&keyframe))) {
+ return;
+ }
+
+ // If the key frame is invalid/infinite, it means the target position is
+ // closing to end of stream. We don't want to skip any frame at this point.
+ if (!keyframe.IsValid() || keyframe.IsInfinite()) {
+ return;
+ }
+ threshold = mOriginalSeekTarget.GetTime();
+ } else {
+ return;
+ }
+
+ LOG("Set seek threshold to %lld", threshold.ToMicroseconds());
+ mVideo.mDecoder->SetSeekThreshold(threshold);
+}
+
+void
MediaFormatReader::DoAudioSeek()
{
MOZ_ASSERT(mPendingSeekTime.isSome());
LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds());
media::TimeUnit seekTime = mPendingSeekTime.ref();
mAudio.mSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
->Then(OwnerThread(), __func__, this,
&MediaFormatReader::OnAudioSeekCompleted,
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -178,16 +178,18 @@ private:
void InputExhausted(TrackType aTrack);
void Error(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
void Reset(TrackType aTrack);
void DrainComplete(TrackType aTrack);
void DropDecodedSamples(TrackType aTrack);
bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
+ void SetVideoDecodeThreshold();
+
size_t SizeOfQueue(TrackType aTrack);
RefPtr<PDMFactory> mPlatform;
class DecoderCallback : public MediaDataDecoderCallback {
public:
DecoderCallback(MediaFormatReader* aReader, TrackType aType)
: mReader(aReader)
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -230,13 +230,21 @@ public:
{
return NS_OK;
}
// Return the name of the MediaDataDecoder, only used for decoding.
// Only return a static const string, as the information may be accessed
// in a non thread-safe fashion.
virtual const char* GetDescriptionName() const = 0;
+
+ // Set a hint of seek target time to decoder. Decoder will drop any decoded
+ // data which pts is smaller than this value. This threshold needs to be clear
+ // after reset decoder.
+ // Decoder may not honor this value. However, it'd be better that
+ // video decoder implements this API to improve seek performance.
+ // Note: it should be called before Input() or after Flush().
+ virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
};
} // namespace mozilla
#endif