tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }