1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This has to be included first.
6 // See http://code.google.com/p/googletest/issues/detail?id=371
7 #include "testing/gtest/include/gtest/gtest.h"
9 #include "base/at_exit.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_vector.h"
15 #include "base/path_service.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/string_split.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "content/common/gpu/media/video_accelerator_unittest_helpers.h"
20 #include "media/base/test_data_util.h"
21 #include "media/filters/jpeg_parser.h"
22 #include "media/video/jpeg_decode_accelerator.h"
23 #include "third_party/libyuv/include/libyuv.h"
24 #include "ui/gfx/codec/jpeg_codec.h"
26 #if defined(OS_CHROMEOS)
27 #if defined(USE_V4L2_CODEC)
28 #include "content/common/gpu/media/v4l2_device.h"
29 #include "content/common/gpu/media/v4l2_jpeg_decode_accelerator.h"
31 #if defined(ARCH_CPU_X86_FAMILY)
32 #include "content/common/gpu/media/vaapi_jpeg_decode_accelerator.h"
33 #include "content/common/gpu/media/vaapi_wrapper.h"
37 using media::JpegDecodeAccelerator
;
42 // Default test image file.
43 const base::FilePath::CharType
* kDefaultJpegFilename
=
44 FILE_PATH_LITERAL("peach_pi-1280x720.jpg");
45 // Decide to save decode results to files or not. Output files will be saved
46 // in the same directory with unittest. File name is like input file but
47 // changing the extension to "yuv".
48 bool g_save_to_file
= false;
49 // Threshold for mean absolute difference of hardware and software decode.
50 // Absolute difference is to calculate the difference between each pixel in two
51 // images. This is used for measuring of the similarity of two images.
52 const double kDecodeSimilarityThreshold
= 1.0;
54 // Environment to create test data for all test cases.
55 class JpegDecodeAcceleratorTestEnvironment
;
56 JpegDecodeAcceleratorTestEnvironment
* g_env
;
58 struct TestImageFile
{
59 explicit TestImageFile(const base::FilePath::StringType
& filename
)
60 : filename(filename
) {}
62 base::FilePath::StringType filename
;
64 // The input content of |filename|.
67 media::JpegParseResult parse_result
;
79 class JpegClient
: public JpegDecodeAccelerator::Client
{
81 JpegClient(const std::vector
<TestImageFile
*>& test_image_files
,
82 ClientStateNotification
<ClientState
>* note
);
83 ~JpegClient() override
;
84 void CreateJpegDecoder();
85 void DestroyJpegDecoder();
86 void StartDecode(int32_t bitstream_buffer_id
);
88 // JpegDecodeAccelerator::Client implementation.
89 void VideoFrameReady(int32_t bitstream_buffer_id
) override
;
90 void NotifyError(int32_t bitstream_buffer_id
,
91 JpegDecodeAccelerator::Error error
) override
;
94 void PrepareMemory(int32_t bitstream_buffer_id
);
95 void SetState(ClientState new_state
);
96 void SaveToFile(int32_t bitstream_buffer_id
);
97 bool GetSoftwareDecodeResult(int32_t bitstream_buffer_id
);
99 // Calculate mean absolute difference of hardware and software decode results
100 // to check the similarity.
101 double GetMeanAbsoluteDifference(int32_t bitstream_buffer_id
);
103 // JpegClient doesn't own |test_image_files_|.
104 const std::vector
<TestImageFile
*>& test_image_files_
;
106 scoped_ptr
<JpegDecodeAccelerator
> decoder_
;
109 // Used to notify another thread about the state. JpegClient does not own
111 ClientStateNotification
<ClientState
>* note_
;
113 // Mapped memory of input file.
114 scoped_ptr
<base::SharedMemory
> in_shm_
;
115 // Mapped memory of output buffer from hardware decoder.
116 scoped_ptr
<base::SharedMemory
> hw_out_shm_
;
117 // Mapped memory of output buffer from software decoder.
118 scoped_ptr
<base::SharedMemory
> sw_out_shm_
;
120 DISALLOW_COPY_AND_ASSIGN(JpegClient
);
123 JpegClient::JpegClient(const std::vector
<TestImageFile
*>& test_image_files
,
124 ClientStateNotification
<ClientState
>* note
)
125 : test_image_files_(test_image_files
), state_(CS_CREATED
), note_(note
) {
128 JpegClient::~JpegClient() {
131 void JpegClient::CreateJpegDecoder() {
132 #if defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY)
134 new VaapiJpegDecodeAccelerator(base::ThreadTaskRunnerHandle::Get()));
135 #elif defined(OS_CHROMEOS) && defined(USE_V4L2_CODEC)
136 scoped_refptr
<V4L2Device
> device
=
137 V4L2Device::Create(V4L2Device::kJpegDecoder
);
139 LOG(ERROR
) << "V4L2Device::Create failed";
143 decoder_
.reset(new V4L2JpegDecodeAccelerator(
144 device
, base::ThreadTaskRunnerHandle::Get()));
146 #error The JpegDecodeAccelerator is not supported on this platform.
148 if (!decoder_
->Initialize(this)) {
149 LOG(ERROR
) << "JpegDecodeAccelerator::Initialize() failed";
153 SetState(CS_INITIALIZED
);
156 void JpegClient::DestroyJpegDecoder() {
160 void JpegClient::VideoFrameReady(int32_t bitstream_buffer_id
) {
161 if (g_save_to_file
) {
162 SaveToFile(bitstream_buffer_id
);
165 if (!GetSoftwareDecodeResult(bitstream_buffer_id
)) {
169 double difference
= GetMeanAbsoluteDifference(bitstream_buffer_id
);
170 if (difference
<= kDecodeSimilarityThreshold
) {
171 SetState(CS_DECODE_PASS
);
173 LOG(ERROR
) << "The mean absolute difference between software and hardware "
174 "decode is " << difference
;
179 void JpegClient::NotifyError(int32_t bitstream_buffer_id
,
180 JpegDecodeAccelerator::Error error
) {
181 LOG(ERROR
) << "Notifying of error " << error
<< " for buffer id "
182 << bitstream_buffer_id
;
186 void JpegClient::PrepareMemory(int32_t bitstream_buffer_id
) {
187 TestImageFile
* image_file
= test_image_files_
[bitstream_buffer_id
];
189 size_t input_size
= image_file
->data_str
.size();
190 if (!in_shm_
.get() || input_size
> in_shm_
->mapped_size()) {
191 in_shm_
.reset(new base::SharedMemory
);
192 CHECK(in_shm_
->CreateAndMapAnonymous(input_size
));
194 memcpy(in_shm_
->memory(), image_file
->data_str
.data(), input_size
);
196 if (!hw_out_shm_
.get() ||
197 image_file
->output_size
> hw_out_shm_
->mapped_size()) {
198 hw_out_shm_
.reset(new base::SharedMemory
);
199 CHECK(hw_out_shm_
->CreateAndMapAnonymous(image_file
->output_size
));
201 memset(hw_out_shm_
->memory(), 0, image_file
->output_size
);
203 if (!sw_out_shm_
.get() ||
204 image_file
->output_size
> sw_out_shm_
->mapped_size()) {
205 sw_out_shm_
.reset(new base::SharedMemory
);
206 CHECK(sw_out_shm_
->CreateAndMapAnonymous(image_file
->output_size
));
208 memset(sw_out_shm_
->memory(), 0, image_file
->output_size
);
211 void JpegClient::SetState(ClientState new_state
) {
212 DVLOG(2) << "Changing state " << state_
<< "->" << new_state
;
213 note_
->Notify(new_state
);
217 void JpegClient::SaveToFile(int32_t bitstream_buffer_id
) {
218 TestImageFile
* image_file
= test_image_files_
[bitstream_buffer_id
];
220 base::FilePath
in_filename(image_file
->filename
);
221 base::FilePath out_filename
= in_filename
.ReplaceExtension(".yuv");
222 int size
= base::checked_cast
<int>(image_file
->output_size
);
224 base::WriteFile(out_filename
,
225 static_cast<char*>(hw_out_shm_
->memory()), size
));
228 double JpegClient::GetMeanAbsoluteDifference(int32_t bitstream_buffer_id
) {
229 TestImageFile
* image_file
= test_image_files_
[bitstream_buffer_id
];
231 double total_difference
= 0;
232 uint8_t* hw_ptr
= static_cast<uint8_t*>(hw_out_shm_
->memory());
233 uint8_t* sw_ptr
= static_cast<uint8_t*>(sw_out_shm_
->memory());
234 for (size_t i
= 0; i
< image_file
->output_size
; i
++)
235 total_difference
+= std::abs(hw_ptr
[i
] - sw_ptr
[i
]);
236 return total_difference
/ image_file
->output_size
;
239 void JpegClient::StartDecode(int32_t bitstream_buffer_id
) {
240 DCHECK_LT(static_cast<size_t>(bitstream_buffer_id
), test_image_files_
.size());
241 TestImageFile
* image_file
= test_image_files_
[bitstream_buffer_id
];
243 PrepareMemory(bitstream_buffer_id
);
245 size_t output_size
= media::VideoFrame::AllocationSize(
246 media::PIXEL_FORMAT_I420
, image_file
->coded_size
);
248 base::SharedMemoryHandle dup_handle
;
249 dup_handle
= base::SharedMemory::DuplicateHandle(in_shm_
->handle());
250 media::BitstreamBuffer
bitstream_buffer(bitstream_buffer_id
, dup_handle
,
251 image_file
->data_str
.size());
252 scoped_refptr
<media::VideoFrame
> out_frame_
=
253 media::VideoFrame::WrapExternalSharedMemory(
254 media::PIXEL_FORMAT_I420
,
255 image_file
->coded_size
,
256 gfx::Rect(image_file
->coded_size
),
257 image_file
->coded_size
,
258 static_cast<uint8_t*>(hw_out_shm_
->memory()),
260 hw_out_shm_
->handle(),
263 CHECK(out_frame_
.get());
264 decoder_
->Decode(bitstream_buffer
, out_frame_
);
267 bool JpegClient::GetSoftwareDecodeResult(int32_t bitstream_buffer_id
) {
268 media::VideoPixelFormat format
= media::PIXEL_FORMAT_I420
;
269 TestImageFile
* image_file
= test_image_files_
[bitstream_buffer_id
];
271 uint8_t* yplane
= static_cast<uint8_t*>(sw_out_shm_
->memory());
274 media::VideoFrame::PlaneSize(format
, media::VideoFrame::kYPlane
,
275 image_file
->coded_size
).GetArea();
278 media::VideoFrame::PlaneSize(format
, media::VideoFrame::kUPlane
,
279 image_file
->coded_size
).GetArea();
280 int yplane_stride
= image_file
->coded_size
.width();
281 int uv_plane_stride
= yplane_stride
/ 2;
283 if (libyuv::ConvertToI420(
284 static_cast<uint8_t*>(in_shm_
->memory()),
285 image_file
->data_str
.size(),
294 image_file
->coded_size
.width(),
295 image_file
->coded_size
.height(),
296 image_file
->coded_size
.width(),
297 image_file
->coded_size
.height(),
299 libyuv::FOURCC_MJPG
) != 0) {
300 LOG(ERROR
) << "Software decode " << image_file
->filename
<< " failed.";
306 class JpegDecodeAcceleratorTestEnvironment
: public ::testing::Environment
{
308 JpegDecodeAcceleratorTestEnvironment(
309 const base::FilePath::CharType
* jpeg_filenames
) {
310 user_jpeg_filenames_
=
311 jpeg_filenames
? jpeg_filenames
: kDefaultJpegFilename
;
313 void SetUp() override
;
314 void TearDown() override
;
316 // Create all black test image with |width| and |height| size.
317 bool CreateTestJpegImage(int width
, int height
, base::FilePath
* filename
);
319 // Read image from |filename| to |image_data|.
320 void ReadTestJpegImage(base::FilePath
& filename
, TestImageFile
* image_data
);
322 // Parsed data of |test_1280x720_jpeg_file_|.
323 scoped_ptr
<TestImageFile
> image_data_1280x720_black_
;
324 // Parsed data of |test_640x368_jpeg_file_|.
325 scoped_ptr
<TestImageFile
> image_data_640x368_black_
;
326 // Parsed data of "peach_pi-1280x720.jpg".
327 scoped_ptr
<TestImageFile
> image_data_1280x720_default_
;
328 // Parsed data of failure image.
329 scoped_ptr
<TestImageFile
> image_data_invalid_
;
330 // Parsed data from command line.
331 ScopedVector
<TestImageFile
> image_data_user_
;
334 const base::FilePath::CharType
* user_jpeg_filenames_
;
336 // Used for InputSizeChange test case. The image size should be smaller than
337 // |kDefaultJpegFilename|.
338 base::FilePath test_1280x720_jpeg_file_
;
339 // Used for ResolutionChange test case.
340 base::FilePath test_640x368_jpeg_file_
;
343 void JpegDecodeAcceleratorTestEnvironment::SetUp() {
344 ASSERT_TRUE(CreateTestJpegImage(1280, 720, &test_1280x720_jpeg_file_
));
345 ASSERT_TRUE(CreateTestJpegImage(640, 368, &test_640x368_jpeg_file_
));
347 image_data_1280x720_black_
.reset(
348 new TestImageFile(test_1280x720_jpeg_file_
.value()));
349 ASSERT_NO_FATAL_FAILURE(ReadTestJpegImage(test_1280x720_jpeg_file_
,
350 image_data_1280x720_black_
.get()));
352 image_data_640x368_black_
.reset(
353 new TestImageFile(test_640x368_jpeg_file_
.value()));
354 ASSERT_NO_FATAL_FAILURE(ReadTestJpegImage(test_640x368_jpeg_file_
,
355 image_data_640x368_black_
.get()));
357 base::FilePath default_jpeg_file
=
358 media::GetTestDataFilePath(kDefaultJpegFilename
);
359 image_data_1280x720_default_
.reset(new TestImageFile(kDefaultJpegFilename
));
360 ASSERT_NO_FATAL_FAILURE(
361 ReadTestJpegImage(default_jpeg_file
, image_data_1280x720_default_
.get()));
363 image_data_invalid_
.reset(new TestImageFile("failure.jpg"));
364 image_data_invalid_
->data_str
.resize(100, 0);
365 image_data_invalid_
->coded_size
.SetSize(1280, 720);
366 image_data_invalid_
->output_size
= media::VideoFrame::AllocationSize(
367 media::PIXEL_FORMAT_I420
, image_data_invalid_
->coded_size
);
369 // |user_jpeg_filenames_| may include many files and use ';' as delimiter.
370 std::vector
<base::FilePath::StringType
> filenames
;
371 base::SplitString(user_jpeg_filenames_
, ';', &filenames
);
372 for (const auto& filename
: filenames
) {
373 base::FilePath input_file
= media::GetTestDataFilePath(filename
);
374 TestImageFile
* image_data
= new TestImageFile(filename
);
375 ASSERT_NO_FATAL_FAILURE(ReadTestJpegImage(input_file
, image_data
));
376 image_data_user_
.push_back(image_data
);
380 void JpegDecodeAcceleratorTestEnvironment::TearDown() {
381 base::DeleteFile(test_1280x720_jpeg_file_
, false);
382 base::DeleteFile(test_640x368_jpeg_file_
, false);
385 bool JpegDecodeAcceleratorTestEnvironment::CreateTestJpegImage(
388 base::FilePath
* filename
) {
389 gfx::Size
coded_size(width
, height
);
390 const int kBytesPerPixel
= 3;
391 const int kJpegQuality
= 100;
392 std::vector
<unsigned char> input_buffer(width
* height
* kBytesPerPixel
);
393 std::vector
<unsigned char> encoded
;
394 if (!gfx::JPEGCodec::Encode(&input_buffer
[0], gfx::JPEGCodec::FORMAT_RGB
,
395 width
, height
, width
* kBytesPerPixel
,
396 kJpegQuality
, &encoded
)) {
400 CHECK(base::CreateTemporaryFile(filename
));
401 EXPECT_TRUE(base::AppendToFile(
402 *filename
, reinterpret_cast<char*>(&encoded
[0]), encoded
.size()));
406 void JpegDecodeAcceleratorTestEnvironment::ReadTestJpegImage(
407 base::FilePath
& input_file
, TestImageFile
* image_data
) {
408 ASSERT_TRUE(base::ReadFileToString(input_file
, &image_data
->data_str
));
410 ASSERT_TRUE(media::ParseJpegPicture(
411 reinterpret_cast<const uint8_t*>(image_data
->data_str
.data()),
412 image_data
->data_str
.size(),
413 &image_data
->parse_result
));
414 image_data
->coded_size
.SetSize(
415 image_data
->parse_result
.frame_header
.coded_width
,
416 image_data
->parse_result
.frame_header
.coded_height
);
417 image_data
->output_size
= media::VideoFrame::AllocationSize(
418 media::PIXEL_FORMAT_I420
, image_data
->coded_size
);
421 class JpegDecodeAcceleratorTest
: public ::testing::Test
{
423 JpegDecodeAcceleratorTest() {}
425 void TestDecode(size_t num_concurrent_decoders
);
427 // The elements of |test_image_files_| are owned by
428 // JpegDecodeAcceleratorTestEnvironment.
429 std::vector
<TestImageFile
*> test_image_files_
;
430 std::vector
<ClientState
> expected_status_
;
433 DISALLOW_COPY_AND_ASSIGN(JpegDecodeAcceleratorTest
);
436 void JpegDecodeAcceleratorTest::TestDecode(size_t num_concurrent_decoders
) {
437 CHECK(test_image_files_
.size() == expected_status_
.size());
438 base::Thread
decoder_thread("DecoderThread");
439 ASSERT_TRUE(decoder_thread
.Start());
441 ScopedVector
<ClientStateNotification
<ClientState
>> notes
;
442 ScopedVector
<JpegClient
> clients
;
444 for (size_t i
= 0; i
< num_concurrent_decoders
; i
++) {
445 notes
.push_back(new ClientStateNotification
<ClientState
>());
446 clients
.push_back(new JpegClient(test_image_files_
, notes
.back()));
447 decoder_thread
.task_runner()->PostTask(
448 FROM_HERE
, base::Bind(&JpegClient::CreateJpegDecoder
,
449 base::Unretained(clients
.back())));
450 ASSERT_EQ(notes
[i
]->Wait(), CS_INITIALIZED
);
453 for (size_t index
= 0; index
< test_image_files_
.size(); index
++) {
454 for (size_t i
= 0; i
< num_concurrent_decoders
; i
++) {
455 decoder_thread
.task_runner()->PostTask(
456 FROM_HERE
, base::Bind(&JpegClient::StartDecode
,
457 base::Unretained(clients
[i
]),
460 for (size_t i
= 0; i
< num_concurrent_decoders
; i
++) {
461 ASSERT_EQ(notes
[i
]->Wait(), expected_status_
[index
]);
465 for (size_t i
= 0; i
< num_concurrent_decoders
; i
++) {
466 decoder_thread
.task_runner()->PostTask(
467 FROM_HERE
, base::Bind(&JpegClient::DestroyJpegDecoder
,
468 base::Unretained(clients
[i
])));
470 decoder_thread
.Stop();
473 TEST_F(JpegDecodeAcceleratorTest
, SimpleDecode
) {
474 for (const auto& image
: g_env
->image_data_user_
) {
475 test_image_files_
.push_back(image
);
476 expected_status_
.push_back(CS_DECODE_PASS
);
481 TEST_F(JpegDecodeAcceleratorTest
, MultipleDecoders
) {
482 for (const auto& image
: g_env
->image_data_user_
) {
483 test_image_files_
.push_back(image
);
484 expected_status_
.push_back(CS_DECODE_PASS
);
489 TEST_F(JpegDecodeAcceleratorTest
, InputSizeChange
) {
490 // The size of |image_data_1280x720_black_| is smaller than
491 // |image_data_1280x720_default_|.
492 test_image_files_
.push_back(g_env
->image_data_1280x720_black_
.get());
493 test_image_files_
.push_back(g_env
->image_data_1280x720_default_
.get());
494 test_image_files_
.push_back(g_env
->image_data_1280x720_black_
.get());
495 for (size_t i
= 0; i
< test_image_files_
.size(); i
++)
496 expected_status_
.push_back(CS_DECODE_PASS
);
500 TEST_F(JpegDecodeAcceleratorTest
, ResolutionChange
) {
501 test_image_files_
.push_back(g_env
->image_data_640x368_black_
.get());
502 test_image_files_
.push_back(g_env
->image_data_1280x720_default_
.get());
503 test_image_files_
.push_back(g_env
->image_data_640x368_black_
.get());
504 for (size_t i
= 0; i
< test_image_files_
.size(); i
++)
505 expected_status_
.push_back(CS_DECODE_PASS
);
509 TEST_F(JpegDecodeAcceleratorTest
, FailureJpeg
) {
510 test_image_files_
.push_back(g_env
->image_data_invalid_
.get());
511 expected_status_
.push_back(CS_ERROR
);
515 TEST_F(JpegDecodeAcceleratorTest
, KeepDecodeAfterFailure
) {
516 test_image_files_
.push_back(g_env
->image_data_invalid_
.get());
517 test_image_files_
.push_back(g_env
->image_data_1280x720_default_
.get());
518 expected_status_
.push_back(CS_ERROR
);
519 expected_status_
.push_back(CS_DECODE_PASS
);
524 } // namespace content
526 int main(int argc
, char** argv
) {
527 testing::InitGoogleTest(&argc
, argv
);
528 base::CommandLine::Init(argc
, argv
);
529 base::ShadowingAtExitManager at_exit_manager
;
531 const base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
534 const base::FilePath::CharType
* jpeg_filenames
= nullptr;
535 base::CommandLine::SwitchMap switches
= cmd_line
->GetSwitches();
536 for (base::CommandLine::SwitchMap::const_iterator it
= switches
.begin();
537 it
!= switches
.end(); ++it
) {
538 // jpeg_filenames can include one or many files and use ';' as delimiter.
539 if (it
->first
== "jpeg_filenames") {
540 jpeg_filenames
= it
->second
.c_str();
543 if (it
->first
== "save_to_file") {
544 content::g_save_to_file
= true;
547 LOG(FATAL
) << "Unexpected switch: " << it
->first
<< ":" << it
->second
;
549 #if defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY)
550 content::VaapiWrapper::PreSandboxInitialization();
554 reinterpret_cast<content::JpegDecodeAcceleratorTestEnvironment
*>(
555 testing::AddGlobalTestEnvironment(
556 new content::JpegDecodeAcceleratorTestEnvironment(
559 return RUN_ALL_TESTS();