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/at_exit.h"
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_loop.h"
17 #include "content/common/gpu/media/vaapi_h264_decoder.h"
18 #include "media/base/video_decoder_config.h"
19 #include "third_party/libyuv/include/libyuv.h"
21 #if defined(USE_OZONE)
22 #include "ui/ozone/public/ozone_platform.h"
25 // This program is run like this:
26 // DISPLAY=:0 ./vaapi_h264_decoder_unittest --input_file input.h264
27 // [--output_file output.i420] [--md5sum expected_md5_hex]
29 // The input is read from input.h264. The output is written to output.i420 if it
30 // is given. It also verifies the MD5 sum of the decoded I420 data if the
31 // expected MD5 sum is given.
36 // These are the command line parameters
37 base::FilePath g_input_file
;
38 base::FilePath g_output_file
;
41 // These default values are used if nothing is specified in the command line.
42 const base::FilePath::CharType
* kDefaultInputFile
=
43 FILE_PATH_LITERAL("test-25fps.h264");
44 const char* kDefaultMD5Sum
= "3af866863225b956001252ebeccdb71d";
46 // This class encapsulates the use of VaapiH264Decoder to a simpler interface.
47 // It reads an H.264 Annex B bytestream from a file and outputs the decoded
48 // frames (in I420 format) to another file. The output file can be played by
49 // $ mplayer test_dec.yuv -demuxer rawvideo -rawvideo w=1920:h=1080
51 // To use the class, construct an instance, call Initialize() to specify the
52 // input and output file paths, then call Run() to decode the whole stream and
55 // This class must be created, called and destroyed on a single thread, and
56 // does nothing internally on any other thread.
57 class VaapiH264DecoderLoop
{
59 VaapiH264DecoderLoop();
60 ~VaapiH264DecoderLoop();
62 // Initialize the decoder. Return true if successful.
63 bool Initialize(base::FilePath input_file
, base::FilePath output_file
);
65 // Run the decode loop. The decoded data is written to the file specified by
66 // output_file in Initialize(). Return true if all decoding is successful.
69 // Get the MD5 sum of the decoded data.
70 std::string
GetMD5Sum();
73 // Callback from the decoder when a picture is decoded.
74 void OutputPicture(int32 input_id
,
75 const scoped_refptr
<VASurface
>& va_surface
);
77 // Recycle one surface and put it on available_surfaces_ list.
78 void RecycleSurface(VASurfaceID va_surface_id
);
80 // Give all surfaces in available_surfaces_ to the decoder.
81 void RefillSurfaces();
83 // Free the current set of surfaces and allocate a new set of
84 // surfaces. Returns true when successful.
85 bool AllocateNewSurfaces();
87 // Use the data in the frame: write to file and update MD5 sum.
88 bool ProcessVideoFrame(const scoped_refptr
<media::VideoFrame
>& frame
);
90 scoped_ptr
<VaapiWrapper
> wrapper_
;
91 scoped_ptr
<VaapiH264Decoder
> decoder_
;
92 std::string data_
; // data read from input_file
93 base::FilePath output_file_
; // output data is written to this file
94 std::vector
<VASurfaceID
> available_surfaces_
;
96 // These members (num_outputted_pictures_, num_surfaces_)
97 // need to be initialized and possibly freed manually.
98 int num_outputted_pictures_
; // number of pictures already outputted
99 size_t num_surfaces_
; // number of surfaces in the current set of surfaces
100 base::MD5Context md5_context_
;
103 VaapiH264DecoderLoop::VaapiH264DecoderLoop()
104 : num_outputted_pictures_(0), num_surfaces_(0) {
105 base::MD5Init(&md5_context_
);
108 VaapiH264DecoderLoop::~VaapiH264DecoderLoop() {
111 void LogOnError(VaapiH264Decoder::VAVDAH264DecoderFailure error
) {
112 LOG(FATAL
) << "Oh noes! Decoder failed: " << error
;
115 bool VaapiH264DecoderLoop::Initialize(base::FilePath input_file
,
116 base::FilePath output_file
) {
117 media::VideoCodecProfile profile
= media::H264PROFILE_BASELINE
;
118 base::Closure report_error_cb
=
119 base::Bind(&LogOnError
, VaapiH264Decoder::VAAPI_ERROR
);
120 wrapper_
= VaapiWrapper::CreateForVideoCodec(VaapiWrapper::kDecode
, profile
,
122 if (!wrapper_
.get()) {
123 LOG(ERROR
) << "Can't create vaapi wrapper";
127 decoder_
.reset(new VaapiH264Decoder(
129 base::Bind(&VaapiH264DecoderLoop::OutputPicture
, base::Unretained(this)),
130 base::Bind(&LogOnError
)));
132 if (!base::ReadFileToString(input_file
, &data_
)) {
133 LOG(ERROR
) << "failed to read input data from " << input_file
.value();
137 const int input_id
= 0; // We don't use input_id in this class.
139 reinterpret_cast<const uint8
*>(data_
.c_str()), data_
.size(), input_id
);
141 // This creates or truncates output_file.
142 // Without it, AppendToFile() will not work.
143 if (!output_file
.empty()) {
144 if (base::WriteFile(output_file
, NULL
, 0) != 0) {
147 output_file_
= output_file
;
153 bool VaapiH264DecoderLoop::Run() {
155 switch (decoder_
->Decode()) {
156 case VaapiH264Decoder::kDecodeError
:
157 LOG(ERROR
) << "Decode Error";
159 case VaapiH264Decoder::kAllocateNewSurfaces
:
160 DVLOG(1) << "Allocate new surfaces";
161 if (!AllocateNewSurfaces()) {
162 LOG(ERROR
) << "Failed to allocate new surfaces";
166 case VaapiH264Decoder::kRanOutOfStreamData
: {
167 bool rc
= decoder_
->Flush();
168 DVLOG(1) << "Flush returns " << rc
;
171 case VaapiH264Decoder::kRanOutOfSurfaces
:
172 DVLOG(1) << "Ran out of surfaces";
179 std::string
VaapiH264DecoderLoop::GetMD5Sum() {
180 base::MD5Digest digest
;
181 base::MD5Final(&digest
, &md5_context_
);
182 return MD5DigestToBase16(digest
);
185 scoped_refptr
<media::VideoFrame
> CopyNV12ToI420(VAImage
* image
, void* mem
) {
186 int width
= image
->width
;
187 int height
= image
->height
;
189 DVLOG(1) << "CopyNV12ToI420 width=" << width
<< ", height=" << height
;
191 const gfx::Size
coded_size(width
, height
);
192 const gfx::Rect
visible_rect(width
, height
);
193 const gfx::Size
natural_size(width
, height
);
195 scoped_refptr
<media::VideoFrame
> frame
=
196 media::VideoFrame::CreateFrame(media::VideoFrame::I420
,
202 uint8_t* mem_byte_ptr
= static_cast<uint8_t*>(mem
);
203 uint8_t* src_y
= mem_byte_ptr
+ image
->offsets
[0];
204 uint8_t* src_uv
= mem_byte_ptr
+ image
->offsets
[1];
205 int src_stride_y
= image
->pitches
[0];
206 int src_stride_uv
= image
->pitches
[1];
208 uint8_t* dst_y
= frame
->data(media::VideoFrame::kYPlane
);
209 uint8_t* dst_u
= frame
->data(media::VideoFrame::kUPlane
);
210 uint8_t* dst_v
= frame
->data(media::VideoFrame::kVPlane
);
211 int dst_stride_y
= frame
->stride(media::VideoFrame::kYPlane
);
212 int dst_stride_u
= frame
->stride(media::VideoFrame::kUPlane
);
213 int dst_stride_v
= frame
->stride(media::VideoFrame::kVPlane
);
215 int rc
= libyuv::NV12ToI420(src_y
,
231 bool VaapiH264DecoderLoop::ProcessVideoFrame(
232 const scoped_refptr
<media::VideoFrame
>& frame
) {
233 frame
->HashFrameForTesting(&md5_context_
);
235 if (output_file_
.empty())
238 for (size_t i
= 0; i
< media::VideoFrame::NumPlanes(frame
->format()); i
++) {
239 int to_write
= media::VideoFrame::PlaneAllocationSize(
240 frame
->format(), i
, frame
->coded_size());
241 const char* buf
= reinterpret_cast<const char*>(frame
->data(i
));
242 if (!base::AppendToFile(output_file_
, buf
, to_write
))
248 void VaapiH264DecoderLoop::OutputPicture(
250 const scoped_refptr
<VASurface
>& va_surface
) {
251 DVLOG(1) << "OutputPicture: picture " << num_outputted_pictures_
++;
256 if (!wrapper_
->GetDerivedVaImage(va_surface
->id(), &image
, &mem
)) {
257 LOG(ERROR
) << "Cannot get VAImage.";
261 if (image
.format
.fourcc
!= VA_FOURCC_NV12
) {
262 LOG(ERROR
) << "Unexpected image format: " << image
.format
.fourcc
;
263 wrapper_
->ReturnVaImage(&image
);
267 // Convert NV12 to I420 format.
268 scoped_refptr
<media::VideoFrame
> frame
= CopyNV12ToI420(&image
, mem
);
271 if (!ProcessVideoFrame(frame
)) {
272 LOG(ERROR
) << "Write to file failed";
275 LOG(ERROR
) << "Cannot convert image to I420.";
278 wrapper_
->ReturnVaImage(&image
);
281 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id
) {
282 available_surfaces_
.push_back(va_surface_id
);
285 void VaapiH264DecoderLoop::RefillSurfaces() {
286 for (size_t i
= 0; i
< available_surfaces_
.size(); i
++) {
287 VASurface::ReleaseCB release_cb
= base::Bind(
288 &VaapiH264DecoderLoop::RecycleSurface
, base::Unretained(this));
289 scoped_refptr
<VASurface
> surface(new VASurface(
290 available_surfaces_
[i
], decoder_
->GetPicSize(), release_cb
));
291 decoder_
->ReuseSurface(surface
);
293 available_surfaces_
.clear();
296 bool VaapiH264DecoderLoop::AllocateNewSurfaces() {
297 CHECK_EQ(num_surfaces_
, available_surfaces_
.size())
298 << "not all surfaces are returned";
300 available_surfaces_
.clear();
301 wrapper_
->DestroySurfaces();
303 gfx::Size size
= decoder_
->GetPicSize();
304 num_surfaces_
= decoder_
->GetRequiredNumOfPictures();
305 return wrapper_
->CreateSurfaces(size
, num_surfaces_
, &available_surfaces_
);
308 TEST(VaapiH264DecoderTest
, TestDecode
) {
309 base::FilePath input_file
= g_input_file
;
310 base::FilePath output_file
= g_output_file
;
311 std::string md5sum
= g_md5sum
;
313 // If nothing specified, use the default file in the source tree.
314 if (input_file
.empty() && output_file
.empty() && md5sum
.empty()) {
315 input_file
= base::FilePath(kDefaultInputFile
);
316 md5sum
= kDefaultMD5Sum
;
318 ASSERT_FALSE(input_file
.empty()) << "Need to specify --input_file";
321 DVLOG(1) << "Input File: " << input_file
.value();
322 DVLOG(1) << "Output File: " << output_file
.value();
323 DVLOG(1) << "Expected MD5 sum: " << md5sum
;
325 content::VaapiH264DecoderLoop loop
;
326 ASSERT_TRUE(loop
.Initialize(input_file
, output_file
))
327 << "initialize decoder loop failed";
328 ASSERT_TRUE(loop
.Run()) << "run decoder loop failed";
330 if (!md5sum
.empty()) {
331 std::string actual
= loop
.GetMD5Sum();
332 DVLOG(1) << "Actual MD5 sum: " << actual
;
333 EXPECT_EQ(md5sum
, actual
);
338 } // namespace content
340 int main(int argc
, char** argv
) {
341 testing::InitGoogleTest(&argc
, argv
); // Removes gtest-specific args.
342 base::CommandLine::Init(argc
, argv
);
344 base::ShadowingAtExitManager at_exit_manager
;
346 // Needed to enable DVLOG through --vmodule.
347 logging::LoggingSettings settings
;
348 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
349 CHECK(logging::InitLogging(settings
));
351 // Process command line.
352 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
355 base::CommandLine::SwitchMap switches
= cmd_line
->GetSwitches();
356 for (base::CommandLine::SwitchMap::const_iterator it
= switches
.begin();
357 it
!= switches
.end();
359 if (it
->first
== "input_file") {
360 content::g_input_file
= base::FilePath(it
->second
);
363 if (it
->first
== "output_file") {
364 content::g_output_file
= base::FilePath(it
->second
);
367 if (it
->first
== "md5sum") {
368 content::g_md5sum
= it
->second
;
371 if (it
->first
== "v" || it
->first
== "vmodule")
373 if (it
->first
== "ozone-platform")
375 LOG(FATAL
) << "Unexpected switch: " << it
->first
<< ":" << it
->second
;
378 #if defined(USE_OZONE)
379 base::MessageLoopForUI main_loop
;
380 ui::OzonePlatform::InitializeForUI();
381 ui::OzonePlatform::InitializeForGPU();
384 return RUN_ALL_TESTS();