Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18024aaf12 | |||
| b2b5cd8fad | |||
| b6e881b90f | |||
| bd60958b2f | |||
| f9ad87a56d | |||
| b07365cdc4 | |||
| f21cbe9aba | |||
| 403b43e77b | |||
| 58d8713369 | |||
| 2f8acc58cf | |||
| e4e76dae8f | |||
| 4cac28a8a3 | |||
| d0dc4be985 | |||
| 4836f9dda6 | |||
| 650b397ced | |||
| d3f7b15633 | |||
| 545dcd6d50 | |||
| 0461b20e1b | |||
| b3a6dbb1b4 | |||
| fe71944199 | |||
| cbd39a8c2a | |||
| 62eae3827b | |||
| 38e7639862 | |||
| 6bc1cb9c88 | |||
| c63900d575 | |||
| 9efda8af8d | |||
| 0c9764a15c | |||
| 5c5a235502 | |||
| 2ebf90ffd7 |
@@ -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
@@ -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}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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}}"
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
@@ -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
@@ -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(
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
+457
-282
@@ -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() {}
|
||||||
@@ -374,8 +415,10 @@ void obsffmpeg::encoder_factory::register_encoder()
|
|||||||
info.oei.type_data = this;
|
info.oei.type_data = this;
|
||||||
|
|
||||||
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_THREADS, 0);
|
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_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,21 +534,25 @@ 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 (avcodec_ptr->pix_fmts) {
|
if (!hw_encode) {
|
||||||
auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT, TRANSLATE(ST_FFMPEG_COLORFORMAT),
|
if (avcodec_ptr->pix_fmts) {
|
||||||
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT,
|
||||||
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_COLORFORMAT)));
|
TRANSLATE(ST_FFMPEG_COLORFORMAT), OBS_COMBO_TYPE_LIST,
|
||||||
obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC),
|
OBS_COMBO_FORMAT_INT);
|
||||||
static_cast<int64_t>(AV_PIX_FMT_NONE));
|
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_COLORFORMAT)));
|
||||||
for (auto ptr = avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
|
obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC),
|
||||||
obs_property_list_add_int(p, ffmpeg::tools::get_pixel_format_name(*ptr),
|
static_cast<int64_t>(AV_PIX_FMT_NONE));
|
||||||
static_cast<int64_t>(*ptr));
|
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<int64_t>(*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)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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,
|
auto p = obs_properties_add_list(grp, ST_FFMPEG_STANDARDCOMPLIANCE,
|
||||||
@@ -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,25 +621,23 @@ 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));
|
||||||
_context->colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace);
|
ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context);
|
||||||
_context->color_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range);
|
|
||||||
_context->pix_fmt = _pixfmt_target;
|
_context->pix_fmt = _pixfmt_target;
|
||||||
_context->field_order = AV_FIELD_PROGRESSIVE;
|
_context->field_order = AV_FIELD_PROGRESSIVE;
|
||||||
_context->time_base.num = voi->fps_den;
|
|
||||||
_context->time_base.den = voi->fps_num;
|
|
||||||
_context->ticks_per_frame = 1;
|
_context->ticks_per_frame = 1;
|
||||||
_context->sample_aspect_ratio.num = _context->sample_aspect_ratio.den = 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_size(_context->width, _context->height);
|
||||||
_swscale.set_source_color(_context->color_range, _context->colorspace);
|
_swscale.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
|
||||||
_swscale.set_source_full_range(voi->range == VIDEO_RANGE_FULL);
|
|
||||||
_swscale.set_source_format(_pixfmt_source);
|
_swscale.set_source_format(_pixfmt_source);
|
||||||
|
|
||||||
_swscale.set_target_size(_context->width, _context->height);
|
_swscale.set_target_size(_context->width, _context->height);
|
||||||
_swscale.set_target_color(_context->color_range, _context->colorspace);
|
_swscale.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
|
||||||
_swscale.set_target_full_range(voi->range == VIDEO_RANGE_FULL);
|
|
||||||
_swscale.set_target_format(_pixfmt_target);
|
_swscale.set_target_format(_pixfmt_target);
|
||||||
|
|
||||||
// Create Scaler
|
// Create Scaler
|
||||||
@@ -665,23 +652,167 @@ 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*)
|
||||||
const char* id = obs_encoder_get_id(_self);
|
{
|
||||||
PLOG_INFO("[%s] Initializing...", id);
|
// Initialize Video Encoding
|
||||||
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", id, _swscale.get_source_width(),
|
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);
|
||||||
|
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(),
|
||||||
_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()),
|
||||||
ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()),
|
ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()),
|
||||||
_swscale.is_source_full_range() ? "Full" : "Partial");
|
_swscale.is_source_full_range() ? "Full" : "Partial");
|
||||||
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", id, _swscale.get_target_width(),
|
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", _codec->name, _swscale.get_target_width(),
|
||||||
_swscale.get_target_height(),
|
_swscale.get_target_height(),
|
||||||
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", id, _context->time_base.num, _context->time_base.den,
|
|
||||||
_context->time_base.num / _context->time_base.den);
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
@@ -694,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()
|
||||||
@@ -720,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);
|
||||||
@@ -746,13 +866,36 @@ 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
|
||||||
obs_video_info ovi;
|
obs_video_info ovi;
|
||||||
@@ -778,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*) {}
|
||||||
@@ -859,18 +1002,21 @@ 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");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vframe->height = _context->height;
|
vframe->height = _context->height;
|
||||||
vframe->format = _context->pix_fmt;
|
vframe->format = _context->pix_fmt;
|
||||||
vframe->color_range = _context->color_range;
|
vframe->color_range = _context->color_range;
|
||||||
vframe->colorspace = _context->colorspace;
|
vframe->colorspace = _context->colorspace;
|
||||||
vframe->pts = frame->pts;
|
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())
|
if ((_swscale.is_source_full_range() == _swscale.is_target_full_range())
|
||||||
&& (_swscale.get_source_colorspace() == _swscale.get_target_colorspace())
|
&& (_swscale.get_source_colorspace() == _swscale.get_target_colorspace())
|
||||||
@@ -888,166 +1034,195 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send and receive frames.
|
if (!encode_avframe(vframe, packet, received_packet))
|
||||||
{
|
return false;
|
||||||
#ifdef _DEBUG
|
|
||||||
ScopeProfiler profile("loop");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool sent_frame = false;
|
|
||||||
bool recv_packet = false;
|
|
||||||
bool should_lag = (_lag_in_frames - _count_send_frames) <= 0;
|
|
||||||
|
|
||||||
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(vframe);
|
|
||||||
switch (res) {
|
|
||||||
case 0:
|
|
||||||
sent_frame = true;
|
|
||||||
vframe = 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 (vframe != nullptr) {
|
|
||||||
_frame_queue.push(vframe);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool obsffmpeg::encoder::video_encode_texture(uint32_t, int64_t, uint64_t, uint64_t*, encoder_packet*, bool*)
|
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)
|
||||||
{
|
{
|
||||||
return false;
|
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)
|
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);
|
int res = avcodec_receive_packet(_context, &_current_packet);
|
||||||
if (res == 0) {
|
if (res != 0) {
|
||||||
if (!_have_first_frame) {
|
return res;
|
||||||
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);
|
|
||||||
}
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
|
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
|
||||||
{
|
{
|
||||||
int res = avcodec_send_frame(_context, frame.get());
|
int res = avcodec_send_frame(_context, frame.get());
|
||||||
switch (res) {
|
if (res == 0) {
|
||||||
case 0:
|
push_used_frame(frame);
|
||||||
_frame_queue_used.push(frame);
|
|
||||||
_count_send_frames++;
|
|
||||||
case AVERROR(EAGAIN):
|
|
||||||
case AVERROR(EOF):
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame> 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;
|
||||||
|
}
|
||||||
|
|||||||
+36
-10
@@ -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;
|
|
||||||
ffmpeg::swscale _swscale;
|
|
||||||
AVPacket _current_packet;
|
|
||||||
|
|
||||||
int64_t _lag_in_frames;
|
std::shared_ptr<obsffmpeg::hwapi::base> _hwapi;
|
||||||
int64_t _count_send_frames;
|
std::shared_ptr<obsffmpeg::hwapi::instance> _hwinst;
|
||||||
|
|
||||||
|
ffmpeg::swscale _swscale;
|
||||||
|
AVPacket _current_packet;
|
||||||
|
|
||||||
|
size_t _lag_in_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
|
||||||
|
|||||||
+28
-95
@@ -220,10 +220,10 @@ AVColorRange ffmpeg::tools::obs_videorangetype_to_avcolorrange(video_range_type
|
|||||||
{
|
{
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case VIDEO_RANGE_DEFAULT:
|
case VIDEO_RANGE_DEFAULT:
|
||||||
case VIDEO_RANGE_FULL:
|
|
||||||
return AVCOL_RANGE_JPEG;
|
|
||||||
case VIDEO_RANGE_PARTIAL:
|
case VIDEO_RANGE_PARTIAL:
|
||||||
return AVCOL_RANGE_MPEG;
|
return AVCOL_RANGE_MPEG;
|
||||||
|
case VIDEO_RANGE_FULL:
|
||||||
|
return AVCOL_RANGE_JPEG;
|
||||||
}
|
}
|
||||||
throw std::invalid_argument("unknown range");
|
throw std::invalid_argument("unknown range");
|
||||||
}
|
}
|
||||||
@@ -279,102 +279,35 @@ 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 = {
|
void ffmpeg::tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context)
|
||||||
{{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();
|
std::map<video_colorspace, std::tuple<AVColorSpace, AVColorPrimaries, AVColorTransferCharacteristic>>
|
||||||
AVPixelFormat best = source;
|
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<video_range_type, AVColorRange> colorranges = {
|
||||||
|
{VIDEO_RANGE_DEFAULT, AVCOL_RANGE_MPEG},
|
||||||
|
{VIDEO_RANGE_PARTIAL, AVCOL_RANGE_MPEG},
|
||||||
|
{VIDEO_RANGE_FULL, AVCOL_RANGE_JPEG},
|
||||||
|
};
|
||||||
|
|
||||||
for (auto fmt = list; fmt && (*fmt != AV_PIX_FMT_NONE); fmt++) {
|
{
|
||||||
auto found = format_compatibility.find(std::pair{source, *fmt});
|
auto found = colorspaces.find(colorspace);
|
||||||
if (found != format_compatibility.end()) {
|
if (found != colorspaces.end()) {
|
||||||
score = found->second;
|
context->colorspace = std::get<AVColorSpace>(found->second);
|
||||||
best = *fmt;
|
context->color_primaries = std::get<AVColorPrimaries>(found->second);
|
||||||
|
context->color_trc = std::get<AVColorTransferCharacteristic>(found->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto found = colorranges.find(range);
|
||||||
|
if (found != colorranges.end()) {
|
||||||
|
context->color_range = found->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score <= 0) {
|
// Downscaling should result in downscaling, not pixelation
|
||||||
int data_loss = 0;
|
context->chroma_sample_location = AVCHROMA_LOC_CENTER;
|
||||||
return avcodec_find_best_pix_fmt_of_list(list, source, 0, &data_loss);
|
|
||||||
}
|
|
||||||
|
|
||||||
return best;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavutil/pixfmt.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/pixfmt.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ffmpeg {
|
namespace ffmpeg {
|
||||||
@@ -56,7 +56,7 @@ 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);
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
} // namespace ffmpeg
|
} // namespace ffmpeg
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+21
-10
@@ -23,20 +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::log_options(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::import_from_ffmpeg(const std::string ffmpeg, obs_data_t* settings, const AVCodec* codec,
|
void obsffmpeg::ui::handler::get_defaults(obs_data_t*, const AVCodec*, AVCodecContext*, bool) {}
|
||||||
AVCodecContext* context)
|
|
||||||
{}
|
|
||||||
|
|
||||||
std::string obsffmpeg::ui::handler::export_for_ffmpeg(obs_data_t* settings, const AVCodec* codec,
|
void obsffmpeg::ui::handler::get_properties(obs_properties_t*, const AVCodec*, AVCodecContext*, bool) {}
|
||||||
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::update(obs_data_t*, const AVCodec*, AVCodecContext*) {}
|
||||||
|
|
||||||
|
void obsffmpeg::ui::handler::log_options(obs_data_t*, const AVCodec*, AVCodecContext*) {}
|
||||||
|
|
||||||
|
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*) {}
|
||||||
|
|||||||
+12
-4
@@ -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,13 +47,18 @@ 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,
|
virtual void override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
|
||||||
AVCodecContext* context) = 0;
|
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) = 0;
|
AVCodecContext* context, bool hw_encode);
|
||||||
|
|
||||||
virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) = 0;
|
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 log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
|
virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
|
||||||
|
|
||||||
@@ -61,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
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Useless except for strict_gop maybe?
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
using namespace obsffmpeg::codecs::h264;
|
using namespace obsffmpeg::codecs::h264;
|
||||||
|
using namespace obsffmpeg::nvenc;
|
||||||
|
|
||||||
std::map<profile, std::string> profiles{
|
std::map<profile, std::string> profiles{
|
||||||
{profile::BASELINE, "baseline"},
|
{profile::BASELINE, "baseline"},
|
||||||
@@ -84,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::override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
|
||||||
|
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,
|
void obsffmpeg::ui::nvenc_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec,
|
||||||
AVCodecContext* context)
|
AVCodecContext* context, bool)
|
||||||
{
|
{
|
||||||
nvenc::get_defaults(settings, codec, context);
|
nvenc::get_defaults(settings, codec, context);
|
||||||
|
|
||||||
@@ -94,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);
|
||||||
@@ -125,6 +132,22 @@ void obsffmpeg::ui::nvenc_h264_handler::update(obs_data_t* settings, const AVCod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void obsffmpeg::ui::nvenc_h264_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
|
||||||
|
{
|
||||||
|
nvenc::log_options(settings, codec, context);
|
||||||
|
|
||||||
|
profile cfg_profile = static_cast<profile>(obs_data_get_int(settings, P_H264_PROFILE));
|
||||||
|
level cfg_level = static_cast<level>(obs_data_get_int(settings, P_H264_LEVEL));
|
||||||
|
|
||||||
|
auto found1 = profiles.find(cfg_profile);
|
||||||
|
if (found1 != profiles.end())
|
||||||
|
PLOG_INFO("[%s] H.264 Profile: %s", codec->name, found1->second.c_str());
|
||||||
|
|
||||||
|
auto found2 = levels.find(cfg_level);
|
||||||
|
if (found2 != levels.end())
|
||||||
|
PLOG_INFO("[%s] H.264 Level: %s", codec->name, found2->second.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void obsffmpeg::ui::nvenc_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
|
void obsffmpeg::ui::nvenc_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
|
||||||
{
|
{
|
||||||
nvenc::get_properties_pre(props, codec);
|
nvenc::get_properties_pre(props, codec);
|
||||||
|
|||||||
@@ -36,15 +36,21 @@ 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;
|
||||||
|
|
||||||
|
virtual void log_options(obs_data_t* settings, const AVCodec* codec,
|
||||||
|
AVCodecContext* context) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
|
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
|
||||||
|
|
||||||
|
|||||||
@@ -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::override_lag_in_frames(size_t& lag, obs_data_t* settings, const AVCodec* codec,
|
||||||
|
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,
|
void obsffmpeg::ui::nvenc_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec,
|
||||||
AVCodecContext* context)
|
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);
|
||||||
@@ -133,6 +139,27 @@ void obsffmpeg::ui::nvenc_hevc_handler::update(obs_data_t* settings, const AVCod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void obsffmpeg::ui::nvenc_hevc_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
|
||||||
|
{
|
||||||
|
nvenc::log_options(settings, codec, context);
|
||||||
|
|
||||||
|
profile cfg_profile = static_cast<profile>(obs_data_get_int(settings, P_HEVC_PROFILE));
|
||||||
|
tier cfg_tier = static_cast<tier>(obs_data_get_int(settings, P_HEVC_TIER));
|
||||||
|
level cfg_level = static_cast<level>(obs_data_get_int(settings, P_HEVC_LEVEL));
|
||||||
|
|
||||||
|
auto found1 = profiles.find(cfg_profile);
|
||||||
|
if (found1 != profiles.end())
|
||||||
|
PLOG_INFO("[%s] H.265 Profile: %s", codec->name, found1->second.c_str());
|
||||||
|
|
||||||
|
auto found2 = levels.find(cfg_level);
|
||||||
|
if (found2 != levels.end())
|
||||||
|
PLOG_INFO("[%s] H.265 Level: %s", codec->name, found2->second.c_str());
|
||||||
|
|
||||||
|
auto found3 = tiers.find(cfg_tier);
|
||||||
|
if (found3 != tiers.end())
|
||||||
|
PLOG_INFO("[%s] H.265 Tier: %s", codec->name, found3->second.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void obsffmpeg::ui::nvenc_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
|
void obsffmpeg::ui::nvenc_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
|
||||||
{
|
{
|
||||||
nvenc::get_properties_pre(props, codec);
|
nvenc::get_properties_pre(props, codec);
|
||||||
|
|||||||
@@ -36,15 +36,21 @@ 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;
|
||||||
|
|
||||||
|
virtual void log_options(obs_data_t* settings, const AVCodec* codec,
|
||||||
|
AVCodecContext* context) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
|
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
|
||||||
|
|
||||||
|
|||||||
+132
-8
@@ -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"
|
||||||
@@ -57,7 +58,7 @@ extern "C" {
|
|||||||
#define ST_RATECONTROL_QP_I ST_RATECONTROL_QP ".I"
|
#define ST_RATECONTROL_QP_I ST_RATECONTROL_QP ".I"
|
||||||
#define ST_RATECONTROL_QP_I_INITIAL ST_RATECONTROL_QP_I ".Initial"
|
#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 ST_RATECONTROL_QP ".P"
|
||||||
#define ST_RATECONTROL_QP_ST_INITIAL ST_RATECONTROL_QP_P ".Initial"
|
#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 ST_RATECONTROL_QP ".B"
|
||||||
#define ST_RATECONTROL_QP_B_INITIAL ST_RATECONTROL_QP_B ".Initial"
|
#define ST_RATECONTROL_QP_B_INITIAL ST_RATECONTROL_QP_B ".Initial"
|
||||||
|
|
||||||
@@ -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));
|
||||||
@@ -152,7 +166,7 @@ void obsffmpeg::nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCode
|
|||||||
obs_data_set_default_int(settings, ST_RATECONTROL_QP_I, 21);
|
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_I_INITIAL, -1);
|
||||||
obs_data_set_default_int(settings, ST_RATECONTROL_QP_P, 21);
|
obs_data_set_default_int(settings, ST_RATECONTROL_QP_P, 21);
|
||||||
obs_data_set_default_int(settings, ST_RATECONTROL_QP_ST_INITIAL, -1);
|
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, 21);
|
||||||
obs_data_set_default_int(settings, ST_RATECONTROL_QP_B_INITIAL, -1);
|
obs_data_set_default_int(settings, ST_RATECONTROL_QP_B_INITIAL, -1);
|
||||||
|
|
||||||
@@ -211,7 +225,7 @@ static bool modified_ratecontrol(obs_properties_t* props, obs_property_t*, obs_d
|
|||||||
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_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_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_I_INITIAL), have_qp_init);
|
||||||
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_ST_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);
|
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), have_qp_init);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -379,9 +393,9 @@ void obsffmpeg::nvenc::get_properties_post(obs_properties_t* props, const AVCode
|
|||||||
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P)));
|
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_ST_INITIAL,
|
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_P_INITIAL,
|
||||||
TRANSLATE(ST_RATECONTROL_QP_ST_INITIAL), -1, 51, 1);
|
TRANSLATE(ST_RATECONTROL_QP_P_INITIAL), -1, 51, 1);
|
||||||
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_ST_INITIAL)));
|
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),
|
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_B, TRANSLATE(ST_RATECONTROL_QP_B),
|
||||||
@@ -483,7 +497,7 @@ void obsffmpeg::nvenc::get_runtime_properties(obs_properties_t* props, const AVC
|
|||||||
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), 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_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), false);
|
||||||
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_ST_INITIAL), 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), false);
|
||||||
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), 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), false);
|
||||||
@@ -597,7 +611,7 @@ void obsffmpeg::nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCode
|
|||||||
av_opt_set_int(context->priv_data, "init_qpI",
|
av_opt_set_int(context->priv_data, "init_qpI",
|
||||||
obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL), 0);
|
obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL), 0);
|
||||||
av_opt_set_int(context->priv_data, "init_qpP",
|
av_opt_set_int(context->priv_data, "init_qpP",
|
||||||
obs_data_get_int(settings, ST_RATECONTROL_QP_ST_INITIAL), 0);
|
obs_data_get_int(settings, ST_RATECONTROL_QP_P_INITIAL), 0);
|
||||||
av_opt_set_int(context->priv_data, "init_qpB",
|
av_opt_set_int(context->priv_data, "init_qpB",
|
||||||
obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL), 0);
|
obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL), 0);
|
||||||
}
|
}
|
||||||
@@ -646,3 +660,113 @@ void obsffmpeg::nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void obsffmpeg::nvenc::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
|
||||||
|
{
|
||||||
|
preset cfg_preset = static_cast<preset>(obs_data_get_int(settings, ST_PRESET));
|
||||||
|
|
||||||
|
auto found1 = preset_to_opt.find(cfg_preset);
|
||||||
|
if (found1 != preset_to_opt.end())
|
||||||
|
PLOG_INFO("[%s] Preset: %s", codec->name, found1->second.c_str());
|
||||||
|
|
||||||
|
ratecontrolmode cfg_rc_mode = static_cast<ratecontrolmode>(obs_data_get_int(settings, ST_RATECONTROL_MODE));
|
||||||
|
int64_t cfg_rc_2pass = obs_data_get_int(settings, ST_RATECONTROL_TWOPASS);
|
||||||
|
int64_t cfg_rc_lahead = obs_data_get_int(settings, ST_RATECONTROL_LOOKAHEAD);
|
||||||
|
bool cfg_rc_adapti = obs_data_get_bool(settings, ST_RATECONTROL_ADAPTIVEI);
|
||||||
|
bool cfg_rc_adaptb = obs_data_get_bool(settings, ST_RATECONTROL_ADAPTIVEB);
|
||||||
|
|
||||||
|
auto found2 = ratecontrolmode_to_opt.find(cfg_rc_mode);
|
||||||
|
if (found2 != ratecontrolmode_to_opt.end())
|
||||||
|
PLOG_INFO("[%s] Rate Control: %s", codec->name, found2->second.c_str());
|
||||||
|
PLOG_INFO("[%s] Two Pass: %s", codec->name,
|
||||||
|
cfg_rc_2pass == 1 ? "Enabled" : (cfg_rc_2pass == 0 ? "Disabled" : "Default"));
|
||||||
|
PLOG_INFO("[%s] Lookahead: %" PRId64 " Frames", codec->name, cfg_rc_lahead);
|
||||||
|
if (cfg_rc_adapti && cfg_rc_lahead > 0)
|
||||||
|
PLOG_INFO("[%s] Adaptive I-Frames Enabled", codec->name);
|
||||||
|
if (cfg_rc_adaptb && cfg_rc_lahead > 0)
|
||||||
|
PLOG_INFO("[%s] Adaptive B-Frames Enabled", codec->name);
|
||||||
|
|
||||||
|
int64_t cfg_rc_bitrate = obs_data_get_int(settings, ST_RATECONTROL_BITRATE_TARGET);
|
||||||
|
int64_t cfg_rc_max_bitrate = obs_data_get_int(settings, ST_RATECONTROL_BITRATE_MAXIMUM);
|
||||||
|
int64_t cfg_rc_bufsize = obs_data_get_int(settings, S_RATECONTROL_BUFFERSIZE);
|
||||||
|
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_max = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM);
|
||||||
|
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_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_i_init = obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL);
|
||||||
|
int64_t cfg_rc_qp_p_init = obs_data_get_int(settings, ST_RATECONTROL_QP_P_INITIAL);
|
||||||
|
int64_t cfg_rc_qp_b_init = obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL);
|
||||||
|
|
||||||
|
{
|
||||||
|
bool have_bitrate = false;
|
||||||
|
bool have_bitrate_max = false;
|
||||||
|
bool have_quality = false;
|
||||||
|
bool have_qp = false;
|
||||||
|
bool have_qp_init = false;
|
||||||
|
|
||||||
|
switch (cfg_rc_mode) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOG_INFO("[%s] Buffer Size: %" PRId64, codec->name, cfg_rc_bufsize);
|
||||||
|
if (have_bitrate)
|
||||||
|
PLOG_INFO("[%s] Bitrate Target: %" PRId64, codec->name, cfg_rc_bitrate);
|
||||||
|
if (have_bitrate_max)
|
||||||
|
PLOG_INFO("[%s] Bitrate Maximum: %" PRId64, codec->name, cfg_rc_max_bitrate);
|
||||||
|
if (have_quality && cfg_rc_quality) {
|
||||||
|
PLOG_INFO("[%s] Quality Limits:", codec->name);
|
||||||
|
PLOG_INFO("[%s] Minimum: %" PRId64, codec->name, cfg_rc_quality_min);
|
||||||
|
PLOG_INFO("[%s] Maximum: %" PRId64, codec->name, cfg_rc_quality_max);
|
||||||
|
PLOG_INFO("[%s] Target: %f", codec->name, cfg_rc_quality_tgt);
|
||||||
|
}
|
||||||
|
if (have_qp)
|
||||||
|
PLOG_INFO("[%s] QP Values: %" PRId64 "/%" PRId64 "/%" PRId64, codec->name, cfg_rc_qp_i,
|
||||||
|
cfg_rc_qp_p, cfg_rc_qp_b);
|
||||||
|
if (have_qp_init)
|
||||||
|
PLOG_INFO("[%s] Initial QP Values: %" PRId64 "/%" PRId64 "/%" PRId64, codec->name,
|
||||||
|
cfg_rc_qp_i_init, cfg_rc_qp_p_init, cfg_rc_qp_b_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cfg_aq_spatial = obs_data_get_bool(settings, ST_AQ_SPATIAL);
|
||||||
|
int64_t cfg_aq_strength = obs_data_get_int(settings, ST_AQ_STRENGTH);
|
||||||
|
bool cfg_aq_temporal = obs_data_get_bool(settings, ST_AQ_TEMPORAL);
|
||||||
|
if (cfg_aq_spatial)
|
||||||
|
PLOG_INFO("[%s] Spatial AQ Enabled: Strength %" PRId64, codec->name, cfg_aq_strength);
|
||||||
|
if (cfg_aq_temporal)
|
||||||
|
PLOG_INFO("[%s] Temporal AQ Enabled", codec->name);
|
||||||
|
|
||||||
|
int64_t cfg_bf = obs_data_get_int(settings, ST_OTHER_BFRAMES);
|
||||||
|
b_ref_mode cfg_bf_mode = static_cast<b_ref_mode>(obs_data_get_int(settings, ST_OTHER_BFRAME_REFERENCEMODE));
|
||||||
|
bool cfg_zerolatency = obs_data_get_bool(settings, ST_OTHER_ZEROLATENCY);
|
||||||
|
bool cfg_weightp = obs_data_get_bool(settings, ST_OTHER_WEIGHTED_PREDICTION);
|
||||||
|
bool cfg_nonrefp = obs_data_get_bool(settings, ST_OTHER_NONREFERENCE_PFRAMES);
|
||||||
|
|
||||||
|
PLOG_INFO("[%s] B-Frames: %" PRId64, codec->name, cfg_bf);
|
||||||
|
auto found3 = b_ref_mode_to_opt.find(cfg_bf_mode);
|
||||||
|
if (found3 != b_ref_mode_to_opt.end())
|
||||||
|
PLOG_INFO("[%s] Reference Mode: %s", codec->name, found3->second.c_str());
|
||||||
|
if (cfg_zerolatency)
|
||||||
|
PLOG_INFO("[%s] Zero Latency Enabled", codec->name);
|
||||||
|
if (cfg_weightp)
|
||||||
|
PLOG_INFO("[%s] Weighted Prediction Enabled", codec->name);
|
||||||
|
if (cfg_nonrefp)
|
||||||
|
PLOG_INFO("[%s] Non-Ref P-Frames Enabled", codec->name);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -84,5 +87,7 @@ namespace obsffmpeg {
|
|||||||
void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context);
|
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 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 nvenc
|
||||||
} // namespace obsffmpeg
|
} // namespace obsffmpeg
|
||||||
|
|||||||
@@ -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),
|
||||||
@@ -104,3 +104,25 @@ 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*)
|
||||||
|
{
|
||||||
|
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)))
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,13 +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,
|
||||||
|
AVCodecContext* context) override;
|
||||||
|
|
||||||
|
virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context) override;
|
||||||
};
|
};
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace obsffmpeg
|
} // namespace obsffmpeg
|
||||||
|
|||||||
Reference in New Issue
Block a user