Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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<MediaFormatReader> reader;
// Thread scheduling provides ordering for thread initializations before
// their first read.
RefPtr<TaskQueue> demuxerThread;
RefPtr<TaskQueue> 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.
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<MockVideoDataDecoder> decoder;
MozPromiseHolder<DecodePromise> 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<void(const char* name)> 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<SamplesHolder> 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<SamplesHolder> 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<MockMediaDecoderOwner>();
RefPtr container = new VideoFrameContainer(
owner.get(),
MakeAndAddRef<ImageContainer>(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
// 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__);
}