From ec75fe23fe00236691fb40dd61ef7354d629a86a Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Tue, 13 Nov 2018 19:04:13 +0100 Subject: [PATCH] Initial code (pre-GitHub) Contains: - ffmpeg object wrappers - base encoder class - Apply ProRes encoder (prores_aw) - OBS plugin structure --- .clang-format | 99 ++++++ CMakeLists.txt | 389 ++++++++++++++++++++++ cmake/DownloadProject.CMakeLists.cmake.in | 17 + cmake/DownloadProject.cmake | 182 ++++++++++ cmake/cppcheck.cmake | 234 +++++++++++++ cmake/module.cpp.in | 17 + cmake/util.cmake | 19 ++ cmake/version.hpp.in | 98 ++++++ cmake/version.rc.in | 55 +++ data/locale/en-US.ini | 15 + source/encoder.cpp | 20 ++ source/encoder.hpp | 145 ++++++++ source/encoders/prores_aw.cpp | 343 +++++++++++++++++++ source/encoders/prores_aw.hpp | 80 +++++ source/ffmpeg/avframe-queue.cpp | 151 +++++++++ source/ffmpeg/avframe-queue.hpp | 70 ++++ source/ffmpeg/swscale.cpp | 196 +++++++++++ source/ffmpeg/swscale.hpp | 82 +++++ source/ffmpeg/tools.cpp | 126 +++++++ source/ffmpeg/tools.hpp | 44 +++ source/plugin.cpp | 55 +++ source/plugin.hpp | 30 ++ source/utility.cpp | 0 source/utility.hpp | 48 +++ 24 files changed, 2515 insertions(+) create mode 100644 .clang-format create mode 100644 CMakeLists.txt create mode 100644 cmake/DownloadProject.CMakeLists.cmake.in create mode 100644 cmake/DownloadProject.cmake create mode 100644 cmake/cppcheck.cmake create mode 100644 cmake/module.cpp.in create mode 100644 cmake/util.cmake create mode 100644 cmake/version.hpp.in create mode 100644 cmake/version.rc.in create mode 100644 data/locale/en-US.ini create mode 100644 source/encoder.cpp create mode 100644 source/encoder.hpp create mode 100644 source/encoders/prores_aw.cpp create mode 100644 source/encoders/prores_aw.hpp create mode 100644 source/ffmpeg/avframe-queue.cpp create mode 100644 source/ffmpeg/avframe-queue.hpp create mode 100644 source/ffmpeg/swscale.cpp create mode 100644 source/ffmpeg/swscale.hpp create mode 100644 source/ffmpeg/tools.cpp create mode 100644 source/ffmpeg/tools.hpp create mode 100644 source/plugin.cpp create mode 100644 source/plugin.hpp create mode 100644 source/utility.cpp create mode 100644 source/utility.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..37b9bdf --- /dev/null +++ b/.clang-format @@ -0,0 +1,99 @@ +# Basic Formatting +TabWidth: 8 +UseTab: ForIndentation +ColumnLimit: 120 + +# Language +Language: Cpp +Standard: Cpp11 + +# Indentation +AccessModifierOffset: 0 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +IndentCaseLabels: false +#IndentPPDirectives: true +IndentWidth: 8 +IndentWrappedFunctionNames: true +NamespaceIndentation: All + +# Includes +#IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<' + Priority: 1 + - Regex: '^"' + Priority: 2 +SortIncludes: true + +# Alignment +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +DerivePointerAlignment: false +PointerAlignment: Left + +# Wrapping and Breaking +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false +# AfterExternBlock: false + AfterFunction: true + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +#BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ConstructorInitializerAllOnOneLineOrOnePerLine: false +Cpp11BracedListStyle: true + +# Spaces +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCpp11BracedList: false +#SpaceBeforeCtorInitializerColon: true +#SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# Other +CommentPragmas: '^(!FIXME!|!TODO!|ToDo:)' +CompactNamespaces: false +DisableFormat: false +FixNamespaceComments: true +#ForEachMacros: '' +KeepEmptyLinesAtTheStartOfBlocks: false +ReflowComments: false +SortUsingDeclarations: true diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..60050b2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,389 @@ +# A Plugin that integrates the AMD AMF encoder into OBS Studio +# Copyright (C) 2016 - 2017 Michael Fabian Dirks +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +# CMake Setup +cmake_minimum_required(VERSION 3.1.0) +include("cmake/util.cmake") + +# Define Project +project( + enc-ffmpeg + VERSION 1.0.0.0 +) +set(PROJECT_FULL_NAME "FFMPEG Encoder for OBS Studio") +set(PROJECT_DESCRIPTION "Plugin for OBS Studio to add FFMPEG options for Recording and Streaming") +set(PROJECT_AUTHORS "Michael Fabian 'Xaymar' Dirks ") +set(PROJECT_COPYRIGHT_YEARS "2018") + +################################################################################ +# CMake / Compiler +################################################################################ + +# Detect Build Type +if("${CMAKE_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") + set(PropertyPrefix "") +else() + set(PropertyPrefix "${PROJECT_NAME}_") +endif() + +# Detect Architecture +math(EXPR BITS "8*${CMAKE_SIZEOF_VOID_P}") +if("${BITS}" STREQUAL "32") + set(ARCH "x86") +else() + set(ARCH "x64") +endif() + +# Configure Version Header +configure_file( + "${PROJECT_SOURCE_DIR}/cmake/version.hpp.in" + "${PROJECT_BINARY_DIR}/source/version.hpp" +) +configure_file( + "${PROJECT_SOURCE_DIR}/cmake/module.cpp.in" + "${PROJECT_BINARY_DIR}/source/module.cpp" +) + +# Windows Specific Resource Definition +if(WIN32) + set(PROJECT_PRODUCT_NAME "${PROJECT_FULL_NAME}") + set(PROJECT_COMPANY_NAME "${PROJECT_AUTHORS}") + set(PROJECT_COPYRIGHT "${PROJECT_AUTHORS} © ${PROJECT_COPYRIGHT_YEARS}") + set(PROJECT_LEGAL_TRADEMARKS_1 "") + set(PROJECT_LEGAL_TRADEMARKS_2 "") + + configure_file( + "${PROJECT_SOURCE_DIR}/cmake/version.rc.in" + "${PROJECT_BINARY_DIR}/cmake/version.rc" + @ONLY + ) +endif() + +# All Warnings, Extra Warnings, Pedantic +if(MSVC) + # Force to always compile with W4 + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() +elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # Update if necessary + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") +endif() + +math(EXPR BITS "8*${CMAKE_SIZEOF_VOID_P}") + +################################################################################ +# Options +################################################################################ +set(${PropertyPrefix}OBS_NATIVE FALSE CACHE BOOL "Use native obs-studio build" FORCE) +set(${PropertyPrefix}OBS_REFERENCE FALSE CACHE BOOL "Use referenced obs-studio build" FORCE) +set(${PropertyPrefix}OBS_PACKAGE FALSE CACHE BOOL "Use packaged obs-studio build" FORCE) +set(${PropertyPrefix}OBS_DOWNLOAD FALSE CACHE BOOL "Use downloaded obs-studio build" FORCE) +mark_as_advanced(FORCE OBS_NATIVE OBS_PACKAGE OBS_REFERENCE OBS_DOWNLOAD) + +if(NOT TARGET libobs) + set(${PropertyPrefix}OBS_STUDIO_DIR "" CACHE PATH "OBS Studio Source/Package Directory") + set(${PropertyPrefix}OBS_DOWNLOAD_VERSION "22.0.2" CACHE STRING "OBS Studio Version to download") +endif() + +if(NOT ${PropertyPrefix}OBS_NATIVE) + set(CMAKE_PACKAGE_PREFIX "${CMAKE_BINARY_DIR}" CACHE PATH "Path for generated archives.") + set(CMAKE_PACKAGE_NAME "${PROJECT_NAME}" CACHE STRING "Name for the generated archives.") + set(CMAKE_PACKAGE_SUFFIX_OVERRIDE "" CACHE STRING "Override for the suffix.") +endif() + +################################################################################ +# Dependencies +################################################################################ + +# Detect OBS Studio Type +if(TARGET libobs) + message(STATUS "${PROJECT_NAME}: Using native obs-studio.") + CacheSet(${PropertyPrefix}OBS_NATIVE TRUE) +else() + CacheSet(${PropertyPrefix}OBS_NATIVE FALSE) + if(EXISTS "${OBS_STUDIO_DIR}/cmake/LibObs/LibObsConfig.cmake") + message(STATUS "${PROJECT_NAME}: Using packaged obs-studio.") + CacheSet(${PropertyPrefix}OBS_PACKAGE TRUE) + elseif(EXISTS "${OBS_STUDIO_DIR}/libobs/obs-module.h") + message(STATUS "${PROJECT_NAME}: Using referenced obs-studio.") + CacheSet(${PropertyPrefix}OBS_REFERENCE TRUE) + else() + message(STATUS "${PROJECT_NAME}: No OBS Studio detected, using downloadable prebuilt binaries.") + CacheSet(${PropertyPrefix}OBS_DOWNLOAD TRUE) + set(${PropertyPrefix}OBS_DOWNLOAD_URL "https://github.com/Xaymar/obs-studio/releases/download/${OBS_DOWNLOAD_VERSION}/obs-studio-${ARCH}-vs2017.7z") + endif() +endif() + +# CMake Modules +if(${PropertyPrefix}OBS_DOWNLOAD) + include("cmake/DownloadProject.cmake") +endif() +if(NOT ${PropertyPrefix}OBS_NATIVE) + include("cmake/cppcheck.cmake") +endif() + +# Load OBS Studio +if(${PropertyPrefix}OBS_NATIVE) + Option(BUILD_FFMPEG_ENCODER "Build AMD Encoder module" ON) + if (NOT BUILD_FFMPEG_ENCODER) + message(STATUS "Not building AMD Encoder") + return() + endif() +elseif(${PropertyPrefix}OBS_PACKAGE) + include("${OBS_STUDIO_DIR}/cmake/LibObs/LibObsConfig.cmake") +elseif(${PropertyPrefix}OBS_REFERENCE) + set(obsPath "${OBS_STUDIO_DIR}") + include("${OBS_STUDIO_DIR}/cmake/external/Findlibobs.cmake") +elseif(${PropertyPrefix}OBS_DOWNLOAD) + download_project( + PROJ libobs + URL ${OBS_DOWNLOAD_URL} + UPDATE_DISCONNECTED 1 + ) + include("${libobs_SOURCE_DIR}/cmake/LibObs/LibObsConfig.cmake") +else() + message(CRITICAL "Impossible case reached, very system stability.") + return() +endif() + +# FFmpeg +find_package(FFmpeg REQUIRED COMPONENTS avutil avcodec swscale) + +################################################################################ +# Code +################################################################################ +set(PROJECT_PRIVATE + "${PROJECT_SOURCE_DIR}/source/encoder.cpp" + "${PROJECT_SOURCE_DIR}/source/encoder.hpp" + "${PROJECT_SOURCE_DIR}/source/plugin.cpp" + "${PROJECT_SOURCE_DIR}/source/plugin.hpp" + "${PROJECT_SOURCE_DIR}/source/utility.cpp" + "${PROJECT_SOURCE_DIR}/source/utility.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/prores_aw.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/prores_aw.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/prores_ks.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/prores_ks.cpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/avframe-queue.cpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/avframe-queue.hpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/swscale.hpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/swscale.cpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.hpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.cpp" +) +SET(PROJECT_GENERATED + "${PROJECT_BINARY_DIR}/source/module.cpp" + "${PROJECT_BINARY_DIR}/source/version.hpp" +) +set(PROJECT_DATA + "${PROJECT_SOURCE_DIR}/data/locale/en-US.ini" + "${PROJECT_SOURCE_DIR}/LICENSE" +) +set(PROJECT_LIBRARIES +) + +# Source Grouping +source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Data Files" FILES ${PROJECT_DATA}) +source_group(TREE "${PROJECT_SOURCE_DIR}/source" PREFIX "Private Files" FILES ${PROJECT_PRIVATE}) +source_group(TREE "${PROJECT_BINARY_DIR}/source" PREFIX "Generated Files" FILES ${PROJECT_GENERATED}) + +################################################################################ +# Target +################################################################################ + +add_library(${PROJECT_NAME} MODULE + ${PROJECT_GENERATED} + ${PROJECT_PRIVATE} + ${PROJECT_DATA} +) + +# Include Directories +target_include_directories(${PROJECT_NAME} + PUBLIC + PRIVATE + "${PROJECT_BINARY_DIR}/source" + "${PROJECT_SOURCE_DIR}/source" + ${FFMPEG_INCLUDE_DIRS} +) + +# OBS Studio +if(${PropertyPrefix}OBS_NATIVE) + target_link_libraries(${PROJECT_NAME} + libobs + ) +elseif(${PropertyPrefix}OBS_REFERENCE) + target_include_directories(${PROJECT_NAME} + PRIVATE + "${OBS_STUDIO_DIR}/libobs" + ) + target_link_libraries(${PROJECT_NAME} + "${LIBOBS_LIB}" + ) +elseif(${PropertyPrefix}OBS_PACKAGE) + target_include_directories(${PROJECT_NAME} + PRIVATE + "${OBS_STUDIO_DIR}/include" + ) + target_link_libraries(${PROJECT_NAME} + libobs + ) +elseif(${PropertyPrefix}OBS_DOWNLOAD) + target_link_libraries(${PROJECT_NAME} + libobs + ) +endif() + +# Link Libraries +target_link_libraries(${PROJECT_NAME} + "${PROJECT_LIBRARIES}" + ${FFMPEG_LIBRARIES} +) + +# Definitions +if (WIN32) + target_compile_definitions(${PROJECT_NAME} + PRIVATE + _CRT_SECURE_NO_WARNINGS + # windows.h + WIN32_LEAN_AND_MEAN + NOGPICAPMASKS + NOVIRTUALKEYCODES + #NOWINMESSAGES + NOWINSTYLES + NOSYSMETRICS + NOMENUS + NOICONS + NOKEYSTATES + NOSYSCOMMANDS + NORASTEROPS + NOSHOWWINDOW + NOATOM + NOCLIPBOARD + NOCOLOR + NOCTLMGR + NODRAWTEXT + #NOGDI + NOKERNEL + #NOUSER + #NONLS + NOMB + NOMEMMGR + NOMETAFILE + NOMINMAX + #NOMSG + NOOPENFILE + NOSCROLL + NOSERVICE + NOSOUND + #NOTEXTMETRIC + NOWH + NOWINOFFSETS + NOCOMM + NOKANJI + NOHELP + NOPROFILER + NODEFERWINDOWPOS + NOMCX + NOIME + NOMDI + NOINOUT + ) +endif() + +# File Version +if(WIN32) + set_target_properties( + ${PROJECT_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK} + ) +else() + set_target_properties( + ${PROJECT_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK} + ) +endif() + +# CPPCheck +if(NOT ${PropertyPrefix}OBS_NATIVE) + set(excludes ) + list(APPEND excludes "${PROJECT_SOURCE_DIR}/AMF") + if(${PropertyPrefix}OBS_REFERENCE) + list(APPEND excludes "${OBS_STUDIO_DIR}/libobs") + elseif(${PropertyPrefix}OBS_PACKAGE) + list(APPEND excludes "${OBS_STUDIO_DIR}/libobs") + elseif(${PropertyPrefix}OBS_DOWNLOAD) + list(APPEND excludes "${libobs_SOURCE_DIR}") + endif() + + cppcheck( + EXCLUDE ${excludes} + ) + cppcheck_add_project(${PROJECT_NAME}) +endif() + +################################################################################ +# Installation +################################################################################ + +if(${PropertyPrefix}OBS_NATIVE) + install_obs_plugin_with_data(${PROJECT_NAME} data) +else() + install( + TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION "./obs-plugins/${BITS}bit/" COMPONENT Runtime + LIBRARY DESTINATION "./obs-plugins/${BITS}bit/" COMPONENT Runtime + ) + if(MSVC) + install( + FILES $ + DESTINATION "./obs-plugins/${BITS}bit/" + OPTIONAL + ) + endif() + + install( + DIRECTORY "${PROJECT_SOURCE_DIR}/resources/" + DESTINATION "./data/obs-plugins/${PROJECT_NAME}/" + ) + + if("${CMAKE_PACKAGE_SUFFIX_OVERRIDE}" STREQUAL "") + set(PackageFullName "${CMAKE_PACKAGE_PREFIX}/${CMAKE_PACKAGE_NAME}-${PROJECT_VERSION}") + else() + set(PackageFullName "${CMAKE_PACKAGE_PREFIX}/${CMAKE_PACKAGE_NAME}-${CMAKE_PACKAGE_SUFFIX_OVERRIDE}") + endif() + + add_custom_target( + PACKAGE_7Z + ${CMAKE_COMMAND} -E tar cfv "${PackageFullName}.7z" --format=7zip -- + "${CMAKE_INSTALL_PREFIX}/obs-plugins" + "${CMAKE_INSTALL_PREFIX}/data" + WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}" + ) + add_custom_target( + PACKAGE_ZIP + ${CMAKE_COMMAND} -E tar cfv "${PackageFullName}.zip" --format=zip -- + "${CMAKE_INSTALL_PREFIX}/obs-plugins" + "${CMAKE_INSTALL_PREFIX}/data" + WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}" + ) +endif() diff --git a/cmake/DownloadProject.CMakeLists.cmake.in b/cmake/DownloadProject.CMakeLists.cmake.in new file mode 100644 index 0000000..89be4fd --- /dev/null +++ b/cmake/DownloadProject.CMakeLists.cmake.in @@ -0,0 +1,17 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. + +cmake_minimum_required(VERSION 2.8.2) + +project(${DL_ARGS_PROJ}-download NONE) + +include(ExternalProject) +ExternalProject_Add(${DL_ARGS_PROJ}-download + ${DL_ARGS_UNPARSED_ARGUMENTS} + SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" + BINARY_DIR "${DL_ARGS_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/DownloadProject.cmake b/cmake/DownloadProject.cmake new file mode 100644 index 0000000..e300f42 --- /dev/null +++ b/cmake/DownloadProject.cmake @@ -0,0 +1,182 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. +# +# MODULE: DownloadProject +# +# PROVIDES: +# download_project( PROJ projectName +# [PREFIX prefixDir] +# [DOWNLOAD_DIR downloadDir] +# [SOURCE_DIR srcDir] +# [BINARY_DIR binDir] +# [QUIET] +# ... +# ) +# +# Provides the ability to download and unpack a tarball, zip file, git repository, +# etc. at configure time (i.e. when the cmake command is run). How the downloaded +# and unpacked contents are used is up to the caller, but the motivating case is +# to download source code which can then be included directly in the build with +# add_subdirectory() after the call to download_project(). Source and build +# directories are set up with this in mind. +# +# The PROJ argument is required. The projectName value will be used to construct +# the following variables upon exit (obviously replace projectName with its actual +# value): +# +# projectName_SOURCE_DIR +# projectName_BINARY_DIR +# +# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically +# need to be provided. They can be specified if you want the downloaded source +# and build directories to be located in a specific place. The contents of +# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the +# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. +# +# The DOWNLOAD_DIR argument does not normally need to be set. It controls the +# location of the temporary CMake build used to perform the download. +# +# The PREFIX argument can be provided to change the base location of the default +# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments +# are provided, then PREFIX will have no effect. The default value for PREFIX is +# CMAKE_BINARY_DIR. +# +# The QUIET option can be given if you do not want to show the output associated +# with downloading the specified project. +# +# In addition to the above, any other options are passed through unmodified to +# ExternalProject_Add() to perform the actual download, patch and update steps. +# The following ExternalProject_Add() options are explicitly prohibited (they +# are reserved for use by the download_project() command): +# +# CONFIGURE_COMMAND +# BUILD_COMMAND +# INSTALL_COMMAND +# TEST_COMMAND +# +# Only those ExternalProject_Add() arguments which relate to downloading, patching +# and updating of the project sources are intended to be used. Also note that at +# least one set of download-related arguments are required. +# +# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to +# prevent a check at the remote end for changes every time CMake is run +# after the first successful download. See the documentation of the ExternalProject +# module for more information. It is likely you will want to use this option if it +# is available to you. Note, however, that the ExternalProject implementation contains +# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when +# using the URL download method or when specifying a SOURCE_DIR with no download +# method. Fixes for these have been created, the last of which is scheduled for +# inclusion in CMake 3.8.0. Details can be found here: +# +# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c +# https://gitlab.kitware.com/cmake/cmake/issues/16428 +# +# If you experience build errors related to the update step, consider avoiding +# the use of UPDATE_DISCONNECTED. +# +# EXAMPLE USAGE: +# +# include(DownloadProject) +# download_project(PROJ googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG master +# UPDATE_DISCONNECTED 1 +# QUIET +# ) +# +# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) +# +#======================================================================================== + + +set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") + +include(CMakeParseArguments) + +function(download_project) + + set(options QUIET) + set(oneValueArgs + PROJ + PREFIX + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + ) + set(multiValueArgs "") + + cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Hide output if requested + if (DL_ARGS_QUIET) + set(OUTPUT_QUIET "OUTPUT_QUIET") + else() + unset(OUTPUT_QUIET) + message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") + endif() + + # Set up where we will put our temporary CMakeLists.txt file and also + # the base point below which the default source and binary dirs will be. + # The prefix must always be an absolute path. + if (NOT DL_ARGS_PREFIX) + set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") + else() + get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if (NOT DL_ARGS_DOWNLOAD_DIR) + set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") + endif() + + # Ensure the caller can know where to find the source and build directories + if (NOT DL_ARGS_SOURCE_DIR) + set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") + endif() + if (NOT DL_ARGS_BINARY_DIR) + set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") + endif() + set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) + set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) + + # The way that CLion manages multiple configurations, it causes a copy of + # the CMakeCache.txt to be copied across due to it not expecting there to + # be a project within a project. This causes the hard-coded paths in the + # cache to be copied and builds to fail. To mitigate this, we simply + # remove the cache if it exists before we configure the new project. It + # is safe to do so because it will be re-generated. Since this is only + # executed at the configure step, it should not cause additional builds or + # downloads. + file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") + + # Create and build a separate CMake project to carry out the download. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project + # has this set to something not findable on the PATH. + configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" + "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" + -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" + . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + +endfunction() diff --git a/cmake/cppcheck.cmake b/cmake/cppcheck.cmake new file mode 100644 index 0000000..032fbd6 --- /dev/null +++ b/cmake/cppcheck.cmake @@ -0,0 +1,234 @@ +set(CPPCHECK_PROJECTS "") +set(CPPCHECK_ARGUMENTS "") +set(CPPCHECK_PLATFORM "") +set(CPPCHECK_LIBRARIES "") + +include(CMakeParseArguments) + +function(cppcheck) + set(CPPCHECK_PATH "" CACHE PATH "Path to cppcheck binary") + set(CPPCHECK_BIN "cppcheck.exe" CACHE STRING "CPPCheck Binary File") + set(CPPCHECK_ENABLE_INCONCLUSIVE OFF CACHE BOOL "Enable inconclusive checks?") + set(CPPCHECK_ENABLE_MISSING_INCLUDE ON CACHE BOOL "Check for missing includes?") + set(CPPCHECK_ENABLE_UNUSED_FUNCTION OFF CACHE BOOL "Check for unused functions?") + set(CPPCHECK_ENABLE_INFORMATION ON CACHE BOOL "Enable information messages?") + set(CPPCHECK_ENABLE_PORTABILITY ON CACHE BOOL "Enable portability messages?") + set(CPPCHECK_ENABLE_PERFORMANCE ON CACHE BOOL "Enable performance messages?") + set(CPPCHECK_ENABLE_WARNING ON CACHE BOOL "Enable warning messages?") + set(CPPCHECK_STD_POSIX OFF CACHE BOOL "POSIX Standard Compatibility Checks") + set(CPPCHECK_STD_C89 OFF CACHE BOOL "C89 Standard Compatibility Checks") + set(CPPCHECK_STD_C99 OFF CACHE BOOL "C99 Standard Compatibility Checks") + set(CPPCHECK_STD_C11 ON CACHE BOOL "C11 Standard Compatibility Checks") + set(CPPCHECK_STD_CPP03 OFF CACHE BOOL "C++03 Standard Compatibility Checks") + set(CPPCHECK_STD_CPP11 OFF CACHE BOOL "C++11 Standard Compatibility Checks") + set(CPPCHECK_STD_CPP14 ON CACHE BOOL "C++14 Standard Compatibility Checks") + set(CPPCHECK_FORCE_C OFF CACHE BOOL "Force checking with C language") + set(CPPCHECK_FORCE_CPP OFF CACHE BOOL "Force checking with C++ language (overrides CPPCHECK_FORCE_C)") + set(CPPCHECK_VERBOSE ON CACHE BOOL "Show more detailed error reports") + set(CPPCHECK_QUIET ON CACHE BOOL "Hide progress reports") + set(CPPCHECK_LIBRARIES "" CACHE STRING "List of Libraries to load separated by semicolon") + set(CPPCHECK_EXCLUDE_DIRECTORIES "" CACHE STRING "List of directories to exclude separated by semicolon") + set(CPPCHECK_PARALLEL_TASKS "4" CACHE STRING "Number of threads to use for cppcheck") + if(WIN32) + set(CPPCHECK_WIN32_UNICODE ON CACHE BOOL "Use Unicode character encoding for Win32") + endif() + + mark_as_advanced(CPPCHECK_BIN CPPCHECK_QUIET CPPCHECK_VERBOSE CPPCHECK_LIBRARIES CPPCHECK_ENABLE_INCONCLUSIVE CPPCHECK_PARALLEL_TASKS) + + # Parse arguments + set(cppcheck_options ) + set(cppcheck_oneval ) + set(cppcheck_mulval EXCLUDE) + cmake_parse_arguments( + CPPCHECKP + "${cppcheck_options}" + "${cppcheck_oneval}" + "${cppcheck_mulval}" + ${ARGN} + ) + + # Detect Architecture (Bitness) + math(EXPR BITS "8*${CMAKE_SIZEOF_VOID_P}") + + # Detect Platform + if(WIN32) + if(BITS EQUAL "32") + if(CPPCHECK_WIN32_UNICODE) + set(CPPCHECK_PLATFORM "win32W") + else() + set(CPPCHECK_PLATFORM "win32A") + endif() + else() + set(CPPCHECK_PLATFORM "win64") + endif() + elseif(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") OR ("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") OR APPLE) + if(BITS EQUAL "32") + set(CPPCHECK_PLATFORM "unix32") + else() + set(CPPCHECK_PLATFORM "unix64") + endif() + else() + set(CPPCHECK_PLATFORM "unspecified") + endif() + if(WIN32) + list(APPEND CPPCHECK_LIBRARIES "windows") + elseif("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") + list(APPEND CPPCHECK_LIBRARIES "bsd") + elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + list(APPEND CPPCHECK_LIBRARIES "gnu") + endif() + + # Arguments + set(CPPCHECK_ARGUMENTS "") + + # Compiler + if(MSVC) + #list(APPEND CPPCHECK_ARGUMENTS --template="{file}|{line}|{severity}|{id}|{message}") + endif() + + # Flags + if(CPPCHECK_ENABLE_INCONCLUSIVE) + list(APPEND CPPCHECK_ARGUMENTS --inconclusive) + endif() + if(CPPCHECK_VERBOSE) + list(APPEND CPPCHECK_ARGUMENTS -v) + endif() + if(CPPCHECK_QUIET) + list(APPEND CPPCHECK_ARGUMENTS -q) + endif() + if(CPPCHECK_PLATFORM) + list(APPEND CPPCHECK_ARGUMENTS --platform=${CPPCHECK_PLATFORM}) + endif() + if(CPPCHECK_PARALLEL_TASKS) + list(APPEND CPPCHECK_ARGUMENTS -j ${CPPCHECK_PARALLEL_TASKS}) + endif() + + # Libraries + foreach(_library ${CPPCHECK_LIBRARIES}) + list(APPEND CPPCHECK_ARGUMENTS --library=${_library}) + endforeach() + + # Exclusion + foreach(_path ${CPPCHECK_EXCLUDE_DIRECTORIES}) + file(TO_NATIVE_PATH "${_path}" _npath) + if(MSVC) + list(APPEND CPPCHECK_ARGUMENTS --suppress=*:${_npath}\\*) + else() + list(APPEND CPPCHECK_ARGUMENTS -i${_npath}) + endif() + endforeach() + if(CPPCHECKP_EXCLUDE) + foreach(_path ${CPPCHECKP_EXCLUDE}) + file(TO_NATIVE_PATH "${_path}" _npath) + if(MSVC) + list(APPEND CPPCHECK_ARGUMENTS --suppress=*:${_npath}\\*) + else() + list(APPEND CPPCHECK_ARGUMENTS -i${_npath}) + endif() + endforeach() + endif() + + # Checks + if(CPPCHECK_ENABLE_MISSING_INCLUDE) + list(APPEND CPPCHECK_ARGUMENTS --enable=missingInclude) + endif() + if(CPPCHECK_ENABLE_UNUSED_FUNCTION) + list(APPEND CPPCHECK_ARGUMENTS --enable=unusedFunction) + endif() + if(CPPCHECK_ENABLE_INFORMATION) + list(APPEND CPPCHECK_ARGUMENTS --enable=information) + endif() + if(CPPCHECK_ENABLE_PORTABILITY) + list(APPEND CPPCHECK_ARGUMENTS --enable=portability) + endif() + if(CPPCHECK_ENABLE_PERFORMANCE) + list(APPEND CPPCHECK_ARGUMENTS --enable=performance) + endif() + if(CPPCHECK_ENABLE_WARNING) + list(APPEND CPPCHECK_ARGUMENTS --enable=warning) + endif() + + # Std + if(CPPCHECK_STD_POSIX) + list(APPEND CPPCHECK_ARGUMENTS --std=posix) + endif() + if(CPPCHECK_STD_C89) + list(APPEND CPPCHECK_ARGUMENTS --std=c89) + endif() + if(CPPCHECK_STD_C99) + list(APPEND CPPCHECK_ARGUMENTS --std=c99) + endif() + if(CPPCHECK_STD_C11) + list(APPEND CPPCHECK_ARGUMENTS --std=c11) + endif() + if(CPPCHECK_STD_CPP03) + list(APPEND CPPCHECK_ARGUMENTS --std=c++03) + endif() + if(CPPCHECK_STD_CPP11) + list(APPEND CPPCHECK_ARGUMENTS --std=c++11) + endif() + if(CPPCHECK_STD_CPP14) + list(APPEND CPPCHECK_ARGUMENTS --std=c++14) + endif() + + # Force Language + if(CPPCHECK_FORCE_CPP) + list(APPEND CPPCHECK_ARGUMENTS --language=c++) + elseif(CPPCHECK_FORCE_C) + list(APPEND CPPCHECK_ARGUMENTS --language=c) + endif() + + add_custom_target( + CPPCHECK + ) + + # Propagate to parent scope + set(CPPCHECK_PROJECTS "${CPPCHECK_PROJECTS}" PARENT_SCOPE) + set(CPPCHECK_ARGUMENTS "${CPPCHECK_ARGUMENTS}" PARENT_SCOPE) + set(CPPCHECK_PLATFORM "${CPPCHECK_PLATFORM}" PARENT_SCOPE) + set(CPPCHECK_LIBRARIES "${CPPCHECK_LIBRARIES}" PARENT_SCOPE) +endfunction() + +function(cppcheck_add_project u_project) + list(APPEND CPPCHECK_PROJECTS ${u_project}) + + # Include Directories + get_target_property(_INCLUDE_DIRECTORIES ${u_project} INCLUDE_DIRECTORIES) + foreach(_path ${_INCLUDE_DIRECTORIES}) + file(TO_NATIVE_PATH "${_path}" _npath) + list(APPEND CPPCHECK_ARGUMENTS -I${_npath}) + endforeach() + + if(MSVC) + add_custom_target( + CPPCHECK_${u_project} + COMMAND "${CPPCHECK_PATH}/${CPPCHECK_BIN}" ${CPPCHECK_ARGUMENTS} --project=${${u_project}_BINARY_DIR}/${u_project}.sln + COMMAND_EXPAND_LISTS + VERBATIM + ) + else() + # Non-MSVC and Unix (Linux, FreeBSD, APPLE) need to have -I, -i, -D and -U specified manually. + # Each file can be added to --file-list= as a comma separated list. + + # Defines + get_target_property(_COMPILE_DEFINITIONS ${u_project} COMPILE_DEFINITIONS) + foreach(_def ${_COMPILE_DEFINITIONS}) + list(APPEND CPPCHECK_ARGUMENTS -D${_def}) + endforeach() + + # Source Files + get_target_property(_SOURCES ${u_project} SOURCES) + foreach(_path ${_SOURCES}) + file(TO_NATIVE_PATH "${_path}" _npath) + list(APPEND CPPCHECK_ARGUMENTS ${_npath}) + endforeach() + + add_custom_target( + CPPCHECK_${u_project} + COMMAND "${CPPCHECK_PATH}/${CPPCHECK_BIN}" ${CPPCHECK_ARGUMENTS} + COMMAND_EXPAND_LISTS + VERBATIM + ) + endif() + add_dependencies(CPPCHECK CPPCHECK_${u_project}) +endfunction() diff --git a/cmake/module.cpp.in b/cmake/module.cpp.in new file mode 100644 index 0000000..07e7967 --- /dev/null +++ b/cmake/module.cpp.in @@ -0,0 +1,17 @@ +#include +#include + +OBS_DECLARE_MODULE(); +OBS_MODULE_AUTHOR("@PROJECT_AUTHORS@"); +OBS_MODULE_USE_DEFAULT_LOCALE("@PROJECT_NAME@", "en-US"); + +MODULE_EXPORT const char* obs_module_name() +{ + return "@PROJECT_FULL_NAME@"; +} + + +MODULE_EXPORT const char* obs_module_description() +{ + return "@PROJECT_DESCRIPTION@"; +} diff --git a/cmake/util.cmake b/cmake/util.cmake new file mode 100644 index 0000000..c155cef --- /dev/null +++ b/cmake/util.cmake @@ -0,0 +1,19 @@ +function(cacheset Name Value) + get_property(V_ADVANCED CACHE "${Name}" PROPERTY ADVANCED) + get_property(V_TYPE CACHE "${Name}" PROPERTY TYPE) + get_property(V_HELPSTRING CACHE "${Name}" PROPERTY HELPSTRING) + set(${Name} ${Value} CACHE ${V_TYPE} ${V_HELPSTRING} FORCE) + if(${V_ADVANCED}) + mark_as_advanced(FORCE ${Name}) + endif() +endfunction() + +function(cacheclear Name) + get_property(V_ADVANCED CACHE "${Name}" PROPERTY ADVANCED) + get_property(V_TYPE CACHE "${Name}" PROPERTY TYPE) + get_property(V_HELPSTRING CACHE "${Name}" PROPERTY HELPSTRING) + set(${Name} 0 CACHE ${V_TYPE} ${V_HELPSTRING} FORCE) + if(${V_ADVANCED}) + mark_as_advanced(FORCE ${Name}) + endif() +endfunction() \ No newline at end of file diff --git a/cmake/version.hpp.in b/cmake/version.hpp.in new file mode 100644 index 0000000..fe6f99e --- /dev/null +++ b/cmake/version.hpp.in @@ -0,0 +1,98 @@ +#pragma once +#include +#include + +#define MAKE_VERSION(major,minor,patch,build) ( \ + ((uint64_t(major) << 48) & 0xFFFF) \ + ((uint64_t(minor) << 32) & 0xFFFF) \ + ((uint64_t(patch) << 16) & 0xFFFF) \ + ((uint64_t(build)) & 0xFFFF)) + +#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define PROJECT_VERSION_BUILD @PROJECT_VERSION_TWEAK@ + +#define PROJECT_NAME "@PROJECT_NAME@" +#define PROJECT_FULL_NAME "@PROJECT_FULL_NAME@" +#define PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" + +struct version { + union { + uint64_t full; + struct { + uint16_t major; + uint16_t minor; + uint16_t patch; + uint16_t build; + }; + }; + + inline bool operator==(version const& rhl) + { + return (rhl.full == this->full); + } + + inline bool operator!=(version const& rhl) + { + return (rhl.full != this->full); + } + + inline bool operator<(version const& rhl) + { + return (rhl.full < this->full); + } + + inline bool operator<=(version const& rhl) + { + return (rhl.full <= this->full); + } + + inline bool operator>(version const& rhl) + { + return (rhl.full > this->full); + } + + inline bool operator>=(version const& rhl) + { + return (rhl.full >= this->full); + } + + inline int32_t compare(version const& rhl, bool& major, bool& minor, bool& patch, bool& build) + { + if (major) { + int32_t diff = (this->major - rhl.major); + if (diff != 0) { + major = true; + minor = patch = build = false; + return diff; + } + } + if (minor) { + int32_t diff = (this->minor - rhl.minor); + if (diff != 0) { + minor = true; + major = patch = build = false; + return diff; + } + } + if (patch) { + int32_t diff = (this->patch - rhl.patch); + if (diff != 0) { + patch = true; + major = minor = build = false; + return diff; + } + } + if (build) { + int32_t diff = (this->build - rhl.build); + if (diff != 0) { + build = true; + major = minor = patch = false; + return diff; + } + } + major = minor = patch = build = false; + return 0; + } +}; diff --git a/cmake/version.rc.in b/cmake/version.rc.in new file mode 100644 index 0000000..2486111 --- /dev/null +++ b/cmake/version.rc.in @@ -0,0 +1,55 @@ +#pragma code_page(65001) +#include +#include + +#define VER_FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@ +#define VER_FILEVERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@PROJECT_VERSION_TWEAK@\0" + +#define VER_PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@ +#define VER_PRODUCTVERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@PROJECT_VERSION_TWEAK@\0" + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS (VER_DEBUG) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "@PROJECT_COMPANY_NAME@\0" + VALUE "FileDescription", "@PROJECT_DESCRIPTION@\0" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "@PROJECT_NAME@\0" + VALUE "LegalCopyright", "@PROJECT_COPYRIGHT@\0" + VALUE "LegalTrademarks1", "@PROJECT_LEGAL_TRADEMARKS_1@\0" + VALUE "LegalTrademarks2", "@PROJECT_LEGAL_TRADEMARKS_1@\0" + VALUE "OriginalFilename", "@PROJECT_NAME@\0" + VALUE "ProductName", "@PROJECT_PRODUCT_NAME@\0" + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + VALUE "Translation", 0x409, 1252 + + END +END \ No newline at end of file diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini new file mode 100644 index 0000000..e583f9c --- /dev/null +++ b/data/locale/en-US.ini @@ -0,0 +1,15 @@ +Bitrate="Bitrate" +Bitrate.Description="Bitrate in kbit/s" +KeyFrame.Type="Key Frame Type" +KeyFrame.Type.Frames="Frames" +KeyFrame.Type.Seconds="Seconds" +KeyFrame.Interval="Key Frame Interval" +KeyFrame.Interval.Description="Interval in which a Key Frame is placed." +CustomParameters="Custom Parameters" +CustomParameters.Description="Format: key1=val1 key2=val2 key3='val3 val4'" + +# ProRes +ProRes.Profile.Proxy="Proxy (PXY)" +ProRes.Profile.Light="Light (LT)" +ProRes.Profile.Standard="Standard" +ProRes.Profile.HighQuality="High Quality (HQ)" diff --git a/source/encoder.cpp b/source/encoder.cpp new file mode 100644 index 0000000..5cab23b --- /dev/null +++ b/source/encoder.cpp @@ -0,0 +1,20 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "encoder.hpp" + +obsffmpeg::encoder::base::~base() {} diff --git a/source/encoder.hpp b/source/encoder.hpp new file mode 100644 index 0000000..3331fa9 --- /dev/null +++ b/source/encoder.hpp @@ -0,0 +1,145 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_ENCODER_HPP +#define OBS_FFMPEG_ENCODER_HPP +#pragma once + +#include +#include +#include "ffmpeg/swscale.hpp" + +#include + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include "libavcodec/avcodec.h" +#include "libswscale/swscale.h" +#pragma warning(pop) +} + +namespace obsffmpeg { + namespace encoder { + class base { + protected: + obs_encoder_t* self = nullptr; + + AVCodec* avcodec = nullptr; + AVCodecContext* avcontext = nullptr; + AVDictionary* avdictionary = nullptr; + ffmpeg::swscale swscale; + + std::pair resolution; + std::pair framerate; + + AVPixelFormat source_format; + AVColorSpace source_colorspace; + AVColorRange source_range; + + AVPixelFormat target_format; + AVColorSpace target_colorspace; + AVColorRange target_range; + + public: + virtual ~base(); + + virtual void get_properties(obs_properties_t* props) = 0; + + virtual bool update(obs_data_t* settings) = 0; + + virtual bool get_extra_data(uint8_t** extra_data, size_t* size) = 0; + + virtual bool get_sei_data(uint8_t** sei_data, size_t* size) = 0; + + virtual void get_video_info(struct video_scale_info* info) = 0; + + virtual bool encode(struct encoder_frame* frame, struct encoder_packet* packet, + bool* received_packet) = 0; + }; + +#define make_encoder_base(_source, _target, _name, _codec) \ + static void* _source##_create(obs_data_t* settings, obs_encoder_t* encoder) \ + { \ + _target* ptr = nullptr; \ + try { \ + ptr = new _target(settings, encoder); \ + } catch (...) { \ + } \ + return ptr; \ + } \ + static void _source##_destroy(void* ptr) \ + { \ + delete static_cast<_target*>(ptr); \ + } \ + static const char* _source##_get_name(void*) \ + { \ + return _name; \ + } \ + static obs_properties_t* _source##_get_properties(void* ptr) \ + { \ + obs_properties_t* pr = _target::get_properties(); \ + if (ptr) { \ + static_cast<_target*>(ptr)->get_properties(pr); \ + } \ + return pr; \ + } \ + static void _source##_get_defaults(obs_data_t* settings) \ + { \ + _target::get_defaults(settings); \ + } \ + static bool _source##_get_extra_data(void* ptr, uint8_t** extra_data, size_t* size) \ + { \ + return static_cast<_target*>(ptr)->get_extra_data(extra_data, size); \ + } \ + static bool _source##_get_sei_data(void* ptr, uint8_t** sei_data, size_t* size) \ + { \ + return static_cast<_target*>(ptr)->get_sei_data(sei_data, size); \ + } \ + static void _source##_get_video_info(void* ptr, struct video_scale_info* info) \ + { \ + static_cast<_target*>(ptr)->get_video_info(info); \ + } \ + static bool _source##_encode(void* ptr, struct encoder_frame* frame, struct encoder_packet* packet, \ + bool* received_packet) \ + { \ + return static_cast<_target*>(ptr)->encode(frame, packet, received_packet); \ + } \ + static obs_encoder_info _source##_info; \ + static void _source##_initialize() \ + { \ + _source##_info.id = "obs-ffmpeg-encoder-" #_source; \ + _source##_info.type = OBS_ENCODER_VIDEO; \ + _source##_info.caps = 0; \ + _source##_info.codec = _codec; \ + _source##_info.create = _source##_create; \ + _source##_info.destroy = _source##_destroy; \ + _source##_info.get_name = _source##_get_name; \ + _source##_info.get_properties = _source##_get_properties; \ + _source##_info.get_defaults = _source##_get_defaults; \ + _source##_info.get_extra_data = _source##_get_extra_data; \ + _source##_info.get_sei_data = _source##_get_sei_data; \ + _source##_info.get_video_info = _source##_get_video_info; \ + _source##_info.encode = _source##_encode; \ + obs_register_encoder(&(_source##_info)); \ + } + //*/ + + } // namespace encoder +} // namespace obsffmpeg + +#endif OBS_FFMPEG_ENCODER_HPP diff --git a/source/encoders/prores_aw.cpp b/source/encoders/prores_aw.cpp new file mode 100644 index 0000000..88a77af --- /dev/null +++ b/source/encoders/prores_aw.cpp @@ -0,0 +1,343 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "prores_aw.hpp" +#include +#include +#include "ffmpeg/tools.hpp" +#include "utility.hpp" + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include "libavutil/dict.h" +#include "libavutil/frame.h" +#include "libavutil/opt.h" +#pragma warning(pop) +} + +#include + +#define T_PROFILE "ProRes.Profile" +#define T_PROFILE_(x) "ProRes.Profile." vstr(x) +#define T_CUSTOM "Custom" + +#define LOG_PREFIX "[prores_aw] " + +make_encoder_base(prores_aw, obsffmpeg::encoder::prores_aw, "ProRes (Anatoliy Wasserman) (FFMPEG)", "prores"); + +void obsffmpeg::encoder::prores_aw::initialize() +{ + auto avd = avcodec_find_encoder_by_name("prores_aw"); + if (!avd) { + PLOG_INFO("ProRes (Anatoliy Wasserman) not supported."); + return; + } + prores_aw_initialize(); + PLOG_INFO("ProRes (Anatoliy Wasserman) supported."); +} + +void obsffmpeg::encoder::prores_aw::finalize() +{ + //prores_aw_finalize(); +} + +void obsffmpeg::encoder::prores_aw::get_defaults(obs_data_t* settings) +{ + obs_data_set_default_int(settings, T_PROFILE, static_cast(profile::HighQuality)); +} + +obs_properties_t* obsffmpeg::encoder::prores_aw::get_properties() +{ + obs_properties_t* prs = obs_properties_create(); + obs_property_t* p = nullptr; + + p = obs_properties_add_list(prs, T_PROFILE, TRANSLATE(T_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(T_PROFILE))); + obs_property_list_add_int(p, TRANSLATE(T_PROFILE_(Proxy)), static_cast(profile::Proxy)); + obs_property_list_add_int(p, TRANSLATE(T_PROFILE_(Light)), static_cast(profile::Light)); + obs_property_list_add_int(p, TRANSLATE(T_PROFILE_(Standard)), static_cast(profile::Standard)); + obs_property_list_add_int(p, TRANSLATE(T_PROFILE_(HighQuality)), static_cast(profile::HighQuality)); + + p = obs_properties_add_text(prs, T_CUSTOM, TRANSLATE(T_CUSTOM), OBS_TEXT_DEFAULT); + obs_property_set_long_description(p, TRANSLATE(DESC(T_CUSTOM))); + + return prs; +} + +obsffmpeg::encoder::prores_aw::prores_aw(obs_data_t* settings, obs_encoder_t* encoder) +{ + this->self = encoder; + auto encvideo = obs_encoder_video(this->self); + auto voi = video_output_get_info(encvideo); + + // Options, Parameters, etc. + /// Resolution, Frame Rate + this->resolution.first = voi->width; + this->resolution.second = voi->height; + this->framerate.first = voi->fps_num; + this->framerate.second = voi->fps_den; + /// Source/Input + this->source_colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace); + this->source_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range); + this->source_format = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format); + /// Target/Output + this->target_colorspace = this->source_colorspace; + this->target_range = this->source_range; + switch (voi->format) { + case VIDEO_FORMAT_RGBA: + case VIDEO_FORMAT_BGRA: + case VIDEO_FORMAT_BGRX: + this->target_format = AV_PIX_FMT_YUV444P10; + this->video_profile = profile::FourFourFourFour; + break; + case VIDEO_FORMAT_I444: + case VIDEO_FORMAT_YVYU: + case VIDEO_FORMAT_YUY2: + case VIDEO_FORMAT_UYVY: + case VIDEO_FORMAT_I420: + case VIDEO_FORMAT_NV12: + this->video_profile = static_cast(obs_data_get_int(settings, T_PROFILE)); + if (this->video_profile == profile::FourFourFourFour) { + this->target_format = AV_PIX_FMT_YUV444P10; + } else { + this->target_format = AV_PIX_FMT_YUV422P10; + } + break; + } + + // Log Settings + PLOG_INFO(LOG_PREFIX + "Initializing encoder...\n\tResolution: %lux%lu\n\tFramerate: %lu/%lu (%.2lf fps)\n\tInput: %s %s " + "%s\n\tOutput: %s %s %s\n\tProfile: %ld", + this->resolution.first, this->resolution.second, this->framerate.first, this->framerate.second, + (double_t(this->framerate.first) / double_t(this->framerate.second)), + ffmpeg::tools::get_pixel_format_name(this->source_format), + ffmpeg::tools::get_color_space_name(this->source_colorspace), + swscale.is_source_full_range() ? "Full" : "Partial", + ffmpeg::tools::get_pixel_format_name(this->target_format), + ffmpeg::tools::get_color_space_name(this->target_colorspace), + swscale.is_target_full_range() ? "Full" : "Partial", static_cast(this->video_profile)); + + // prores_aw restriction + if (this->resolution.first % 2 == 1) { + PLOG_ERROR(LOG_PREFIX "Width must be a multiple of 2."); + throw std::exception(); + } + + // Quit if we for some reason can't find prores_aw anymore. + this->avcodec = avcodec_find_encoder_by_name("prores_aw"); + if (!this->avcodec) { + PLOG_ERROR(LOG_PREFIX "Failed to find encoder."); + throw std::exception(); + } + + this->avcontext = avcodec_alloc_context3(this->avcodec); + if (!this->avcontext) { + PLOG_ERROR(LOG_PREFIX "Failed to create context."); + throw std::exception(); + } + + // Apply Settings + /// Resolution + this->avcontext->width = this->resolution.first; + this->avcontext->height = this->resolution.second; + /// Framerate + this->avcontext->time_base.num = this->framerate.first; + this->avcontext->time_base.den = this->framerate.second; + this->avcontext->ticks_per_frame = 1; + /// GOP/Keyframe (ProRes is Intra only) + this->avcontext->gop_size = 0; + /// Color, Profile + this->avcontext->colorspace = this->target_colorspace; + this->avcontext->color_range = this->target_range; + this->avcontext->pix_fmt = this->target_format; + this->avcontext->profile = static_cast(this->video_profile); + /// Other + this->avcontext->field_order = AV_FIELD_PROGRESSIVE; + this->avcontext->strict_std_compliance = FF_COMPLIANCE_NORMAL; + this->avcontext->debug = 0; + /// Threading + this->avcontext->thread_type = FF_THREAD_FRAME; +#if defined(__cplusplus) + this->avcontext->thread_count = std::thread::hardware_concurrency(); +#elif defined(_WIN32) + { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + this->avcontext->thread_count = sysinfo.dwNumberOfProcessors; + } +#elif defined(_GNU) + this->avcontext->thread_count = 16; +#else + this->avcontext->thread_count = 16; +#endif + /// Dynamic Stuff + this->update(settings); + + // Open Encoder + int res = avcodec_open2(this->avcontext, this->avcodec, NULL); + if (res < 0) { + avcodec_free_context(&this->avcontext); + PLOG_ERROR(LOG_PREFIX "Failed to open codec: %s (%ld).", ffmpeg::tools::get_error_description(res), + res); + throw std::exception(); + } + + // Configure and initialize SWScale + swscale.set_source_size(voi->width, voi->height); + swscale.set_source_format(this->source_format); + swscale.set_source_color(this->source_range == AVCOL_RANGE_JPEG, this->source_colorspace); + swscale.set_target_size(voi->width, voi->height); + swscale.set_target_format(this->target_format); + swscale.set_target_color(this->target_range == AVCOL_RANGE_JPEG, this->target_colorspace); + + if (!swscale.initialize(SWS_FAST_BILINEAR)) { + PLOG_ERROR(LOG_PREFIX "Incompatible conversion parameters:\n\tInput: %s %s %s\n\tOutput: %s %s %s", + ffmpeg::tools::get_pixel_format_name(this->source_format), + ffmpeg::tools::get_color_space_name(this->source_colorspace), + swscale.is_source_full_range() ? "Full" : "Partial", + ffmpeg::tools::get_pixel_format_name(this->target_format), + ffmpeg::tools::get_color_space_name(this->target_colorspace), + swscale.is_target_full_range() ? "Full" : "Partial"); + throw std::exception(); + } + + frame_queue.set_pixel_format(this->avcontext->pix_fmt); + frame_queue.set_resolution(this->resolution.first, this->resolution.second); + frame_queue.precache(this->avcontext->thread_count / 2); + + this->current_packet = av_packet_alloc(); + if (!this->current_packet) { + PLOG_ERROR(LOG_PREFIX "Failed to allocated packet."); + throw std::exception(); + } + + PLOG_INFO(LOG_PREFIX "Encoder initialized."); +} + +obsffmpeg::encoder::prores_aw::~prores_aw() +{ + frame_queue.clear(); + + swscale.finalize(); + if (this->avcontext) { + avcodec_close(this->avcontext); + avcodec_free_context(&this->avcontext); + } +} + +void obsffmpeg::encoder::prores_aw::get_properties(obs_properties_t* props) {} + +bool obsffmpeg::encoder::prores_aw::update(obs_data_t* settings) +{ + return false; +} + +bool obsffmpeg::encoder::prores_aw::get_extra_data(uint8_t** extra_data, size_t* size) +{ + if (!this->avcontext->extradata) { + return false; + } + *extra_data = this->avcontext->extradata; + *size = this->avcontext->extradata_size; + return true; +} + +bool obsffmpeg::encoder::prores_aw::get_sei_data(uint8_t** sei_data, size_t* size) +{ + return false; +} + +void obsffmpeg::encoder::prores_aw::get_video_info(video_scale_info* info) +{ + return; +} + +bool obsffmpeg::encoder::prores_aw::encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet) +{ + int res = 0; + + { + ScopeProfiler sp_frame("frame"); + AVFrame* vframe = frame_queue.pop(); + vframe->pts = frame->pts; + + vframe->color_range = this->avcontext->color_range; + vframe->colorspace = this->avcontext->colorspace; + + { + ScopeProfiler profile("convert"); + res = swscale.convert(reinterpret_cast(frame->data), + reinterpret_cast(frame->linesize), 0, this->resolution.second, + vframe->data, vframe->linesize); + if (res <= 0) { + PLOG_ERROR(LOG_PREFIX "Failed to convert frame: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } + + { + ScopeProfiler profile("send"); + res = avcodec_send_frame(this->avcontext, vframe); + if (res < 0) { + PLOG_ERROR(LOG_PREFIX "Failed to encode frame: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } + + frame_queue_used.push(vframe); + } + + { + ScopeProfiler profile("receive"); + res = avcodec_receive_packet(this->avcontext, this->current_packet); + if (res < 0) { + if (res == AVERROR(EAGAIN)) { + *received_packet = false; + return true; + } else if (res == AVERROR(EOF)) { + return true; + } else { + PLOG_ERROR(LOG_PREFIX "Failed to receive packet: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } else { + AVFrame* uframe = frame_queue_used.pop_only(); + if (uframe) { + if (frame_queue.empty()) { + frame_queue.push(uframe); + } else { + av_frame_free(&uframe); + } + } + packet->type = OBS_ENCODER_VIDEO; + packet->pts = this->current_packet->pts; + packet->dts = this->current_packet->pts; + packet->data = this->current_packet->data; + packet->size = this->current_packet->size; + packet->keyframe = true; // There are only keyframes in ProRes (Intra Only) + packet->drop_priority = 0; + *received_packet = true; + } + } + + return true; +} diff --git a/source/encoders/prores_aw.hpp b/source/encoders/prores_aw.hpp new file mode 100644 index 0000000..a63340e --- /dev/null +++ b/source/encoders/prores_aw.hpp @@ -0,0 +1,80 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_ENCODER_PRORES_AW +#define OBS_FFMPEG_ENCODER_PRORES_AW +#pragma once + +#include +#include "ffmpeg/avframe-queue.hpp" +#include + +extern "C" { +#include +} + +namespace obsffmpeg { + namespace encoder { + class prores_aw : base { + enum class profile { + Auto = FF_PROFILE_UNKNOWN, + Proxy = 0 /*FF_PROFILE_PRORES_PROXY*/, + Light = 1 /*FF_PROFILE_PRORES_LT*/, + Standard = 2 /*FF_PROFILE_PRORES_STANDARD*/, + HighQuality = 3 /*FF_PROFILE_PRORES_HQ*/, + FourFourFourFour = 4 /*FF_PROFILE_PRORES_4444*/ // Automatically set if I444 or RGB input. + }; + + private: + profile video_profile = profile::Auto; + ffmpeg::avframe_queue frame_queue; + ffmpeg::avframe_queue frame_queue_used; + AVPacket* current_packet = nullptr; + + + + public: + prores_aw(obs_data_t* settings, obs_encoder_t* encoder); + + ~prores_aw(); + + virtual void get_properties(obs_properties_t* props) override; + + virtual bool update(obs_data_t* settings) override; + + virtual bool get_extra_data(uint8_t** extra_data, size_t* size) override; + + virtual bool get_sei_data(uint8_t** sei_data, size_t* size) override; + + virtual void get_video_info(video_scale_info* info) override; + + virtual bool encode(encoder_frame* frame, encoder_packet* packet, + bool* received_packet) override; + + public: + static void initialize(); + + static void finalize(); + + static void get_defaults(obs_data_t* settings); + + static obs_properties_t* get_properties(); + }; + } // namespace encoder +} // namespace obsffmpeg + +#endif OBS_FFMPEG_ENCODER_PRORES_AW diff --git a/source/ffmpeg/avframe-queue.cpp b/source/ffmpeg/avframe-queue.cpp new file mode 100644 index 0000000..7fa5980 --- /dev/null +++ b/source/ffmpeg/avframe-queue.cpp @@ -0,0 +1,151 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "avframe-queue.hpp" +#include "tools.hpp" + +AVFrame* ffmpeg::avframe_queue::create_frame() +{ + AVFrame* frame = av_frame_alloc(); + frame->width = this->resolution.first; + frame->height = this->resolution.second; + frame->format = this->format; + int res = av_frame_get_buffer(frame, 0); + if (res < 0) { + throw std::exception(ffmpeg::tools::get_error_description(res)); + } + + return frame; +} + +void ffmpeg::avframe_queue::destroy_frame(AVFrame* frame) +{ + if (frame == nullptr) + return; + + av_frame_free(&frame); +} + +ffmpeg::avframe_queue::avframe_queue() {} + +ffmpeg::avframe_queue::~avframe_queue() +{ + clear(); +} + +void ffmpeg::avframe_queue::set_resolution(uint32_t width, uint32_t height) +{ + this->resolution.first = width; + this->resolution.second = height; +} + +void ffmpeg::avframe_queue::get_resolution(uint32_t& width, uint32_t& height) +{ + width = this->resolution.first; + height = this->resolution.second; +} + +uint32_t ffmpeg::avframe_queue::get_width() +{ + return this->resolution.first; +} + +uint32_t ffmpeg::avframe_queue::get_height() +{ + return this->resolution.second; +} + +void ffmpeg::avframe_queue::set_pixel_format(AVPixelFormat format) +{ + this->format = format; +} + +AVPixelFormat ffmpeg::avframe_queue::get_pixel_format() +{ + return this->format; +} + +void ffmpeg::avframe_queue::precache(size_t count) +{ + for (size_t n = 0; n < count; n++) { + push(create_frame()); + } +} + +void ffmpeg::avframe_queue::clear() +{ + std::unique_lock ulock(this->lock); + for (AVFrame* frame : frames) { + destroy_frame(frame); + } + frames.clear(); +} + +void ffmpeg::avframe_queue::push(AVFrame* frame) +{ + std::unique_lock ulock(this->lock); + frames.push_back(frame); +} + +AVFrame* ffmpeg::avframe_queue::pop() +{ + std::unique_lock ulock(this->lock); + + AVFrame* ret = nullptr; + while (ret == nullptr) { + if (frames.size() == 0) { + ret = create_frame(); + } else { + ret = frames.front(); + if (ret == nullptr) { + ret = create_frame(); + } else { + frames.pop_front(); + if ((ret->width != this->resolution.first) || (ret->height != this->resolution.second) + || (ret->format != this->format)) { + destroy_frame(ret); + ret = nullptr; + } + } + } + } + return ret; +} + +AVFrame* ffmpeg::avframe_queue::pop_only() +{ + std::unique_lock ulock(this->lock); + if (frames.size() == 0) { + return nullptr; + } + AVFrame* ret = frames.front(); + if (ret == nullptr) { + return nullptr; + } + frames.pop_front(); + return ret; +} + +bool ffmpeg::avframe_queue::empty() +{ + return frames.empty(); +} + +size_t ffmpeg::avframe_queue::size() +{ + return frames.size(); +} diff --git a/source/ffmpeg/avframe-queue.hpp b/source/ffmpeg/avframe-queue.hpp new file mode 100644 index 0000000..f384c15 --- /dev/null +++ b/source/ffmpeg/avframe-queue.hpp @@ -0,0 +1,70 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_FFMPEG_AVFRAME_QUEUE +#define OBS_FFMPEG_FFMPEG_AVFRAME_QUEUE +#pragma once + +#include +#include + +extern "C" { +#include +} + +namespace ffmpeg { + class avframe_queue { + std::deque frames; + std::mutex lock; + + std::pair resolution; + AVPixelFormat format = AV_PIX_FMT_NONE; + + AVFrame* create_frame(); + void destroy_frame(AVFrame* frame); + + public: + avframe_queue(); + ~avframe_queue(); + + void set_resolution(uint32_t width, uint32_t height); + void get_resolution(uint32_t& width, uint32_t& height); + uint32_t get_width(); + uint32_t get_height(); + + void set_pixel_format(AVPixelFormat format); + AVPixelFormat get_pixel_format(); + + void precache(size_t count); + + void clear(); + + void push(AVFrame* frame); + + AVFrame* pop(); + + AVFrame* pop_only(); + + bool empty(); + + size_t size(); + + + }; +} // namespace ffmpeg + +#endif OBS_FFMPEG_FFMPEG_AVFRAME_QUEUE diff --git a/source/ffmpeg/swscale.cpp b/source/ffmpeg/swscale.cpp new file mode 100644 index 0000000..184a22a --- /dev/null +++ b/source/ffmpeg/swscale.cpp @@ -0,0 +1,196 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "swscale.hpp" +#include + +ffmpeg::swscale::swscale() {} + +ffmpeg::swscale::~swscale() +{ + finalize(); +} + +void ffmpeg::swscale::set_source_size(uint32_t width, uint32_t height) +{ + source_size.first = width; + source_size.second = height; +} + +void ffmpeg::swscale::get_source_size(uint32_t& width, uint32_t& height) +{ + width = this->source_size.first; + height = this->source_size.second; +} + +std::pair ffmpeg::swscale::get_source_size() +{ + return this->source_size; +} + +uint32_t ffmpeg::swscale::get_source_width() +{ + return this->source_size.first; +} + +uint32_t ffmpeg::swscale::get_source_height() +{ + return this->source_size.second; +} + +void ffmpeg::swscale::set_source_format(AVPixelFormat format) +{ + source_format = format; +} + +AVPixelFormat ffmpeg::swscale::get_source_format() +{ + return this->source_format; +} + +void ffmpeg::swscale::set_source_color(bool full_range, AVColorSpace space) +{ + source_full_range = full_range; + source_colorspace = space; +} + +void ffmpeg::swscale::set_source_colorspace(AVColorSpace space) +{ + this->source_colorspace = space; +} + +AVColorSpace ffmpeg::swscale::get_source_colorspace() +{ + return this->source_colorspace; +} + +void ffmpeg::swscale::set_source_full_range(bool full_range) +{ + this->source_full_range = full_range; +} + +bool ffmpeg::swscale::is_source_full_range() +{ + return this->source_full_range; +} + +void ffmpeg::swscale::set_target_size(uint32_t width, uint32_t height) +{ + target_size.first = width; + target_size.second = height; +} + +void ffmpeg::swscale::get_target_size(uint32_t& width, uint32_t& height) {} + +std::pair ffmpeg::swscale::get_target_size() +{ + return this->target_size; +} + +uint32_t ffmpeg::swscale::get_target_width() +{ + return this->target_size.first; +} + +uint32_t ffmpeg::swscale::get_target_height() +{ + return this->target_size.second; +} + +void ffmpeg::swscale::set_target_format(AVPixelFormat format) +{ + target_format = format; +} + +AVPixelFormat ffmpeg::swscale::get_target_format() +{ + return this->target_format; +} + +void ffmpeg::swscale::set_target_color(bool full_range, AVColorSpace space) +{ + target_full_range = full_range; + target_colorspace = space; +} + +void ffmpeg::swscale::set_target_colorspace(AVColorSpace space) +{ + this->target_colorspace = space; +} + +AVColorSpace ffmpeg::swscale::get_target_colorspace() +{ + return this->target_colorspace; +} + +void ffmpeg::swscale::set_target_full_range(bool full_range) +{ + this->target_full_range = full_range; +} + +bool ffmpeg::swscale::is_target_full_range() +{ + return this->target_full_range; +} + +bool ffmpeg::swscale::initialize(int flags) +{ + if (this->context) { + return false; + } + if (source_size.first == 0 || source_size.second == 0 || source_format == AV_PIX_FMT_NONE + || source_colorspace == AVCOL_SPC_UNSPECIFIED) { + throw std::invalid_argument("not all source parameters were set"); + } + if (target_size.first == 0 || target_size.second == 0 || target_format == AV_PIX_FMT_NONE + || target_colorspace == AVCOL_SPC_UNSPECIFIED) { + throw std::invalid_argument("not all target parameters were set"); + } + + this->context = sws_getContext(source_size.first, source_size.second, source_format, target_size.first, + target_size.second, target_format, flags, nullptr, nullptr, nullptr); + if (!this->context) { + return false; + } + + sws_setColorspaceDetails(this->context, sws_getCoefficients(source_colorspace), source_full_range ? 1 : 0, + sws_getCoefficients(target_colorspace), target_full_range ? 1 : 0, 1l << 16 | 0l, + 1l << 16 | 0l, 1l << 16 | 0l); + + return true; +} + +bool ffmpeg::swscale::finalize() +{ + if (this->context) { + sws_freeContext(this->context); + this->context = nullptr; + return true; + } + return false; +} + +int32_t ffmpeg::swscale::convert(const uint8_t* const source_data[], const int source_stride[], int32_t source_row, + int32_t source_rows, uint8_t* const target_data[], const int target_stride[]) +{ + if (!this->context) { + return 0; + } + int height = + sws_scale(this->context, source_data, source_stride, source_row, source_rows, target_data, target_stride); + return height; +} diff --git a/source/ffmpeg/swscale.hpp b/source/ffmpeg/swscale.hpp new file mode 100644 index 0000000..1e64d7c --- /dev/null +++ b/source/ffmpeg/swscale.hpp @@ -0,0 +1,82 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_FFMPEG_SWSCALE +#define OBS_FFMPEG_FFMPEG_SWSCALE +#pragma once + +#include +#include + +extern "C" { +#include +#include +} + +namespace ffmpeg { + class swscale { + std::pair source_size; + AVPixelFormat source_format = AV_PIX_FMT_NONE; + bool source_full_range = false; + AVColorSpace source_colorspace = AVCOL_SPC_UNSPECIFIED; + + std::pair target_size; + AVPixelFormat target_format = AV_PIX_FMT_NONE; + bool target_full_range = false; + AVColorSpace target_colorspace = AVCOL_SPC_UNSPECIFIED; + + SwsContext* context = nullptr; + + public: + swscale(); + ~swscale(); + + void set_source_size(uint32_t width, uint32_t height); + void get_source_size(uint32_t& width, uint32_t& height); + std::pair get_source_size(); + uint32_t get_source_width(); + uint32_t get_source_height(); + void set_source_format(AVPixelFormat format); + AVPixelFormat get_source_format(); + void set_source_color(bool full_range, AVColorSpace space); + void set_source_colorspace(AVColorSpace space); + AVColorSpace get_source_colorspace(); + void set_source_full_range(bool full_range); + bool is_source_full_range(); + + void set_target_size(uint32_t width, uint32_t height); + void get_target_size(uint32_t& width, uint32_t& height); + std::pair get_target_size(); + uint32_t get_target_width(); + uint32_t get_target_height(); + void set_target_format(AVPixelFormat format); + AVPixelFormat get_target_format(); + void set_target_color(bool full_range, AVColorSpace space); + void set_target_colorspace(AVColorSpace space); + AVColorSpace get_target_colorspace(); + void set_target_full_range(bool full_range); + bool is_target_full_range(); + + bool initialize(int flags); + bool finalize(); + + int32_t convert(const uint8_t* const source_data[], const int source_stride[], int32_t source_row, + int32_t source_rows, uint8_t* const target_data[], const int target_stride[]); + }; +} // namespace ffmpeg + +#endif OBS_FFMPEG_FFMPEG_SWSCALE diff --git a/source/ffmpeg/tools.cpp b/source/ffmpeg/tools.cpp new file mode 100644 index 0000000..9801cd9 --- /dev/null +++ b/source/ffmpeg/tools.cpp @@ -0,0 +1,126 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "tools.hpp" +#include + +extern "C" { +#include +#include +} + +const char* ffmpeg::tools::get_pixel_format_name(AVPixelFormat v) +{ + return av_get_pix_fmt_name(v); +} + +const char* ffmpeg::tools::get_color_space_name(AVColorSpace v) +{ + switch (v) { + case AVCOL_SPC_RGB: + return "RGB"; + case AVCOL_SPC_BT709: + return "BT.709"; + case AVCOL_SPC_FCC: + return "FCC Title 47 CoFR 73.682 (a)(20)"; + case AVCOL_SPC_BT470BG: + return "BT.601 625"; + case AVCOL_SPC_SMPTE170M: + case AVCOL_SPC_SMPTE240M: + return "BT.601 525"; + case AVCOL_SPC_YCGCO: + return "ITU-T SG16"; + case AVCOL_SPC_BT2020_NCL: + return "BT.2020 NCL"; + case AVCOL_SPC_BT2020_CL: + return "BT.2020 CL"; + case AVCOL_SPC_SMPTE2085: + return "SMPTE 2085"; + case AVCOL_SPC_CHROMA_DERIVED_NCL: + return "Chroma NCL"; + case AVCOL_SPC_CHROMA_DERIVED_CL: + return "Chroma CL"; + case AVCOL_SPC_ICTCP: + return "BT.2100"; + case AVCOL_SPC_NB: + return "Not Part of ABI"; + } + return "Unknown"; +} + +const char* ffmpeg::tools::get_error_description(int error) +{ + switch (error) { + case AVERROR(EPERM): + return "Permission Denied"; + case AVERROR(ENOMEM): + return "Out Of Memory"; + case AVERROR(EINVAL): + return "Invalid Value for Parameter"; + } + return "Not Translated Yet"; +} + +AVPixelFormat ffmpeg::tools::obs_videoformat_to_avpixelformat(video_format v) +{ + switch (v) { + // 32-Bits + case VIDEO_FORMAT_BGRX: + return AV_PIX_FMT_BGRA; + case VIDEO_FORMAT_BGRA: + return AV_PIX_FMT_BGRA; + case VIDEO_FORMAT_RGBA: + return AV_PIX_FMT_RGBA; + case VIDEO_FORMAT_I444: + return AV_PIX_FMT_YUV444P; + case VIDEO_FORMAT_YUY2: + return AV_PIX_FMT_YUYV422; + case VIDEO_FORMAT_YVYU: + return AV_PIX_FMT_YVYU422; + case VIDEO_FORMAT_UYVY: + return AV_PIX_FMT_UYVY422; + case VIDEO_FORMAT_I420: + return AV_PIX_FMT_YUV420P; + case VIDEO_FORMAT_NV12: + return AV_PIX_FMT_NV12; + } + throw std::invalid_argument("unknown format"); +} + +AVColorSpace ffmpeg::tools::obs_videocolorspace_to_avcolorspace(video_colorspace v) +{ + switch (v) { + case VIDEO_CS_DEFAULT: + case VIDEO_CS_709: + return AVCOL_SPC_BT709; + case VIDEO_CS_601: + return AVCOL_SPC_SMPTE170M; + } + throw std::invalid_argument("unknown color space"); +} + +AVColorRange ffmpeg::tools::obs_videorangetype_to_avcolorrange(video_range_type v) +{ + switch (v) { + case VIDEO_RANGE_DEFAULT: + case VIDEO_RANGE_FULL: + return AVCOL_RANGE_JPEG; + case VIDEO_RANGE_PARTIAL: + return AVCOL_RANGE_MPEG; + } + throw std::invalid_argument("unknown range"); +} diff --git a/source/ffmpeg/tools.hpp b/source/ffmpeg/tools.hpp new file mode 100644 index 0000000..ad07f48 --- /dev/null +++ b/source/ffmpeg/tools.hpp @@ -0,0 +1,44 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_FFMPEG_UTILITY +#define OBS_FFMPEG_FFMPEG_UTILITY +#pragma once + +#include + +extern "C" { +#include +} + +namespace ffmpeg { + namespace tools { + const char* get_pixel_format_name(AVPixelFormat v); + + const char* get_color_space_name(AVColorSpace v); + + const char* get_error_description(int error); + + AVPixelFormat obs_videoformat_to_avpixelformat(video_format v); + + AVColorSpace obs_videocolorspace_to_avcolorspace(video_colorspace v); + + AVColorRange obs_videorangetype_to_avcolorrange(video_range_type v); + } +} // namespace ffmpeg + +#endif OBS_FFMPEG_FFMPEG_UTILITY diff --git a/source/plugin.cpp b/source/plugin.cpp new file mode 100644 index 0000000..b376bed --- /dev/null +++ b/source/plugin.cpp @@ -0,0 +1,55 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "plugin.hpp" +#include +#include +#include "utility.hpp" + +#include "encoders/prores_aw.hpp" + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +MODULE_EXPORT bool obs_module_load(void) +{ + try { + avcodec_register_all(); + obsffmpeg::encoder::prores_aw::initialize(); + return true; + } catch (std::exception ex) { + PLOG_ERROR("Exception during initalization: %s.", ex.what()); + } catch (...) { + PLOG_ERROR("Unrecognized exception during initalization."); + } + return false; +} + +MODULE_EXPORT void obs_module_unload(void) +{ + try { + obsffmpeg::encoder::prores_aw::finalize(); + } catch (std::exception ex) { + PLOG_ERROR("Exception during finalizing: %s.", ex.what()); + } catch (...) { + PLOG_ERROR("Unrecognized exception during finalizing."); + } +} diff --git a/source/plugin.hpp b/source/plugin.hpp new file mode 100644 index 0000000..d3107bb --- /dev/null +++ b/source/plugin.hpp @@ -0,0 +1,30 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_PLUGIN_HPP +#define OBS_FFMPEG_PLUGIN_HPP +#pragma once + +#include +#include + +namespace obsffmpeg { + + +} // namespace obsffmpeg + +#endif OBS_FFMPEG_PLUGIN_HPP diff --git a/source/utility.cpp b/source/utility.cpp new file mode 100644 index 0000000..e69de29 diff --git a/source/utility.hpp b/source/utility.hpp new file mode 100644 index 0000000..0181bb6 --- /dev/null +++ b/source/utility.hpp @@ -0,0 +1,48 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (C) 2018 - 2018 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef OBS_FFMPEG_UTILITY_HPP +#define OBS_FFMPEG_UTILITY_HPP +#pragma once + +#include "version.hpp" + +// Logging +#define PLOG(level, ...) blog(level, "["##PROJECT_NAME##"] " __VA_ARGS__); +#define PLOG_ERROR(...) PLOG(LOG_ERROR, __VA_ARGS__) +#define PLOG_WARNING(...) PLOG(LOG_WARNING, __VA_ARGS__) +#define PLOG_INFO(...) PLOG(LOG_INFO, __VA_ARGS__) +#define PLOG_DEBUG(...) PLOG(LOG_DEBUG, __VA_ARGS__) + +// Function Name +#ifndef __FUNCTION_NAME__ +#if defined(_WIN32) || defined(_WIN64) //WINDOWS +#define __FUNCTION_NAME__ __FUNCTION__ +#else //*NIX +#define __FUNCTION_NAME__ __func__ +#endif +#endif + +// I18n +#define TRANSLATE(x) obs_module_text(x) +#define DESC(x) x ".Description" + +// Other +#define vstr(s) dstr(s) +#define dstr(s) #s + +#endif OBS_FFMPEG_UTILITY_HPP