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..3c46c505423674364be5acdfc52bc0125cb5f9e5 |
| --- /dev/null |
| +++ b/content/common/gpu/media/jpeg_decode_accelerator_unittest.cc |
| @@ -0,0 +1,429 @@ |
| +// Copyright (c) 2012 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. |
| + |
| +// 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/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(base::FilePath::StringType file_name) |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
|
| + : 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(std::vector<TestImageFile*>* test_image_files, |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:04
Done.
|
| + ClientStateNotification<ClientState>* note, |
| + bool use_software_decode); |
| + ~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); |
| + |
| + std::vector<TestImageFile*>* test_image_files_; |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
|
| + bool use_software_decode_; |
| + |
| + 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_; |
| +}; |
| + |
| +JpegClient::JpegClient(std::vector<TestImageFile*>* test_image_files, |
| + ClientStateNotification<ClientState>* note, |
| + bool use_software_decode) |
| + : test_image_files_(test_image_files), |
| + use_software_decode_(use_software_decode), |
| + 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 (use_software_decode_ && !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, *uplane, *vplane; |
|
kcwu
2015/06/08 10:04:28
Please declare these variables and use them togeth
henryhsu
2015/06/09 10:20:05
Done.
|
| + int yplane_stride, uv_plane_stride; |
| + |
| + yplane = (uint8 *)sw_out_shm_->memory(); |
|
kcwu
2015/06/08 10:04:28
use c++ casting
henryhsu
2015/06/09 10:20:05
Done.
|
| + uplane = yplane + media::VideoFrame::PlaneAllocationSize( |
| + format, media::VideoFrame::kYPlane, image_file->coded_size); |
| + vplane = uplane + media::VideoFrame::PlaneAllocationSize( |
| + format, media::VideoFrame::kUPlane, image_file->coded_size); |
| + yplane_stride = image_file->coded_size.width(); |
| + 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(base::FilePath::StringType data, |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
|
| + std::vector<TestImageFile*>* test_image_files); |
| + |
| + std::vector<TestImageFile*> test_image_files_; |
|
kcwu
2015/06/08 10:04:28
how about ScopedVector?
henryhsu
2015/06/09 10:20:04
Done.
|
| + |
| + 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, &test_image_files_); |
| +} |
| + |
| +void JpegDecodeAcceleratorTest::TearDown() { |
| + for (size_t i = 0; i < test_image_files_.size(); i++) { |
|
kcwu
2015/06/08 10:04:28
STLDeleteElements
henryhsu
2015/06/09 10:20:04
not used. ScopedVector will handle this.
|
| + delete test_image_files_[i]; |
| + } |
| +} |
| + |
| +void JpegDecodeAcceleratorTest::ReadTestData(base::FilePath::StringType data, |
| + std::vector<TestImageFile*>* test_image_files) { |
|
kcwu
2015/06/08 10:04:28
indent looks strange?
henryhsu
2015/06/09 10:20:05
Done.
|
| + 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: |
| +// - Use software deocder or not. |
|
kcwu
2015/06/08 10:04:28
s/deocder/decoder/
henryhsu
2015/06/09 10:20:05
Done.
|
| +// - Expected result. |
| +class JpegDecodeAcceleratorParamTest |
| + : public JpegDecodeAcceleratorTest, |
| + public ::testing::WithParamInterface< base::Tuple<bool, ClientState> > { |
|
kcwu
2015/06/08 10:04:28
s/< ... >/<...>/
henryhsu
2015/06/09 10:20:04
Done.
|
| +}; |
| + |
| +TEST_P(JpegDecodeAcceleratorParamTest, TestSimpleDecode) { |
| + bool use_software_decode = base::get<0>(GetParam()); |
| + ClientState expected_result = base::get<1>(GetParam()); |
| + |
| + base::Thread decoder_thread("EncoderThread"); |
| + ASSERT_TRUE(decoder_thread.Start()); |
| + |
| + ClientStateNotification<ClientState>* note = |
|
kcwu
2015/06/08 10:04:28
scoped_ptr
henryhsu
2015/06/09 10:20:05
Done.
|
| + new ClientStateNotification<ClientState>(); |
| + JpegClient* client = new JpegClient( |
|
kcwu
2015/06/08 10:04:28
scoped_ptr<JpegClient>
henryhsu
2015/06/09 10:20:05
Done.
|
| + &test_image_files_, note, use_software_decode); |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::CreateJpegDecoder, |
| + base::Unretained(client))); |
| + |
| + ASSERT_EQ(note->Wait(), CS_DECODER_SET); |
| + ASSERT_EQ(note->Wait(), CS_INITIALIZED); |
| + |
| + for (size_t index = 0; index < test_image_files_.size(); index++) { |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::StartDecode, base::Unretained(client), index)); |
| + |
| + ASSERT_EQ(note->Wait(), CS_DECODING); |
| + ASSERT_EQ(note->Wait(), expected_result); |
| + } |
| + |
| + decoder_thread.task_runner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&JpegClient::DestroyJpegDecoder, base::Unretained(client))); |
| + ASSERT_EQ(note->Wait(), CS_DESTROYED); |
| + decoder_thread.Stop(); |
| +} |
| + |
| +INSTANTIATE_TEST_CASE_P( |
| + DecodeSuccess, JpegDecodeAcceleratorParamTest, |
| + ::testing::Values(base::MakeTuple(true, CS_DECODE_PASS))); |
| + |
| +INSTANTIATE_TEST_CASE_P( |
| + DecodeFail, JpegDecodeAcceleratorParamTest, |
| + ::testing::Values(base::MakeTuple(false, CS_DECODE_FAIL))); |
|
kcwu
2015/06/08 10:04:29
I don't understand the point of this test.
IIUC, w
henryhsu
2015/06/09 10:20:05
Removed
|
| + |
| +} // 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; |
| + } |
| + return RUN_ALL_TESTS(); |
| +} |