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