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