[MD settings] moving attached() code
[chromium-blink-merge.git] / media / filters / ffmpeg_glue.cc
blob8aee0bc655257b5f403a04aeca3fe95f49ed1d00
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.
5 #include "media/filters/ffmpeg_glue.h"
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"
14 namespace media {
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 };
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;
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;
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;
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;
55 case AVSEEK_SIZE:
56 protocol->GetSize(&new_offset);
57 break;
59 default:
60 NOTREACHED();
62 if (new_offset < 0)
63 new_offset = AVERROR(EIO);
64 return new_offset;
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;
73 case AV_LOCK_OBTAIN:
74 static_cast<base::Lock*>(*lock)->Acquire();
75 return 0;
77 case AV_LOCK_RELEASE:
78 static_cast<base::Lock*>(*lock)->Release();
79 return 0;
81 case AV_LOCK_DESTROY:
82 delete static_cast<base::Lock*>(*lock);
83 *lock = nullptr;
84 return 0;
86 return 1;
89 // FFmpeg must only be initialized once, so use a LazyInstance to ensure this.
90 class FFmpegInitializer {
91 public:
92 bool initialized() { return initialized_; }
94 private:
95 friend struct base::DefaultLazyInstanceTraits<FFmpegInitializer>;
97 FFmpegInitializer()
98 : initialized_(false) {
99 // Register our protocol glue code with FFmpeg.
100 if (av_lockmgr_register(&LockManagerOperation) != 0)
101 return;
103 // Now register the rest of FFmpeg.
104 av_register_all();
106 initialized_ = true;
109 ~FFmpegInitializer() {
110 NOTREACHED() << "FFmpegInitializer should be leaky!";
113 bool initialized_;
115 DISALLOW_COPY_AND_ASSIGN(FFmpegInitializer);
118 static base::LazyInstance<FFmpegInitializer>::Leaky g_lazy_instance =
119 LAZY_INSTANCE_INITIALIZER;
120 void FFmpegGlue::InitializeFFmpeg() {
121 // Get() will invoke the FFmpegInitializer constructor once.
122 CHECK(g_lazy_instance.Get().initialized());
125 FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol)
126 : open_called_(false) {
127 InitializeFFmpeg();
129 // Initialize an AVIOContext using our custom read and seek operations. Don't
130 // keep pointers to the buffer since FFmpeg may reallocate it on the fly. It
131 // will be cleaned up
132 format_context_ = avformat_alloc_context();
133 avio_context_.reset(avio_alloc_context(
134 static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0,
135 protocol, &AVIOReadOperation, nullptr, &AVIOSeekOperation));
137 // Ensure FFmpeg only tries to seek on resources we know to be seekable.
138 avio_context_->seekable =
139 protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL;
141 // Ensure writing is disabled.
142 avio_context_->write_flag = 0;
144 // Tell the format context about our custom IO context. avformat_open_input()
145 // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an
146 // early error state doesn't cause FFmpeg to free our resources in error.
147 format_context_->flags |= AVFMT_FLAG_CUSTOM_IO;
149 // Enable fast, but inaccurate seeks for MP3.
150 format_context_->flags |= AVFMT_FLAG_FAST_SEEK;
152 format_context_->pb = avio_context_.get();
155 bool FFmpegGlue::OpenContext() {
156 DCHECK(!open_called_) << "OpenContext() should't be called twice.";
158 // If avformat_open_input() is called we have to take a slightly different
159 // destruction path to avoid double frees.
160 open_called_ = true;
162 // Attempt to recognize the container by looking at the first few bytes of the
163 // stream. The stream position is left unchanged.
164 scoped_ptr<std::vector<uint8> > buffer(new std::vector<uint8>(8192));
166 int64 pos = AVIOSeekOperation(avio_context_.get()->opaque, 0, SEEK_CUR);
167 AVIOSeekOperation(avio_context_.get()->opaque, 0, SEEK_SET);
168 int numRead = AVIOReadOperation(
169 avio_context_.get()->opaque, buffer.get()->data(), buffer.get()->size());
170 AVIOSeekOperation(avio_context_.get()->opaque, pos, SEEK_SET);
171 if (numRead > 0) {
172 // < 0 means Read failed
173 container_names::MediaContainerName container =
174 container_names::DetermineContainer(buffer.get()->data(), numRead);
175 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedContainer", container);
178 // Use TOC when available to quickly seek MP3 file.
179 // TODO(dalecurtis): Remove this upon rolling ffmpeg. Commit c43bd08... will
180 // make setting AVFMT_FLAG_FAST_SEEK (which we already do) default usetoc = 1.
181 AVDictionary* options = nullptr;
182 av_dict_set(&options, "usetoc", "1", 0);
184 // By passing nullptr for the filename (second parameter) we are telling
185 // FFmpeg to use the AVIO context we setup from the AVFormatContext structure.
186 bool success =
187 avformat_open_input(&format_context_, nullptr, nullptr, &options) == 0;
189 if (options)
190 av_dict_free(&options);
192 return success;
195 FFmpegGlue::~FFmpegGlue() {
196 // In the event of avformat_open_input() failure, FFmpeg may sometimes free
197 // our AVFormatContext behind the scenes, but leave the buffer alive. It will
198 // helpfully set |format_context_| to nullptr in this case.
199 if (!format_context_) {
200 av_free(avio_context_->buffer);
201 return;
204 // If avformat_open_input() hasn't been called, we should simply free the
205 // AVFormatContext and buffer instead of using avformat_close_input().
206 if (!open_called_) {
207 avformat_free_context(format_context_);
208 av_free(avio_context_->buffer);
209 return;
212 // If avformat_open_input() has been called with this context, we need to
213 // close out any codecs/streams before closing the context.
214 if (format_context_->streams) {
215 for (int i = format_context_->nb_streams - 1; i >= 0; --i) {
216 AVStream* stream = format_context_->streams[i];
218 // The conditions for calling avcodec_close():
219 // 1. AVStream is alive.
220 // 2. AVCodecContext in AVStream is alive.
221 // 3. AVCodec in AVCodecContext is alive.
223 // Closing a codec context without prior avcodec_open2() will result in
224 // a crash in FFmpeg.
225 if (stream && stream->codec && stream->codec->codec) {
226 stream->discard = AVDISCARD_ALL;
227 avcodec_close(stream->codec);
232 avformat_close_input(&format_context_);
233 av_free(avio_context_->buffer);
236 } // namespace media