Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 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. | |
| 4 | |
| 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" | |
| 8 | |
| 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/path_service.h" | |
| 15 #include "base/strings/string_piece.h" | |
| 16 #include "base/strings/string_split.h" | |
| 17 #include "base/thread_task_runner_handle.h" | |
| 18 #include "content/common/gpu/media/video_accelerator_unittest_helpers.h" | |
| 19 #include "media/base/test_data_util.h" | |
| 20 #include "media/filters/jpeg_parser.h" | |
| 21 #include "media/video/jpeg_decode_accelerator.h" | |
| 22 #include "third_party/libyuv/include/libyuv.h" | |
| 23 | |
| 24 #if defined(OS_CHROMEOS) | |
| 25 #if defined(USE_V4L2_CODEC) | |
| 26 #include "content/common/gpu/media/v4l2_device.h" | |
| 27 #include "content/common/gpu/media/v4l2_jpeg_decode_accelerator.h" | |
| 28 #endif | |
| 29 #if defined(ARCH_CPU_X86_FAMILY) | |
| 30 #include "content/common/gpu/media/vaapi_jpeg_decode_accelerator.h" | |
| 31 #include "content/common/gpu/media/vaapi_wrapper.h" | |
| 32 #endif // defined(ARCH_CPU_X86_FAMILY) | |
| 33 #else | |
| 34 #error The JpegAccelerator tests are not supported on this platform. | |
| 35 #endif | |
| 36 | |
| 37 using media::JpegDecodeAccelerator; | |
| 38 | |
| 39 namespace content { | |
| 40 namespace { | |
| 41 | |
| 42 const base::FilePath::CharType* g_test_image_data = | |
| 43 FILE_PATH_LITERAL("pi-1280x720.jpg"); | |
| 44 bool g_save_to_file = false; | |
| 45 const size_t kDecodeThreshold = 5; | |
| 46 | |
| 47 struct TestImageFile { | |
| 48 explicit TestImageFile(base::FilePath::StringType file_name) | |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
| |
| 49 : file_name(file_name) { | |
| 50 } | |
| 51 | |
| 52 base::FilePath::StringType file_name; | |
| 53 gfx::Size coded_size; | |
| 54 media::JpegParseResult parse_result; | |
| 55 std::string data_str; | |
| 56 size_t output_size; | |
| 57 }; | |
| 58 | |
| 59 enum ClientState { | |
| 60 CS_CREATED, | |
| 61 CS_DECODER_SET, | |
| 62 CS_INITIALIZED, | |
| 63 CS_DECODING, | |
| 64 CS_DECODE_PASS, | |
| 65 CS_DECODE_FAIL, | |
| 66 CS_DESTROYED, | |
| 67 CS_ERROR, | |
| 68 }; | |
| 69 | |
| 70 class JpegClient : public JpegDecodeAccelerator::Client { | |
| 71 public: | |
| 72 JpegClient(std::vector<TestImageFile*>* test_image_files, | |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:04
Done.
| |
| 73 ClientStateNotification<ClientState>* note, | |
| 74 bool use_software_decode); | |
| 75 ~JpegClient() override; | |
| 76 void CreateJpegDecoder(); | |
| 77 void DestroyJpegDecoder(); | |
| 78 void StartDecode(int32_t bitstream_buffer_id); | |
| 79 | |
| 80 // JpegDecodeAccelerator::Client implementation. | |
| 81 void VideoFrameReady(int32_t bitstream_buffer_id) override; | |
| 82 void NotifyError(int32_t bitstream_buffer_id, | |
| 83 JpegDecodeAccelerator::Error error) override; | |
| 84 | |
| 85 private: | |
| 86 void PrepareMemory(int32_t bitstream_buffer_id); | |
| 87 void SetState(ClientState new_state); | |
| 88 void SaveToFile(int32_t bitstream_buffer_id); | |
| 89 bool GetSoftwareDecodeResult(int32_t bitstream_buffer_id); | |
| 90 size_t GetMaxGapFromHwSwResult(int32_t bitstream_buffer_id); | |
| 91 | |
| 92 std::vector<TestImageFile*>* test_image_files_; | |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
| |
| 93 bool use_software_decode_; | |
| 94 | |
| 95 scoped_ptr<JpegDecodeAccelerator> decoder_; | |
| 96 ClientState state_; | |
| 97 | |
| 98 // Used to notify another thread about the state. JpegClient does not own | |
| 99 // this. | |
| 100 ClientStateNotification<ClientState>* note_; | |
| 101 | |
| 102 scoped_ptr<base::SharedMemory> in_shm_; | |
| 103 scoped_ptr<base::SharedMemory> hw_out_shm_; | |
| 104 scoped_ptr<base::SharedMemory> sw_out_shm_; | |
| 105 scoped_refptr<media::VideoFrame> out_frame_; | |
| 106 }; | |
| 107 | |
| 108 JpegClient::JpegClient(std::vector<TestImageFile*>* test_image_files, | |
| 109 ClientStateNotification<ClientState>* note, | |
| 110 bool use_software_decode) | |
| 111 : test_image_files_(test_image_files), | |
| 112 use_software_decode_(use_software_decode), | |
| 113 state_(CS_CREATED), | |
| 114 note_(note) { | |
| 115 | |
| 116 } | |
| 117 | |
| 118 JpegClient::~JpegClient() { | |
| 119 } | |
| 120 | |
| 121 void JpegClient::CreateJpegDecoder() { | |
| 122 #if defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) | |
| 123 decoder_.reset( | |
| 124 new VaapiJpegDecodeAccelerator(base::ThreadTaskRunnerHandle::Get())); | |
| 125 #elif defined(OS_CHROMEOS) && defined(USE_V4L2_CODEC) | |
| 126 scoped_refptr<V4L2Device> device = V4L2Device::Create( | |
| 127 V4L2Device::kJpegDecoder); | |
| 128 if (!device.get()) { | |
| 129 LOG(ERROR) << "V4L2Device::Create failed"; | |
| 130 SetState(CS_ERROR); | |
| 131 return; | |
| 132 } | |
| 133 decoder_.reset(new V4L2JpegDecodeAccelerator( | |
| 134 device, | |
| 135 base::ThreadTaskRunnerHandle::Get())); | |
| 136 #else | |
| 137 DVLOG(1) << "HW JPEG decode acceleration not available."; | |
| 138 SetState(CS_ERROR); | |
| 139 return; | |
| 140 #endif | |
| 141 SetState(CS_DECODER_SET); | |
| 142 if (!decoder_->Initialize(this)) { | |
| 143 LOG(ERROR) << "JpegDecodeAccelerator::Initialize() failed"; | |
| 144 SetState(CS_ERROR); | |
| 145 return; | |
| 146 } | |
| 147 SetState(CS_INITIALIZED); | |
| 148 } | |
| 149 | |
| 150 void JpegClient::DestroyJpegDecoder() { | |
| 151 if (decoder_.get()) | |
| 152 decoder_.reset(); | |
| 153 in_shm_.reset(); | |
| 154 hw_out_shm_.reset(); | |
| 155 SetState(CS_DESTROYED); | |
| 156 } | |
| 157 | |
| 158 void JpegClient::VideoFrameReady(int32_t bitstream_buffer_id) { | |
| 159 if (g_save_to_file) { | |
| 160 SaveToFile(bitstream_buffer_id); | |
| 161 } | |
| 162 size_t gap = GetMaxGapFromHwSwResult(bitstream_buffer_id); | |
| 163 DVLOG(1) << "The maximum difference between software and hardware decode is" | |
| 164 << gap; | |
| 165 if (gap <= kDecodeThreshold) | |
| 166 SetState(CS_DECODE_PASS); | |
| 167 else | |
| 168 SetState(CS_DECODE_FAIL); | |
| 169 } | |
| 170 | |
| 171 void JpegClient::NotifyError(int32_t bitstream_buffer_id, | |
| 172 JpegDecodeAccelerator::Error error) { | |
| 173 } | |
| 174 | |
| 175 void JpegClient::PrepareMemory(int32_t bitstream_buffer_id) { | |
| 176 TestImageFile* image_file = (*test_image_files_)[bitstream_buffer_id]; | |
| 177 | |
| 178 size_t input_size = image_file->data_str.size(); | |
| 179 if (!in_shm_.get() || input_size > in_shm_->mapped_size()) { | |
| 180 in_shm_.reset(new base::SharedMemory); | |
| 181 CHECK(in_shm_->CreateAndMapAnonymous(input_size)); | |
| 182 } | |
| 183 memcpy(in_shm_->memory(), image_file->data_str.data(), input_size); | |
| 184 | |
| 185 if (!hw_out_shm_.get() || | |
| 186 image_file->output_size > hw_out_shm_->mapped_size()) { | |
| 187 hw_out_shm_.reset(new base::SharedMemory); | |
| 188 CHECK(hw_out_shm_->CreateAndMapAnonymous(image_file->output_size)); | |
| 189 } | |
| 190 memset(hw_out_shm_->memory(), 0, image_file->output_size); | |
| 191 | |
| 192 if (!sw_out_shm_.get() || | |
| 193 image_file->output_size > sw_out_shm_->mapped_size()) { | |
| 194 sw_out_shm_.reset(new base::SharedMemory); | |
| 195 CHECK(sw_out_shm_->CreateAndMapAnonymous(image_file->output_size)); | |
| 196 } | |
| 197 memset(sw_out_shm_->memory(), 0, image_file->output_size); | |
| 198 } | |
| 199 | |
| 200 void JpegClient::SetState(ClientState new_state) { | |
| 201 DVLOG(4) << "Changing state " << state_ << "->" << new_state; | |
| 202 note_->Notify(new_state); | |
| 203 state_ = new_state; | |
| 204 } | |
| 205 | |
| 206 void JpegClient::SaveToFile(int32_t bitstream_buffer_id) { | |
| 207 TestImageFile* image_file = (*test_image_files_)[bitstream_buffer_id]; | |
| 208 | |
| 209 base::FilePath filename(image_file->file_name); | |
| 210 base::FilePath out_filename = filename.ReplaceExtension(".yuv"); | |
| 211 EXPECT_EQ(0, base::WriteFile(out_filename, NULL, 0)); | |
| 212 EXPECT_TRUE(base::AppendToFile( | |
| 213 out_filename, | |
| 214 static_cast<char*>(hw_out_shm_->memory()), | |
| 215 base::checked_cast<int>(image_file->output_size))); | |
| 216 } | |
| 217 | |
| 218 size_t JpegClient::GetMaxGapFromHwSwResult(int32_t bitstream_buffer_id) { | |
| 219 TestImageFile* image_file = (*test_image_files_)[bitstream_buffer_id]; | |
| 220 | |
| 221 size_t gap = 0; | |
| 222 uint8* hw_ptr = reinterpret_cast<uint8*>(hw_out_shm_->memory()); | |
| 223 uint8* sw_ptr = reinterpret_cast<uint8*>(sw_out_shm_->memory()); | |
| 224 for (size_t i = 0; i < image_file->output_size; i++) { | |
| 225 if (std::abs(hw_ptr[i] - sw_ptr[i]) > gap) | |
| 226 gap = std::abs(hw_ptr[i] - sw_ptr[i]); | |
| 227 } | |
| 228 return gap; | |
| 229 } | |
| 230 | |
| 231 void JpegClient::StartDecode(int32_t bitstream_buffer_id) { | |
| 232 DCHECK(static_cast<size_t>(bitstream_buffer_id) < test_image_files_->size()); | |
| 233 TestImageFile* image_file = (*test_image_files_)[bitstream_buffer_id]; | |
| 234 | |
| 235 SetState(CS_DECODING); | |
| 236 PrepareMemory(bitstream_buffer_id); | |
| 237 if (use_software_decode_ && !GetSoftwareDecodeResult(bitstream_buffer_id)) { | |
| 238 SetState(CS_DECODE_FAIL); | |
| 239 return; | |
| 240 } | |
| 241 | |
| 242 size_t output_size = media::VideoFrame::AllocationSize( | |
| 243 media::VideoFrame::I420, image_file->coded_size); | |
| 244 | |
| 245 base::SharedMemoryHandle dup_handle; | |
| 246 CHECK(in_shm_->ShareToProcess(base::GetCurrentProcessHandle(), &dup_handle)); | |
| 247 media::BitstreamBuffer bitstream_buffer( | |
| 248 bitstream_buffer_id, dup_handle, image_file->data_str.size()); | |
| 249 out_frame_ = media::VideoFrame::WrapExternalSharedMemory( | |
| 250 media::VideoFrame::I420, | |
| 251 image_file->coded_size, | |
| 252 gfx::Rect(image_file->coded_size), | |
| 253 image_file->coded_size, | |
| 254 reinterpret_cast<uint8*>(hw_out_shm_->memory()), | |
| 255 output_size, | |
| 256 hw_out_shm_->handle(), | |
| 257 0, | |
| 258 base::TimeDelta()); | |
| 259 DCHECK(out_frame_.get()); | |
| 260 decoder_->Decode(bitstream_buffer, out_frame_); | |
| 261 } | |
| 262 | |
| 263 bool JpegClient::GetSoftwareDecodeResult(int32_t bitstream_buffer_id) { | |
| 264 media::VideoFrame::Format format = media::VideoFrame::I420; | |
| 265 TestImageFile* image_file = (*test_image_files_)[bitstream_buffer_id]; | |
| 266 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.
| |
| 267 int yplane_stride, uv_plane_stride; | |
| 268 | |
| 269 yplane = (uint8 *)sw_out_shm_->memory(); | |
|
kcwu
2015/06/08 10:04:28
use c++ casting
henryhsu
2015/06/09 10:20:05
Done.
| |
| 270 uplane = yplane + media::VideoFrame::PlaneAllocationSize( | |
| 271 format, media::VideoFrame::kYPlane, image_file->coded_size); | |
| 272 vplane = uplane + media::VideoFrame::PlaneAllocationSize( | |
| 273 format, media::VideoFrame::kUPlane, image_file->coded_size); | |
| 274 yplane_stride = image_file->coded_size.width(); | |
| 275 uv_plane_stride = yplane_stride / 2; | |
| 276 | |
| 277 if (libyuv::ConvertToI420( | |
| 278 reinterpret_cast<uint8*>(in_shm_->memory()), | |
| 279 image_file->data_str.size(), | |
| 280 yplane, | |
| 281 yplane_stride, | |
| 282 uplane, | |
| 283 uv_plane_stride, | |
| 284 vplane, | |
| 285 uv_plane_stride, | |
| 286 0, | |
| 287 0, | |
| 288 image_file->coded_size.width(), | |
| 289 image_file->coded_size.height(), | |
| 290 image_file->coded_size.width(), | |
| 291 image_file->coded_size.height(), | |
| 292 libyuv::kRotate0, | |
| 293 libyuv::FOURCC_MJPG) != 0) { | |
| 294 LOG(ERROR) << "Software decode " << image_file->file_name << " failed."; | |
| 295 return false; | |
| 296 } | |
| 297 return true; | |
| 298 } | |
| 299 | |
| 300 class JpegDecodeAcceleratorTest : public ::testing::Test { | |
| 301 protected: | |
| 302 JpegDecodeAcceleratorTest() {} | |
| 303 void SetUp() override; | |
| 304 void TearDown() override; | |
| 305 | |
| 306 void ReadTestData(base::FilePath::StringType data, | |
|
kcwu
2015/06/08 10:04:28
const ... &
henryhsu
2015/06/09 10:20:05
Done.
| |
| 307 std::vector<TestImageFile*>* test_image_files); | |
| 308 | |
| 309 std::vector<TestImageFile*> test_image_files_; | |
|
kcwu
2015/06/08 10:04:28
how about ScopedVector?
henryhsu
2015/06/09 10:20:04
Done.
| |
| 310 | |
| 311 protected: | |
| 312 // Required for Thread to work. Not used otherwise. | |
| 313 base::ShadowingAtExitManager at_exit_manager_; | |
| 314 | |
| 315 DISALLOW_COPY_AND_ASSIGN(JpegDecodeAcceleratorTest); | |
| 316 }; | |
| 317 | |
| 318 void JpegDecodeAcceleratorTest::SetUp() { | |
| 319 ReadTestData(g_test_image_data, &test_image_files_); | |
| 320 } | |
| 321 | |
| 322 void JpegDecodeAcceleratorTest::TearDown() { | |
| 323 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.
| |
| 324 delete test_image_files_[i]; | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 void JpegDecodeAcceleratorTest::ReadTestData(base::FilePath::StringType data, | |
| 329 std::vector<TestImageFile*>* test_image_files) { | |
|
kcwu
2015/06/08 10:04:28
indent looks strange?
henryhsu
2015/06/09 10:20:05
Done.
| |
| 330 std::vector<base::FilePath::StringType> entries; | |
| 331 base::SplitString(data, ';', &entries); | |
| 332 CHECK_GE(entries.size(), 1U) << data; | |
| 333 for (size_t index = 0; index < entries.size(); ++index) { | |
| 334 TestImageFile* image_file = new TestImageFile(entries[index]); | |
| 335 // Read in the video data. | |
| 336 base::FilePath input_file = media::GetTestDataFilePath(entries[index]); | |
| 337 ASSERT_TRUE(base::ReadFileToString(input_file, &image_file->data_str)) | |
| 338 << "failed to read input data from " << input_file.value(); | |
| 339 | |
| 340 ASSERT_TRUE(media::ParseJpegPicture( | |
| 341 reinterpret_cast<const uint8_t*>(image_file->data_str.data()), | |
| 342 image_file->data_str.size(), | |
| 343 &image_file->parse_result)); | |
| 344 image_file->coded_size.SetSize( | |
| 345 image_file->parse_result.frame_header.coded_width, | |
| 346 image_file->parse_result.frame_header.coded_height); | |
| 347 image_file->output_size = media::VideoFrame::AllocationSize( | |
| 348 media::VideoFrame::I420, image_file->coded_size); | |
| 349 test_image_files->push_back(image_file); | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 // Test parameters: | |
| 354 // - Use software deocder or not. | |
|
kcwu
2015/06/08 10:04:28
s/deocder/decoder/
henryhsu
2015/06/09 10:20:05
Done.
| |
| 355 // - Expected result. | |
| 356 class JpegDecodeAcceleratorParamTest | |
| 357 : public JpegDecodeAcceleratorTest, | |
| 358 public ::testing::WithParamInterface< base::Tuple<bool, ClientState> > { | |
|
kcwu
2015/06/08 10:04:28
s/< ... >/<...>/
henryhsu
2015/06/09 10:20:04
Done.
| |
| 359 }; | |
| 360 | |
| 361 TEST_P(JpegDecodeAcceleratorParamTest, TestSimpleDecode) { | |
| 362 bool use_software_decode = base::get<0>(GetParam()); | |
| 363 ClientState expected_result = base::get<1>(GetParam()); | |
| 364 | |
| 365 base::Thread decoder_thread("EncoderThread"); | |
| 366 ASSERT_TRUE(decoder_thread.Start()); | |
| 367 | |
| 368 ClientStateNotification<ClientState>* note = | |
|
kcwu
2015/06/08 10:04:28
scoped_ptr
henryhsu
2015/06/09 10:20:05
Done.
| |
| 369 new ClientStateNotification<ClientState>(); | |
| 370 JpegClient* client = new JpegClient( | |
|
kcwu
2015/06/08 10:04:28
scoped_ptr<JpegClient>
henryhsu
2015/06/09 10:20:05
Done.
| |
| 371 &test_image_files_, note, use_software_decode); | |
| 372 decoder_thread.task_runner()->PostTask( | |
| 373 FROM_HERE, | |
| 374 base::Bind(&JpegClient::CreateJpegDecoder, | |
| 375 base::Unretained(client))); | |
| 376 | |
| 377 ASSERT_EQ(note->Wait(), CS_DECODER_SET); | |
| 378 ASSERT_EQ(note->Wait(), CS_INITIALIZED); | |
| 379 | |
| 380 for (size_t index = 0; index < test_image_files_.size(); index++) { | |
| 381 decoder_thread.task_runner()->PostTask( | |
| 382 FROM_HERE, | |
| 383 base::Bind(&JpegClient::StartDecode, base::Unretained(client), index)); | |
| 384 | |
| 385 ASSERT_EQ(note->Wait(), CS_DECODING); | |
| 386 ASSERT_EQ(note->Wait(), expected_result); | |
| 387 } | |
| 388 | |
| 389 decoder_thread.task_runner()->PostTask( | |
| 390 FROM_HERE, | |
| 391 base::Bind(&JpegClient::DestroyJpegDecoder, base::Unretained(client))); | |
| 392 ASSERT_EQ(note->Wait(), CS_DESTROYED); | |
| 393 decoder_thread.Stop(); | |
| 394 } | |
| 395 | |
| 396 INSTANTIATE_TEST_CASE_P( | |
| 397 DecodeSuccess, JpegDecodeAcceleratorParamTest, | |
| 398 ::testing::Values(base::MakeTuple(true, CS_DECODE_PASS))); | |
| 399 | |
| 400 INSTANTIATE_TEST_CASE_P( | |
| 401 DecodeFail, JpegDecodeAcceleratorParamTest, | |
| 402 ::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
| |
| 403 | |
| 404 } // namespace | |
| 405 } // namespace content | |
| 406 | |
| 407 int main(int argc, char** argv) { | |
| 408 testing::InitGoogleTest(&argc, argv); | |
| 409 base::CommandLine::Init(argc, argv); | |
| 410 base::ShadowingAtExitManager at_exit_manager; | |
| 411 | |
| 412 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
| 413 DCHECK(cmd_line); | |
| 414 | |
| 415 base::CommandLine::SwitchMap switches = cmd_line->GetSwitches(); | |
| 416 for (base::CommandLine::SwitchMap::const_iterator it = switches.begin(); | |
| 417 it != switches.end(); ++it) { | |
| 418 if (it->first == "test_image_data") { | |
| 419 content::g_test_image_data = it->second.c_str(); | |
| 420 continue; | |
| 421 } | |
| 422 if (it->first == "save_to_file") { | |
| 423 content::g_save_to_file = true; | |
| 424 continue; | |
| 425 } | |
| 426 LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second; | |
| 427 } | |
| 428 return RUN_ALL_TESTS(); | |
| 429 } | |
| OLD | NEW |