/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FFmpegRuntimeLinker.h" #include "mozilla/ArrayUtils.h" #include "FFmpegLog.h" #include "mozilla/Preferences.h" #include "prlink.h" #ifdef XP_DARWIN #include "prenv.h" #include #include #include #endif /* XP_DARWIN */ namespace mozilla { FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus = LinkStatus_INIT; template class FFmpegDecoderModule { public: static already_AddRefed Create(); }; static const char* sLibs[] = { #if defined(XP_DARWIN) "libavcodec.57.dylib", "libavcodec.56.dylib", "libavcodec.55.dylib", "libavcodec.54.dylib", "libavcodec.53.dylib", #else "libavcodec-ffmpeg.so.57", "libavcodec-ffmpeg.so.56", "libavcodec.so.57", "libavcodec.so.56", "libavcodec.so.55", "libavcodec.so.54", "libavcodec.so.53", #endif }; #ifdef XP_DARWIN void* FFmpegRuntimeLinker::sLinkedLib = nullptr; #else PRLibrary* FFmpegRuntimeLinker::sLinkedLib = nullptr; #endif /* XP_DARWIN */ const char* FFmpegRuntimeLinker::sLib = nullptr; static unsigned (*avcodec_version)() = nullptr; #define AV_FUNC(func, ver) void (*func)(); #define LIBAVCODEC_ALLVERSION #include "FFmpegFunctionList.h" #undef LIBAVCODEC_ALLVERSION #undef AV_FUNC /* static */ bool FFmpegRuntimeLinker::Link() { if (sLinkStatus) { return sLinkStatus == LinkStatus_SUCCEEDED; } MOZ_ASSERT(NS_IsMainThread()); #ifdef XP_DARWIN // Explanation below. char execPath[PATH_MAX]; execPath[0] = '\0'; uint32_t pathlen = PATH_MAX; _NSGetExecutablePath(execPath, &pathlen); char *execDir = dirname(execPath); #endif /* XP_DARWIN */ for (size_t i = 0; i < ArrayLength(sLibs); i++) { const char* lib = sLibs[i]; #ifdef XP_DARWIN /* OlgaTPark's ffmpeg loader hack. Loading ffmpeg on Darwin fails because by default Mozilla searches for symbols defined in libavutil with a handle to libavcodec. This is due to the fact that NSPR uses NSAddressOfSymbol et al. that limit search only to libavcodec and not its dependencies. We don't have this issue with dlsym(). */ if (!(sLinkedLib = dlopen(lib, RTLD_NOW | RTLD_LOCAL))) { /* Bonus time: if we don't find libavcodec in standard locations, we look if our venerable ffmpeg's libraries are in the same folder as XUL. */ char *libFullPath = NULL; if (asprintf(&libFullPath, "%s/%s", execDir, lib) > 0 && libFullPath) { #if DEBUG fprintf(stderr, "TenFourFox looking for FFmpeg: %s\n", libFullPath); #endif sLinkedLib = dlopen(libFullPath, RTLD_NOW | RTLD_LOCAL); #if DEBUG if (!sLinkedLib) fprintf(stderr, "Failed to load %s: %s\n", libFullPath, dlerror()); #endif free(libFullPath); } // Try also finding the library in ~/Library/ffmpeg. if (!sLinkedLib && PR_GetEnv("HOME") && asprintf(&libFullPath, "%s/Library/ffmpeg/%s", PR_GetEnv("HOME"), lib) > 0 && libFullPath) { #if DEBUG fprintf(stderr, "TenFourFox looking for FFmpeg: %s\n", libFullPath); #endif sLinkedLib = dlopen(libFullPath, RTLD_NOW | RTLD_LOCAL); #if DEBUG if (!sLinkedLib) fprintf(stderr, "Failed to load %s: %s\n", libFullPath, dlerror()); #endif free(libFullPath); } } #else PRLibSpec lspec; lspec.type = PR_LibSpec_Pathname; lspec.value.pathname = lib; sLinkedLib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL); #endif /* XP_DARWIN */ if (sLinkedLib) { if (Bind(lib)) { sLib = lib; sLinkStatus = LinkStatus_SUCCEEDED; NS_WARNING("FFmpeg successfully linked to TenFourFox"); return true; } // Shouldn't happen but if it does then we try the next lib.. Unlink(); } } FFMPEG_LOG("H264/AAC codecs unsupported without ["); for (size_t i = 0; i < ArrayLength(sLibs); i++) { FFMPEG_LOG("%s %s", i ? "," : "", sLibs[i]); } FFMPEG_LOG(" ]\n"); Unlink(); fprintf(stderr, "Warning: FFmpeg could not be linked into TenFourFox. H.264 video will not be available.\n"); sLinkStatus = LinkStatus_FAILED; return false; } /* static */ bool FFmpegRuntimeLinker::Bind(const char* aLibName) { #ifdef XP_DARWIN avcodec_version = (typeof(avcodec_version))dlsym(sLinkedLib, #else avcodec_version = (typeof(avcodec_version))PR_FindSymbol(sLinkedLib, #endif /* XP_DARWIN */ "avcodec_version"); uint32_t fullVersion, major, minor, micro; fullVersion = GetVersion(major, minor, micro); if (!fullVersion) { return false; } if (micro < 100 && fullVersion < (54u << 16 | 35u << 8 | 1u) && !Preferences::GetBool("media.libavcodec.allow-obsolete", false)) { // Refuse any libavcodec version prior to 54.35.1. // (Unless media.libavcodec.allow-obsolete==true) Unlink(); LogToBrowserConsole(NS_LITERAL_STRING( "libavcodec may be vulnerable or is not supported, and should be updated to play video.")); return false; } int version; switch (major) { case 53: version = AV_FUNC_53; break; case 54: version = AV_FUNC_54; break; case 56: // We use libavcodec 55 code instead. Fallback case 55: version = AV_FUNC_55; break; case 57: if (micro < 100) { // a micro version >= 100 indicates that it's FFmpeg (as opposed to LibAV). // Due to current AVCodecContext binary incompatibility we can only // support FFmpeg at this stage. return false; } version = AV_FUNC_57; break; default: // Not supported at this stage. return false; } #define LIBAVCODEC_ALLVERSION #ifdef XP_DARWIN #define AV_FUNC(func, ver) \ if ((ver) & version) { \ if (!(func = (typeof(func))dlsym(sLinkedLib, #func))) { \ FFMPEG_LOG("Couldn't load function " #func " from %s.", aLibName); \ return false; \ } \ } else { \ func = (typeof(func))nullptr; \ } #else #define AV_FUNC(func, ver) \ if ((ver) & version) { \ if (!(func = (typeof(func))PR_FindSymbol(sLinkedLib, #func))) { \ FFMPEG_LOG("Couldn't load function " #func " from %s.", aLibName); \ return false; \ } \ } else { \ func = (typeof(func))nullptr; \ } #endif /* XP_DARWIN */ #include "FFmpegFunctionList.h" #undef AV_FUNC #undef LIBAVCODEC_ALLVERSION return true; } /* static */ already_AddRefed FFmpegRuntimeLinker::CreateDecoderModule() { if (!Link()) { return nullptr; } uint32_t major, minor, micro; if (!GetVersion(major, minor, micro)) { return nullptr; } RefPtr module; switch (major) { case 53: module = FFmpegDecoderModule<53>::Create(); break; case 54: module = FFmpegDecoderModule<54>::Create(); break; case 55: case 56: module = FFmpegDecoderModule<55>::Create(); break; case 57: module = FFmpegDecoderModule<57>::Create(); break; default: module = nullptr; } return module.forget(); } /* static */ void FFmpegRuntimeLinker::Unlink() { if (sLinkedLib) { #ifdef XP_DARWIN NS_WARNING("FFmpeg Runtime unlinked"); dlclose(sLinkedLib); #else PR_UnloadLibrary(sLinkedLib); #endif /* XP_DARWIN */ sLinkedLib = nullptr; sLib = nullptr; sLinkStatus = LinkStatus_INIT; avcodec_version = nullptr; } } /* static */ uint32_t FFmpegRuntimeLinker::GetVersion(uint32_t& aMajor, uint32_t& aMinor, uint32_t& aMicro) { if (!avcodec_version) { return 0u; } uint32_t version = avcodec_version(); aMajor = (version >> 16) & 0xff; aMinor = (version >> 8) & 0xff; aMicro = version & 0xff; return version; } } // namespace mozilla