blob: 1646bba8eba1679de29435bfd58acfadfde7ac1e [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cast/sender/video_encoder.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ref_counted.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/rtp_time.h"
#include "media/cast/sender/fake_video_encode_accelerator_factory.h"
#include "media/cast/sender/video_frame_factory.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/video_utility.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_MACOSX)
#include "media/cast/sender/h264_vt_encoder.h"
#endif
namespace cobalt {
namespace media {
namespace cast {
class VideoEncoderTest
: public ::testing::TestWithParam<std::pair<Codec, bool>> {
protected:
VideoEncoderTest()
: testing_clock_(new base::SimpleTestTickClock()),
task_runner_(new FakeSingleThreadTaskRunner(testing_clock_)),
cast_environment_(new CastEnvironment(
std::unique_ptr<base::TickClock>(testing_clock_), task_runner_,
task_runner_, task_runner_)),
video_config_(GetDefaultVideoSenderConfig()),
operational_status_(STATUS_UNINITIALIZED),
count_frames_delivered_(0) {
testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks());
first_frame_time_ = testing_clock_->NowTicks();
}
~VideoEncoderTest() override {}
void SetUp() final {
video_config_.codec = GetParam().first;
video_config_.use_external_encoder = GetParam().second;
if (video_config_.use_external_encoder)
vea_factory_.reset(new 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::Bind(&VideoEncoderTest::OnOperationalStatusChange,
base::Unretained(this)),
base::Bind(
&FakeVideoEncodeAcceleratorFactory::CreateVideoEncodeAccelerator,
base::Unretained(vea_factory_.get())),
base::Bind(&FakeVideoEncodeAcceleratorFactory::CreateSharedMemory,
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_VIDEO_VP8 &&
!video_config_.use_external_encoder;
}
bool is_testing_video_toolbox_encoder() const {
return
#if defined(OS_MACOSX)
(!video_config_.use_external_encoder &&
H264VideoToolboxEncoder::IsSupported(video_config_)) ||
#endif
false;
}
bool is_testing_platform_encoder() const {
return video_config_.use_external_encoder ||
is_testing_video_toolbox_encoder();
}
bool encoder_has_resize_delay() const {
return is_testing_platform_encoder() && !is_testing_video_toolbox_encoder();
}
VideoEncoder* video_encoder() const { return video_encoder_.get(); }
void DestroyEncoder() { video_encoder_.reset(); }
base::TimeTicks Now() const { return testing_clock_->NowTicks(); }
void RunTasksAndAdvanceClock() const {
DCHECK_GT(video_config_.max_frame_rate, 0);
const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
1000000.0 / video_config_.max_frame_rate);
#if defined(OS_MACOSX)
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);
}
int count_frames_delivered() const { return count_frames_delivered_; }
void WaitForAllFramesToBeDelivered(int total_expected) const {
video_encoder_->EmitFrames();
while (count_frames_delivered_ < total_expected) RunTasksAndAdvanceClock();
}
// 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_)
frame = video_frame_factory_->MaybeCreateFrame(size, timestamp);
if (!frame) {
frame = media::VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size,
gfx::Rect(size), size, timestamp);
}
PopulateVideoFrame(frame.get(), 123);
return frame;
}
// Requests encoding the |video_frame| and has the resulting frame delivered
// via a callback that checks for expected results. Returns false if the
// encoder rejected the request.
bool EncodeAndCheckDelivery(
const scoped_refptr<media::VideoFrame>& video_frame, FrameId frame_id,
FrameId reference_frame_id) {
return video_encoder_->EncodeVideoFrame(
video_frame, Now(),
base::Bind(&VideoEncoderTest::DeliverEncodedVideoFrame,
base::Unretained(this), frame_id, reference_frame_id,
RtpTimeTicks::FromTimeDelta(video_frame->timestamp(),
kVideoFrequency),
Now()));
}
// 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 ExpectVEAResponsesForExternalVideoEncoder(int vea_response_count,
int shm_response_count) const {
if (!vea_factory_) return;
EXPECT_EQ(vea_response_count, vea_factory_->vea_response_count());
EXPECT_EQ(shm_response_count, vea_factory_->shm_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();
}
// Checks that |encoded_frame| matches expected values. This is the method
// bound in the callback returned from EncodeAndCheckDelivery().
void DeliverEncodedVideoFrame(
FrameId expected_frame_id, FrameId expected_last_referenced_frame_id,
RtpTimeTicks expected_rtp_timestamp,
const base::TimeTicks& expected_reference_time,
std::unique_ptr<SenderEncodedFrame> encoded_frame) {
EXPECT_TRUE(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
EXPECT_EQ(expected_frame_id, encoded_frame->frame_id);
EXPECT_EQ(expected_rtp_timestamp, encoded_frame->rtp_timestamp);
EXPECT_EQ(expected_reference_time, encoded_frame->reference_time);
// The platform encoders are "black boxes" and may choose to vend key frames
// and/or empty data at any time. The software encoders, however, should
// strictly adhere to expected behavior.
if (is_testing_platform_encoder()) {
const bool expected_key_frame =
expected_frame_id == expected_last_referenced_frame_id;
const bool have_key_frame =
encoded_frame->dependency == EncodedFrame::KEY;
EXPECT_EQ(have_key_frame,
encoded_frame->frame_id == encoded_frame->referenced_frame_id);
LOG_IF(WARNING, expected_key_frame != have_key_frame)
<< "Platform encoder chose to emit a "
<< (have_key_frame ? "key" : "delta")
<< " frame instead of the expected kind @ frame_id="
<< encoded_frame->frame_id;
LOG_IF(WARNING, encoded_frame->data.empty())
<< "Platform encoder returned an empty frame @ frame_id="
<< encoded_frame->frame_id;
} else {
if (expected_frame_id != expected_last_referenced_frame_id) {
EXPECT_EQ(EncodedFrame::DEPENDENT, encoded_frame->dependency);
} else if (video_config_.video_codec_params
.max_number_of_video_buffers_used == 1) {
EXPECT_EQ(EncodedFrame::KEY, encoded_frame->dependency);
}
EXPECT_EQ(expected_last_referenced_frame_id,
encoded_frame->referenced_frame_id);
EXPECT_FALSE(encoded_frame->data.empty());
ASSERT_TRUE(std::isfinite(encoded_frame->encoder_utilization));
EXPECT_LE(0.0, encoded_frame->encoder_utilization);
ASSERT_TRUE(std::isfinite(encoded_frame->lossy_utilization));
EXPECT_LE(0.0, encoded_frame->lossy_utilization);
}
++count_frames_delivered_;
}
base::SimpleTestTickClock* const testing_clock_; // Owned by CastEnvironment.
const scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
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_;
int count_frames_delivered_;
DISALLOW_COPY_AND_ASSIGN(VideoEncoderTest);
};
// A simple test to encode three frames of video, expecting to see one key frame
// followed by two delta frames.
TEST_P(VideoEncoderTest, GeneratesKeyFrameThenOnlyDeltaFrames) {
CreateEncoder();
SetVEAFactoryAutoRespond(true);
EXPECT_EQ(0, count_frames_delivered());
ExpectVEAResponsesForExternalVideoEncoder(0, 0);
FrameId frame_id = FrameId::first();
FrameId reference_frame_id = FrameId::first();
const gfx::Size frame_size(1280, 720);
// Some encoders drop one or more frames initially while the encoder
// initializes. Then, for all encoders, expect one key frame is delivered.
bool accepted_first_frame = false;
do {
accepted_first_frame = EncodeAndCheckDelivery(
CreateTestVideoFrame(frame_size), frame_id, reference_frame_id);
if (!encoder_has_resize_delay()) EXPECT_TRUE(accepted_first_frame);
RunTasksAndAdvanceClock();
} while (!accepted_first_frame);
ExpectVEAResponsesForExternalVideoEncoder(1, 3);
// Expect the remaining frames are encoded as delta frames.
for (++frame_id; frame_id < FrameId::first() + 3;
++frame_id, ++reference_frame_id) {
EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
frame_id, reference_frame_id));
RunTasksAndAdvanceClock();
}
WaitForAllFramesToBeDelivered(3);
ExpectVEAResponsesForExternalVideoEncoder(1, 3);
}
// Tests that the encoder continues to output EncodedFrames as the 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);
EXPECT_EQ(0, count_frames_delivered());
ExpectVEAResponsesForExternalVideoEncoder(0, 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.
FrameId frame_id = FrameId::first();
// Encode one frame at each size. For encoders with a resize delay, except no
// frames to be delivered since each frame size change will sprun
// re-initialization of the underlying encoder. Otherwise expect all key
// frames to come out.
for (const auto& frame_size : frame_sizes) {
EXPECT_EQ(!encoder_has_resize_delay(),
EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size), frame_id,
frame_id));
RunTasksAndAdvanceClock();
if (!encoder_has_resize_delay()) ++frame_id;
}
// Encode three frames at each size. For encoders with a resize delay, expect
// the first one or more frames are dropped while the encoder re-inits. Then,
// for all encoders, expect one key frame followed by all delta frames.
for (const auto& frame_size : frame_sizes) {
bool accepted_first_frame = false;
do {
accepted_first_frame = EncodeAndCheckDelivery(
CreateTestVideoFrame(frame_size), frame_id, frame_id);
if (!encoder_has_resize_delay()) EXPECT_TRUE(accepted_first_frame);
RunTasksAndAdvanceClock();
} while (!accepted_first_frame);
++frame_id;
for (int i = 1; i < 3; ++i, ++frame_id) {
EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
frame_id, frame_id - 1));
RunTasksAndAdvanceClock();
}
}
WaitForAllFramesToBeDelivered(3 * frame_sizes.size());
ExpectVEAResponsesForExternalVideoEncoder(2 * frame_sizes.size(),
6 * frame_sizes.size());
}
// 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.
EncodeAndCheckDelivery(CreateTestVideoFrame(gfx::Size(128, 72)),
FrameId::first(), FrameId::first());
// Destroy the encoder, and confirm the VEA Factory did not respond yet.
DestroyEncoder();
ExpectVEAResponsesForExternalVideoEncoder(0, 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();
ExpectVEAResponsesForExternalVideoEncoder(1, 0);
}
namespace {
std::vector<std::pair<Codec, bool>> DetermineEncodersToTest() {
std::vector<std::pair<Codec, bool>> values;
// Fake encoder.
values.push_back(std::make_pair(CODEC_VIDEO_FAKE, false));
// Software VP8 encoder.
values.push_back(std::make_pair(CODEC_VIDEO_VP8, false));
// Hardware-accelerated encoder (faked).
values.push_back(std::make_pair(CODEC_VIDEO_VP8, true));
#if defined(OS_MACOSX)
// VideoToolbox encoder (when VideoToolbox is present).
FrameSenderConfig video_config = GetDefaultVideoSenderConfig();
video_config.use_external_encoder = false;
video_config.codec = CODEC_VIDEO_H264;
if (H264VideoToolboxEncoder::IsSupported(video_config))
values.push_back(std::make_pair(CODEC_VIDEO_H264, false));
#endif
return values;
}
} // namespace
INSTANTIATE_TEST_CASE_P(, VideoEncoderTest,
::testing::ValuesIn(DetermineEncodersToTest()));
} // namespace cast
} // namespace media
} // namespace cobalt