22 Commits

Author SHA1 Message Date
Michael Fabian 'Xaymar' Dirks 18024aaf12 project: Version 0.3.1
* Replaced AppVeyor with GitHub Actions.
* Fixed rescaling on the output tab not working.
2019-10-31 08:13:42 +01:00
Michael Fabian 'Xaymar' Dirks b2b5cd8fad encoder: Fix width and height to use rescaled resolution
The function video_output_get_info does not contain rescaling information from the output tab, so by replacing it with obs_encoder_get_width/height we can actually use the scaled resolution.
2019-10-31 06:34:58 +01:00
Michael Fabian 'Xaymar' Dirks b6e881b90f ci: Remove AppVeyor scripts 2019-10-31 05:53:34 +01:00
Michael Fabian 'Xaymar' Dirks bd60958b2f ci: Also build InnoSetup installer on Github Actions 2019-10-31 05:53:34 +01:00
Michael Fabian 'Xaymar' Dirks f9ad87a56d ci: Add packaging and artifacts to Github Actions
Github Actions is going to replace AppVeyor in the long term for all release archives, simply because it integrates better with GitHub.
2019-10-31 05:53:34 +01:00
Michael Fabian 'Xaymar' Dirks b07365cdc4 project: Version 0.3.0
* Updated the libOBS dependency to 24.0.0. You will need to use OBS Studio 24.0.0 or newer in order to run the plugin.
* Implemented full hardware encoding for all GPU capable encoders (NVENC H264, NVENC H265). This should be perform identical to the OBS included full hardware encoders.
* Reduced the latency for some encoders to be as low as 1 frame, if settings allow. For NVENC, latency is tied to (B-Frames + 1) and Lookahead Frames, whichever is bigger.
* Further improved performance by throwing even less memory away every frame. Almost all memory is now re-used if possible and only released when the encoding is stopped by any means.
* Apple ProRes encoded files should now be remuxable when stored in Matroska (MKV) and survive at least one remuxing. This bug was already fixed in current master FFmpeg, and thanks for discovering this bug go to FRANKIEonPC.
* All color metadata is now set for encoders that support this, such as NVENC. Video players and editors that support this kind of metadata should see an improvement in color quality, especially with 10-bit or higher output.
* Framerate is now set to be fixed, further improving compatibility with players. It was previously left to be guessed by the video player through the timing information.
* Settings are now output to the log file for easier debugging. Please remember to send a log file with any issue you have with the plugin.
2019-10-19 02:52:23 +02:00
Michael Fabian 'Xaymar' Dirks f21cbe9aba ui/nvenc_shared: Fix lag calculation for rc-lookahead 2019-10-19 02:40:50 +02:00
Michael Fabian 'Xaymar' Dirks 403b43e77b cmake: Also include template files in project generation 2019-10-17 08:17:06 +02:00
Michael Fabian 'Xaymar' Dirks 58d8713369 enocder: Use ffmpeg for pixel format again
Reverts an earlier change as this apparently works now.
2019-10-17 08:16:42 +02:00
Michael Fabian 'Xaymar' Dirks 2f8acc58cf encoder: Separate get_defaults and get_properties for SW/HW encoders 2019-10-04 19:27:45 +02:00
Michael Fabian 'Xaymar' Dirks e4e76dae8f ui/nvenc: Override lag in frames to use Max. B-Frames 2019-10-03 20:10:03 +02:00
Michael Fabian 'Xaymar' Dirks 4cac28a8a3 ui/handler, encoder: Allow overriding lag in frames
The lag in frames is not dictated by the number of threads being used by some encoders. At least for hardware encoders, the expected frame lag in real time encoding is 1 + the number of bframes
2019-10-03 20:09:34 +02:00
Michael Fabian 'Xaymar' Dirks d0dc4be985 encoder: Replace ffmpeg::avframe_queue with integrated implementation 2019-10-03 19:21:54 +02:00
Michael Fabian 'Xaymar' Dirks 4836f9dda6 hwapi/d3d11: Implement new HWAPI code 2019-10-03 19:19:20 +02:00
Michael Fabian 'Xaymar' Dirks 650b397ced hwapi/base: Improve frame allocation method
This allows us to cache existing frames, reducing the CPU and GPU memory load that appears due to constantly recreating frames.
2019-10-03 19:18:44 +02:00
Michael Fabian 'Xaymar' Dirks d3f7b15633 hwapi/base: Add missing #include <memory> 2019-09-29 19:33:51 +02:00
Michael Fabian 'Xaymar' Dirks 545dcd6d50 encoder: Add support for true hardware encoding
This is the last step towards truly efficient encoding on AMD, Nvidia and Intel GPUs. With this we have no software overhead and can directly encode the content that OBS gives us, without going through any intermediate CPU layer. This is effectively what @jp9000 did for the OBS-integrated nvenc, but thanks to FFmpeg it works on all encoders that support D3D11VA acceleration.

With the change, the encoding should now work flawlessly even in very constrained situations (unless OBS itself is being starved of resources). Especially people streaming and recording Ubisoft games will likely see a drastic increase in encoding capability, and thanks to the new options will also be able to get a much higher quality stream and recording with the same hardware.
2019-09-29 19:29:00 +02:00
Michael Fabian 'Xaymar' Dirks 0461b20e1b ui/handler: Allow Codec handlers to select the proper device 2019-09-29 19:16:26 +02:00
Michael Fabian 'Xaymar' Dirks b3a6dbb1b4 hwapi: Add API handlers to deal with some heavy lifting code
Makes our life easier when actually dealing with hardware encoding.
2019-09-29 19:15:45 +02:00
Michael Fabian 'Xaymar' Dirks fe71944199 ui/prores_aw_handler: Workaround for FFmpeg bug with ProRes in Matroska
In current FFmpeg, whenever Matroska with ProRes is demuxed it creates an atom that is just 8 bytes short of the true size necessary.

We can work around this by padding the actual packet by 8 0x00 bytes, which should result in older FFmpeg versions working fine.

An FFmpeg patch is available: http://ffmpeg.org/pipermail/ffmpeg-devel/2019-September/250724.html
2019-09-29 07:27:22 +02:00
Michael Fabian 'Xaymar' Dirks cbd39a8c2a encoder: Cache the UI handler and allow packet processing 2019-09-29 06:58:01 +02:00
Michael Fabian 'Xaymar' Dirks 62eae3827b ui/handler: Add function for packet processing 2019-09-29 06:55:42 +02:00
28 changed files with 1040 additions and 544 deletions
+24 -7
View File
@@ -3,32 +3,49 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: windows:
strategy: strategy:
matrix: matrix:
os: [windows-2016, windows-2019] os: [windows-2016, windows-2019]
include: include:
- os: windows-2016 - os: windows-2016
generator_32: "Visual Studio 15 2017" generator_32: "Visual Studio 15 2017"
generator_64: "Visual Studio 15 2017 Win64" generator_64: "Visual Studio 15 2017"
sysversion: "10.0.17763.0" sysversion: "10.0.17763.0"
- os: windows-2019 - os: windows-2019
generator_32: generator_32: "Visual Studio 16 2019"
generator_64: "Visual Studio 16 2019" generator_64: "Visual Studio 16 2019"
sysversion: "10.0.18362.0" sysversion: "10.0.18362.0"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Clone Repository - name: "Clone Repository"
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Update Submodules
run: git submodule update --init --force --recursive
- name: Install Node.JS 10.x - name: Install Node.JS 10.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 10 node-version: 10
- name: Build - name: Configure & Compile
env: env:
CMAKE_GENERATOR_32: ${{ matrix.generator_32 }} CMAKE_GENERATOR_32: ${{ matrix.generator_32 }}
CMAKE_GENERATOR_64: ${{ matrix.generator_64 }} CMAKE_GENERATOR_64: ${{ matrix.generator_64 }}
CMAKE_SYSTEM_VERSION: ${{ matrix.sysversion }} CMAKE_SYSTEM_VERSION: ${{ matrix.sysversion }}
run: node ./ci/builder.js run: node ./ci/builder.js
- name: Package
env:
CMAKE_GENERATOR_32: ${{ matrix.generator_32 }}
CMAKE_GENERATOR_64: ${{ matrix.generator_64 }}
run: |
mkdir build/package
node ./ci/packager.js
- name: "Package Installer (Prereqs)"
run: |
curl "-kL" "https://cdn.xaymar.com/ci/innosetup-6.0.3.exe" "-f" "--retry" "5" "-o" "inno.exe"
.\inno.exe /VERYSILENT /SP- /SUPPRESSMSGBOXES /NORESTART
- name: "Package Installer (Compile)"
run: |
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' /Qp ".\build\64\installer.iss"
- name: "Upload Artifacts"
uses: actions/upload-artifact@v1
with:
name: ${{ matrix.os }}
path: build/package
+21 -3
View File
@@ -25,8 +25,8 @@ Include("cmake/util.cmake")
# Automatic Versioning # Automatic Versioning
set(VERSION_MAJOR 0) set(VERSION_MAJOR 0)
set(VERSION_MINOR 2) set(VERSION_MINOR 3)
set(VERSION_PATCH 0) set(VERSION_PATCH 1)
set(VERSION_TWEAK 0) set(VERSION_TWEAK 0)
set(PROJECT_COMMIT "N/A") set(PROJECT_COMMIT "N/A")
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git") if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
@@ -116,18 +116,27 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
configure_file( configure_file(
"${PROJECT_SOURCE_DIR}/cmake/version.hpp.in" "${PROJECT_SOURCE_DIR}/cmake/version.hpp.in"
"${PROJECT_BINARY_DIR}/source/version.hpp" "${PROJECT_BINARY_DIR}/source/version.hpp"
@ONLY
) )
configure_file( configure_file(
"${PROJECT_SOURCE_DIR}/cmake/module.cpp.in" "${PROJECT_SOURCE_DIR}/cmake/module.cpp.in"
"${PROJECT_BINARY_DIR}/source/module.cpp" "${PROJECT_BINARY_DIR}/source/module.cpp"
@ONLY
) )
# Windows # Windows
if (WIN32) if (WIN32)
## Installer (InnoSetup) ## Installer (InnoSetup)
get_filename_component(ISS_FILES_DIR "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_FILES_DIR}" ISS_FILES_DIR)
get_filename_component(ISS_PACKAGE_DIR "${CMAKE_PACKAGE_PREFIX}" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PACKAGE_DIR}" ISS_PACKAGE_DIR)
get_filename_component(ISS_SOURCE_DIR "${PROJECT_SOURCE_DIR}" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_SOURCE_DIR}" ISS_SOURCE_DIR)
configure_file( configure_file(
"${PROJECT_SOURCE_DIR}/cmake/installer.iss.in" "${PROJECT_SOURCE_DIR}/cmake/installer.iss.in"
"${PROJECT_BINARY_DIR}/installer.iss" "${PROJECT_BINARY_DIR}/installer.iss"
@ONLY
) )
# Windows Specific Resource Definition # Windows Specific Resource Definition
@@ -280,6 +289,8 @@ set(PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/ffmpeg/swscale.cpp" "${PROJECT_SOURCE_DIR}/source/ffmpeg/swscale.cpp"
"${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.hpp" "${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.hpp"
"${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.cpp" "${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.cpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/base.hpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/base.cpp"
"${PROJECT_SOURCE_DIR}/source/ui/handler.hpp" "${PROJECT_SOURCE_DIR}/source/ui/handler.hpp"
"${PROJECT_SOURCE_DIR}/source/ui/handler.cpp" "${PROJECT_SOURCE_DIR}/source/ui/handler.cpp"
"${PROJECT_SOURCE_DIR}/source/ui/debug_handler.hpp" "${PROJECT_SOURCE_DIR}/source/ui/debug_handler.hpp"
@@ -293,6 +304,12 @@ set(PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.hpp" "${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.hpp"
"${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.cpp" "${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.cpp"
) )
if(WIN32)
list(APPEND PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/hwapi/d3d11.hpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/d3d11.cpp"
)
endif()
# Source Grouping # Source Grouping
source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Data Files" FILES ${PROJECT_DATA}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Data Files" FILES ${PROJECT_DATA})
@@ -317,6 +334,7 @@ add_library(${PROJECT_NAME} MODULE
${PROJECT_GENERATED} ${PROJECT_GENERATED}
${PROJECT_PRIVATE} ${PROJECT_PRIVATE}
${PROJECT_DATA} ${PROJECT_DATA}
${PROJECT_TEMPLATES}
) )
# Include Directories # Include Directories
@@ -357,7 +375,7 @@ endif()
# Link Libraries # Link Libraries
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
"${PROJECT_LIBRARIES}" ${PROJECT_LIBRARIES}
${FFMPEG_LIBRARIES} ${FFMPEG_LIBRARIES}
) )
-74
View File
@@ -1,74 +0,0 @@
# Generic Settings
version: '{build}-{branch}'
matrix:
fast_finish: true
# Build Image & Environment
platform: x64
# Build Tags only
skip_non_tags: true
image:
- Visual Studio 2017
environment:
CMAKE_SYSTEM_VERSION: 10.0.17134.0
CMAKE_GENERATOR_32: "Visual Studio 15 2017"
CMAKE_GENERATOR_64: "Visual Studio 15 2017 Win64"
PACKAGE_PREFIX: obs-ffmpeg-encoder
INNOSETUP_URL: http://www.jrsoftware.org/download.php/is.exe
CURL_VERSION: 7.39.0
# Resource Cache
cache:
- inno.exe
- build/32/libobs-download
- build/32/libobs-src
- build/64/libobs-download
- build/64/libobs-src
# Building
install:
- cmd: ci/appveyor-install.bat
build_script:
- cmd: node ci/builder.js
after_build:
- cmd: node ci/packager.js
- cmd: ci/appveyor-package.bat
# Testing
test: off
# Artifacts
artifacts:
- path: build/obs-ffmpeg-encoder-*.zip
- path: build/obs-ffmpeg-encoder-*.7z
- path: build/obs-ffmpeg-encoder-*.exe
# Deploying
deploy:
- provider: GitHub
auth_token:
secure: diGN1FzupARljI1iJsiAdZHut8aXODkUC6YDDi2oDRikEp5Ic8kQd8SSRDyA4pAJ
draft: true
prerelease: false
force_update: true
on:
appveyor_repo_tag: true
# Notifications
notifications:
- provider: Webhook
url:
secure: PTtt5ALhmK0q42jYyx4/Qa1Uf18+gLMXKGdzJjDISJt8IE/K0Zyp58UYmDDbbyLp4pBRf/Ylj8rn/zYL/mqBoDVRIH5zasPqIvBD0ZhtvNjTOxQ3QoRkAmxgpWeMowm3A3I1rLizA2H4EctPpoAJGrvQ1G2HEYn9tVsGYeetFTo=
on_build_success: false
on_build_failure: false
on_build_status_changed: true
body: >-
{
"content": "**Build {{status}}**: [{{commitId}}] {{commitMessage}}\nBy {{commitAuthor}} on {{commitDate}}\n{{buildUrl}}"
}
-9
View File
@@ -1,9 +0,0 @@
@ECHO OFF
git submodule update --init --force --recursive
IF EXIST inno.exe (
curl -kL "%INNOSETUP_URL%" -f --retry 5 -o inno.exe -z inno.exe
) else (
curl -kL "%INNOSETUP_URL%" -f --retry 5 -o inno.exe
)
inno.exe /VERYSILENT /NORETART /SP- /SUPPRESSMSGBOXES
-3
View File
@@ -1,3 +0,0 @@
@ECHO OFF
ECHO -- Building Installer --
"C:\Program Files (x86)\Inno Setup 5\ISCC.exe" /Qp ".\build\64\installer.iss" > nul
+5 -8
View File
@@ -13,7 +13,7 @@ if ((process.platform == "win32") || (process.platform == "win64")) {
`-DCMAKE_SYSTEM_VERSION=${process.env.CMAKE_SYSTEM_VERSION}`, `-DCMAKE_SYSTEM_VERSION=${process.env.CMAKE_SYSTEM_VERSION}`,
`-DCMAKE_PACKAGE_NAME=obs-ffmpeg-encoder`, `-DCMAKE_PACKAGE_NAME=obs-ffmpeg-encoder`,
'-DCMAKE_INSTALL_PREFIX="build/distrib/"', '-DCMAKE_INSTALL_PREFIX="build/distrib/"',
'-DCMAKE_PACKAGE_PREFIX="build/"', '-DCMAKE_PACKAGE_PREFIX="build/package/"',
]; ];
let extra_build = [ let extra_build = [
@@ -26,9 +26,8 @@ if ((process.platform == "win32") || (process.platform == "win64")) {
if ((process.env.CMAKE_GENERATOR_32 !== undefined) && (process.env.CMAKE_GENERATOR_32 !== "")) { if ((process.env.CMAKE_GENERATOR_32 !== undefined) && (process.env.CMAKE_GENERATOR_32 !== "")) {
x32_steps.push( x32_steps.push(
[ 'cmake', [ [ 'cmake', [
'-H.', '-H.', '-Bbuild/32',
'-Bbuild/32', `-G"${process.env.CMAKE_GENERATOR_32}"`, '-AWin32', '-T"host=x64"',
`-G"${process.env.CMAKE_GENERATOR_32}"`,
].concat(extra_conf), env ] ].concat(extra_conf), env ]
); );
x32_steps.push( x32_steps.push(
@@ -42,10 +41,8 @@ if ((process.platform == "win32") || (process.platform == "win64")) {
if ((process.env.CMAKE_GENERATOR_64 !== undefined) && (process.env.CMAKE_GENERATOR_64 !== "")) { if ((process.env.CMAKE_GENERATOR_64 !== undefined) && (process.env.CMAKE_GENERATOR_64 !== "")) {
x64_steps.push( x64_steps.push(
[ 'cmake', [ [ 'cmake', [
'-H.', '-H.', '-Bbuild/64',
'-Bbuild/64', `-G"${process.env.CMAKE_GENERATOR_64}"`, '-Ax64', '-T"host=x64"',
`-G"${process.env.CMAKE_GENERATOR_64}"`,
'-T"host=x64"'
].concat(extra_conf), env ] ].concat(extra_conf), env ]
); );
x64_steps.push( x64_steps.push(
+19 -13
View File
@@ -2,20 +2,8 @@
const process = require('process'); const process = require('process');
const runner = require('./runner.js'); const runner = require('./runner.js');
function runRunners(runnerArray, name) {
return new Promise(async (resolve, reject) => {
let local = runnerArray.reverse();
while (local.length > 0) {
let task = local.pop();
let work = new runner(name, task[0], task[1], task[2]);
await work.run();
}
resolve(0);
});
}
let env = process.env; let env = process.env;
let steps = []; let steps = [];
if ((process.env.CMAKE_GENERATOR_64 !== undefined) && (process.env.CMAKE_GENERATOR_64 !== "")) { if ((process.env.CMAKE_GENERATOR_64 !== undefined) && (process.env.CMAKE_GENERATOR_64 !== "")) {
@@ -50,6 +38,24 @@ if ((process.env.CMAKE_GENERATOR_64 !== undefined) && (process.env.CMAKE_GENERAT
); );
} }
function runRunners(runnerArray, name) {
return new Promise(async (resolve, reject) => {
let local = runnerArray.reverse();
while (local.length > 0) {
try {
let task = local.pop();
let work = new runner(name, task[0], task[1], task[2]);
await work.run();
} catch (e) {
reject(e);
return;
}
}
resolve(0);
});
}
let promises = []; let promises = [];
promises.push(runRunners(steps, "32-Bit")); promises.push(runRunners(steps, "32-Bit"));
Promise.all(promises).then( Promise.all(promises).then(
+3 -3
View File
@@ -21,8 +21,8 @@ AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName} DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
AllowNoIcons=yes AllowNoIcons=yes
LicenseFile="@PROJECT_SOURCE_DIR@/LICENSE" LicenseFile="@ISS_SOURCE_DIR@/LICENSE"
OutputDir="@CMAKE_INSTALL_PREFIX@/../" OutputDir="@ISS_PACKAGE_DIR@"
OutputBaseFilename=obs-ffmpeg-encoder-{#MyAppVersion} OutputBaseFilename=obs-ffmpeg-encoder-{#MyAppVersion}
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
@@ -34,7 +34,7 @@ VersionInfoDescription={#MyAppName} Setup
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
[Files] [Files]
Source: "@CMAKE_INSTALL_PREFIX@/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "@ISS_FILES_DIR@/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
+370 -201
View File
@@ -52,6 +52,11 @@ extern "C" {
// - encode_texture/encode // - encode_texture/encode
// I don't understand what get_video_info is actually for in this order, as this postpones initialization to 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 // FFmpeg
#define ST_FFMPEG "FFmpeg" #define ST_FFMPEG "FFmpeg"
#define ST_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings" #define ST_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
@@ -79,7 +84,7 @@ static void* _create_texture(obs_data_t* settings, obs_encoder_t* encoder) noexc
PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, encoder); PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, encoder);
#endif #endif
return reinterpret_cast<void*>(new obsffmpeg::encoder(settings, encoder, true)); return reinterpret_cast<void*>(new obsffmpeg::encoder(settings, encoder, true));
} catch (const obsffmpeg::unsupported_gpu_exception& ex) { } catch (const obsffmpeg::unsupported_gpu_exception&) {
obsffmpeg::encoder_factory* fac = obsffmpeg::encoder_factory* fac =
reinterpret_cast<obsffmpeg::encoder_factory*>(obs_encoder_get_type_data(encoder)); reinterpret_cast<obsffmpeg::encoder_factory*>(obs_encoder_get_type_data(encoder));
PLOG_WARNING("<%s> GPU not supported for hardware encoding, falling back to software.", PLOG_WARNING("<%s> GPU not supported for hardware encoding, falling back to software.",
@@ -142,6 +147,17 @@ static void _get_defaults(obs_data_t* settings, void* type_data) noexcept try {
PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); 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<obsffmpeg::encoder_factory*>(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 { static obs_properties_t* _get_properties(void* ptr, void* type_data) noexcept try {
#ifdef DEBUG_CALL_ORDER #ifdef DEBUG_CALL_ORDER
PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, type_data); PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, type_data);
@@ -162,6 +178,26 @@ static obs_properties_t* _get_properties(void* ptr, void* type_data) noexcept tr
return reinterpret_cast<obs_properties_t*>(0); return reinterpret_cast<obs_properties_t*>(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<obsffmpeg::encoder_factory*>(type_data)->get_properties(props, true);
}
if (ptr != nullptr) {
reinterpret_cast<obsffmpeg::encoder*>(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<obs_properties_t*>(0);
} catch (...) {
PLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
return reinterpret_cast<obs_properties_t*>(0);
}
static bool _update(void* ptr, obs_data_t* settings) noexcept try { static bool _update(void* ptr, obs_data_t* settings) noexcept try {
#ifdef DEBUG_CALL_ORDER #ifdef DEBUG_CALL_ORDER
PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, settings); PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, ptr, settings);
@@ -282,6 +318,9 @@ static bool _encode_audio(void* ptr, struct encoder_frame* frame, struct encoder
obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(codec), info(), info_fallback() 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. // Unique Id is FFmpeg name.
info.uid = avcodec_ptr->name; info.uid = avcodec_ptr->name;
@@ -298,8 +337,8 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
info.readable_name = sstr.str(); info.readable_name = sstr.str();
// Allow UI Handler to replace visible name. // Allow UI Handler to replace visible name.
obsffmpeg::find_codec_handler(avcodec_ptr->name) if (_handler)
->override_visible_name(avcodec_ptr, info.readable_name); _handler->override_visible_name(avcodec_ptr, info.readable_name);
} }
// Assign Ids. // Assign Ids.
@@ -325,6 +364,7 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
#endif #endif
// Hardware encoder? // Hardware encoder?
#ifdef HARDWARE_ENCODING
if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) { if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) {
info_fallback.uid = info.uid + "_sw"; info_fallback.uid = info.uid + "_sw";
info_fallback.codec = info.codec; info_fallback.codec = info.codec;
@@ -338,6 +378,7 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
info.oei.caps |= OBS_ENCODER_CAP_PASS_TEXTURE; info.oei.caps |= OBS_ENCODER_CAP_PASS_TEXTURE;
} }
#endif
} }
obsffmpeg::encoder_factory::~encoder_factory() {} obsffmpeg::encoder_factory::~encoder_factory() {}
@@ -376,6 +417,8 @@ void obsffmpeg::encoder_factory::register_encoder()
if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) { if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) {
info.oei.create = _create_texture; info.oei.create = _create_texture;
info.oei.encode_texture = _encode_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.type = info.oei.type;
info_fallback.oei.create = _create; info_fallback.oei.create = _create;
@@ -403,14 +446,10 @@ void obsffmpeg::encoder_factory::register_encoder()
avcodec_ptr->name, avcodec_ptr->long_name, avcodec_ptr->capabilities); avcodec_ptr->name, avcodec_ptr->long_name, avcodec_ptr->capabilities);
} }
void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings) void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings, bool hw_encode)
{ {
{ // Handler if (_handler)
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name); _handler->get_defaults(settings, avcodec_ptr, nullptr, hw_encode);
if (ptr) {
ptr->get_defaults(settings, avcodec_ptr, nullptr);
}
}
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) { if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
obs_data_set_default_int(settings, S_KEYFRAMES_INTERVALTYPE, 0); obs_data_set_default_int(settings, S_KEYFRAMES_INTERVALTYPE, 0);
@@ -421,8 +460,11 @@ void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings)
{ // Integrated Options { // Integrated Options
// FFmpeg // FFmpeg
obs_data_set_default_string(settings, ST_FFMPEG_CUSTOMSETTINGS, ""); obs_data_set_default_string(settings, ST_FFMPEG_CUSTOMSETTINGS, "");
obs_data_set_default_int(settings, ST_FFMPEG_COLORFORMAT, static_cast<int64_t>(AV_PIX_FMT_NONE)); if (!hw_encode) {
obs_data_set_default_int(settings, ST_FFMPEG_COLORFORMAT,
static_cast<int64_t>(AV_PIX_FMT_NONE));
obs_data_set_default_int(settings, ST_FFMPEG_THREADS, 0); obs_data_set_default_int(settings, ST_FFMPEG_THREADS, 0);
}
obs_data_set_default_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT); obs_data_set_default_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT);
} }
} }
@@ -440,14 +482,10 @@ static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_dat
return false; return false;
} }
void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props) void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props, bool hw_encode)
{ {
{ // Handler if (_handler)
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name); _handler->get_properties(props, avcodec_ptr, nullptr, hw_encode);
if (ptr) {
ptr->get_properties(props, avcodec_ptr, nullptr);
}
}
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) { if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options // Key-Frame Options
@@ -496,9 +534,11 @@ void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
obs_text_type::OBS_TEXT_DEFAULT); obs_text_type::OBS_TEXT_DEFAULT);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_CUSTOMSETTINGS))); obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_CUSTOMSETTINGS)));
} }
if (!hw_encode) {
if (avcodec_ptr->pix_fmts) { if (avcodec_ptr->pix_fmts) {
auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT, TRANSLATE(ST_FFMPEG_COLORFORMAT), auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); 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_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_COLORFORMAT)));
obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC), obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC),
static_cast<int64_t>(AV_PIX_FMT_NONE)); static_cast<int64_t>(AV_PIX_FMT_NONE));
@@ -508,10 +548,12 @@ void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
} }
} }
if (avcodec_ptr->capabilities & (AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) { 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, auto p =
std::thread::hardware_concurrency() * 2, 1); 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))); obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_THREADS)));
} }
}
{ {
auto p = obs_properties_add_list(grp, ST_FFMPEG_STANDARDCOMPLIANCE, auto p = obs_properties_add_list(grp, ST_FFMPEG_STANDARDCOMPLIANCE,
TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST, TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST,
@@ -546,56 +588,8 @@ const obsffmpeg::encoder_info& obsffmpeg::encoder_factory::get_fallback()
return info_fallback; return info_fallback;
} }
obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode) void obsffmpeg::encoder::initialize_sw(obs_data_t* settings)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
{ {
if (is_texture_encode) {
throw obsffmpeg::unsupported_gpu_exception("not implemented yet");
}
_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
// Verify that the codec actually still exists.
_codec = avcodec_find_encoder_by_name(_factory->get_avcodec()->name);
if (!_codec) {
PLOG_ERROR("Failed to find encoder for codec '%s'.", _factory->get_avcodec()->name);
throw std::runtime_error("failed to find codec");
}
// 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");
}
// Settings
/// Rate Control
_context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
_context->debug = 0;
/// Threading
if (_codec->capabilities
& (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
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;
}
int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
if (threads > 0) {
_context->thread_count = static_cast<int>(threads);
_lag_in_frames = _context->thread_count;
} else {
_context->thread_count = std::thread::hardware_concurrency();
_lag_in_frames = _context->thread_count;
}
}
// 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 (_codec->type == AVMEDIA_TYPE_VIDEO) { if (_codec->type == AVMEDIA_TYPE_VIDEO) {
// Initialize Video Encoding // Initialize Video Encoding
auto voi = video_output_get_info(obs_encoder_video(_self)); auto voi = video_output_get_info(obs_encoder_video(_self));
@@ -606,15 +600,10 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
static_cast<AVPixelFormat>(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT)); static_cast<AVPixelFormat>(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT));
if (_pixfmt_target == AV_PIX_FMT_NONE) { if (_pixfmt_target == AV_PIX_FMT_NONE) {
// Find the best conversion format. // Find the best conversion format.
std::vector<AVPixelFormat> fmts = ffmpeg::tools::get_software_formats(_codec->pix_fmts); _pixfmt_target = ffmpeg::tools::get_least_lossy_format(_codec->pix_fmts, _pixfmt_source);
_pixfmt_target = ffmpeg::tools::get_best_compatible_format(fmts.data(), _pixfmt_source);
{ // Allow Handler to override the automatic color format for sanity reasons. if (_handler) // Allow Handler to override the automatic color format for sanity reasons.
auto ptr = obsffmpeg::find_codec_handler(_codec->name); _handler->override_colorformat(_pixfmt_target, settings, _codec, _context);
if (ptr) {
ptr->override_colorformat(_pixfmt_target, settings, _codec, _context);
}
}
} else { } else {
// Use user override, guaranteed to be supported. // Use user override, guaranteed to be supported.
bool is_format_supported = false; bool is_format_supported = false;
@@ -632,8 +621,8 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
} }
} }
_context->width = voi->width; _context->width = static_cast<int>(obs_encoder_get_width(_self));
_context->height = voi->height; _context->height = static_cast<int>(obs_encoder_get_height(_self));
ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context); ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context);
_context->pix_fmt = _pixfmt_target; _context->pix_fmt = _pixfmt_target;
@@ -663,9 +652,153 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
throw std::runtime_error(sstr.str()); throw std::runtime_error(sstr.str());
} }
} }
}
{ // Log Encoder info 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<AVHWFramesContext*>(_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<AVFrame> 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<AVFrame> obsffmpeg::encoder::pop_free_frame()
{
std::shared_ptr<AVFrame> 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<AVFrame>(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<AVFrame> frame)
{
_used_frames.push(frame);
}
std::shared_ptr<AVFrame> 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), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
{
// Initial set up.
_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
_codec = _factory->get_avcodec();
_handler = obsffmpeg::find_codec_handler(_codec->name);
if (is_texture_encode) {
#ifdef WIN32
_hwapi = std::make_shared<obsffmpeg::hwapi::d3d11>();
#endif
obsffmpeg::hwapi::device dev;
if (_handler)
dev = _handler->find_hw_device(_hwapi, _codec, _context);
try {
_hwinst = _hwapi->create(dev);
} catch (...) {
throw obsffmpeg::unsupported_gpu_exception("Creating GPU context failed.");
}
}
// 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_sw(settings);
} else {
try {
initialize_hw(settings);
} catch (...) {
throw obsffmpeg::unsupported_gpu_exception("Initializing hardware context failed.");
}
}
// Log Encoder info
PLOG_INFO("[%s] Initializing...", _codec->name); PLOG_INFO("[%s] Initializing...", _codec->name);
if (_hwinst) {
PLOG_INFO("[%s] Video Input: %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),
_swscale.is_source_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Video Output: %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),
_swscale.is_target_full_range() ? "Full" : "Partial");
} else {
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(), PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(),
_swscale.get_source_height(), _swscale.get_source_height(),
ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()), ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()),
@@ -676,13 +809,10 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()), ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()), ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()),
_swscale.is_target_full_range() ? "Full" : "Partial"); _swscale.is_target_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Framerate: %ld/%ld (%f FPS)", _codec->name, _context->time_base.den,
_context->time_base.num,
static_cast<double_t>(_context->time_base.den)
/ static_cast<double_t>(_context->time_base.num));
PLOG_INFO("[%s] Custom Settings: %s", _codec->name,
obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS));
} }
PLOG_INFO("[%s] Framerate: %ld/%ld (%f FPS)", _codec->name, _context->time_base.den, _context->time_base.num,
static_cast<double_t>(_context->time_base.den) / static_cast<double_t>(_context->time_base.num));
PLOG_INFO("[%s] Custom Settings: %s", _codec->name, obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS));
// Update settings // Update settings
update(settings); update(settings);
@@ -695,11 +825,6 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
<< "' failed with error: " << ffmpeg::tools::get_error_description(res) << " (code " << res << ")"; << "' failed with error: " << ffmpeg::tools::get_error_description(res) << " (code " << res << ")";
throw std::runtime_error(sstr.str()); throw std::runtime_error(sstr.str());
} }
// Create Frame queue
_frame_queue.set_pixel_format(_context->pix_fmt);
_frame_queue.set_resolution(_context->width, _context->height);
_frame_queue.precache(2);
} }
obsffmpeg::encoder::~encoder() obsffmpeg::encoder::~encoder()
@@ -721,19 +846,13 @@ obsffmpeg::encoder::~encoder()
av_packet_unref(&_current_packet); av_packet_unref(&_current_packet);
_frame_queue.clear();
_frame_queue_used.clear();
_swscale.finalize(); _swscale.finalize();
} }
void obsffmpeg::encoder::get_properties(obs_properties_t* props) void obsffmpeg::encoder::get_properties(obs_properties_t* props, bool hw_encode)
{ {
{ // Handler if (_handler)
auto ptr = obsffmpeg::find_codec_handler(_codec->name); _handler->get_properties(props, _codec, _context, hw_encode);
if (ptr) {
ptr->get_properties(props, _codec, _context);
}
}
obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES), false); 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_INTERVALTYPE), false);
@@ -747,12 +866,35 @@ void obsffmpeg::encoder::get_properties(obs_properties_t* props)
bool obsffmpeg::encoder::update(obs_data_t* settings) bool obsffmpeg::encoder::update(obs_data_t* settings)
{ {
{ // Handler // Settings
auto ptr = obsffmpeg::find_codec_handler(_codec->name); /// Rate Control
if (ptr) { _context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
ptr->update(settings, _codec, _context); _context->debug = 0;
/// Threading
if (_codec->capabilities & (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)
&& !_hwinst) {
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;
} }
int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
if (threads > 0) {
_context->thread_count = static_cast<int>(threads);
_lag_in_frames = _context->thread_count;
} else {
_context->thread_count = std::thread::hardware_concurrency();
_lag_in_frames = _context->thread_count;
}
} else {
_context->thread_count = 1;
_context->thread_type = 0;
_lag_in_frames = 1;
}
if (_handler)
_handler->update(settings, _codec, _context);
if ((_codec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) { if ((_codec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options // Key-Frame Options
@@ -779,14 +921,14 @@ bool obsffmpeg::encoder::update(obs_data_t* settings)
nullptr, "=", ";"); nullptr, "=", ";");
} }
{ // Handler Logging if (_handler)
auto ptr = obsffmpeg::find_codec_handler(_codec->name); _handler->override_lag_in_frames(_lag_in_frames, settings, _codec, _context);
if (ptr) {
ptr->log_options(settings, _codec, _context);
}
}
return false; // Handler Logging
if (_handler)
_handler->log_options(settings, _codec, _context);
return true;
} }
void obsffmpeg::encoder::get_audio_info(audio_convert_info*) {} void obsffmpeg::encoder::get_audio_info(audio_convert_info*) {}
@@ -860,8 +1002,9 @@ static inline void copy_data(encoder_frame* frame, AVFrame* vframe)
bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet) bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
{ {
std::shared_ptr<AVFrame> vframe = pop_free_frame(); // Retrieve an empty frame.
// Convert frame. // Convert frame.
std::shared_ptr<AVFrame> vframe = _frame_queue.pop(); // Retrieve an empty frame.
{ {
#ifdef _DEBUG #ifdef _DEBUG
ScopeProfiler profile("convert"); ScopeProfiler profile("convert");
@@ -891,7 +1034,113 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
} }
} }
// Send and receive frames. 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<AVFrame> 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)
{
av_packet_unref(&_current_packet);
int 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<int>(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<AVFrame> const frame)
{
int res = avcodec_send_frame(_context, frame.get());
if (res == 0) {
push_used_frame(frame);
}
return res;
}
bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame> frame, encoder_packet* packet, bool* received_packet)
{ {
#ifdef _DEBUG #ifdef _DEBUG
ScopeProfiler profile("loop"); ScopeProfiler profile("loop");
@@ -899,7 +1148,7 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
bool sent_frame = false; bool sent_frame = false;
bool recv_packet = false; bool recv_packet = false;
bool should_lag = (_lag_in_frames - _count_send_frames) <= 0; bool should_lag = (_count_send_frames >= _lag_in_frames);
auto loop_begin = std::chrono::high_resolution_clock::now(); auto loop_begin = std::chrono::high_resolution_clock::now();
auto loop_end = loop_begin + std::chrono::milliseconds(50); auto loop_end = loop_begin + std::chrono::milliseconds(50);
@@ -912,18 +1161,17 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
#ifdef _DEBUG #ifdef _DEBUG
ScopeProfiler profile_inner("send"); ScopeProfiler profile_inner("send");
#endif #endif
int res = send_frame(vframe); int res = send_frame(frame);
switch (res) { switch (res) {
case 0: case 0:
sent_frame = true; sent_frame = true;
vframe = nullptr; frame = nullptr;
break; break;
case AVERROR(EAGAIN): case AVERROR(EAGAIN):
// This means we should call receive_packet again, but what do we do with that data? // 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? // Why can't we queue on both? Do I really have to implement threading for this stuff?
if (*received_packet == true) { if (*received_packet == true) {
PLOG_WARNING( PLOG_WARNING("Skipped frame due to EAGAIN when a packet was already returned.");
"Skipped frame due to EAGAIN when a packet was already returned.");
sent_frame = true; sent_frame = true;
} }
eagain_is_stupid = true; eagain_is_stupid = true;
@@ -972,88 +1220,9 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
} }
}
if (vframe != nullptr) { if (!sent_frame)
_frame_queue.push(vframe); push_free_frame(frame);
}
return true; return true;
} }
bool obsffmpeg::encoder::video_encode_texture(uint32_t, int64_t, uint64_t, uint64_t*, encoder_packet*, bool*)
{
return false;
}
int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_packet* packet)
{
int res = avcodec_receive_packet(_context, &_current_packet);
if (res == 0) {
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);
}
std::memcpy(_current_packet.data, tmp_packet, sz_packet);
_current_packet.size = static_cast<int>(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;
}
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;
{
std::shared_ptr<AVFrame> uframe = _frame_queue_used.pop_only();
_frame_queue.push(uframe);
}
}
return res;
}
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
{
int res = avcodec_send_frame(_context, frame.get());
switch (res) {
case 0:
_frame_queue_used.push(frame);
_count_send_frames++;
case AVERROR(EAGAIN):
case AVERROR(EOF):
break;
}
return res;
}
+34 -8
View File
@@ -23,10 +23,14 @@
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <queue>
#include <stack>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "ffmpeg/avframe-queue.hpp" #include "ffmpeg/avframe-queue.hpp"
#include "ffmpeg/swscale.hpp" #include "ffmpeg/swscale.hpp"
#include "hwapi/base.hpp"
#include "ui/handler.hpp"
extern "C" { extern "C" {
#include <obs-properties.h> #include <obs-properties.h>
@@ -48,7 +52,7 @@ namespace obsffmpeg {
std::string uid; std::string uid;
std::string codec; std::string codec;
std::string readable_name; std::string readable_name;
obs_encoder_info oei; obs_encoder_info oei = {0};
}; };
class encoder_factory { class encoder_factory {
@@ -56,15 +60,17 @@ namespace obsffmpeg {
encoder_info info_fallback; encoder_info info_fallback;
const AVCodec* avcodec_ptr; const AVCodec* avcodec_ptr;
std::shared_ptr<obsffmpeg::ui::handler> _handler;
public: public:
encoder_factory(const AVCodec* codec); encoder_factory(const AVCodec* codec);
virtual ~encoder_factory(); virtual ~encoder_factory();
void register_encoder(); void register_encoder();
void get_defaults(obs_data_t* settings); void get_defaults(obs_data_t* settings, bool hw_encoder = false);
void get_properties(obs_properties_t* props); void get_properties(obs_properties_t* props, bool hw_encoder = false);
const AVCodec* get_avcodec(); const AVCodec* get_avcodec();
@@ -80,26 +86,43 @@ namespace obsffmpeg {
const AVCodec* _codec; const AVCodec* _codec;
AVCodecContext* _context; AVCodecContext* _context;
ffmpeg::avframe_queue _frame_queue; std::shared_ptr<obsffmpeg::ui::handler> _handler;
ffmpeg::avframe_queue _frame_queue_used;
std::shared_ptr<obsffmpeg::hwapi::base> _hwapi;
std::shared_ptr<obsffmpeg::hwapi::instance> _hwinst;
ffmpeg::swscale _swscale; ffmpeg::swscale _swscale;
AVPacket _current_packet; AVPacket _current_packet;
int64_t _lag_in_frames; size_t _lag_in_frames;
int64_t _count_send_frames; size_t _count_send_frames;
// Extra Data // Extra Data
bool _have_first_frame; bool _have_first_frame;
std::vector<uint8_t> _extra_data; std::vector<uint8_t> _extra_data;
std::vector<uint8_t> _sei_data; std::vector<uint8_t> _sei_data;
// Frame Stack and Queue
std::stack<std::shared_ptr<AVFrame>> _free_frames;
std::queue<std::shared_ptr<AVFrame>> _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<AVFrame> frame);
std::shared_ptr<AVFrame> pop_free_frame();
void push_used_frame(std::shared_ptr<AVFrame> frame);
std::shared_ptr<AVFrame> pop_used_frame();
public: public:
encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode = false); encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode = false);
virtual ~encoder(); virtual ~encoder();
public: // OBS API public: // OBS API
// Shared // Shared
void get_properties(obs_properties_t* props); void get_properties(obs_properties_t* props, bool hw_encode = false);
bool update(obs_data_t* settings); bool update(obs_data_t* settings);
@@ -125,5 +148,8 @@ namespace obsffmpeg {
int receive_packet(bool* received_packet, struct encoder_packet* packet); int receive_packet(bool* received_packet, struct encoder_packet* packet);
int send_frame(std::shared_ptr<AVFrame> frame); int send_frame(std::shared_ptr<AVFrame> frame);
bool encode_avframe(std::shared_ptr<AVFrame> frame, struct encoder_packet* packet,
bool* received_packet);
}; };
} // namespace obsffmpeg } // namespace obsffmpeg
-100
View File
@@ -279,106 +279,6 @@ std::vector<AVPixelFormat> ffmpeg::tools::get_software_formats(const AVPixelForm
return std::move(fmts); return std::move(fmts);
} }
static std::map<std::pair<AVPixelFormat, AVPixelFormat>, double_t> format_compatibility = {
{{AV_PIX_FMT_NV12, AV_PIX_FMT_NV12}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_NV12, AV_PIX_FMT_NV21}, 65535.0},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P}, 65535.0},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P9}, 58981.5},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10}, 53083.35},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P12}, 47775.015},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P14}, 42997.5135},
{{AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P16}, 38697.76215},
{{AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P9}, 65535.0},
{{AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P10}, 58981.5},
{{AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P16}, 53083.35},
{{AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P}, 32767.0},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA422P}, 65535.0},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P9}, 58981.5},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P10}, 53083.35},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P12}, 47775.015},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P14}, 42997.5135},
{{AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P16}, 38697.76215},
{{AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA422P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P9}, 65535.0},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P10}, 58981.5},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P16}, 53083.35},
{{AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P}, 32767.0},
{{AV_PIX_FMT_YVYU422, AV_PIX_FMT_YVYU422}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YVYU422, AV_PIX_FMT_YUYV422}, 65535.0},
{{AV_PIX_FMT_UYVY422, AV_PIX_FMT_UYVY422}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_UYVY422, AV_PIX_FMT_YVYU422}, 65535.0},
{{AV_PIX_FMT_YUYV422, AV_PIX_FMT_YUYV422}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVA444P}, 65535.0},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P9}, 58981.5},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P10}, 53083.35},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P12}, 47775.015},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P14}, 42997.5135},
{{AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P16}, 38697.76215},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P9}, 65535.0},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P10}, 58981.5},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P16}, 53083.35},
{{AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P}, 32767.0},
{{AV_PIX_FMT_RGBA, AV_PIX_FMT_RGBA}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB0}, 65535.0},
{{AV_PIX_FMT_RGBA, AV_PIX_FMT_0RGB}, 32767.0},
{{AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB24}, 16384.0},
{{AV_PIX_FMT_BGRA, AV_PIX_FMT_BGRA}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR0}, 65535.0},
{{AV_PIX_FMT_BGRA, AV_PIX_FMT_0BGR}, 32767.0},
{{AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR24}, 16384.0},
{{AV_PIX_FMT_BGR0, AV_PIX_FMT_BGR0}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_BGR0, AV_PIX_FMT_BGRA}, 65535.0},
{{AV_PIX_FMT_BGR0, AV_PIX_FMT_BGR24}, 32767.0},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY8}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9}, 65535.0},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY10}, 58981.5},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY12}, 53083.35},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY14}, 47775.015},
{{AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY16}, 42997.5135},
{{AV_PIX_FMT_BGR24, AV_PIX_FMT_BGR24}, std::numeric_limits<double_t>::max()},
{{AV_PIX_FMT_BGR24, AV_PIX_FMT_RGB24}, 32767.0},
};
AVPixelFormat ffmpeg::tools::get_best_compatible_format(const AVPixelFormat* list, AVPixelFormat source)
{
double_t score = std::numeric_limits<double_t>::min();
AVPixelFormat best = source;
for (auto fmt = list; fmt && (*fmt != AV_PIX_FMT_NONE); fmt++) {
auto found = format_compatibility.find(std::pair{source, *fmt});
if (found != format_compatibility.end() && (score < found->second)) {
score = found->second;
best = *fmt;
}
}
if (score <= 0) {
int data_loss = 0;
return avcodec_find_best_pix_fmt_of_list(list, source, 0, &data_loss);
}
return best;
}
void ffmpeg::tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context) void ffmpeg::tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context)
{ {
std::map<video_colorspace, std::tuple<AVColorSpace, AVColorPrimaries, AVColorTransferCharacteristic>> std::map<video_colorspace, std::tuple<AVColorSpace, AVColorPrimaries, AVColorTransferCharacteristic>>
-2
View File
@@ -56,8 +56,6 @@ namespace ffmpeg {
std::vector<AVPixelFormat> get_software_formats(const AVPixelFormat* list); std::vector<AVPixelFormat> get_software_formats(const AVPixelFormat* list);
AVPixelFormat get_best_compatible_format(const AVPixelFormat* list, AVPixelFormat source);
void setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context); void setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context);
} // namespace tools } // namespace tools
} // namespace ffmpeg } // namespace ffmpeg
+22
View File
@@ -0,0 +1,22 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// 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"
+68
View File
@@ -0,0 +1,68 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// 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 <cinttypes>
#include <list>
#include <memory>
#include <string>
#include <utility>
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavutil/frame.h>
#include <libavutil/hwcontext.h>
#pragma warning(pop)
}
namespace obsffmpeg {
namespace hwapi {
struct device {
std::pair<int64_t, int64_t> id;
std::string name;
};
class instance;
class base {
public:
virtual std::list<obsffmpeg::hwapi::device> enumerate_adapters() = 0;
virtual std::shared_ptr<obsffmpeg::hwapi::instance> create(obsffmpeg::hwapi::device target) = 0;
};
class instance {
public:
virtual AVBufferRef* create_device_context() = 0;
virtual std::shared_ptr<AVFrame> 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<AVFrame> frame) = 0;
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key,
uint64_t* next_lock_key) = 0;
};
} // namespace hwapi
} // namespace obsffmpeg
+217
View File
@@ -0,0 +1,217 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// 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 <sstream>
#include <vector>
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavutil/hwcontext_d3d11va.h>
#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<CreateDXGIFactory_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory"));
_CreateDXGIFactory1 =
reinterpret_cast<CreateDXGIFactory1_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory1"));
_D3D11CreateDevice = reinterpret_cast<D3D11CreateDevice_t>(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::device> obsffmpeg::hwapi::d3d11::enumerate_adapters()
{
std::list<device> 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<char> 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::instance> obsffmpeg::hwapi::d3d11::create(obsffmpeg::hwapi::device target)
{
std::shared_ptr<d3d11_instance> inst;
ATL::CComPtr<ID3D11Device> device;
ATL::CComPtr<ID3D11DeviceContext> 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<D3D_FEATURE_LEVEL> 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<UINT>(feature_levels.size()), D3D11_SDK_VERSION, &device, NULL,
&context))) {
throw std::runtime_error("Failed to create D3D11 device for target.");
}
return std::make_shared<d3d11_instance>(device, context);
}
struct D3D11AVFrame {
ATL::CComPtr<ID3D11Texture2D> handle;
};
obsffmpeg::hwapi::d3d11_instance::d3d11_instance(ATL::CComPtr<ID3D11Device> device,
ATL::CComPtr<ID3D11DeviceContext> 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<AVHWDeviceContext*>(dctx_ref->data);
AVD3D11VADeviceContext* d3d11va = reinterpret_cast<AVD3D11VADeviceContext*>(dctx->hwctx);
// TODO: Determine if these need an additional reference.
d3d11va->device = _device;
d3d11va->device->AddRef();
d3d11va->device_context = _context;
d3d11va->device_context->AddRef();
if (av_hwdevice_ctx_init(dctx_ref) < 0)
throw std::runtime_error("Failed to initialize AVHWDeviceContext.");
return dctx_ref;
}
std::shared_ptr<AVFrame> obsffmpeg::hwapi::d3d11_instance::allocate_frame(AVBufferRef* frames)
{
auto frame = std::shared_ptr<AVFrame>(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<AVFrame> frame)
{
ATL::CComPtr<IDXGIKeyedMutex> mutex;
ATL::CComPtr<ID3D11Texture2D> input;
if (FAILED(_device->OpenSharedResource(reinterpret_cast<HANDLE>(static_cast<uintptr_t>(handle)),
__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&input)))) {
throw std::runtime_error("Failed to open shared texture resource.");
}
if (FAILED(input->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&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<ID3D11Texture2D*>(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<AVFrame> obsffmpeg::hwapi::d3d11_instance::avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key, uint64_t* next_lock_key)
{
auto frame = this->allocate_frame(frames);
this->copy_from_obs(frames, handle, lock_key, next_lock_key, frame);
return frame;
}
+78
View File
@@ -0,0 +1,78 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// 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 <atlutil.h>
#include <d3d11.h>
#include <d3d11_1.h>
#include <dxgi.h>
#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<IDXGIFactory1> _dxgifactory;
public:
d3d11();
virtual ~d3d11();
virtual std::list<obsffmpeg::hwapi::device> enumerate_adapters() override;
virtual std::shared_ptr<obsffmpeg::hwapi::instance>
create(obsffmpeg::hwapi::device target) override;
};
class d3d11_instance : public ::obsffmpeg::hwapi::instance {
ATL::CComPtr<ID3D11Device> _device;
ATL::CComPtr<ID3D11DeviceContext> _context;
public:
d3d11_instance(ATL::CComPtr<ID3D11Device> device, ATL::CComPtr<ID3D11DeviceContext> context);
virtual ~d3d11_instance();
virtual AVBufferRef* create_device_context() override;
virtual std::shared_ptr<AVFrame> 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<AVFrame> frame) override;
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key,
uint64_t* next_lock_key) override;
};
} // namespace hwapi
} // namespace obsffmpeg
+3 -2
View File
@@ -35,7 +35,7 @@ extern "C" {
#pragma warning(pop) #pragma warning(pop)
} }
void obsffmpeg::ui::debug_handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*) {} void obsffmpeg::ui::debug_handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*, bool) {}
template<typename T> template<typename T>
std::string to_string(T value){}; std::string to_string(T value){};
@@ -64,7 +64,8 @@ std::string to_string(double_t value)
return std::string(buf.data(), buf.data() + buf.size()); return std::string(buf.data(), buf.data() + buf.size());
} }
void obsffmpeg::ui::debug_handler::get_properties(obs_properties_t*, const AVCodec* codec, AVCodecContext* context) void obsffmpeg::ui::debug_handler::get_properties(obs_properties_t*, const AVCodec* codec, AVCodecContext* context,
bool)
{ {
if (context) if (context)
return; return;
+3 -3
View File
@@ -26,11 +26,11 @@ namespace obsffmpeg {
namespace ui { namespace ui {
class debug_handler : public handler { class debug_handler : public handler {
public: public:
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context,
AVCodecContext* context) override; bool hw_encode) override;
virtual void get_properties(obs_properties_t* props, const AVCodec* codec, virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context, bool hw_encode) override;
virtual void update(obs_data_t* settings, const AVCodec* codec, virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context) override;
+18 -13
View File
@@ -23,26 +23,31 @@
void obsffmpeg::ui::handler::override_visible_name(const AVCodec*, std::string&) {} void obsffmpeg::ui::handler::override_visible_name(const AVCodec*, std::string&) {}
void obsffmpeg::ui::handler::override_info(obs_encoder_info* main, obs_encoder_info* fallback) {} void obsffmpeg::ui::handler::override_info(obs_encoder_info*, obs_encoder_info*) {}
void obsffmpeg::ui::handler::override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, void obsffmpeg::ui::handler::override_colorformat(AVPixelFormat&, obs_data_t*, const AVCodec*, AVCodecContext*) {}
const AVCodec* codec, AVCodecContext* context)
{}
void obsffmpeg::ui::handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {} void obsffmpeg::ui::handler::override_lag_in_frames(size_t&, obs_data_t*, const AVCodec*, AVCodecContext*) {}
void obsffmpeg::ui::handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context) {} void obsffmpeg::ui::handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*, bool) {}
void obsffmpeg::ui::handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {} void obsffmpeg::ui::handler::get_properties(obs_properties_t*, const AVCodec*, AVCodecContext*, bool) {}
void obsffmpeg::ui::handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {} obsffmpeg::hwapi::device obsffmpeg::ui::handler::find_hw_device(std::shared_ptr<obsffmpeg::hwapi::base>, const AVCodec*,
AVCodecContext*)
{
return obsffmpeg::hwapi::device();
}
void obsffmpeg::ui::handler::import_from_ffmpeg(const std::string ffmpeg, obs_data_t* settings, const AVCodec* codec, void obsffmpeg::ui::handler::update(obs_data_t*, const AVCodec*, AVCodecContext*) {}
AVCodecContext* context)
{}
std::string obsffmpeg::ui::handler::export_for_ffmpeg(obs_data_t* settings, const AVCodec* codec, void obsffmpeg::ui::handler::log_options(obs_data_t*, const AVCodec*, AVCodecContext*) {}
AVCodecContext* context)
void obsffmpeg::ui::handler::import_from_ffmpeg(const std::string, obs_data_t*, const AVCodec*, AVCodecContext*) {}
std::string obsffmpeg::ui::handler::export_for_ffmpeg(obs_data_t*, const AVCodec*, AVCodecContext*)
{ {
return std::string(); return std::string();
} }
void obsffmpeg::ui::handler::process_avpacket(AVPacket&, const AVCodec*, AVCodecContext*) {}
+11 -2
View File
@@ -22,6 +22,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include "hwapi/base.hpp"
extern "C" { extern "C" {
#include <obs.h> #include <obs.h>
@@ -46,10 +47,16 @@ namespace obsffmpeg {
virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings,
const AVCodec* codec, AVCodecContext* context); const AVCodec* codec, AVCodecContext* context);
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); virtual void override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context);
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode);
virtual void get_properties(obs_properties_t* props, const AVCodec* codec, virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context); AVCodecContext* context, bool hw_encode);
virtual obsffmpeg::hwapi::device find_hw_device(std::shared_ptr<obsffmpeg::hwapi::base> api,
const AVCodec* codec, AVCodecContext* context);
virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
@@ -60,6 +67,8 @@ namespace obsffmpeg {
virtual std::string export_for_ffmpeg(obs_data_t* settings, const AVCodec* codec, virtual std::string export_for_ffmpeg(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context); AVCodecContext* context);
virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context);
}; };
} // namespace ui } // namespace ui
} // namespace obsffmpeg } // namespace obsffmpeg
+8 -2
View File
@@ -85,8 +85,14 @@ void obsffmpeg::ui::nvenc_h264_handler::override_visible_name(const AVCodec*, st
name = "H.264/AVC Encoder (NVidia NVENC)"; name = "H.264/AVC Encoder (NVidia NVENC)";
} }
void obsffmpeg::ui::nvenc_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, void obsffmpeg::ui::nvenc_h264_handler::override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) AVCodecContext* context)
{
nvenc::override_lag_in_frames(lag, settings, codec, context);
}
void obsffmpeg::ui::nvenc_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context, bool)
{ {
nvenc::get_defaults(settings, codec, context); nvenc::get_defaults(settings, codec, context);
@@ -95,7 +101,7 @@ void obsffmpeg::ui::nvenc_h264_handler::get_defaults(obs_data_t* settings, const
} }
void obsffmpeg::ui::nvenc_h264_handler::get_properties(obs_properties_t* props, const AVCodec* codec, void obsffmpeg::ui::nvenc_h264_handler::get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) AVCodecContext* context, bool)
{ {
if (!context) { if (!context) {
this->get_encoder_properties(props, codec); this->get_encoder_properties(props, codec);
+5 -2
View File
@@ -36,11 +36,14 @@ namespace obsffmpeg {
public: public:
virtual void override_visible_name(const AVCodec* codec, std::string& name) override; virtual void override_visible_name(const AVCodec* codec, std::string& name) override;
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, virtual void override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; 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, virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context, bool hw_encode) override;
virtual void update(obs_data_t* settings, const AVCodec* codec, virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context) override;
+8 -2
View File
@@ -87,8 +87,14 @@ void obsffmpeg::ui::nvenc_hevc_handler::override_visible_name(const AVCodec*, st
name = "H.265/HEVC Encoder (NVidia NVENC)"; name = "H.265/HEVC Encoder (NVidia NVENC)";
} }
void obsffmpeg::ui::nvenc_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, void obsffmpeg::ui::nvenc_hevc_handler::override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) AVCodecContext* context)
{
nvenc::override_lag_in_frames(lag, settings, codec, context);
}
void obsffmpeg::ui::nvenc_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context, bool)
{ {
nvenc::get_defaults(settings, codec, context); nvenc::get_defaults(settings, codec, context);
@@ -98,7 +104,7 @@ void obsffmpeg::ui::nvenc_hevc_handler::get_defaults(obs_data_t* settings, const
} }
void obsffmpeg::ui::nvenc_hevc_handler::get_properties(obs_properties_t* props, const AVCodec* codec, void obsffmpeg::ui::nvenc_hevc_handler::get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) AVCodecContext* context, bool)
{ {
if (!context) { if (!context) {
this->get_encoder_properties(props, codec); this->get_encoder_properties(props, codec);
+5 -2
View File
@@ -36,11 +36,14 @@ namespace obsffmpeg {
public: public:
virtual void override_visible_name(const AVCodec* codec, std::string& name) override; virtual void override_visible_name(const AVCodec* codec, std::string& name) override;
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, virtual void override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; 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, virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context, bool hw_encode) override;
virtual void update(obs_data_t* settings, const AVCodec* codec, virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context) override;
+15 -1
View File
@@ -20,6 +20,7 @@
// SOFTWARE. // SOFTWARE.
#include "nvenc_shared.hpp" #include "nvenc_shared.hpp"
#include <algorithm>
#include "codecs/hevc.hpp" #include "codecs/hevc.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "strings.hpp" #include "strings.hpp"
@@ -131,6 +132,19 @@ std::map<b_ref_mode, std::string> obsffmpeg::nvenc::b_ref_mode_to_opt{
{b_ref_mode::MIDDLE, "middle"}, {b_ref_mode::MIDDLE, "middle"},
}; };
void obsffmpeg::nvenc::override_lag_in_frames(size_t& lag, obs_data_t*, const AVCodec*, AVCodecContext* context)
{
// With NVENC, the number of frames lagged before we get our first
// packet is determined by the number of b-frames. Threads, lookahead
// frames and various other settings are ignored.
// The minimum number of lagged frames is 1.
int64_t rcla = 0;
av_opt_get_int(context, "rc-lookahead", AV_OPT_SEARCH_CHILDREN, &rcla);
lag = static_cast<size_t>(std::max(1ll + static_cast<int64_t>(context->max_b_frames), rcla));
}
void obsffmpeg::nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*) void obsffmpeg::nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*)
{ {
obs_data_set_default_int(settings, ST_PRESET, static_cast<int64_t>(preset::DEFAULT)); obs_data_set_default_int(settings, ST_PRESET, static_cast<int64_t>(preset::DEFAULT));
@@ -678,7 +692,7 @@ void obsffmpeg::nvenc::log_options(obs_data_t* settings, const AVCodec* codec, A
bool cfg_rc_quality = obs_data_get_bool(settings, ST_RATECONTROL_QUALITY); bool cfg_rc_quality = obs_data_get_bool(settings, ST_RATECONTROL_QUALITY);
int64_t cfg_rc_quality_min = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MINIMUM); int64_t cfg_rc_quality_min = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MINIMUM);
int64_t cfg_rc_quality_max = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM); int64_t cfg_rc_quality_max = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM);
double_t cfg_rc_quality_tgt = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_TARGET); double_t cfg_rc_quality_tgt = obs_data_get_double(settings, ST_RATECONTROL_QUALITY_TARGET) / 100.0 * 51.0;
int64_t cfg_rc_qp_i = obs_data_get_int(settings, ST_RATECONTROL_QP_I); int64_t cfg_rc_qp_i = obs_data_get_int(settings, ST_RATECONTROL_QP_I);
int64_t cfg_rc_qp_p = obs_data_get_int(settings, ST_RATECONTROL_QP_P); int64_t cfg_rc_qp_p = obs_data_get_int(settings, ST_RATECONTROL_QP_P);
int64_t cfg_rc_qp_b = obs_data_get_int(settings, ST_RATECONTROL_QP_B); int64_t cfg_rc_qp_b = obs_data_get_int(settings, ST_RATECONTROL_QP_B);
+3
View File
@@ -75,6 +75,9 @@ namespace obsffmpeg {
extern std::map<b_ref_mode, std::string> b_ref_mode_to_opt; extern std::map<b_ref_mode, std::string> b_ref_mode_to_opt;
void override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context);
void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context); 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_pre(obs_properties_t* props, const AVCodec* codec);
+18 -4
View File
@@ -36,7 +36,7 @@ INITIALIZER(prores_aw_handler_init)
}; };
void obsffmpeg::ui::prores_aw_handler::override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, void obsffmpeg::ui::prores_aw_handler::override_colorformat(AVPixelFormat& target_format, obs_data_t* settings,
const AVCodec* codec, AVCodecContext* context) const AVCodec* codec, AVCodecContext*)
{ {
std::string profile = ""; std::string profile = "";
@@ -60,13 +60,13 @@ void obsffmpeg::ui::prores_aw_handler::override_colorformat(AVPixelFormat& targe
} }
} }
void obsffmpeg::ui::prores_aw_handler::get_defaults(obs_data_t* settings, const AVCodec*, AVCodecContext*) 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); obs_data_set_default_int(settings, P_PRORES_PROFILE, 0);
} }
void obsffmpeg::ui::prores_aw_handler::get_properties(obs_properties_t* props, const AVCodec* codec, void obsffmpeg::ui::prores_aw_handler::get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) AVCodecContext* context, bool)
{ {
if (!context) { if (!context) {
auto p = obs_properties_add_list(props, P_PRORES_PROFILE, TRANSLATE(P_PRORES_PROFILE), auto p = obs_properties_add_list(props, P_PRORES_PROFILE, TRANSLATE(P_PRORES_PROFILE),
@@ -105,10 +105,24 @@ void obsffmpeg::ui::prores_aw_handler::update(obs_data_t* settings, const AVCode
context->profile = static_cast<int>(obs_data_get_int(settings, P_PRORES_PROFILE)); context->profile = static_cast<int>(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) void obsffmpeg::ui::prores_aw_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext*)
{ {
for (auto ptr = codec->profiles; ptr->profile != FF_PROFILE_UNKNOWN; ptr++) { for (auto ptr = codec->profiles; ptr->profile != FF_PROFILE_UNKNOWN; ptr++) {
if (ptr->profile == static_cast<int>(obs_data_get_int(settings, P_PRORES_PROFILE))) if (ptr->profile == static_cast<int>(obs_data_get_int(settings, P_PRORES_PROFILE)))
PLOG_INFO("[%s] Profile: %s", codec->name, ptr->name); PLOG_INFO("[%s] Profile: %s", codec->name, ptr->name);
} }
} }
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);
}
+4 -2
View File
@@ -38,16 +38,18 @@ namespace obsffmpeg {
const AVCodec* codec, AVCodecContext* context) override; const AVCodec* codec, AVCodecContext* context) override;
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, virtual void get_defaults(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context, bool hw_encode) override;
virtual void get_properties(obs_properties_t* props, const AVCodec* codec, virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context, bool hw_encode) override;
virtual void update(obs_data_t* settings, const AVCodec* codec, virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context) override;
virtual void log_options(obs_data_t* settings, const AVCodec* codec, virtual void log_options(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override; AVCodecContext* context) override;
virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context) override;
}; };
} // namespace ui } // namespace ui
} // namespace obsffmpeg } // namespace obsffmpeg