TestMediaFormatReader.cpp (9054B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ImageContainer.h" 8 #include "MediaFormatReader.h" 9 #include "MockDecoderModule.h" 10 #include "MockMediaDataDemuxer.h" 11 #include "MockMediaDecoderOwner.h" 12 #include "PDMFactory.h" 13 #include "ReaderProxy.h" 14 #include "TimeUnits.h" 15 #include "VideoFrameContainer.h" 16 #include "gtest/gtest.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/gtest/MozAssertions.h" 19 #include "mozilla/gtest/WaitFor.h" 20 #include "nsQueryObject.h" 21 22 using namespace mozilla; 23 using namespace mozilla::layers; 24 25 using DecodePromise = MediaDataDecoder::DecodePromise; 26 using SamplesHolder = MediaTrackDemuxer::SamplesHolder; 27 using SamplesPromise = MediaTrackDemuxer::SamplesPromise; 28 using SeekPromise = MediaTrackDemuxer::SeekPromise; 29 using TrackType = TrackInfo::TrackType; 30 using media::TimeIntervals; 31 using media::TimeUnit; 32 using testing::InSequence; 33 using testing::MockFunction; 34 using testing::Return; 35 using testing::StrEq; 36 37 TEST(TestMediaFormatReader, WaitingForDemuxAfterInternalSeek) 38 { 39 RefPtr<MediaFormatReader> reader; 40 // Thread scheduling provides ordering for thread initializations before 41 // their first read. 42 RefPtr<TaskQueue> demuxerThread; 43 RefPtr<TaskQueue> decoderThread; 44 45 // Wait enough for the MediaFormatReader to process at least 46 // aCount demuxer or decoder operations, if pending. 47 auto WaitForReaderOperations = [&](int aCount) { 48 // AwaitIdle() ensures that no tasks are pending and any task for another 49 // thread is already in the other thread's queue, only if dispatch across 50 // threads is not via tail dispatch. Tail dispatch is not used because 51 // the demuxer and decoder threads do not support tail dispatch, even 52 // though the MediaFormatReader task queue supports tail dispatch. 53 // https://searchfox.org/mozilla-central/rev/126697140e711e04a9d95edae537541c3bde89cc/xpcom/threads/AbstractThread.cpp#285-289 54 MOZ_ASSERT(!demuxerThread->SupportsTailDispatch()); 55 MOZ_ASSERT(!decoderThread->SupportsTailDispatch()); 56 // Check that the reader thread has dispatched the first request to 57 // the demuxer or decoder thread. 58 reader->OwnerThread()->AwaitIdle(); 59 for (int i = 0; i < aCount; ++i) { 60 demuxerThread->AwaitIdle(); 61 decoderThread->AwaitIdle(); 62 reader->OwnerThread()->AwaitIdle(); 63 } 64 }; 65 66 RefPtr dataDemuxer = new MockMediaDataDemuxer(); 67 RefPtr trackDemuxer = 68 // VideoInfo::IsValid() needs dimensions. 69 new MockMediaTrackDemuxer("video/x-test; width=640; height=360"); 70 71 ON_CALL(*dataDemuxer, GetNumberTracks(TrackType::kVideoTrack)) 72 .WillByDefault(Return(1)); 73 74 ON_CALL(*dataDemuxer, GetTrackDemuxer) 75 .WillByDefault([&](TrackType aType, uint32_t aTrackNumber) { 76 EXPECT_EQ(aTrackNumber, 0u); 77 EXPECT_EQ(aType, TrackType::kVideoTrack); 78 if (!demuxerThread) { 79 demuxerThread = do_QueryObject(AbstractThread::GetCurrent()); 80 } 81 return do_AddRef(trackDemuxer); 82 }); 83 84 RefPtr pdm = new MockDecoderModule(); 85 PDMFactory::AutoForcePDM autoForcePDM(pdm); 86 RefPtr<MockVideoDataDecoder> decoder; 87 MozPromiseHolder<DecodePromise> drainPromise; 88 EXPECT_CALL(*pdm, CreateVideoDecoder) 89 .WillOnce([&](const CreateDecoderParams& aParams) { 90 decoder = new MockVideoDataDecoder(aParams); 91 InSequence s; 92 // The first drain requires two calls: one to fetch the frames... 93 EXPECT_CALL(*decoder, Drain).WillOnce([&] { 94 MOZ_ASSERT(!decoderThread); 95 decoderThread = do_QueryObject(AbstractThread::GetCurrent()); 96 return decoder->DummyMediaDataDecoder::Drain(); 97 }); 98 // ... and a second to confirm that no more frames are remaining. 99 EXPECT_CALL(*decoder, Drain).Times(1); 100 // Delay responding to the second drain request until testing is done. 101 EXPECT_CALL(*decoder, Drain).WillOnce([&] { 102 return drainPromise.Ensure(__func__); 103 }); 104 decoder->SetLatencyFrameCount(8); 105 return do_AddRef(decoder); 106 }); 107 108 MockFunction<void(const char* name)> checkpoint; 109 { 110 InSequence s; 111 112 EXPECT_CALL(*trackDemuxer, MockGetSamples).Times(2).WillRepeatedly([]() { 113 static int count = 0; 114 RefPtr sample = new MediaRawData; 115 sample->mTime = TimeUnit(count, 30); 116 ++count; 117 RefPtr<SamplesHolder> samples = new SamplesHolder; 118 samples->AppendSample(std::move(sample)); 119 return SamplesPromise::CreateAndResolve(samples, __func__); 120 }); 121 EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { 122 return SamplesPromise::CreateAndReject( 123 NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); 124 }); 125 EXPECT_CALL(*trackDemuxer, Seek).WillOnce([&](const TimeUnit& aTime) { 126 // Reset mWaitingForDataStartTime so that OnDemuxFailed() calls 127 // RequestDrain(). 128 EXPECT_NS_SUCCEEDED(reader->OwnerThread()->Dispatch( 129 NewRunnableMethod("NotifyDataArrived", reader.get(), 130 &MediaFormatReader::NotifyDataArrived))); 131 return SeekPromise::CreateAndResolve(TimeUnit::Zero(), __func__); 132 }); 133 EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { 134 RefPtr sample = new MediaRawData; 135 // Time is zero after the seek. 136 sample->mTime = TimeUnit(0, 30); 137 RefPtr<SamplesHolder> samples = new SamplesHolder; 138 samples->AppendSample(std::move(sample)); 139 return SamplesPromise::CreateAndResolve(samples, __func__); 140 }); 141 EXPECT_CALL(*trackDemuxer, MockGetSamples).WillOnce([]() { 142 return SamplesPromise::CreateAndReject( 143 NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); 144 }); 145 EXPECT_CALL(checkpoint, Call(StrEq("Internal seek waiting for data"))); 146 147 EXPECT_CALL(*trackDemuxer, MockGetSamples).WillRepeatedly([]() { 148 return SamplesPromise::CreateAndReject( 149 NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); 150 }); 151 } 152 153 auto owner = std::make_unique<MockMediaDecoderOwner>(); 154 RefPtr container = new VideoFrameContainer( 155 owner.get(), 156 MakeAndAddRef<ImageContainer>(ImageUsageType::VideoFrameContainer, 157 #ifdef MOZ_WIDGET_ANDROID 158 // Work around bug 1922144 159 ImageContainer::SYNCHRONOUS 160 #else 161 ImageContainer::ASYNCHRONOUS 162 #endif 163 )); 164 MediaFormatReaderInit init; 165 init.mVideoFrameContainer = container; 166 reader = new MediaFormatReader(init, dataDemuxer); 167 RefPtr proxy = new ReaderProxy(AbstractThread::MainThread(), reader); 168 EXPECT_NS_SUCCEEDED(reader->Init()); 169 170 // ReadMetadata() to init demuxer. 171 (void)WaitForResolve(proxy->ReadMetadata()); 172 // Two samples are provided by the demuxer, but the third demux request is 173 // rejected. The first drain provides two decoded samples. 174 for (int i = 0; i < 2; ++i) { 175 (void)WaitForResolve(proxy->RequestVideoData(TimeUnit(), false)); 176 } 177 // A third sample is not available. 178 MediaResult result = 179 WaitForReject(proxy->RequestVideoData(TimeUnit(), false)); 180 EXPECT_EQ(result.Code(), NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA); 181 // The first drain is complete. Wait for the internal seek to begin 182 // re-priming the decoder, for NotifyDataArrived to be processed by the 183 // demuxer, for a successful demux, for a decode, and for a failed demux. 184 // Demux failure triggers a drain. This drain is not beneficial or 185 // necessary because no samples are available for the current playback 186 // position, but MediaFormatReader repeats the drain process because of the 187 // NotifyDataArrived triggered by the mock Seek(). 188 WaitForReaderOperations(5); 189 190 checkpoint.Call("Internal seek waiting for data"); 191 MOZ_ASSERT(!drainPromise.IsEmpty()); 192 // Request more data to check that this does not clear the status of the 193 // in-progress drain, as in step 5 of 194 // https://bugzilla.mozilla.org/show_bug.cgi?id=1941164#c6 195 // At the time of writing, without bug 1941164, MediaFormatReader does not 196 // reject this promise until the drain completes. However, the promise 197 // could sensibly be rejected earlier because the failed demux has indicated 198 // that video data is not available for the current playback position. 199 (void)proxy->RequestVideoData(TimeUnit(), false); 200 // Trigger another Update() to check that another drain does not start. 201 EXPECT_NS_SUCCEEDED(reader->OwnerThread()->Dispatch( 202 NewRunnableMethod("NotifyDataArrived", reader.get(), 203 &MediaFormatReader::NotifyDataArrived))); 204 // Wait for NotifyDataArrived to be processed by the demuxer and for another 205 // demux request to complete. 206 WaitForReaderOperations(2); 207 // Clean up. 208 WaitForResolve(proxy->Shutdown()); 209 drainPromise.Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); 210 }