blob: 3ccb481ea22ad76ea2758fd3c5deb9ae7d461d9a [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file implements the FFMPEGDispatch interface with dynamic loading of
// the libraries.
#include <dlfcn.h>
#include <pthread.h>
#include <map>
#include "starboard/common/log.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/common/string.h"
#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
#include "starboard/shared/starboard/lazy_initialization_internal.h"
namespace starboard {
namespace shared {
namespace ffmpeg {
namespace {
const char kAVCodecLibraryName[] = "libavcodec.so";
const char kAVFormatLibraryName[] = "libavformat.so";
const char kAVUtilLibraryName[] = "libavutil.so";
pthread_once_t g_dynamic_load_once = PTHREAD_ONCE_INIT;
struct LibraryMajorVersions {
int avcodec;
int avformat;
int avutil;
LibraryMajorVersions(int avcodec, int avformat, int avutil)
: avcodec(avcodec), avformat(avformat), avutil(avutil) {}
};
// Map containing the major version for all combinations of libraries in
// supported ffmpeg installations. The index is the specialization version
// matching the template parameter for the available specialization.
typedef std::map<int, LibraryMajorVersions> LibraryMajorVersionsMap;
class FFMPEGDispatchImpl {
public:
FFMPEGDispatchImpl();
~FFMPEGDispatchImpl();
static FFMPEGDispatchImpl* GetInstance();
bool RegisterSpecialization(int specialization,
int avcodec,
int avformat,
int avutil);
bool is_valid() const { return avcodec_ && avformat_ && avutil_; }
FFMPEGDispatch* get_ffmpeg_dispatch();
private:
pthread_mutex_t mutex_;
FFMPEGDispatch* ffmpeg_;
// Load the ffmpeg shared libraries, return true if successful.
bool OpenLibraries();
// Load the symbols from the shared libraries.
void LoadSymbols();
void* avcodec_;
void* avformat_;
void* avutil_;
LibraryMajorVersionsMap versions_;
};
FFMPEGDispatchImpl* g_ffmpeg_dispatch_impl;
void construct_ffmpeg_dispatch_impl() {
g_ffmpeg_dispatch_impl = new FFMPEGDispatchImpl();
}
FFMPEGDispatchImpl::FFMPEGDispatchImpl()
: mutex_(PTHREAD_MUTEX_INITIALIZER),
ffmpeg_(NULL),
avcodec_(NULL),
avformat_(NULL),
avutil_(NULL) {}
FFMPEGDispatchImpl::~FFMPEGDispatchImpl() {
delete ffmpeg_;
if (avformat_) {
dlclose(avformat_);
}
if (avcodec_) {
dlclose(avcodec_);
}
if (avutil_) {
dlclose(avutil_);
}
}
// static
FFMPEGDispatchImpl* FFMPEGDispatchImpl::GetInstance() {
bool initialized =
pthread_once(&g_dynamic_load_once, construct_ffmpeg_dispatch_impl) == 0;
SB_DCHECK(initialized);
return g_ffmpeg_dispatch_impl;
}
bool FFMPEGDispatchImpl::RegisterSpecialization(int specialization,
int avcodec,
int avformat,
int avutil) {
pthread_mutex_lock(&mutex_);
auto result = versions_.insert(std::make_pair(
specialization, LibraryMajorVersions(avcodec, avformat, avutil)));
bool success = result.second;
if (!success) {
// Element was not inserted because an entry with the same key already
// exists. Registration is still successful if the parameters are the same.
const LibraryMajorVersions& existing_versions = result.first->second;
success = existing_versions.avcodec == avcodec &&
existing_versions.avformat == avformat &&
existing_versions.avutil == avutil;
}
pthread_mutex_unlock(&mutex_);
return success;
}
FFMPEGDispatch* FFMPEGDispatchImpl::get_ffmpeg_dispatch() {
pthread_mutex_lock(&mutex_);
if (!ffmpeg_) {
ffmpeg_ = new FFMPEGDispatch();
// Dynamically load the libraries and retrieve the function pointers.
if (OpenLibraries()) {
LoadSymbols();
if (ffmpeg_->avformat_version() < kAVFormatDoesNotHaveRegisterAll) {
ffmpeg_->av_register_all();
}
}
}
pthread_mutex_unlock(&mutex_);
return ffmpeg_;
}
const int kMaxVersionedLibraryNameLength = 32;
std::string GetVersionedLibraryName(const char* name, const int version) {
char s[kMaxVersionedLibraryNameLength];
snprintf(s, kMaxVersionedLibraryNameLength, "%s.%d", name, version);
return std::string(s);
}
bool FFMPEGDispatchImpl::OpenLibraries() {
// Helper lambda to ensure all av libraries are closed and reset.
const auto reset_av_libraries = [this]() {
if (avformat_) {
dlclose(avformat_);
avformat_ = NULL;
}
if (avcodec_) {
dlclose(avcodec_);
avcodec_ = NULL;
}
if (avutil_) {
dlclose(avutil_);
avutil_ = NULL;
}
SB_DCHECK(!is_valid());
};
for (auto version_iterator = versions_.rbegin();
version_iterator != versions_.rend(); ++version_iterator) {
LibraryMajorVersions& versions = version_iterator->second;
std::string library_file =
GetVersionedLibraryName(kAVUtilLibraryName, versions.avutil);
avutil_ = dlopen(library_file.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!avutil_) {
SB_DLOG(WARNING) << "Unable to open shared library " << library_file;
reset_av_libraries();
continue;
}
library_file =
GetVersionedLibraryName(kAVCodecLibraryName, versions.avcodec);
avcodec_ = dlopen(library_file.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!avcodec_) {
SB_DLOG(WARNING) << "Unable to open shared library " << library_file;
reset_av_libraries();
continue;
}
library_file =
GetVersionedLibraryName(kAVFormatLibraryName, versions.avformat);
avformat_ = dlopen(library_file.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!avformat_) {
SB_DLOG(WARNING) << "Unable to open shared library " << library_file;
reset_av_libraries();
continue;
}
SB_DCHECK(is_valid());
break;
}
if (is_valid()) {
return true;
}
SB_LOG(WARNING) << "Failed to load FFMPEG libs with specified versions. "
<< "Trying to load FFMPEG libs without version numbers.";
// Attempt to load the libraries without a version number.
// This allows loading of the libraries on machines where a versioned and
// supported library is not available. Additionally, if this results in a
// library version being loaded that is not supported, then the decoder
// instantiation can output a more informative log message.
avutil_ = dlopen(kAVUtilLibraryName, RTLD_NOW | RTLD_GLOBAL);
if (!avutil_) {
SB_DLOG(WARNING) << "Unable to open shared library " << kAVUtilLibraryName;
reset_av_libraries();
return false;
}
avcodec_ = dlopen(kAVCodecLibraryName, RTLD_NOW | RTLD_GLOBAL);
if (!avcodec_) {
SB_DLOG(WARNING) << "Unable to open shared library " << kAVCodecLibraryName;
reset_av_libraries();
return false;
}
avformat_ = dlopen(kAVFormatLibraryName, RTLD_NOW | RTLD_GLOBAL);
if (!avformat_) {
SB_DLOG(WARNING) << "Unable to open shared library "
<< kAVFormatLibraryName;
reset_av_libraries();
return false;
}
SB_DCHECK(is_valid());
return true;
}
void FFMPEGDispatchImpl::LoadSymbols() {
SB_DCHECK(is_valid());
// Load the desired symbols from the shared libraries. Note: If a symbol is
// listed as a '.text' entry in the output of 'objdump -T' on the shared
// library file, then it is directly available from it.
char* errstr;
errstr = dlerror();
if (errstr != NULL) {
SB_LOG(INFO) << "LoadSymbols - checking dlerror shows:" << errstr;
}
#define INITSYMBOL(library, symbol) \
ffmpeg_->symbol = reinterpret_cast<decltype(FFMPEGDispatch::symbol)>( \
dlsym(library, #symbol)); \
errstr = dlerror(); \
if (errstr != NULL) { \
SB_LOG(INFO) << "Load Symbols ran into error:" << errstr; \
}
// Load symbols from the avutil shared library.
INITSYMBOL(avutil_, avutil_version);
SB_DCHECK(ffmpeg_->avutil_version);
SB_LOG(INFO) << "Opened libavutil - version is:"
<< ffmpeg_->avutil_version();
INITSYMBOL(avutil_, av_malloc);
INITSYMBOL(avutil_, av_freep);
INITSYMBOL(avutil_, av_free);
INITSYMBOL(avutil_, av_rescale_rnd);
INITSYMBOL(avutil_, av_samples_get_buffer_size);
INITSYMBOL(avutil_, av_opt_set_int);
INITSYMBOL(avutil_, av_image_check_size);
if (ffmpeg_->avutil_version() > kAVUtilSupportsBufferCreate) {
INITSYMBOL(avutil_, av_buffer_create);
}
// Load symbols from the avcodec shared library.
INITSYMBOL(avcodec_, avcodec_version);
SB_DCHECK(ffmpeg_->avcodec_version);
SB_LOG(INFO) << "Opened libavcodec - version is:"
<< ffmpeg_->avcodec_version();
if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) {
INITSYMBOL(avcodec_, av_frame_alloc);
INITSYMBOL(avcodec_, av_frame_unref);
INITSYMBOL(avcodec_, av_frame_free);
INITSYMBOL(avcodec_, av_dict_get);
} else {
INITSYMBOL(avcodec_, avcodec_alloc_frame);
INITSYMBOL(avcodec_, avcodec_get_frame_defaults);
}
INITSYMBOL(avcodec_, avcodec_alloc_context3);
if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvcodecFreeContext) {
INITSYMBOL(avcodec_, avcodec_free_context);
}
INITSYMBOL(avcodec_, avcodec_find_decoder);
INITSYMBOL(avcodec_, avcodec_close);
INITSYMBOL(avcodec_, avcodec_open2);
INITSYMBOL(avcodec_, av_init_packet);
if (ffmpeg_->avcodec_version() < kAVCodecHasUniformDecodeAPI) {
INITSYMBOL(avcodec_, avcodec_decode_audio4);
INITSYMBOL(avcodec_, avcodec_decode_video2);
} else {
INITSYMBOL(avcodec_, avcodec_send_packet);
INITSYMBOL(avcodec_, avcodec_receive_frame);
}
INITSYMBOL(avcodec_, avcodec_flush_buffers);
INITSYMBOL(avcodec_, avcodec_align_dimensions2);
if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvPacketAlloc) {
INITSYMBOL(avcodec_, av_packet_alloc);
INITSYMBOL(avcodec_, av_packet_free);
INITSYMBOL(avcodec_, av_packet_unref);
INITSYMBOL(avcodec_, avcodec_parameters_to_context);
} else {
INITSYMBOL(avcodec_, av_free_packet);
}
// Load symbols from the avformat shared library.
INITSYMBOL(avformat_, avformat_version);
SB_DCHECK(ffmpeg_->avformat_version);
SB_LOG(INFO) << "Opened libavformat - version is:"
<< ffmpeg_->avformat_version();
if (ffmpeg_->avformat_version() < kAVFormatDoesNotHaveRegisterAll) {
INITSYMBOL(avformat_, av_register_all);
SB_DCHECK(ffmpeg_->av_register_all);
}
INITSYMBOL(avformat_, av_read_frame);
INITSYMBOL(avformat_, av_seek_frame);
INITSYMBOL(avformat_, avformat_open_input);
INITSYMBOL(avformat_, avformat_close_input);
INITSYMBOL(avformat_, avformat_alloc_context);
INITSYMBOL(avformat_, avformat_find_stream_info);
INITSYMBOL(avformat_, avio_alloc_context);
#undef INITSYMBOL
}
} // namespace
FFMPEGDispatch::FFMPEGDispatch() {}
FFMPEGDispatch::~FFMPEGDispatch() {}
// static
FFMPEGDispatch* FFMPEGDispatch::GetInstance() {
return FFMPEGDispatchImpl::GetInstance()->get_ffmpeg_dispatch();
}
// static
bool FFMPEGDispatch::RegisterSpecialization(int specialization,
int avcodec,
int avformat,
int avutil) {
return FFMPEGDispatchImpl::GetInstance()->RegisterSpecialization(
specialization, avcodec, avformat, avutil);
}
bool FFMPEGDispatch::is_valid() const {
return FFMPEGDispatchImpl::GetInstance()->is_valid();
}
int FFMPEGDispatch::specialization_version() const {
// Return the specialization version, which is the major version of the
// avcodec library plus a single digit indicating if it's ffmpeg or libav,
// derived from the libavcodec micro version number.
return (avcodec_version() >> 16) * 10 + ((avcodec_version() & 0xFF) >= 100);
}
} // namespace ffmpeg
} // namespace shared
} // namespace starboard