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