Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(738)

Unified Diff: content/common/gpu/media/jpeg_decode_accelerator_unittest.cc

Issue 1125263005: MJPEG acceleration for V4L2 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address kcwu's comments Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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();
+}

Powered by Google App Engine
This is Rietveld 408576698