| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/filters/ffmpeg_glue.h" |
| |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/synchronization/lock.h" |
| #include "media/ffmpeg/ffmpeg_common.h" |
| |
| namespace media { |
| |
| // Internal buffer size used by AVIO for reading. |
| // TODO(dalecurtis): Experiment with this buffer size and measure impact on |
| // performance. Currently we want to use 32kb to preserve existing behavior |
| // with the previous URLProtocol based approach. |
| enum { kBufferSize = 32 * 1024 }; |
| |
| static int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) { |
| FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque); |
| int result = protocol->Read(buf_size, buf); |
| if (result < 0) |
| result = AVERROR(EIO); |
| return result; |
| } |
| |
| static int64 AVIOSeekOperation(void* opaque, int64 offset, int whence) { |
| FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque); |
| int64 new_offset = AVERROR(EIO); |
| switch (whence) { |
| case SEEK_SET: |
| if (protocol->SetPosition(offset)) |
| protocol->GetPosition(&new_offset); |
| break; |
| |
| case SEEK_CUR: |
| int64 pos; |
| if (!protocol->GetPosition(&pos)) |
| break; |
| if (protocol->SetPosition(pos + offset)) |
| protocol->GetPosition(&new_offset); |
| break; |
| |
| case SEEK_END: |
| int64 size; |
| if (!protocol->GetSize(&size)) |
| break; |
| if (protocol->SetPosition(size + offset)) |
| protocol->GetPosition(&new_offset); |
| break; |
| |
| case AVSEEK_SIZE: |
| protocol->GetSize(&new_offset); |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| if (new_offset < 0) |
| new_offset = AVERROR(EIO); |
| return new_offset; |
| } |
| |
| static int LockManagerOperation(void** lock, enum AVLockOp op) { |
| switch (op) { |
| case AV_LOCK_CREATE: |
| *lock = new base::Lock(); |
| return 0; |
| |
| case AV_LOCK_OBTAIN: |
| static_cast<base::Lock*>(*lock)->Acquire(); |
| return 0; |
| |
| case AV_LOCK_RELEASE: |
| static_cast<base::Lock*>(*lock)->Release(); |
| return 0; |
| |
| case AV_LOCK_DESTROY: |
| delete static_cast<base::Lock*>(*lock); |
| *lock = NULL; |
| return 0; |
| } |
| return 1; |
| } |
| |
| // FFmpeg must only be initialized once, so use a LazyInstance to ensure this. |
| class FFmpegInitializer { |
| public: |
| bool initialized() { return initialized_; } |
| |
| private: |
| friend struct base::DefaultLazyInstanceTraits<FFmpegInitializer>; |
| |
| FFmpegInitializer() |
| : initialized_(false) { |
| // Before doing anything disable logging as it interferes with layout tests. |
| av_log_set_level(AV_LOG_QUIET); |
| |
| // Register our protocol glue code with FFmpeg. |
| if (av_lockmgr_register(&LockManagerOperation) != 0) |
| return; |
| |
| // Now register the rest of FFmpeg. |
| av_register_all(); |
| |
| initialized_ = true; |
| } |
| |
| ~FFmpegInitializer() { |
| NOTREACHED() << "FFmpegInitializer should be leaky!"; |
| } |
| |
| bool initialized_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FFmpegInitializer); |
| }; |
| |
| void FFmpegGlue::InitializeFFmpeg() { |
| static base::LazyInstance<FFmpegInitializer>::Leaky li = |
| LAZY_INSTANCE_INITIALIZER; |
| CHECK(li.Get().initialized()); |
| } |
| |
| FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol) |
| : open_called_(false) { |
| InitializeFFmpeg(); |
| |
| // Initialize an AVIOContext using our custom read and seek operations. Don't |
| // keep pointers to the buffer since FFmpeg may reallocate it on the fly. It |
| // will be cleaned up |
| format_context_ = avformat_alloc_context(); |
| avio_context_.reset(avio_alloc_context( |
| static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0, |
| protocol, &AVIOReadOperation, NULL, &AVIOSeekOperation)); |
| |
| // Ensure FFmpeg only tries to seek on resources we know to be seekable. |
| avio_context_->seekable = |
| protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL; |
| |
| // Ensure writing is disabled. |
| avio_context_->write_flag = 0; |
| |
| // Tell the format context about our custom IO context. avformat_open_input() |
| // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an |
| // early error state doesn't cause FFmpeg to free our resources in error. |
| format_context_->flags |= AVFMT_FLAG_CUSTOM_IO; |
| format_context_->pb = avio_context_.get(); |
| } |
| |
| bool FFmpegGlue::OpenContext() { |
| DCHECK(!open_called_) << "OpenContext() should't be called twice."; |
| |
| // If avformat_open_input() is called we have to take a slightly different |
| // destruction path to avoid double frees. |
| open_called_ = true; |
| |
| // By passing NULL for the filename (second parameter) we are telling FFmpeg |
| // to use the AVIO context we setup from the AVFormatContext structure. |
| return avformat_open_input(&format_context_, NULL, NULL, NULL) == 0; |
| } |
| |
| FFmpegGlue::~FFmpegGlue() { |
| // In the event of avformat_open_input() failure, FFmpeg may sometimes free |
| // our AVFormatContext behind the scenes, but leave the buffer alive. It will |
| // helpfully set |format_context_| to NULL in this case. |
| if (!format_context_) { |
| av_free(avio_context_->buffer); |
| return; |
| } |
| |
| // If avformat_open_input() hasn't been called, we should simply free the |
| // AVFormatContext and buffer instead of using avformat_close_input(). |
| if (!open_called_) { |
| avformat_free_context(format_context_); |
| av_free(avio_context_->buffer); |
| return; |
| } |
| |
| // If avformat_open_input() has been called with this context, we need to |
| // close out any codecs/streams before closing the context. |
| if (format_context_->streams) { |
| for (int i = format_context_->nb_streams - 1; i >= 0; --i) { |
| AVStream* stream = format_context_->streams[i]; |
| |
| // The conditions for calling avcodec_close(): |
| // 1. AVStream is alive. |
| // 2. AVCodecContext in AVStream is alive. |
| // 3. AVCodec in AVCodecContext is alive. |
| // |
| // Closing a codec context without prior avcodec_open2() will result in |
| // a crash in FFmpeg. |
| if (stream && stream->codec && stream->codec->codec) { |
| stream->discard = AVDISCARD_ALL; |
| avcodec_close(stream->codec); |
| } |
| } |
| } |
| |
| avformat_close_input(&format_context_); |
| av_free(avio_context_->buffer); |
| } |
| |
| } // namespace media |