Chromium Code Reviews| Index: content/common/gpu/media/jpeg_decode_accelerator_unittest.cc |
| diff --git a/content/common/gpu/media/jpeg_decode_accelerator_unittest.cc b/content/common/gpu/media/jpeg_decode_accelerator_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d4f21e9b6ecde7618011a4e774e1bbd847eb8c06 |
| --- /dev/null |
| +++ b/content/common/gpu/media/jpeg_decode_accelerator_unittest.cc |
| @@ -0,0 +1,440 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
|
wuchengli
2015/06/11 11:08:54
Please make JDA unittest a separate CL. This file
henryhsu
2015/06/12 05:47:59
Done.
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +// This has to be included first. |
| +// See http://code.google.com/p/googletest/issues/detail?id=371 |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +#include "base/at_exit.h" |
| +#include "base/bind.h" |
| +#include "base/command_line.h" |
| +#include "base/files/file_util.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_vector.h" |
| +#include "base/path_service.h" |
| +#include "base/strings/string_piece.h" |
| +#include "base/strings/string_split.h" |
| +#include "base/thread_task_runner_handle.h" |
| +#include "content/common/gpu/media/video_accelerator_unittest_helpers.h" |
| +#include "media/base/test_data_util.h" |
| +#include "media/filters/jpeg_parser.h" |
| +#include "media/video/jpeg_decode_accelerator.h" |
| +#include "third_party/libyuv/include/libyuv.h" |
| + |
| +#if defined(OS_CHROMEOS) |
| +#if defined(USE_V4L2_CODEC) |
| +#include "content/common/gpu/media/v4l2_device.h" |
| +#include "content/common/gpu/media/v4l2_jpeg_decode_accelerator.h" |
| +#endif |
| +#if defined(ARCH_CPU_X86_FAMILY) |
| +#include "content/common/gpu/media/vaapi_jpeg_decode_accelerator.h" |
| +#include "content/common/gpu/media/vaapi_wrapper.h" |
| +#endif // defined(ARCH_CPU_X86_FAMILY) |
| +#else |
| +#error The JpegAccelerator tests are not supported on this platform. |
| +#endif |
| + |
| +using media::JpegDecodeAccelerator; |
| + |
| +namespace content { |
| +namespace { |
| + |
| +const base::FilePath::CharType* g_test_image_data = |
| + FILE_PATH_LITERAL("pi-1280x720.jpg"); |
| +bool g_save_to_file = false; |
| +const size_t kDecodeThreshold = 5; |
| + |
| +struct TestImageFile { |
| + explicit TestImageFile(const base::FilePath::StringType& file_name) |
| + : file_name(file_name) { |
| + } |
| + |
| + base::FilePath::StringType file_name; |
| + gfx::Size coded_size; |
| + media::JpegParseResult parse_result; |
| + std::string data_str; |
| + size_t output_size; |
| +}; |
| + |
| +enum ClientState { |
| + CS_CREATED, |
| + CS_DECODER_SET, |
| + CS_INITIALIZED, |
| + CS_DECODING, |
| + CS_DECODE_PASS, |
| + CS_DECODE_FAIL, |
| + CS_DESTROYED, |
| + CS_ERROR, |
| +}; |
| + |
| +class JpegClient : public JpegDecodeAccelerator::Client { |
| + public: |
| + JpegClient(const ScopedVector<TestImageFile>& test_image_files, |
| + ClientStateNotification<ClientState>* note); |
| + ~JpegClient() override; |
| + void CreateJpegDecoder(); |
| + void DestroyJpegDecoder(); |
| + void StartDecode(int32_t bitstream_buffer_id); |
| + |
| + // JpegDecodeAccelerator::Client implementation. |
| + void VideoFrameReady(int32_t bitstream_buffer_id) override; |
| + void NotifyError(int32_t bitstream_buffer_id, |
| + JpegDecodeAccelerator::Error error) override; |
| + |
| + private: |
| + void PrepareMemory(int32_t bitstream_buffer_id); |
| + void SetState(ClientState new_state); |
| + void SaveToFile(int32_t bitstream_buffer_id); |
| + bool GetSoftwareDecodeResult(int32_t bitstream_buffer_id); |
| + size_t GetMaxGapFromHwSwResult(int32_t bitstream_buffer_id); |
| + |
| + const ScopedVector<TestImageFile>& test_image_files_; |
| + |
| + scoped_ptr<JpegDecodeAccelerator> decoder_; |
| + ClientState state_; |
| + |
| + // Used to notify another thread about the state. JpegClient does not own |
| + // this. |
| + ClientStateNotification<ClientState>* note_; |
| + |
| + scoped_ptr<base::SharedMemory> in_shm_; |
| + scoped_ptr<base::SharedMemory> hw_out_shm_; |
| + scoped_ptr<base::SharedMemory> sw_out_shm_; |
| + scoped_refptr<media::VideoFrame> out_frame_; |
| +}; |
|
kcwu
2015/06/09 12:17:44
DISALLOW_COPY_AND_ASSIGN(JpegClient);
henryhsu
2015/06/12 05:48:00
Done.
|
| + |
| +JpegClient::JpegClient(const ScopedVector<TestImageFile>& test_image_files, |
| + ClientStateNotification<ClientState>* note) |
| + : test_image_files_(test_image_files), |
| + state_(CS_CREATED), |
| + note_(note) { |
| + |
| +} |
| + |
| +JpegClient::~JpegClient() { |
| +} |
| + |
| +void JpegClient::CreateJpegDecoder() { |
| +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) |
| + decoder_.reset( |
| + new VaapiJpegDecodeAccelerator(base::ThreadTaskRunnerHandle::Get())); |
| +#elif defined(OS_CHROMEOS) && defined(USE_V4L2_CODEC) |
| + scoped_refptr<V4L2Device> device = V4L2Device::Create( |
| + V4L2Device::kJpegDecoder); |
| + if (!device.get()) { |
| + LOG(ERROR) << "V4L2Device::Create failed"; |
| + SetState(CS_ERROR); |
| + return; |
| + } |
| + decoder_.reset(new V4L2JpegDecodeAccelerator( |
| + device, |
| + base::ThreadTaskRunnerHandle::Get())); |
| +#else |
| + DVLOG(1) << "HW JPEG decode acceleration not available."; |
| + SetState(CS_ERROR); |
| + return; |
| +#endif |
| + SetState(CS_DECODER_SET); |
| + if (!decoder_->Initialize(this)) { |
| + LOG(ERROR) << "JpegDecodeAccelerator::Initialize() failed"; |
| + SetState(CS_ERROR); |
| + return; |
| + } |
| + SetState(CS_INITIALIZED); |
| +} |
| + |
| +void JpegClient::DestroyJpegDecoder() { |
| + if (decoder_.get()) |
| + decoder_.reset(); |
| + in_shm_.reset(); |
| + hw_out_shm_.reset(); |
| + SetState(CS_DESTROYED); |
| +} |
| + |
| +void JpegClient::VideoFrameReady(int32_t bitstream_buffer_id) { |
| + if (g_save_to_file) { |
| + SaveToFile(bitstream_buffer_id); |
| + } |
| + size_t gap = GetMaxGapFromHwSwResult(bitstream_buffer_id); |
| + DVLOG(1) << "The maximum difference between software and hardware decode is" |
| + << gap; |
| + if (gap <= kDecodeThreshold) |
| + SetState(CS_DECODE_PASS); |
| + else |
| + SetState(CS_DECODE_FAIL); |
| +} |
| + |
| +void JpegClient::NotifyError(int32_t bitstream_buffer_id, |
| + JpegDecodeAccelerator::Error error) { |
| +} |
| + |
| +void JpegClient::PrepareMemory(int32_t bitstream_buffer_id) { |
| + TestImageFile* image_file = test_image_files_[bitstream_buffer_id]; |
| + |
| + size_t input_size = image_file->data_str.size(); |
| + if (!in_shm_.get() || input_size > in_shm_->mapped_size()) { |
| + in_shm_.reset(new base::SharedMemory); |
| + CHECK(in_shm_->CreateAndMapAnonymous(input_size)); |
| + } |
| + memcpy(in_shm_->memory(), image_file->data_str.data(), input_size); |
| + |
| + if (!hw_out_shm_.get() || |
| + image_file->output_size > hw_out_shm_->mapped_size()) { |
| + hw_out_shm_.reset(new base::SharedMemory); |
| + CHECK(hw_out_shm_->CreateAndMapAnonymous(image_file->output_size)); |
| + } |
| + memset(hw_out_shm_->memory(), 0, image_file->output_size); |
| + |
| + if (!sw_out_shm_.get() || |
| + image_file->output_size > sw_out_shm_->mapped_size()) { |
| + sw_out_shm_.reset(new base::SharedMemory); |
| + CHECK(sw_out_shm_->CreateAndMapAnonymous(image_file->output_size)); |
| + } |
| + memset(sw_out_shm_->memory(), 0, image_file->output_size); |
| +} |
| + |
| +void JpegClient::SetState(ClientState new_state) { |
| + DVLOG(4) << "Changing state " << state_ << "->" << new_state; |
| + note_->Notify(new_state); |
| + state_ = new_state; |
| +} |
| + |
| +void JpegClient::SaveToFile(int32_t bitstream_buffer_id) { |
| + TestImageFile* image_file = test_image_files_[bitstream_buffer_id]; |
| + |
| + base::FilePath filename(image_file->file_name); |
| + base::FilePath out_filename = filename.ReplaceExtension(".yuv"); |
| + EXPECT_EQ(0, base::WriteFile(out_filename, NULL, 0)); |
| + EXPECT_TRUE(base::AppendToFile( |
| + out_filename, |
| + static_cast<char*>(hw_out_shm_->memory()), |
| + base::checked_cast<int>(image_file->output_size))); |
| +} |
| + |
| +size_t JpegClient::GetMaxGapFromHwSwResult(int32_t bitstream_buffer_id) { |
| + TestImageFile* image_file = test_image_files_[bitstream_buffer_id]; |
| + |
| + size_t gap = 0; |
| + uint8* hw_ptr = reinterpret_cast<uint8*>(hw_out_shm_->memory()); |
| + uint8* sw_ptr = reinterpret_cast<uint8*>(sw_out_shm_->memory()); |
| + for (size_t i = 0; i < image_file->output_size; i++) { |
| + if (std::abs(hw_ptr[i] - sw_ptr[i]) > gap) |
| + gap = std::abs(hw_ptr[i] - sw_ptr[i]); |
| + } |
| + return gap; |
| +} |
| + |
| +void JpegClient::StartDecode(int32_t bitstream_buffer_id) { |
| + DCHECK(static_cast<size_t>(bitstream_buffer_id) < test_image_files_.size()); |
| + TestImageFile* image_file = test_image_files_[bitstream_buffer_id]; |
| + |
| + SetState(CS_DECODING); |
| + PrepareMemory(bitstream_buffer_id); |
| + if (!GetSoftwareDecodeResult(bitstream_buffer_id)) { |
| + SetState(CS_DECODE_FAIL); |
| + return; |
| + } |
| + |
| + size_t output_size = media::VideoFrame::AllocationSize( |
| + media::VideoFrame::I420, image_file->coded_size); |
| + |
| + base::SharedMemoryHandle dup_handle; |
| + CHECK(in_shm_->ShareToProcess(base::GetCurrentProcessHandle(), &dup_handle)); |
| + media::BitstreamBuffer bitstream_buffer( |
| + bitstream_buffer_id, dup_handle, image_file->data_str.size()); |
| + out_frame_ = media::VideoFrame::WrapExternalSharedMemory( |
| + media::VideoFrame::I420, |
| + image_file->coded_size, |
| + gfx::Rect(image_file->coded_size), |
| + image_file->coded_size, |
| + reinterpret_cast<uint8*>(hw_out_shm_->memory()), |
| + output_size, |
| + hw_out_shm_->handle(), |
| + 0, |
| + base::TimeDelta()); |
| + DCHECK(out_frame_.get()); |
| + decoder_->Decode(bitstream_buffer, out_frame_); |
| +} |
| + |
| +bool JpegClient::GetSoftwareDecodeResult(int32_t bitstream_buffer_id) { |
| + media::VideoFrame::Format format = media::VideoFrame::I420; |
| + TestImageFile* image_file = test_image_files_[bitstream_buffer_id]; |
| + |
| + uint8 *yplane = reinterpret_cast<uint8*>(sw_out_shm_->memory()); |
|
kcwu
2015/06/09 12:17:44
"uint8* foo" instead of "uint8 *foo".
henryhsu
2015/06/12 05:48:00
Done.
|
| + uint8 *uplane = yplane + media::VideoFrame::PlaneAllocationSize( |
| + format, media::VideoFrame::kYPlane, image_file->coded_size); |
| + uint8 *vplane = uplane + media::VideoFrame::PlaneAllocationSize( |
| + format, media::VideoFrame::kUPlane, image_file->coded_size); |
| + int yplane_stride = image_file->coded_size.width(); |
| + int uv_plane_stride = yplane_stride / 2; |
| + |
| + if (libyuv::ConvertToI420( |
| + reinterpret_cast<uint8*>(in_shm_->memory()), |
| + image_file->data_str.size(), |
| + yplane, |
| + yplane_stride, |
| + uplane, |
| + uv_plane_stride, |
| + vplane, |
| + uv_plane_stride, |
| + 0, |
| + 0, |
| + image_file->coded_size.width(), |
| + image_file->coded_size.height(), |
| + image_file->coded_size.width(), |
| + image_file->coded_size.height(), |
| + libyuv::kRotate0, |
| + libyuv::FOURCC_MJPG) != 0) { |
| + LOG(ERROR) << "Software decode " << image_file->file_name << " failed."; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +class JpegDecodeAcceleratorTest : public ::testing::Test { |
| + protected: |
| + JpegDecodeAcceleratorTest() {} |
| + void SetUp() override; |
| + void TearDown() override; |
| + |
| + void ReadTestData(const base::FilePath::StringType& data); |
| + |
| + ScopedVector<TestImageFile> test_image_files_; |
| + |
| + protected: |
| + // Required for Thread to work. Not used otherwise. |
| + base::ShadowingAtExitManager at_exit_manager_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(JpegDecodeAcceleratorTest); |
| +}; |
| + |
| +void JpegDecodeAcceleratorTest::SetUp() { |
| + ReadTestData(g_test_image_data); |
| +} |
| + |
| +void JpegDecodeAcceleratorTest::TearDown() { |
| +} |
| + |
| +void JpegDecodeAcceleratorTest::ReadTestData( |
| + const base::FilePath::StringType& data) { |
| + std::vector<base::FilePath::StringType> entries; |
| + base::SplitString(data, ';', &entries); |
| + CHECK_GE(entries.size(), 1U) << data; |
| + for (size_t index = 0; index < entries.size(); ++index) { |
| + TestImageFile* image_file = new TestImageFile(entries[index]); |
| + // Read in the video data. |
| + base::FilePath input_file = media::GetTestDataFilePath(entries[index]); |
| + ASSERT_TRUE(base::ReadFileToString(input_file, &image_file->data_str)) |
| + << "failed to read input data from " << input_file.value(); |
| + |
| + ASSERT_TRUE(media::ParseJpegPicture( |
| + reinterpret_cast<const uint8_t*>(image_file->data_str.data()), |
| + image_file->data_str.size(), |
| + &image_file->parse_result)); |
| + image_file->coded_size.SetSize( |
| + image_file->parse_result.frame_header.coded_width, |
| + image_file->parse_result.frame_header.coded_height); |
| + image_file->output_size = media::VideoFrame::AllocationSize( |
| + media::VideoFrame::I420, image_file->coded_size); |
| + test_image_files_.push_back(image_file); |
| + } |
| +} |
| + |
| +// Test parameters: |
| +// - Number of concurrent Jpeg decoder. |
| +// - Expected result. |
| +class JpegDecodeAcceleratorParamTest |
| + : public JpegDecodeAcceleratorTest, |
| + public ::testing::WithParamInterface<base::Tuple<int, ClientState>> { |
| +}; |
| + |
| +TEST_P(JpegDecodeAcceleratorParamTest, TestSimpleDecode) { |
| + size_t num_concurrent_decoders = base::get<0>(GetParam()); |
| + ClientState expected_result = base::get<1>(GetParam()); |
| + |
| + base::Thread decoder_thread("DecoderThread"); |
| + ASSERT_TRUE(decoder_thread.Start()); |
| + |
| + ScopedVector<ClientStateNotification<ClientState> > notes; |
|
kcwu
2015/06/09 12:17:44
s/> >/>>/
henryhsu
2015/06/12 05:47:59
Done.
|
| + ScopedVector<JpegClient> clients; |
| + |
| + for (size_t i = 0; i < num_concurrent_decoders; i++) { |
| + notes.push_back(new ClientStateNotification<ClientState>()); |
| + clients.push_back(new JpegClient(test_image_files_, notes.back())); |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::CreateJpegDecoder, |
| + base::Unretained(clients.back()))); |
| + } |
| + |
| + for (size_t i = 0; i < num_concurrent_decoders; i++) { |
| + ASSERT_EQ(notes[i]->Wait(), CS_DECODER_SET); |
| + ASSERT_EQ(notes[i]->Wait(), CS_INITIALIZED); |
| + } |
| + |
| + for (size_t index = 0; index < test_image_files_.size(); index++) { |
| + for (size_t i = 0; i < num_concurrent_decoders; i++) { |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::StartDecode, |
| + base::Unretained(clients[i]), |
| + index)); |
| + |
| + } |
| + } |
| + |
| + for (size_t index = 0; index < test_image_files_.size(); index++) { |
| + for (size_t i = 0; i < num_concurrent_decoders; i++) { |
| + ASSERT_EQ(notes[i]->Wait(), CS_DECODING); |
| + ASSERT_EQ(notes[i]->Wait(), expected_result); |
| + } |
| + } |
| + |
| + for (size_t i = 0; i < num_concurrent_decoders; i++) { |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::DestroyJpegDecoder, |
| + base::Unretained(clients[i]))); |
| + ASSERT_EQ(notes[i]->Wait(), CS_DESTROYED); |
| + } |
| + decoder_thread.Stop(); |
| +} |
| + |
| +INSTANTIATE_TEST_CASE_P( |
| + DecodeSuccess, JpegDecodeAcceleratorParamTest, |
| + ::testing::Values(base::MakeTuple(1, CS_DECODE_PASS))); |
| + |
| +INSTANTIATE_TEST_CASE_P( |
| + MultipleDecoder, JpegDecodeAcceleratorParamTest, |
| + ::testing::Values(base::MakeTuple(3, CS_DECODE_PASS))); |
| + |
| +} // namespace |
| +} // namespace content |
| + |
| +int main(int argc, char** argv) { |
| + testing::InitGoogleTest(&argc, argv); |
| + base::CommandLine::Init(argc, argv); |
| + base::ShadowingAtExitManager at_exit_manager; |
| + |
| + const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| + DCHECK(cmd_line); |
| + |
| + base::CommandLine::SwitchMap switches = cmd_line->GetSwitches(); |
| + for (base::CommandLine::SwitchMap::const_iterator it = switches.begin(); |
| + it != switches.end(); ++it) { |
| + if (it->first == "test_image_data") { |
| + content::g_test_image_data = it->second.c_str(); |
| + continue; |
| + } |
| + if (it->first == "save_to_file") { |
| + content::g_save_to_file = true; |
| + continue; |
| + } |
| + LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second; |
| + } |
| +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) |
| + content::VaapiWrapper::PreSandboxInitialization(); |
| +#endif |
| + return RUN_ALL_TESTS(); |
| +} |