Update To 11.40.268.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     if (!base::AppendToFile(output_file_, buf, to_write))
253       return false;
254   }
255   return true;
256 }
257
258 void VaapiH264DecoderLoop::OutputPicture(
259     int32 input_id,
260     const scoped_refptr<VASurface>& va_surface) {
261   VLOG(1) << "OutputPicture: picture " << num_outputted_pictures_++;
262
263   VAImage image;
264   void* mem;
265
266   if (!wrapper_->GetVaImageForTesting(va_surface->id(), &image, &mem)) {
267     LOG(ERROR) << "Cannot get VAImage.";
268     return;
269   }
270
271   if (image.format.fourcc != VA_FOURCC_NV12) {
272     LOG(ERROR) << "Unexpected image format: " << image.format.fourcc;
273     wrapper_->ReturnVaImageForTesting(&image);
274     return;
275   }
276
277   // Convert NV12 to I420 format.
278   scoped_refptr<media::VideoFrame> frame = CopyNV12ToI420(&image, mem);
279
280   if (frame.get()) {
281     if (!ProcessVideoFrame(frame)) {
282       LOG(ERROR) << "Write to file failed";
283     }
284   } else {
285     LOG(ERROR) << "Cannot convert image to I420.";
286   }
287
288   wrapper_->ReturnVaImageForTesting(&image);
289 }
290
291 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id) {
292   available_surfaces_.push_back(va_surface_id);
293 }
294
295 void VaapiH264DecoderLoop::RefillSurfaces() {
296   for (size_t i = 0; i < available_surfaces_.size(); i++) {
297     VASurface::ReleaseCB release_cb = base::Bind(
298         &VaapiH264DecoderLoop::RecycleSurface, base::Unretained(this));
299     scoped_refptr<VASurface> surface(
300         new VASurface(available_surfaces_[i], release_cb));
301     decoder_->ReuseSurface(surface);
302   }
303   available_surfaces_.clear();
304 }
305
306 bool VaapiH264DecoderLoop::AllocateNewSurfaces() {
307   CHECK_EQ(num_surfaces_, available_surfaces_.size())
308       << "not all surfaces are returned";
309
310   available_surfaces_.clear();
311   wrapper_->DestroySurfaces();
312
313   gfx::Size size = decoder_->GetPicSize();
314   num_surfaces_ = decoder_->GetRequiredNumOfPictures();
315   return wrapper_->CreateSurfaces(size, num_surfaces_, &available_surfaces_);
316 }
317
318 TEST(VaapiH264DecoderTest, TestDecode) {
319   base::FilePath input_file = g_input_file;
320   base::FilePath output_file = g_output_file;
321   std::string md5sum = g_md5sum;
322
323   // If nothing specified, use the default file in the source tree.
324   if (input_file.empty() && output_file.empty() && md5sum.empty()) {
325     input_file = base::FilePath(kDefaultInputFile);
326     md5sum = kDefaultMD5Sum;
327   } else {
328     ASSERT_FALSE(input_file.empty()) << "Need to specify --input_file";
329   }
330
331   VLOG(1) << "Input File: " << input_file.value();
332   VLOG(1) << "Output File: " << output_file.value();
333   VLOG(1) << "Expected MD5 sum: " << md5sum;
334
335   content::VaapiH264DecoderLoop loop;
336   ASSERT_TRUE(loop.Initialize(input_file, output_file))
337       << "initialize decoder loop failed";
338   ASSERT_TRUE(loop.Run()) << "run decoder loop failed";
339
340   if (!md5sum.empty()) {
341     std::string actual = loop.GetMD5Sum();
342     VLOG(1) << "Actual MD5 sum: " << actual;
343     EXPECT_EQ(md5sum, actual);
344   }
345 }
346
347 }  // namespace
348 }  // namespace content
349
350 int main(int argc, char** argv) {
351   testing::InitGoogleTest(&argc, argv);  // Removes gtest-specific args.
352   base::CommandLine::Init(argc, argv);
353
354   // Needed to enable DVLOG through --vmodule.
355   logging::LoggingSettings settings;
356   settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
357   CHECK(logging::InitLogging(settings));
358
359   // Process command line.
360   base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
361   CHECK(cmd_line);
362
363   base::CommandLine::SwitchMap switches = cmd_line->GetSwitches();
364   for (base::CommandLine::SwitchMap::const_iterator it = switches.begin();
365        it != switches.end();
366        ++it) {
367     if (it->first == "input_file") {
368       content::g_input_file = base::FilePath(it->second);
369       continue;
370     }
371     if (it->first == "output_file") {
372       content::g_output_file = base::FilePath(it->second);
373       continue;
374     }
375     if (it->first == "md5sum") {
376       content::g_md5sum = it->second;
377       continue;
378     }
379     if (it->first == "v" || it->first == "vmodule")
380       continue;
381     LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second;
382   }
383
384   return RUN_ALL_TESTS();
385 }