1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AccurateSeekTask.h"
8 #include "MediaDecoderReaderWrapper.h"
9 #include "mozilla/AbstractThread.h"
10 #include "mozilla/Assertions.h"
11 #include "nsPrintfCString.h"
12 
13 namespace mozilla {
14 
15 extern LazyLogModule gMediaDecoderLog;
16 extern LazyLogModule gMediaSampleLog;
17 
18 // avoid redefined macro in unified build
19 #undef FMT
20 #undef DECODER_LOG
21 #undef SAMPLE_LOG
22 #undef DECODER_WARN
23 
24 #define FMT(x, ...) "[AccurateSeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__
25 #define DECODER_LOG(...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,   (FMT(__VA_ARGS__)))
26 #define SAMPLE_LOG(...)  MOZ_LOG(gMediaSampleLog,  LogLevel::Debug,   (FMT(__VA_ARGS__)))
27 #define DECODER_WARN(...) NS_WARNING(nsPrintfCString(FMT(__VA_ARGS__)).get())
28 
AccurateSeekTask(const void * aDecoderID,AbstractThread * aThread,MediaDecoderReaderWrapper * aReader,const SeekTarget & aTarget,const MediaInfo & aInfo,const media::TimeUnit & aEnd,int64_t aCurrentMediaTime)29 AccurateSeekTask::AccurateSeekTask(const void* aDecoderID,
30                                    AbstractThread* aThread,
31                                    MediaDecoderReaderWrapper* aReader,
32                                    const SeekTarget& aTarget,
33                                    const MediaInfo& aInfo,
34                                    const media::TimeUnit& aEnd,
35                                    int64_t aCurrentMediaTime)
36   : SeekTask(aDecoderID, aThread, aReader, aTarget)
37   , mCurrentTimeBeforeSeek(media::TimeUnit::FromMicroseconds(aCurrentMediaTime))
38   , mAudioRate(aInfo.mAudio.mRate)
39   , mDoneAudioSeeking(!aInfo.HasAudio() || aTarget.IsVideoOnly())
40   , mDoneVideoSeeking(!aInfo.HasVideo())
41 {
42   AssertOwnerThread();
43 
44   // Bound the seek time to be inside the media range.
45   NS_ASSERTION(aEnd.ToMicroseconds() != -1, "Should know end time by now");
46   mTarget.SetTime(std::max(media::TimeUnit(), std::min(mTarget.GetTime(), aEnd)));
47 
48   // Configure MediaDecoderReaderWrapper.
49   SetCallbacks();
50 }
51 
~AccurateSeekTask()52 AccurateSeekTask::~AccurateSeekTask()
53 {
54   AssertOwnerThread();
55   MOZ_ASSERT(mIsDiscarded);
56 }
57 
58 void
Discard()59 AccurateSeekTask::Discard()
60 {
61   AssertOwnerThread();
62 
63   // Disconnect MDSM.
64   RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
65 
66   // Disconnect MediaDecoderReaderWrapper.
67   mSeekRequest.DisconnectIfExists();
68   CancelCallbacks();
69 
70   mIsDiscarded = true;
71 }
72 
73 bool
NeedToResetMDSM() const74 AccurateSeekTask::NeedToResetMDSM() const
75 {
76   AssertOwnerThread();
77   return true;
78 }
79 
80 RefPtr<AccurateSeekTask::SeekTaskPromise>
Seek(const media::TimeUnit & aDuration)81 AccurateSeekTask::Seek(const media::TimeUnit& aDuration)
82 {
83   AssertOwnerThread();
84 
85   // Do the seek.
86   mSeekRequest.Begin(mReader->Seek(mTarget, aDuration)
87     ->Then(OwnerThread(), __func__, this,
88            &AccurateSeekTask::OnSeekResolved, &AccurateSeekTask::OnSeekRejected));
89 
90   return mSeekTaskPromise.Ensure(__func__);
91 }
92 
93 void
RequestAudioData()94 AccurateSeekTask::RequestAudioData()
95 {
96   AssertOwnerThread();
97   MOZ_ASSERT(!mDoneAudioSeeking);
98   MOZ_ASSERT(!mReader->IsRequestingAudioData());
99   MOZ_ASSERT(!mReader->IsWaitingAudioData());
100   mReader->RequestAudioData();
101 }
102 
103 void
RequestVideoData()104 AccurateSeekTask::RequestVideoData()
105 {
106   AssertOwnerThread();
107   MOZ_ASSERT(!mDoneVideoSeeking);
108   MOZ_ASSERT(!mReader->IsRequestingVideoData());
109   MOZ_ASSERT(!mReader->IsWaitingVideoData());
110   mReader->RequestVideoData(false, media::TimeUnit());
111 }
112 
113 nsresult
DropAudioUpToSeekTarget(MediaData * aSample)114 AccurateSeekTask::DropAudioUpToSeekTarget(MediaData* aSample)
115 {
116   AssertOwnerThread();
117 
118   RefPtr<AudioData> audio(aSample->As<AudioData>());
119   MOZ_ASSERT(audio && mTarget.IsAccurate());
120 
121   CheckedInt64 sampleDuration = FramesToUsecs(audio->mFrames, mAudioRate);
122   if (!sampleDuration.isValid()) {
123     return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
124   }
125 
126   if (audio->mTime + sampleDuration.value() <= mTarget.GetTime().ToMicroseconds()) {
127     // Our seek target lies after the frames in this AudioData. Don't
128     // push it onto the audio queue, and keep decoding forwards.
129     return NS_OK;
130   }
131 
132   if (audio->mTime > mTarget.GetTime().ToMicroseconds()) {
133     // The seek target doesn't lie in the audio block just after the last
134     // audio frames we've seen which were before the seek target. This
135     // could have been the first audio data we've seen after seek, i.e. the
136     // seek terminated after the seek target in the audio stream. Just
137     // abort the audio decode-to-target, the state machine will play
138     // silence to cover the gap. Typically this happens in poorly muxed
139     // files.
140     DECODER_WARN("Audio not synced after seek, maybe a poorly muxed file?");
141     mSeekedAudioData = audio;
142     mDoneAudioSeeking = true;
143     return NS_OK;
144   }
145 
146   // The seek target lies somewhere in this AudioData's frames, strip off
147   // any frames which lie before the seek target, so we'll begin playback
148   // exactly at the seek target.
149   NS_ASSERTION(mTarget.GetTime().ToMicroseconds() >= audio->mTime,
150                "Target must at or be after data start.");
151   NS_ASSERTION(mTarget.GetTime().ToMicroseconds() < audio->mTime + sampleDuration.value(),
152                "Data must end after target.");
153 
154   CheckedInt64 framesToPrune =
155     UsecsToFrames(mTarget.GetTime().ToMicroseconds() - audio->mTime, mAudioRate);
156   if (!framesToPrune.isValid()) {
157     return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
158   }
159   if (framesToPrune.value() > audio->mFrames) {
160     // We've messed up somehow. Don't try to trim frames, the |frames|
161     // variable below will overflow.
162     DECODER_WARN("Can't prune more frames that we have!");
163     return NS_ERROR_FAILURE;
164   }
165   uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune.value());
166   uint32_t channels = audio->mChannels;
167   AlignedAudioBuffer audioData(frames * channels);
168   if (!audioData) {
169     return NS_ERROR_OUT_OF_MEMORY;
170   }
171 
172   memcpy(audioData.get(),
173          audio->mAudioData.get() + (framesToPrune.value() * channels),
174          frames * channels * sizeof(AudioDataValue));
175   CheckedInt64 duration = FramesToUsecs(frames, mAudioRate);
176   if (!duration.isValid()) {
177     return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
178   }
179   RefPtr<AudioData> data(new AudioData(audio->mOffset,
180                                        mTarget.GetTime().ToMicroseconds(),
181                                        duration.value(),
182                                        frames,
183                                        Move(audioData),
184                                        channels,
185                                        audio->mRate));
186   MOZ_ASSERT(!mSeekedAudioData, "Should be the 1st sample after seeking");
187   mSeekedAudioData = data;
188   mDoneAudioSeeking = true;
189 
190   return NS_OK;
191 }
192 
193 nsresult
DropVideoUpToSeekTarget(MediaData * aSample)194 AccurateSeekTask::DropVideoUpToSeekTarget(MediaData* aSample)
195 {
196   AssertOwnerThread();
197 
198   RefPtr<VideoData> video(aSample->As<VideoData>());
199   MOZ_ASSERT(video);
200   DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld]",
201               video->mTime, video->GetEndTime());
202   const int64_t target = mTarget.GetTime().ToMicroseconds();
203 
204   // If the frame end time is less than the seek target, we won't want
205   // to display this frame after the seek, so discard it.
206   if (target >= video->GetEndTime()) {
207     DECODER_LOG("DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
208                 video->mTime, video->GetEndTime(), target);
209     mFirstVideoFrameAfterSeek = video;
210   } else {
211     if (target >= video->mTime && video->GetEndTime() >= target) {
212       // The seek target lies inside this frame's time slice. Adjust the frame's
213       // start time to match the seek target. We do this by replacing the
214       // first frame with a shallow copy which has the new timestamp.
215       RefPtr<VideoData> temp = VideoData::ShallowCopyUpdateTimestamp(video.get(), target);
216       video = temp;
217     }
218     mFirstVideoFrameAfterSeek = nullptr;
219 
220     DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
221                 video->mTime, video->GetEndTime(), target);
222 
223     MOZ_ASSERT(!mSeekedVideoData, "Should be the 1st sample after seeking");
224     mSeekedVideoData = video;
225     mDoneVideoSeeking = true;
226   }
227 
228   return NS_OK;
229 }
230 
231 void
MaybeFinishSeek()232 AccurateSeekTask::MaybeFinishSeek()
233 {
234   AssertOwnerThread();
235   if (mDoneAudioSeeking && mDoneVideoSeeking) {
236     Resolve(__func__); // Call to MDSM::SeekCompleted();
237   }
238 }
239 
240 void
OnSeekResolved(media::TimeUnit)241 AccurateSeekTask::OnSeekResolved(media::TimeUnit)
242 {
243   AssertOwnerThread();
244 
245   mSeekRequest.Complete();
246   // We must decode the first samples of active streams, so we can determine
247   // the new stream time. So dispatch tasks to do that.
248   if (!mDoneVideoSeeking) {
249     RequestVideoData();
250   }
251   if (!mDoneAudioSeeking) {
252     RequestAudioData();
253   }
254 }
255 
256 void
OnSeekRejected(nsresult aResult)257 AccurateSeekTask::OnSeekRejected(nsresult aResult)
258 {
259   AssertOwnerThread();
260 
261   mSeekRequest.Complete();
262   MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
263   RejectIfExist(aResult, __func__);
264 }
265 
266 void
AdjustFastSeekIfNeeded(MediaData * aSample)267 AccurateSeekTask::AdjustFastSeekIfNeeded(MediaData* aSample)
268 {
269   AssertOwnerThread();
270   if (mTarget.IsFast() &&
271       mTarget.GetTime() > mCurrentTimeBeforeSeek &&
272       aSample->mTime < mCurrentTimeBeforeSeek.ToMicroseconds()) {
273     // We are doing a fastSeek, but we ended up *before* the previous
274     // playback position. This is surprising UX, so switch to an accurate
275     // seek and decode to the seek target. This is not conformant to the
276     // spec, fastSeek should always be fast, but until we get the time to
277     // change all Readers to seek to the keyframe after the currentTime
278     // in this case, we'll just decode forward. Bug 1026330.
279     mTarget.SetType(SeekTarget::Accurate);
280   }
281 }
282 
283 void
OnAudioDecoded(MediaData * aAudioSample)284 AccurateSeekTask::OnAudioDecoded(MediaData* aAudioSample)
285 {
286   AssertOwnerThread();
287   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
288 
289   RefPtr<MediaData> audio(aAudioSample);
290   MOZ_ASSERT(audio);
291 
292   // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
293   // resolved.
294 
295   SAMPLE_LOG("OnAudioDecoded [%lld,%lld]", audio->mTime, audio->GetEndTime());
296 
297   // Video-only seek doesn't reset audio decoder. There might be pending audio
298   // requests when AccurateSeekTask::Seek() begins. We will just store the data
299   // without checking |mDiscontinuity| or calling DropAudioUpToSeekTarget().
300   if (mTarget.IsVideoOnly()) {
301     mSeekedAudioData = audio.forget();
302     return;
303   }
304 
305   AdjustFastSeekIfNeeded(audio);
306 
307   if (mTarget.IsFast()) {
308     // Non-precise seek; we can stop the seek at the first sample.
309     mSeekedAudioData = audio;
310     mDoneAudioSeeking = true;
311   } else {
312     nsresult rv = DropAudioUpToSeekTarget(audio);
313     if (NS_FAILED(rv)) {
314       CancelCallbacks();
315       RejectIfExist(rv, __func__);
316       return;
317     }
318   }
319 
320   if (!mDoneAudioSeeking) {
321     RequestAudioData();
322     return;
323   }
324   MaybeFinishSeek();
325 }
326 
327 void
OnNotDecoded(MediaData::Type aType,const MediaResult & aError)328 AccurateSeekTask::OnNotDecoded(MediaData::Type aType,
329                                const MediaResult& aError)
330 {
331   AssertOwnerThread();
332   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
333 
334   SAMPLE_LOG("OnNotDecoded type=%d reason=%u", aType, aError.Code());
335 
336   // Ignore pending requests from video-only seek.
337   if (aType == MediaData::AUDIO_DATA && mTarget.IsVideoOnly()) {
338     return;
339   }
340 
341   // If the decoder is waiting for data, we tell it to call us back when the
342   // data arrives.
343   if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
344     mReader->WaitForData(aType);
345     return;
346   }
347 
348   if (aError == NS_ERROR_DOM_MEDIA_CANCELED) {
349     if (aType == MediaData::AUDIO_DATA) {
350       RequestAudioData();
351     } else {
352       RequestVideoData();
353     }
354     return;
355   }
356 
357   if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
358     if (aType == MediaData::AUDIO_DATA) {
359       mIsAudioQueueFinished = true;
360       mDoneAudioSeeking = true;
361     } else {
362       mIsVideoQueueFinished = true;
363       mDoneVideoSeeking = true;
364       if (mFirstVideoFrameAfterSeek) {
365         // Hit the end of stream. Move mFirstVideoFrameAfterSeek into
366         // mSeekedVideoData so we have something to display after seeking.
367         mSeekedVideoData = mFirstVideoFrameAfterSeek.forget();
368       }
369     }
370     MaybeFinishSeek();
371     return;
372   }
373 
374   // This is a decode error, delegate to the generic error path.
375   CancelCallbacks();
376   RejectIfExist(aError, __func__);
377 }
378 
379 void
OnVideoDecoded(MediaData * aVideoSample)380 AccurateSeekTask::OnVideoDecoded(MediaData* aVideoSample)
381 {
382   AssertOwnerThread();
383   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
384 
385   RefPtr<MediaData> video(aVideoSample);
386   MOZ_ASSERT(video);
387 
388   // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
389   // resolved.
390 
391   SAMPLE_LOG("OnVideoDecoded [%lld,%lld]", video->mTime, video->GetEndTime());
392 
393   AdjustFastSeekIfNeeded(video);
394 
395   if (mTarget.IsFast()) {
396     // Non-precise seek. We can stop the seek at the first sample.
397     mSeekedVideoData = video;
398     mDoneVideoSeeking = true;
399   } else {
400     nsresult rv = DropVideoUpToSeekTarget(video.get());
401     if (NS_FAILED(rv)) {
402       CancelCallbacks();
403       RejectIfExist(rv, __func__);
404       return;
405     }
406   }
407 
408   if (!mDoneVideoSeeking) {
409     RequestVideoData();
410     return;
411   }
412   MaybeFinishSeek();
413 }
414 
415 void
SetCallbacks()416 AccurateSeekTask::SetCallbacks()
417 {
418   AssertOwnerThread();
419 
420   RefPtr<AccurateSeekTask> self = this;
421   mAudioCallback = mReader->AudioCallback().Connect(
422     OwnerThread(), [self] (AudioCallbackData aData) {
423     if (aData.is<MediaData*>()) {
424       self->OnAudioDecoded(aData.as<MediaData*>());
425     } else {
426       self->OnNotDecoded(MediaData::AUDIO_DATA,
427         aData.as<MediaResult>());
428     }
429   });
430 
431   mVideoCallback = mReader->VideoCallback().Connect(
432     OwnerThread(), [self] (VideoCallbackData aData) {
433     typedef Tuple<MediaData*, TimeStamp> Type;
434     if (aData.is<Type>()) {
435       self->OnVideoDecoded(Get<0>(aData.as<Type>()));
436     } else {
437       self->OnNotDecoded(MediaData::VIDEO_DATA,
438         aData.as<MediaResult>());
439     }
440   });
441 
442   mAudioWaitCallback = mReader->AudioWaitCallback().Connect(
443     OwnerThread(), [self] (WaitCallbackData aData) {
444     // Ignore pending requests from video-only seek.
445     if (self->mTarget.IsVideoOnly()) {
446       return;
447     }
448     if (aData.is<MediaData::Type>()) {
449       self->RequestAudioData();
450     }
451   });
452 
453   mVideoWaitCallback = mReader->VideoWaitCallback().Connect(
454     OwnerThread(), [self] (WaitCallbackData aData) {
455     if (aData.is<MediaData::Type>()) {
456       self->RequestVideoData();
457     }
458   });
459 }
460 
461 void
CancelCallbacks()462 AccurateSeekTask::CancelCallbacks()
463 {
464   AssertOwnerThread();
465   mAudioCallback.DisconnectIfExists();
466   mVideoCallback.DisconnectIfExists();
467   mAudioWaitCallback.DisconnectIfExists();
468   mVideoWaitCallback.DisconnectIfExists();
469 }
470 } // namespace mozilla
471