diff --git a/CMakeLists.txt b/CMakeLists.txt index e8d4fdef..10adfe75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,16 +56,16 @@ project( VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK} ) set(PROJECT_FULL_NAME "StreamFX for OBS Studio") -set(PROJECT_DESCRIPTION "Adds new Effects, like Sources, Transitions and Filters to OBS Studio.") +set(PROJECT_DESCRIPTION "Better Production Quality, for free.") set(PROJECT_AUTHORS "Michael Fabian 'Xaymar' Dirks ") -set(PROJECT_COPYRIGHT_YEARS "2018 - 2019") +set(PROJECT_COPYRIGHT_YEARS "2018 - 2020") ################################################################################ # Setup / Bootstrap ################################################################################ # Search Path -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/;${CMAKE_MODULE_PATH}") +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # CMake Modules include("util") @@ -127,19 +127,40 @@ set(_CXX_EXTENSIONS OFF) 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) +set(${PropertyPrefix}OBS_DOWNLOAD FALSE CACHE BOOL "Use downloaded obs-studio build") 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 "24.0.3-ci" CACHE STRING "OBS Studio Version to download") -endif() +# Solve OBS_NATIVE, OBS_REFERENCE, OBS_PACKAGE, OBS_DOWNLOAD +if(TARGET libobs) + message(STATUS "${PROJECT_NAME}: Using native obs-studio.") + CacheSet(${PropertyPrefix}OBS_NATIVE TRUE) +else() + message(STATUS "${PROJECT_NAME}: Using packaged or remote obs-studio.") + CacheSet(${PropertyPrefix}OBS_NATIVE FALSE) -if(NOT ${PropertyPrefix}OBS_NATIVE) - set(${PropertyPrefix}OBS_DEPENDENCIES_DIR "" CACHE PATH "Path to OBS Dependencies") 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.") + + if(${PropertyPrefix}OBS_DOWNLOAD) + set(${PropertyPrefix}OBS_DOWNLOAD_VERSION "24.0.3" CACHE STRING "OBS Studio Version to download") + set(${PropertyPrefix}OBS_DEPENDENCIES_VERSION "24.0.0" CACHE STRING "OBS Studio Version to download") + set(${PropertyPrefix}OBS_DOWNLOAD_URL "https://github.com/Xaymar/obs-studio/releases/download/${OBS_DOWNLOAD_VERSION}-ci/obs-studio-${ARCH}-0.0.0.0-vs2017.7z") + set(${PropertyPrefix}OBS_DEPENDENCIES_URL "https://cdn.xaymar.com/obs/dependencies_${OBS_DEPENDENCIES_VERSION}.zip") + else() + set(${PropertyPrefix}OBS_STUDIO_DIR "" CACHE PATH "OBS Studio Source/Package Directory") + set(${PropertyPrefix}OBS_DEPENDENCIES_DIR "" CACHE PATH "OBS Studio Dependencies Directory") + 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(FATAL_ERROR "${PROJECT_NAME}: No OBS Studio detected. If you wish to continue, either check ${PropertyPrefix}OBS_DOWNLOAD or fix your configuration") + return() + endif() + endif() endif() ################################################################################ @@ -192,49 +213,59 @@ if(WIN32) ) endif() -# 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}-0.0.0.0-vs2017.7z") - endif() -endif() - -# CMake Modules +# Download OBS Studio and OBS Dependencies if(${PropertyPrefix}OBS_DOWNLOAD) include("DownloadProject") -endif() - -# Load OBS Studio -if(${PropertyPrefix}OBS_NATIVE) -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 ) + + download_project( + PROJ obsdeps + URL ${OBS_DEPENDENCIES_URL} + UPDATE_DISCONNECTED 1 + ) +endif() + +# Load OBS Studio & Dependencies +if(${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) include("${libobs_SOURCE_DIR}/cmake/LibObs/LibObsConfig.cmake") else() message(CRITICAL "Impossible case reached, verify system stability.") return() endif() +# Load FFmpeg +find_path( + FFmpegPath "libavcodec/avcodec.h" + HINTS + ${OBS_DEPENDENCIES_DIR} + ${obsdeps_SOURCE_DIR} + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + PATH_SUFFIXES + win${BITS} + win${BITS}/bin + win${BITS}/include + win${ARCH} + win${ARCH}/bin + win${ARCH}/include + bin + include +) +find_package(FFmpeg REQUIRED COMPONENTS avutil avcodec swscale) + ################################################################################ # Code ################################################################################ @@ -297,7 +328,6 @@ set(PROJECT_PRIVATE_GENERATED "${PROJECT_BINARY_DIR}/source/version.hpp" ) set(PROJECT_PRIVATE_SOURCE - # Plugin "${PROJECT_SOURCE_DIR}/source/plugin.hpp" "${PROJECT_SOURCE_DIR}/source/plugin.cpp" @@ -399,7 +429,44 @@ set(PROJECT_PRIVATE_SOURCE "${PROJECT_SOURCE_DIR}/source/filters/filter-transform.cpp" # Transitions + + # ffmpeg + "${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" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/hwapi/base.hpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/hwapi/base.cpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/hwapi/d3d11.hpp" + "${PROJECT_SOURCE_DIR}/source/ffmpeg/hwapi/d3d11.cpp" + + # Encoders + "${PROJECT_SOURCE_DIR}/source/encoders/encoder.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/encoder.cpp" + # Encoders/Codecs + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/hevc.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/hevc.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/h264.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/h264.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/prores.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/codecs/prores.cpp" + # Encoders/Handlers + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/handler.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/handler.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/debug_handler.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/debug_handler.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/prores_aw_handler.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/prores_aw_handler.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_shared.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_shared.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_h264_handler.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_h264_handler.cpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_hevc_handler.hpp" + "${PROJECT_SOURCE_DIR}/source/encoders/handlers/nvenc_hevc_handler.cpp" ) + set(PROJECT_PRIVATE ${PROJECT_DATA} ${PROJECT_PRIVATE_GENERATED} @@ -474,7 +541,8 @@ endif() # Link Libraries target_link_libraries(${PROJECT_NAME} - "${PROJECT_LIBRARIES}" + ${PROJECT_LIBRARIES} + ${FFMPEG_LIBRARIES} ) # Definitions diff --git a/cmake/ClangToolkit.cmake b/cmake/ClangToolkit.cmake index 51fc929c..a918898c 100644 --- a/cmake/ClangToolkit.cmake +++ b/cmake/ClangToolkit.cmake @@ -1,4 +1,4 @@ -set(CLANG_FORMAT_BIN "clang-format" CACHE PATH "Path (or name) of the clang-format binary") +set(CLANG_PATH "" CACHE PATH "Path to Clang Toolset (if not in environment)") function(clang_format) cmake_parse_arguments( @@ -9,15 +9,23 @@ function(clang_format) "TARGETS" ) - if(NOT EXISTS ${CLANG_FORMAT_BIN}) - find_program(clang_format_bin_tmp ${CLANG_FORMAT_BIN}) - if(clang_format_bin_tmp) - set(CLANG_FORMAT_BIN "${clang_format_bin_tmp}" CACHE PATH "Path (or name) of the clang-format binary") - unset(clang_format_bin_tmp) - else() - message(WARNING "Clang: Could not find clang-format at path '${CLANG_FORMAT_BIN}'. Disabling clang-format...") - return() - endif() + find_program(CLANG_FORMAT_BIN + "clang-format" + HINTS + ${CLANG_PATH} + PATHS + /bin + /sbin + /usr/bin + /usr/local/bin + PATH_SUFFIXES + bin + bin64 + bin32 + ) + if(NOT CLANG_FORMAT_BIN) + message(WARNING "Clang: Could not find clang-format at path '${CLANG_FORMAT_BIN}'. Disabling clang-format...") + return() endif() if(NOT _CLANG_FORMAT_FILTER) diff --git a/cmake/modules/FindFFmpeg.cmake b/cmake/modules/FindFFmpeg.cmake new file mode 100644 index 00000000..52066958 --- /dev/null +++ b/cmake/modules/FindFFmpeg.cmake @@ -0,0 +1,155 @@ +# +# This module defines the following variables: +# +# FFMPEG_FOUND - All required components and the core library were found +# FFMPEG_INCLUDE_DIRS - Combined list of all components include dirs +# FFMPEG_LIBRARIES - Combined list of all componenets libraries +# FFMPEG_VERSION_STRING - Version of the first component requested +# +# For each requested component the following variables are defined: +# +# FFMPEG__FOUND - The component was found +# FFMPEG__INCLUDE_DIRS - The components include dirs +# FFMPEG__LIBRARIES - The components libraries +# FFMPEG__VERSION_STRING - The components version string +# FFMPEG__VERSION_MAJOR - The components major version +# FFMPEG__VERSION_MINOR - The components minor version +# FFMPEG__VERSION_MICRO - The components micro version +# +# is the uppercase name of the component + + +find_package(PkgConfig QUIET) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_lib_suffix 64) +else() + set(_lib_suffix 32) +endif() + +function(find_ffmpeg_library component header) + string(TOUPPER "${component}" component_u) + set(FFMPEG_${component_u}_FOUND FALSE PARENT_SCOPE) + set(FFmpeg_${component}_FOUND FALSE PARENT_SCOPE) + + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_${component} QUIET lib${component}) + endif() + + find_path(FFMPEG_${component}_INCLUDE_DIR + NAMES + "lib${component}/${header}" "lib${component}/version.h" + HINTS + ENV FFmpegPath${_lib_suffix} + ENV FFmpegPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${FFmpegPath${_lib_suffix}} + ${FFmpegPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${PC_FFMPEG_${component}_INCLUDE_DIRS} + PATHS + /usr/include /usr/local/include /opt/local/include /sw/include + PATH_SUFFIXES ffmpeg libav include) + + find_library(FFMPEG_${component}_LIBRARY + NAMES + "${component}" "lib${component}" + HINTS + ENV FFmpegPath${_lib_suffix} + ENV FFmpegPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${FFmpegPath${_lib_suffix}} + ${FFmpegPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${PC_FFMPEG_${component}_LIBRARY_DIRS} + PATHS + /usr/lib /usr/local/lib /opt/local/lib /sw/lib + PATH_SUFFIXES + lib${_lib_suffix} lib + libs${_lib_suffix} libs + bin${_lib_suffix} bin + ../lib${_lib_suffix} ../lib + ../libs${_lib_suffix} ../libs + ../bin${_lib_suffix} ../bin) + + set(FFMPEG_${component_u}_INCLUDE_DIRS ${FFMPEG_${component}_INCLUDE_DIR} PARENT_SCOPE) + set(FFMPEG_${component_u}_LIBRARIES ${FFMPEG_${component}_LIBRARY} PARENT_SCOPE) + + mark_as_advanced(FFMPEG_${component}_INCLUDE_DIR FFMPEG_${component}_LIBRARY) + + if(FFMPEG_${component}_INCLUDE_DIR AND FFMPEG_${component}_LIBRARY) + set(FFMPEG_${component_u}_FOUND TRUE PARENT_SCOPE) + set(FFmpeg_${component}_FOUND TRUE PARENT_SCOPE) + + list(APPEND FFMPEG_INCLUDE_DIRS ${FFMPEG_${component}_INCLUDE_DIR}) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + set(FFMPEG_INCLUDE_DIRS "${FFMPEG_INCLUDE_DIRS}" PARENT_SCOPE) + + list(APPEND FFMPEG_LIBRARIES ${FFMPEG_${component}_LIBRARY}) + list(REMOVE_DUPLICATES FFMPEG_LIBRARIES) + set(FFMPEG_LIBRARIES "${FFMPEG_LIBRARIES}" PARENT_SCOPE) + + set(FFMPEG_${component_u}_VERSION_STRING "unknown" PARENT_SCOPE) + set(_vfile "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h") + + if(EXISTS "${_vfile}") + file(STRINGS "${_vfile}" _version_parse REGEX "^.*VERSION_(MAJOR|MINOR|MICRO)[ \t]+[0-9]+[ \t]*$") + string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" _major "${_version_parse}") + string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" _minor "${_version_parse}") + string(REGEX REPLACE ".*VERSION_MICRO[ \t]+([0-9]+).*" "\\1" _micro "${_version_parse}") + + set(FFMPEG_${component_u}_VERSION_MAJOR "${_major}" PARENT_SCOPE) + set(FFMPEG_${component_u}_VERSION_MINOR "${_minor}" PARENT_SCOPE) + set(FFMPEG_${component_u}_VERSION_MICRO "${_micro}" PARENT_SCOPE) + + set(FFMPEG_${component_u}_VERSION_STRING "${_major}.${_minor}.${_micro}" PARENT_SCOPE) + else() + message(STATUS "Failed parsing FFmpeg ${component} version") + endif() + endif() +endfunction() + +set(FFMPEG_INCLUDE_DIRS) +set(FFMPEG_LIBRARIES) + +if(NOT FFmpeg_FIND_COMPONENTS) + message(FATAL_ERROR "No FFmpeg components requested") +endif() + +list(GET FFmpeg_FIND_COMPONENTS 0 _first_comp) +string(TOUPPER "${_first_comp}" _first_comp) + +foreach(component ${FFmpeg_FIND_COMPONENTS}) + if(component STREQUAL "avcodec") + find_ffmpeg_library("${component}" "avcodec.h") + elseif(component STREQUAL "avdevice") + find_ffmpeg_library("${component}" "avdevice.h") + elseif(component STREQUAL "avfilter") + find_ffmpeg_library("${component}" "avfilter.h") + elseif(component STREQUAL "avformat") + find_ffmpeg_library("${component}" "avformat.h") + elseif(component STREQUAL "avresample") + find_ffmpeg_library("${component}" "avresample.h") + elseif(component STREQUAL "avutil") + find_ffmpeg_library("${component}" "avutil.h") + elseif(component STREQUAL "postproc") + find_ffmpeg_library("${component}" "postprocess.h") + elseif(component STREQUAL "swresample") + find_ffmpeg_library("${component}" "swresample.h") + elseif(component STREQUAL "swscale") + find_ffmpeg_library("${component}" "swscale.h") + else() + message(FATAL_ERROR "Unknown FFmpeg component requested: ${component}") + endif() +endforeach() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFmpeg + FOUND_VAR FFMPEG_FOUND + REQUIRED_VARS FFMPEG_${_first_comp}_LIBRARIES FFMPEG_${_first_comp}_INCLUDE_DIRS + VERSION_VAR FFMPEG_${_first_comp}_VERSION_STRING + HANDLE_COMPONENTS) diff --git a/source/encoders/codecs/h264.cpp b/source/encoders/codecs/h264.cpp new file mode 100644 index 00000000..c9e39e66 --- /dev/null +++ b/source/encoders/codecs/h264.cpp @@ -0,0 +1,22 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "h264.hpp" diff --git a/source/encoders/codecs/h264.hpp b/source/encoders/codecs/h264.hpp new file mode 100644 index 00000000..2d0babe7 --- /dev/null +++ b/source/encoders/codecs/h264.hpp @@ -0,0 +1,67 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include + +// Codec: H264 +#define P_H264 "Codec.H264" +#define P_H264_PROFILE "Codec.H264.Profile" +#define P_H264_LEVEL "Codec.H264.Level" + +namespace obsffmpeg { + namespace codecs { + namespace h264 { + enum class profile { + CONSTRAINED_BASELINE, + BASELINE, + MAIN, + HIGH, + HIGH444_PREDICTIVE, + UNKNOWN = -1, + }; + + enum class level { + L1_0 = 10, + L1_0b, + L1_1, + L1_2, + L1_3, + L2_0 = 20, + L2_1, + L2_2, + L3_0 = 30, + L3_1, + L3_2, + L4_0 = 40, + L4_1, + L4_2, + L5_0 = 50, + L5_1, + L5_2, + L6_0 = 60, + L6_1, + L6_2, + UNKNOWN = -1, + }; + } // namespace h264 + } // namespace codecs +} // namespace obsffmpeg diff --git a/source/encoders/codecs/hevc.cpp b/source/encoders/codecs/hevc.cpp new file mode 100644 index 00000000..5f276be3 --- /dev/null +++ b/source/encoders/codecs/hevc.cpp @@ -0,0 +1,236 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "hevc.hpp" +#include "utility.hpp" + +enum class nal_unit_type : uint8_t { // 6 bits + TRAIL_N = 0, + TRAIL_R = 1, + TSA_N = 2, + TSA_R = 3, + STSA_N = 4, + STSA_R = 5, + RADL_N = 6, + RADL_R = 7, + RASL_N = 8, + RASL_R = 9, + RSV_VCL_N10 = 10, + RSV_VCL_R11 = 11, + RSV_VCL_N12 = 12, + RSV_VCL_R13 = 13, + RSV_VCL_N14 = 14, + RSV_VCL_R15 = 15, + BLA_W_LP = 16, + BLA_W_RADL = 17, + BLA_N_LP = 18, + IDR_W_RADL = 19, + IDR_N_LP = 20, + CRA = 21, + RSV_IRAP_VCL22 = 22, + RSV_IRAP_VCL23 = 23, + RSV_VCL24 = 24, + RSV_VCL25 = 25, + RSV_VCL26 = 26, + RSV_VCL27 = 27, + RSV_VCL28 = 28, + RSV_VCL29 = 29, + RSV_VCL30 = 30, + RSV_VCL31 = 31, + VPS = 32, + SPS = 33, + PPS = 34, + AUD = 35, + EOS = 36, + EOB = 37, + FD = 38, + PREFIX_SEI = 39, + SUFFIX_SEI = 40, + RSV_NVCL41 = 41, + RSV_NVCL42 = 42, + RSV_NVCL43 = 43, + RSV_NVCL44 = 44, + RSV_NVCL45 = 45, + RSV_NVCL46 = 46, + RSV_NVCL47 = 47, + UNSPEC48 = 48, + UNSPEC49 = 49, + UNSPEC50 = 50, + UNSPEC51 = 51, + UNSPEC52 = 52, + UNSPEC53 = 53, + UNSPEC54 = 54, + UNSPEC55 = 55, + UNSPEC56 = 56, + UNSPEC57 = 57, + UNSPEC58 = 58, + UNSPEC59 = 59, + UNSPEC60 = 60, + UNSPEC61 = 61, + UNSPEC62 = 62, + UNSPEC63 = 63, +}; + +struct hevc_nal_unit_header { + bool zero_bit : 1; + nal_unit_type nut : 6; + uint8_t layer_id : 6; + uint8_t temporal_id_plus1 : 3; +}; + +struct hevc_nal { + hevc_nal_unit_header* header; + size_t size = 0; + uint8_t* data = nullptr; +}; + +bool is_nal(uint8_t* data, uint8_t* end) +{ + size_t s = end - data; + if (s < 4) + return false; + + if (*data != 0x0) + return false; + if (*(data + 1) != 0x0) + return false; + if (*(data + 2) != 0x0) + return false; + if (*(data + 3) != 0x1) + return false; + + return true; +} + +bool seek_to_nal(uint8_t*& data, uint8_t* end) +{ + if (data > end) + return false; + + for (; data <= end; data++) { + if (is_nal(data, end)) { + return true; + } + } + + return false; +} + +size_t get_nal_size(uint8_t* data, uint8_t* end) +{ + uint8_t* ptr = data + 4; + if (!seek_to_nal(ptr, end)) { + return end - data; + } + return ptr - data; +} + +bool is_discard_marker(uint8_t* data, uint8_t* end) +{ + size_t s = end - data; + if (s < 4) + return false; + + if (*data != 0x0) + return false; + if (*(data + 1) != 0x0) + return false; + + if (*(data + 2) == 0x3) { + // Discard marker only if the next byte is not 0x0, 0x1, 0x2 or 0x3. + if (*(data + 3) != 0x0) + return false; + if (*(data + 3) != 0x1) + return false; + if (*(data + 3) != 0x2) + return false; + if (*(data + 3) != 0x3) + return false; + + return true; + } else { + if (*(data + 2) == 0x0) + return true; + if (*(data + 2) == 0x1) + return true; + if (*(data + 2) == 0x2) + return true; + + return false; + } +} + +bool should_discard_nal(uint8_t* data, uint8_t* end) +{ + if (data > end) + return true; + + for (; data <= end; data++) { + if (is_discard_marker(data, end)) + return true; + } + + return false; +} + +void progress_parse(uint8_t*& ptr, uint8_t* end, size_t& sz) +{ + ptr += sz; + sz = get_nal_size(ptr, end); +} + +void obsffmpeg::codecs::hevc::extract_header_sei(uint8_t* data, size_t sz_data, std::vector& header, + std::vector& sei) +{ + uint8_t* ptr = data; + uint8_t* end = data + sz_data; + + // Reserve enough memory to store the entire packet data if necessary. + header.reserve(sz_data); + sei.reserve(sz_data); + + if (!seek_to_nal(ptr, end)) { + return; + } + + for (size_t nal_sz = get_nal_size(ptr, end); nal_sz > 0; progress_parse(ptr, end, nal_sz)) { + if (should_discard_nal(ptr + 4, ptr + nal_sz)) { + continue; + } + + hevc_nal nal; + nal.header = reinterpret_cast(ptr + 4); + nal.size = nal_sz - 4 - 2; + nal.data = ptr + 4 + 2; + + switch (nal.header->nut) { + case nal_unit_type::VPS: + case nal_unit_type::SPS: + case nal_unit_type::PPS: + header.insert(header.end(), ptr, ptr + nal_sz); + break; + case nal_unit_type::PREFIX_SEI: + case nal_unit_type::SUFFIX_SEI: + sei.insert(sei.end(), ptr, ptr + nal_sz); + break; + } + } +} diff --git a/source/encoders/codecs/hevc.hpp b/source/encoders/codecs/hevc.hpp new file mode 100644 index 00000000..413cfd41 --- /dev/null +++ b/source/encoders/codecs/hevc.hpp @@ -0,0 +1,69 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include + +// Codec: HEVC +#define P_HEVC "Codec.HEVC" +#define P_HEVC_PROFILE "Codec.HEVC.Profile" +#define P_HEVC_TIER "Codec.HEVC.Tier" +#define P_HEVC_LEVEL "Codec.HEVC.Level" + +namespace obsffmpeg { + namespace codecs { + namespace hevc { + enum class profile { + MAIN, + MAIN10, + RANGE_EXTENDED, + UNKNOWN = -1, + }; + + enum class tier { + MAIN, + HIGH, + UNKNOWN = -1, + }; + + enum class level { + L1_0 = 30, + L2_0 = 60, + L2_1 = 63, + L3_0 = 90, + L3_1 = 93, + L4_0 = 120, + L4_1 = 123, + L5_0 = 150, + L5_1 = 153, + L5_2 = 156, + L6_0 = 180, + L6_1 = 183, + L6_2 = 186, + UNKNOWN = -1, + }; + + void extract_header_sei(uint8_t* data, size_t sz_data, std::vector& header, + std::vector& sei); + + } // namespace hevc + } // namespace codecs +} // namespace obsffmpeg diff --git a/source/encoders/codecs/prores.cpp b/source/encoders/codecs/prores.cpp new file mode 100644 index 00000000..f80f5862 --- /dev/null +++ b/source/encoders/codecs/prores.cpp @@ -0,0 +1,22 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "prores.hpp" diff --git a/source/encoders/codecs/prores.hpp b/source/encoders/codecs/prores.hpp new file mode 100644 index 00000000..b9a1f37c --- /dev/null +++ b/source/encoders/codecs/prores.hpp @@ -0,0 +1,32 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +// Codec: ProRes +#define P_PRORES "Codec.ProRes" +#define P_PRORES_PROFILE "Codec.ProRes.Profile" +#define P_PRORES_PROFILE_APCS "Codec.ProRes.Profile.APCS" +#define P_PRORES_PROFILE_APCO "Codec.ProRes.Profile.APCO" +#define P_PRORES_PROFILE_APCN "Codec.ProRes.Profile.APCN" +#define P_PRORES_PROFILE_APCH "Codec.ProRes.Profile.APCH" +#define P_PRORES_PROFILE_AP4H "Codec.ProRes.Profile.AP4H" +#define P_PRORES_PROFILE_AP4X "Codec.ProRes.Profile.AP4X" diff --git a/source/encoders/encoder.cpp b/source/encoders/encoder.cpp new file mode 100644 index 00000000..42a219a6 --- /dev/null +++ b/source/encoders/encoder.cpp @@ -0,0 +1,1429 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "encoder.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "codecs/hevc.hpp" +#include "ffmpeg/tools.hpp" +#include "plugin.hpp" +#include "strings.hpp" +#include "utility.hpp" + +extern "C" { +#include +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#include +#include +#include +#pragma warning(pop) +} + +//#define DEBUG_CALL_ORDER +// Call Order should be: +// - create_texture/create +// - get_video_info +// - encode_texture/encode +// I don't understand what get_video_info is actually for in this order, as this postpones initialization to encode... + +#ifdef WIN32 +#define HARDWARE_ENCODING +#include "hwapi/d3d11.hpp" +#endif + +// FFmpeg +#define ST_FFMPEG "FFmpeg" +#define ST_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings" +#define ST_FFMPEG_THREADS "FFmpeg.Threads" +#define ST_FFMPEG_COLORFORMAT "FFmpeg.ColorFormat" +#define ST_FFMPEG_STANDARDCOMPLIANCE "FFmpeg.StandardCompliance" +#define ST_FFMPEG_GPU "FFmpeg.GPU" + +enum class keyframe_type { SECONDS, FRAMES }; + +static void* _create(obs_data_t* settings, obs_encoder_t* encoder) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, encoder); +#endif + return reinterpret_cast(new obsffmpeg::encoder(settings, encoder)); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return nullptr; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return nullptr; +} + +static void* _create_texture(obs_data_t* settings, obs_encoder_t* encoder) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, encoder); +#endif + return reinterpret_cast(new obsffmpeg::encoder(settings, encoder, true)); +} catch (const obsffmpeg::unsupported_gpu_exception&) { + obsffmpeg::encoder_factory* fac = + reinterpret_cast(obs_encoder_get_type_data(encoder)); + PLOG_WARNING("<%s> GPU not supported for hardware encoding, falling back to software.", + fac->get_avcodec()->name); + return obs_encoder_create_rerouted(encoder, fac->get_fallback().oei.id); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return nullptr; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return nullptr; +} + +static void _destroy(void* ptr) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX", __FUNCTION_NAME__, ptr); +#endif + if (ptr) + delete reinterpret_cast(ptr); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); +} + +static const char* _get_name(void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX", __FUNCTION_NAME__, type_data); +#endif + return reinterpret_cast(type_data)->get_info().readable_name.c_str(); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return nullptr; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return nullptr; +} + +static const char* _get_name_fallback(void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX", __FUNCTION_NAME__, type_data); +#endif + return reinterpret_cast(type_data)->get_fallback().readable_name.c_str(); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return nullptr; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return nullptr; +} + +static void _get_defaults(obs_data_t* settings, void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, type_data); +#endif + reinterpret_cast(type_data)->get_defaults(settings); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); +}; + +static void _get_defaults_texture(obs_data_t* settings, void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, type_data); +#endif + reinterpret_cast(type_data)->get_defaults(settings, true); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); +}; + +static obs_properties_t* _get_properties(void* ptr, void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, type_data); +#endif + obs_properties_t* props = obs_properties_create(); + if (type_data != nullptr) { + reinterpret_cast(type_data)->get_properties(props); + } + if (ptr != nullptr) { + reinterpret_cast(ptr)->get_properties(props); + } + return props; +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return reinterpret_cast(0); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return reinterpret_cast(0); +} + +static obs_properties_t* _get_properties_texture(void* ptr, void* type_data) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, type_data); +#endif + obs_properties_t* props = obs_properties_create(); + if (type_data != nullptr) { + reinterpret_cast(type_data)->get_properties(props, true); + } + if (ptr != nullptr) { + reinterpret_cast(ptr)->get_properties(props, true); + } + return props; +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return reinterpret_cast(0); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return reinterpret_cast(0); +} + +static bool _update(void* ptr, obs_data_t* settings) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, settings); +#endif + return reinterpret_cast(ptr)->update(settings); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static bool _get_sei_data(void* ptr, uint8_t** sei_data, size_t* size) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX %llX", __FUNCTION_NAME__, ptr, sei_data, size); +#endif + return reinterpret_cast(ptr)->get_sei_data(sei_data, size); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static bool _get_extra_data(void* ptr, uint8_t** extra_data, size_t* size) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX %llX", __FUNCTION_NAME__, ptr, extra_data, size); +#endif + return reinterpret_cast(ptr)->get_extra_data(extra_data, size); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static void _get_video_info(void* ptr, struct video_scale_info* info) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, info); +#endif + reinterpret_cast(ptr)->get_video_info(info); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); +} + +static bool _encode(void* ptr, struct encoder_frame* frame, struct encoder_packet* packet, + bool* received_packet) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX %llX %llX", __FUNCTION_NAME__, ptr, frame, packet, received_packet); +#endif + return reinterpret_cast(ptr)->video_encode(frame, packet, received_packet); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static bool _encode_texture(void* ptr, uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key, + struct encoder_packet* packet, bool* received_packet) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %lI %llI %llU %llX %llX %llX", __FUNCTION_NAME__, ptr, handle, pts, lock_key, next_key, packet, + received_packet); +#endif + return reinterpret_cast(ptr)->video_encode_texture(handle, pts, lock_key, next_key, packet, + received_packet); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static void _get_audio_info(void* ptr, struct audio_convert_info* info) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, info); +#endif + reinterpret_cast(ptr)->get_audio_info(info); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); +} + +static size_t _get_frame_size(void* ptr) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX", __FUNCTION_NAME__, ptr); +#endif + return reinterpret_cast(ptr)->get_frame_size(); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return 0; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return 0; +} + +static bool _encode_audio(void* ptr, struct encoder_frame* frame, struct encoder_packet* packet, + bool* received_packet) noexcept +try { +#ifdef DEBUG_CALL_ORDER + PLOG_INFO("%s %llX %llX %llX %llX", __FUNCTION_NAME__, ptr, frame, packet, received_packet); +#endif + return reinterpret_cast(ptr)->audio_encode(frame, packet, received_packet); +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(codec), info(), info_fallback() +{ + // Find Codec UI handler. + _handler = obsffmpeg::find_codec_handler(avcodec_ptr->name); + + // Unique Id is FFmpeg name. + info.uid = std::string("obs-ffmpeg-encoder_") + avcodec_ptr->name; + + // Also generate a human readable name while we're at it. + { + std::stringstream sstr; + if (!obsffmpeg::has_codec_handler(avcodec_ptr->name)) { + sstr << "[UNSUPPORTED] "; + } + sstr << (avcodec_ptr->long_name ? avcodec_ptr->long_name : avcodec_ptr->name); + if (avcodec_ptr->long_name) { + sstr << " (" << avcodec_ptr->name << ")"; + } + info.readable_name = sstr.str(); + } + + // Assign Ids. + { + const AVCodecDescriptor* desc = avcodec_descriptor_get(avcodec_ptr->id); + if (desc) { + info.codec = desc->name; + } else { + // Fall back to encoder name in the case that FFmpeg itself doesn't know + // what codec this actually is. + info.codec = avcodec_ptr->name; + } + } + + info.oei.id = info.uid.c_str(); + info.oei.codec = info.codec.c_str(); + +#ifndef _DEBUG + // Is this a deprecated encoder? + if (!obsffmpeg::has_codec_handler(avcodec_ptr->name)) { + info.oei.caps |= OBS_ENCODER_CAP_DEPRECATED; + } +#endif + + // Hardware encoder? +#ifdef HARDWARE_ENCODING + if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) { + info_fallback.uid = info.uid + "_sw"; + info_fallback.codec = info.codec; + info_fallback.readable_name = info.readable_name + " (Software)"; + + // Copy capabilities and hide from view. + info_fallback.oei.id = info_fallback.uid.c_str(); + info_fallback.oei.codec = info.oei.codec; + info_fallback.oei.caps = info.oei.caps; + //info_fallback.oei.caps |= OBS_ENCODER_CAP_DEPRECATED; + + info.oei.caps |= OBS_ENCODER_CAP_PASS_TEXTURE; + } +#endif +} + +obsffmpeg::encoder_factory::~encoder_factory() {} + +void obsffmpeg::encoder_factory::register_encoder() +{ + // Detect encoder type (only Video and Audio supported) + if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) { + info.oei.type = obs_encoder_type::OBS_ENCODER_VIDEO; + } else if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) { + info.oei.type = obs_encoder_type::OBS_ENCODER_AUDIO; + } else { + throw std::invalid_argument("unsupported codec type"); + } + + // Register functions. + info.oei.destroy = _destroy; + info.oei.get_name = _get_name; + info.oei.get_defaults2 = _get_defaults; + info.oei.get_properties2 = _get_properties; + info.oei.update = _update; + info.oei.get_sei_data = _get_sei_data; + info.oei.get_extra_data = _get_extra_data; + + if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) { + info.oei.get_video_info = _get_video_info; + } else if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) { + info.oei.get_audio_info = _get_audio_info; + info.oei.get_frame_size = _get_frame_size; + info.oei.encode = _encode_audio; + } + + // Finally store ourself as type data. + info.oei.type_data = this; + + if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) { + info.oei.create = _create_texture; + info.oei.encode_texture = _encode_texture; + info.oei.get_defaults2 = _get_defaults_texture; + info.oei.get_properties2 = _get_properties_texture; + + info_fallback.oei.type = info.oei.type; + info_fallback.oei.create = _create; + info_fallback.oei.destroy = _destroy; + info_fallback.oei.get_name = _get_name_fallback; + info_fallback.oei.get_defaults2 = _get_defaults; + info_fallback.oei.get_properties2 = _get_properties; + info_fallback.oei.update = _update; + info_fallback.oei.get_sei_data = _get_sei_data; + info_fallback.oei.get_extra_data = _get_extra_data; + info_fallback.oei.get_video_info = _get_video_info; + info_fallback.oei.encode = _encode; + info_fallback.oei.type_data = this; + + } else { + // Is not a GPU Encoder, don't implement fallback. + info.oei.create = _create; + info.oei.encode = _encode; + } + + if (_handler) + _handler->adjust_encoder_info(this, &info, &info_fallback); + + obs_register_encoder(&info.oei); + PLOG_DEBUG("Registered encoder #%llX with name '%s' and long name '%s' and caps %llX", avcodec_ptr, + avcodec_ptr->name, avcodec_ptr->long_name, avcodec_ptr->capabilities); + if (info_fallback.uid.size() > 0) { + obs_register_encoder(&info_fallback.oei); + PLOG_DEBUG("Registered software fallback for encoder #%llX", avcodec_ptr); + } +} + +void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings, bool hw_encode) +{ + if (_handler) + _handler->get_defaults(settings, avcodec_ptr, nullptr, hw_encode); + + if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) { + obs_data_set_default_int(settings, S_KEYFRAMES_INTERVALTYPE, 0); + obs_data_set_default_double(settings, S_KEYFRAMES_INTERVAL_SECONDS, 2.0); + obs_data_set_default_int(settings, S_KEYFRAMES_INTERVAL_FRAMES, 300); + } + + { // Integrated Options + // FFmpeg + obs_data_set_default_string(settings, ST_FFMPEG_CUSTOMSETTINGS, ""); + if (!hw_encode) { + obs_data_set_default_int(settings, ST_FFMPEG_COLORFORMAT, + static_cast(AV_PIX_FMT_NONE)); + obs_data_set_default_int(settings, ST_FFMPEG_THREADS, 0); + obs_data_set_default_int(settings, ST_FFMPEG_GPU, 0); + } + obs_data_set_default_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT); + } +} + +static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_data_t* settings) +try { + bool is_seconds = obs_data_get_int(settings, S_KEYFRAMES_INTERVALTYPE) == 0; + obs_property_set_visible(obs_properties_get(props, S_KEYFRAMES_INTERVAL_FRAMES), !is_seconds); + obs_property_set_visible(obs_properties_get(props, S_KEYFRAMES_INTERVAL_SECONDS), is_seconds); + return true; +} catch (const std::exception& ex) { + PLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props, bool hw_encode) +{ + if (_handler) + _handler->get_properties(props, avcodec_ptr, nullptr, hw_encode); + + if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) { + // Key-Frame Options + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, S_KEYFRAMES, TRANSLATE(S_KEYFRAMES), OBS_GROUP_NORMAL, grp); + } + + { + auto p = + obs_properties_add_list(grp, S_KEYFRAMES_INTERVALTYPE, TRANSLATE(S_KEYFRAMES_INTERVALTYPE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(S_KEYFRAMES_INTERVALTYPE))); + obs_property_set_modified_callback(p, modified_keyframes); + obs_property_list_add_int(p, TRANSLATE(S_KEYFRAMES_INTERVALTYPE_(Seconds)), 0); + obs_property_list_add_int(p, TRANSLATE(S_KEYFRAMES_INTERVALTYPE_(Frames)), 1); + } + { + auto p = + obs_properties_add_float(grp, S_KEYFRAMES_INTERVAL_SECONDS, TRANSLATE(S_KEYFRAMES_INTERVAL), + 0.00, std::numeric_limits::max(), 0.01); + obs_property_set_long_description(p, TRANSLATE(DESC(S_KEYFRAMES_INTERVAL))); + obs_property_float_set_suffix(p, " seconds"); + } + { + auto p = + obs_properties_add_int(grp, S_KEYFRAMES_INTERVAL_FRAMES, TRANSLATE(S_KEYFRAMES_INTERVAL), 0, + std::numeric_limits::max(), 1); + obs_property_set_long_description(p, TRANSLATE(DESC(S_KEYFRAMES_INTERVAL))); + obs_property_int_set_suffix(p, " frames"); + } + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + auto prs = obs_properties_create(); + obs_properties_add_group(props, ST_FFMPEG, TRANSLATE(ST_FFMPEG), OBS_GROUP_NORMAL, prs); + grp = prs; + } + + { + auto p = + obs_properties_add_text(grp, ST_FFMPEG_CUSTOMSETTINGS, TRANSLATE(ST_FFMPEG_CUSTOMSETTINGS), + obs_text_type::OBS_TEXT_DEFAULT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_CUSTOMSETTINGS))); + } + if (!hw_encode) { + { + auto p = obs_properties_add_int(grp, ST_FFMPEG_GPU, TRANSLATE(ST_FFMPEG_GPU), 0, + std::numeric_limits::max(), 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_GPU))); + } + if (avcodec_ptr->pix_fmts) { + auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT, + TRANSLATE(ST_FFMPEG_COLORFORMAT), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_COLORFORMAT))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC), + static_cast(AV_PIX_FMT_NONE)); + for (auto ptr = avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) { + obs_property_list_add_int(p, ffmpeg::tools::get_pixel_format_name(*ptr), + static_cast(*ptr)); + } + } + if (avcodec_ptr->capabilities & (AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) { + auto p = + obs_properties_add_int_slider(grp, ST_FFMPEG_THREADS, TRANSLATE(ST_FFMPEG_THREADS), + 0, std::thread::hardware_concurrency() * 2, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_THREADS))); + } + } + { + auto p = obs_properties_add_list(grp, ST_FFMPEG_STANDARDCOMPLIANCE, + TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_STANDARDCOMPLIANCE))); + obs_property_list_add_int(p, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".VeryStrict"), + FF_COMPLIANCE_VERY_STRICT); + obs_property_list_add_int(p, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Strict"), + FF_COMPLIANCE_STRICT); + obs_property_list_add_int(p, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Normal"), + FF_COMPLIANCE_NORMAL); + obs_property_list_add_int(p, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Unofficial"), + FF_COMPLIANCE_UNOFFICIAL); + obs_property_list_add_int(p, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Experimental"), + FF_COMPLIANCE_EXPERIMENTAL); + } + }; +} + +const AVCodec* obsffmpeg::encoder_factory::get_avcodec() +{ + return avcodec_ptr; +} + +const obsffmpeg::encoder_info& obsffmpeg::encoder_factory::get_info() +{ + return info; +} + +const obsffmpeg::encoder_info& obsffmpeg::encoder_factory::get_fallback() +{ + return info_fallback; +} + +void obsffmpeg::encoder::initialize_sw(obs_data_t* settings) +{ + if (_codec->type == AVMEDIA_TYPE_VIDEO) { + // Initialize Video Encoding + auto voi = video_output_get_info(obs_encoder_video(_self)); + + // Find a suitable Pixel Format. + AVPixelFormat _pixfmt_source = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format); + AVPixelFormat _pixfmt_target = + static_cast(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT)); + if (_pixfmt_target == AV_PIX_FMT_NONE) { + // Find the best conversion format. + _pixfmt_target = ffmpeg::tools::get_least_lossy_format(_codec->pix_fmts, _pixfmt_source); + + if (_handler) // Allow Handler to override the automatic color format for sanity reasons. + _handler->override_colorformat(_pixfmt_target, settings, _codec, _context); + } else { + // Use user override, guaranteed to be supported. + bool is_format_supported = false; + for (auto ptr = _codec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) { + if (*ptr == _pixfmt_target) { + is_format_supported = true; + } + } + + if (!is_format_supported) { + std::stringstream sstr; + sstr << "Color Format '" << ffmpeg::tools::get_pixel_format_name(_pixfmt_target) + << "' is not supported by the encoder."; + throw std::exception(sstr.str().c_str()); + } + } + + _context->width = static_cast(obs_encoder_get_width(_self)); + _context->height = static_cast(obs_encoder_get_height(_self)); + ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context); + + _context->pix_fmt = _pixfmt_target; + _context->field_order = AV_FIELD_PROGRESSIVE; + _context->ticks_per_frame = 1; + _context->sample_aspect_ratio.num = _context->sample_aspect_ratio.den = 1; + _context->framerate.num = _context->time_base.den = voi->fps_num; + _context->framerate.den = _context->time_base.num = voi->fps_den; + + _swscale.set_source_size(_context->width, _context->height); + _swscale.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace); + _swscale.set_source_format(_pixfmt_source); + + _swscale.set_target_size(_context->width, _context->height); + _swscale.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace); + _swscale.set_target_format(_pixfmt_target); + + // Create Scaler + if (!_swscale.initialize(SWS_POINT)) { + std::stringstream sstr; + sstr << "Initializing scaler failed for conversion from '" + << ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()) << "' to '" + << ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()) + << "' with color space '" + << ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()) << "' and " + << (_swscale.is_source_full_range() ? "full" : "partial") << " range."; + throw std::runtime_error(sstr.str()); + } + } +} + +void obsffmpeg::encoder::initialize_hw(obs_data_t*) +{ + // Initialize Video Encoding + auto voi = video_output_get_info(obs_encoder_video(_self)); + + _context->width = voi->width; + _context->height = voi->height; + _context->field_order = AV_FIELD_PROGRESSIVE; + _context->ticks_per_frame = 1; + _context->sample_aspect_ratio.num = _context->sample_aspect_ratio.den = 1; + _context->framerate.num = _context->time_base.den = voi->fps_num; + _context->framerate.den = _context->time_base.num = voi->fps_den; + ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context); + _context->sw_pix_fmt = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format); + +#ifdef WIN32 + _context->pix_fmt = AV_PIX_FMT_D3D11; +#endif + + _context->hw_device_ctx = _hwinst->create_device_context(); + + _context->hw_frames_ctx = av_hwframe_ctx_alloc(_context->hw_device_ctx); + if (!_context->hw_frames_ctx) + throw std::runtime_error("Failed to allocate AVHWFramesContext."); + + AVHWFramesContext* ctx = reinterpret_cast(_context->hw_frames_ctx->data); + ctx->width = _context->width; + ctx->height = _context->height; + ctx->format = _context->pix_fmt; + ctx->sw_format = _context->sw_pix_fmt; + + if (av_hwframe_ctx_init(_context->hw_frames_ctx) < 0) + throw std::runtime_error("Failed to initialize AVHWFramesContext."); +} + +void obsffmpeg::encoder::push_free_frame(std::shared_ptr frame) +{ + auto now = std::chrono::high_resolution_clock::now(); + if (_free_frames.size() > 0) { + if ((now - _free_frames_last_used) < std::chrono::seconds(1)) { + _free_frames.push(frame); + } + } else { + _free_frames.push(frame); + _free_frames_last_used = std::chrono::high_resolution_clock::now(); + } +} + +std::shared_ptr obsffmpeg::encoder::pop_free_frame() +{ + std::shared_ptr frame; + if (_free_frames.size() > 0) { + // Re-use existing frames first. + frame = _free_frames.top(); + _free_frames.pop(); + } else { + if (_hwinst) { + frame = _hwinst->allocate_frame(_context->hw_frames_ctx); + } else { + frame = std::shared_ptr(av_frame_alloc(), [](AVFrame* frame) { + av_frame_unref(frame); + av_frame_free(&frame); + }); + + frame->width = _context->width; + frame->height = _context->height; + frame->format = _context->pix_fmt; + + int res = av_frame_get_buffer(frame.get(), 32); + if (res < 0) { + throw std::runtime_error(ffmpeg::tools::get_error_description(res)); + } + } + } + + return frame; +} + +void obsffmpeg::encoder::push_used_frame(std::shared_ptr frame) +{ + _used_frames.push(frame); +} + +std::shared_ptr obsffmpeg::encoder::pop_used_frame() +{ + auto frame = _used_frames.front(); + _used_frames.pop(); + return frame; +} + +obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode) + : _self(encoder), _factory(reinterpret_cast(obs_encoder_get_type_data(_self))), + _codec(_factory->get_avcodec()), _context(nullptr), _lag_in_frames(0), _count_send_frames(0), + _have_first_frame(false) +{ + // Find a handler + _handler = obsffmpeg::find_codec_handler(_codec->name); + + // Initialize GPU Stuff + if (is_texture_encode) { +#ifdef WIN32 + auto gctx = obsffmpeg::obs_graphics(); + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + _hwapi = std::make_shared(); + } +#endif + _hwinst = _hwapi->create_from_obs(); + } + + // Initialize context. + _context = avcodec_alloc_context3(_codec); + if (!_context) { + PLOG_ERROR("Failed to create context for encoder '%s'.", _codec->name); + throw std::runtime_error("failed to create context"); + } + + // Create 8MB of precached Packet data for use later on. + av_init_packet(&_current_packet); + av_new_packet(&_current_packet, 8 * 1024 * 1024); // 8 MB precached Packet size. + + if (is_texture_encode) { + initialize_hw(settings); + } else { + initialize_sw(settings); + } + + // Update settings + update(settings); + + // Initialize Encoder + auto gctx = obsffmpeg::obs_graphics(); + int res = avcodec_open2(_context, _codec, NULL); + if (res < 0) { + std::stringstream sstr; + sstr << "Initializing encoder '" << _codec->name + << "' failed with error: " << ffmpeg::tools::get_error_description(res) << " (code " << res << ")"; + throw std::runtime_error(sstr.str()); + } +} + +obsffmpeg::encoder::~encoder() +{ + auto gctx = obsffmpeg::obs_graphics(); + if (_context) { + // Flush encoders that require it. + if ((_codec->capabilities & AV_CODEC_CAP_DELAY) != 0) { + avcodec_send_frame(_context, nullptr); + while (avcodec_receive_packet(_context, &_current_packet) >= 0) { + avcodec_send_frame(_context, nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + // Close and free context. + avcodec_close(_context); + avcodec_free_context(&_context); + } + + av_packet_unref(&_current_packet); + + _swscale.finalize(); +} + +void obsffmpeg::encoder::get_properties(obs_properties_t* props, bool hw_encode) +{ + if (_handler) + _handler->get_properties(props, _codec, _context, hw_encode); + + obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES), false); + obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES_INTERVALTYPE), false); + obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES_INTERVAL_SECONDS), false); + obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES_INTERVAL_FRAMES), false); + + obs_property_set_enabled(obs_properties_get(props, ST_FFMPEG_COLORFORMAT), false); + obs_property_set_enabled(obs_properties_get(props, ST_FFMPEG_THREADS), false); + obs_property_set_enabled(obs_properties_get(props, ST_FFMPEG_STANDARDCOMPLIANCE), false); + obs_property_set_enabled(obs_properties_get(props, ST_FFMPEG_GPU), false); +} + +bool obsffmpeg::encoder::update(obs_data_t* settings) +{ + // FFmpeg Options + _context->debug = 0; + _context->strict_std_compliance = static_cast(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE)); + + /// Threading + if (!_hwinst) { + _context->thread_type = 0; + if (_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) { + _context->thread_type |= FF_THREAD_FRAME; + } + if (_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) { + _context->thread_type |= FF_THREAD_SLICE; + } + if (_context->thread_type != 0) { + int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS); + if (threads > 0) { + _context->thread_count = static_cast(threads); + } else { + _context->thread_count = std::thread::hardware_concurrency(); + } + } else { + _context->thread_count = 1; + } + // Frame Delay (Lag In Frames) + _context->delay = _context->thread_count; + } else { + _context->delay = 0; + } + + // Apply GPU Selection + if (!_hwinst && ffmpeg::tools::can_hardware_encode(_codec)) { + av_opt_set_int(_context, "gpu", (int)obs_data_get_int(settings, ST_FFMPEG_GPU), AV_OPT_SEARCH_CHILDREN); + } + + // Keyframes + if (_handler && _handler->has_keyframe_support(this)) { + // Key-Frame Options + obs_video_info ovi; + if (!obs_get_video_info(&ovi)) { + throw std::runtime_error( + "obs_get_video_info failed, restart OBS Studio to fix it (hopefully)."); + } + + int64_t kf_type = obs_data_get_int(settings, S_KEYFRAMES_INTERVALTYPE); + bool is_seconds = (kf_type == 0); + + if (is_seconds) { + _context->gop_size = static_cast( + obs_data_get_double(settings, S_KEYFRAMES_INTERVAL_SECONDS) * (ovi.fps_num / ovi.fps_den)); + } else { + _context->gop_size = static_cast(obs_data_get_int(settings, S_KEYFRAMES_INTERVAL_FRAMES)); + } + _context->keyint_min = _context->gop_size; + } + + // Handler Options + if (_handler) + _handler->update(settings, _codec, _context); + + { // FFmpeg Custom Options + const char* opts = obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS); + size_t opts_len = strnlen(opts, 65535); + + parse_ffmpeg_commandline(std::string{opts, opts + opts_len}); + } + + // Handler Overrides + if (_handler) + _handler->override_update(this, settings); + + // Handler Logging + if (_handler) { + PLOG_INFO("[%s] Initializing...", _codec->name); + PLOG_INFO("[%s] FFmpeg:", _codec->name); + PLOG_INFO("[%s] Custom Settings: %s", _codec->name, + obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS)); + PLOG_INFO("[%s] Standard Compliance: %s", _codec->name, + ffmpeg::tools::get_std_compliance_name(_context->strict_std_compliance)); + PLOG_INFO("[%s] Threading: %s (with %i threads)", _codec->name, + ffmpeg::tools::get_thread_type_name(_context->thread_type), _context->thread_count); + + PLOG_INFO("[%s] Video:", _codec->name); + if (_hwinst) { + PLOG_INFO("[%s] Texture: %ldx%ld %s %s %s", _codec->name, _context->width, _context->height, + ffmpeg::tools::get_pixel_format_name(_context->sw_pix_fmt), + ffmpeg::tools::get_color_space_name(_context->colorspace), + av_color_range_name(_context->color_range)); + } else { + PLOG_INFO("[%s] Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(), + _swscale.get_source_height(), + ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()), + ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()), + _swscale.is_source_full_range() ? "Full" : "Partial"); + PLOG_INFO("[%s] Output: %ldx%ld %s %s %s", _codec->name, _swscale.get_target_width(), + _swscale.get_target_height(), + ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()), + ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()), + _swscale.is_target_full_range() ? "Full" : "Partial"); + if (!_hwinst) + PLOG_INFO("[%s] On GPU Index: %lli", _codec->name, + obs_data_get_int(settings, ST_FFMPEG_GPU)); + } + PLOG_INFO("[%s] Framerate: %ld/%ld (%f FPS)", _codec->name, _context->time_base.den, + _context->time_base.num, + static_cast(_context->time_base.den) + / static_cast(_context->time_base.num)); + + PLOG_INFO("[%s] Keyframes: ", _codec->name); + if (_context->keyint_min != _context->gop_size) { + PLOG_INFO("[%s] Minimum: %i frames", _codec->name, _context->keyint_min); + PLOG_INFO("[%s] Maximum: %i frames", _codec->name, _context->gop_size); + } else { + PLOG_INFO("[%s] Distance: %i frames", _codec->name, _context->gop_size); + } + _handler->log_options(settings, _codec, _context); + } + + return true; +} + +void obsffmpeg::encoder::get_audio_info(audio_convert_info*) {} + +size_t obsffmpeg::encoder::get_frame_size() +{ + return size_t(); +} + +bool obsffmpeg::encoder::audio_encode(encoder_frame*, encoder_packet*, bool*) +{ + return false; +} + +void obsffmpeg::encoder::get_video_info(video_scale_info* vsi) +{ + vsi->width = _swscale.get_source_width(); + vsi->height = _swscale.get_source_height(); + vsi->format = ffmpeg::tools::avpixelformat_to_obs_videoformat(_swscale.get_source_format()); +} + +bool obsffmpeg::encoder::get_sei_data(uint8_t** data, size_t* size) +{ + if (_sei_data.size() == 0) + return false; + + *data = _sei_data.data(); + *size = _sei_data.size(); + return true; +} + +bool obsffmpeg::encoder::get_extra_data(uint8_t** data, size_t* size) +{ + if (_extra_data.size() == 0) + return false; + + *data = _extra_data.data(); + *size = _extra_data.size(); + return true; +} + +static inline void copy_data(encoder_frame* frame, AVFrame* vframe) +{ + int h_chroma_shift, v_chroma_shift; + av_pix_fmt_get_chroma_sub_sample(static_cast(vframe->format), &h_chroma_shift, &v_chroma_shift); + + for (size_t idx = 0; idx < MAX_AV_PLANES; idx++) { + if (!frame->data[idx] || !vframe->data[idx]) + continue; + + size_t plane_height = vframe->height >> (idx ? v_chroma_shift : 0); + + if (static_cast(vframe->linesize[idx]) == frame->linesize[idx]) { + std::memcpy(vframe->data[idx], frame->data[idx], frame->linesize[idx] * plane_height); + } else { + size_t ls_in = frame->linesize[idx]; + size_t ls_out = vframe->linesize[idx]; + size_t bytes = ls_in < ls_out ? ls_in : ls_out; + + uint8_t* to = vframe->data[idx]; + uint8_t* from = frame->data[idx]; + + for (size_t y = 0; y < plane_height; y++) { + std::memcpy(to, from, bytes); + to += ls_out; + from += ls_in; + } + } + } +} + +bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet) +{ + std::shared_ptr vframe = pop_free_frame(); // Retrieve an empty frame. + + // Convert frame. + { +#ifdef _DEBUG + ScopeProfiler profile("convert"); +#endif + + vframe->height = _context->height; + vframe->format = _context->pix_fmt; + vframe->color_range = _context->color_range; + vframe->colorspace = _context->colorspace; + vframe->color_primaries = _context->color_primaries; + vframe->color_trc = _context->color_trc; + vframe->pts = frame->pts; + + if ((_swscale.is_source_full_range() == _swscale.is_target_full_range()) + && (_swscale.get_source_colorspace() == _swscale.get_target_colorspace()) + && (_swscale.get_source_format() == _swscale.get_target_format())) { + copy_data(frame, vframe.get()); + } else { + int res = _swscale.convert(reinterpret_cast(frame->data), + reinterpret_cast(frame->linesize), 0, _context->height, + vframe->data, vframe->linesize); + if (res <= 0) { + PLOG_ERROR("Failed to convert frame: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } + } + + if (!encode_avframe(vframe, packet, received_packet)) + return false; + + return true; +} + +bool obsffmpeg::encoder::video_encode_texture(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_lock_key, + encoder_packet* packet, bool* received_packet) +{ + if (handle == GS_INVALID_HANDLE) { + PLOG_ERROR("Received invalid handle."); + *next_lock_key = lock_key; + return false; + } + + std::shared_ptr vframe = pop_free_frame(); + _hwinst->copy_from_obs(_context->hw_frames_ctx, handle, lock_key, next_lock_key, vframe); + + vframe->color_range = _context->color_range; + vframe->colorspace = _context->colorspace; + vframe->color_primaries = _context->color_primaries; + vframe->color_trc = _context->color_trc; + vframe->pts = pts; + + if (!encode_avframe(vframe, packet, received_packet)) + return false; + + *next_lock_key = lock_key; + + return true; +} + +int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_packet* packet) +{ + int res = 0; + + av_packet_unref(&_current_packet); + + { + auto gctx = obsffmpeg::obs_graphics(); + res = avcodec_receive_packet(_context, &_current_packet); + } + if (res != 0) { + return res; + } + + if (!_have_first_frame) { + if (_codec->id == AV_CODEC_ID_H264) { + uint8_t* tmp_packet; + uint8_t* tmp_header; + uint8_t* tmp_sei; + size_t sz_packet, sz_header, sz_sei; + + obs_extract_avc_headers(_current_packet.data, _current_packet.size, &tmp_packet, &sz_packet, + &tmp_header, &sz_header, &tmp_sei, &sz_sei); + + if (sz_header) { + _extra_data.resize(sz_header); + std::memcpy(_extra_data.data(), tmp_header, sz_header); + } + + if (sz_sei) { + _sei_data.resize(sz_sei); + std::memcpy(_sei_data.data(), tmp_sei, sz_sei); + } + + // Not required, we only need the Extra Data and SEI Data anyway. + //std::memcpy(_current_packet.data, tmp_packet, sz_packet); + //_current_packet.size = static_cast(sz_packet); + + bfree(tmp_packet); + bfree(tmp_header); + bfree(tmp_sei); + } else if (_codec->id == AV_CODEC_ID_HEVC) { + obsffmpeg::codecs::hevc::extract_header_sei(_current_packet.data, _current_packet.size, + _extra_data, _sei_data); + } else if (_context->extradata != nullptr) { + _extra_data.resize(_context->extradata_size); + std::memcpy(_extra_data.data(), _context->extradata, _context->extradata_size); + } + _have_first_frame = true; + } + + // Allow Handler Post-Processing + if (_handler) + _handler->process_avpacket(_current_packet, _codec, _context); + + packet->type = OBS_ENCODER_VIDEO; + packet->pts = _current_packet.pts; + packet->dts = _current_packet.dts; + packet->data = _current_packet.data; + packet->size = _current_packet.size; + packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY); + packet->drop_priority = packet->keyframe ? 0 : 1; + *received_packet = true; + + push_free_frame(pop_used_frame()); + + return res; +} + +int obsffmpeg::encoder::send_frame(std::shared_ptr const frame) +{ + int res = 0; + { + auto gctx = obsffmpeg::obs_graphics(); + res = avcodec_send_frame(_context, frame.get()); + } + if (res == 0) { + push_used_frame(frame); + } + + return res; +} + +bool obsffmpeg::encoder::encode_avframe(std::shared_ptr frame, encoder_packet* packet, bool* received_packet) +{ +#ifdef _DEBUG + ScopeProfiler profile("loop"); +#endif + + bool sent_frame = false; + bool recv_packet = false; + bool should_lag = (_count_send_frames >= _lag_in_frames); + + auto loop_begin = std::chrono::high_resolution_clock::now(); + auto loop_end = loop_begin + std::chrono::milliseconds(50); + + while ((!sent_frame || (should_lag && !recv_packet)) + && !(std::chrono::high_resolution_clock::now() > loop_end)) { + bool eagain_is_stupid = false; + + if (!sent_frame) { +#ifdef _DEBUG + ScopeProfiler profile_inner("send"); +#endif + int res = send_frame(frame); + switch (res) { + case 0: + sent_frame = true; + frame = nullptr; + break; + case AVERROR(EAGAIN): + // This means we should call receive_packet again, but what do we do with that data? + // Why can't we queue on both? Do I really have to implement threading for this stuff? + if (*received_packet == true) { + PLOG_WARNING("Skipped frame due to EAGAIN when a packet was already returned."); + sent_frame = true; + } + eagain_is_stupid = true; + break; + case AVERROR(EOF): + PLOG_ERROR("Skipped frame due to end of stream."); + sent_frame = true; + break; + default: + PLOG_ERROR("Failed to encode frame: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } + + if (!recv_packet) { +#ifdef _DEBUG + ScopeProfiler profile_inner("recieve"); +#endif + int res = receive_packet(received_packet, packet); + switch (res) { + case 0: + recv_packet = true; + break; + case AVERROR(EOF): + PLOG_ERROR("Received end of file."); + recv_packet = true; + break; + case AVERROR(EAGAIN): + if (sent_frame) { + recv_packet = true; + } + if (eagain_is_stupid) { + PLOG_ERROR("Both send and recieve returned EAGAIN, encoder is broken."); + return false; + } + break; + default: + PLOG_ERROR("Failed to receive packet: %s (%ld).", + ffmpeg::tools::get_error_description(res), res); + return false; + } + } + + if (!sent_frame || !recv_packet) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + if (!sent_frame) + push_free_frame(frame); + + return true; +} + +bool obsffmpeg::encoder::is_hardware_encode() +{ + return _hwinst != nullptr; +} + +const AVCodec* obsffmpeg::encoder::get_avcodec() +{ + return _codec; +} + +const AVCodecContext* obsffmpeg::encoder::get_avcodeccontext() +{ + return _context; +} + +void obsffmpeg::encoder::parse_ffmpeg_commandline(std::string text) +{ + // Steps to properly parse a command line: + // 1. Split by space and package by quotes. + // 2. Parse each resulting option individually. + + // First, we split by space and of course respect quotes while doing so. + // That means that "-foo= bar" is stored as std::string("-foo= bar"), + // and things like -foo="bar" is stored as std::string("-foo=\"bar\""). + // However "-foo"=bar" -foo2=bar" is stored as std::string("-foo=bar -foo2=bar") + // because the quote was not escaped. + std::list opts; + std::stringstream opt_stream{std::ios_base::in | std::ios_base::out | std::ios_base::binary}; + std::stack quote_stack; + for (size_t p = 0; p <= text.size(); p++) { + char here = p < text.size() ? text.at(p) : 0; + + if (here == '\\') { + size_t p2 = p + 1; + if (p2 < text.size()) { + char here2 = text.at(p2); + if (isdigit(here2)) { // Octal + // Not supported yet. + p++; + } else if (here2 == 'x') { // Hexadecimal + // Not supported yet. + p += 3; + } else if (here2 == 'u') { // 4 or 8 wide Unicode. + // Not supported yet. + } else if (here2 == 'a') { + opt_stream << '\a'; + p++; + } else if (here2 == 'b') { + opt_stream << '\b'; + p++; + } else if (here2 == 'f') { + opt_stream << '\f'; + p++; + } else if (here2 == 'n') { + opt_stream << '\n'; + p++; + } else if (here2 == 'r') { + opt_stream << '\r'; + p++; + } else if (here2 == 't') { + opt_stream << '\t'; + p++; + } else if (here2 == 'v') { + opt_stream << '\v'; + p++; + } else if (here2 == '\\') { + opt_stream << '\\'; + p++; + } else if (here2 == '\'') { + opt_stream << '\''; + p++; + } else if (here2 == '"') { + opt_stream << '"'; + p++; + } else if (here2 == '?') { + opt_stream << '\?'; + p++; + } + } + } else if ((here == '\'') || (here == '"')) { + if (quote_stack.size() > 1) { + opt_stream << here; + } + if (quote_stack.size() == 0) { + quote_stack.push(here); + } else if (quote_stack.top() == here) { + quote_stack.pop(); + } else { + quote_stack.push(here); + } + } else if ((here == 0) || ((here == ' ') && (quote_stack.size() == 0))) { + std::string ropt = opt_stream.str(); + if (ropt.size() > 0) { + opts.push_back(ropt); + opt_stream.str(std::string()); + opt_stream.clear(); + } + } else { + opt_stream << here; + } + } + + // Now that we have a list of parameters as neatly grouped strings, and + // have also dealt with escaping for the most part. We want to parse + // an FFmpeg commandline option set here, so the first character in + // the string must be a '-'. + for (std::string& opt : opts) { + // Skip empty options. + if (opt.size() == 0) + continue; + + // Skip options that don't start with a '-'. + if (opt.at(0) != '-') { + PLOG_WARNING("Option '%s' is malformed, must start with a '-'.", opt.c_str()); + continue; + } + + // Skip options that don't contain a '='. + const char* cstr = opt.c_str(); + const char* eq_at = strchr(cstr, '='); + if (eq_at == nullptr) { + PLOG_WARNING("Option '%s' is malformed, must contain a '='.", opt.c_str()); + continue; + } + + try { + std::string key = opt.substr(1, eq_at - cstr - 1); + std::string value = opt.substr(eq_at - cstr + 1); + + int res = av_opt_set(_context, key.c_str(), value.c_str(), AV_OPT_SEARCH_CHILDREN); + if (res < 0) { + PLOG_WARNING("Option '%s' (key: '%s', value: '%s') encountered error: %s", opt.c_str(), + key.c_str(), value.c_str(), ffmpeg::tools::get_error_description(res)); + } + } catch (const std::exception& ex) { + PLOG_ERROR("Option '%s' encountered exception: %s", opt.c_str(), ex.what()) + } + } +} diff --git a/source/encoders/encoder.hpp b/source/encoders/encoder.hpp new file mode 100644 index 00000000..5fa266df --- /dev/null +++ b/source/encoders/encoder.hpp @@ -0,0 +1,164 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "ffmpeg/avframe-queue.hpp" +#include "ffmpeg/swscale.hpp" +#include "hwapi/base.hpp" +#include "ui/handler.hpp" + +extern "C" { +#include +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + class unsupported_gpu_exception : public std::runtime_error { + public: + unsupported_gpu_exception(const std::string& reason) : runtime_error(reason) {} + }; + + struct encoder_info { + std::string uid; + std::string codec; + std::string readable_name; + obs_encoder_info oei = {0}; + }; + + class encoder_factory { + encoder_info info; + encoder_info info_fallback; + const AVCodec* avcodec_ptr; + + std::shared_ptr _handler; + + public: + encoder_factory(const AVCodec* codec); + virtual ~encoder_factory(); + + void register_encoder(); + + void get_defaults(obs_data_t* settings, bool hw_encoder = false); + + void get_properties(obs_properties_t* props, bool hw_encoder = false); + + const AVCodec* get_avcodec(); + + const encoder_info& get_info(); + + const encoder_info& get_fallback(); + }; + + class encoder { + obs_encoder_t* _self; + encoder_factory* _factory; + + const AVCodec* _codec; + AVCodecContext* _context; + + std::shared_ptr _handler; + + std::shared_ptr _hwapi; + std::shared_ptr _hwinst; + + ffmpeg::swscale _swscale; + AVPacket _current_packet; + + size_t _lag_in_frames; + size_t _count_send_frames; + + // Extra Data + bool _have_first_frame; + std::vector _extra_data; + std::vector _sei_data; + + // Frame Stack and Queue + std::stack> _free_frames; + std::queue> _used_frames; + std::chrono::high_resolution_clock::time_point _free_frames_last_used; + + void initialize_sw(obs_data_t* settings); + void initialize_hw(obs_data_t* settings); + + void push_free_frame(std::shared_ptr frame); + std::shared_ptr pop_free_frame(); + + void push_used_frame(std::shared_ptr frame); + std::shared_ptr pop_used_frame(); + + public: + encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode = false); + virtual ~encoder(); + + public: // OBS API + // Shared + void get_properties(obs_properties_t* props, bool hw_encode = false); + + bool update(obs_data_t* settings); + + // Audio only + void get_audio_info(struct audio_convert_info* info); + + size_t get_frame_size(); + + bool audio_encode(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet); + + // Video only + void get_video_info(struct video_scale_info* info); + + bool get_sei_data(uint8_t** sei_data, size_t* size); + + bool get_extra_data(uint8_t** extra_data, size_t* size); + + bool video_encode(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet); + + bool video_encode_texture(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key, + struct encoder_packet* packet, bool* received_packet); + + int receive_packet(bool* received_packet, struct encoder_packet* packet); + + int send_frame(std::shared_ptr frame); + + bool encode_avframe(std::shared_ptr frame, struct encoder_packet* packet, + bool* received_packet); + + public: // Handler API + bool is_hardware_encode(); + + const AVCodec* get_avcodec(); + + const AVCodecContext* get_avcodeccontext(); + + void parse_ffmpeg_commandline(std::string text); + }; +} // namespace obsffmpeg diff --git a/source/encoders/handlers/debug_handler.cpp b/source/encoders/handlers/debug_handler.cpp new file mode 100644 index 00000000..cb78f22d --- /dev/null +++ b/source/encoders/handlers/debug_handler.cpp @@ -0,0 +1,210 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "debug_handler.hpp" +#include +#include +#include +#include "handler.hpp" +#include "plugin.hpp" +#include "utility.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +void obsffmpeg::ui::debug_handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*, bool) {} + +template +std::string to_string(T value){}; + +template<> +std::string to_string(int64_t value) +{ + std::vector buf(32); + snprintf(buf.data(), buf.size(), "%lld", value); + return std::string(buf.data(), buf.data() + buf.size()); +} + +template<> +std::string to_string(uint64_t value) +{ + std::vector buf(32); + snprintf(buf.data(), buf.size(), "%llu", value); + return std::string(buf.data(), buf.data() + buf.size()); +} + +template<> +std::string to_string(double_t value) +{ + std::vector buf(32); + snprintf(buf.data(), buf.size(), "%f", value); + return std::string(buf.data(), buf.data() + buf.size()); +} + +void obsffmpeg::ui::debug_handler::get_properties(obs_properties_t*, const AVCodec* codec, AVCodecContext* context, + bool) +{ + if (context) + return; + + AVCodecContext* ctx = avcodec_alloc_context3(codec); + if (!ctx->priv_data) { + avcodec_free_context(&ctx); + return; + } + + PLOG_INFO("Options for '%s':", codec->name); + + std::pair opt_type_name[] = { + {AV_OPT_TYPE_FLAGS, "Flags"}, + {AV_OPT_TYPE_INT, "Int"}, + {AV_OPT_TYPE_INT64, "Int64"}, + {AV_OPT_TYPE_DOUBLE, "Double"}, + {AV_OPT_TYPE_FLOAT, "Float"}, + {AV_OPT_TYPE_STRING, "String"}, + {AV_OPT_TYPE_RATIONAL, "Rational"}, + {AV_OPT_TYPE_BINARY, "Binary"}, + {AV_OPT_TYPE_DICT, "Dictionary"}, + {AV_OPT_TYPE_UINT64, "Unsigned Int64"}, + {AV_OPT_TYPE_CONST, "Constant"}, + {AV_OPT_TYPE_IMAGE_SIZE, "Image Size"}, + {AV_OPT_TYPE_PIXEL_FMT, "Pixel Format"}, + {AV_OPT_TYPE_SAMPLE_FMT, "Sample Format"}, + {AV_OPT_TYPE_VIDEO_RATE, "Video Rate"}, + {AV_OPT_TYPE_DURATION, "Duration"}, + {AV_OPT_TYPE_COLOR, "Color"}, + {AV_OPT_TYPE_CHANNEL_LAYOUT, "Layout"}, + {AV_OPT_TYPE_BOOL, "Bool"}, + }; + std::map unit_types; + + const AVOption* opt = nullptr; + while ((opt = av_opt_next(ctx->priv_data, opt)) != nullptr) { + std::string type_name = ""; + for (auto kv : opt_type_name) { + if (opt->type == kv.first) { + type_name = kv.second; + break; + } + } + + if (opt->type == AV_OPT_TYPE_CONST) { + if (opt->unit == nullptr) { + PLOG_INFO(" Constant '%s' and help text '%s' with unknown settings.", opt->name, + opt->help); + } else { + auto unit_type = unit_types.find(opt->unit); + if (unit_type == unit_types.end()) { + PLOG_INFO(" [%s] Flag '%s' and help text '%s' with value '%lld'.", opt->unit, + opt->name, opt->help, opt->default_val.i64); + } else { + std::string out; + switch (unit_type->second) { + case AV_OPT_TYPE_BOOL: + out = opt->default_val.i64 ? "true" : "false"; + break; + case AV_OPT_TYPE_INT: + out = to_string(opt->default_val.i64); + break; + case AV_OPT_TYPE_UINT64: + out = to_string(static_cast(opt->default_val.i64)); + break; + case AV_OPT_TYPE_FLAGS: + out = to_string(static_cast(opt->default_val.i64)); + break; + case AV_OPT_TYPE_FLOAT: + case AV_OPT_TYPE_DOUBLE: + out = to_string(opt->default_val.dbl); + break; + case AV_OPT_TYPE_STRING: + out = opt->default_val.str; + break; + case AV_OPT_TYPE_BINARY: + case AV_OPT_TYPE_IMAGE_SIZE: + case AV_OPT_TYPE_PIXEL_FMT: + case AV_OPT_TYPE_SAMPLE_FMT: + case AV_OPT_TYPE_VIDEO_RATE: + case AV_OPT_TYPE_DURATION: + case AV_OPT_TYPE_COLOR: + case AV_OPT_TYPE_CHANNEL_LAYOUT: + break; + } + + PLOG_INFO(" [%s] Constant '%s' and help text '%s' with value '%s'.", opt->unit, + opt->name, opt->help, out.c_str()); + } + } + } else { + if (opt->unit != nullptr) { + unit_types.emplace(opt->name, opt->type); + } + + std::string minimum = "", maximum = "", out; + minimum = to_string(opt->min); + maximum = to_string(opt->max); + { + switch (opt->type) { + case AV_OPT_TYPE_BOOL: + out = opt->default_val.i64 ? "true" : "false"; + break; + case AV_OPT_TYPE_INT: + out = to_string(opt->default_val.i64); + break; + case AV_OPT_TYPE_UINT64: + out = to_string(static_cast(opt->default_val.i64)); + break; + case AV_OPT_TYPE_FLAGS: + out = to_string(static_cast(opt->default_val.i64)); + break; + case AV_OPT_TYPE_FLOAT: + case AV_OPT_TYPE_DOUBLE: + out = to_string(opt->default_val.dbl); + break; + case AV_OPT_TYPE_STRING: + out = opt->default_val.str ? opt->default_val.str : ""; + break; + case AV_OPT_TYPE_BINARY: + case AV_OPT_TYPE_IMAGE_SIZE: + case AV_OPT_TYPE_PIXEL_FMT: + case AV_OPT_TYPE_SAMPLE_FMT: + case AV_OPT_TYPE_VIDEO_RATE: + case AV_OPT_TYPE_DURATION: + case AV_OPT_TYPE_COLOR: + case AV_OPT_TYPE_CHANNEL_LAYOUT: + break; + } + } + + PLOG_INFO( + " Option '%s'%s%s%s with help '%s' of type '%s' with default value '%s', minimum '%s' and maximum '%s'.", + opt->name, opt->unit ? " with unit (" : "", opt->unit ? opt->unit : "", + opt->unit ? ")" : "", opt->help, type_name.c_str(), out.c_str(), minimum.c_str(), + maximum.c_str()); + } + } +} + +void obsffmpeg::ui::debug_handler::update(obs_data_t*, const AVCodec*, AVCodecContext*) {} diff --git a/source/encoders/handlers/debug_handler.hpp b/source/encoders/handlers/debug_handler.hpp new file mode 100644 index 00000000..ad6e9504 --- /dev/null +++ b/source/encoders/handlers/debug_handler.hpp @@ -0,0 +1,39 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "handler.hpp" + +namespace obsffmpeg { + namespace ui { + class debug_handler : public handler { + public: + virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, + bool hw_encode) override; + + virtual void get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool hw_encode) override; + + virtual void update(obs_data_t* settings, const AVCodec* codec, + AVCodecContext* context) override; + }; + } // namespace ui +} // namespace obsffmpeg diff --git a/source/encoders/handlers/handler.cpp b/source/encoders/handlers/handler.cpp new file mode 100644 index 00000000..86d73294 --- /dev/null +++ b/source/encoders/handlers/handler.cpp @@ -0,0 +1,46 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "handler.hpp" +#include "encoder.hpp" + +void obsffmpeg::ui::handler::adjust_encoder_info(obsffmpeg::encoder_factory*, obsffmpeg::encoder_info*, + obsffmpeg::encoder_info*) +{} + +void obsffmpeg::ui::handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*, bool) {} + +bool obsffmpeg::ui::handler::has_keyframe_support(obsffmpeg::encoder* instance) +{ + return (instance->get_avcodec()->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0; +} + +void obsffmpeg::ui::handler::get_properties(obs_properties_t*, const AVCodec*, AVCodecContext*, bool) {} + +void obsffmpeg::ui::handler::update(obs_data_t*, const AVCodec*, AVCodecContext*) {} + +void obsffmpeg::ui::handler::override_update(obsffmpeg::encoder*, obs_data_t*) {} + +void obsffmpeg::ui::handler::log_options(obs_data_t*, const AVCodec*, AVCodecContext*) {} + +void obsffmpeg::ui::handler::override_colorformat(AVPixelFormat&, obs_data_t*, const AVCodec*, AVCodecContext*) {} + +void obsffmpeg::ui::handler::process_avpacket(AVPacket&, const AVCodec*, AVCodecContext*) {} diff --git a/source/encoders/handlers/handler.hpp b/source/encoders/handlers/handler.hpp new file mode 100644 index 00000000..f350a453 --- /dev/null +++ b/source/encoders/handlers/handler.hpp @@ -0,0 +1,74 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include "hwapi/base.hpp" + +extern "C" { +#include + +#include +#include +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + struct encoder_info; + class encoder_factory; + class encoder; + + namespace ui { + class handler { + public /*factory*/: + virtual void adjust_encoder_info(obsffmpeg::encoder_factory* factory, + obsffmpeg::encoder_info* main, + obsffmpeg::encoder_info* fallback); + + virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, + bool hw_encode); + + public /*settings*/: + virtual bool has_keyframe_support(obsffmpeg::encoder* instance); + + virtual void get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool hw_encode); + + virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + virtual void override_update(obsffmpeg::encoder* instance, obs_data_t* settings); + + virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + public /*instance*/: + + virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, + const AVCodec* codec, AVCodecContext* context); + + virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context); + }; + } // namespace ui +} // namespace obsffmpeg diff --git a/source/encoders/handlers/nvenc_h264_handler.cpp b/source/encoders/handlers/nvenc_h264_handler.cpp new file mode 100644 index 00000000..e79a7015 --- /dev/null +++ b/source/encoders/handlers/nvenc_h264_handler.cpp @@ -0,0 +1,182 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "nvenc_h264_handler.hpp" +#include "codecs/h264.hpp" +#include "encoder.hpp" +#include "ffmpeg/tools.hpp" +#include "nvenc_shared.hpp" +#include "plugin.hpp" +#include "strings.hpp" +#include "utility.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +using namespace obsffmpeg::codecs::h264; + +std::map profiles{ + {profile::BASELINE, "baseline"}, + {profile::MAIN, "main"}, + {profile::HIGH, "high"}, + {profile::HIGH444_PREDICTIVE, "high444p"}, +}; + +std::map levels{ + {level::L1_0, "1.0"}, {level::L1_0b, "1.0b"}, {level::L1_1, "1.1"}, {level::L1_2, "1.2"}, {level::L1_3, "1.3"}, + {level::L2_0, "2.0"}, {level::L2_1, "2.1"}, {level::L2_2, "2.2"}, {level::L3_0, "3.0"}, {level::L3_1, "3.1"}, + {level::L3_2, "3.2"}, {level::L4_0, "4.0"}, {level::L4_1, "4.1"}, {level::L4_2, "4.2"}, {level::L5_0, "5.0"}, + {level::L5_1, "5.1"}, {level::L5_2, "5.2"}, +}; + +INITIALIZER(nvenc_h264_handler_init) +{ + obsffmpeg::initializers.push_back([]() { + obsffmpeg::register_codec_handler("h264_nvenc", std::make_shared()); + }); +}; + +void obsffmpeg::ui::nvenc_h264_handler::adjust_encoder_info(obsffmpeg::encoder_factory*, obsffmpeg::encoder_info* main, + obsffmpeg::encoder_info* fallback) +{ + main->readable_name = "H.264/AVC NVidia NVENC (Hardware)"; + fallback->readable_name = "H.264/AVC NVidia NVENC (Software)"; +} + +void obsffmpeg::ui::nvenc_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, + AVCodecContext* context, bool) +{ + nvenc::get_defaults(settings, codec, context); + + obs_data_set_default_int(settings, P_H264_PROFILE, static_cast(codecs::h264::profile::HIGH)); + obs_data_set_default_int(settings, P_H264_LEVEL, static_cast(codecs::h264::level::UNKNOWN)); +} + +bool obsffmpeg::ui::nvenc_h264_handler::has_keyframe_support(obsffmpeg::encoder*) +{ + return true; +} + +void obsffmpeg::ui::nvenc_h264_handler::get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool) +{ + if (!context) { + this->get_encoder_properties(props, codec); + } else { + this->get_runtime_properties(props, codec, context); + } +} + +void obsffmpeg::ui::nvenc_h264_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + nvenc::update(settings, codec, context); + + { + auto found = + profiles.find(static_cast(obs_data_get_int(settings, P_H264_PROFILE))); + if (found != profiles.end()) { + av_opt_set(context->priv_data, "profile", found->second.c_str(), 0); + } + } + + { + auto found = levels.find(static_cast(obs_data_get_int(settings, P_H264_LEVEL))); + if (found != levels.end()) { + av_opt_set(context->priv_data, "level", found->second.c_str(), 0); + } else { + av_opt_set(context->priv_data, "level", "auto", 0); + } + } +} + +void obsffmpeg::ui::nvenc_h264_handler::override_update(obsffmpeg::encoder* instance, obs_data_t* settings) +{ + nvenc::override_update(instance, settings); +} + +void obsffmpeg::ui::nvenc_h264_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + nvenc::log_options(settings, codec, context); + + PLOG_INFO("[%s] H.265/HEVC:", codec->name); + ffmpeg::tools::print_av_option_string(context, "profile", " Profile", [](int64_t v) { + profile val = static_cast(v); + auto index = profiles.find(val); + if (index != profiles.end()) + return index->second; + return std::string(""); + }); + ffmpeg::tools::print_av_option_string(context, "level", " Level", [](int64_t v) { + level val = static_cast(v); + auto index = levels.find(val); + if (index != levels.end()) + return index->second; + return std::string(""); + }); +} + +void obsffmpeg::ui::nvenc_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec) +{ + nvenc::get_properties_pre(props, codec); + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, P_H264, TRANSLATE(P_H264), OBS_GROUP_NORMAL, grp); + } + + { + auto p = obs_properties_add_list(grp, P_H264_PROFILE, TRANSLATE(P_H264_PROFILE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_H264_PROFILE))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_DEFAULT), + static_cast(codecs::h264::profile::UNKNOWN)); + for (auto const kv : profiles) { + std::string trans = std::string(P_H264_PROFILE) + "." + kv.second; + obs_property_list_add_int(p, TRANSLATE(trans.c_str()), static_cast(kv.first)); + } + } + { + auto p = obs_properties_add_list(grp, P_H264_LEVEL, TRANSLATE(P_H264_LEVEL), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_H264_LEVEL))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC), + static_cast(codecs::h264::level::UNKNOWN)); + for (auto const kv : levels) { + obs_property_list_add_int(p, kv.second.c_str(), static_cast(kv.first)); + } + } + } + + nvenc::get_properties_post(props, codec); +} + +void obsffmpeg::ui::nvenc_h264_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context) +{ + nvenc::get_runtime_properties(props, codec, context); +} diff --git a/source/encoders/handlers/nvenc_h264_handler.hpp b/source/encoders/handlers/nvenc_h264_handler.hpp new file mode 100644 index 00000000..a71eaadc --- /dev/null +++ b/source/encoders/handlers/nvenc_h264_handler.hpp @@ -0,0 +1,66 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "handler.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + namespace ui { + class nvenc_h264_handler : public handler { + public /*factory*/: + virtual void adjust_encoder_info(obsffmpeg::encoder_factory* factory, + obsffmpeg::encoder_info* main, + obsffmpeg::encoder_info* fallback); + + virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, + bool hw_encode); + + public /*settings*/: + virtual bool has_keyframe_support(obsffmpeg::encoder* instance); + + virtual void get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool hw_encode); + + virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + virtual void override_update(obsffmpeg::encoder* instance, obs_data_t* settings); + + virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + public /*instance*/: + //virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + private: + void get_encoder_properties(obs_properties_t* props, const AVCodec* codec); + + void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context); + }; + } // namespace ui +} // namespace obsffmpeg diff --git a/source/encoders/handlers/nvenc_hevc_handler.cpp b/source/encoders/handlers/nvenc_hevc_handler.cpp new file mode 100644 index 00000000..67547994 --- /dev/null +++ b/source/encoders/handlers/nvenc_hevc_handler.cpp @@ -0,0 +1,208 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "nvenc_hevc_handler.hpp" +#include "codecs/hevc.hpp" +#include "encoder.hpp" +#include "ffmpeg/tools.hpp" +#include "nvenc_shared.hpp" +#include "plugin.hpp" +#include "strings.hpp" +#include "utility.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +using namespace obsffmpeg::codecs::hevc; + +std::map profiles{ + {profile::MAIN, "main"}, + {profile::MAIN10, "main10"}, + {profile::RANGE_EXTENDED, "rext"}, +}; + +std::map tiers{ + {tier::MAIN, "main"}, + {tier::HIGH, "high"}, +}; + +std::map levels{ + {level::L1_0, "1.0"}, {level::L2_0, "2.0"}, {level::L2_1, "2.1"}, {level::L3_0, "3.0"}, {level::L3_1, "3.1"}, + {level::L4_0, "4.0"}, {level::L4_1, "4.1"}, {level::L5_0, "5.0"}, {level::L5_1, "5.1"}, {level::L5_2, "5.2"}, + {level::L6_0, "6.0"}, {level::L6_1, "6.1"}, {level::L6_2, "6.2"}, +}; + +INITIALIZER(nvenc_hevc_handler_init) +{ + obsffmpeg::initializers.push_back([]() { + obsffmpeg::register_codec_handler("hevc_nvenc", std::make_shared()); + }); +}; + +void obsffmpeg::ui::nvenc_hevc_handler::adjust_encoder_info(obsffmpeg::encoder_factory*, obsffmpeg::encoder_info* main, + obsffmpeg::encoder_info* fallback) +{ + main->readable_name = "H.265/HEVC Nvidia NVENC (Hardware)"; + fallback->readable_name = "H.265/HEVC Nvidia NVENC (Software)"; +} + +void obsffmpeg::ui::nvenc_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, + AVCodecContext* context, bool) +{ + nvenc::get_defaults(settings, codec, context); + + obs_data_set_default_int(settings, P_HEVC_PROFILE, static_cast(codecs::hevc::profile::MAIN)); + obs_data_set_default_int(settings, P_HEVC_TIER, static_cast(codecs::hevc::profile::MAIN)); + obs_data_set_default_int(settings, P_HEVC_LEVEL, static_cast(codecs::hevc::level::UNKNOWN)); +} + +bool obsffmpeg::ui::nvenc_hevc_handler::has_keyframe_support(obsffmpeg::encoder*) +{ + return true; +} + +void obsffmpeg::ui::nvenc_hevc_handler::get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool) +{ + if (!context) { + this->get_encoder_properties(props, codec); + } else { + this->get_runtime_properties(props, codec, context); + } +} + +void obsffmpeg::ui::nvenc_hevc_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + nvenc::update(settings, codec, context); + + { // HEVC Options + auto found = profiles.find(static_cast(obs_data_get_int(settings, P_HEVC_PROFILE))); + if (found != profiles.end()) { + av_opt_set(context->priv_data, "profile", found->second.c_str(), 0); + } + } + { + auto found = tiers.find(static_cast(obs_data_get_int(settings, P_HEVC_TIER))); + if (found != tiers.end()) { + av_opt_set(context->priv_data, "tier", found->second.c_str(), 0); + } + } + { + auto found = levels.find(static_cast(obs_data_get_int(settings, P_HEVC_LEVEL))); + if (found != levels.end()) { + av_opt_set(context->priv_data, "level", found->second.c_str(), 0); + } else { + av_opt_set(context->priv_data, "level", "auto", 0); + } + } +} + +void obsffmpeg::ui::nvenc_hevc_handler::override_update(obsffmpeg::encoder* instance, obs_data_t* settings) +{ + nvenc::override_update(instance, settings); +} + +void obsffmpeg::ui::nvenc_hevc_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + nvenc::log_options(settings, codec, context); + + PLOG_INFO("[%s] H.265/HEVC:", codec->name); + ffmpeg::tools::print_av_option_string(context, "profile", " Profile", [](int64_t v) { + profile val = static_cast(v); + auto index = profiles.find(val); + if (index != profiles.end()) + return index->second; + return std::string(""); + }); + ffmpeg::tools::print_av_option_string(context, "level", " Level", [](int64_t v) { + level val = static_cast(v); + auto index = levels.find(val); + if (index != levels.end()) + return index->second; + return std::string(""); + }); + ffmpeg::tools::print_av_option_string(context, "tier", " Tier", [](int64_t v) { + tier val = static_cast(v); + auto index = tiers.find(val); + if (index != tiers.end()) + return index->second; + return std::string(""); + }); +} + +void obsffmpeg::ui::nvenc_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec) +{ + nvenc::get_properties_pre(props, codec); + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, P_HEVC, TRANSLATE(P_HEVC), OBS_GROUP_NORMAL, grp); + } + + { + auto p = obs_properties_add_list(grp, P_HEVC_PROFILE, TRANSLATE(P_HEVC_PROFILE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_HEVC_PROFILE))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_DEFAULT), + static_cast(codecs::hevc::profile::UNKNOWN)); + for (auto const kv : profiles) { + std::string trans = std::string(P_HEVC_PROFILE) + "." + kv.second; + obs_property_list_add_int(p, TRANSLATE(trans.c_str()), static_cast(kv.first)); + } + } + { + auto p = obs_properties_add_list(grp, P_HEVC_TIER, TRANSLATE(P_HEVC_TIER), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_HEVC_TIER))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_DEFAULT), + static_cast(codecs::hevc::tier::UNKNOWN)); + for (auto const kv : tiers) { + std::string trans = std::string(P_HEVC_TIER) + "." + kv.second; + obs_property_list_add_int(p, TRANSLATE(trans.c_str()), static_cast(kv.first)); + } + } + { + auto p = obs_properties_add_list(grp, P_HEVC_LEVEL, TRANSLATE(P_HEVC_LEVEL), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_HEVC_LEVEL))); + obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC), + static_cast(codecs::hevc::level::UNKNOWN)); + for (auto const kv : levels) { + obs_property_list_add_int(p, kv.second.c_str(), static_cast(kv.first)); + } + } + } + + nvenc::get_properties_post(props, codec); +} + +void obsffmpeg::ui::nvenc_hevc_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context) +{ + nvenc::get_runtime_properties(props, codec, context); +} diff --git a/source/encoders/handlers/nvenc_hevc_handler.hpp b/source/encoders/handlers/nvenc_hevc_handler.hpp new file mode 100644 index 00000000..891de4ce --- /dev/null +++ b/source/encoders/handlers/nvenc_hevc_handler.hpp @@ -0,0 +1,66 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "handler.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + namespace ui { + class nvenc_hevc_handler : public handler { + public /*factory*/: + virtual void adjust_encoder_info(obsffmpeg::encoder_factory* factory, + obsffmpeg::encoder_info* main, + obsffmpeg::encoder_info* fallback); + + virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, + bool hw_encode); + + public /*settings*/: + virtual bool has_keyframe_support(obsffmpeg::encoder* instance); + + virtual void get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool hw_encode); + + virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + virtual void override_update(obsffmpeg::encoder* instance, obs_data_t* settings); + + virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + public /*instance*/: + //virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + private: + void get_encoder_properties(obs_properties_t* props, const AVCodec* codec); + + void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context); + }; + } // namespace ui +} // namespace obsffmpeg diff --git a/source/encoders/handlers/nvenc_shared.cpp b/source/encoders/handlers/nvenc_shared.cpp new file mode 100644 index 00000000..14a5c968 --- /dev/null +++ b/source/encoders/handlers/nvenc_shared.cpp @@ -0,0 +1,767 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "nvenc_shared.hpp" +#include +#include "codecs/hevc.hpp" +#include "encoder.hpp" +#include "ffmpeg/tools.hpp" +#include "plugin.hpp" +#include "strings.hpp" +#include "utility.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +#define ST_PRESET "NVENC.Preset" +#define ST_PRESET_(x) ST_PRESET "." D_VSTR(x) + +#define ST_RATECONTROL "NVENC.RateControl" +#define ST_RATECONTROL_MODE ST_RATECONTROL ".Mode" +#define ST_RATECONTROL_MODE_(x) ST_RATECONTROL_MODE "." D_VSTR(x) +#define ST_RATECONTROL_TWOPASS ST_RATECONTROL ".TwoPass" +#define ST_RATECONTROL_LOOKAHEAD ST_RATECONTROL ".LookAhead" +#define ST_RATECONTROL_ADAPTIVEI ST_RATECONTROL ".AdaptiveI" +#define ST_RATECONTROL_ADAPTIVEB ST_RATECONTROL ".AdaptiveB" + +#define ST_RATECONTROL_BITRATE ST_RATECONTROL ".Bitrate" +#define ST_RATECONTROL_BITRATE_TARGET ST_RATECONTROL_BITRATE ".Target" +#define ST_RATECONTROL_BITRATE_MAXIMUM ST_RATECONTROL_BITRATE ".Maximum" + +#define ST_RATECONTROL_QUALITY ST_RATECONTROL ".Quality" +#define ST_RATECONTROL_QUALITY_MINIMUM ST_RATECONTROL_QUALITY ".Minimum" +#define ST_RATECONTROL_QUALITY_MAXIMUM ST_RATECONTROL_QUALITY ".Maximum" +#define ST_RATECONTROL_QUALITY_TARGET ST_RATECONTROL_QUALITY ".Target" + +#define ST_RATECONTROL_QP ST_RATECONTROL ".QP" +#define ST_RATECONTROL_QP_I ST_RATECONTROL_QP ".I" +#define ST_RATECONTROL_QP_I_INITIAL ST_RATECONTROL_QP_I ".Initial" +#define ST_RATECONTROL_QP_P ST_RATECONTROL_QP ".P" +#define ST_RATECONTROL_QP_P_INITIAL ST_RATECONTROL_QP_P ".Initial" +#define ST_RATECONTROL_QP_B ST_RATECONTROL_QP ".B" +#define ST_RATECONTROL_QP_B_INITIAL ST_RATECONTROL_QP_B ".Initial" + +#define ST_AQ "NVENC.AQ" +#define ST_AQ_SPATIAL ST_AQ ".Spatial" +#define ST_AQ_TEMPORAL ST_AQ ".Temporal" +#define ST_AQ_STRENGTH ST_AQ ".Strength" + +#define ST_OTHER "NVENC.Other" +#define ST_OTHER_BFRAMES ST_OTHER ".BFrames" +#define ST_OTHER_BFRAME_REFERENCEMODE ST_OTHER ".BFrameReferenceMode" +#define ST_OTHER_ZEROLATENCY ST_OTHER ".ZeroLatency" +#define ST_OTHER_WEIGHTED_PREDICTION ST_OTHER ".WeightedPrediction" +#define ST_OTHER_NONREFERENCE_PFRAMES ST_OTHER ".NonReferencePFrames" + +using namespace obsffmpeg::nvenc; + +std::map obsffmpeg::nvenc::presets{ + {preset::DEFAULT, ST_PRESET_(Default)}, + {preset::SLOW, ST_PRESET_(Slow)}, + {preset::MEDIUM, ST_PRESET_(Medium)}, + {preset::FAST, ST_PRESET_(Fast)}, + {preset::HIGH_PERFORMANCE, ST_PRESET_(HighPerformance)}, + {preset::HIGH_QUALITY, ST_PRESET_(HighQuality)}, + {preset::BLURAYDISC, ST_PRESET_(BluRayDisc)}, + {preset::LOW_LATENCY, ST_PRESET_(LowLatency)}, + {preset::LOW_LATENCY_HIGH_PERFORMANCE, ST_PRESET_(LowLatencyHighPerformance)}, + {preset::LOW_LATENCY_HIGH_QUALITY, ST_PRESET_(LowLatencyHighQuality)}, + {preset::LOSSLESS, ST_PRESET_(Lossless)}, + {preset::LOSSLESS_HIGH_PERFORMANCE, ST_PRESET_(LosslessHighPerformance)}, +}; + +std::map obsffmpeg::nvenc::preset_to_opt{ + {preset::DEFAULT, "default"}, + {preset::SLOW, "slow"}, + {preset::MEDIUM, "medium"}, + {preset::FAST, "fast"}, + {preset::HIGH_PERFORMANCE, "hp"}, + {preset::HIGH_QUALITY, "hq"}, + {preset::BLURAYDISC, "bd"}, + {preset::LOW_LATENCY, "ll"}, + {preset::LOW_LATENCY_HIGH_PERFORMANCE, "llhp"}, + {preset::LOW_LATENCY_HIGH_QUALITY, "llhq"}, + {preset::LOSSLESS, "lossless"}, + {preset::LOSSLESS_HIGH_PERFORMANCE, "losslesshp"}, +}; + +std::map obsffmpeg::nvenc::ratecontrolmodes{ + {ratecontrolmode::CQP, ST_RATECONTROL_MODE_(CQP)}, + {ratecontrolmode::VBR, ST_RATECONTROL_MODE_(VBR)}, + {ratecontrolmode::VBR_HQ, ST_RATECONTROL_MODE_(VBR_HQ)}, + {ratecontrolmode::CBR, ST_RATECONTROL_MODE_(CBR)}, + {ratecontrolmode::CBR_HQ, ST_RATECONTROL_MODE_(CBR_HQ)}, + {ratecontrolmode::CBR_LD_HQ, ST_RATECONTROL_MODE_(CBR_LD_HQ)}, +}; + +std::map obsffmpeg::nvenc::ratecontrolmode_to_opt{ + {ratecontrolmode::CQP, "constqp"}, {ratecontrolmode::VBR, "vbr"}, {ratecontrolmode::VBR_HQ, "vbr_hq"}, + {ratecontrolmode::CBR, "cbr"}, {ratecontrolmode::CBR_HQ, "cbr_hq"}, {ratecontrolmode::CBR_LD_HQ, "cbr_ld_hq"}, +}; + +std::map obsffmpeg::nvenc::b_ref_modes{ + {b_ref_mode::DISABLED, S_STATE_DISABLED}, + {b_ref_mode::EACH, ST_OTHER_BFRAME_REFERENCEMODE ".Each"}, + {b_ref_mode::MIDDLE, ST_OTHER_BFRAME_REFERENCEMODE ".Middle"}, +}; + +std::map obsffmpeg::nvenc::b_ref_mode_to_opt{ + {b_ref_mode::DISABLED, "disabled"}, + {b_ref_mode::EACH, "each"}, + {b_ref_mode::MIDDLE, "middle"}, +}; + +void obsffmpeg::nvenc::override_update(obsffmpeg::encoder* instance, obs_data_t*) +{ + AVCodecContext* context = const_cast(instance->get_avcodeccontext()); + + int64_t rclookahead = 0; + int64_t surfaces = 0; + int64_t async_depth = 0; + + av_opt_get_int(context, "rc-lookahead", AV_OPT_SEARCH_CHILDREN, &rclookahead); + av_opt_get_int(context, "surfaces", AV_OPT_SEARCH_CHILDREN, &surfaces); + av_opt_get_int(context, "async_depth", AV_OPT_SEARCH_CHILDREN, &async_depth); + + // Calculate and set the number of surfaces to allocate (if not user overridden). + if (surfaces == 0) { + surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); + if (rclookahead > 0) { + surfaces = std::max(1ll, std::max(surfaces, rclookahead + (context->max_b_frames + 5ll))); + } else if (context->max_b_frames > 0) { + surfaces = std::max(4ll, (context->max_b_frames + 1ll) * 4ll); + } else { + surfaces = 4; + } + + av_opt_set_int(context, "surfaces", surfaces, AV_OPT_SEARCH_CHILDREN); + } + + // Set delay + context->delay = static_cast(std::min(std::max(async_depth, 3ll), surfaces - 1)); +} + +void obsffmpeg::nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*) +{ + obs_data_set_default_int(settings, ST_PRESET, static_cast(preset::DEFAULT)); + + obs_data_set_default_int(settings, ST_RATECONTROL_MODE, static_cast(ratecontrolmode::CBR_HQ)); + obs_data_set_default_int(settings, ST_RATECONTROL_TWOPASS, -1); + obs_data_set_default_int(settings, ST_RATECONTROL_LOOKAHEAD, 0); + obs_data_set_default_int(settings, ST_RATECONTROL_ADAPTIVEI, -1); + obs_data_set_default_int(settings, ST_RATECONTROL_ADAPTIVEB, -1); + + obs_data_set_default_int(settings, ST_RATECONTROL_BITRATE_TARGET, 6000); + obs_data_set_default_int(settings, ST_RATECONTROL_BITRATE_MAXIMUM, 6000); + obs_data_set_default_int(settings, S_RATECONTROL_BUFFERSIZE, 12000); + + obs_data_set_default_int(settings, ST_RATECONTROL_QUALITY_MINIMUM, 51); + obs_data_set_default_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM, -1); + obs_data_set_default_int(settings, ST_RATECONTROL_QUALITY_TARGET, 0); + + obs_data_set_default_int(settings, ST_RATECONTROL_QP_I, 21); + obs_data_set_default_int(settings, ST_RATECONTROL_QP_I_INITIAL, -1); + obs_data_set_default_int(settings, ST_RATECONTROL_QP_P, 21); + obs_data_set_default_int(settings, ST_RATECONTROL_QP_P_INITIAL, -1); + obs_data_set_default_int(settings, ST_RATECONTROL_QP_B, 21); + obs_data_set_default_int(settings, ST_RATECONTROL_QP_B_INITIAL, -1); + + obs_data_set_default_int(settings, ST_AQ_SPATIAL, -1); + obs_data_set_default_int(settings, ST_AQ_STRENGTH, 8); + obs_data_set_default_int(settings, ST_AQ_TEMPORAL, -1); + + obs_data_set_default_int(settings, ST_OTHER_BFRAMES, 2); + obs_data_set_default_int(settings, ST_OTHER_BFRAME_REFERENCEMODE, static_cast(b_ref_mode::DISABLED)); + obs_data_set_default_int(settings, ST_OTHER_ZEROLATENCY, -1); + obs_data_set_default_int(settings, ST_OTHER_WEIGHTED_PREDICTION, -1); + obs_data_set_default_int(settings, ST_OTHER_NONREFERENCE_PFRAMES, -1); + + // Replay Buffer + obs_data_set_default_int(settings, "bitrate", 0); +} + +static bool modified_ratecontrol(obs_properties_t* props, obs_property_t*, obs_data_t* settings) +{ + using namespace obsffmpeg::nvenc; + + bool have_bitrate = false; + bool have_bitrate_max = false; + bool have_quality = false; + bool have_qp = false; + bool have_qp_init = false; + + ratecontrolmode rc = static_cast(obs_data_get_int(settings, ST_RATECONTROL_MODE)); + switch (rc) { + case ratecontrolmode::CQP: + have_qp = true; + break; + case ratecontrolmode::CBR: + case ratecontrolmode::CBR_HQ: + case ratecontrolmode::CBR_LD_HQ: + have_bitrate = true; + break; + case ratecontrolmode::VBR: + case ratecontrolmode::VBR_HQ: + have_bitrate = true; + have_bitrate_max = true; + have_quality = true; + have_qp_init = true; + break; + } + + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_BITRATE), have_bitrate || have_bitrate_max); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_BITRATE_TARGET), have_bitrate); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_BITRATE_MAXIMUM), have_bitrate_max); + obs_property_set_visible(obs_properties_get(props, S_RATECONTROL_BUFFERSIZE), have_bitrate || have_bitrate_max); + + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QUALITY), have_quality); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QUALITY_MINIMUM), have_quality); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QUALITY_MAXIMUM), have_quality); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QUALITY_TARGET), have_quality); + + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP), have_qp || have_qp_init); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_I), have_qp); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_P), have_qp); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_B), have_qp); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_I_INITIAL), have_qp_init); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_P_INITIAL), have_qp_init); + obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), have_qp_init); + + return true; +} + +static bool modified_quality(obs_properties_t* props, obs_property_t*, obs_data_t* settings) +{ + bool enabled = obs_data_get_bool(settings, ST_RATECONTROL_QUALITY); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY_MINIMUM), enabled); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY_MAXIMUM), enabled); + return true; +} + +static bool modified_aq(obs_properties_t* props, obs_property_t*, obs_data_t* settings) +{ + bool spatial_aq = obs_data_get_int(settings, ST_AQ_SPATIAL) == 1; + obs_property_set_visible(obs_properties_get(props, ST_AQ_STRENGTH), spatial_aq); + return true; +} + +void obsffmpeg::nvenc::get_properties_pre(obs_properties_t* props, const AVCodec*) +{ + { + auto p = obs_properties_add_list(props, ST_PRESET, TRANSLATE(ST_PRESET), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_PRESET))); + for (auto kv : presets) { + obs_property_list_add_int(p, TRANSLATE(kv.second.c_str()), static_cast(kv.first)); + } + } +} + +void obsffmpeg::nvenc::get_properties_post(obs_properties_t* props, const AVCodec* codec) +{ + { // Rate Control + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, ST_RATECONTROL, TRANSLATE(ST_RATECONTROL), OBS_GROUP_NORMAL, + grp); + } + + { + auto p = obs_properties_add_list(grp, ST_RATECONTROL_MODE, TRANSLATE(ST_RATECONTROL_MODE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_MODE))); + obs_property_set_modified_callback(p, modified_ratecontrol); + for (auto kv : ratecontrolmodes) { + obs_property_list_add_int(p, TRANSLATE(kv.second.c_str()), + static_cast(kv.first)); + } + } + + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_RATECONTROL_TWOPASS, + TRANSLATE(ST_RATECONTROL_TWOPASS)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_TWOPASS))); + } + + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_LOOKAHEAD, + TRANSLATE(ST_RATECONTROL_LOOKAHEAD), 0, 32, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_LOOKAHEAD))); + obs_property_int_set_suffix(p, " frames"); + } + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_RATECONTROL_ADAPTIVEI, + TRANSLATE(ST_RATECONTROL_ADAPTIVEI)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_ADAPTIVEI))); + } + if (strcmp(codec->name, "h264_nvenc") == 0) { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_RATECONTROL_ADAPTIVEB, + TRANSLATE(ST_RATECONTROL_ADAPTIVEB)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_ADAPTIVEB))); + } + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, ST_RATECONTROL_BITRATE, TRANSLATE(ST_RATECONTROL_BITRATE), + OBS_GROUP_NORMAL, grp); + } + + { + auto p = obs_properties_add_int(grp, ST_RATECONTROL_BITRATE_TARGET, + TRANSLATE(ST_RATECONTROL_BITRATE_TARGET), 1, + std::numeric_limits::max(), 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_BITRATE_TARGET))); + obs_property_int_set_suffix(p, " kbit/s"); + } + { + auto p = obs_properties_add_int(grp, ST_RATECONTROL_BITRATE_MAXIMUM, + TRANSLATE(ST_RATECONTROL_BITRATE_MAXIMUM), 0, + std::numeric_limits::max(), 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_BITRATE_MAXIMUM))); + obs_property_int_set_suffix(p, " kbit/s"); + } + { + auto p = + obs_properties_add_int(grp, S_RATECONTROL_BUFFERSIZE, TRANSLATE(S_RATECONTROL_BUFFERSIZE), + 0, std::numeric_limits::max(), 1); + obs_property_set_long_description(p, TRANSLATE(DESC(S_RATECONTROL_BUFFERSIZE))); + obs_property_int_set_suffix(p, " kbit"); + } + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + auto p = obs_properties_add_group(props, ST_RATECONTROL_QUALITY, + TRANSLATE(ST_RATECONTROL_QUALITY), OBS_GROUP_CHECKABLE, grp); + obs_property_set_modified_callback(p, modified_quality); + } else { + auto p = + obs_properties_add_bool(props, ST_RATECONTROL_QUALITY, TRANSLATE(ST_RATECONTROL_QUALITY)); + obs_property_set_modified_callback(p, modified_quality); + } + + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QUALITY_MINIMUM, + TRANSLATE(ST_RATECONTROL_QUALITY_MINIMUM), 0, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QUALITY_MINIMUM))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QUALITY_MAXIMUM, + TRANSLATE(ST_RATECONTROL_QUALITY_MAXIMUM), -1, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QUALITY_MAXIMUM))); + } + } + + { + auto p = obs_properties_add_float_slider(props, ST_RATECONTROL_QUALITY_TARGET, + TRANSLATE(ST_RATECONTROL_QUALITY_TARGET), 0, 100, 0.01); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QUALITY_TARGET))); + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + auto p = obs_properties_add_group(props, ST_RATECONTROL_QP, TRANSLATE(ST_RATECONTROL_QP), + OBS_GROUP_CHECKABLE, grp); + obs_property_set_modified_callback(p, modified_quality); + } + + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_I, TRANSLATE(ST_RATECONTROL_QP_I), + 0, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_I))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_I_INITIAL, + TRANSLATE(ST_RATECONTROL_QP_I_INITIAL), -1, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_I_INITIAL))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_P, TRANSLATE(ST_RATECONTROL_QP_P), + 0, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_P_INITIAL, + TRANSLATE(ST_RATECONTROL_QP_P_INITIAL), -1, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P_INITIAL))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_B, TRANSLATE(ST_RATECONTROL_QP_B), + 0, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_B))); + } + { + auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_B_INITIAL, + TRANSLATE(ST_RATECONTROL_QP_B_INITIAL), -1, 51, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_B_INITIAL))); + } + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, ST_AQ, TRANSLATE(ST_AQ), OBS_GROUP_NORMAL, grp); + } + + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_AQ_SPATIAL, TRANSLATE(ST_AQ_SPATIAL)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_AQ_SPATIAL))); + obs_property_set_modified_callback(p, modified_aq); + } + { + auto p = + obs_properties_add_int_slider(grp, ST_AQ_STRENGTH, TRANSLATE(ST_AQ_STRENGTH), 1, 15, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_AQ_STRENGTH))); + } + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_AQ_TEMPORAL, TRANSLATE(ST_AQ_TEMPORAL)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_AQ_TEMPORAL))); + } + } + + { + obs_properties_t* grp = props; + if (!obsffmpeg::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(props, ST_OTHER, TRANSLATE(ST_OTHER), OBS_GROUP_NORMAL, grp); + } + + { + auto p = + obs_properties_add_int_slider(grp, ST_OTHER_BFRAMES, TRANSLATE(ST_OTHER_BFRAMES), 0, 4, 1); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_OTHER_BFRAMES))); + obs_property_int_set_suffix(p, " frames"); + } + + { + auto p = obs_properties_add_list(grp, ST_OTHER_BFRAME_REFERENCEMODE, + TRANSLATE(ST_OTHER_BFRAME_REFERENCEMODE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_OTHER_BFRAME_REFERENCEMODE))); + for (auto kv : b_ref_modes) { + obs_property_list_add_int(p, TRANSLATE(kv.second.c_str()), + static_cast(kv.first)); + } + } + + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_OTHER_ZEROLATENCY, + TRANSLATE(ST_OTHER_ZEROLATENCY)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_OTHER_ZEROLATENCY))); + } + + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_OTHER_WEIGHTED_PREDICTION, + TRANSLATE(ST_OTHER_WEIGHTED_PREDICTION)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_OTHER_WEIGHTED_PREDICTION))); + } + + { + auto p = obsffmpeg::obs_properties_add_tristate(grp, ST_OTHER_NONREFERENCE_PFRAMES, + TRANSLATE(ST_OTHER_NONREFERENCE_PFRAMES)); + obs_property_set_long_description(p, TRANSLATE(DESC(ST_OTHER_NONREFERENCE_PFRAMES))); + } + } +} + +void obsffmpeg::nvenc::get_runtime_properties(obs_properties_t* props, const AVCodec*, AVCodecContext*) +{ + obs_property_set_enabled(obs_properties_get(props, ST_PRESET), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_MODE), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_TWOPASS), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_LOOKAHEAD), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_ADAPTIVEI), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_ADAPTIVEB), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_BITRATE), true); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_BITRATE_TARGET), true); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_BITRATE_MAXIMUM), true); + obs_property_set_enabled(obs_properties_get(props, S_RATECONTROL_BUFFERSIZE), true); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY_MINIMUM), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY_MAXIMUM), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QUALITY_TARGET), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_I), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_I_INITIAL), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_P), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_P_INITIAL), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_B), false); + obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), false); + obs_property_set_enabled(obs_properties_get(props, ST_AQ), false); + obs_property_set_enabled(obs_properties_get(props, ST_AQ_SPATIAL), false); + obs_property_set_enabled(obs_properties_get(props, ST_AQ_STRENGTH), false); + obs_property_set_enabled(obs_properties_get(props, ST_AQ_TEMPORAL), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER_BFRAMES), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER_BFRAME_REFERENCEMODE), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER_ZEROLATENCY), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER_WEIGHTED_PREDICTION), false); + obs_property_set_enabled(obs_properties_get(props, ST_OTHER_NONREFERENCE_PFRAMES), false); +} + +void obsffmpeg::nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + { + preset c_preset = static_cast(obs_data_get_int(settings, ST_PRESET)); + auto found = preset_to_opt.find(c_preset); + if (found != preset_to_opt.end()) { + av_opt_set(context->priv_data, "preset", found->second.c_str(), 0); + } else { + av_opt_set(context->priv_data, "preset", nullptr, 0); + } + } + + { // Rate Control + bool have_bitrate = false; + bool have_bitrate_max = false; + bool have_quality = false; + bool have_qp = false; + bool have_qp_init = false; + + ratecontrolmode rc = static_cast(obs_data_get_int(settings, ST_RATECONTROL_MODE)); + auto rcopt = nvenc::ratecontrolmode_to_opt.find(rc); + if (rcopt != nvenc::ratecontrolmode_to_opt.end()) { + av_opt_set(context->priv_data, "rc", rcopt->second.c_str(), 0); + } + + av_opt_set_int(context->priv_data, "cbr", 0, 0); + switch (rc) { + case ratecontrolmode::CQP: + have_qp = true; + break; + case ratecontrolmode::CBR: + case ratecontrolmode::CBR_HQ: + case ratecontrolmode::CBR_LD_HQ: + have_bitrate = true; + av_opt_set_int(context->priv_data, "cbr", 1, 0); + break; + case ratecontrolmode::VBR: + case ratecontrolmode::VBR_HQ: + have_bitrate_max = true; + have_bitrate = true; + have_quality = true; + have_qp_init = true; + break; + } + + int tp = static_cast(obs_data_get_int(settings, ST_RATECONTROL_TWOPASS)); + if (tp >= 0) { + av_opt_set_int(context->priv_data, "2pass", tp ? 1 : 0, 0); + } + + int la = static_cast(obs_data_get_int(settings, ST_RATECONTROL_LOOKAHEAD)); + av_opt_set_int(context->priv_data, "rc-lookahead", la, 0); + if (la > 0) { + int64_t adapt_i = obs_data_get_int(settings, ST_RATECONTROL_ADAPTIVEI); + if (!is_tristate_default(adapt_i)) { + av_opt_set_int(context->priv_data, "no-scenecut", adapt_i, AV_OPT_SEARCH_CHILDREN); + } + + if (strcmp(codec->name, "h264_nvenc")) { + int64_t adapt_b = obs_data_get_int(settings, ST_RATECONTROL_ADAPTIVEB); + if (!is_tristate_default(adapt_b)) { + av_opt_set_int(context->priv_data, "b_adapt", adapt_b, AV_OPT_SEARCH_CHILDREN); + } + } + } + + if (have_bitrate) { + context->bit_rate = + static_cast(obs_data_get_int(settings, ST_RATECONTROL_BITRATE_TARGET) * 1000); + // Support for Replay Buffer + obs_data_set_int(settings, "bitrate", + obs_data_get_int(settings, ST_RATECONTROL_BITRATE_TARGET)); + } + if (have_bitrate_max) + context->rc_max_rate = + static_cast(obs_data_get_int(settings, ST_RATECONTROL_BITRATE_MAXIMUM) * 1000); + if (have_bitrate || have_bitrate_max) + context->rc_buffer_size = + static_cast(obs_data_get_int(settings, S_RATECONTROL_BUFFERSIZE) * 1000); + + if (have_quality && obs_data_get_bool(settings, ST_RATECONTROL_QUALITY)) { + int qmin = static_cast(obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MINIMUM)); + context->qmin = qmin; + if (qmin >= 0) { + context->qmax = + static_cast(obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM)); + } + } + + { + double_t v = obs_data_get_double(settings, ST_RATECONTROL_QUALITY_TARGET) / 100.0 * 51.0; + if (v > 0) { + av_opt_set_double(context->priv_data, "cq", v, 0); + } + } + + if (have_qp) { + av_opt_set_int(context->priv_data, "init_qpI", + static_cast(obs_data_get_int(settings, ST_RATECONTROL_QP_I)), 0); + av_opt_set_int(context->priv_data, "init_qpP", + static_cast(obs_data_get_int(settings, ST_RATECONTROL_QP_P)), 0); + av_opt_set_int(context->priv_data, "init_qpB", + static_cast(obs_data_get_int(settings, ST_RATECONTROL_QP_B)), 0); + } + if (have_qp_init) { + av_opt_set_int(context->priv_data, "init_qpI", + obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL), 0); + av_opt_set_int(context->priv_data, "init_qpP", + obs_data_get_int(settings, ST_RATECONTROL_QP_P_INITIAL), 0); + av_opt_set_int(context->priv_data, "init_qpB", + obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL), 0); + } + } + + { // AQ + int64_t saq = obs_data_get_int(settings, ST_AQ_SPATIAL); + int64_t taq = obs_data_get_int(settings, ST_AQ_TEMPORAL); + + if (strcmp(codec->name, "h264_nvenc") == 0) { + if (!is_tristate_default(saq)) + av_opt_set_int(context->priv_data, "spatial-aq", saq, 0); + if (!is_tristate_default(taq)) + av_opt_set_int(context->priv_data, "temporal-aq", taq, 0); + } else { + if (!is_tristate_default(saq)) + av_opt_set_int(context->priv_data, "spatial_aq", saq, 0); + if (!is_tristate_default(taq)) + av_opt_set_int(context->priv_data, "temporal_aq", taq, 0); + } + if (is_tristate_enabled(saq)) + av_opt_set_int(context->priv_data, "aq-strength", + static_cast(obs_data_get_int(settings, ST_AQ_STRENGTH)), 0); + } + + { // Other + int64_t zl = obs_data_get_int(settings, ST_OTHER_ZEROLATENCY); + int64_t wp = obs_data_get_int(settings, ST_OTHER_WEIGHTED_PREDICTION); + int64_t nrp = obs_data_get_int(settings, ST_OTHER_NONREFERENCE_PFRAMES); + + context->max_b_frames = static_cast(obs_data_get_int(settings, ST_OTHER_BFRAMES)); + + if (!is_tristate_default(zl)) + av_opt_set_int(context->priv_data, "zerolatency", zl, 0); + if (!is_tristate_default(nrp)) + av_opt_set_int(context->priv_data, "nonref_p", nrp, 0); + + if ((context->max_b_frames != 0) && is_tristate_enabled(wp)) { + PLOG_WARNING("[%s] Weighted Prediction disabled because of B-Frames being used.", codec->name); + av_opt_set_int(context->priv_data, "weighted_pred", 0, 0); + } else if (!is_tristate_default(wp)) { + av_opt_set_int(context->priv_data, "weighted_pred", wp, 0); + } + + { + auto found = b_ref_mode_to_opt.find( + static_cast(obs_data_get_int(settings, ST_OTHER_BFRAME_REFERENCEMODE))); + if (found != b_ref_mode_to_opt.end()) { + av_opt_set(context->priv_data, "b_ref_mode", found->second.c_str(), 0); + } + } + } +} + +void obsffmpeg::nvenc::log_options(obs_data_t*, const AVCodec* codec, AVCodecContext* context) +{ + PLOG_INFO("[%s] Nvidia NVENC:", codec->name); + ffmpeg::tools::print_av_option_string(context, "preset", " Preset", [](int64_t v) { + preset val = static_cast(v); + std::string name = ""; + auto index = preset_to_opt.find(val); + if (index != preset_to_opt.end()) + name = index->second; + return name; + }); + ffmpeg::tools::print_av_option_string(context, "rc", " Rate Control", [](int64_t v) { + ratecontrolmode val = static_cast(v); + std::string name = ""; + auto index = ratecontrolmode_to_opt.find(val); + if (index != ratecontrolmode_to_opt.end()) + name = index->second; + return name; + }); + ffmpeg::tools::print_av_option_bool(context, "2pass", " Two Pass"); + ffmpeg::tools::print_av_option_int(context, "rc-lookahead", " Look-Ahead", "Frames"); + ffmpeg::tools::print_av_option_bool(context, "no-scenecut", " Adaptive I-Frames"); + if (strcmp(codec->name, "h264_nvenc") == 0) + ffmpeg::tools::print_av_option_bool(context, "b_adapt", " Adaptive B-Frames"); + + PLOG_INFO("[%s] Bitrate:", codec->name); + ffmpeg::tools::print_av_option_int(context, "bitrate", " Target", "bits/sec"); + ffmpeg::tools::print_av_option_int(context, "rc_max_rate", " Maximum", "bits/sec"); + ffmpeg::tools::print_av_option_int(context, "rc_buffer_size", " Buffer", "bits"); + PLOG_INFO("[%s] Quality:", codec->name); + ffmpeg::tools::print_av_option_int(context, "qmin", " Minimum", ""); + ffmpeg::tools::print_av_option_int(context, "cq", " Target", ""); + ffmpeg::tools::print_av_option_int(context, "qmax", " Maximum", ""); + PLOG_INFO("[%s] Quantization Parameters:", codec->name); + ffmpeg::tools::print_av_option_int(context, "init_qpI", " I-Frame", ""); + ffmpeg::tools::print_av_option_int(context, "init_qpP", " P-Frame", ""); + ffmpeg::tools::print_av_option_int(context, "init_qpB", " B-Frame", ""); + + ffmpeg::tools::print_av_option_int(context, "max_b_frames", " B-Frames", "Frames"); + ffmpeg::tools::print_av_option_string(context, "b_ref_mode", " Reference Mode", [](int64_t v) { + b_ref_mode val = static_cast(v); + std::string name = ""; + auto index = b_ref_mode_to_opt.find(val); + if (index != b_ref_mode_to_opt.end()) + name = index->second; + return name; + }); + + PLOG_INFO("[%s] Adaptive Quantization:", codec->name); + if (strcmp(codec->name, "h264_nvenc") == 0) { + ffmpeg::tools::print_av_option_bool(context, "spatial-aq", " Spatial AQ"); + ffmpeg::tools::print_av_option_int(context, "aq-strength", " Strength", ""); + ffmpeg::tools::print_av_option_bool(context, "temporal-aq", " Temporal AQ"); + } else { + ffmpeg::tools::print_av_option_bool(context, "spatial_aq", " Spatial AQ"); + ffmpeg::tools::print_av_option_int(context, "aq-strength", " Strength", ""); + ffmpeg::tools::print_av_option_bool(context, "temporal_aq", " Temporal AQ"); + } + + PLOG_INFO("[%s] Other:", codec->name); + ffmpeg::tools::print_av_option_bool(context, "zerolatency", " Zero Latency"); + ffmpeg::tools::print_av_option_bool(context, "weighted_pred", " Weighted Prediction"); + ffmpeg::tools::print_av_option_bool(context, "nonref_p", " Non-reference P-Frames"); + ffmpeg::tools::print_av_option_bool(context, "strict_gop", " Strict GOP"); + ffmpeg::tools::print_av_option_bool(context, "aud", " Access Unit Delimiters"); + ffmpeg::tools::print_av_option_bool(context, "bluray-compat", " Bluray Compatibility"); + if (strcmp(codec->name, "h264_nvenc") == 0) + ffmpeg::tools::print_av_option_bool(context, "a53cc", " A53 Closed Captions"); + ffmpeg::tools::print_av_option_int(context, "dpb_size", " DPB Size", ""); +} diff --git a/source/encoders/handlers/nvenc_shared.hpp b/source/encoders/handlers/nvenc_shared.hpp new file mode 100644 index 00000000..00f28d66 --- /dev/null +++ b/source/encoders/handlers/nvenc_shared.hpp @@ -0,0 +1,100 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include +#include "utility.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + class encoder; + + namespace nvenc { + enum class preset : int64_t { + DEFAULT, + SLOW, + MEDIUM, + FAST, + HIGH_PERFORMANCE, + HIGH_QUALITY, + BLURAYDISC, + LOW_LATENCY, + LOW_LATENCY_HIGH_PERFORMANCE, + LOW_LATENCY_HIGH_QUALITY, + LOSSLESS, + LOSSLESS_HIGH_PERFORMANCE, + // Append things before this. + INVALID = -1, + }; + + enum class ratecontrolmode : int64_t { + CQP, + VBR, + VBR_HQ, + CBR, + CBR_HQ, + CBR_LD_HQ, + // Append things before this. + INVALID = -1, + }; + + enum class b_ref_mode : int64_t { + DISABLED, + EACH, + MIDDLE, + // Append things before this. + INVALID = -1, + }; + + extern std::map presets; + + extern std::map preset_to_opt; + + extern std::map ratecontrolmodes; + + extern std::map ratecontrolmode_to_opt; + + extern std::map b_ref_modes; + + extern std::map b_ref_mode_to_opt; + + void override_update(obsffmpeg::encoder* instance, obs_data_t* settings); + + void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + void get_properties_pre(obs_properties_t* props, const AVCodec* codec); + + void get_properties_post(obs_properties_t* props, const AVCodec* codec); + + void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context); + + void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + + void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); + } // namespace nvenc +} // namespace obsffmpeg diff --git a/source/encoders/handlers/prores_aw_handler.cpp b/source/encoders/handlers/prores_aw_handler.cpp new file mode 100644 index 00000000..c867aae1 --- /dev/null +++ b/source/encoders/handlers/prores_aw_handler.cpp @@ -0,0 +1,135 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "prores_aw_handler.hpp" +#include "codecs/prores.hpp" +#include "ffmpeg/tools.hpp" +#include "plugin.hpp" +#include "utility.hpp" + +extern "C" { +#include +} + +INITIALIZER(prores_aw_handler_init) +{ + obsffmpeg::initializers.push_back([]() { + obsffmpeg::register_codec_handler("prores_aw", std::make_shared()); + }); +}; + +void obsffmpeg::ui::prores_aw_handler::override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, + const AVCodec* codec, AVCodecContext*) +{ + std::string profile = ""; + + int profile_id = static_cast(obs_data_get_int(settings, P_PRORES_PROFILE)); + for (auto ptr = codec->profiles; ptr->profile != FF_PROFILE_UNKNOWN; ptr++) { + if (ptr->profile == profile_id) { + profile = ptr->name; + break; + } + } + + std::unordered_map> valid_formats = { + {AV_PIX_FMT_YUV422P10, {"apco", "apcs", "apcn", "apch"}}, {AV_PIX_FMT_YUV444P10, {"ap4h", "ap4x"}}}; + + for (auto kv : valid_formats) { + for (auto name : kv.second) { + if (profile == name) { + target_format = kv.first; + } + } + } +} + +void obsffmpeg::ui::prores_aw_handler::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*, bool) +{ + obs_data_set_default_int(settings, P_PRORES_PROFILE, 0); +} + +inline const char* profile_to_name(const AVProfile* ptr) +{ + switch (ptr->profile) { + case 0: + return TRANSLATE(P_PRORES_PROFILE_APCO); + case 1: + return TRANSLATE(P_PRORES_PROFILE_APCS); + case 2: + return TRANSLATE(P_PRORES_PROFILE_APCN); + case 3: + return TRANSLATE(P_PRORES_PROFILE_APCH); + case 4: + return TRANSLATE(P_PRORES_PROFILE_AP4H); + case 5: + return TRANSLATE(P_PRORES_PROFILE_AP4X); + default: + return ptr->name; + } +} + +void obsffmpeg::ui::prores_aw_handler::get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool) +{ + if (!context) { + auto p = obs_properties_add_list(props, P_PRORES_PROFILE, TRANSLATE(P_PRORES_PROFILE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, TRANSLATE(DESC(P_PRORES_PROFILE))); + for (auto ptr = codec->profiles; ptr->profile != FF_PROFILE_UNKNOWN; ptr++) { + obs_property_list_add_int(p, profile_to_name(ptr), static_cast(ptr->profile)); + } + } else { + obs_property_set_enabled(obs_properties_get(props, P_PRORES_PROFILE), false); + } +} + +void obsffmpeg::ui::prores_aw_handler::update(obs_data_t* settings, const AVCodec*, AVCodecContext* context) +{ + context->profile = static_cast(obs_data_get_int(settings, P_PRORES_PROFILE)); +} + +void obsffmpeg::ui::prores_aw_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) +{ + PLOG_INFO("[%s] Apple ProRes:", codec->name); + ffmpeg::tools::print_av_option_string(context, "profile", " Profile", [&codec](int64_t v) { + int val = static_cast(v); + for (auto ptr = codec->profiles; (ptr->profile != FF_PROFILE_UNKNOWN) && (ptr != nullptr); ptr++) { + if (ptr->profile == val) { + return std::string(profile_to_name(ptr)); + } + } + return std::string(""); + }); +} + +void obsffmpeg::ui::prores_aw_handler::process_avpacket(AVPacket& packet, const AVCodec*, AVCodecContext*) +{ + //FFmpeg Bug: + // When ProRes content is stored in Matroska, FFmpeg strips the size + // from the atom. Later when the ProRes content is demuxed from Matroska, + // FFmpeg creates an atom with the incorrect size, as the ATOM size + // should be content + atom, but FFmpeg set it to only be content. This + // difference leads to decoders to be off by 8 bytes. + //Fix (until FFmpeg stops being broken): + // Pad the packet with 8 bytes of 0x00. + + av_grow_packet(&packet, 8); +} diff --git a/source/encoders/handlers/prores_aw_handler.hpp b/source/encoders/handlers/prores_aw_handler.hpp new file mode 100644 index 00000000..18c2471f --- /dev/null +++ b/source/encoders/handlers/prores_aw_handler.hpp @@ -0,0 +1,56 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "handler.hpp" + +extern "C" { +#include +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + namespace ui { + class prores_aw_handler : public handler { + public: + virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, + const AVCodec* codec, AVCodecContext* context) override; + + virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, + bool hw_encode) override; + + virtual void get_properties(obs_properties_t* props, const AVCodec* codec, + AVCodecContext* context, bool hw_encode) override; + + virtual void update(obs_data_t* settings, const AVCodec* codec, + AVCodecContext* context) override; + + virtual void log_options(obs_data_t* settings, const AVCodec* codec, + AVCodecContext* context) override; + + virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, + AVCodecContext* context) override; + }; + } // namespace ui +} // namespace obsffmpeg diff --git a/source/ffmpeg/avframe-queue.cpp b/source/ffmpeg/avframe-queue.cpp new file mode 100644 index 00000000..bd86cf77 --- /dev/null +++ b/source/ffmpeg/avframe-queue.cpp @@ -0,0 +1,147 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "avframe-queue.hpp" +#include "tools.hpp" + +std::shared_ptr ffmpeg::avframe_queue::create_frame() +{ + std::shared_ptr frame = std::shared_ptr(av_frame_alloc(), [](AVFrame* frame) { + av_frame_unref(frame); + av_frame_free(&frame); + }); + frame->width = this->resolution.first; + frame->height = this->resolution.second; + frame->format = this->format; + + int res = av_frame_get_buffer(frame.get(), 32); + if (res < 0) { + throw std::exception(ffmpeg::tools::get_error_description(res)); + } + + return frame; +} + +ffmpeg::avframe_queue::avframe_queue() {} + +ffmpeg::avframe_queue::~avframe_queue() +{ + clear(); +} + +void ffmpeg::avframe_queue::set_resolution(uint32_t const width, uint32_t const 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 const 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); + frames.clear(); +} + +void ffmpeg::avframe_queue::push(std::shared_ptr const frame) +{ + std::unique_lock ulock(this->lock); + frames.push_back(frame); +} + +std::shared_ptr ffmpeg::avframe_queue::pop() +{ + std::unique_lock ulock(this->lock); + std::shared_ptr ret; + 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 ((static_cast(ret->width) != this->resolution.first) + || (static_cast(ret->height) != this->resolution.second) + || (ret->format != this->format)) { + ret = nullptr; + } + } + } + } + return ret; +} + +std::shared_ptr ffmpeg::avframe_queue::pop_only() +{ + std::unique_lock ulock(this->lock); + if (frames.size() == 0) { + return nullptr; + } + std::shared_ptr 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 00000000..521e5bf9 --- /dev/null +++ b/source/ffmpeg/avframe-queue.hpp @@ -0,0 +1,69 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include +#include + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#pragma warning(pop) +} + +namespace ffmpeg { + class avframe_queue { + std::deque> frames; + std::mutex lock; + + std::pair resolution; + AVPixelFormat format = AV_PIX_FMT_NONE; + + std::shared_ptr create_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(std::shared_ptr frame); + + std::shared_ptr pop(); + + std::shared_ptr pop_only(); + + bool empty(); + + size_t size(); + }; +} // namespace ffmpeg diff --git a/source/ffmpeg/hwapi/base.cpp b/source/ffmpeg/hwapi/base.cpp new file mode 100644 index 00000000..09ee348f --- /dev/null +++ b/source/ffmpeg/hwapi/base.cpp @@ -0,0 +1,22 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "base.hpp" diff --git a/source/ffmpeg/hwapi/base.hpp b/source/ffmpeg/hwapi/base.hpp new file mode 100644 index 00000000..878d499c --- /dev/null +++ b/source/ffmpeg/hwapi/base.hpp @@ -0,0 +1,70 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#pragma warning(pop) +} + +namespace obsffmpeg { + namespace hwapi { + struct device { + std::pair id; + std::string name; + }; + + class instance; + + class base { + public: + virtual std::list enumerate_adapters() = 0; + + virtual std::shared_ptr create(obsffmpeg::hwapi::device target) = 0; + + virtual std::shared_ptr create_from_obs() = 0; + }; + + class instance { + public: + virtual AVBufferRef* create_device_context() = 0; + + virtual std::shared_ptr allocate_frame(AVBufferRef* frames) = 0; + + virtual void copy_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key, + uint64_t* next_lock_key, std::shared_ptr frame) = 0; + + virtual std::shared_ptr avframe_from_obs(AVBufferRef* frames, uint32_t handle, + uint64_t lock_key, + uint64_t* next_lock_key) = 0; + }; + } // namespace hwapi +} // namespace obsffmpeg diff --git a/source/ffmpeg/hwapi/d3d11.cpp b/source/ffmpeg/hwapi/d3d11.cpp new file mode 100644 index 00000000..f4691e16 --- /dev/null +++ b/source/ffmpeg/hwapi/d3d11.cpp @@ -0,0 +1,245 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "d3d11.hpp" +#include +#include +#include "utility.hpp" + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#include +#pragma warning(pop) +} + +obsffmpeg::hwapi::d3d11::d3d11() : _dxgi_module(0), _d3d11_module(0) +{ + _dxgi_module = LoadLibraryW(L"dxgi.dll"); + if (!_dxgi_module) + throw std::runtime_error("Unable to load DXGI"); + + _d3d11_module = LoadLibraryW(L"d3d11.dll"); + if (!_d3d11_module) + throw std::runtime_error("Unable to load D3D11"); + + _CreateDXGIFactory = reinterpret_cast(GetProcAddress(_dxgi_module, "CreateDXGIFactory")); + _CreateDXGIFactory1 = + reinterpret_cast(GetProcAddress(_dxgi_module, "CreateDXGIFactory1")); + _D3D11CreateDevice = reinterpret_cast(GetProcAddress(_d3d11_module, "D3D11CreateDevice")); + + if (!_CreateDXGIFactory && !_CreateDXGIFactory1) + throw std::runtime_error("DXGI not supported"); + + if (!_D3D11CreateDevice) + throw std::runtime_error("D3D11 not supported"); + + HRESULT hr = _CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&_dxgifactory); + if (FAILED(hr)) { + std::stringstream sstr; + sstr << "Failed to create DXGI Factory (" << hr << ")"; + throw std::runtime_error(sstr.str()); + } +} + +obsffmpeg::hwapi::d3d11::~d3d11() +{ + FreeLibrary(_dxgi_module); + FreeLibrary(_d3d11_module); +} + +std::list obsffmpeg::hwapi::d3d11::enumerate_adapters() +{ + std::list adapters; + + // Enumerate Adapters + IDXGIAdapter1* dxgi_adapter = nullptr; + for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) { + DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1(); + dxgi_adapter->GetDesc1(&desc); + + std::vector buf(1024); + size_t len = snprintf(buf.data(), buf.size(), "%ls (VEN_%04x/DEV_%04x/SUB_%04x/REV_%04x)", + desc.Description, desc.VendorId, desc.DeviceId, desc.SubSysId, desc.Revision); + + device dev; + dev.name = std::string(buf.data(), buf.data() + len); + dev.id.first = desc.AdapterLuid.HighPart; + dev.id.second = desc.AdapterLuid.LowPart; + + adapters.push_back(dev); + } + + return std::move(adapters); +} + +std::shared_ptr obsffmpeg::hwapi::d3d11::create(obsffmpeg::hwapi::device target) +{ + std::shared_ptr inst; + ATL::CComPtr device; + ATL::CComPtr context; + IDXGIAdapter1* adapter = nullptr; + + // Find the correct "Adapter" (device). + IDXGIAdapter1* dxgi_adapter = nullptr; + for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) { + DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1(); + dxgi_adapter->GetDesc1(&desc); + + if ((desc.AdapterLuid.LowPart == target.id.second) && (desc.AdapterLuid.HighPart == target.id.first)) { + adapter = dxgi_adapter; + break; + } + } + + // Create a D3D11 Device + UINT device_flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + std::vector feature_levels = {D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1}; + + if (FAILED(_D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_HARDWARE, NULL, device_flags, feature_levels.data(), + static_cast(feature_levels.size()), D3D11_SDK_VERSION, &device, NULL, + &context))) { + throw std::runtime_error("Failed to create D3D11 device for target."); + } + + return std::make_shared(device, context); +} + +std::shared_ptr obsffmpeg::hwapi::d3d11::create_from_obs() +{ + auto gctx = obsffmpeg::obs_graphics(); + + if (GS_DEVICE_DIRECT3D_11 != gs_get_device_type()) { + throw std::runtime_error("OBS Device is not a D3D11 Device."); + } + + ATL::CComPtr device = + ATL::CComPtr(reinterpret_cast(gs_get_device_obj())); + ATL::CComPtr context; + device->GetImmediateContext(&context); + + return std::make_shared(device, context); +} + +struct D3D11AVFrame { + ATL::CComPtr handle; +}; + +obsffmpeg::hwapi::d3d11_instance::d3d11_instance(ATL::CComPtr device, + ATL::CComPtr context) +{ + _device = device; + _context = context; +} + +obsffmpeg::hwapi::d3d11_instance::~d3d11_instance() {} + +AVBufferRef* obsffmpeg::hwapi::d3d11_instance::create_device_context() +{ + AVBufferRef* dctx_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA); + if (!dctx_ref) + throw std::runtime_error("Failed to allocate AVHWDeviceContext."); + + AVHWDeviceContext* dctx = reinterpret_cast(dctx_ref->data); + AVD3D11VADeviceContext* d3d11va = reinterpret_cast(dctx->hwctx); + + // TODO: Determine if these need an additional reference. + d3d11va->device = _device; + d3d11va->device->AddRef(); + d3d11va->device_context = _context; + d3d11va->device_context->AddRef(); + d3d11va->lock = [](void*) { obs_enter_graphics(); }; + d3d11va->unlock = [](void*) { obs_leave_graphics(); }; + + int ret = av_hwdevice_ctx_init(dctx_ref); + if (ret < 0) + throw std::runtime_error("Failed to initialize AVHWDeviceContext."); + + return dctx_ref; +} + +std::shared_ptr obsffmpeg::hwapi::d3d11_instance::allocate_frame(AVBufferRef* frames) +{ + auto gctx = obsffmpeg::obs_graphics(); + + auto frame = std::shared_ptr(av_frame_alloc(), [](AVFrame* frame) { + av_frame_unref(frame); + av_frame_free(&frame); + }); + + if (av_hwframe_get_buffer(frames, frame.get(), 0) < 0) { + throw std::runtime_error("Failed to create AVFrame."); + } + + return frame; +} + +void obsffmpeg::hwapi::d3d11_instance::copy_from_obs(AVBufferRef*, uint32_t handle, uint64_t lock_key, + uint64_t* next_lock_key, std::shared_ptr frame) +{ + auto gctx = obsffmpeg::obs_graphics(); + + ATL::CComPtr mutex; + ATL::CComPtr input; + + if (FAILED(_device->OpenSharedResource(reinterpret_cast(static_cast(handle)), + __uuidof(ID3D11Texture2D), reinterpret_cast(&input)))) { + throw std::runtime_error("Failed to open shared texture resource."); + } + + if (FAILED(input->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast(&mutex)))) { + throw std::runtime_error("Failed to retrieve mutex for texture resource."); + } + + if (FAILED(mutex->AcquireSync(lock_key, 1000))) { + throw std::runtime_error("Failed to acquire lock on input texture."); + } + + // Set some parameters on the input texture, and get its description. + UINT evict = input->GetEvictionPriority(); + input->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM); + + // Clone the content of the input texture. + _context->CopyResource(reinterpret_cast(frame->data[0]), input); + + // Restore original parameters on input. + input->SetEvictionPriority(evict); + + if (FAILED(mutex->ReleaseSync(lock_key))) { + throw std::runtime_error("Failed to release lock on input texture."); + } + + // TODO: Determine if this is necessary. + mutex->ReleaseSync(*next_lock_key); +} + +std::shared_ptr obsffmpeg::hwapi::d3d11_instance::avframe_from_obs(AVBufferRef* frames, uint32_t handle, + uint64_t lock_key, uint64_t* next_lock_key) +{ + auto gctx = obsffmpeg::obs_graphics(); + + auto frame = this->allocate_frame(frames); + this->copy_from_obs(frames, handle, lock_key, next_lock_key, frame); + return frame; +} diff --git a/source/ffmpeg/hwapi/d3d11.hpp b/source/ffmpeg/hwapi/d3d11.hpp new file mode 100644 index 00000000..4050c5fc --- /dev/null +++ b/source/ffmpeg/hwapi/d3d11.hpp @@ -0,0 +1,80 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include +#include +#include +#include "base.hpp" + +namespace obsffmpeg { + namespace hwapi { + class d3d11 : public ::obsffmpeg::hwapi::base { + typedef HRESULT(__stdcall* CreateDXGIFactory_t)(REFIID, void**); + typedef HRESULT(__stdcall* CreateDXGIFactory1_t)(REFIID, void**); + typedef HRESULT(__stdcall* D3D11CreateDevice_t)(_In_opt_ IDXGIAdapter*, D3D_DRIVER_TYPE, + HMODULE, UINT, CONST D3D_FEATURE_LEVEL*, UINT, + UINT, _Out_opt_ ID3D11Device**, + _Out_opt_ D3D_FEATURE_LEVEL*, + _Out_opt_ ID3D11DeviceContext**); + + HMODULE _dxgi_module; + CreateDXGIFactory_t _CreateDXGIFactory; + CreateDXGIFactory1_t _CreateDXGIFactory1; + + HMODULE _d3d11_module; + D3D11CreateDevice_t _D3D11CreateDevice; + + ATL::CComPtr _dxgifactory; + + public: + d3d11(); + virtual ~d3d11(); + + virtual std::list enumerate_adapters() override; + + virtual std::shared_ptr + create(obsffmpeg::hwapi::device target) override; + + virtual std::shared_ptr create_from_obs() override; + }; + + class d3d11_instance : public ::obsffmpeg::hwapi::instance { + ATL::CComPtr _device; + ATL::CComPtr _context; + + public: + d3d11_instance(ATL::CComPtr device, ATL::CComPtr context); + virtual ~d3d11_instance(); + + virtual AVBufferRef* create_device_context() override; + + virtual std::shared_ptr allocate_frame(AVBufferRef* frames) override; + + virtual void copy_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key, + uint64_t* next_lock_key, std::shared_ptr frame) override; + + virtual std::shared_ptr avframe_from_obs(AVBufferRef* frames, uint32_t handle, + uint64_t lock_key, + uint64_t* next_lock_key) override; + }; + } // namespace hwapi +} // namespace obsffmpeg diff --git a/source/ffmpeg/swscale.cpp b/source/ffmpeg/swscale.cpp new file mode 100644 index 00000000..079d78ac --- /dev/null +++ b/source/ffmpeg/swscale.cpp @@ -0,0 +1,204 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#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) +{ + width = target_size.first; + height = target_size.second; +} + +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 00000000..a701c7fa --- /dev/null +++ b/source/ffmpeg/swscale.hpp @@ -0,0 +1,89 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef OBS_FFMPEG_FFMPEG_SWSCALE +#define OBS_FFMPEG_FFMPEG_SWSCALE +#pragma once + +#include +#include + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#pragma warning(pop) +} + +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 00000000..91f99663 --- /dev/null +++ b/source/ffmpeg/tools.cpp @@ -0,0 +1,353 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "tools.hpp" +#include +#include +#include +#include +#include "plugin.hpp" +#include "utility.hpp" + +extern "C" { +#pragma warning(push) +#pragma warning(disable : 4244) +#include +#include +#include +#include +#pragma warning(pop) +} + +std::string ffmpeg::tools::translate_encoder_capabilities(int capabilities) +{ + // Sorted by relative importance. + std::pair caps[] = { + {AV_CODEC_CAP_EXPERIMENTAL, "Experimental"}, + + // Quality + {AV_CODEC_CAP_LOSSLESS, "Lossless"}, + + // Features + {AV_CODEC_CAP_PARAM_CHANGE, "Dynamic Parameter Change"}, + {AV_CODEC_CAP_SUBFRAMES, "Sub-Frames"}, + {AV_CODEC_CAP_VARIABLE_FRAME_SIZE, "Variable Frame Size"}, + {AV_CODEC_CAP_SMALL_LAST_FRAME, "Small Final Frame"}, + + // Other + {AV_CODEC_CAP_TRUNCATED, "Truncated"}, + {AV_CODEC_CAP_CHANNEL_CONF, "AV_CODEC_CAP_CHANNEL_CONF"}, + {AV_CODEC_CAP_DRAW_HORIZ_BAND, "AV_CODEC_CAP_DRAW_HORIZ_BAND"}, + {AV_CODEC_CAP_AVOID_PROBING, "AV_CODEC_CAP_AVOID_PROBING"}, + }; + + std::stringstream sstr; + for (auto const kv : caps) { + if (capabilities & kv.first) { + capabilities &= ~kv.first; + sstr << kv.second; + if (capabilities != 0) { + sstr << ", "; + } else { + break; + } + } + } + + return sstr.str(); +} + +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) +{ + thread_local char error_buf[AV_ERROR_MAX_STRING_SIZE + 1]; + if (av_strerror(error, error_buf, AV_ERROR_MAX_STRING_SIZE) < 0) { + snprintf(error_buf, AV_ERROR_MAX_STRING_SIZE, "Unknown Error (%i)", error); + } + return error_buf; +} + +static std::map obs_to_av_format_map = { + {VIDEO_FORMAT_I420, AV_PIX_FMT_YUV420P}, // YUV 4:2:0 + {VIDEO_FORMAT_NV12, AV_PIX_FMT_NV12}, // NV12 Packed YUV + {VIDEO_FORMAT_YVYU, AV_PIX_FMT_YVYU422}, // YVYU Packed YUV + {VIDEO_FORMAT_YUY2, AV_PIX_FMT_YUYV422}, // YUYV Packed YUV + {VIDEO_FORMAT_UYVY, AV_PIX_FMT_UYVY422}, // UYVY Packed YUV + {VIDEO_FORMAT_RGBA, AV_PIX_FMT_RGBA}, // + {VIDEO_FORMAT_BGRA, AV_PIX_FMT_BGRA}, // + {VIDEO_FORMAT_BGRX, AV_PIX_FMT_BGR0}, // + {VIDEO_FORMAT_Y800, AV_PIX_FMT_GRAY8}, // + {VIDEO_FORMAT_I444, AV_PIX_FMT_YUV444P}, // + {VIDEO_FORMAT_BGR3, AV_PIX_FMT_BGR24}, // + {VIDEO_FORMAT_I422, AV_PIX_FMT_YUV422P}, // + {VIDEO_FORMAT_I40A, AV_PIX_FMT_YUVA420P}, // + {VIDEO_FORMAT_I42A, AV_PIX_FMT_YUVA422P}, // + {VIDEO_FORMAT_YUVA, AV_PIX_FMT_YUVA444P}, // + //{VIDEO_FORMAT_AYUV, AV_PIX_FMT_AYUV444P}, // +}; + +AVPixelFormat ffmpeg::tools::obs_videoformat_to_avpixelformat(video_format v) +{ + auto found = obs_to_av_format_map.find(v); + if (found != obs_to_av_format_map.end()) { + return found->second; + } + return AV_PIX_FMT_NONE; +} + +video_format ffmpeg::tools::avpixelformat_to_obs_videoformat(AVPixelFormat v) +{ + for (const auto& kv : obs_to_av_format_map) { + if (kv.second == v) + return kv.first; + } + return VIDEO_FORMAT_NONE; +} + +AVPixelFormat ffmpeg::tools::get_least_lossy_format(const AVPixelFormat* haystack, AVPixelFormat needle) +{ + int data_loss = 0; + return avcodec_find_best_pix_fmt_of_list(haystack, needle, 0, &data_loss); +} + +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_BT470BG; + } + 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_PARTIAL: + return AVCOL_RANGE_MPEG; + case VIDEO_RANGE_FULL: + return AVCOL_RANGE_JPEG; + } + throw std::invalid_argument("unknown range"); +} + +bool ffmpeg::tools::can_hardware_encode(const AVCodec* codec) +{ + AVPixelFormat hardware_formats[] = {AV_PIX_FMT_D3D11}; + + for (const AVPixelFormat* fmt = codec->pix_fmts; (fmt != nullptr) && (*fmt != AV_PIX_FMT_NONE); fmt++) { + for (auto cmp : hardware_formats) { + if (*fmt == cmp) { + return true; + } + } + } + return false; +} + +std::vector ffmpeg::tools::get_software_formats(const AVPixelFormat* list) +{ + AVPixelFormat hardware_formats[] = { +#if FF_API_VAAPI + AV_PIX_FMT_VAAPI_MOCO, + AV_PIX_FMT_VAAPI_IDCT, +#endif + AV_PIX_FMT_VAAPI, + AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VDPAU, + AV_PIX_FMT_QSV, + AV_PIX_FMT_MMAL, + AV_PIX_FMT_D3D11VA_VLD, + AV_PIX_FMT_CUDA, + AV_PIX_FMT_XVMC, + AV_PIX_FMT_VIDEOTOOLBOX, + AV_PIX_FMT_MEDIACODEC, + AV_PIX_FMT_D3D11, + AV_PIX_FMT_OPENCL, + }; + + std::vector fmts; + for (auto fmt = list; fmt && (*fmt != AV_PIX_FMT_NONE); fmt++) { + bool is_blacklisted = false; + for (auto blacklisted : hardware_formats) { + if (*fmt == blacklisted) + is_blacklisted = true; + } + if (!is_blacklisted) + fmts.push_back(*fmt); + } + + fmts.push_back(AV_PIX_FMT_NONE); + + return std::move(fmts); +} + +void ffmpeg::tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context) +{ + std::map> + colorspaces = { + {VIDEO_CS_DEFAULT, {AVCOL_SPC_BT470BG, AVCOL_PRI_BT470BG, AVCOL_TRC_SMPTE170M}}, + {VIDEO_CS_601, {AVCOL_SPC_BT470BG, AVCOL_PRI_BT470BG, AVCOL_TRC_SMPTE170M}}, + {VIDEO_CS_709, {AVCOL_SPC_BT709, AVCOL_PRI_BT709, AVCOL_TRC_BT709}}, + }; + std::map colorranges = { + {VIDEO_RANGE_DEFAULT, AVCOL_RANGE_MPEG}, + {VIDEO_RANGE_PARTIAL, AVCOL_RANGE_MPEG}, + {VIDEO_RANGE_FULL, AVCOL_RANGE_JPEG}, + }; + + { + auto found = colorspaces.find(colorspace); + if (found != colorspaces.end()) { + context->colorspace = std::get(found->second); + context->color_primaries = std::get(found->second); + context->color_trc = std::get(found->second); + } + } + { + auto found = colorranges.find(range); + if (found != colorranges.end()) { + context->color_range = found->second; + } + } + + // Downscaling should result in downscaling, not pixelation + context->chroma_sample_location = AVCHROMA_LOC_CENTER; +} + +const char* ffmpeg::tools::get_std_compliance_name(int compliance) +{ + switch (compliance) { + case FF_COMPLIANCE_VERY_STRICT: + return "Very Strict"; + case FF_COMPLIANCE_STRICT: + return "Strict"; + case FF_COMPLIANCE_NORMAL: + return "Normal"; + case FF_COMPLIANCE_UNOFFICIAL: + return "Unofficial"; + case FF_COMPLIANCE_EXPERIMENTAL: + return "Experimental"; + } + return "Invalid"; +} + +const char* ffmpeg::tools::get_thread_type_name(int thread_type) +{ + switch (thread_type) { + case FF_THREAD_FRAME | FF_THREAD_SLICE: + return "Slice & Frame"; + case FF_THREAD_FRAME: + return "Frame"; + case FF_THREAD_SLICE: + return "Slice"; + default: + return "None"; + } +} + +void ffmpeg::tools::print_av_option_bool(AVCodecContext* context, const char* option, std::string text) +{ + if (av_opt_is_set_to_default_by_name(context, option, AV_OPT_SEARCH_CHILDREN) == 0) { + int64_t v = 0; + if (av_opt_get_int(context, option, AV_OPT_SEARCH_CHILDREN, &v) == 0) { + PLOG_INFO("[%s] %s: %s", context->codec->name, text.c_str(), v == 0 ? "Disabled" : "Enabled"); + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } +} + +void ffmpeg::tools::print_av_option_int(AVCodecContext* context, const char* option, std::string text, + std::string suffix) +{ + if (av_opt_is_set_to_default_by_name(context, option, AV_OPT_SEARCH_CHILDREN) == 0) { + int64_t v = 0; + if (av_opt_get_int(context, option, AV_OPT_SEARCH_CHILDREN, &v) == 0) { + PLOG_INFO("[%s] %s: %lld %s", context->codec->name, text.c_str(), v, suffix.c_str()); + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } +} + +void ffmpeg::tools::print_av_option_string(AVCodecContext* context, const char* option, std::string text, + std::function decoder) +{ + if (av_opt_is_set_to_default_by_name(context, option, AV_OPT_SEARCH_CHILDREN) == 0) { + int64_t v = 0; + if (av_opt_get_int(context, option, AV_OPT_SEARCH_CHILDREN, &v) == 0) { + std::string name = ""; + if (decoder) + name = decoder(v); + PLOG_INFO("[%s] %s: %s", context->codec->name, text.c_str(), name.c_str()); + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } + } else { + PLOG_INFO("[%s] %s: ", context->codec->name, text.c_str()); + } +} diff --git a/source/ffmpeg/tools.hpp b/source/ffmpeg/tools.hpp new file mode 100644 index 00000000..f33fb94f --- /dev/null +++ b/source/ffmpeg/tools.hpp @@ -0,0 +1,77 @@ +// FFMPEG Video Encoder Integration for OBS Studio +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef OBS_FFMPEG_FFMPEG_UTILITY +#define OBS_FFMPEG_FFMPEG_UTILITY +#pragma once + +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +namespace ffmpeg { + namespace tools { + std::string translate_encoder_capabilities(int capabilities); + + 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); + + video_format avpixelformat_to_obs_videoformat(AVPixelFormat v); + + AVPixelFormat get_least_lossy_format(const AVPixelFormat* haystack, AVPixelFormat needle); + + AVColorSpace obs_videocolorspace_to_avcolorspace(video_colorspace v); + + AVColorRange obs_videorangetype_to_avcolorrange(video_range_type v); + + bool can_hardware_encode(const AVCodec* codec); + + std::vector get_software_formats(const AVPixelFormat* list); + + void setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context); + + const char* get_std_compliance_name(int compliance); + + const char* get_thread_type_name(int thread_type); + + void print_av_option_bool(AVCodecContext* context, const char* option, std::string text); + + void print_av_option_int(AVCodecContext* context, const char* option, std::string text, + std::string suffix); + + void print_av_option_string(AVCodecContext* context, const char* option, std::string text, + std::function decoder); + + } // namespace tools +} // namespace ffmpeg + +#endif OBS_FFMPEG_FFMPEG_UTILITY diff --git a/tools/make-solutions.bat b/tools/make-solutions.bat deleted file mode 100644 index 1c59122d..00000000 --- a/tools/make-solutions.bat +++ /dev/null @@ -1,30 +0,0 @@ -@ECHO OFF -SETLOCAL EnableDelayedExpansion -mkdir %~dp0\..\build -CD /D %~dp0\..\build - -SET "DEPSPATH=%CD%/deps/obs" - -:: Compilers -SET COMPILER#=0 -SET /A COMPILER#=COMPILER#+1 -SET "COMPILER[%COMPILER#%]=Visual Studio 15 2017" -SET "TOOLSET[%COMPILER#%]=" -SET "PATH[%COMPILER#%]=%CD%/vs2017-32" -SET "DISTRIB[%COMPILER#%]=%CD%/vs2017-distrib" -SET "SYSTEM[%COMPILER#%]=win32" -SET /A COMPILER#=COMPILER#+1 -SET "COMPILER[%COMPILER#%]=Visual Studio 15 2017 Win64" -SET "TOOLSET[%COMPILER#%]=host=x64" -SET "PATH[%COMPILER#%]=%CD%/vs2017-64" -SET "DISTRIB[%COMPILER#%]=%CD%/vs2017-distrib" -SET "SYSTEM[%COMPILER#%]=win64" - -FOR /L %%i IN (1,1,%COMPILER#%) DO ( - ECHO -- BUILD FOR "!COMPILER[%%i]!" -- - mkdir "!PATH[%%i]!" - pushd "!PATH[%%i]!" - cmake -G "!COMPILER[%%i]!" -T "!TOOLSET[%%i]!" -DCMAKE_INSTALL_PREFIX="!DISTRIB[%%i]!" -DCPACK_SYSTEM_NAME="!SYSTEM[%%i]!" ../../ - popd -) -PAUSE \ No newline at end of file