Home | History | Annotate | Download | only in filters
      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 #include "media/filters/ffmpeg_glue.h"
      6 
      7 #include "base/lazy_instance.h"
      8 #include "base/logging.h"
      9 #include "base/metrics/sparse_histogram.h"
     10 #include "base/synchronization/lock.h"
     11 #include "media/base/container_names.h"
     12 #include "media/ffmpeg/ffmpeg_common.h"
     13 
     14 namespace media {
     15 
     16 // Internal buffer size used by AVIO for reading.
     17 // TODO(dalecurtis): Experiment with this buffer size and measure impact on
     18 // performance.  Currently we want to use 32kb to preserve existing behavior
     19 // with the previous URLProtocol based approach.
     20 enum { kBufferSize = 32 * 1024 };
     21 
     22 static int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) {
     23   FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
     24   int result = protocol->Read(buf_size, buf);
     25   if (result < 0)
     26     result = AVERROR(EIO);
     27   return result;
     28 }
     29 
     30 static int64 AVIOSeekOperation(void* opaque, int64 offset, int whence) {
     31   FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
     32   int64 new_offset = AVERROR(EIO);
     33   switch (whence) {
     34     case SEEK_SET:
     35       if (protocol->SetPosition(offset))
     36         protocol->GetPosition(&new_offset);
     37       break;
     38 
     39     case SEEK_CUR:
     40       int64 pos;
     41       if (!protocol->GetPosition(&pos))
     42         break;
     43       if (protocol->SetPosition(pos + offset))
     44         protocol->GetPosition(&new_offset);
     45       break;
     46 
     47     case SEEK_END:
     48       int64 size;
     49       if (!protocol->GetSize(&size))
     50         break;
     51       if (protocol->SetPosition(size + offset))
     52         protocol->GetPosition(&new_offset);
     53       break;
     54 
     55     case AVSEEK_SIZE:
     56       protocol->GetSize(&new_offset);
     57       break;
     58 
     59     default:
     60       NOTREACHED();
     61   }
     62   if (new_offset < 0)
     63     new_offset = AVERROR(EIO);
     64   return new_offset;
     65 }
     66 
     67 static int LockManagerOperation(void** lock, enum AVLockOp op) {
     68   switch (op) {
     69     case AV_LOCK_CREATE:
     70       *lock = new base::Lock();
     71       return 0;
     72 
     73     case AV_LOCK_OBTAIN:
     74       static_cast<base::Lock*>(*lock)->Acquire();
     75       return 0;
     76 
     77     case AV_LOCK_RELEASE:
     78       static_cast<base::Lock*>(*lock)->Release();
     79       return 0;
     80 
     81     case AV_LOCK_DESTROY:
     82       delete static_cast<base::Lock*>(*lock);
     83       *lock = NULL;
     84       return 0;
     85   }
     86   return 1;
     87 }
     88 
     89 // FFmpeg must only be initialized once, so use a LazyInstance to ensure this.
     90 class FFmpegInitializer {
     91  public:
     92   bool initialized() { return initialized_; }
     93 
     94  private:
     95   friend struct base::DefaultLazyInstanceTraits<FFmpegInitializer>;
     96 
     97   FFmpegInitializer()
     98       : initialized_(false) {
     99     // Before doing anything disable logging as it interferes with layout tests.
    100     av_log_set_level(AV_LOG_QUIET);
    101 
    102     // Register our protocol glue code with FFmpeg.
    103     if (av_lockmgr_register(&LockManagerOperation) != 0)
    104       return;
    105 
    106     // Now register the rest of FFmpeg.
    107     av_register_all();
    108 
    109     initialized_ = true;
    110   }
    111 
    112   ~FFmpegInitializer() {
    113     NOTREACHED() << "FFmpegInitializer should be leaky!";
    114   }
    115 
    116   bool initialized_;
    117 
    118   DISALLOW_COPY_AND_ASSIGN(FFmpegInitializer);
    119 };
    120 
    121 void FFmpegGlue::InitializeFFmpeg() {
    122   static base::LazyInstance<FFmpegInitializer>::Leaky li =
    123       LAZY_INSTANCE_INITIALIZER;
    124   CHECK(li.Get().initialized());
    125 }
    126 
    127 FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol)
    128     : open_called_(false) {
    129   InitializeFFmpeg();
    130 
    131   // Initialize an AVIOContext using our custom read and seek operations.  Don't
    132   // keep pointers to the buffer since FFmpeg may reallocate it on the fly.  It
    133   // will be cleaned up
    134   format_context_ = avformat_alloc_context();
    135   avio_context_.reset(avio_alloc_context(
    136       static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0,
    137       protocol, &AVIOReadOperation, NULL, &AVIOSeekOperation));
    138 
    139   // Ensure FFmpeg only tries to seek on resources we know to be seekable.
    140   avio_context_->seekable =
    141       protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL;
    142 
    143   // Ensure writing is disabled.
    144   avio_context_->write_flag = 0;
    145 
    146   // Tell the format context about our custom IO context.  avformat_open_input()
    147   // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an
    148   // early error state doesn't cause FFmpeg to free our resources in error.
    149   format_context_->flags |= AVFMT_FLAG_CUSTOM_IO;
    150   format_context_->pb = avio_context_.get();
    151 }
    152 
    153 bool FFmpegGlue::OpenContext() {
    154   DCHECK(!open_called_) << "OpenContext() should't be called twice.";
    155 
    156   // If avformat_open_input() is called we have to take a slightly different
    157   // destruction path to avoid double frees.
    158   open_called_ = true;
    159 
    160   // Attempt to recognize the container by looking at the first few bytes of the
    161   // stream. The stream position is left unchanged.
    162   scoped_ptr<std::vector<uint8> > buffer(new std::vector<uint8>(8192));
    163 
    164   int64 pos = AVIOSeekOperation(avio_context_.get()->opaque, 0, SEEK_CUR);
    165   AVIOSeekOperation(avio_context_.get()->opaque, 0, SEEK_SET);
    166   int numRead = AVIOReadOperation(
    167       avio_context_.get()->opaque, buffer.get()->data(), buffer.get()->size());
    168   AVIOSeekOperation(avio_context_.get()->opaque, pos, SEEK_SET);
    169   if (numRead > 0) {
    170     // < 0 means Read failed
    171     container_names::MediaContainerName container =
    172         container_names::DetermineContainer(buffer.get()->data(), numRead);
    173     UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedContainer", container);
    174   }
    175 
    176   // By passing NULL for the filename (second parameter) we are telling FFmpeg
    177   // to use the AVIO context we setup from the AVFormatContext structure.
    178   return avformat_open_input(&format_context_, NULL, NULL, NULL) == 0;
    179 }
    180 
    181 FFmpegGlue::~FFmpegGlue() {
    182   // In the event of avformat_open_input() failure, FFmpeg may sometimes free
    183   // our AVFormatContext behind the scenes, but leave the buffer alive.  It will
    184   // helpfully set |format_context_| to NULL in this case.
    185   if (!format_context_) {
    186     av_free(avio_context_->buffer);
    187     return;
    188   }
    189 
    190   // If avformat_open_input() hasn't been called, we should simply free the
    191   // AVFormatContext and buffer instead of using avformat_close_input().
    192   if (!open_called_) {
    193     avformat_free_context(format_context_);
    194     av_free(avio_context_->buffer);
    195     return;
    196   }
    197 
    198   // If avformat_open_input() has been called with this context, we need to
    199   // close out any codecs/streams before closing the context.
    200   if (format_context_->streams) {
    201     for (int i = format_context_->nb_streams - 1; i >= 0; --i) {
    202       AVStream* stream = format_context_->streams[i];
    203 
    204       // The conditions for calling avcodec_close():
    205       // 1. AVStream is alive.
    206       // 2. AVCodecContext in AVStream is alive.
    207       // 3. AVCodec in AVCodecContext is alive.
    208       //
    209       // Closing a codec context without prior avcodec_open2() will result in
    210       // a crash in FFmpeg.
    211       if (stream && stream->codec && stream->codec->codec) {
    212         stream->discard = AVDISCARD_ALL;
    213         avcodec_close(stream->codec);
    214       }
    215     }
    216   }
    217 
    218   avformat_close_input(&format_context_);
    219   av_free(avio_context_->buffer);
    220 }
    221 
    222 }  // namespace media
    223