Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "WebrtcGmpVideoCodec.h"
#include "api/video/i420_buffer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/base/media_constants.h"
#include "mozilla/gtest/WaitFor.h"
using testing::_;
using testing::AtLeast;
using testing::Eq;
using testing::Ge;
using testing::Gt;
using testing::InSequence;
using testing::Property;
using testing::Test;
namespace mozilla {
struct TestWebrtcGmpVideoEncoder : public Test {
nsCOMPtr<nsIThread> mGmpThread;
RefPtr<WebrtcGmpVideoEncoder> mEncoder;
webrtc::VideoCodec mCodecSettings;
webrtc::VideoEncoder::Settings mSettings = {
webrtc::VideoEncoder::Capabilities(/*loss_notification=*/true),
/*number_of_cores=*/1, /*max_payload_size=*/0};
void SetUp() override {
mEncoder = MakeRefPtr<WebrtcGmpVideoEncoder>(
webrtc::SdpVideoFormat(webrtc::kH264CodecName), "dummy");
mCodecSettings.codecType = webrtc::VideoCodecType::kVideoCodecH264;
mCodecSettings.numberOfSimulcastStreams = 1;
mCodecSettings.simulcastStream[0].active = true;
mCodecSettings.simulcastStream[0].numberOfTemporalLayers = 1;
mCodecSettings.width = 640;
mCodecSettings.height = 480;
mCodecSettings.maxFramerate = 60;
mCodecSettings.minBitrate = 50;
mCodecSettings.startBitrate = 200;
mCodecSettings.maxBitrate = 1000;
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
ASSERT_TRUE(mps);
mps->GetThread(getter_AddRefs(mGmpThread));
ASSERT_TRUE(mGmpThread);
}
void TearDown() override { mEncoder = nullptr; }
};
struct MockEncodedImageCallback : public webrtc::EncodedImageCallback {
MOCK_METHOD(Result, OnEncodedImage,
(const webrtc::EncodedImage&, const webrtc::CodecSpecificInfo*),
(override));
MOCK_METHOD(void, OnDroppedFrame, (DropReason), (override));
};
auto CreateBlackFrame(int width, int height) {
auto buffer = webrtc::I420Buffer::Create(width, height);
webrtc::I420Buffer::SetBlack(buffer.get());
return buffer;
}
TEST_F(TestWebrtcGmpVideoEncoder, EmptyLifecycle) {}
TEST_F(TestWebrtcGmpVideoEncoder, InitEncode) {
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
}
TEST_F(TestWebrtcGmpVideoEncoder, Encode) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtp_time = 55;
EXPECT_CALL(
callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp, Eq(rtp_time)), _))
.WillOnce([&] {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::Encode");
return Result(Result::OK);
});
mEncoder->RegisterEncodeCompleteCallback(&callback);
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtp_time)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
EXPECT_EQ(WaitForResolve(donePromise), true);
}
TEST_F(TestWebrtcGmpVideoEncoder, BackPressure) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtpTime = 55;
constexpr size_t iterations = 1000;
Atomic<uint32_t> lastRtpTime{};
Atomic<size_t> eventCount{};
const auto countIteration = [&] {
size_t c = ++eventCount;
EXPECT_LE(c, iterations);
if (c == iterations) {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::BackPressure");
}
};
EXPECT_CALL(
callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp,
testing::AllOf(Ge(rtpTime), Gt<uint32_t>(lastRtpTime))),
_))
.Times(AtLeast(1))
.WillRepeatedly([&](const auto& aImage, const auto*) {
lastRtpTime = aImage.RtpTimestamp();
countIteration();
return Result(Result::OK);
});
EXPECT_CALL(
callback,
OnDroppedFrame(MockEncodedImageCallback::DropReason::kDroppedByEncoder))
.Times(AtLeast(iterations / 10))
.WillRepeatedly(countIteration);
mEncoder->RegisterEncodeCompleteCallback(&callback);
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
for (size_t i = 1; i < iterations; ++i) {
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime + i)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types);
}
EXPECT_EQ(WaitForResolve(donePromise), true);
EXPECT_EQ(eventCount, iterations);
}
TEST_F(TestWebrtcGmpVideoEncoder, ReUse) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtpTime = 55;
constexpr uint32_t rtpTime2 = rtpTime * 2;
EXPECT_CALL(callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp, rtpTime2), _))
.WillOnce([&] {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::ReUse");
return Result(Result::OK);
});
mEncoder->RegisterEncodeCompleteCallback(&callback);
// Block the GMP thread until after Shutdown() as to avoid racing between the
// first encoded callback and the Shutdown() call.
Monitor mon(__func__);
bool block = true;
MOZ_ALWAYS_SUCCEEDS(
mGmpThread->Dispatch(NS_NewRunnableFunction(__func__, [&] {
MonitorAutoLock lock(mon);
while (block) {
lock.Wait();
}
})));
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
// Shutdown mid-encode, then re-init and encode again.
mEncoder->Shutdown();
{
MonitorAutoLock lock(mon);
block = false;
lock.Notify();
}
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
mEncoder->RegisterEncodeCompleteCallback(&callback);
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime2)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
EXPECT_EQ(WaitForResolve(donePromise), true);
}
TEST_F(TestWebrtcGmpVideoEncoder, TrackedFrameDrops) {
using Result = webrtc::EncodedImageCallback::Result;
// Tell the fakeopenh264 plugin to drop some allocated input frames without
// telling us. It will drop every fifth input frame. This shall get tracked
// as frame drops.
mCodecSettings.SetFrameDropEnabled(true);
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
Monitor m(__func__);
size_t numEvents = 0;
const auto handleEvent = ([&] {
MonitorAutoLock lock(m);
++numEvents;
lock.Notify();
});
MockEncodedImageCallback callback;
{
InSequence s;
EXPECT_CALL(callback, OnEncodedImage(_, _)).Times(4).WillRepeatedly([&] {
handleEvent();
return Result(Result::OK);
});
EXPECT_CALL(
callback,
OnDroppedFrame(MockEncodedImageCallback::DropReason::kDroppedByEncoder))
.WillOnce(handleEvent);
}
mEncoder->RegisterEncodeCompleteCallback(&callback);
constexpr uint32_t ntpTime = 55;
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
for (uint8_t i = 0; i < 5; ++i) {
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_ntp_time_ms(ntpTime * (i + 1))
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
MonitorAutoLock lock(m);
while (numEvents <= i) {
lock.Wait();
}
}
}
} // namespace mozilla