1 // Copyright 2014 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.
7 // This has to be included first.
8 // See http://code.google.com/p/googletest/issues/detail?id=371
9 #include "testing/gtest/include/gtest/gtest.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/file_util.h"
14 #include "base/logging.h"
15 #include "content/common/gpu/media/vaapi_h264_decoder.h"
16 #include "media/base/video_decoder_config.h"
17 #include "third_party/libyuv/include/libyuv.h"
19 // This program is run like this:
20 // DISPLAY=:0 ./vaapi_h264_decoder_unittest --input_file input.h264
21 // [--output_file output.i420] [--md5sum expected_md5_hex]
23 // The input is read from input.h264. The output is written to output.i420 if it
24 // is given. It also verifies the MD5 sum of the decoded I420 data if the
25 // expected MD5 sum is given.
30 // These are the command line parameters
31 base::FilePath g_input_file
;
32 base::FilePath g_output_file
;
35 // These default values are used if nothing is specified in the command line.
36 const base::FilePath::CharType
* kDefaultInputFile
=
37 FILE_PATH_LITERAL("test-25fps.h264");
38 const char* kDefaultMD5Sum
= "3af866863225b956001252ebeccdb71d";
40 // This class encapsulates the use of VaapiH264Decoder to a simpler interface.
41 // It reads an H.264 Annex B bytestream from a file and outputs the decoded
42 // frames (in I420 format) to another file. The output file can be played by
43 // $ mplayer test_dec.yuv -demuxer rawvideo -rawvideo w=1920:h=1080
45 // To use the class, construct an instance, call Initialize() to specify the
46 // input and output file paths, then call Run() to decode the whole stream and
49 // This class must be created, called and destroyed on a single thread, and
50 // does nothing internally on any other thread.
51 class VaapiH264DecoderLoop
{
53 VaapiH264DecoderLoop();
54 ~VaapiH264DecoderLoop();
56 // Initialize the decoder. Return true if successful.
57 bool Initialize(base::FilePath input_file
, base::FilePath output_file
);
59 // Run the decode loop. The decoded data is written to the file specified by
60 // output_file in Initialize(). Return true if all decoding is successful.
63 // Get the MD5 sum of the decoded data.
64 std::string
GetMD5Sum();
67 // Callback from the decoder when a picture is decoded.
68 void OutputPicture(int32 input_id
,
69 const scoped_refptr
<VASurface
>& va_surface
);
71 // Recycle one surface and put it on available_surfaces_ list.
72 void RecycleSurface(VASurfaceID va_surface_id
);
74 // Give all surfaces in available_surfaces_ to the decoder.
75 void RefillSurfaces();
77 // Free the current set of surfaces and allocate a new set of
78 // surfaces. Returns true when successful.
79 bool AllocateNewSurfaces();
81 // Use the data in the frame: write to file and update MD5 sum.
82 bool ProcessVideoFrame(const scoped_refptr
<media::VideoFrame
>& frame
);
84 scoped_ptr
<VaapiWrapper
> wrapper_
;
85 scoped_ptr
<VaapiH264Decoder
> decoder_
;
86 std::string data_
; // data read from input_file
87 base::FilePath output_file_
; // output data is written to this file
88 std::vector
<VASurfaceID
> available_surfaces_
;
90 // These members (x_display_, num_outputted_pictures_, num_surfaces_)
91 // need to be initialized and possibly freed manually.
93 int num_outputted_pictures_
; // number of pictures already outputted
94 size_t num_surfaces_
; // number of surfaces in the current set of surfaces
95 base::MD5Context md5_context_
;
98 VaapiH264DecoderLoop::VaapiH264DecoderLoop()
99 : x_display_(NULL
), num_outputted_pictures_(0), num_surfaces_(0) {
100 base::MD5Init(&md5_context_
);
103 VaapiH264DecoderLoop::~VaapiH264DecoderLoop() {
104 // We need to destruct decoder and wrapper first because:
105 // (1) The decoder has a reference to the wrapper.
106 // (2) The wrapper has a reference to x_display_.
111 XCloseDisplay(x_display_
);
115 void LogOnError(VaapiH264Decoder::VAVDAH264DecoderFailure error
) {
116 LOG(FATAL
) << "Oh noes! Decoder failed: " << error
;
119 bool VaapiH264DecoderLoop::Initialize(base::FilePath input_file
,
120 base::FilePath output_file
) {
121 x_display_
= XOpenDisplay(NULL
);
123 LOG(ERROR
) << "Can't open X display";
127 media::VideoCodecProfile profile
= media::H264PROFILE_BASELINE
;
128 base::Closure report_error_cb
=
129 base::Bind(&LogOnError
, VaapiH264Decoder::VAAPI_ERROR
);
130 wrapper_
= VaapiWrapper::Create(
131 VaapiWrapper::kDecode
, profile
, x_display_
, report_error_cb
);
132 if (!wrapper_
.get()) {
133 LOG(ERROR
) << "Can't create vaapi wrapper";
137 decoder_
.reset(new VaapiH264Decoder(
139 base::Bind(&VaapiH264DecoderLoop::OutputPicture
, base::Unretained(this)),
140 base::Bind(&LogOnError
)));
142 if (!base::ReadFileToString(input_file
, &data_
)) {
143 LOG(ERROR
) << "failed to read input data from " << input_file
.value();
147 const int input_id
= 0; // We don't use input_id in this class.
149 reinterpret_cast<const uint8
*>(data_
.c_str()), data_
.size(), input_id
);
151 // This creates or truncates output_file.
152 // Without it, AppendToFile() will not work.
153 if (!output_file
.empty()) {
154 if (base::WriteFile(output_file
, NULL
, 0) != 0) {
157 output_file_
= output_file
;
163 bool VaapiH264DecoderLoop::Run() {
165 switch (decoder_
->Decode()) {
166 case VaapiH264Decoder::kDecodeError
:
167 LOG(ERROR
) << "Decode Error";
169 case VaapiH264Decoder::kAllocateNewSurfaces
:
170 VLOG(1) << "Allocate new surfaces";
171 if (!AllocateNewSurfaces()) {
172 LOG(ERROR
) << "Failed to allocate new surfaces";
176 case VaapiH264Decoder::kRanOutOfStreamData
: {
177 bool rc
= decoder_
->Flush();
178 VLOG(1) << "Flush returns " << rc
;
181 case VaapiH264Decoder::kRanOutOfSurfaces
:
182 VLOG(1) << "Ran out of surfaces";
189 std::string
VaapiH264DecoderLoop::GetMD5Sum() {
190 base::MD5Digest digest
;
191 base::MD5Final(&digest
, &md5_context_
);
192 return MD5DigestToBase16(digest
);
195 scoped_refptr
<media::VideoFrame
> CopyNV12ToI420(VAImage
* image
, void* mem
) {
196 int width
= image
->width
;
197 int height
= image
->height
;
199 DVLOG(1) << "CopyNV12ToI420 width=" << width
<< ", height=" << height
;
201 const gfx::Size
coded_size(width
, height
);
202 const gfx::Rect
visible_rect(width
, height
);
203 const gfx::Size
natural_size(width
, height
);
205 scoped_refptr
<media::VideoFrame
> frame
=
206 media::VideoFrame::CreateFrame(media::VideoFrame::I420
,
212 uint8_t* mem_byte_ptr
= static_cast<uint8_t*>(mem
);
213 uint8_t* src_y
= mem_byte_ptr
+ image
->offsets
[0];
214 uint8_t* src_uv
= mem_byte_ptr
+ image
->offsets
[1];
215 int src_stride_y
= image
->pitches
[0];
216 int src_stride_uv
= image
->pitches
[1];
218 uint8_t* dst_y
= frame
->data(media::VideoFrame::kYPlane
);
219 uint8_t* dst_u
= frame
->data(media::VideoFrame::kUPlane
);
220 uint8_t* dst_v
= frame
->data(media::VideoFrame::kVPlane
);
221 int dst_stride_y
= frame
->stride(media::VideoFrame::kYPlane
);
222 int dst_stride_u
= frame
->stride(media::VideoFrame::kUPlane
);
223 int dst_stride_v
= frame
->stride(media::VideoFrame::kVPlane
);
225 int rc
= libyuv::NV12ToI420(src_y
,
241 bool VaapiH264DecoderLoop::ProcessVideoFrame(
242 const scoped_refptr
<media::VideoFrame
>& frame
) {
243 frame
->HashFrameForTesting(&md5_context_
);
245 if (output_file_
.empty())
248 for (size_t i
= 0; i
< media::VideoFrame::NumPlanes(frame
->format()); i
++) {
249 int to_write
= media::VideoFrame::PlaneAllocationSize(
250 frame
->format(), i
, frame
->coded_size());
251 const char* buf
= reinterpret_cast<const char*>(frame
->data(i
));
252 int written
= base::AppendToFile(output_file_
, buf
, to_write
);
253 if (written
!= to_write
)
259 void VaapiH264DecoderLoop::OutputPicture(
261 const scoped_refptr
<VASurface
>& va_surface
) {
262 VLOG(1) << "OutputPicture: picture " << num_outputted_pictures_
++;
267 if (!wrapper_
->GetVaImageForTesting(va_surface
->id(), &image
, &mem
)) {
268 LOG(ERROR
) << "Cannot get VAImage.";
272 if (image
.format
.fourcc
!= VA_FOURCC_NV12
) {
273 LOG(ERROR
) << "Unexpected image format: " << image
.format
.fourcc
;
274 wrapper_
->ReturnVaImageForTesting(&image
);
278 // Convert NV12 to I420 format.
279 scoped_refptr
<media::VideoFrame
> frame
= CopyNV12ToI420(&image
, mem
);
282 if (!ProcessVideoFrame(frame
)) {
283 LOG(ERROR
) << "Write to file failed";
286 LOG(ERROR
) << "Cannot convert image to I420.";
289 wrapper_
->ReturnVaImageForTesting(&image
);
292 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id
) {
293 available_surfaces_
.push_back(va_surface_id
);
296 void VaapiH264DecoderLoop::RefillSurfaces() {
297 for (size_t i
= 0; i
< available_surfaces_
.size(); i
++) {
298 VASurface::ReleaseCB release_cb
= base::Bind(
299 &VaapiH264DecoderLoop::RecycleSurface
, base::Unretained(this));
300 scoped_refptr
<VASurface
> surface(
301 new VASurface(available_surfaces_
[i
], release_cb
));
302 decoder_
->ReuseSurface(surface
);
304 available_surfaces_
.clear();
307 bool VaapiH264DecoderLoop::AllocateNewSurfaces() {
308 CHECK_EQ(num_surfaces_
, available_surfaces_
.size())
309 << "not all surfaces are returned";
311 available_surfaces_
.clear();
312 wrapper_
->DestroySurfaces();
314 gfx::Size size
= decoder_
->GetPicSize();
315 num_surfaces_
= decoder_
->GetRequiredNumOfPictures();
316 return wrapper_
->CreateSurfaces(size
, num_surfaces_
, &available_surfaces_
);
319 TEST(VaapiH264DecoderTest
, TestDecode
) {
320 base::FilePath input_file
= g_input_file
;
321 base::FilePath output_file
= g_output_file
;
322 std::string md5sum
= g_md5sum
;
324 // If nothing specified, use the default file in the source tree.
325 if (input_file
.empty() && output_file
.empty() && md5sum
.empty()) {
326 input_file
= base::FilePath(kDefaultInputFile
);
327 md5sum
= kDefaultMD5Sum
;
329 ASSERT_FALSE(input_file
.empty()) << "Need to specify --input_file";
332 VLOG(1) << "Input File: " << input_file
.value();
333 VLOG(1) << "Output File: " << output_file
.value();
334 VLOG(1) << "Expected MD5 sum: " << md5sum
;
336 content::VaapiH264DecoderLoop loop
;
337 ASSERT_TRUE(loop
.Initialize(input_file
, output_file
))
338 << "initialize decoder loop failed";
339 ASSERT_TRUE(loop
.Run()) << "run decoder loop failed";
341 if (!md5sum
.empty()) {
342 std::string actual
= loop
.GetMD5Sum();
343 VLOG(1) << "Actual MD5 sum: " << actual
;
344 EXPECT_EQ(md5sum
, actual
);
349 } // namespace content
351 int main(int argc
, char** argv
) {
352 testing::InitGoogleTest(&argc
, argv
); // Removes gtest-specific args.
353 base::CommandLine::Init(argc
, argv
);
355 // Needed to enable DVLOG through --vmodule.
356 logging::LoggingSettings settings
;
357 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
358 CHECK(logging::InitLogging(settings
));
360 // Process command line.
361 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
364 base::CommandLine::SwitchMap switches
= cmd_line
->GetSwitches();
365 for (base::CommandLine::SwitchMap::const_iterator it
= switches
.begin();
366 it
!= switches
.end();
368 if (it
->first
== "input_file") {
369 content::g_input_file
= base::FilePath(it
->second
);
372 if (it
->first
== "output_file") {
373 content::g_output_file
= base::FilePath(it
->second
);
376 if (it
->first
== "md5sum") {
377 content::g_md5sum
= it
->second
;
380 if (it
->first
== "v" || it
->first
== "vmodule")
382 LOG(FATAL
) << "Unexpected switch: " << it
->first
<< ":" << it
->second
;
385 return RUN_ALL_TESTS();