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