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/files/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 (num_outputted_pictures_, num_surfaces_)
91 // need to be initialized and possibly freed manually.
92 int num_outputted_pictures_
; // number of pictures already outputted
93 size_t num_surfaces_
; // number of surfaces in the current set of surfaces
94 base::MD5Context md5_context_
;
97 VaapiH264DecoderLoop::VaapiH264DecoderLoop()
98 : num_outputted_pictures_(0), num_surfaces_(0) {
99 base::MD5Init(&md5_context_
);
102 VaapiH264DecoderLoop::~VaapiH264DecoderLoop() {
105 void LogOnError(VaapiH264Decoder::VAVDAH264DecoderFailure error
) {
106 LOG(FATAL
) << "Oh noes! Decoder failed: " << error
;
109 bool VaapiH264DecoderLoop::Initialize(base::FilePath input_file
,
110 base::FilePath output_file
) {
111 media::VideoCodecProfile profile
= media::H264PROFILE_BASELINE
;
112 base::Closure report_error_cb
=
113 base::Bind(&LogOnError
, VaapiH264Decoder::VAAPI_ERROR
);
114 wrapper_
= VaapiWrapper::CreateForVideoCodec(VaapiWrapper::kDecode
, profile
,
116 if (!wrapper_
.get()) {
117 LOG(ERROR
) << "Can't create vaapi wrapper";
121 decoder_
.reset(new VaapiH264Decoder(
123 base::Bind(&VaapiH264DecoderLoop::OutputPicture
, base::Unretained(this)),
124 base::Bind(&LogOnError
)));
126 if (!base::ReadFileToString(input_file
, &data_
)) {
127 LOG(ERROR
) << "failed to read input data from " << input_file
.value();
131 const int input_id
= 0; // We don't use input_id in this class.
133 reinterpret_cast<const uint8
*>(data_
.c_str()), data_
.size(), input_id
);
135 // This creates or truncates output_file.
136 // Without it, AppendToFile() will not work.
137 if (!output_file
.empty()) {
138 if (base::WriteFile(output_file
, NULL
, 0) != 0) {
141 output_file_
= output_file
;
147 bool VaapiH264DecoderLoop::Run() {
149 switch (decoder_
->Decode()) {
150 case VaapiH264Decoder::kDecodeError
:
151 LOG(ERROR
) << "Decode Error";
153 case VaapiH264Decoder::kAllocateNewSurfaces
:
154 DVLOG(1) << "Allocate new surfaces";
155 if (!AllocateNewSurfaces()) {
156 LOG(ERROR
) << "Failed to allocate new surfaces";
160 case VaapiH264Decoder::kRanOutOfStreamData
: {
161 bool rc
= decoder_
->Flush();
162 DVLOG(1) << "Flush returns " << rc
;
165 case VaapiH264Decoder::kRanOutOfSurfaces
:
166 DVLOG(1) << "Ran out of surfaces";
173 std::string
VaapiH264DecoderLoop::GetMD5Sum() {
174 base::MD5Digest digest
;
175 base::MD5Final(&digest
, &md5_context_
);
176 return MD5DigestToBase16(digest
);
179 scoped_refptr
<media::VideoFrame
> CopyNV12ToI420(VAImage
* image
, void* mem
) {
180 int width
= image
->width
;
181 int height
= image
->height
;
183 DVLOG(1) << "CopyNV12ToI420 width=" << width
<< ", height=" << height
;
185 const gfx::Size
coded_size(width
, height
);
186 const gfx::Rect
visible_rect(width
, height
);
187 const gfx::Size
natural_size(width
, height
);
189 scoped_refptr
<media::VideoFrame
> frame
=
190 media::VideoFrame::CreateFrame(media::VideoFrame::I420
,
196 uint8_t* mem_byte_ptr
= static_cast<uint8_t*>(mem
);
197 uint8_t* src_y
= mem_byte_ptr
+ image
->offsets
[0];
198 uint8_t* src_uv
= mem_byte_ptr
+ image
->offsets
[1];
199 int src_stride_y
= image
->pitches
[0];
200 int src_stride_uv
= image
->pitches
[1];
202 uint8_t* dst_y
= frame
->data(media::VideoFrame::kYPlane
);
203 uint8_t* dst_u
= frame
->data(media::VideoFrame::kUPlane
);
204 uint8_t* dst_v
= frame
->data(media::VideoFrame::kVPlane
);
205 int dst_stride_y
= frame
->stride(media::VideoFrame::kYPlane
);
206 int dst_stride_u
= frame
->stride(media::VideoFrame::kUPlane
);
207 int dst_stride_v
= frame
->stride(media::VideoFrame::kVPlane
);
209 int rc
= libyuv::NV12ToI420(src_y
,
225 bool VaapiH264DecoderLoop::ProcessVideoFrame(
226 const scoped_refptr
<media::VideoFrame
>& frame
) {
227 frame
->HashFrameForTesting(&md5_context_
);
229 if (output_file_
.empty())
232 for (size_t i
= 0; i
< media::VideoFrame::NumPlanes(frame
->format()); i
++) {
233 int to_write
= media::VideoFrame::PlaneAllocationSize(
234 frame
->format(), i
, frame
->coded_size());
235 const char* buf
= reinterpret_cast<const char*>(frame
->data(i
));
236 if (!base::AppendToFile(output_file_
, buf
, to_write
))
242 void VaapiH264DecoderLoop::OutputPicture(
244 const scoped_refptr
<VASurface
>& va_surface
) {
245 DVLOG(1) << "OutputPicture: picture " << num_outputted_pictures_
++;
250 if (!wrapper_
->GetDerivedVaImage(va_surface
->id(), &image
, &mem
)) {
251 LOG(ERROR
) << "Cannot get VAImage.";
255 if (image
.format
.fourcc
!= VA_FOURCC_NV12
) {
256 LOG(ERROR
) << "Unexpected image format: " << image
.format
.fourcc
;
257 wrapper_
->ReturnVaImage(&image
);
261 // Convert NV12 to I420 format.
262 scoped_refptr
<media::VideoFrame
> frame
= CopyNV12ToI420(&image
, mem
);
265 if (!ProcessVideoFrame(frame
)) {
266 LOG(ERROR
) << "Write to file failed";
269 LOG(ERROR
) << "Cannot convert image to I420.";
272 wrapper_
->ReturnVaImage(&image
);
275 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id
) {
276 available_surfaces_
.push_back(va_surface_id
);
279 void VaapiH264DecoderLoop::RefillSurfaces() {
280 for (size_t i
= 0; i
< available_surfaces_
.size(); i
++) {
281 VASurface::ReleaseCB release_cb
= base::Bind(
282 &VaapiH264DecoderLoop::RecycleSurface
, base::Unretained(this));
283 scoped_refptr
<VASurface
> surface(new VASurface(
284 available_surfaces_
[i
], decoder_
->GetPicSize(), release_cb
));
285 decoder_
->ReuseSurface(surface
);
287 available_surfaces_
.clear();
290 bool VaapiH264DecoderLoop::AllocateNewSurfaces() {
291 CHECK_EQ(num_surfaces_
, available_surfaces_
.size())
292 << "not all surfaces are returned";
294 available_surfaces_
.clear();
295 wrapper_
->DestroySurfaces();
297 gfx::Size size
= decoder_
->GetPicSize();
298 num_surfaces_
= decoder_
->GetRequiredNumOfPictures();
299 return wrapper_
->CreateSurfaces(size
, num_surfaces_
, &available_surfaces_
);
302 TEST(VaapiH264DecoderTest
, TestDecode
) {
303 base::FilePath input_file
= g_input_file
;
304 base::FilePath output_file
= g_output_file
;
305 std::string md5sum
= g_md5sum
;
307 // If nothing specified, use the default file in the source tree.
308 if (input_file
.empty() && output_file
.empty() && md5sum
.empty()) {
309 input_file
= base::FilePath(kDefaultInputFile
);
310 md5sum
= kDefaultMD5Sum
;
312 ASSERT_FALSE(input_file
.empty()) << "Need to specify --input_file";
315 DVLOG(1) << "Input File: " << input_file
.value();
316 DVLOG(1) << "Output File: " << output_file
.value();
317 DVLOG(1) << "Expected MD5 sum: " << md5sum
;
319 content::VaapiH264DecoderLoop loop
;
320 ASSERT_TRUE(loop
.Initialize(input_file
, output_file
))
321 << "initialize decoder loop failed";
322 ASSERT_TRUE(loop
.Run()) << "run decoder loop failed";
324 if (!md5sum
.empty()) {
325 std::string actual
= loop
.GetMD5Sum();
326 DVLOG(1) << "Actual MD5 sum: " << actual
;
327 EXPECT_EQ(md5sum
, actual
);
332 } // namespace content
334 int main(int argc
, char** argv
) {
335 testing::InitGoogleTest(&argc
, argv
); // Removes gtest-specific args.
336 base::CommandLine::Init(argc
, argv
);
338 // Needed to enable DVLOG through --vmodule.
339 logging::LoggingSettings settings
;
340 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
341 CHECK(logging::InitLogging(settings
));
343 // Process command line.
344 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
347 base::CommandLine::SwitchMap switches
= cmd_line
->GetSwitches();
348 for (base::CommandLine::SwitchMap::const_iterator it
= switches
.begin();
349 it
!= switches
.end();
351 if (it
->first
== "input_file") {
352 content::g_input_file
= base::FilePath(it
->second
);
355 if (it
->first
== "output_file") {
356 content::g_output_file
= base::FilePath(it
->second
);
359 if (it
->first
== "md5sum") {
360 content::g_md5sum
= it
->second
;
363 if (it
->first
== "v" || it
->first
== "vmodule")
365 LOG(FATAL
) << "Unexpected switch: " << it
->first
<< ":" << it
->second
;
368 return RUN_ALL_TESTS();