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"
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
);
26 result
= AVERROR(EIO
);
30 static int64
AVIOSeekOperation(void* opaque
, int64 offset
, int whence
) {
31 FFmpegURLProtocol
* protocol
= reinterpret_cast<FFmpegURLProtocol
*>(opaque
);
32 int64 new_offset
= AVERROR(EIO
);
35 if (protocol
->SetPosition(offset
))
36 protocol
->GetPosition(&new_offset
);
41 if (!protocol
->GetPosition(&pos
))
43 if (protocol
->SetPosition(pos
+ offset
))
44 protocol
->GetPosition(&new_offset
);
49 if (!protocol
->GetSize(&size
))
51 if (protocol
->SetPosition(size
+ offset
))
52 protocol
->GetPosition(&new_offset
);
56 protocol
->GetSize(&new_offset
);
63 new_offset
= AVERROR(EIO
);
67 static int LockManagerOperation(void** lock
, enum AVLockOp op
) {
70 *lock
= new base::Lock();
74 static_cast<base::Lock
*>(*lock
)->Acquire();
78 static_cast<base::Lock
*>(*lock
)->Release();
82 delete static_cast<base::Lock
*>(*lock
);
89 // FFmpeg must only be initialized once, so use a LazyInstance to ensure this.
90 class FFmpegInitializer
{
92 bool initialized() { return initialized_
; }
95 friend struct base::DefaultLazyInstanceTraits
<FFmpegInitializer
>;
98 : initialized_(false) {
99 // Before doing anything disable logging as it interferes with layout tests.
100 av_log_set_level(AV_LOG_QUIET
);
102 // Register our protocol glue code with FFmpeg.
103 if (av_lockmgr_register(&LockManagerOperation
) != 0)
106 // Now register the rest of FFmpeg.
112 ~FFmpegInitializer() {
113 NOTREACHED() << "FFmpegInitializer should be leaky!";
118 DISALLOW_COPY_AND_ASSIGN(FFmpegInitializer
);
121 static base::LazyInstance
<FFmpegInitializer
>::Leaky g_lazy_instance
=
122 LAZY_INSTANCE_INITIALIZER
;
123 void FFmpegGlue::InitializeFFmpeg() {
124 // Get() will invoke the FFmpegInitializer constructor once.
125 CHECK(g_lazy_instance
.Get().initialized());
128 FFmpegGlue::FFmpegGlue(FFmpegURLProtocol
* protocol
)
129 : open_called_(false) {
132 // Initialize an AVIOContext using our custom read and seek operations. Don't
133 // keep pointers to the buffer since FFmpeg may reallocate it on the fly. It
134 // will be cleaned up
135 format_context_
= avformat_alloc_context();
136 avio_context_
.reset(avio_alloc_context(
137 static_cast<unsigned char*>(av_malloc(kBufferSize
)), kBufferSize
, 0,
138 protocol
, &AVIOReadOperation
, NULL
, &AVIOSeekOperation
));
140 // Ensure FFmpeg only tries to seek on resources we know to be seekable.
141 avio_context_
->seekable
=
142 protocol
->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL
;
144 // Ensure writing is disabled.
145 avio_context_
->write_flag
= 0;
147 // Tell the format context about our custom IO context. avformat_open_input()
148 // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an
149 // early error state doesn't cause FFmpeg to free our resources in error.
150 format_context_
->flags
|= AVFMT_FLAG_CUSTOM_IO
;
151 format_context_
->pb
= avio_context_
.get();
154 bool FFmpegGlue::OpenContext() {
155 DCHECK(!open_called_
) << "OpenContext() should't be called twice.";
157 // If avformat_open_input() is called we have to take a slightly different
158 // destruction path to avoid double frees.
161 // Attempt to recognize the container by looking at the first few bytes of the
162 // stream. The stream position is left unchanged.
163 scoped_ptr
<std::vector
<uint8
> > buffer(new std::vector
<uint8
>(8192));
165 int64 pos
= AVIOSeekOperation(avio_context_
.get()->opaque
, 0, SEEK_CUR
);
166 AVIOSeekOperation(avio_context_
.get()->opaque
, 0, SEEK_SET
);
167 int numRead
= AVIOReadOperation(
168 avio_context_
.get()->opaque
, buffer
.get()->data(), buffer
.get()->size());
169 AVIOSeekOperation(avio_context_
.get()->opaque
, pos
, SEEK_SET
);
171 // < 0 means Read failed
172 container_names::MediaContainerName container
=
173 container_names::DetermineContainer(buffer
.get()->data(), numRead
);
174 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedContainer", container
);
177 // By passing NULL for the filename (second parameter) we are telling FFmpeg
178 // to use the AVIO context we setup from the AVFormatContext structure.
179 return avformat_open_input(&format_context_
, NULL
, NULL
, NULL
) == 0;
182 FFmpegGlue::~FFmpegGlue() {
183 // In the event of avformat_open_input() failure, FFmpeg may sometimes free
184 // our AVFormatContext behind the scenes, but leave the buffer alive. It will
185 // helpfully set |format_context_| to NULL in this case.
186 if (!format_context_
) {
187 av_free(avio_context_
->buffer
);
191 // If avformat_open_input() hasn't been called, we should simply free the
192 // AVFormatContext and buffer instead of using avformat_close_input().
194 avformat_free_context(format_context_
);
195 av_free(avio_context_
->buffer
);
199 // If avformat_open_input() has been called with this context, we need to
200 // close out any codecs/streams before closing the context.
201 if (format_context_
->streams
) {
202 for (int i
= format_context_
->nb_streams
- 1; i
>= 0; --i
) {
203 AVStream
* stream
= format_context_
->streams
[i
];
205 // The conditions for calling avcodec_close():
206 // 1. AVStream is alive.
207 // 2. AVCodecContext in AVStream is alive.
208 // 3. AVCodec in AVCodecContext is alive.
210 // Closing a codec context without prior avcodec_open2() will result in
211 // a crash in FFmpeg.
212 if (stream
&& stream
->codec
&& stream
->codec
->codec
) {
213 stream
->discard
= AVDISCARD_ALL
;
214 avcodec_close(stream
->codec
);
219 avformat_close_input(&format_context_
);
220 av_free(avio_context_
->buffer
);