blob: 0f9e410ad42fb4dbd48b167e747951e5808ededc [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cast/encoding/video_encoder.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/fake_single_thread_task_runner.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_environment.h"
#include "media/cast/common/openscreen_conversion_helpers.h"
#include "media/cast/common/rtp_time.h"
#include "media/cast/common/sender_encoded_frame.h"
#include "media/cast/common/video_frame_factory.h"
#include "media/cast/test/fake_video_encode_accelerator_factory.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/video_utility.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/openscreen/src/cast/streaming/encoded_frame.h"
#if BUILDFLAG(IS_MAC)
#include "base/threading/platform_thread.h"
#include "media/cast/encoding/h264_vt_encoder.h"
#endif
namespace media {
namespace cast {
class VideoEncoderTest
: public ::testing::TestWithParam<std::pair<Codec, bool>> {
public:
VideoEncoderTest(const VideoEncoderTest&) = delete;
VideoEncoderTest& operator=(const VideoEncoderTest&) = delete;
protected:
VideoEncoderTest()
: task_runner_(new FakeSingleThreadTaskRunner(&testing_clock_)),
task_runner_current_handle_override_(task_runner_),
cast_environment_(new CastEnvironment(&testing_clock_,
task_runner_,
task_runner_,
task_runner_)),
video_config_(GetDefaultVideoSenderConfig()),
operational_status_(STATUS_UNINITIALIZED) {
testing_clock_.Advance(base::TimeTicks::Now() - base::TimeTicks());
first_frame_time_ = testing_clock_.NowTicks();
}
~VideoEncoderTest() override = default;
void SetUp() final {
Codec codec = GetParam().first;
if (codec == Codec::kVideoFake) {
video_config_.enable_fake_codec_for_tests = true;
}
video_config_.codec = codec;
video_config_.use_hardware_encoder = GetParam().second;
if (is_testing_external_video_encoder()) {
vea_factory_ =
std::make_unique<FakeVideoEncodeAcceleratorFactory>(task_runner_);
}
}
void TearDown() final {
video_encoder_.reset();
RunTasksAndAdvanceClock();
}
void CreateEncoder() {
ASSERT_EQ(STATUS_UNINITIALIZED, operational_status_);
video_config_.video_codec_params.max_number_of_video_buffers_used = 1;
video_encoder_ = VideoEncoder::Create(
cast_environment_, video_config_,
base::BindRepeating(&VideoEncoderTest::OnOperationalStatusChange,
base::Unretained(this)),
base::BindRepeating(
&FakeVideoEncodeAcceleratorFactory::CreateVideoEncodeAccelerator,
base::Unretained(vea_factory_.get())));
RunTasksAndAdvanceClock();
if (is_encoder_present())
ASSERT_EQ(STATUS_INITIALIZED, operational_status_);
}
bool is_encoder_present() const { return !!video_encoder_; }
bool is_testing_software_vp8_encoder() const {
return video_config_.codec == Codec::kVideoVp8 &&
!video_config_.use_hardware_encoder;
}
bool is_testing_video_toolbox_encoder() const {
return
#if BUILDFLAG(IS_MAC)
(video_config_.use_hardware_encoder &&
H264VideoToolboxEncoder::IsSupported(video_config_)) ||
#endif
false;
}
bool is_testing_external_video_encoder() const {
return video_config_.use_hardware_encoder &&
!is_testing_video_toolbox_encoder();
}
VideoEncoder* video_encoder() const { return video_encoder_.get(); }
void DestroyEncoder() { video_encoder_.reset(); }
base::TimeTicks Now() { return testing_clock_.NowTicks(); }
void RunTasksAndAdvanceClock() {
DCHECK_GT(video_config_.max_frame_rate, 0);
const base::TimeDelta frame_duration =
base::Microseconds(1000000.0 / video_config_.max_frame_rate);
#if BUILDFLAG(IS_MAC)
if (is_testing_video_toolbox_encoder()) {
// The H264VideoToolboxEncoder (on MAC_OSX and IOS) is not a faked
// implementation in these tests, and performs its encoding asynchronously
// on an unknown set of threads. Therefore, sleep the current thread for
// the real amount of time to avoid excessively spinning the CPU while
// waiting for something to happen.
base::PlatformThread::Sleep(frame_duration);
}
#endif
task_runner_->RunTasks();
testing_clock_.Advance(frame_duration);
}
// Creates a new VideoFrame of the given |size|, filled with a test pattern.
// When available, it attempts to use the VideoFrameFactory provided by the
// encoder.
scoped_refptr<media::VideoFrame> CreateTestVideoFrame(const gfx::Size& size) {
const base::TimeDelta timestamp =
testing_clock_.NowTicks() - first_frame_time_;
scoped_refptr<media::VideoFrame> frame;
if (video_frame_factory_) {
DVLOG(1) << "MaybeCreateFrame";
frame = video_frame_factory_->MaybeCreateFrame(size, timestamp);
}
if (!frame) {
DVLOG(1) << "No VideoFrame, create using VideoFrame::CreateFrame";
frame = media::VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size,
gfx::Rect(size), size, timestamp);
}
PopulateVideoFrame(frame.get(), 123);
return frame;
}
// If the implementation of |video_encoder_| is ExternalVideoEncoder, check
// that the VEA factory has responded (by running the callbacks) a specific
// number of times. Otherwise, check that the VEA factory is inactive.
void ExpectVEAResponseForExternalVideoEncoder(int vea_response_count) const {
if (!vea_factory_)
return;
EXPECT_EQ(vea_response_count, vea_factory_->vea_response_count());
}
void SetVEAFactoryAutoRespond(bool auto_respond) {
if (vea_factory_)
vea_factory_->SetAutoRespond(auto_respond);
}
private:
void OnOperationalStatusChange(OperationalStatus status) {
DVLOG(1) << "OnOperationalStatusChange: from " << operational_status_
<< " to " << status;
operational_status_ = status;
EXPECT_TRUE(operational_status_ == STATUS_CODEC_REINIT_PENDING ||
operational_status_ == STATUS_INITIALIZED);
// Create the VideoFrameFactory the first time status changes to
// STATUS_INITIALIZED.
if (operational_status_ == STATUS_INITIALIZED && !video_frame_factory_)
video_frame_factory_ = video_encoder_->CreateVideoFrameFactory();
}
base::SimpleTestTickClock testing_clock_;
const scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
base::SingleThreadTaskRunner::CurrentHandleOverrideForTesting
task_runner_current_handle_override_;
const scoped_refptr<CastEnvironment> cast_environment_;
FrameSenderConfig video_config_;
std::unique_ptr<FakeVideoEncodeAcceleratorFactory> vea_factory_;
base::TimeTicks first_frame_time_;
OperationalStatus operational_status_;
std::unique_ptr<VideoEncoder> video_encoder_;
std::unique_ptr<VideoFrameFactory> video_frame_factory_;
};
// Tests that the encoder outputs encoded frames, and also responds to frame
// size changes. See media/cast/receiver/video_decoder_unittest.cc for a
// complete encode/decode cycle of varied frame sizes that actually checks the
// frame content.
TEST_P(VideoEncoderTest, EncodesVariedFrameSizes) {
CreateEncoder();
SetVEAFactoryAutoRespond(true);
ExpectVEAResponseForExternalVideoEncoder(0);
std::vector<gfx::Size> frame_sizes;
frame_sizes.push_back(gfx::Size(128, 72));
frame_sizes.push_back(gfx::Size(64, 36)); // Shrink both dimensions.
frame_sizes.push_back(gfx::Size(30, 20)); // Shrink both dimensions again.
frame_sizes.push_back(gfx::Size(20, 30)); // Same area.
frame_sizes.push_back(gfx::Size(60, 40)); // Grow both dimensions.
frame_sizes.push_back(gfx::Size(58, 40)); // Shrink only one dimension.
frame_sizes.push_back(gfx::Size(58, 38)); // Shrink the other dimension.
frame_sizes.push_back(gfx::Size(32, 18)); // Shrink both dimensions again.
frame_sizes.push_back(gfx::Size(34, 18)); // Grow only one dimension.
frame_sizes.push_back(gfx::Size(34, 20)); // Grow the other dimension.
frame_sizes.push_back(gfx::Size(192, 108)); // Grow both dimensions again.
int count_frames_accepted = 0;
using EncodedFrames = std::vector<std::unique_ptr<SenderEncodedFrame>>;
EncodedFrames encoded_frames;
base::WeakPtrFactory<EncodedFrames> encoded_frames_weak_factory(
&encoded_frames);
// Encode several frames at each size. For encoders with a resize delay,
// expect the first one or more frames are dropped while the encoder
// re-inits. For all encoders, expect one key frame followed by all delta
// frames.
for (const auto& frame_size : frame_sizes) {
// Encode frames until there are four consecutive frames successfully
// encoded.
while (encoded_frames.size() <= 4 ||
!(encoded_frames[encoded_frames.size() - 1] &&
encoded_frames[encoded_frames.size() - 2] &&
encoded_frames[encoded_frames.size() - 3] &&
encoded_frames[encoded_frames.size() - 4])) {
auto video_frame = CreateTestVideoFrame(frame_size);
const base::TimeTicks reference_time = Now();
const base::TimeDelta timestamp = video_frame->timestamp();
const bool accepted_request = video_encoder()->EncodeVideoFrame(
std::move(video_frame), reference_time,
base::BindOnce(
[](base::WeakPtr<EncodedFrames> encoded_frames,
RtpTimeTicks expected_rtp_timestamp,
base::TimeTicks expected_reference_time,
std::unique_ptr<SenderEncodedFrame> encoded_frame) {
if (!encoded_frames) {
return;
}
if (encoded_frame) {
EXPECT_EQ(expected_rtp_timestamp,
encoded_frame->rtp_timestamp);
EXPECT_EQ(expected_reference_time,
encoded_frame->reference_time);
}
encoded_frames->emplace_back(std::move(encoded_frame));
},
encoded_frames_weak_factory.GetWeakPtr(),
ToRtpTimeTicks(timestamp, kVideoFrequency), reference_time));
if (accepted_request) {
++count_frames_accepted;
}
if (!is_testing_external_video_encoder()) {
EXPECT_TRUE(accepted_request);
}
RunTasksAndAdvanceClock();
}
}
// Flush the encoder and wait until all queued frames have been delivered.
// Then, shut it all down.
video_encoder()->EmitFrames();
while (encoded_frames.size() < static_cast<size_t>(count_frames_accepted))
RunTasksAndAdvanceClock();
DestroyEncoder();
RunTasksAndAdvanceClock();
encoded_frames_weak_factory.InvalidateWeakPtrs();
// Walk through the encoded frames and check that they have reasonable frame
// IDs, dependency relationships, etc. provided.
FrameId last_key_frame_id;
for (const std::unique_ptr<SenderEncodedFrame>& encoded_frame :
encoded_frames) {
if (!encoded_frame) {
continue;
}
if (encoded_frame->dependency ==
openscreen::cast::EncodedFrame::Dependency::kKeyFrame) {
EXPECT_EQ(encoded_frame->frame_id, encoded_frame->referenced_frame_id);
last_key_frame_id = encoded_frame->frame_id;
} else {
EXPECT_EQ(openscreen::cast::EncodedFrame::Dependency::kDependent,
encoded_frame->dependency);
EXPECT_GT(encoded_frame->frame_id, encoded_frame->referenced_frame_id);
// There must always be a KEY frame before any DEPENDENT ones.
ASSERT_FALSE(last_key_frame_id.is_null());
EXPECT_GE(encoded_frame->referenced_frame_id, last_key_frame_id);
}
EXPECT_FALSE(encoded_frame->data.empty());
// The utilization metrics are computed for all but the Mac Video Toolbox
// encoder.
if (is_testing_software_vp8_encoder()) {
ASSERT_TRUE(std::isfinite(encoded_frame->encoder_utilization));
EXPECT_LE(0.0, encoded_frame->encoder_utilization);
EXPECT_LE(0, encoded_frame->encoder_bitrate);
ASSERT_TRUE(std::isfinite(encoded_frame->lossiness));
EXPECT_LE(0.0, encoded_frame->lossiness);
}
}
}
// Verify that everything goes well even if ExternalVideoEncoder is destroyed
// before it has a chance to receive the VEA creation callback. For all other
// encoders, this tests that the encoder can be safely destroyed before the task
// is run that delivers the first EncodedFrame.
TEST_P(VideoEncoderTest, CanBeDestroyedBeforeVEAIsCreated) {
CreateEncoder();
// Send a frame to spawn creation of the ExternalVideoEncoder instance.
video_encoder()->EncodeVideoFrame(
CreateTestVideoFrame(gfx::Size(128, 72)), Now(),
base::BindOnce([](std::unique_ptr<SenderEncodedFrame> encoded_frame) {}));
// Destroy the encoder, and confirm the VEA Factory did not respond yet.
DestroyEncoder();
ExpectVEAResponseForExternalVideoEncoder(0);
// Allow the VEA Factory to respond by running the creation callback. When
// the task runs, it will be a no-op since the weak pointers to the
// ExternalVideoEncoder were invalidated.
SetVEAFactoryAutoRespond(true);
RunTasksAndAdvanceClock();
ExpectVEAResponseForExternalVideoEncoder(1);
}
namespace {
std::vector<std::pair<Codec, bool>> DetermineEncodersToTest() {
std::vector<std::pair<Codec, bool>> values;
// Fake encoder.
values.emplace_back(Codec::kVideoFake, false);
// Software VP8 encoder.
values.emplace_back(Codec::kVideoVp8, false);
// Hardware-accelerated encoder (faked).
values.emplace_back(Codec::kVideoVp8, true);
#if BUILDFLAG(IS_MAC)
// VideoToolbox encoder (when VideoToolbox is present).
FrameSenderConfig video_config = GetDefaultVideoSenderConfig();
video_config.use_hardware_encoder = true;
video_config.codec = Codec::kVideoH264;
if (H264VideoToolboxEncoder::IsSupported(video_config)) {
values.emplace_back(Codec::kVideoH264, true);
}
#endif
return values;
}
} // namespace
INSTANTIATE_TEST_SUITE_P(All,
VideoEncoderTest,
::testing::ValuesIn(DetermineEncodersToTest()));
} // namespace cast
} // namespace media