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(); |
+} |