This source file includes following definitions.
- ParseAndReadTestStreamData
- seen_idr_
- ProcessStreamBuffer
- seen_keyframe_
- ProcessStreamBuffer
- Create
- has_encoder
- encoded_stream_size_
- CreateEncoder
- DestroyEncoder
- RequireBitstreamBuffers
- BitstreamBufferReady
- NotifyError
- SetState
- SetInitialConfiguration
- InputNoLongerNeededCallback
- PrepareInputFrame
- FeedEncoderWithInputs
- FeedEncoderWithOutput
- FlushEncoder
- HandleEncodedFrame
- ChecksAtFinish
- TEST_P
- main
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/memory/scoped_vector.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "content/common/gpu/media/exynos_video_encode_accelerator.h"
#include "content/common/gpu/media/video_accelerator_unittest_helpers.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/test_data_util.h"
#include "media/filters/h264_parser.h"
#include "media/video/video_encode_accelerator.h"
#include "testing/gtest/include/gtest/gtest.h"
using media::VideoEncodeAccelerator;
namespace content {
namespace {
const media::VideoFrame::Format kInputFormat = media::VideoFrame::I420;
const unsigned int kNumOutputBuffers = 4;
const unsigned int kNumExtraInputFrames = 4;
const unsigned int kMaxKeyframeDelay = 4;
const unsigned int kMaxFrameNum =
std::numeric_limits<unsigned int>::max() - kMaxKeyframeDelay;
const uint32 kDefaultBitrate = 2000000;
const double kBitrateTolerance = 0.1;
const uint32 kDefaultFPS = 30;
const char* g_default_in_filename = "sync_192p20_frames.yuv";
const char* g_default_in_parameters = ":320:192:1:out.h264:200000";
base::FilePath::StringType* g_test_stream_data;
struct TestStream {
TestStream() : requested_bitrate(0) {}
~TestStream() {}
gfx::Size size;
base::MemoryMappedFile input_file;
media::VideoCodecProfile requested_profile;
std::string out_filename;
unsigned int requested_bitrate;
};
static void ParseAndReadTestStreamData(const base::FilePath::StringType& data,
TestStream* test_stream) {
std::vector<base::FilePath::StringType> fields;
base::SplitString(data, ':', &fields);
CHECK_GE(fields.size(), 4U) << data;
CHECK_LE(fields.size(), 6U) << data;
base::FilePath::StringType filename = fields[0];
int width, height;
CHECK(base::StringToInt(fields[1], &width));
CHECK(base::StringToInt(fields[2], &height));
test_stream->size = gfx::Size(width, height);
CHECK(!test_stream->size.IsEmpty());
int profile;
CHECK(base::StringToInt(fields[3], &profile));
CHECK_GT(profile, media::VIDEO_CODEC_PROFILE_UNKNOWN);
CHECK_LE(profile, media::VIDEO_CODEC_PROFILE_MAX);
test_stream->requested_profile =
static_cast<media::VideoCodecProfile>(profile);
if (fields.size() >= 5 && !fields[4].empty())
test_stream->out_filename = fields[4];
if (fields.size() >= 6 && !fields[5].empty())
CHECK(base::StringToUint(fields[5], &test_stream->requested_bitrate));
CHECK(test_stream->input_file.Initialize(base::FilePath(filename)));
}
enum ClientState {
CS_CREATED,
CS_ENCODER_SET,
CS_INITIALIZED,
CS_ENCODING,
CS_FINISHING,
CS_FINISHED,
CS_ERROR,
};
class StreamValidator {
public:
typedef base::Callback<bool(bool)> FrameFoundCallback;
virtual ~StreamValidator() {}
static scoped_ptr<StreamValidator> Create(media::VideoCodecProfile profile,
const FrameFoundCallback& frame_cb);
virtual void ProcessStreamBuffer(const uint8* stream, size_t size) = 0;
protected:
explicit StreamValidator(const FrameFoundCallback& frame_cb)
: frame_cb_(frame_cb) {}
FrameFoundCallback frame_cb_;
};
class H264Validator : public StreamValidator {
public:
explicit H264Validator(const FrameFoundCallback& frame_cb)
: StreamValidator(frame_cb),
seen_sps_(false),
seen_pps_(false),
seen_idr_(false) {}
void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE;
private:
bool seen_sps_;
bool seen_pps_;
bool seen_idr_;
};
void H264Validator::ProcessStreamBuffer(const uint8* stream, size_t size) {
media::H264Parser h264_parser;
h264_parser.SetStream(stream, size);
while (1) {
media::H264NALU nalu;
media::H264Parser::Result result;
result = h264_parser.AdvanceToNextNALU(&nalu);
if (result == media::H264Parser::kEOStream)
break;
ASSERT_EQ(result, media::H264Parser::kOk);
bool keyframe = false;
switch (nalu.nal_unit_type) {
case media::H264NALU::kIDRSlice:
ASSERT_TRUE(seen_sps_);
ASSERT_TRUE(seen_pps_);
seen_idr_ = keyframe = true;
case media::H264NALU::kNonIDRSlice:
ASSERT_TRUE(seen_idr_);
if (!frame_cb_.Run(keyframe))
return;
break;
case media::H264NALU::kSPS:
seen_sps_ = true;
break;
case media::H264NALU::kPPS:
ASSERT_TRUE(seen_sps_);
seen_pps_ = true;
break;
default:
break;
}
}
}
class VP8Validator : public StreamValidator {
public:
explicit VP8Validator(const FrameFoundCallback& frame_cb)
: StreamValidator(frame_cb),
seen_keyframe_(false) {}
void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE;
private:
bool seen_keyframe_;
};
void VP8Validator::ProcessStreamBuffer(const uint8* stream, size_t size) {
bool keyframe = !(stream[0] & 0x01);
if (keyframe)
seen_keyframe_ = true;
EXPECT_TRUE(seen_keyframe_);
frame_cb_.Run(keyframe);
}
scoped_ptr<StreamValidator> StreamValidator::Create(
media::VideoCodecProfile profile,
const FrameFoundCallback& frame_cb) {
scoped_ptr<StreamValidator> validator;
if (profile >= media::H264PROFILE_MIN &&
profile <= media::H264PROFILE_MAX) {
validator.reset(new H264Validator(frame_cb));
} else if (profile >= media::VP8PROFILE_MIN &&
profile <= media::VP8PROFILE_MAX) {
validator.reset(new VP8Validator(frame_cb));
} else {
LOG(FATAL) << "Unsupported profile: " << profile;
}
return validator.Pass();
}
class VEAClient : public VideoEncodeAccelerator::Client {
public:
VEAClient(const TestStream& test_stream,
ClientStateNotification<ClientState>* note,
bool save_to_file,
unsigned int keyframe_period,
bool force_bitrate);
virtual ~VEAClient();
void CreateEncoder();
void DestroyEncoder();
void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) OVERRIDE;
void BitstreamBufferReady(int32 bitstream_buffer_id,
size_t payload_size,
bool key_frame) OVERRIDE;
void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE;
private:
bool has_encoder() { return encoder_.get(); }
void SetState(ClientState new_state);
void SetInitialConfiguration();
void InputNoLongerNeededCallback(int32 input_id);
void FeedEncoderWithInputs();
void FeedEncoderWithOutput(base::SharedMemory* shm);
void FlushEncoder();
bool HandleEncodedFrame(bool keyframe);
void ChecksAtFinish();
scoped_refptr<media::VideoFrame> PrepareInputFrame(off_t position);
ClientState state_;
scoped_ptr<VideoEncodeAccelerator> encoder_;
const TestStream& test_stream_;
ClientStateNotification<ClientState>* note_;
std::set<int32> inputs_at_client_;
int32 next_input_id_;
typedef std::map<int32, base::SharedMemory*> IdToSHM;
ScopedVector<base::SharedMemory> output_shms_;
IdToSHM output_buffers_at_client_;
int32 next_output_buffer_id_;
off_t pos_in_input_stream_;
size_t input_buffer_size_;
gfx::Size input_coded_size_;
unsigned int num_required_input_buffers_;
size_t output_buffer_size_;
unsigned int num_frames_in_stream_;
unsigned int num_encoded_frames_;
bool seen_keyframe_in_this_buffer_;
bool save_to_file_;
const unsigned int keyframe_period_;
unsigned int keyframe_requested_at_;
bool force_bitrate_;
size_t encoded_stream_size_;
scoped_ptr<StreamValidator> validator_;
base::ThreadChecker thread_checker_;
};
VEAClient::VEAClient(const TestStream& test_stream,
ClientStateNotification<ClientState>* note,
bool save_to_file,
unsigned int keyframe_period,
bool force_bitrate)
: state_(CS_CREATED),
test_stream_(test_stream),
note_(note),
next_input_id_(1),
next_output_buffer_id_(0),
pos_in_input_stream_(0),
input_buffer_size_(0),
num_required_input_buffers_(0),
output_buffer_size_(0),
num_frames_in_stream_(0),
num_encoded_frames_(0),
seen_keyframe_in_this_buffer_(false),
save_to_file_(save_to_file),
keyframe_period_(keyframe_period),
keyframe_requested_at_(kMaxFrameNum),
force_bitrate_(force_bitrate),
encoded_stream_size_(0) {
if (keyframe_period_)
CHECK_LT(kMaxKeyframeDelay, keyframe_period_);
validator_ = StreamValidator::Create(
test_stream_.requested_profile,
base::Bind(&VEAClient::HandleEncodedFrame, base::Unretained(this)));
CHECK(validator_.get());
if (save_to_file_) {
CHECK(!test_stream_.out_filename.empty());
base::FilePath out_filename(test_stream_.out_filename);
EXPECT_EQ(0, base::WriteFile(out_filename, NULL, 0));
}
thread_checker_.DetachFromThread();
}
VEAClient::~VEAClient() { CHECK(!has_encoder()); }
void VEAClient::CreateEncoder() {
DCHECK(thread_checker_.CalledOnValidThread());
CHECK(!has_encoder());
encoder_.reset(new ExynosVideoEncodeAccelerator());
SetState(CS_ENCODER_SET);
DVLOG(1) << "Profile: " << test_stream_.requested_profile
<< ", requested bitrate: " << test_stream_.requested_bitrate;
if (!encoder_->Initialize(kInputFormat,
test_stream_.size,
test_stream_.requested_profile,
test_stream_.requested_bitrate,
this)) {
DLOG(ERROR) << "VideoEncodeAccelerator::Initialize() failed";
SetState(CS_ERROR);
return;
}
SetInitialConfiguration();
SetState(CS_INITIALIZED);
}
void VEAClient::DestroyEncoder() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!has_encoder())
return;
encoder_.release()->Destroy();
}
void VEAClient::RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_size) {
DCHECK(thread_checker_.CalledOnValidThread());
ASSERT_EQ(state_, CS_INITIALIZED);
SetState(CS_ENCODING);
input_coded_size_ = input_coded_size;
ASSERT_EQ(input_coded_size_, test_stream_.size);
input_buffer_size_ = media::VideoFrame::AllocationSize(kInputFormat,
input_coded_size_);
CHECK_GT(input_buffer_size_, 0UL);
ASSERT_EQ(input_buffer_size_ & 63, 0)
<< "Frame size has to be a multiple of 64 bytes";
ASSERT_EQ(reinterpret_cast<off_t>(test_stream_.input_file.data()) & 63, 0)
<< "Mapped file should be mapped at a 64 byte boundary";
num_required_input_buffers_ = input_count;
ASSERT_GT(num_required_input_buffers_, 0UL);
num_frames_in_stream_ = test_stream_.input_file.length() / input_buffer_size_;
CHECK_GT(num_frames_in_stream_, 0UL);
CHECK_LE(num_frames_in_stream_, kMaxFrameNum);
CHECK_EQ(num_frames_in_stream_ * input_buffer_size_,
test_stream_.input_file.length());
output_buffer_size_ = output_size;
ASSERT_GT(output_buffer_size_, 0UL);
for (unsigned int i = 0; i < kNumOutputBuffers; ++i) {
base::SharedMemory* shm = new base::SharedMemory();
CHECK(shm->CreateAndMapAnonymous(output_buffer_size_));
output_shms_.push_back(shm);
FeedEncoderWithOutput(shm);
}
FeedEncoderWithInputs();
}
void VEAClient::BitstreamBufferReady(int32 bitstream_buffer_id,
size_t payload_size,
bool key_frame) {
DCHECK(thread_checker_.CalledOnValidThread());
ASSERT_LE(payload_size, output_buffer_size_);
IdToSHM::iterator it = output_buffers_at_client_.find(bitstream_buffer_id);
ASSERT_NE(it, output_buffers_at_client_.end());
base::SharedMemory* shm = it->second;
output_buffers_at_client_.erase(it);
if (state_ == CS_FINISHED)
return;
encoded_stream_size_ += payload_size;
const uint8* stream_ptr = static_cast<const uint8*>(shm->memory());
if (payload_size > 0)
validator_->ProcessStreamBuffer(stream_ptr, payload_size);
EXPECT_EQ(key_frame, seen_keyframe_in_this_buffer_);
seen_keyframe_in_this_buffer_ = false;
if (save_to_file_) {
int size = base::checked_cast<int>(payload_size);
EXPECT_EQ(base::AppendToFile(
base::FilePath::FromUTF8Unsafe(test_stream_.out_filename),
static_cast<char*>(shm->memory()),
size),
size);
}
FeedEncoderWithOutput(shm);
}
void VEAClient::NotifyError(VideoEncodeAccelerator::Error error) {
DCHECK(thread_checker_.CalledOnValidThread());
SetState(CS_ERROR);
}
void VEAClient::SetState(ClientState new_state) {
note_->Notify(new_state);
state_ = new_state;
}
void VEAClient::SetInitialConfiguration() {
if (force_bitrate_) {
CHECK_GT(test_stream_.requested_bitrate, 0UL);
encoder_->RequestEncodingParametersChange(test_stream_.requested_bitrate,
kDefaultFPS);
}
}
void VEAClient::InputNoLongerNeededCallback(int32 input_id) {
std::set<int32>::iterator it = inputs_at_client_.find(input_id);
ASSERT_NE(it, inputs_at_client_.end());
inputs_at_client_.erase(it);
FeedEncoderWithInputs();
}
scoped_refptr<media::VideoFrame> VEAClient::PrepareInputFrame(off_t position) {
CHECK_LE(position + input_buffer_size_, test_stream_.input_file.length());
uint8* frame_data =
const_cast<uint8*>(test_stream_.input_file.data() + position);
scoped_refptr<media::VideoFrame> frame =
media::VideoFrame::WrapExternalYuvData(
kInputFormat,
input_coded_size_,
gfx::Rect(test_stream_.size),
test_stream_.size,
input_coded_size_.width(),
input_coded_size_.width() / 2,
input_coded_size_.width() / 2,
frame_data,
frame_data + input_coded_size_.GetArea(),
frame_data + (input_coded_size_.GetArea() * 5 / 4),
base::TimeDelta(),
media::BindToCurrentLoop(
base::Bind(&VEAClient::InputNoLongerNeededCallback,
base::Unretained(this),
next_input_id_)));
CHECK(inputs_at_client_.insert(next_input_id_).second);
++next_input_id_;
return frame;
}
void VEAClient::FeedEncoderWithInputs() {
if (!has_encoder())
return;
if (state_ != CS_ENCODING)
return;
while (inputs_at_client_.size() <
num_required_input_buffers_ + kNumExtraInputFrames) {
size_t bytes_left = test_stream_.input_file.length() - pos_in_input_stream_;
if (bytes_left < input_buffer_size_) {
DCHECK_EQ(bytes_left, 0UL);
FlushEncoder();
return;
}
bool force_keyframe = false;
if (keyframe_period_ && next_input_id_ % keyframe_period_ == 0) {
keyframe_requested_at_ = next_input_id_;
force_keyframe = true;
}
scoped_refptr<media::VideoFrame> video_frame =
PrepareInputFrame(pos_in_input_stream_);
pos_in_input_stream_ += input_buffer_size_;
encoder_->Encode(video_frame, force_keyframe);
}
}
void VEAClient::FeedEncoderWithOutput(base::SharedMemory* shm) {
if (!has_encoder())
return;
if (state_ != CS_ENCODING && state_ != CS_FINISHING)
return;
base::SharedMemoryHandle dup_handle;
CHECK(shm->ShareToProcess(base::Process::Current().handle(), &dup_handle));
media::BitstreamBuffer bitstream_buffer(
next_output_buffer_id_++, dup_handle, output_buffer_size_);
CHECK(output_buffers_at_client_.insert(std::make_pair(bitstream_buffer.id(),
shm)).second);
encoder_->UseOutputBitstreamBuffer(bitstream_buffer);
}
void VEAClient::FlushEncoder() {
ASSERT_EQ(state_, CS_ENCODING);
SetState(CS_FINISHING);
for (unsigned int i = 0; i < num_required_input_buffers_; ++i) {
scoped_refptr<media::VideoFrame> frame = PrepareInputFrame(0);
encoder_->Encode(frame, false);
}
}
bool VEAClient::HandleEncodedFrame(bool keyframe) {
CHECK_LE(num_encoded_frames_, num_frames_in_stream_);
++num_encoded_frames_;
if (keyframe) {
keyframe_requested_at_ = kMaxFrameNum;
seen_keyframe_in_this_buffer_ = true;
}
EXPECT_LE(num_encoded_frames_, keyframe_requested_at_ + kMaxKeyframeDelay);
if (num_encoded_frames_ == num_frames_in_stream_) {
ChecksAtFinish();
SetState(CS_FINISHED);
return false;
}
return true;
}
void VEAClient::ChecksAtFinish() {
unsigned int bitrate =
encoded_stream_size_ * 8 * kDefaultFPS / num_frames_in_stream_;
DVLOG(1) << "Final bitrate: " << bitrate
<< " num frames: " << num_frames_in_stream_;
if (force_bitrate_) {
EXPECT_NEAR(bitrate,
test_stream_.requested_bitrate,
kBitrateTolerance * test_stream_.requested_bitrate);
}
}
class VideoEncodeAcceleratorTest
: public ::testing::TestWithParam<Tuple3<bool, int, bool> > {};
TEST_P(VideoEncodeAcceleratorTest, TestSimpleEncode) {
const unsigned int keyframe_period = GetParam().b;
const bool force_bitrate = GetParam().c;
TestStream test_stream;
ParseAndReadTestStreamData(*g_test_stream_data, &test_stream);
const bool save_to_file = GetParam().a && !test_stream.out_filename.empty();
if (test_stream.requested_bitrate == 0)
test_stream.requested_bitrate = kDefaultBitrate;
base::Thread encoder_thread("EncoderThread");
encoder_thread.Start();
ClientStateNotification<ClientState> note;
scoped_ptr<VEAClient> client(new VEAClient(test_stream,
¬e,
save_to_file,
keyframe_period,
force_bitrate));
encoder_thread.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VEAClient::CreateEncoder, base::Unretained(client.get())));
ASSERT_EQ(note.Wait(), CS_ENCODER_SET);
ASSERT_EQ(note.Wait(), CS_INITIALIZED);
ASSERT_EQ(note.Wait(), CS_ENCODING);
ASSERT_EQ(note.Wait(), CS_FINISHING);
ASSERT_EQ(note.Wait(), CS_FINISHED);
encoder_thread.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VEAClient::DestroyEncoder, base::Unretained(client.get())));
encoder_thread.Stop();
}
INSTANTIATE_TEST_CASE_P(SimpleEncode,
VideoEncodeAcceleratorTest,
::testing::Values(MakeTuple(true, 0, false)));
INSTANTIATE_TEST_CASE_P(ForceKeyframes,
VideoEncodeAcceleratorTest,
::testing::Values(MakeTuple(false, 10, false)));
INSTANTIATE_TEST_CASE_P(ForceBitrate,
VideoEncodeAcceleratorTest,
::testing::Values(MakeTuple(false, 0, true)));
}
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
CommandLine::Init(argc, argv);
base::ShadowingAtExitManager at_exit_manager;
scoped_ptr<base::FilePath::StringType> test_stream_data(
new base::FilePath::StringType(
media::GetTestDataFilePath(content::g_default_in_filename).value() +
content::g_default_in_parameters));
content::g_test_stream_data = test_stream_data.get();
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
CHECK(logging::InitLogging(settings));
CommandLine* cmd_line = CommandLine::ForCurrentProcess();
DCHECK(cmd_line);
CommandLine::SwitchMap switches = cmd_line->GetSwitches();
for (CommandLine::SwitchMap::const_iterator it = switches.begin();
it != switches.end();
++it) {
if (it->first == "test_stream_data") {
test_stream_data->assign(it->second.c_str());
continue;
}
if (it->first == "v" || it->first == "vmodule")
continue;
LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second;
}
return RUN_ALL_TESTS();
}