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 |