diff --git a/dom/media/gtest/TestMP4Demuxer.cpp b/dom/media/gtest/TestMP4Demuxer.cpp index 5f43136c2bef..b756e8a4dd73 100644 --- a/dom/media/gtest/TestMP4Demuxer.cpp +++ b/dom/media/gtest/TestMP4Demuxer.cpp @@ -4,32 +4,151 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" -#include "mp4_demuxer/mp4_demuxer.h" +#include "MP4Demuxer.h" #include "MP4Stream.h" +#include "MozPromise.h" +#include "MediaDataDemuxer.h" +#include "SharedThreadPool.h" +#include "TaskQueue.h" #include "mozilla/ArrayUtils.h" #include "MockMediaResource.h" using namespace mozilla; using namespace mp4_demuxer; +class AutoTaskQueue; + +#define DO_FAIL []()->void { EXPECT_TRUE(false); } + class MP4DemuxerBinding { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP4DemuxerBinding); nsRefPtr resource; - Monitor mMonitor; - nsRefPtr demuxer; + nsRefPtr mDemuxer; + nsRefPtr mTaskQueue; + nsRefPtr mAudioTrack; + nsRefPtr mVideoTrack; + uint32_t mIndex; + nsTArray> mSamples; + nsTArray mKeyFrameTimecodes; + MozPromiseHolder mCheckTrackKeyFramePromise; + MozPromiseHolder mCheckTrackSamples; explicit MP4DemuxerBinding(const char* aFileName = "dash_dashinit.mp4") : resource(new MockMediaResource(aFileName)) - , mMonitor("TestMP4Demuxer monitor") - , demuxer(new MP4Demuxer(new MP4Stream(resource), &mMonitor)) + , mDemuxer(new MP4Demuxer(resource)) + , mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK))) + , mIndex(0) { EXPECT_EQ(NS_OK, resource->Open(nullptr)); } + template + void RunTestAndWait(const Function& aFunction) + { + Function func(aFunction); + mDemuxer->Init()->Then(mTaskQueue, __func__, Move(func), DO_FAIL); + mTaskQueue->AwaitShutdownAndIdle(); + } + + nsRefPtr + CheckTrackKeyFrame(MediaTrackDemuxer* aTrackDemuxer) + { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + nsRefPtr track = aTrackDemuxer; + nsRefPtr self = this; + + int64_t time = -1; + while (mIndex < mSamples.Length()) { + uint32_t i = mIndex++; + if (mSamples[i]->mKeyframe) { + time = mSamples[i]->mTime; + break; + } + } + + nsRefPtr p = mCheckTrackKeyFramePromise.Ensure(__func__); + + if (time == -1) { + mCheckTrackKeyFramePromise.Resolve(true, __func__); + return p; + } + + + DispatchTask( + [track, time, self] () { + track->Seek(media::TimeUnit::FromMicroseconds(time))->Then(self->mTaskQueue, __func__, + [track, time, self] () { + track->GetSamples()->Then(self->mTaskQueue, __func__, + [track, time, self] (nsRefPtr aSamples) { + EXPECT_EQ(time, aSamples->mSamples[0]->mTime); + self->CheckTrackKeyFrame(track); + }, + DO_FAIL + ); + }, + DO_FAIL + ); + } + ); + + return p; + } + + nsRefPtr + CheckTrackSamples(MediaTrackDemuxer* aTrackDemuxer) + { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + nsRefPtr track = aTrackDemuxer; + nsRefPtr self = this; + + nsRefPtr p = mCheckTrackSamples.Ensure(__func__); + + DispatchTask( + [track, self] () { + track->GetSamples()->Then(self->mTaskQueue, __func__, + [track, self] (nsRefPtr aSamples) { + if (aSamples->mSamples.Length()) { + self->mSamples.AppendElements(aSamples->mSamples); + self->CheckTrackSamples(track); + } + }, + [self] (DemuxerFailureReason aReason) { + if (aReason == DemuxerFailureReason::DEMUXER_ERROR) { + EXPECT_TRUE(false); + self->mCheckTrackSamples.Reject(NS_ERROR_FAILURE, __func__); + } else if (aReason == DemuxerFailureReason::END_OF_STREAM) { + EXPECT_TRUE(self->mSamples.Length() > 1); + for (uint32_t i = 0; i < (self->mSamples.Length() - 1); i++) { + EXPECT_LT(self->mSamples[i]->mTimecode, self->mSamples[i + 1]->mTimecode); + if (self->mSamples[i]->mKeyframe) { + self->mKeyFrameTimecodes.AppendElement(self->mSamples[i]->mTimecode); + } + } + self->mCheckTrackSamples.Resolve(true, __func__); + } + } + ); + } + ); + + return p; + } + private: + + template + void + DispatchTask(FunctionType aFun) + { + nsRefPtr r = NS_NewRunnableFunction(aFun); + mTaskQueue->Dispatch(r.forget()); + } + virtual ~MP4DemuxerBinding() { } @@ -37,30 +156,20 @@ private: TEST(MP4Demuxer, Seek) { - nsRefPtr b = new MP4DemuxerBinding(); - MonitorAutoLock mon(b->mMonitor); - MP4Demuxer* d = b->demuxer; + nsRefPtr binding = new MP4DemuxerBinding(); - EXPECT_TRUE(d->Init()); - - nsTArray> samples; - nsRefPtr sample; - while (!!(sample = d->DemuxVideoSample())) { - samples.AppendElement(sample); - if (samples.Length() >= 2) { - EXPECT_LT(samples[samples.Length() - 2]->mTimecode, - samples[samples.Length() - 1]->mTimecode); - } - } - Microseconds keyFrame = 0; - for (size_t i = 0; i < samples.Length(); i++) { - if (samples[i]->mKeyframe) { - keyFrame = samples[i]->mTimecode; - } - d->SeekVideo(samples[i]->mTime); - sample = d->DemuxVideoSample(); - EXPECT_EQ(keyFrame, sample->mTimecode); - } + binding->RunTestAndWait([binding] () { + binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); + binding->CheckTrackSamples(binding->mVideoTrack) + ->Then(binding->mTaskQueue, __func__, + [binding] () { + binding->CheckTrackKeyFrame(binding->mVideoTrack) + ->Then(binding->mTaskQueue, __func__, + [binding] () { + binding->mTaskQueue->BeginShutdown(); + }, DO_FAIL); + }, DO_FAIL); + }); } static nsCString @@ -87,14 +196,10 @@ ToCryptoString(const CryptoSample& aCrypto) return res; } +#ifndef XP_WIN // VC2013 doesn't support C++11 array initialization. + TEST(MP4Demuxer, CENCFrag) { - nsRefPtr b = new MP4DemuxerBinding("gizmo-frag.mp4"); - MonitorAutoLock mon(b->mMonitor); - MP4Demuxer* d = b->demuxer; - - EXPECT_TRUE(d->Init()); - const char* video[] = { "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000000 5,684 5,16980", "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000450 5,1826", @@ -158,13 +263,22 @@ TEST(MP4Demuxer, CENCFrag) "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000019cd 5,2392", }; - nsRefPtr sample; - size_t i = 0; - while (!!(sample = d->DemuxVideoSample())) { - nsCString text = ToCryptoString(sample->mCrypto); - EXPECT_STREQ(video[i++], text.get()); - } - EXPECT_EQ(ArrayLength(video), i); + nsRefPtr binding = new MP4DemuxerBinding("gizmo-frag.mp4"); + + binding->RunTestAndWait([binding, video] () { + // grab all video samples. + binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); + binding->CheckTrackSamples(binding->mVideoTrack) + ->Then(binding->mTaskQueue, __func__, + [binding, video] () { + for (uint32_t i = 0; i < binding->mSamples.Length(); i++) { + nsCString text = ToCryptoString(binding->mSamples[i]->mCrypto); + EXPECT_STREQ(video[i++], text.get()); + } + EXPECT_EQ(ArrayLength(video), binding->mSamples.Length()); + binding->mTaskQueue->BeginShutdown(); + }, DO_FAIL); + }); const char* audio[] = { "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000000 0,281", @@ -262,42 +376,52 @@ TEST(MP4Demuxer, CENCFrag) "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008cd 0,433", "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008e9 0,481", }; + nsRefPtr audiobinding = new MP4DemuxerBinding("gizmo-frag.mp4"); - i = 0; - while (!!(sample = d->DemuxAudioSample())) { - nsCString text = ToCryptoString(sample->mCrypto); - EXPECT_STREQ(audio[i++], text.get()); - } - EXPECT_EQ(ArrayLength(audio), i); + audiobinding->RunTestAndWait([audiobinding, audio] () { + // grab all audio samples. + audiobinding->mAudioTrack = audiobinding->mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0); + audiobinding->CheckTrackSamples(audiobinding->mAudioTrack) + ->Then(audiobinding->mTaskQueue, __func__, + [audiobinding, audio] () { + EXPECT_TRUE(audiobinding->mSamples.Length() > 1); + for (uint32_t i = 0; i < audiobinding->mSamples.Length(); i++) { + nsCString text = ToCryptoString(audiobinding->mSamples[i]->mCrypto); + EXPECT_STREQ(audio[i++], text.get()); + } + EXPECT_EQ(ArrayLength(audio), audiobinding->mSamples.Length()); + audiobinding->mTaskQueue->BeginShutdown(); + }, DO_FAIL); + }); } +#endif + TEST(MP4Demuxer, GetNextKeyframe) { - nsRefPtr b = new MP4DemuxerBinding("gizmo-frag.mp4"); - MonitorAutoLock mon(b->mMonitor); - MP4Demuxer* d = b->demuxer; + nsRefPtr binding = new MP4DemuxerBinding("gizmo-frag.mp4"); - EXPECT_TRUE(d->Init()); + binding->RunTestAndWait([binding] () { + // Insert a [0,end] buffered range, to simulate Moof's being buffered + // via MSE. + auto len = binding->resource->GetLength(); + binding->resource->MockAddBufferedRange(0, len); - // Insert a [0,end] buffered range, to simulate Moof's being buffered - // via MSE. - auto len = b->resource->GetLength(); - b->resource->MockAddBufferedRange(0, len); - - // Rebuild the index so that it can be used to find the keyframes. - nsTArray ranges; - EXPECT_TRUE(NS_SUCCEEDED(b->resource->GetCachedRanges(ranges))); - d->UpdateIndex(ranges); - - // gizmp-frag has two keyframes; one at dts=cts=0, and another at - // dts=cts=1000000. Verify we get expected results. - - nsRefPtr sample; - size_t i = 0; - const int64_t keyframe = 1000000; - while (!!(sample = d->DemuxVideoSample())) { - int64_t expected = (sample->mTimecode < keyframe) ? keyframe : -1; - EXPECT_EQ(d->GetNextKeyframeTime(), expected); - i++; - } + // gizmp-frag has two keyframes; one at dts=cts=0, and another at + // dts=cts=1000000. Verify we get expected results. + media::TimeUnit time; + binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); + binding->mVideoTrack->Reset(); + binding->mVideoTrack->GetNextRandomAccessPoint(&time); + EXPECT_EQ(time.ToMicroseconds(), 0); + binding->mVideoTrack->GetSamples()->Then(binding->mTaskQueue, __func__, + [binding] () { + media::TimeUnit time; + binding->mVideoTrack->GetNextRandomAccessPoint(&time); + EXPECT_EQ(time.ToMicroseconds(), 1000000); + binding->mTaskQueue->BeginShutdown(); + }, + DO_FAIL + ); + }); } diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build index 8cb5223d8bfc..2e8db6947801 100644 --- a/dom/media/gtest/moz.build +++ b/dom/media/gtest/moz.build @@ -12,6 +12,7 @@ UNIFIED_SOURCES += [ 'TestIntervalSet.cpp', 'TestMozPromise.cpp', 'TestMP3Demuxer.cpp', + 'TestMP4Demuxer.cpp', # 'TestMP4Reader.cpp', disabled so we can turn check tests back on (bug 1175752) 'TestTrackEncoder.cpp', 'TestVideoSegment.cpp',