Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / common / gpu / media / vaapi_h264_decoder_unittest.cc
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.
4
5 #include <string>
6
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"
10
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"
18
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]
22 //
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.
26
27 namespace content {
28 namespace {
29
30 // These are the command line parameters
31 base::FilePath g_input_file;
32 base::FilePath g_output_file;
33 std::string g_md5sum;
34
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";
39
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
44 //
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
47 // output the frames.
48 //
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 {
52  public:
53   VaapiH264DecoderLoop();
54   ~VaapiH264DecoderLoop();
55
56   // Initialize the decoder. Return true if successful.
57   bool Initialize(base::FilePath input_file, base::FilePath output_file);
58
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.
61   bool Run();
62
63   // Get the MD5 sum of the decoded data.
64   std::string GetMD5Sum();
65
66  private:
67   // Callback from the decoder when a picture is decoded.
68   void OutputPicture(int32 input_id,
69                      const scoped_refptr<VASurface>& va_surface);
70
71   // Recycle one surface and put it on available_surfaces_ list.
72   void RecycleSurface(VASurfaceID va_surface_id);
73
74   // Give all surfaces in available_surfaces_ to the decoder.
75   void RefillSurfaces();
76
77   // Free the current set of surfaces and allocate a new set of
78   // surfaces. Returns true when successful.
79   bool AllocateNewSurfaces();
80
81   // Use the data in the frame: write to file and update MD5 sum.
82   bool ProcessVideoFrame(const scoped_refptr<media::VideoFrame>& frame);
83
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_;
89
90   // These members (x_display_, num_outputted_pictures_, num_surfaces_)
91   // need to be initialized and possibly freed manually.
92   Display* x_display_;
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_;
96 };
97
98 VaapiH264DecoderLoop::VaapiH264DecoderLoop()
99     : x_display_(NULL), num_outputted_pictures_(0), num_surfaces_(0) {
100   base::MD5Init(&md5_context_);
101 }
102
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_.
107   decoder_.reset();
108   wrapper_.reset();
109
110   if (x_display_) {
111     XCloseDisplay(x_display_);
112   }
113 }
114
115 void LogOnError(VaapiH264Decoder::VAVDAH264DecoderFailure error) {
116   LOG(FATAL) << "Oh noes! Decoder failed: " << error;
117 }
118
119 bool VaapiH264DecoderLoop::Initialize(base::FilePath input_file,
120                                       base::FilePath output_file) {
121   x_display_ = XOpenDisplay(NULL);
122   if (!x_display_) {
123     LOG(ERROR) << "Can't open X display";
124     return false;
125   }
126
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";
134     return false;
135   }
136
137   decoder_.reset(new VaapiH264Decoder(
138       wrapper_.get(),
139       base::Bind(&VaapiH264DecoderLoop::OutputPicture, base::Unretained(this)),
140       base::Bind(&LogOnError)));
141
142   if (!base::ReadFileToString(input_file, &data_)) {
143     LOG(ERROR) << "failed to read input data from " << input_file.value();
144     return false;
145   }
146
147   const int input_id = 0;  // We don't use input_id in this class.
148   decoder_->SetStream(
149       reinterpret_cast<const uint8*>(data_.c_str()), data_.size(), input_id);
150
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) {
155       return false;
156     }
157     output_file_ = output_file;
158   }
159
160   return true;
161 }
162
163 bool VaapiH264DecoderLoop::Run() {
164   while (1) {
165     switch (decoder_->Decode()) {
166       case VaapiH264Decoder::kDecodeError:
167         LOG(ERROR) << "Decode Error";
168         return false;
169       case VaapiH264Decoder::kAllocateNewSurfaces:
170         VLOG(1) << "Allocate new surfaces";
171         if (!AllocateNewSurfaces()) {
172           LOG(ERROR) << "Failed to allocate new surfaces";
173           return false;
174         }
175         break;
176       case VaapiH264Decoder::kRanOutOfStreamData: {
177         bool rc = decoder_->Flush();
178         VLOG(1) << "Flush returns " << rc;
179         return rc;
180       }
181       case VaapiH264Decoder::kRanOutOfSurfaces:
182         VLOG(1) << "Ran out of surfaces";
183         RefillSurfaces();
184         break;
185     }
186   }
187 }
188
189 std::string VaapiH264DecoderLoop::GetMD5Sum() {
190   base::MD5Digest digest;
191   base::MD5Final(&digest, &md5_context_);
192   return MD5DigestToBase16(digest);
193 }
194
195 scoped_refptr<media::VideoFrame> CopyNV12ToI420(VAImage* image, void* mem) {
196   int width = image->width;
197   int height = image->height;
198
199   DVLOG(1) << "CopyNV12ToI420 width=" << width << ", height=" << height;
200
201   const gfx::Size coded_size(width, height);
202   const gfx::Rect visible_rect(width, height);
203   const gfx::Size natural_size(width, height);
204
205   scoped_refptr<media::VideoFrame> frame =
206       media::VideoFrame::CreateFrame(media::VideoFrame::I420,
207                                      coded_size,
208                                      visible_rect,
209                                      natural_size,
210                                      base::TimeDelta());
211
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];
217
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);
224
225   int rc = libyuv::NV12ToI420(src_y,
226                               src_stride_y,
227                               src_uv,
228                               src_stride_uv,
229                               dst_y,
230                               dst_stride_y,
231                               dst_u,
232                               dst_stride_u,
233                               dst_v,
234                               dst_stride_v,
235                               width,
236                               height);
237   CHECK_EQ(0, rc);
238   return frame;
239 }
240
241 bool VaapiH264DecoderLoop::ProcessVideoFrame(
242     const scoped_refptr<media::VideoFrame>& frame) {
243   frame->HashFrameForTesting(&md5_context_);
244
245   if (output_file_.empty())
246     return true;
247
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)
254       return false;
255   }
256   return true;
257 }
258
259 void VaapiH264DecoderLoop::OutputPicture(
260     int32 input_id,
261     const scoped_refptr<VASurface>& va_surface) {
262   VLOG(1) << "OutputPicture: picture " << num_outputted_pictures_++;
263
264   VAImage image;
265   void* mem;
266
267   if (!wrapper_->GetVaImageForTesting(va_surface->id(), &image, &mem)) {
268     LOG(ERROR) << "Cannot get VAImage.";
269     return;
270   }
271
272   if (image.format.fourcc != VA_FOURCC_NV12) {
273     LOG(ERROR) << "Unexpected image format: " << image.format.fourcc;
274     wrapper_->ReturnVaImageForTesting(&image);
275     return;
276   }
277
278   // Convert NV12 to I420 format.
279   scoped_refptr<media::VideoFrame> frame = CopyNV12ToI420(&image, mem);
280
281   if (frame.get()) {
282     if (!ProcessVideoFrame(frame)) {
283       LOG(ERROR) << "Write to file failed";
284     }
285   } else {
286     LOG(ERROR) << "Cannot convert image to I420.";
287   }
288
289   wrapper_->ReturnVaImageForTesting(&image);
290 }
291
292 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id) {
293   available_surfaces_.push_back(va_surface_id);
294 }
295
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);
303   }
304   available_surfaces_.clear();
305 }
306
307 bool VaapiH264DecoderLoop::AllocateNewSurfaces() {
308   CHECK_EQ(num_surfaces_, available_surfaces_.size())
309       << "not all surfaces are returned";
310
311   available_surfaces_.clear();
312   wrapper_->DestroySurfaces();
313
314   gfx::Size size = decoder_->GetPicSize();
315   num_surfaces_ = decoder_->GetRequiredNumOfPictures();
316   return wrapper_->CreateSurfaces(size, num_surfaces_, &available_surfaces_);
317 }
318
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;
323
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;
328   } else {
329     ASSERT_FALSE(input_file.empty()) << "Need to specify --input_file";
330   }
331
332   VLOG(1) << "Input File: " << input_file.value();
333   VLOG(1) << "Output File: " << output_file.value();
334   VLOG(1) << "Expected MD5 sum: " << md5sum;
335
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";
340
341   if (!md5sum.empty()) {
342     std::string actual = loop.GetMD5Sum();
343     VLOG(1) << "Actual MD5 sum: " << actual;
344     EXPECT_EQ(md5sum, actual);
345   }
346 }
347
348 }  // namespace
349 }  // namespace content
350
351 int main(int argc, char** argv) {
352   testing::InitGoogleTest(&argc, argv);  // Removes gtest-specific args.
353   base::CommandLine::Init(argc, argv);
354
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));
359
360   // Process command line.
361   base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
362   CHECK(cmd_line);
363
364   base::CommandLine::SwitchMap switches = cmd_line->GetSwitches();
365   for (base::CommandLine::SwitchMap::const_iterator it = switches.begin();
366        it != switches.end();
367        ++it) {
368     if (it->first == "input_file") {
369       content::g_input_file = base::FilePath(it->second);
370       continue;
371     }
372     if (it->first == "output_file") {
373       content::g_output_file = base::FilePath(it->second);
374       continue;
375     }
376     if (it->first == "md5sum") {
377       content::g_md5sum = it->second;
378       continue;
379     }
380     if (it->first == "v" || it->first == "vmodule")
381       continue;
382     LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second;
383   }
384
385   return RUN_ALL_TESTS();
386 }