/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ImageContainer.h" #include "MediaFormatReader.h" #include "MockDecoderModule.h" #include "MockMediaDataDemuxer.h" #include "MockMediaDecoderOwner.h" #include "PDMFactory.h" #include "ReaderProxy.h" #include "TimeUnits.h" #include "VideoFrameContainer.h" #include "gtest/gtest.h" #include "mozilla/Preferences.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/gtest/WaitFor.h" #include "nsQueryObject.h" using namespace mozilla; using namespace mozilla::layers; using DecodePromise = MediaDataDecoder::DecodePromise; using SamplesHolder = MediaTrackDemuxer::SamplesHolder; using SamplesPromise = MediaTrackDemuxer::SamplesPromise; using SeekPromise = MediaTrackDemuxer::SeekPromise; using TrackType = TrackInfo::TrackType; using media::TimeIntervals; using media::TimeUnit; using testing::InSequence; using testing::MockFunction; using testing::Return; using testing::StrEq; TEST(TestMediaFormatReader, WaitingForDemuxAfterInternalSeek) { RefPtr reader; // Thread scheduling provides ordering for thread initializations before // their first read. RefPtr demuxerThread; RefPtr decoderThread; // Wait enough for the MediaFormatReader to process at least // aCount demuxer or decoder operations, if pending. auto WaitForReaderOperations = [&](int aCount) { // AwaitIdle() ensures that no tasks are pending and any task for another // thread is already in the other thread's queue, only if dispatch across // threads is not via tail dispatch. Tail dispatch is not used because // the demuxer and decoder threads do not support tail dispatch, even // though the MediaFormatReader task queue supports tail dispatch. // https://searchfox.org/mozilla-central/rev/126697140e711e04a9d95edae537541c3bde89cc/xpcom/threads/AbstractThread.cpp#285-289 MOZ_ASSERT(!demuxerThread->SupportsTailDispatch()); MOZ_ASSERT(!decoderThread->SupportsTailDispatch()); // Check that the reader thread has dispatched the first request to // the demuxer or decoder thread. reader->OwnerThread()->AwaitIdle(); for (int i = 0; i < aCount; ++i) { demuxerThread->AwaitIdle(); decoderThread->AwaitIdle(); reader->OwnerThread()->AwaitIdle(); } }; RefPtr dataDemuxer = new MockMediaDataDemuxer(); RefPtr trackDemuxer = // VideoInfo::IsValid() needs dimensions. new MockMediaTrackDemuxer("video/x-test; width=640; height=360"); ON_CALL(*dataDemuxer, GetNumberTracks(TrackType::kVideoTrack)) .WillByDefault(Return(1)); ON_CALL(*dataDemuxer, GetTrackDemuxer) .WillByDefault([&](TrackType aType, uint32_t aTrackNumber) { EXPECT_EQ(aTrackNumber, 0u); EXPECT_EQ(aType, TrackType::kVideoTrack); if (!demuxerThread) { demuxerThread = do_QueryObject(AbstractThread::GetCurrent()); } return do_AddRef(trackDemuxer); }); RefPtr pdm = new MockDecoderModule(); PDMFactory::AutoForcePDM autoForcePDM(pdm); RefPtr decoder; MozPromiseHolder drainPromise; EXPECT_CALL(*pdm, CreateVideoDecoder) .WillOnce([&](const CreateDecoderParams& aParams) { decoder = new MockVideoDataDecoder(aParams); InSequence s; // The first drain requires two calls: one to fetch the frames... EXPECT_CALL(*decoder, Drain).WillOnce([&] { MOZ_ASSERT(!decoderThread); decoderThread = do_QueryObject(AbstractThread::GetCurrent()); return decoder->DummyMediaDataDecoder::Drain(); }); // ... and a second to confirm that no more frames are remaining. EXPECT_CALL(*decoder, Drain).Times(1); // Delay responding to the second drain request until testing is done. EXPECT_CALL(*decoder, Drain).WillOnce([&] { return drainPromise.Ensure(__func__); }); decoder->SetLatencyFrameCount(8); return do_AddRef(decoder); }); MockFunction checkpoint; { InSequence s; EXPECT_CALL(*trackDemuxer, MockGetSamples).Times(2).WillRepeatedly([]() { static int count = 0; RefPtr sample = new MediaRawData; sample->mTime = TimeUnit(count, 30); ++count; RefPtr samples = new SamplesHolder; samples->AppendSample(std::move(sample)); return SamplesPromise::CreateAndResolve(samples, __func__); }); EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { return SamplesPromise::CreateAndReject( NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); }); EXPECT_CALL(*trackDemuxer, Seek).WillOnce([&](const TimeUnit& aTime) { // Reset mWaitingForDataStartTime so that OnDemuxFailed() calls // RequestDrain(). EXPECT_NS_SUCCEEDED(reader->OwnerThread()->Dispatch( NewRunnableMethod("NotifyDataArrived", reader.get(), &MediaFormatReader::NotifyDataArrived))); return SeekPromise::CreateAndResolve(TimeUnit::Zero(), __func__); }); EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { RefPtr sample = new MediaRawData; // Time is zero after the seek. sample->mTime = TimeUnit(0, 30); RefPtr samples = new SamplesHolder; samples->AppendSample(std::move(sample)); return SamplesPromise::CreateAndResolve(samples, __func__); }); EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { return SamplesPromise::CreateAndReject( NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); }); EXPECT_CALL(checkpoint, Call(StrEq("Internal seek waiting for data"))); EXPECT_CALL(*trackDemuxer, MockGetSamples).WillRepeatedly([]() { return SamplesPromise::CreateAndReject( NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); }); } auto owner = std::make_unique(); RefPtr container = new VideoFrameContainer( owner.get(), MakeAndAddRef(ImageUsageType::VideoFrameContainer, #ifdef MOZ_WIDGET_ANDROID // Work around bug 1922144 ImageContainer::SYNCHRONOUS #else ImageContainer::ASYNCHRONOUS #endif )); MediaFormatReaderInit init; init.mVideoFrameContainer = container; reader = new MediaFormatReader(init, dataDemuxer); RefPtr proxy = new ReaderProxy(AbstractThread::MainThread(), reader); EXPECT_NS_SUCCEEDED(reader->Init()); // ReadMetadata() to init demuxer. (void)WaitForResolve(proxy->ReadMetadata()); // Two samples are provided by the demuxer, but the third demux request is // rejected. The first drain provides two decoded samples. for (int i = 0; i < 2; ++i) { (void)WaitForResolve(proxy->RequestVideoData(TimeUnit(), false)); } // A third sample is not available. MediaResult result = WaitForReject(proxy->RequestVideoData(TimeUnit(), false)); EXPECT_EQ(result.Code(), NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA); // The first drain is complete. Wait for the internal seek to begin // re-priming the decoder, for NotifyDataArrived to be processed by the // demuxer, for a successful demux, for a decode, and for a failed demux. // Demux failure triggers a drain. This drain is not beneficial or // necessary because no samples are available for the current playback // position, but MediaFormatReader repeats the drain process because of the // NotifyDataArrived triggered by the mock Seek(). WaitForReaderOperations(5); checkpoint.Call("Internal seek waiting for data"); MOZ_ASSERT(!drainPromise.IsEmpty()); // Request more data to check that this does not clear the status of the // in-progress drain, as in step 5 of // https://bugzilla.mozilla.org/show_bug.cgi?id=1941164#c6 // At the time of writing, without bug 1941164, MediaFormatReader does not // reject this promise until the drain completes. However, the promise // could sensibly be rejected earlier because the failed demux has indicated // that video data is not available for the current playback position. (void)proxy->RequestVideoData(TimeUnit(), false); // Trigger another Update() to check that another drain does not start. EXPECT_NS_SUCCEEDED(reader->OwnerThread()->Dispatch( NewRunnableMethod("NotifyDataArrived", reader.get(), &MediaFormatReader::NotifyDataArrived))); // Wait for NotifyDataArrived to be processed by the demuxer and for another // demux request to complete. WaitForReaderOperations(2); // Clean up. WaitForResolve(proxy->Shutdown()); drainPromise.Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); }