Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/filters/ffmpeg_glue.h" | 5 #include "media/filters/ffmpeg_glue.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/stringprintf.h" | 8 #include "base/synchronization/lock.h" |
| 9 #include "media/ffmpeg/ffmpeg_common.h" | 9 #include "media/ffmpeg/ffmpeg_common.h" |
| 10 | 10 |
| 11 namespace media { | 11 namespace media { |
| 12 | 12 |
| 13 static FFmpegURLProtocol* ToProtocol(void* data) { | 13 // Internal buffer size used by AVIO for reading. |
| 14 return reinterpret_cast<FFmpegURLProtocol*>(data); | 14 // TODO(dalecurtis): Experiment with this buffer size and measure impact on |
| 15 } | 15 // performance. Currently we want to use 32kb to preserve existing behavior |
| 16 // with the previous URLProtocol based approach. | |
| 17 enum { kBufferSize = 32 * 1024 }; | |
| 16 | 18 |
| 17 // FFmpeg protocol interface. | 19 static int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) { |
| 18 static int OpenContext(URLContext* h, const char* filename, int flags) { | 20 FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque); |
| 19 FFmpegURLProtocol* protocol; | 21 int result = protocol->Read(buf_size, buf); |
| 20 FFmpegGlue::GetInstance()->GetProtocol(filename, &protocol); | |
| 21 if (!protocol) | |
| 22 return AVERROR(EIO); | |
| 23 | |
| 24 h->priv_data = protocol; | |
| 25 h->flags = AVIO_FLAG_READ; | |
| 26 h->is_streamed = protocol->IsStreaming(); | |
| 27 return 0; | |
| 28 } | |
| 29 | |
| 30 static int ReadContext(URLContext* h, unsigned char* buf, int size) { | |
| 31 FFmpegURLProtocol* protocol = ToProtocol(h->priv_data); | |
| 32 int result = protocol->Read(size, buf); | |
| 33 if (result < 0) | 22 if (result < 0) |
| 34 result = AVERROR(EIO); | 23 result = AVERROR(EIO); |
| 35 return result; | 24 return result; |
| 36 } | 25 } |
| 37 | 26 |
| 38 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 68, 0) | 27 static int64 AVIOSeekOperation(void* opaque, int64 offset, int whence) { |
| 39 static int WriteContext(URLContext* h, const unsigned char* buf, int size) { | 28 FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque); |
| 40 #else | |
| 41 static int WriteContext(URLContext* h, unsigned char* buf, int size) { | |
| 42 #endif | |
| 43 // We don't support writing. | |
| 44 return AVERROR(EIO); | |
| 45 } | |
| 46 | |
| 47 static int64 SeekContext(URLContext* h, int64 offset, int whence) { | |
| 48 FFmpegURLProtocol* protocol = ToProtocol(h->priv_data); | |
| 49 int64 new_offset = AVERROR(EIO); | 29 int64 new_offset = AVERROR(EIO); |
| 50 switch (whence) { | 30 switch (whence) { |
| 51 case SEEK_SET: | 31 case SEEK_SET: |
| 52 if (protocol->SetPosition(offset)) | 32 if (protocol->SetPosition(offset)) |
| 53 protocol->GetPosition(&new_offset); | 33 protocol->GetPosition(&new_offset); |
| 54 break; | 34 break; |
| 55 | 35 |
| 56 case SEEK_CUR: | 36 case SEEK_CUR: |
| 57 int64 pos; | 37 int64 pos; |
| 58 if (!protocol->GetPosition(&pos)) | 38 if (!protocol->GetPosition(&pos)) |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 74 break; | 54 break; |
| 75 | 55 |
| 76 default: | 56 default: |
| 77 NOTREACHED(); | 57 NOTREACHED(); |
| 78 } | 58 } |
| 79 if (new_offset < 0) | 59 if (new_offset < 0) |
| 80 new_offset = AVERROR(EIO); | 60 new_offset = AVERROR(EIO); |
| 81 return new_offset; | 61 return new_offset; |
| 82 } | 62 } |
| 83 | 63 |
| 84 static int CloseContext(URLContext* h) { | |
| 85 h->priv_data = NULL; | |
| 86 return 0; | |
| 87 } | |
| 88 | |
| 89 static int LockManagerOperation(void** lock, enum AVLockOp op) { | 64 static int LockManagerOperation(void** lock, enum AVLockOp op) { |
| 90 switch (op) { | 65 switch (op) { |
| 91 case AV_LOCK_CREATE: | 66 case AV_LOCK_CREATE: |
| 92 *lock = new base::Lock(); | 67 *lock = new base::Lock(); |
| 93 if (!*lock) | 68 if (!*lock) |
| 94 return 1; | 69 return 1; |
| 95 return 0; | 70 return 0; |
| 96 | 71 |
| 97 case AV_LOCK_OBTAIN: | 72 case AV_LOCK_OBTAIN: |
| 98 static_cast<base::Lock*>(*lock)->Acquire(); | 73 static_cast<base::Lock*>(*lock)->Acquire(); |
| 99 return 0; | 74 return 0; |
| 100 | 75 |
| 101 case AV_LOCK_RELEASE: | 76 case AV_LOCK_RELEASE: |
| 102 static_cast<base::Lock*>(*lock)->Release(); | 77 static_cast<base::Lock*>(*lock)->Release(); |
| 103 return 0; | 78 return 0; |
| 104 | 79 |
| 105 case AV_LOCK_DESTROY: | 80 case AV_LOCK_DESTROY: |
| 106 delete static_cast<base::Lock*>(*lock); | 81 delete static_cast<base::Lock*>(*lock); |
| 107 *lock = NULL; | 82 *lock = NULL; |
| 108 return 0; | 83 return 0; |
| 109 } | 84 } |
| 110 return 1; | 85 return 1; |
| 111 } | 86 } |
| 112 | 87 |
| 113 // Use the HTTP protocol to avoid any file path separator issues. | 88 static bool InitializeFFmpegInternal() { |
| 114 static const char kProtocol[] = "http"; | |
| 115 | |
| 116 // Fill out our FFmpeg protocol definition. | |
| 117 static URLProtocol kFFmpegURLProtocol = { | |
| 118 kProtocol, | |
| 119 &OpenContext, | |
| 120 NULL, // url_open2 | |
| 121 &ReadContext, | |
| 122 &WriteContext, | |
| 123 &SeekContext, | |
| 124 &CloseContext, | |
| 125 }; | |
| 126 | |
| 127 FFmpegGlue::FFmpegGlue() { | |
| 128 // Before doing anything disable logging as it interferes with layout tests. | 89 // Before doing anything disable logging as it interferes with layout tests. |
| 129 av_log_set_level(AV_LOG_QUIET); | 90 av_log_set_level(AV_LOG_QUIET); |
| 130 | 91 |
| 131 // Register our protocol glue code with FFmpeg. | 92 // Register our protocol glue code with FFmpeg. |
| 132 av_register_protocol2(&kFFmpegURLProtocol, sizeof(kFFmpegURLProtocol)); | 93 if (av_lockmgr_register(&LockManagerOperation) != 0) |
| 133 av_lockmgr_register(&LockManagerOperation); | 94 return false; |
| 134 | 95 |
| 135 // Now register the rest of FFmpeg. | 96 // Now register the rest of FFmpeg. |
| 136 av_register_all(); | 97 av_register_all(); |
| 98 return true; | |
| 99 } | |
| 100 | |
| 101 void FFmpegGlue::InitializeFFmpeg() { | |
| 102 // FFmpeg only needs to be initialized once. | |
| 103 static const bool kStatus = InitializeFFmpegInternal(); | |
| 104 CHECK(kStatus); | |
| 105 } | |
| 106 | |
| 107 FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol) | |
| 108 : open_called_(false) { | |
| 109 InitializeFFmpeg(); | |
| 110 | |
| 111 // Initialize an AVIOContext using our custom read and seek operations. Don't | |
| 112 // keep pointers to the buffer since FFmpeg may reallocate it on the fly. It | |
| 113 // will be cleaned up | |
| 114 format_context_ = avformat_alloc_context(); | |
| 115 avio_context_.reset(avio_alloc_context( | |
| 116 static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0, | |
| 117 protocol, &AVIOReadOperation, NULL, &AVIOSeekOperation)); | |
| 118 | |
| 119 // Ensure FFmpeg only tries to seek on resources we know to be seekable. | |
| 120 avio_context_->seekable = | |
| 121 protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL; | |
| 122 | |
| 123 // Ensure writing is disabled. | |
| 124 avio_context_->write_flag = 0; | |
| 125 | |
| 126 // Tell the format context about our custom IO context. avformat_open_input() | |
| 127 // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an | |
| 128 // early error state doesn't cause FFmpeg to free our resources in error. | |
| 129 format_context_->flags |= AVFMT_FLAG_CUSTOM_IO; | |
| 130 format_context_->pb = avio_context_.get(); | |
| 131 } | |
| 132 | |
| 133 int FFmpegGlue::OpenContext() { | |
| 134 // If avformat_open_input() is called we have to take a slightly different | |
| 135 // destruction path to avoid double frees. | |
| 136 open_called_ = true; | |
|
scherkus (not reviewing)
2012/10/02 00:20:56
do we need to worry about calling OpenContext() mu
DaleCurtis
2012/10/02 01:24:23
Good point. It shouldn't be done in any of our exi
| |
| 137 // By passing NULL for the filename (second parameter) we are telling FFmpeg | |
|
scherkus (not reviewing)
2012/10/02 00:20:56
blank line before
DaleCurtis
2012/10/02 01:24:23
Done.
| |
| 138 // to use the AVIO context we setup from the AVFormatContext structure. | |
| 139 return avformat_open_input(&format_context_, NULL, NULL, NULL); | |
| 137 } | 140 } |
| 138 | 141 |
| 139 FFmpegGlue::~FFmpegGlue() { | 142 FFmpegGlue::~FFmpegGlue() { |
| 140 av_lockmgr_register(NULL); | 143 // In the event of avformat_open_input() failure, FFmpeg may sometimes free |
| 141 } | 144 // our AVFormatContext behind the scenes, but leave the buffer alive. It will |
| 142 | 145 // helpfully set |format_context_| to NULL in this case. |
| 143 // static | 146 if (!format_context_) { |
| 144 FFmpegGlue* FFmpegGlue::GetInstance() { | 147 av_free(avio_context_->buffer); |
| 145 return Singleton<FFmpegGlue>::get(); | |
| 146 } | |
| 147 | |
| 148 // static | |
| 149 URLProtocol* FFmpegGlue::url_protocol() { | |
| 150 return &kFFmpegURLProtocol; | |
| 151 } | |
| 152 | |
| 153 std::string FFmpegGlue::AddProtocol(FFmpegURLProtocol* protocol) { | |
| 154 base::AutoLock auto_lock(lock_); | |
| 155 std::string key = GetProtocolKey(protocol); | |
| 156 if (protocols_.find(key) == protocols_.end()) { | |
| 157 protocols_[key] = protocol; | |
| 158 } | |
| 159 return key; | |
| 160 } | |
| 161 | |
| 162 void FFmpegGlue::RemoveProtocol(FFmpegURLProtocol* protocol) { | |
| 163 base::AutoLock auto_lock(lock_); | |
| 164 for (ProtocolMap::iterator cur, iter = protocols_.begin(); | |
| 165 iter != protocols_.end();) { | |
| 166 cur = iter; | |
| 167 iter++; | |
| 168 | |
| 169 if (cur->second == protocol) | |
| 170 protocols_.erase(cur); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 void FFmpegGlue::GetProtocol(const std::string& key, | |
| 175 FFmpegURLProtocol** protocol) { | |
| 176 base::AutoLock auto_lock(lock_); | |
| 177 ProtocolMap::iterator iter = protocols_.find(key); | |
| 178 if (iter == protocols_.end()) { | |
| 179 *protocol = NULL; | |
| 180 return; | 148 return; |
| 181 } | 149 } |
| 182 *protocol = iter->second; | |
| 183 } | |
| 184 | 150 |
| 185 std::string FFmpegGlue::GetProtocolKey(FFmpegURLProtocol* protocol) { | 151 // If avformat_open_input() hasn't been called, we should simply free the |
| 186 // Use the FFmpegURLProtocol's memory address to generate the unique string. | 152 // AVFormatContext and buffer instead of using avformat_close_input(). |
| 187 // This also has the nice property that adding the same FFmpegURLProtocol | 153 if (!open_called_) { |
| 188 // reference will not generate duplicate entries. | 154 avformat_free_context(format_context_); |
| 189 return base::StringPrintf("%s://%p", kProtocol, static_cast<void*>(protocol)); | 155 av_free(avio_context_->buffer); |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 // If avformat_open_input() has been called with this context, we need to | |
| 160 // close out any codecs/streams before closing the context. | |
| 161 if (format_context_->streams) { | |
| 162 for (int i = format_context_->nb_streams - 1; i >= 0; --i) { | |
| 163 AVStream* stream = format_context_->streams[i]; | |
| 164 | |
| 165 // The conditions for calling avcodec_close(): | |
| 166 // 1. AVStream is alive. | |
| 167 // 2. AVCodecContext in AVStream is alive. | |
| 168 // 3. AVCodec in AVCodecContext is alive. | |
| 169 // | |
| 170 // Closing a codec context without prior avcodec_open2() will result in | |
| 171 // a crash in FFmpeg. | |
| 172 if (stream && stream->codec && stream->codec->codec) { | |
| 173 stream->discard = AVDISCARD_ALL; | |
| 174 avcodec_close(stream->codec); | |
|
Ami GONE FROM CHROMIUM
2012/09/27 03:24:45
Ah crap - is this once again freeing out stuff fro
DaleCurtis
2012/09/27 03:46:12
I'll need to take a closer look at how FFmpegDemux
DaleCurtis
2012/09/28 23:17:41
Discussed offline. The problem is FFmpegDemuxer do
| |
| 175 } | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 avformat_close_input(&format_context_); | |
| 180 av_free(avio_context_->buffer); | |
| 190 } | 181 } |
| 191 | 182 |
| 192 } // namespace media | 183 } // namespace media |
| OLD | NEW |