1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
// 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/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.h"
#include "media/base/container_names.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_t AVIOSeekOperation(void* opaque, int64_t offset, int whence) {
FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
int64_t new_offset = AVERROR(EIO);
switch (whence) {
case SEEK_SET:
if (protocol->SetPosition(offset))
protocol->GetPosition(&new_offset);
break;
case SEEK_CUR:
int64_t pos;
if (!protocol->GetPosition(&pos))
break;
if (protocol->SetPosition(pos + offset))
protocol->GetPosition(&new_offset);
break;
case SEEK_END:
int64_t 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;
}
void FFmpegGlue::InitializeFFmpeg() {
av_register_all();
}
FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol) {
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, nullptr, &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;
// Enable fast, but inaccurate seeks for MP3.
format_context_->flags |= AVFMT_FLAG_FAST_SEEK;
// Ensures we can read out various metadata bits like vp8 alpha.
format_context_->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;
// Ensures format parsing errors will bail out. From an audit on 11/2017, all
// instances were real failures. Solves bugs like http://crbug.com/710791.
format_context_->error_recognition |= AV_EF_EXPLODE;
format_context_->pb = avio_context_.get();
}
bool FFmpegGlue::OpenContext(bool is_local_file) {
DCHECK(!open_called_) << "OpenContext() shouldn'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 nullptr for the filename (second parameter) we are telling
// FFmpeg to use the AVIO context we setup from the AVFormatContext structure.
const int ret =
avformat_open_input(&format_context_, nullptr, nullptr, nullptr);
// If FFmpeg can't identify the file, read the first 8k and attempt to guess
// at the container type ourselves. This way we can track emergent formats.
// Only try on AVERROR_INVALIDDATA to avoid running after I/O errors.
if (ret == AVERROR_INVALIDDATA) {
std::vector<uint8_t> buffer(8192);
const int64_t pos = AVIOSeekOperation(avio_context_->opaque, 0, SEEK_SET);
if (pos < 0)
return false;
const int num_read =
AVIOReadOperation(avio_context_->opaque, buffer.data(), buffer.size());
if (num_read < container_names::kMinimumContainerSize)
return false;
container_ = container_names::DetermineContainer(buffer.data(), num_read);
base::UmaHistogramSparse("Media.DetectedContainer", container_);
if (is_local_file)
base::UmaHistogramSparse("Media.DetectedContainer.Local", container_);
detected_hls_ =
container_ == container_names::MediaContainerName::CONTAINER_HLS;
return false;
} else if (ret < 0) {
return false;
}
// Rely on ffmpeg's parsing if we're able to succesfully open the file.
if (strcmp(format_context_->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0)
container_ = container_names::CONTAINER_MOV;
else if (strcmp(format_context_->iformat->name, "flac") == 0)
container_ = container_names::CONTAINER_FLAC;
else if (strcmp(format_context_->iformat->name, "matroska,webm") == 0)
container_ = container_names::CONTAINER_WEBM;
else if (strcmp(format_context_->iformat->name, "ogg") == 0)
container_ = container_names::CONTAINER_OGG;
else if (strcmp(format_context_->iformat->name, "wav") == 0)
container_ = container_names::CONTAINER_WAV;
else if (strcmp(format_context_->iformat->name, "aac") == 0)
container_ = container_names::CONTAINER_AAC;
else if (strcmp(format_context_->iformat->name, "mp3") == 0)
container_ = container_names::CONTAINER_MP3;
else if (strcmp(format_context_->iformat->name, "amr") == 0)
container_ = container_names::CONTAINER_AMR;
else if (strcmp(format_context_->iformat->name, "avi") == 0)
container_ = container_names::CONTAINER_AVI;
DCHECK_NE(container_, container_names::CONTAINER_UNKNOWN);
base::UmaHistogramSparse("Media.DetectedContainer", container_);
return true;
}
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 nullptr 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;
}
avformat_close_input(&format_context_);
av_free(avio_context_->buffer);
}
} // namespace media
|