Commit 07326822 authored by David Staessens's avatar David Staessens Committed by Commit Bot

media/gpu/test: Add video_encode_accelerator_tests.

This CL adds a new video_encode_accelerator_tests target, modeled after
the recently introduced video_decode_accelerator_tests. The new tests
make use of the test video encoder framework, that will greatly simplify
the process of writing complex test flows.

Currently only a single testcase is present, but in the future all tests
in the video_encode_accelerator_unittest target should be migrated to the
new encoder test framework.

TEST=./video_encode_accelerator_tests on nocturne

BUG=1045825

Change-Id: I37b7a4d446d91750ac1ddc84e497c7df9499a0c1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2028872
Commit-Queue: David Staessens <dstaessens@chromium.org>
Reviewed-by: default avatarChih-Yu Huang <akahuang@chromium.org>
Reviewed-by: default avatarHirokazu Honda <hiroh@chromium.org>
Reviewed-by: default avatarAlexandre Courbot <acourbot@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747641}
parent cfa590cc
......@@ -545,4 +545,17 @@ if (use_v4l2_codec || use_vaapi) {
"//testing/gtest",
]
}
test("video_encode_accelerator_tests") {
sources = [ "video_encode_accelerator_tests.cc" ]
data = [ "//media/test/data/" ]
deps = [
":buildflags",
"test:helpers",
"test:video_encoder",
"test:video_encoder_test_environment",
"//media:test_support",
"//testing/gtest",
]
}
}
......@@ -132,6 +132,35 @@ static_library("video_player_test_environment") {
]
}
static_library("video_encoder") {
testonly = true
sources = [
"bitstream_helpers.h",
"video_encoder/video_encoder.cc",
"video_encoder/video_encoder.h",
"video_encoder/video_encoder_client.cc",
"video_encoder/video_encoder_client.h",
]
deps = [
":test_helpers",
"//media/gpu",
"//testing/gtest:gtest",
]
}
static_library("video_encoder_test_environment") {
testonly = true
sources = [
"video_encoder/video_encoder_test_environment.cc",
"video_encoder/video_encoder_test_environment.h",
]
data = [ "//media/test/data/" ]
deps = [
":helpers",
"//media/gpu",
]
}
if (use_vaapi || use_v4l2_codec) {
static_library("image_processor") {
testonly = true
......
// Copyright 2020 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.
#ifndef MEDIA_GPU_TEST_BITSTREAM_HELPERS_H_
#define MEDIA_GPU_TEST_BITSTREAM_HELPERS_H_
namespace base {
class UnsafeSharedMemoryRegion;
}
namespace media {
struct BitstreamBufferMetadata;
namespace test {
// This class defines an abstract interface for classes that are interested in
// processing bitstreams (e.g. BitstreamBufferValidator,...).
class BitstreamProcessor {
public:
virtual ~BitstreamProcessor() = default;
// Process the specified bitstream buffer. This can e.g. validate the
// buffer's contents or calculate the buffer's checksum.
virtual void ProcessBitstreamBuffer(
int32_t bitstream_buffer_id,
const BitstreamBufferMetadata& metadata,
const base::UnsafeSharedMemoryRegion* shm) = 0;
// Wait until all currently scheduled bitstream buffers have been processed.
// Returns whether processing was successful.
virtual bool WaitUntilDone() = 0;
};
} // namespace test
} // namespace media
#endif // MEDIA_GPU_TEST_BITSTREAM_HELPERS_H_
// Copyright 2020 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/gpu/test/video_encoder/video_encoder.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "media/gpu/macros.h"
#include "media/gpu/test/video.h"
#include "media/gpu/test/video_encoder/video_encoder_client.h"
namespace media {
namespace test {
namespace {
// Get the name of the specified video encoder |event|.
const char* EventName(VideoEncoder::EncoderEvent event) {
switch (event) {
case VideoEncoder::EncoderEvent::kInitialized:
return "Initialized";
case VideoEncoder::EncoderEvent::kFrameReleased:
return "FrameReleased";
case VideoEncoder::EncoderEvent::kBitstreamReady:
return "BitstreamReady";
case VideoEncoder::EncoderEvent::kFlushing:
return "Flushing";
case VideoEncoder::EncoderEvent::kFlushDone:
return "FlushDone";
default:
return "Unknown";
}
}
// Default timeout used when waiting for events.
constexpr base::TimeDelta kDefaultEventWaitTimeout =
base::TimeDelta::FromSeconds(30);
// Default initial size used for |video_encoder_events_|.
constexpr size_t kDefaultEventListSize = 512;
} // namespace
// static
std::unique_ptr<VideoEncoder> VideoEncoder::Create(
const VideoEncoderClientConfig& config,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors) {
auto video_encoder = base::WrapUnique(new VideoEncoder());
if (!video_encoder->CreateEncoderClient(config,
std::move(bitstream_processors))) {
return nullptr;
}
return video_encoder;
}
VideoEncoder::VideoEncoder()
: event_timeout_(kDefaultEventWaitTimeout),
video_encoder_event_counts_{},
next_unprocessed_event_(0) {
video_encoder_events_.reserve(kDefaultEventListSize);
}
VideoEncoder::~VideoEncoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOGF(4);
encoder_client_.reset();
}
bool VideoEncoder::CreateEncoderClient(
const VideoEncoderClientConfig& config,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(video_encoder_state_.load(), EncoderState::kUninitialized);
DVLOGF(4);
// base::Unretained is safe here as we will never receive callbacks after
// destroying the video encoder, since the video encoder client will be
// destroyed first.
EventCallback event_cb =
base::BindRepeating(&VideoEncoder::NotifyEvent, base::Unretained(this));
encoder_client_ = VideoEncoderClient::Create(
event_cb, std::move(bitstream_processors), config);
if (!encoder_client_) {
VLOGF(1) << "Failed to create video encoder client";
return false;
}
return true;
}
void VideoEncoder::SetEventWaitTimeout(base::TimeDelta timeout) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOGF(4);
event_timeout_ = timeout;
}
bool VideoEncoder::Initialize(const Video* video) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(video_encoder_state_ == EncoderState::kUninitialized ||
video_encoder_state_ == EncoderState::kIdle);
DCHECK(video);
DVLOGF(4);
if (!encoder_client_->Initialize(video))
return false;
// Wait until the video encoder is initialized.
if (!WaitForEvent(EncoderEvent::kInitialized)) {
LOG(ERROR) << "Timeout while initializing video encode accelerator";
return false;
}
video_ = video;
video_encoder_state_ = EncoderState::kIdle;
return true;
}
void VideoEncoder::Encode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(video_encoder_state_.load(), EncoderState::kIdle);
DVLOGF(4);
// Encode until the end of the video.
EncodeUntil(EncoderEvent::kNumEvents, std::numeric_limits<size_t>::max());
}
void VideoEncoder::EncodeUntil(EncoderEvent event, size_t event_count) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(video_encoder_state_.load(), EncoderState::kIdle);
DCHECK(video_);
DVLOGF(4);
// Start encoding the video.
encode_until_ = std::make_pair(event, event_count);
video_encoder_state_ = EncoderState::kEncoding;
encoder_client_->Encode();
}
void VideoEncoder::Flush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOGF(4);
encoder_client_->Flush();
}
VideoEncoder::EncoderState VideoEncoder::GetState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return video_encoder_state_;
}
bool VideoEncoder::WaitForEvent(EncoderEvent event, size_t times) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOGF(4) << "Event ID: " << EventName(event);
if (times == 0)
return true;
base::TimeDelta time_waiting;
base::AutoLock auto_lock(event_lock_);
while (true) {
// Go through the list of events since last wait, looking for the event
// we're interested in.
while (next_unprocessed_event_ < video_encoder_events_.size()) {
if (video_encoder_events_[next_unprocessed_event_++] == event) {
if (--times == 0)
return true;
}
}
// Check whether we've exceeded the maximum time we're allowed to wait.
if (time_waiting >= event_timeout_) {
LOG(ERROR) << "Timeout while waiting for '" << EventName(event)
<< "' event";
return false;
}
const base::TimeTicks start_time = base::TimeTicks::Now();
event_cv_.TimedWait(event_timeout_ - time_waiting);
time_waiting += base::TimeTicks::Now() - start_time;
}
}
bool VideoEncoder::WaitForFlushDone() {
return WaitForEvent(EncoderEvent::kFlushDone);
}
bool VideoEncoder::WaitForFrameReleased(size_t times) {
return WaitForEvent(EncoderEvent::kFrameReleased, times);
}
size_t VideoEncoder::GetEventCount(EncoderEvent event) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AutoLock auto_lock(event_lock_);
return video_encoder_event_counts_[event];
}
bool VideoEncoder::WaitForBitstreamProcessors() {
return !encoder_client_ || encoder_client_->WaitForBitstreamProcessors();
}
size_t VideoEncoder::GetFlushDoneCount() const {
return GetEventCount(EncoderEvent::kFlushDone);
}
size_t VideoEncoder::GetFrameReleasedCount() const {
return GetEventCount(EncoderEvent::kFrameReleased);
}
bool VideoEncoder::NotifyEvent(EncoderEvent event) {
base::AutoLock auto_lock(event_lock_);
if (event == EncoderEvent::kFlushDone) {
video_encoder_state_ = EncoderState::kIdle;
}
video_encoder_events_.push_back(event);
video_encoder_event_counts_[event]++;
event_cv_.Signal();
// Check whether video encoding should be paused after this event.
if (encode_until_.first == event &&
encode_until_.second == video_encoder_event_counts_[event]) {
video_encoder_state_ = EncoderState::kIdle;
return false;
}
return true;
}
} // namespace test
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_H_
#define MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_H_
#include <limits.h>
#include <atomic>
#include <memory>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/sequence_checker.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
namespace media {
namespace test {
class BitstreamProcessor;
class Video;
class VideoEncoderClient;
struct VideoEncoderClientConfig;
// This class provides a framework to build video encode accelerator tests upon.
// It provides methods to control video encoding, and wait for specific events
// to occur.
class VideoEncoder {
public:
// Different video encoder states.
enum class EncoderState { kUninitialized = 0, kIdle, kEncoding };
// The list of events that can be thrown by the video encoder.
enum EncoderEvent {
kInitialized,
kFrameReleased,
kBitstreamReady,
kFlushing,
kFlushDone,
kNumEvents,
};
using EventCallback = base::RepeatingCallback<bool(EncoderEvent)>;
// Create an instance of the video encoder.
static std::unique_ptr<VideoEncoder> Create(
const VideoEncoderClientConfig& config,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors =
{});
// Disallow copy and assign.
VideoEncoder(const VideoEncoder&) = delete;
VideoEncoder& operator=(const VideoEncoder&) = delete;
~VideoEncoder();
// Wait until all processors have finished processing the currently queued
// list of bitstream buffers. Returns whether processing was successful.
bool WaitForBitstreamProcessors();
// Set the maximum time we will wait for an event to finish.
void SetEventWaitTimeout(base::TimeDelta timeout);
// Initialize the video encoder for the specified |video|. The |video| will
// not be owned by the video encoder, the caller should guarantee it outlives
// the video encoder.
bool Initialize(const Video* video);
// Start encoding the video asynchronously.
void Encode();
// Encode the video asynchronously. Automatically pause encoding when the
// specified |event| occurred |event_count| times.
void EncodeUntil(EncoderEvent event, size_t event_count = 1);
// Flush the encoder.
void Flush();
// Get the current state of the video encoder.
EncoderState GetState() const;
// Wait for an event to occur the specified number of times. All events that
// occurred since last calling this function will be taken into account. All
// events with different types will be consumed. Will return false if the
// specified timeout is exceeded while waiting for the events.
bool WaitForEvent(EncoderEvent event, size_t times = 1);
// Helper function to wait for a FlushDone event.
bool WaitForFlushDone();
// Helper function to wait for the specified number of FrameReleased events.
bool WaitForFrameReleased(size_t times);
// Get the number of times the specified event occurred.
size_t GetEventCount(EncoderEvent event) const;
// Helper function to get the number of FlushDone events thrown.
size_t GetFlushDoneCount() const;
// Helper function to get the number of FrameReleased events thrown.
size_t GetFrameReleasedCount() const;
private:
VideoEncoder();
bool CreateEncoderClient(
const VideoEncoderClientConfig& config,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors);
// Notify the video encoder an event has occurred (e.g. bitstream ready).
// Returns whether the encoder client should continue encoding frames.
bool NotifyEvent(EncoderEvent event);
// The video currently being encoded.
const Video* video_ = nullptr;
// The state of the video encoder.
std::atomic<EncoderState> video_encoder_state_{EncoderState::kUninitialized};
// The video encoder client communicating between this class and the hardware
// video encode accelerator.
std::unique_ptr<VideoEncoderClient> encoder_client_;
// The timeout used when waiting for events.
base::TimeDelta event_timeout_;
mutable base::Lock event_lock_;
base::ConditionVariable event_cv_{&event_lock_};
// The list of events thrown by the video encoder client.
std::vector<EncoderEvent> video_encoder_events_ GUARDED_BY(event_lock_);
// The number of times each event has occurred.
size_t video_encoder_event_counts_[EncoderEvent::kNumEvents] GUARDED_BY(
event_lock_);
// The index of the next event to start at, when waiting for events.
size_t next_unprocessed_event_ GUARDED_BY(event_lock_);
// Automatically pause encoding once the video encoder has seen the specified
// number of events occur.
std::pair<EncoderEvent, size_t> encode_until_{
kNumEvents, std::numeric_limits<size_t>::max()};
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace test
} // namespace media
#endif // MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_H_
This diff is collapsed.
// Copyright 2020 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.
#ifndef MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_CLIENT_H_
#define MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_CLIENT_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/threading/thread.h"
#include "media/gpu/test/bitstream_helpers.h"
#include "media/gpu/test/video_encoder/video_encoder.h"
#include "media/video/video_encode_accelerator.h"
namespace gpu {
class gpu_memory_buffer_factory;
}
namespace media {
class Video;
namespace test {
class AlignedDataHelper;
// Video encoder client configuration.
// TODO(dstaessens): Add extra parameters (e.g. h264 output level)
struct VideoEncoderClientConfig {
// The output output profile to be used.
VideoCodecProfile output_profile = VideoCodecProfile::H264PROFILE_MAIN;
// The maximum number of bitstream buffer encodes that can be requested
// without waiting for the result of the previous encodes requests.
size_t max_outstanding_encode_requests = 1;
// The desired bitrate in bits/second.
uint32_t bitrate = 64000;
// The desired framerate in frames/second.
uint32_t framerate = 30.0;
};
// The video encoder client is responsible for the communication between the
// test video encoder and the video encoder. It also communicates with the
// attached bitstream processors. The video encoder client can only have one
// active encoder at any time. To encode a different stream the Destroy() and
// Initialize() functions have to be called to destroy and re-create the
// encoder.
//
// All communication with the encoder is done on the |encoder_client_thread_|,
// so callbacks scheduled by the encoder can be executed asynchronously. This is
// necessary in order not to interrupt the test flow.
class VideoEncoderClient : public VideoEncodeAccelerator::Client {
public:
// Disallow copy and assign.
VideoEncoderClient(const VideoEncoderClient&) = delete;
VideoEncoderClient& operator=(const VideoEncoderClient&) = delete;
~VideoEncoderClient() override;
// Return an instance of the VideoEncoderClient. The
// |gpu_memory_buffer_factory| will not be owned by the encoder client, the
// caller should guarantee it outlives the encoder client. The |event_cb| will
// be called whenever an event occurs (e.g. frame encoded) and should be
// thread-safe.
static std::unique_ptr<VideoEncoderClient> Create(
const VideoEncoder::EventCallback& event_cb,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors,
const VideoEncoderClientConfig& config);
// Initialize the video encode accelerator for the specified |video|.
// Initialization is performed asynchronous, upon completion a 'kInitialized'
// event will be sent to the test encoder.
bool Initialize(const Video* video);
// Start encoding the video stream, encoder should be idle when this function
// is called. This function is non-blocking, for each frame encoded a
// 'kFrameEncoded' event will be sent to the test encoder.
void Encode();
// Flush all scheduled encode tasks. This function is non-blocking, a
// kFlushing/kFlushDone event is sent upon start/finish. The kFlushDone
// event is always sent after all associated kFrameEncoded events.
void Flush();
// Wait until all bitstream processors have finished processing. Returns
// whether processing was successful.
bool WaitForBitstreamProcessors();
// VideoEncodeAccelerator::Client implementation
void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) override;
void BitstreamBufferReady(int32_t bitstream_buffer_id,
const BitstreamBufferMetadata& metadata) override;
void NotifyError(VideoEncodeAccelerator::Error error) override;
void NotifyEncoderInfoChange(const VideoEncoderInfo& info) override;
private:
enum class VideoEncoderClientState {
kUninitialized = 0,
kIdle,
kEncoding,
kFlushing,
};
VideoEncoderClient(
const VideoEncoder::EventCallback& event_cb,
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors,
const VideoEncoderClientConfig& config);
// Destroy the video encoder client.
void Destroy();
// Create a new video |encoder_| on the |encoder_client_thread_|.
void CreateEncoderTask(const Video* video,
bool* success,
base::WaitableEvent* done);
// Destroy the active video |encoder_| on the |encoder_client_thread_|.
void DestroyEncoderTask(base::WaitableEvent* done);
// Start encoding video stream buffers on the |encoder_client_thread_|.
void EncodeTask();
// Instruct the encoder to encode the next video frame on the
// |encoder_client_thread_|.
void EncodeNextFrameTask();
// Instruct the encoder to perform a flush on the |encoder_client_thread_|.
void FlushTask();
// Called by the encoder when a frame has been encoded.
void EncodeDoneTask(base::TimeDelta timestamp);
// Called by the encoder when flushing has completed.
void FlushDoneTask(bool success);
// Fire the specified event.
void FireEvent(VideoEncoder::EncoderEvent event);
// Get the next bitstream buffer id to be used.
int32_t GetNextBitstreamBufferId();
// The callback used to notify the test video encoder of events.
VideoEncoder::EventCallback event_cb_;
// The list of bitstream processors. All decoded bitstream buffers will be
// forwarded to the bitstream processors (e.g. verification of contents).
std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors_;
// The currently active video encode accelerator.
std::unique_ptr<media::VideoEncodeAccelerator> encoder_;
// The video encoder client configuration.
const VideoEncoderClientConfig encoder_client_config_;
// The thread used for all communication with the video encode accelerator.
base::Thread encoder_client_thread_;
// The task runner associated with the |encoder_client_thread_|;
scoped_refptr<base::SingleThreadTaskRunner> encoder_client_task_runner_;
// The current number of outgoing frame encode requests.
size_t num_outstanding_encode_requests_ = 0;
// Encoder client state, should only be accessed on the encoder client thread.
VideoEncoderClientState encoder_client_state_;
// The video being encoded, owned by the video encoder test environment.
const Video* video_ = nullptr;
// Helper used to align data and create frames from the raw video stream.
std::unique_ptr<media::test::AlignedDataHelper> aligned_data_helper_;
// Size of the output buffer requested by the encoder.
size_t output_buffer_size_ = 0u;
// Maps bitstream buffer Id's on the associated memory.
std::map<int32_t, std::unique_ptr<base::UnsafeSharedMemoryRegion>>
bitstream_buffers_;
// Id to be used for the the next bitstream buffer.
int32_t next_bitstream_buffer_id_ = 0;
SEQUENCE_CHECKER(test_sequence_checker_);
SEQUENCE_CHECKER(encoder_client_sequence_checker_);
base::WeakPtr<VideoEncoderClient> weak_this_;
base::WeakPtrFactory<VideoEncoderClient> weak_this_factory_{this};
};
} // namespace test
} // namespace media
#endif // MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_CLIENT_H_
// Copyright 2020 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/gpu/test/video_encoder/video_encoder_test_environment.h"
#include <utility>
#include "gpu/ipc/service/gpu_memory_buffer_factory.h"
#include "media/gpu/test/video.h"
namespace media {
namespace test {
// static
VideoEncoderTestEnvironment* VideoEncoderTestEnvironment::Create(
const base::FilePath& video_path,
const base::FilePath& video_metadata_path,
const base::FilePath& output_folder) {
if (video_path.empty()) {
LOG(ERROR) << "No video specified";
return nullptr;
}
auto video =
std::make_unique<media::test::Video>(video_path, video_metadata_path);
if (!video->Load()) {
LOG(ERROR) << "Failed to load " << video_path;
return nullptr;
}
return new VideoEncoderTestEnvironment(std::move(video), output_folder);
}
VideoEncoderTestEnvironment::VideoEncoderTestEnvironment(
std::unique_ptr<media::test::Video> video,
const base::FilePath& output_folder)
: video_(std::move(video)),
output_folder_(output_folder),
gpu_memory_buffer_factory_(
gpu::GpuMemoryBufferFactory::CreateNativeType(nullptr)) {}
VideoEncoderTestEnvironment::~VideoEncoderTestEnvironment() = default;
const media::test::Video* VideoEncoderTestEnvironment::Video() const {
return video_.get();
}
const base::FilePath& VideoEncoderTestEnvironment::OutputFolder() const {
return output_folder_;
}
gpu::GpuMemoryBufferFactory*
VideoEncoderTestEnvironment::GetGpuMemoryBufferFactory() const {
return gpu_memory_buffer_factory_.get();
}
} // namespace test
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_TEST_ENVIRONMENT_H_
#define MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_TEST_ENVIRONMENT_H_
#include <limits>
#include <memory>
#include "base/files/file_path.h"
#include "media/gpu/test/video_test_environment.h"
namespace gpu {
class GpuMemoryBufferFactory;
}
namespace media {
namespace test {
class Video;
// Test environment for video encoder tests. Performs setup and teardown once
// for the entire test run.
class VideoEncoderTestEnvironment : public VideoTestEnvironment {
public:
static VideoEncoderTestEnvironment* Create(
const base::FilePath& video_path,
const base::FilePath& video_metadata_path,
const base::FilePath& output_folder);
~VideoEncoderTestEnvironment() override;
// Get the video the tests will be ran on.
const media::test::Video* Video() const;
// Get the output folder.
const base::FilePath& OutputFolder() const;
// Get the GpuMemoryBufferFactory for doing buffer allocations. This needs to
// survive as long as the process is alive just like in production which is
// why it's in here as there are threads that won't immediately die when an
// individual test is completed.
gpu::GpuMemoryBufferFactory* GetGpuMemoryBufferFactory() const;
private:
VideoEncoderTestEnvironment(std::unique_ptr<media::test::Video> video,
const base::FilePath& output_folder);
// Video file to be used for testing.
const std::unique_ptr<media::test::Video> video_;
// Output folder to be used to store test artifacts (e.g. perf metrics).
const base::FilePath output_folder_;
std::unique_ptr<gpu::GpuMemoryBufferFactory> gpu_memory_buffer_factory_;
};
} // namespace test
} // namespace media
#endif // MEDIA_GPU_TEST_VIDEO_ENCODER_VIDEO_ENCODER_TEST_ENVIRONMENT_H_
......@@ -9,6 +9,7 @@
#include "base/stl_util.h"
#include "base/sys_byteorder.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame_layout.h"
#include "media/video/h264_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -247,5 +248,136 @@ bool EncodedDataHelper::HasConfigInfo(const uint8_t* data,
return false;
}
AlignedDataHelper::AlignedDataHelper(const std::vector<uint8_t>& stream,
uint32_t num_frames,
VideoPixelFormat pixel_format,
const gfx::Rect& visible_area,
const gfx::Size& coded_size)
: num_frames_(num_frames),
pixel_format_(pixel_format),
visible_area_(visible_area),
coded_size_(coded_size) {
// TODO(b/150257482): Rather than aligning the video stream data here, we
// could directly create a vector of aligned video frames.
CreateAlignedInputStream(stream);
}
AlignedDataHelper::~AlignedDataHelper() {}
scoped_refptr<VideoFrame> AlignedDataHelper::GetNextFrame() {
size_t num_planes = VideoFrame::NumPlanes(pixel_format_);
CHECK_LE(num_planes, 3u);
uint8_t* frame_data[3] = {};
std::vector<ColorPlaneLayout> planes(num_planes);
size_t offset = data_pos_;
for (size_t i = 0; i < num_planes; i++) {
frame_data[i] = reinterpret_cast<uint8_t*>(&aligned_data_[0]) + offset;
planes[i].stride =
VideoFrame::RowBytes(i, pixel_format_, coded_size_.width());
planes[i].offset = offset;
planes[i].size = aligned_plane_size_[i];
offset += aligned_plane_size_[i];
}
auto layout = VideoFrameLayout::CreateWithPlanes(pixel_format_, coded_size_,
std::move(planes));
if (!layout) {
LOG(ERROR) << "Failed to create VideoFrameLayout";
return nullptr;
}
// TODO(crbug.com/1045825): Investigate use of MOJO_SHARED_BUFFER, similar to
// changes made in crrev.com/c/2050895.
scoped_refptr<VideoFrame> video_frame =
VideoFrame::WrapExternalYuvDataWithLayout(
*layout, visible_area_, visible_area_.size(), frame_data[0],
frame_data[1], frame_data[2], base::TimeTicks::Now().since_origin());
data_pos_ += static_cast<off_t>(aligned_frame_size_);
DCHECK_LE(data_pos_, aligned_data_.size());
EXPECT_NE(nullptr, video_frame.get());
return video_frame;
}
void AlignedDataHelper::Rewind() {
data_pos_ = 0;
}
bool AlignedDataHelper::AtHeadOfStream() const {
return data_pos_ == 0;
}
bool AlignedDataHelper::AtEndOfStream() const {
return data_pos_ == aligned_data_.size();
}
void AlignedDataHelper::CreateAlignedInputStream(
const std::vector<uint8_t>& stream) {
ASSERT_NE(pixel_format_, PIXEL_FORMAT_UNKNOWN);
size_t num_planes = VideoFrame::NumPlanes(pixel_format_);
std::vector<size_t> coded_bpl(num_planes);
std::vector<size_t> visible_bpl(num_planes);
std::vector<size_t> visible_plane_rows(num_planes);
// Calculate padding in bytes to be added after each plane required to keep
// starting addresses of all planes at a byte boundary required by the
// platform. This padding will be added after each plane when copying to the
// temporary file.
// At the same time we also need to take into account coded_size requested by
// the VEA; each row of visible_bpl bytes in the original file needs to be
// copied into a row of coded_bpl bytes in the aligned file.
for (size_t i = 0; i < num_planes; i++) {
coded_bpl[i] = VideoFrame::RowBytes(i, pixel_format_, coded_size_.width());
visible_bpl[i] =
VideoFrame::RowBytes(i, pixel_format_, visible_area_.width());
visible_plane_rows[i] =
VideoFrame::Rows(i, pixel_format_, visible_area_.height());
size_t coded_area_size =
coded_bpl[i] * VideoFrame::Rows(i, pixel_format_, coded_size_.height());
const size_t aligned_size = AlignToPlatformRequirements(coded_area_size);
aligned_plane_size_.push_back(aligned_size);
aligned_frame_size_ += aligned_size;
}
// NOTE: VideoFrame::AllocationSize() cannot used here because the width and
// height on each plane is aligned by 2 for YUV format.
size_t frame_buffer_size = 0;
for (size_t i = 0; i < num_planes; ++i) {
size_t row_bytes =
VideoFrame::RowBytes(i, pixel_format_, visible_area_.width());
size_t rows = VideoFrame::Rows(i, pixel_format_, visible_area_.height());
frame_buffer_size += rows * row_bytes;
}
LOG_ASSERT(stream.size() % frame_buffer_size == 0U)
<< "Stream byte size is not a product of calculated frame byte size";
LOG_ASSERT(aligned_frame_size_ > 0UL);
aligned_data_.resize(aligned_frame_size_ * num_frames_);
off_t src_offset = 0;
off_t dest_offset = 0;
for (size_t frame = 0; frame < num_frames_; frame++) {
const char* src_ptr = reinterpret_cast<const char*>(&stream[src_offset]);
for (size_t i = 0; i < num_planes; i++) {
// Assert that each plane of frame starts at required byte boundary.
ASSERT_EQ(0u, dest_offset & (kPlatformBufferAlignment - 1))
<< "Planes of frame should be mapped per platform requirements";
char* dst_ptr = &aligned_data_[dest_offset];
for (size_t j = 0; j < visible_plane_rows[i]; j++) {
memcpy(dst_ptr, src_ptr, visible_bpl[i]);
src_ptr += visible_bpl[i];
dst_ptr += static_cast<off_t>(coded_bpl[i]);
}
dest_offset += aligned_plane_size_[i];
}
src_offset += static_cast<off_t>(frame_buffer_size);
}
}
} // namespace test
} // namespace media
......@@ -18,6 +18,10 @@
#include "build/build_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace media {
namespace test {
......@@ -66,6 +70,7 @@ StateEnum ClientStateNotification<StateEnum>::Wait() {
return ret;
}
// Helper to extract fragments from encoded video stream.
class EncodedDataHelper {
public:
EncodedDataHelper(const std::vector<uint8_t>& stream,
......@@ -155,6 +160,53 @@ class AlignedAllocator : public std::allocator<T> {
}
};
// Helper to align data and extract frames from raw video streams.
// TODO(crbug.com/1045825): Reduces number of data copies performed.
class AlignedDataHelper {
public:
AlignedDataHelper(const std::vector<uint8_t>& stream,
uint32_t num_frames,
VideoPixelFormat pixel_format,
const gfx::Rect& visible_area,
const gfx::Size& coded_size);
~AlignedDataHelper();
// Compute and return the next frame to be sent to the encoder.
scoped_refptr<VideoFrame> GetNextFrame();
// Rewind to the position of the video stream.
void Rewind();
// Check whether we are at the start of the video stream.
bool AtHeadOfStream() const;
// Check whether we are at the end of the video stream.
bool AtEndOfStream() const;
private:
// Align the video stream to platform requirements.
void CreateAlignedInputStream(const std::vector<uint8_t>& stream);
// Current position in the video stream.
size_t data_pos_ = 0;
// The number of frames in the video stream.
uint32_t num_frames_ = 0;
// The video stream's pixel format.
VideoPixelFormat pixel_format_ = VideoPixelFormat::PIXEL_FORMAT_UNKNOWN;
// The video stream's visible area.
gfx::Rect visible_area_;
// The video's coded size, as requested by the encoder.
gfx::Size coded_size_;
// Aligned data, each plane is aligned to the specified platform alignment
// requirements.
std::vector<char, AlignedAllocator<char, kPlatformBufferAlignment>>
aligned_data_;
// Byte size of each frame in |aligned_data_|.
size_t aligned_frame_size_ = 0;
// Byte size for each aligned plane in a frame.
std::vector<size_t> aligned_plane_size_;
};
} // namespace test
} // namespace media
#endif // MEDIA_GPU_TEST_VIDEO_TEST_HELPERS_H_
// Copyright 2020 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 <limits>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "media/base/test_data_util.h"
#include "media/gpu/test/video.h"
#include "media/gpu/test/video_encoder/video_encoder.h"
#include "media/gpu/test/video_encoder/video_encoder_client.h"
#include "media/gpu/test/video_encoder/video_encoder_test_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace test {
namespace {
// Video encoder tests usage message. Make sure to also update the documentation
// under docs/media/gpu/video_encoder_test_usage.md when making changes here.
// TODO(dstaessens): Add video_encoder_test_usage.md
constexpr const char* usage_msg =
"usage: video_encode_accelerator_tests\n"
" [-v=<level>] [--vmodule=<config>] [--gtest_help] [--help]\n"
" [<video path>] [<video metadata path>]\n";
// Video encoder tests help message.
constexpr const char* help_msg =
"Run the video encoder accelerator tests on the video specified by\n"
"<video path>. If no <video path> is given the default\n"
"\"puppets-320x180.nv12.yuv\" video will be used.\n"
"\nThe <video metadata path> should specify the location of a json file\n"
"containing the video's metadata, such as frame checksums. By default\n"
"<video path>.json will be used.\n"
"\nThe following arguments are supported:\n"
" -v enable verbose mode, e.g. -v=2.\n"
" --vmodule enable verbose mode for the specified module,\n"
" e.g. --vmodule=*media/gpu*=2.\n\n"
" --gtest_help display the gtest help and exit.\n"
" --help display this help and exit.\n";
// Default video to be used if no test video was specified.
constexpr base::FilePath::CharType kDefaultTestVideoPath[] =
FILE_PATH_LITERAL("puppets-320x180.nv12.yuv");
media::test::VideoEncoderTestEnvironment* g_env;
// Video encode test class. Performs setup and teardown for each single test.
class VideoEncoderTest : public ::testing::Test {
public:
std::unique_ptr<VideoEncoder> CreateVideoEncoder(
const Video* video,
VideoEncoderClientConfig config = VideoEncoderClientConfig()) {
LOG_ASSERT(video);
auto video_encoder = VideoEncoder::Create(config);
LOG_ASSERT(video_encoder);
LOG_ASSERT(video_encoder->Initialize(video));
return video_encoder;
}
};
} // namespace
// TODO(dstaessens): Add more test scenarios:
// - Vary framerate
// - Vary bitrate
// - Flush midstream
// - Forcing key frames
// - Add support for webm files
// Encode video from start to end. Wait for the kFlushDone event at the end of
// the stream, that notifies us all frames have been encoded.
TEST_F(VideoEncoderTest, FlushAtEndOfStream) {
VideoEncoderClientConfig config = VideoEncoderClientConfig();
config.framerate = g_env->Video()->FrameRate();
auto encoder = CreateVideoEncoder(g_env->Video(), config);
encoder->Encode();
EXPECT_TRUE(encoder->WaitForFlushDone());
EXPECT_EQ(encoder->GetFlushDoneCount(), 1u);
EXPECT_EQ(encoder->GetFrameReleasedCount(), g_env->Video()->NumFrames());
EXPECT_TRUE(encoder->WaitForBitstreamProcessors());
}
} // namespace test
} // namespace media
int main(int argc, char** argv) {
// Set the default test data path.
media::test::Video::SetTestDataPath(media::GetTestDataPath());
// Print the help message if requested. This needs to be done before
// initializing gtest, to overwrite the default gtest help message.
base::CommandLine::Init(argc, argv);
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
LOG_ASSERT(cmd_line);
if (cmd_line->HasSwitch("help")) {
std::cout << media::test::usage_msg << "\n" << media::test::help_msg;
return 0;
}
// Check if a video was specified on the command line.
base::CommandLine::StringVector args = cmd_line->GetArgs();
base::FilePath video_path =
(args.size() >= 1) ? base::FilePath(args[0])
: base::FilePath(media::test::kDefaultTestVideoPath);
base::FilePath video_metadata_path =
(args.size() >= 2) ? base::FilePath(args[1]) : base::FilePath();
// Parse command line arguments.
base::CommandLine::SwitchMap switches = cmd_line->GetSwitches();
for (base::CommandLine::SwitchMap::const_iterator it = switches.begin();
it != switches.end(); ++it) {
if (it->first.find("gtest_") == 0 || // Handled by GoogleTest
it->first == "v" || it->first == "vmodule") { // Handled by Chrome
continue;
}
std::cout << "unknown option: --" << it->first << "\n"
<< media::test::usage_msg;
return EXIT_FAILURE;
}
testing::InitGoogleTest(&argc, argv);
// Set up our test environment.
media::test::VideoEncoderTestEnvironment* test_environment =
media::test::VideoEncoderTestEnvironment::Create(
video_path, video_metadata_path, base::FilePath());
if (!test_environment)
return EXIT_FAILURE;
media::test::g_env = static_cast<media::test::VideoEncoderTestEnvironment*>(
testing::AddGlobalTestEnvironment(test_environment));
return RUN_ALL_TESTS();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment