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/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 }
    387