TestVideoSink.cpp (6971B)
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 "AudioDeviceInfo.h" 8 #include "AudioSink.h" 9 #include "AudioSinkWrapper.h" 10 #include "ImageContainer.h" 11 #include "MockCubeb.h" 12 #include "MockMediaDecoderOwner.h" 13 #include "TimeUnits.h" 14 #include "VideoFrameContainer.h" 15 #include "VideoSink.h" 16 #include "gtest/gtest.h" 17 #include "mozilla/gtest/WaitFor.h" 18 19 using namespace mozilla; 20 using namespace mozilla::layers; 21 22 using media::TimeUnit; 23 24 TEST(TestVideoSink, FrameThrottling) 25 { 26 MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual); 27 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 28 29 MediaInfo info; 30 info.EnableAudio(); // to control the advance of time through MockCubeb 31 info.EnableVideo(); 32 33 MediaQueue<AudioData> audioQueue; 34 auto audioSinkCreator = [&]() { 35 return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(), 36 audioQueue, info.mAudio, 37 /*resistFingerprinting*/ false)}; 38 }; 39 RefPtr wrapper = new AudioSinkWrapper( 40 AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator), 41 /*initialVolume*/ 1.0, /*playbackRate*/ 1.0, /*preservesPitch*/ true, 42 /*sinkDevice*/ nullptr); 43 44 auto owner = std::make_unique<MockMediaDecoderOwner>(); 45 RefPtr container = new VideoFrameContainer( 46 owner.get(), 47 MakeAndAddRef<ImageContainer>(ImageUsageType::VideoFrameContainer, 48 #ifdef MOZ_WIDGET_ANDROID 49 // Work around bug 1922144 50 ImageContainer::SYNCHRONOUS 51 #else 52 ImageContainer::ASYNCHRONOUS 53 #endif 54 )); 55 56 MediaQueue<VideoData> videoQueue; 57 RefPtr frameStatistics = new FrameStatistics(); 58 RefPtr videoSink = new VideoSink(AbstractThread::GetCurrent(), wrapper, 59 videoQueue, container, *frameStatistics, 60 /*aVQueueSentToCompositerSize*/ 9999); 61 auto initPromise = TakeN(cubeb->StreamInitEvent(), 1); 62 videoSink->Start(TimeUnit::Zero(), info); 63 auto [stream] = WaitFor(initPromise).unwrap()[0]; 64 uint32_t audioRate = stream->SampleRate(); 65 66 // Enough audio data that it does not underrun, which would stop the clock. 67 size_t audioFrameCount = 1000 * info.mAudio.mRate / audioRate; 68 AlignedAudioBuffer samples(audioFrameCount * info.mAudio.mChannels); 69 RefPtr audioData = new AudioData( 70 /*aOffset*/ 0, /*aTime*/ TimeUnit(0, info.mAudio.mRate), 71 std::move(samples), info.mAudio.mChannels, info.mAudio.mRate); 72 audioQueue.Push(audioData); 73 74 auto image = container->GetImageContainer()->CreatePlanarYCbCrImage(); 75 static uint8_t pixel[] = {0x00}; 76 PlanarYCbCrData imageData; 77 imageData.mYChannel = imageData.mCbChannel = imageData.mCrChannel = pixel; 78 imageData.mYStride = imageData.mCbCrStride = 1; 79 imageData.mPictureRect = gfx::IntRect(0, 0, 1, 1); 80 image->CopyData(imageData); 81 82 TimeUnit nextFrameTime = TimeUnit(0, audioRate); 83 auto PushVideoFrame = [&](const gfx::IntSize& aSize, 84 const TimeUnit& aDuration) { 85 static bool isKeyFrame = true; 86 RefPtr frame = VideoData::CreateFromImage(aSize, /*aOffset*/ 0, 87 /*aTime*/ nextFrameTime, 88 aDuration, image, isKeyFrame, 89 /*aTimecode*/ nextFrameTime); 90 frame->mFrameID = container->NewFrameID(); 91 videoQueue.Push(frame); 92 nextFrameTime = frame->GetEndTime(); 93 isKeyFrame = false; 94 }; 95 96 gfx::IntSize size1{1, 1}; 97 PushVideoFrame(size1, TimeUnit(1, audioRate)); 98 gfx::IntSize size2{1, 2}; 99 PushVideoFrame(size2, TimeUnit(1, audioRate)); 100 // UpdateRenderedVideoFrames() will keep scheduling additional events in 101 // antipication of the audio clock advancing for the second frame, so wait 102 // for only the initial size from the first frame. 103 SpinEventLoopUntil("the intrinsic size receives an initial value"_ns, [&] { 104 return container->CurrentIntrinsicSize().isSome(); 105 }); 106 EXPECT_EQ(container->CurrentIntrinsicSize().value(), size1); 107 108 // Advance time to expire both frames. 109 stream->ManualDataCallback(nextFrameTime.ToTicksAtRate(audioRate) + 1); 110 // Run UpdateRenderedVideoFramesByTimer(), which is scheduled on TimeStamp's 111 // clock, which we don't control. 112 SpinEventLoopUntil( 113 "the intrinsic size is updated to that of frame 2"_ns, 114 [&] { return container->CurrentIntrinsicSize().value() == size2; }); 115 116 // The next frame is overdue but has not yet expired. 117 gfx::IntSize size3{1, 3}; 118 PushVideoFrame(size3, TimeUnit(2, audioRate)); 119 gfx::IntSize size4{1, 4}; 120 PushVideoFrame(size4, TimeUnit(1, audioRate)); 121 // Run UpdateRenderedVideoFrames() via OnVideoQueuePushed(). 122 NS_ProcessPendingEvents(nullptr); 123 EXPECT_EQ(container->CurrentIntrinsicSize().value(), size3); 124 EXPECT_EQ(frameStatistics->GetDroppedSinkFrames(), 0u); 125 126 // Advance time to expire the two frames in the queue and the next three. 127 stream->ManualDataCallback(static_cast<long>( 128 nextFrameTime.ToTicksAtRate(audioRate) + 11 - stream->Position())); 129 // This frame has a longer duration and is late. 130 gfx::IntSize size5{1, 5}; 131 PushVideoFrame(size5, TimeUnit(8, audioRate)); 132 // The most recent frame was late, and so is not rendered yet because it may 133 // be dropped. 134 // 135 // OnVideoQueuePushed() uses TryUpdateRenderedVideoFrames(), which no-ops if 136 // an update is already scheduled. Wait for the update scheduled for 137 // frame 4. 138 SpinEventLoopUntil( 139 "the intrinsic size is updated to that of frame 4"_ns, 140 [&] { return container->CurrentIntrinsicSize().value() == size4; }); 141 142 // This frame is also late. 143 gfx::IntSize size6{1, 6}; 144 PushVideoFrame(size6, TimeUnit(1, audioRate)); 145 NS_ProcessPendingEvents(nullptr); 146 // One frame was dropped, but the most recent frame was rendered because its 147 // lateness was less than the duration of the dropped frame. 148 EXPECT_EQ(frameStatistics->GetDroppedSinkFrames(), 1u); 149 EXPECT_EQ(container->CurrentIntrinsicSize().value(), size6); 150 151 gfx::IntSize size7{1, 7}; 152 PushVideoFrame(size7, TimeUnit(1, audioRate)); 153 NS_ProcessPendingEvents(nullptr); 154 // The most recent frame was late, and so is not rendered yet because it may 155 // be dropped. 156 EXPECT_EQ(container->CurrentIntrinsicSize().value(), size6); 157 158 // On playback pause, the most recent frame is rendered. 159 videoSink->SetPlaying(false); 160 EXPECT_EQ(container->CurrentIntrinsicSize().value(), size7); 161 EXPECT_EQ(frameStatistics->GetDroppedSinkFrames(), 1u); 162 videoSink->Stop(); 163 videoSink->Shutdown(); 164 }