- add sources.
[platform/framework/web/crosswalk.git] / src / media / filters / ffmpeg_glue.cc
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