Home | History | Annotate | Download | only in ffmpeg
      1 // Copyright (c) 2012 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 // ffmpeg_unittests verify that the parts of the FFmpeg API that Chromium uses
      6 // function as advertised for each media format that Chromium supports.  This
      7 // mostly includes stuff like reporting proper timestamps, seeking to
      8 // keyframes, and supporting certain features like reordered_opaque.
      9 //
     10 
     11 #include <limits>
     12 #include <queue>
     13 
     14 #include "base/base_paths.h"
     15 #include "base/file_util.h"
     16 #include "base/files/file_path.h"
     17 #include "base/files/memory_mapped_file.h"
     18 #include "base/memory/scoped_ptr.h"
     19 #include "base/path_service.h"
     20 #include "base/perftimer.h"
     21 #include "base/strings/string_util.h"
     22 #include "base/test/perf_test_suite.h"
     23 #include "media/base/media.h"
     24 #include "media/ffmpeg/ffmpeg_common.h"
     25 #include "media/filters/ffmpeg_glue.h"
     26 #include "media/filters/in_memory_url_protocol.h"
     27 #include "testing/gtest/include/gtest/gtest.h"
     28 
     29 int main(int argc, char** argv) {
     30   return base::PerfTestSuite(argc, argv).Run();
     31 }
     32 
     33 namespace media {
     34 
     35 // Mirror setting in ffmpeg_video_decoder.
     36 static const int kDecodeThreads = 2;
     37 
     38 class AVPacketQueue {
     39  public:
     40   AVPacketQueue() {
     41   }
     42 
     43   ~AVPacketQueue() {
     44     flush();
     45   }
     46 
     47   bool empty() {
     48     return packets_.empty();
     49   }
     50 
     51   AVPacket* peek() {
     52     return packets_.front();
     53   }
     54 
     55   void pop() {
     56     AVPacket* packet = packets_.front();
     57     packets_.pop();
     58     av_free_packet(packet);
     59     delete packet;
     60   }
     61 
     62   void push(AVPacket* packet) {
     63     av_dup_packet(packet);
     64     packets_.push(packet);
     65   }
     66 
     67   void flush() {
     68     while (!empty()) {
     69       pop();
     70     }
     71   }
     72 
     73  private:
     74   std::queue<AVPacket*> packets_;
     75 
     76   DISALLOW_COPY_AND_ASSIGN(AVPacketQueue);
     77 };
     78 
     79 // TODO(dalecurtis): We should really just use PipelineIntegrationTests instead
     80 // of a one-off step decoder so we're exercising the real pipeline.
     81 class FFmpegTest : public testing::TestWithParam<const char*> {
     82  protected:
     83   FFmpegTest()
     84       : av_format_context_(NULL),
     85         audio_stream_index_(-1),
     86         video_stream_index_(-1),
     87         decoded_audio_time_(AV_NOPTS_VALUE),
     88         decoded_audio_duration_(AV_NOPTS_VALUE),
     89         decoded_video_time_(AV_NOPTS_VALUE),
     90         decoded_video_duration_(AV_NOPTS_VALUE),
     91         duration_(AV_NOPTS_VALUE) {
     92     InitializeFFmpeg();
     93 
     94     audio_buffer_.reset(avcodec_alloc_frame());
     95     video_buffer_.reset(avcodec_alloc_frame());
     96   }
     97 
     98   virtual ~FFmpegTest() {
     99   }
    100 
    101   void OpenAndReadFile(const std::string& name) {
    102     OpenFile(name);
    103     OpenCodecs();
    104     ReadRemainingFile();
    105   }
    106 
    107   void OpenFile(const std::string& name) {
    108     base::FilePath path;
    109     PathService::Get(base::DIR_SOURCE_ROOT, &path);
    110     path = path.AppendASCII("media")
    111         .AppendASCII("test")
    112         .AppendASCII("data")
    113         .AppendASCII("content")
    114         .AppendASCII(name.c_str());
    115     EXPECT_TRUE(base::PathExists(path));
    116 
    117     CHECK(file_data_.Initialize(path));
    118     protocol_.reset(new InMemoryUrlProtocol(
    119         file_data_.data(), file_data_.length(), false));
    120     glue_.reset(new FFmpegGlue(protocol_.get()));
    121 
    122     ASSERT_TRUE(glue_->OpenContext()) << "Could not open " << path.value();
    123     av_format_context_ = glue_->format_context();
    124     ASSERT_LE(0, avformat_find_stream_info(av_format_context_, NULL))
    125         << "Could not find stream information for " << path.value();
    126 
    127     // Determine duration by picking max stream duration.
    128     for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
    129       AVStream* av_stream = av_format_context_->streams[i];
    130       int64 duration = ConvertFromTimeBase(
    131           av_stream->time_base, av_stream->duration).InMicroseconds();
    132       duration_ = std::max(duration_, duration);
    133     }
    134 
    135     // Final check to see if the container itself specifies a duration.
    136     AVRational av_time_base = {1, AV_TIME_BASE};
    137     int64 duration =
    138         ConvertFromTimeBase(av_time_base,
    139                             av_format_context_->duration).InMicroseconds();
    140     duration_ = std::max(duration_, duration);
    141   }
    142 
    143   void OpenCodecs() {
    144     for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
    145       AVStream* av_stream = av_format_context_->streams[i];
    146       AVCodecContext* av_codec_context = av_stream->codec;
    147       AVCodec* av_codec = avcodec_find_decoder(av_codec_context->codec_id);
    148 
    149       EXPECT_TRUE(av_codec)
    150           << "Could not find AVCodec with CodecID "
    151           << av_codec_context->codec_id;
    152 
    153       av_codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
    154       av_codec_context->thread_count = kDecodeThreads;
    155 
    156       EXPECT_EQ(0, avcodec_open2(av_codec_context, av_codec, NULL))
    157           << "Could not open AVCodecContext with CodecID "
    158           << av_codec_context->codec_id;
    159 
    160       if (av_codec->type == AVMEDIA_TYPE_AUDIO) {
    161         EXPECT_EQ(-1, audio_stream_index_) << "Found multiple audio streams.";
    162         audio_stream_index_ = static_cast<int>(i);
    163       } else if (av_codec->type == AVMEDIA_TYPE_VIDEO) {
    164         EXPECT_EQ(-1, video_stream_index_) << "Found multiple video streams.";
    165         video_stream_index_ = static_cast<int>(i);
    166       } else {
    167         ADD_FAILURE() << "Found unknown stream type.";
    168       }
    169     }
    170   }
    171 
    172   void Flush() {
    173     if (has_audio()) {
    174       audio_packets_.flush();
    175       avcodec_flush_buffers(av_audio_context());
    176     }
    177     if (has_video()) {
    178       video_packets_.flush();
    179       avcodec_flush_buffers(av_video_context());
    180     }
    181   }
    182 
    183   void ReadUntil(int64 time) {
    184     while (true) {
    185       scoped_ptr<AVPacket> packet(new AVPacket());
    186       if (av_read_frame(av_format_context_, packet.get()) < 0) {
    187         break;
    188       }
    189 
    190       int stream_index = static_cast<int>(packet->stream_index);
    191       int64 packet_time = AV_NOPTS_VALUE;
    192       if (stream_index == audio_stream_index_) {
    193         packet_time =
    194             ConvertFromTimeBase(av_audio_stream()->time_base, packet->pts)
    195                 .InMicroseconds();
    196         audio_packets_.push(packet.release());
    197       } else if (stream_index == video_stream_index_) {
    198         packet_time =
    199             ConvertFromTimeBase(av_video_stream()->time_base, packet->pts)
    200                 .InMicroseconds();
    201         video_packets_.push(packet.release());
    202       } else {
    203         ADD_FAILURE() << "Found packet that belongs to unknown stream.";
    204       }
    205 
    206       if (packet_time > time) {
    207         break;
    208       }
    209     }
    210   }
    211 
    212   void ReadRemainingFile() {
    213     ReadUntil(std::numeric_limits<int64>::max());
    214   }
    215 
    216   bool StepDecodeAudio() {
    217     EXPECT_TRUE(has_audio());
    218     if (!has_audio() || audio_packets_.empty()) {
    219       return false;
    220     }
    221 
    222     // Decode until output is produced, end of stream, or error.
    223     while (true) {
    224       int result = 0;
    225       int got_audio = 0;
    226       bool end_of_stream = false;
    227 
    228       AVPacket packet;
    229       if (audio_packets_.empty()) {
    230         av_init_packet(&packet);
    231         end_of_stream = true;
    232       } else {
    233         memcpy(&packet, audio_packets_.peek(), sizeof(packet));
    234       }
    235 
    236       avcodec_get_frame_defaults(audio_buffer_.get());
    237       result = avcodec_decode_audio4(av_audio_context(), audio_buffer_.get(),
    238                                      &got_audio, &packet);
    239       if (!audio_packets_.empty()) {
    240         audio_packets_.pop();
    241       }
    242 
    243       EXPECT_GE(result, 0) << "Audio decode error.";
    244       if (result < 0 || (got_audio == 0 && end_of_stream)) {
    245         return false;
    246       }
    247 
    248       if (result > 0) {
    249         double microseconds = 1.0L * audio_buffer_->nb_samples /
    250             av_audio_context()->sample_rate *
    251             base::Time::kMicrosecondsPerSecond;
    252         decoded_audio_duration_ = static_cast<int64>(microseconds);
    253 
    254         if (packet.pts == static_cast<int64>(AV_NOPTS_VALUE)) {
    255           EXPECT_NE(decoded_audio_time_, static_cast<int64>(AV_NOPTS_VALUE))
    256               << "We never received an initial timestamped audio packet! "
    257               << "Looks like there's a seeking/parsing bug in FFmpeg.";
    258           decoded_audio_time_ += decoded_audio_duration_;
    259         } else {
    260           decoded_audio_time_ =
    261               ConvertFromTimeBase(av_audio_stream()->time_base, packet.pts)
    262                   .InMicroseconds();
    263         }
    264         return true;
    265       }
    266     }
    267     return true;
    268   }
    269 
    270   bool StepDecodeVideo() {
    271     EXPECT_TRUE(has_video());
    272     if (!has_video() || video_packets_.empty()) {
    273       return false;
    274     }
    275 
    276     // Decode until output is produced, end of stream, or error.
    277     while (true) {
    278       int result = 0;
    279       int got_picture = 0;
    280       bool end_of_stream = false;
    281 
    282       AVPacket packet;
    283       if (video_packets_.empty()) {
    284         av_init_packet(&packet);
    285         end_of_stream = true;
    286       } else {
    287         memcpy(&packet, video_packets_.peek(), sizeof(packet));
    288       }
    289 
    290       avcodec_get_frame_defaults(video_buffer_.get());
    291       av_video_context()->reordered_opaque = packet.pts;
    292       result = avcodec_decode_video2(av_video_context(), video_buffer_.get(),
    293                                      &got_picture, &packet);
    294       if (!video_packets_.empty()) {
    295         video_packets_.pop();
    296       }
    297 
    298       EXPECT_GE(result, 0) << "Video decode error.";
    299       if (result < 0 || (got_picture == 0 && end_of_stream)) {
    300         return false;
    301       }
    302 
    303       if (got_picture) {
    304         AVRational doubled_time_base;
    305         doubled_time_base.den = av_video_stream()->r_frame_rate.num;
    306         doubled_time_base.num = av_video_stream()->r_frame_rate.den;
    307         doubled_time_base.den *= 2;
    308 
    309         decoded_video_time_ =
    310             ConvertFromTimeBase(av_video_stream()->time_base,
    311                              video_buffer_->reordered_opaque)
    312                 .InMicroseconds();
    313         decoded_video_duration_ =
    314             ConvertFromTimeBase(doubled_time_base,
    315                              2 + video_buffer_->repeat_pict)
    316                 .InMicroseconds();
    317         return true;
    318       }
    319     }
    320   }
    321 
    322   void DecodeRemainingAudio() {
    323     while (StepDecodeAudio()) {}
    324   }
    325 
    326   void DecodeRemainingVideo() {
    327     while (StepDecodeVideo()) {}
    328   }
    329 
    330   void SeekTo(double position) {
    331     int64 seek_time =
    332         static_cast<int64>(position * base::Time::kMicrosecondsPerSecond);
    333     int flags = AVSEEK_FLAG_BACKWARD;
    334 
    335     // Passing -1 as our stream index lets FFmpeg pick a default stream.
    336     // FFmpeg will attempt to use the lowest-index video stream, if present,
    337     // followed by the lowest-index audio stream.
    338     EXPECT_GE(0, av_seek_frame(av_format_context_, -1, seek_time, flags))
    339         << "Failed to seek to position " << position;
    340     Flush();
    341   }
    342 
    343   bool has_audio() { return audio_stream_index_ >= 0; }
    344   bool has_video() { return video_stream_index_ >= 0; }
    345   int64 decoded_audio_time() { return decoded_audio_time_; }
    346   int64 decoded_audio_duration() { return decoded_audio_duration_; }
    347   int64 decoded_video_time() { return decoded_video_time_; }
    348   int64 decoded_video_duration() { return decoded_video_duration_; }
    349   int64 duration() { return duration_; }
    350 
    351   AVStream* av_audio_stream() {
    352     return av_format_context_->streams[audio_stream_index_];
    353   }
    354   AVStream* av_video_stream() {
    355     return av_format_context_->streams[video_stream_index_];
    356   }
    357   AVCodecContext* av_audio_context() {
    358     return av_audio_stream()->codec;
    359   }
    360   AVCodecContext* av_video_context() {
    361     return av_video_stream()->codec;
    362   }
    363 
    364  private:
    365   void InitializeFFmpeg() {
    366     static bool initialized = false;
    367     if (initialized) {
    368       return;
    369     }
    370 
    371     base::FilePath path;
    372     PathService::Get(base::DIR_MODULE, &path);
    373     EXPECT_TRUE(InitializeMediaLibrary(path))
    374         << "Could not initialize media library.";
    375 
    376     initialized = true;
    377   }
    378 
    379   AVFormatContext* av_format_context_;
    380   int audio_stream_index_;
    381   int video_stream_index_;
    382   AVPacketQueue audio_packets_;
    383   AVPacketQueue video_packets_;
    384 
    385   scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_buffer_;
    386   scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_buffer_;
    387 
    388   int64 decoded_audio_time_;
    389   int64 decoded_audio_duration_;
    390   int64 decoded_video_time_;
    391   int64 decoded_video_duration_;
    392   int64 duration_;
    393 
    394   base::MemoryMappedFile file_data_;
    395   scoped_ptr<InMemoryUrlProtocol> protocol_;
    396   scoped_ptr<FFmpegGlue> glue_;
    397 
    398   DISALLOW_COPY_AND_ASSIGN(FFmpegTest);
    399 };
    400 
    401 #define FFMPEG_TEST_CASE(name, extension) \
    402     INSTANTIATE_TEST_CASE_P(name##_##extension, FFmpegTest, \
    403                             testing::Values(#name "." #extension));
    404 
    405 // Covers all our basic formats.
    406 FFMPEG_TEST_CASE(sync0, mp4);
    407 FFMPEG_TEST_CASE(sync0, ogv);
    408 FFMPEG_TEST_CASE(sync0, webm);
    409 FFMPEG_TEST_CASE(sync1, m4a);
    410 FFMPEG_TEST_CASE(sync1, mp3);
    411 FFMPEG_TEST_CASE(sync1, mp4);
    412 FFMPEG_TEST_CASE(sync1, ogg);
    413 FFMPEG_TEST_CASE(sync1, ogv);
    414 FFMPEG_TEST_CASE(sync1, webm);
    415 FFMPEG_TEST_CASE(sync2, m4a);
    416 FFMPEG_TEST_CASE(sync2, mp3);
    417 FFMPEG_TEST_CASE(sync2, mp4);
    418 FFMPEG_TEST_CASE(sync2, ogg);
    419 FFMPEG_TEST_CASE(sync2, ogv);
    420 FFMPEG_TEST_CASE(sync2, webm);
    421 
    422 // Covers our LayoutTest file.
    423 FFMPEG_TEST_CASE(counting, ogv);
    424 
    425 TEST_P(FFmpegTest, Perf) {
    426   {
    427     PerfTimeLogger timer("Opening file");
    428     OpenFile(GetParam());
    429   }
    430   {
    431     PerfTimeLogger timer("Opening codecs");
    432     OpenCodecs();
    433   }
    434   {
    435     PerfTimeLogger timer("Reading file");
    436     ReadRemainingFile();
    437   }
    438   if (has_audio()) {
    439     PerfTimeLogger timer("Decoding audio");
    440     DecodeRemainingAudio();
    441   }
    442   if (has_video()) {
    443     PerfTimeLogger timer("Decoding video");
    444     DecodeRemainingVideo();
    445   }
    446   {
    447     PerfTimeLogger timer("Seeking to zero");
    448     SeekTo(0);
    449   }
    450 }
    451 
    452 TEST_P(FFmpegTest, Loop_Audio) {
    453   OpenAndReadFile(GetParam());
    454   if (!has_audio()) {
    455     return;
    456   }
    457 
    458   const int kSteps = 4;
    459   std::vector<int64> expected_timestamps_;
    460   for (int i = 0; i < kSteps; ++i) {
    461     EXPECT_TRUE(StepDecodeAudio());
    462     expected_timestamps_.push_back(decoded_audio_time());
    463   }
    464 
    465   SeekTo(0);
    466   ReadRemainingFile();
    467 
    468   for (int i = 0; i < kSteps; ++i) {
    469     EXPECT_TRUE(StepDecodeAudio());
    470     EXPECT_EQ(expected_timestamps_[i], decoded_audio_time())
    471         << "Frame " << i << " had a mismatched timestamp.";
    472   }
    473 }
    474 
    475 TEST_P(FFmpegTest, Loop_Video) {
    476   OpenAndReadFile(GetParam());
    477   if (!has_video()) {
    478     return;
    479   }
    480 
    481   const int kSteps = 4;
    482   std::vector<int64> expected_timestamps_;
    483   for (int i = 0; i < kSteps; ++i) {
    484     EXPECT_TRUE(StepDecodeVideo());
    485     expected_timestamps_.push_back(decoded_video_time());
    486   }
    487 
    488   SeekTo(0);
    489   ReadRemainingFile();
    490 
    491   for (int i = 0; i < kSteps; ++i) {
    492     EXPECT_TRUE(StepDecodeVideo());
    493     EXPECT_EQ(expected_timestamps_[i], decoded_video_time())
    494         << "Frame " << i << " had a mismatched timestamp.";
    495   }
    496 }
    497 
    498 TEST_P(FFmpegTest, Seek_Audio) {
    499   OpenAndReadFile(GetParam());
    500   if (!has_audio() && duration() >= 0.5) {
    501     return;
    502   }
    503 
    504   SeekTo(duration() - 0.5);
    505   ReadRemainingFile();
    506 
    507   EXPECT_TRUE(StepDecodeAudio());
    508   EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_audio_time());
    509 }
    510 
    511 TEST_P(FFmpegTest, Seek_Video) {
    512   OpenAndReadFile(GetParam());
    513   if (!has_video() && duration() >= 0.5) {
    514     return;
    515   }
    516 
    517   SeekTo(duration() - 0.5);
    518   ReadRemainingFile();
    519 
    520   EXPECT_TRUE(StepDecodeVideo());
    521   EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_video_time());
    522 }
    523 
    524 TEST_P(FFmpegTest, Decode_Audio) {
    525   OpenAndReadFile(GetParam());
    526   if (!has_audio()) {
    527     return;
    528   }
    529 
    530   int64 last_audio_time = AV_NOPTS_VALUE;
    531   while (StepDecodeAudio()) {
    532     ASSERT_GT(decoded_audio_time(), last_audio_time);
    533     last_audio_time = decoded_audio_time();
    534   }
    535 }
    536 
    537 TEST_P(FFmpegTest, Decode_Video) {
    538   OpenAndReadFile(GetParam());
    539   if (!has_video()) {
    540     return;
    541   }
    542 
    543   int64 last_video_time = AV_NOPTS_VALUE;
    544   while (StepDecodeVideo()) {
    545     ASSERT_GT(decoded_video_time(), last_video_time);
    546     last_video_time = decoded_video_time();
    547   }
    548 }
    549 
    550 TEST_P(FFmpegTest, Duration) {
    551   OpenAndReadFile(GetParam());
    552 
    553   if (has_audio()) {
    554     DecodeRemainingAudio();
    555   }
    556 
    557   if (has_video()) {
    558     DecodeRemainingVideo();
    559   }
    560 
    561   double expected = static_cast<double>(duration());
    562   double actual = static_cast<double>(
    563       std::max(decoded_audio_time() + decoded_audio_duration(),
    564                decoded_video_time() + decoded_video_duration()));
    565   EXPECT_NEAR(expected, actual, 500000)
    566       << "Duration is off by more than 0.5 seconds.";
    567 }
    568 
    569 TEST_F(FFmpegTest, VideoPlayedCollapse) {
    570   OpenFile("test.ogv");
    571   OpenCodecs();
    572 
    573   SeekTo(0.5);
    574   ReadRemainingFile();
    575   EXPECT_TRUE(StepDecodeVideo());
    576   VLOG(1) << decoded_video_time();
    577 
    578   SeekTo(2.83);
    579   ReadRemainingFile();
    580   EXPECT_TRUE(StepDecodeVideo());
    581   VLOG(1) << decoded_video_time();
    582 
    583   SeekTo(0.4);
    584   ReadRemainingFile();
    585   EXPECT_TRUE(StepDecodeVideo());
    586   VLOG(1) << decoded_video_time();
    587 }
    588 
    589 }  // namespace media
    590