Home | History | Annotate | Download | only in media
      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/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(profile, x_display_, report_error_cb);
    131   if (!wrapper_.get()) {
    132     LOG(ERROR) << "Can't create vaapi wrapper";
    133     return false;
    134   }
    135 
    136   decoder_.reset(new VaapiH264Decoder(
    137       wrapper_.get(),
    138       base::Bind(&VaapiH264DecoderLoop::OutputPicture, base::Unretained(this)),
    139       base::Bind(&LogOnError)));
    140 
    141   if (!base::ReadFileToString(input_file, &data_)) {
    142     LOG(ERROR) << "failed to read input data from " << input_file.value();
    143     return false;
    144   }
    145 
    146   const int input_id = 0;  // We don't use input_id in this class.
    147   decoder_->SetStream(
    148       reinterpret_cast<const uint8*>(data_.c_str()), data_.size(), input_id);
    149 
    150   // This creates or truncates output_file.
    151   // Without it, AppendToFile() will not work.
    152   if (!output_file.empty()) {
    153     if (base::WriteFile(output_file, NULL, 0) != 0) {
    154       return false;
    155     }
    156     output_file_ = output_file;
    157   }
    158 
    159   return true;
    160 }
    161 
    162 bool VaapiH264DecoderLoop::Run() {
    163   while (1) {
    164     switch (decoder_->Decode()) {
    165       case VaapiH264Decoder::kDecodeError:
    166         LOG(ERROR) << "Decode Error";
    167         return false;
    168       case VaapiH264Decoder::kAllocateNewSurfaces:
    169         VLOG(1) << "Allocate new surfaces";
    170         if (!AllocateNewSurfaces()) {
    171           LOG(ERROR) << "Failed to allocate new surfaces";
    172           return false;
    173         }
    174         break;
    175       case VaapiH264Decoder::kRanOutOfStreamData: {
    176         bool rc = decoder_->Flush();
    177         VLOG(1) << "Flush returns " << rc;
    178         return rc;
    179       }
    180       case VaapiH264Decoder::kRanOutOfSurfaces:
    181         VLOG(1) << "Ran out of surfaces";
    182         RefillSurfaces();
    183         break;
    184     }
    185   }
    186 }
    187 
    188 std::string VaapiH264DecoderLoop::GetMD5Sum() {
    189   base::MD5Digest digest;
    190   base::MD5Final(&digest, &md5_context_);
    191   return MD5DigestToBase16(digest);
    192 }
    193 
    194 scoped_refptr<media::VideoFrame> CopyNV12ToI420(VAImage* image, void* mem) {
    195   int width = image->width;
    196   int height = image->height;
    197 
    198   DVLOG(1) << "CopyNV12ToI420 width=" << width << ", height=" << height;
    199 
    200   const gfx::Size coded_size(width, height);
    201   const gfx::Rect visible_rect(width, height);
    202   const gfx::Size natural_size(width, height);
    203 
    204   scoped_refptr<media::VideoFrame> frame =
    205       media::VideoFrame::CreateFrame(media::VideoFrame::I420,
    206                                      coded_size,
    207                                      visible_rect,
    208                                      natural_size,
    209                                      base::TimeDelta());
    210 
    211   uint8_t* mem_byte_ptr = static_cast<uint8_t*>(mem);
    212   uint8_t* src_y = mem_byte_ptr + image->offsets[0];
    213   uint8_t* src_uv = mem_byte_ptr + image->offsets[1];
    214   int src_stride_y = image->pitches[0];
    215   int src_stride_uv = image->pitches[1];
    216 
    217   uint8_t* dst_y = frame->data(media::VideoFrame::kYPlane);
    218   uint8_t* dst_u = frame->data(media::VideoFrame::kUPlane);
    219   uint8_t* dst_v = frame->data(media::VideoFrame::kVPlane);
    220   int dst_stride_y = frame->stride(media::VideoFrame::kYPlane);
    221   int dst_stride_u = frame->stride(media::VideoFrame::kUPlane);
    222   int dst_stride_v = frame->stride(media::VideoFrame::kVPlane);
    223 
    224   int rc = libyuv::NV12ToI420(src_y,
    225                               src_stride_y,
    226                               src_uv,
    227                               src_stride_uv,
    228                               dst_y,
    229                               dst_stride_y,
    230                               dst_u,
    231                               dst_stride_u,
    232                               dst_v,
    233                               dst_stride_v,
    234                               width,
    235                               height);
    236   CHECK_EQ(0, rc);
    237   return frame;
    238 }
    239 
    240 bool VaapiH264DecoderLoop::ProcessVideoFrame(
    241     const scoped_refptr<media::VideoFrame>& frame) {
    242   frame->HashFrameForTesting(&md5_context_);
    243 
    244   if (output_file_.empty())
    245     return true;
    246 
    247   for (size_t i = 0; i < media::VideoFrame::NumPlanes(frame->format()); i++) {
    248     int to_write = media::VideoFrame::PlaneAllocationSize(
    249         frame->format(), i, frame->coded_size());
    250     const char* buf = reinterpret_cast<const char*>(frame->data(i));
    251     int written = base::AppendToFile(output_file_, buf, to_write);
    252     if (written != 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   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   CommandLine* cmd_line = CommandLine::ForCurrentProcess();
    361   CHECK(cmd_line);
    362 
    363   CommandLine::SwitchMap switches = cmd_line->GetSwitches();
    364   for (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 }
    386