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
14namespace 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.
20enum { kBufferSize = 32 * 1024 };
21
22static 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
30static 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
67static 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.
90class 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
121void FFmpegGlue::InitializeFFmpeg() {
122  static base::LazyInstance<FFmpegInitializer>::Leaky li =
123      LAZY_INSTANCE_INITIALIZER;
124  CHECK(li.Get().initialized());
125}
126
127FFmpegGlue::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
153bool 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
181FFmpegGlue::~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