1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "mozilla/MozPromise.h"
8 #include "MediaDecoderReaderWrapper.h"
9 
10 namespace mozilla {
11 
12 extern LazyLogModule gMediaDecoderLog;
13 
14 #undef LOG
15 #define LOG(...) \
16   MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
17 
18 // StartTimeRendezvous is a helper class that quarantines the first sample
19 // until it gets a sample from both channels, such that we can be guaranteed
20 // to know the start time by the time On{Audio,Video}Decoded is called on MDSM.
21 class StartTimeRendezvous {
22   typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
23   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StartTimeRendezvous);
24 
25 public:
StartTimeRendezvous(AbstractThread * aOwnerThread,bool aHasAudio,bool aHasVideo,bool aForceZeroStartTime)26   StartTimeRendezvous(AbstractThread* aOwnerThread,
27                       bool aHasAudio,
28                       bool aHasVideo,
29                       bool aForceZeroStartTime)
30     : mOwnerThread(aOwnerThread)
31   {
32     if (aForceZeroStartTime) {
33       mAudioStartTime.emplace(0);
34       mVideoStartTime.emplace(0);
35       return;
36     }
37     if (!aHasAudio) {
38       mAudioStartTime.emplace(INT64_MAX);
39     }
40     if (!aHasVideo) {
41       mVideoStartTime.emplace(INT64_MAX);
42     }
43   }
44 
Destroy()45   void Destroy()
46   {
47     mAudioStartTime = Some(mAudioStartTime.refOr(INT64_MAX));
48     mVideoStartTime = Some(mVideoStartTime.refOr(INT64_MAX));
49     mHaveStartTimePromise.RejectIfExists(false, __func__);
50   }
51 
AwaitStartTime()52   RefPtr<HaveStartTimePromise> AwaitStartTime()
53   {
54     if (HaveStartTime()) {
55       return HaveStartTimePromise::CreateAndResolve(true, __func__);
56     }
57     return mHaveStartTimePromise.Ensure(__func__);
58   }
59 
60   template<MediaData::Type SampleType>
61   RefPtr<MediaDataPromise>
ProcessFirstSample(MediaData * aData)62   ProcessFirstSample(MediaData* aData)
63   {
64     typedef typename MediaDataPromise::Private PromisePrivate;
65     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
66 
67     MaybeSetChannelStartTime<SampleType>(aData->mTime);
68 
69     RefPtr<PromisePrivate> p = new PromisePrivate(__func__);
70     RefPtr<MediaData> data = aData;
71     RefPtr<StartTimeRendezvous> self = this;
72     AwaitStartTime()->Then(
73       mOwnerThread, __func__,
74       [p, data, self] () {
75         MOZ_ASSERT(self->mOwnerThread->IsCurrentThreadIn());
76         p->Resolve(data, __func__);
77       },
78       [p] () {
79         p->Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
80       });
81 
82     return p.forget();
83   }
84 
85   template<MediaData::Type SampleType>
FirstSampleRejected(const MediaResult & aError)86   void FirstSampleRejected(const MediaResult& aError)
87   {
88     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
89     if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
90       LOG("StartTimeRendezvous=%p SampleType(%d) Has no samples.",
91            this, SampleType);
92       MaybeSetChannelStartTime<SampleType>(INT64_MAX);
93     } else if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
94       mHaveStartTimePromise.RejectIfExists(false, __func__);
95     }
96   }
97 
HaveStartTime() const98   bool HaveStartTime() const
99   {
100     return mAudioStartTime.isSome() && mVideoStartTime.isSome();
101   }
102 
StartTime() const103   int64_t StartTime() const
104   {
105     int64_t time = std::min(mAudioStartTime.ref(), mVideoStartTime.ref());
106     return time == INT64_MAX ? 0 : time;
107   }
108 
109 private:
~StartTimeRendezvous()110   ~StartTimeRendezvous() {}
111 
112   template<MediaData::Type SampleType>
MaybeSetChannelStartTime(int64_t aStartTime)113   void MaybeSetChannelStartTime(int64_t aStartTime)
114   {
115     if (ChannelStartTime(SampleType).isSome()) {
116       // If we're initialized with aForceZeroStartTime=true, the channel start
117       // times are already set.
118       return;
119     }
120 
121     LOG("StartTimeRendezvous=%p Setting SampleType(%d) start time to %lld",
122         this, SampleType, aStartTime);
123 
124     ChannelStartTime(SampleType).emplace(aStartTime);
125     if (HaveStartTime()) {
126       mHaveStartTimePromise.ResolveIfExists(true, __func__);
127     }
128   }
129 
ChannelStartTime(MediaData::Type aType)130   Maybe<int64_t>& ChannelStartTime(MediaData::Type aType)
131   {
132     return aType == MediaData::AUDIO_DATA ? mAudioStartTime : mVideoStartTime;
133   }
134 
135   MozPromiseHolder<HaveStartTimePromise> mHaveStartTimePromise;
136   RefPtr<AbstractThread> mOwnerThread;
137   Maybe<int64_t> mAudioStartTime;
138   Maybe<int64_t> mVideoStartTime;
139 };
140 
MediaDecoderReaderWrapper(AbstractThread * aOwnerThread,MediaDecoderReader * aReader)141 MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(AbstractThread* aOwnerThread,
142                                                      MediaDecoderReader* aReader)
143   : mForceZeroStartTime(aReader->ForceZeroStartTime())
144   , mOwnerThread(aOwnerThread)
145   , mReader(aReader)
146 {}
147 
~MediaDecoderReaderWrapper()148 MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper()
149 {}
150 
151 media::TimeUnit
StartTime() const152 MediaDecoderReaderWrapper::StartTime() const
153 {
154   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
155   MOZ_ASSERT(!mShutdown);
156   return media::TimeUnit::FromMicroseconds(mStartTimeRendezvous->StartTime());
157 }
158 
159 RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
ReadMetadata()160 MediaDecoderReaderWrapper::ReadMetadata()
161 {
162   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
163   MOZ_ASSERT(!mShutdown);
164   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
165                      &MediaDecoderReader::AsyncReadMetadata)
166          ->Then(mOwnerThread, __func__, this,
167                 &MediaDecoderReaderWrapper::OnMetadataRead,
168                 &MediaDecoderReaderWrapper::OnMetadataNotRead)
169          ->CompletionPromise();
170 }
171 
172 RefPtr<HaveStartTimePromise>
AwaitStartTime()173 MediaDecoderReaderWrapper::AwaitStartTime()
174 {
175   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
176   MOZ_ASSERT(!mShutdown);
177   return mStartTimeRendezvous->AwaitStartTime();
178 }
179 
180 void
RequestAudioData()181 MediaDecoderReaderWrapper::RequestAudioData()
182 {
183   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
184   MOZ_ASSERT(!mShutdown);
185 
186   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
187                        &MediaDecoderReader::RequestAudioData);
188 
189   if (!mStartTimeRendezvous->HaveStartTime()) {
190     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
191                 &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>,
192                 &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>)
193          ->CompletionPromise();
194   }
195 
196   RefPtr<MediaDecoderReaderWrapper> self = this;
197   mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__,
198     [self] (MediaData* aAudioSample) {
199       self->mAudioDataRequest.Complete();
200       aAudioSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
201       self->mAudioCallback.Notify(AsVariant(aAudioSample));
202     },
203     [self] (const MediaResult& aError) {
204       self->mAudioDataRequest.Complete();
205       self->mAudioCallback.Notify(AsVariant(aError));
206     }));
207 }
208 
209 void
RequestVideoData(bool aSkipToNextKeyframe,media::TimeUnit aTimeThreshold)210 MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe,
211                                             media::TimeUnit aTimeThreshold)
212 {
213   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
214   MOZ_ASSERT(!mShutdown);
215 
216   // Time the video decode and send this value back to callbacks who accept
217   // a TimeStamp as its second parameter.
218   TimeStamp videoDecodeStartTime = TimeStamp::Now();
219 
220   if (aTimeThreshold.ToMicroseconds() > 0 &&
221       mStartTimeRendezvous->HaveStartTime()) {
222     aTimeThreshold += StartTime();
223   }
224 
225   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
226                        &MediaDecoderReader::RequestVideoData,
227                        aSkipToNextKeyframe, aTimeThreshold.ToMicroseconds());
228 
229   if (!mStartTimeRendezvous->HaveStartTime()) {
230     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
231                 &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>,
232                 &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
233          ->CompletionPromise();
234   }
235 
236   RefPtr<MediaDecoderReaderWrapper> self = this;
237   mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__,
238     [self, videoDecodeStartTime] (MediaData* aVideoSample) {
239       self->mVideoDataRequest.Complete();
240       aVideoSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
241       self->mVideoCallback.Notify(AsVariant(MakeTuple(aVideoSample, videoDecodeStartTime)));
242     },
243     [self] (const MediaResult& aError) {
244       self->mVideoDataRequest.Complete();
245       self->mVideoCallback.Notify(AsVariant(aError));
246     }));
247 }
248 
249 bool
IsRequestingAudioData() const250 MediaDecoderReaderWrapper::IsRequestingAudioData() const
251 {
252   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
253   return mAudioDataRequest.Exists();
254 }
255 
256 bool
IsRequestingVideoData() const257 MediaDecoderReaderWrapper::IsRequestingVideoData() const
258 {
259   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
260   return mVideoDataRequest.Exists();
261 }
262 
263 bool
IsWaitingAudioData() const264 MediaDecoderReaderWrapper::IsWaitingAudioData() const
265 {
266   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
267   return mAudioWaitRequest.Exists();
268 }
269 
270 bool
IsWaitingVideoData() const271 MediaDecoderReaderWrapper::IsWaitingVideoData() const
272 {
273   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
274   return mVideoWaitRequest.Exists();
275 }
276 
277 RefPtr<MediaDecoderReader::SeekPromise>
Seek(SeekTarget aTarget,media::TimeUnit aEndTime)278 MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime)
279 {
280   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
281   aTarget.SetTime(aTarget.GetTime() + StartTime());
282   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
283                      &MediaDecoderReader::Seek, aTarget,
284                      aEndTime.ToMicroseconds());
285 }
286 
287 void
WaitForData(MediaData::Type aType)288 MediaDecoderReaderWrapper::WaitForData(MediaData::Type aType)
289 {
290   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
291 
292   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
293                        &MediaDecoderReader::WaitForData, aType);
294 
295   RefPtr<MediaDecoderReaderWrapper> self = this;
296   WaitRequestRef(aType).Begin(p->Then(mOwnerThread, __func__,
297     [self] (MediaData::Type aType) {
298       self->WaitRequestRef(aType).Complete();
299       self->WaitCallbackRef(aType).Notify(AsVariant(aType));
300     },
301     [self, aType] (WaitForDataRejectValue aRejection) {
302       self->WaitRequestRef(aType).Complete();
303       self->WaitCallbackRef(aType).Notify(AsVariant(aRejection));
304     }));
305 }
306 
307 MediaCallbackExc<WaitCallbackData>&
WaitCallbackRef(MediaData::Type aType)308 MediaDecoderReaderWrapper::WaitCallbackRef(MediaData::Type aType)
309 {
310   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
311   return aType == MediaData::AUDIO_DATA ? mAudioWaitCallback : mVideoWaitCallback;
312 }
313 
314 MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>&
WaitRequestRef(MediaData::Type aType)315 MediaDecoderReaderWrapper::WaitRequestRef(MediaData::Type aType)
316 {
317   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
318   return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest;
319 }
320 
321 RefPtr<MediaDecoderReaderWrapper::BufferedUpdatePromise>
UpdateBufferedWithPromise()322 MediaDecoderReaderWrapper::UpdateBufferedWithPromise()
323 {
324   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
325   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
326                      &MediaDecoderReader::UpdateBufferedWithPromise);
327 }
328 
329 void
ReleaseResources()330 MediaDecoderReaderWrapper::ReleaseResources()
331 {
332   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
333   nsCOMPtr<nsIRunnable> r =
334     NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseResources);
335   mReader->OwnerThread()->Dispatch(r.forget());
336 }
337 
338 void
SetIdle()339 MediaDecoderReaderWrapper::SetIdle()
340 {
341   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
342   nsCOMPtr<nsIRunnable> r =
343     NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
344   mReader->OwnerThread()->Dispatch(r.forget());
345 }
346 
347 void
ResetDecode(TrackSet aTracks)348 MediaDecoderReaderWrapper::ResetDecode(TrackSet aTracks)
349 {
350   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
351 
352   if (aTracks.contains(TrackInfo::kAudioTrack)) {
353     mAudioDataRequest.DisconnectIfExists();
354     mAudioWaitRequest.DisconnectIfExists();
355   }
356 
357   if (aTracks.contains(TrackInfo::kVideoTrack)) {
358     mVideoDataRequest.DisconnectIfExists();
359     mVideoWaitRequest.DisconnectIfExists();
360   }
361 
362   nsCOMPtr<nsIRunnable> r =
363     NewRunnableMethod<TrackSet>(mReader,
364                                 &MediaDecoderReader::ResetDecode,
365                                 aTracks);
366   mReader->OwnerThread()->Dispatch(r.forget());
367 }
368 
369 RefPtr<ShutdownPromise>
Shutdown()370 MediaDecoderReaderWrapper::Shutdown()
371 {
372   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
373   MOZ_ASSERT(!mAudioDataRequest.Exists());
374   MOZ_ASSERT(!mVideoDataRequest.Exists());
375 
376   mShutdown = true;
377   if (mStartTimeRendezvous) {
378     mStartTimeRendezvous->Destroy();
379     mStartTimeRendezvous = nullptr;
380   }
381   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
382                      &MediaDecoderReader::Shutdown);
383 }
384 
385 void
OnMetadataRead(MetadataHolder * aMetadata)386 MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder* aMetadata)
387 {
388   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
389   if (mShutdown) {
390     return;
391   }
392   // Set up the start time rendezvous if it doesn't already exist (which is
393   // generally the case, unless we're coming out of dormant mode).
394   if (!mStartTimeRendezvous) {
395     mStartTimeRendezvous = new StartTimeRendezvous(
396       mOwnerThread, aMetadata->mInfo.HasAudio(),
397       aMetadata->mInfo.HasVideo(), mForceZeroStartTime);
398 
399     RefPtr<MediaDecoderReaderWrapper> self = this;
400     mStartTimeRendezvous->AwaitStartTime()->Then(
401       mOwnerThread, __func__,
402       [self] ()  {
403         NS_ENSURE_TRUE_VOID(!self->mShutdown);
404         self->mReader->DispatchSetStartTime(self->StartTime().ToMicroseconds());
405       },
406       [] () {
407         NS_WARNING("Setting start time on reader failed");
408       });
409   }
410 }
411 
412 void
SetVideoBlankDecode(bool aIsBlankDecode)413 MediaDecoderReaderWrapper::SetVideoBlankDecode(bool aIsBlankDecode)
414 {
415   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
416   nsCOMPtr<nsIRunnable> r =
417     NewRunnableMethod<bool>(mReader, &MediaDecoderReader::SetVideoBlankDecode,
418                             aIsBlankDecode);
419   mReader->OwnerThread()->Dispatch(r.forget());
420 }
421 
422 } // namespace mozilla
423