diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b2d70b1d..7297e698 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -83,14 +83,15 @@ jobs: sudo apt-get purge libjpeg9-dev:amd64 libjpeg8-dev:amd64 libjpeg-turbo8-dev:amd64 sudo apt-get install \ build-essential \ + checkinstall \ + pkg-config \ cmake \ ninja-build \ git \ + gcc-9 g++9 \ qt5-default libqwt-qt5-dev libqt5svg5-dev \ libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev \ - libcurl4-openssl-dev \ - gcc-9 g++9 \ - checkinstall pkg-config + libcurl4-openssl-dev sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 800 --slave /usr/bin/g++ g++ /usr/bin/g++-9 - name: "libobs: Cache" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 919d4382..5182bed1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,7 @@ env: QT_VERSION: "5.15.2" OBS_VERSION: "27.0.0-ci" OBSDEPS_VERSION: "27.0.0" + LIBAOM_VERSION: "3.1.2.115" jobs: windows: @@ -64,6 +65,20 @@ jobs: build/temp/qt-build build/temp/qt-src key: qt-${{ matrix.id }}-${{ env.OBSDEPS_VERSION }}-${{ env.QT_VERSION }}-${{ secrets.CACHE_VERSION }} + - name: "libaom: Cache" + uses: actions/cache@v2 + id: libaom-cache + with: + path: | + build/libaom + key: libaom-${{ matrix.id }}-${{ env.LIBAOM_VERSION }}-${{ secrets.CACHE_VERSION }} + - name: "libaom: Install" + if: "steps.libaom-cache.outputs.cache-hit != 'true'" + id: libaom-install + shell: bash + run: | + curl -L -o "aom.7z" "https://github.com/Xaymar/aom/releases/download/v${{ env.LIBAOM_VERSION }}/aom-windows-64-shared.7z" + 7z x -o"build/libaom/" "aom.7z" - name: "StreamFX: Configure" shell: bash run: | @@ -72,13 +87,14 @@ jobs: -DCMAKE_INSTALL_PREFIX="build/distrib" \ -DPACKAGE_NAME="streamfx-${{ matrix.id }}" \ -DPACKAGE_PREFIX="build/package" \ - -DDOWNLOAD_QT=ON \ -DDOWNLOAD_OBS_URL="https://github.com/Xaymar/obs-studio/releases/download/${{ env.OBS_VERSION }}/obs-studio-x64-0.0.0.0-windows-x86-64.7z" \ -DDOWNLOAD_OBS_HASH="SHA256=EBF9853C8A553E16ECBCA22523F401E6CF1EB2E8DA93F1493FEF41D65BD06633" \ -DDOWNLOAD_OBSDEPS_URL="https://github.com/Xaymar/obs-studio/releases/download/${{ env.OBSDEPS_VERSION }}/deps-windows-x86.7z" \ -DDOWNLOAD_OBSDEPS_HASH="SHA256=B4AED165016F0B64A7E8B256CCC12EAF8AF087F61B0B239B9D3D00277485B5B5" \ + -DDOWNLOAD_QT=ON \ -DDOWNLOAD_QT_URL="https://github.com/Xaymar/obs-studio/releases/download/${{ env.OBSDEPS_VERSION }}/qt-${{ env.QT_VERSION }}-windows-x86-64.7z" \ - -DDOWNLOAD_QT_HASH="SHA256=109B9C21EF165B0C46DFAA9AD23124F2070ED4D74207C4AFB308183CB8D43BDD" + -DDOWNLOAD_QT_HASH="SHA256=109B9C21EF165B0C46DFAA9AD23124F2070ED4D74207C4AFB308183CB8D43BDD" \ + -DAOM_PATH="build/libaom/" - name: "StreamFX: Build" shell: bash run: | @@ -101,6 +117,7 @@ jobs: with: name: ${{ matrix.id }} path: build/package + ubuntu: name: "Ubuntu 64-bit" strategy: @@ -142,14 +159,15 @@ jobs: sudo apt-get purge libjpeg9-dev:amd64 libjpeg8-dev:amd64 libjpeg-turbo8-dev:amd64 sudo apt-get install \ build-essential \ + checkinstall \ + pkg-config \ cmake \ ninja-build \ git \ + ${{ matrix.packages }} \ qt5-default libqwt-qt5-dev libqt5svg5-dev \ libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev \ - libcurl4-openssl-dev \ - ${{ matrix.packages }} \ - checkinstall pkg-config + libcurl4-openssl-dev ${{ matrix.extra_command }} - name: "libobs: Cache" uses: actions/cache@v2 @@ -185,6 +203,7 @@ jobs: with: name: ${{ matrix.id }} path: build/package + macos: strategy: fail-fast: true diff --git a/CMakeLists.txt b/CMakeLists.txt index d35b2146..edeacc47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,6 +294,7 @@ set(${PREFIX}ENABLE_ENCODER_FFMPEG ON CACHE BOOL "Enable FFmpeg Encoder integrat set(${PREFIX}ENABLE_ENCODER_FFMPEG_AMF ON CACHE BOOL "Enable AMF Encoder in FFmpeg.") set(${PREFIX}ENABLE_ENCODER_FFMPEG_NVENC ON CACHE BOOL "Enable NVENC Encoder in FFmpeg.") set(${PREFIX}ENABLE_ENCODER_FFMPEG_PRORES ON CACHE BOOL "Enable ProRes Encoder in FFmpeg.") +set(${PREFIX}ENABLE_ENCODER_AOM_AV1 ON CACHE BOOL "Enable AOM AV1 Encoder.") ## Filters set(${PREFIX}ENABLE_FILTER_BLUR ON CACHE BOOL "Enable Blur Filter") @@ -636,6 +637,18 @@ function(feature_encoder_ffmpeg RESOLVE) endif() endfunction() +function(feature_encoder_aom_av1 RESOLVE) + is_feature_enabled(ENCODER_AOM_AV1 T_CHECK) + if(RESOLVE AND T_CHECK) + if(NOT HAVE_AOM) + message(WARNING "${LOGPREFIX}: AOM AV1 encoder missing AOM library. Disabling...") + set_feature_disabled(ENCODER_AOM_AV1 ON) + endif() + elseif(T_CHECK) + set(REQUIRE_AOM ON PARENT_SCOPE) + endif() +endfunction() + function(feature_filter_blur RESOLVE) is_feature_enabled(FILTER_BLUR T_CHECK) endfunction() @@ -749,6 +762,7 @@ endfunction() # Set Requirements feature_encoder_ffmpeg(OFF) +feature_encoder_aom_av1(OFF) feature_filter_blur(OFF) feature_filter_color_grade(OFF) feature_filter_displacement(OFF) @@ -821,6 +835,15 @@ if(REQUIRE_FFMPEG) set(HAVE_FFMPEG ${FFmpeg_FOUND}) endif() +#- AOM +set(HAVE_AOM OFF) +if(REQUIRE_AOM) + if(NOT D_PLATFORM_MAC) + find_package(AOM) + set(HAVE_AOM ${AOM_FOUND}) + endif() +endif() + #- JSON set(HAVE_JSON OFF) if(REQUIRE_JSON) @@ -894,6 +917,7 @@ endif() # Verify Requirements feature_encoder_ffmpeg(ON) +feature_encoder_aom_av1(ON) feature_filter_blur(ON) feature_filter_color_grade(ON) feature_filter_displacement(ON) @@ -1215,6 +1239,23 @@ if(T_CHECK) endif() endif() +# Encoder/AOM-AV1 +is_feature_enabled(ENCODER_AOM_AV1 T_CHECK) +if(T_CHECK) + list (APPEND PROJECT_PRIVATE_SOURCE + "source/encoders/codecs/av1.hpp" + "source/encoders/codecs/av1.cpp" + "source/encoders/encoder-aom-av1.hpp" + "source/encoders/encoder-aom-av1.cpp" + ) + list (APPEND PROJECT_INCLUDE_DIRS + ${AOM_INCLUDE_DIR} + ) + list(APPEND PROJECT_DEFINITIONS + ENABLE_ENCODER_AOM_AV1 + ) +endif() + # Filter/Blur is_feature_enabled(FILTER_BLUR T_CHECK) if(T_CHECK) @@ -1740,6 +1781,15 @@ endif() if(${PREFIX}OBS_NATIVE) # Grouped builds don't offer standalone services. install_obs_plugin_with_data(${PROJECT_NAME} data) + + # Dependency: AOM + if(HAVE_AOM AND AOM_BINARY AND D_PLATFORM_WINDOWS) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "${AOM_BINARY}" + "${CMAKE_BINARY_DIR}/rundir/$/data/obs-plugins/${PROJECT_NAME}" + VERBATIM) + endif() else() if(STRUCTURE_UNIFIED) install( @@ -1764,6 +1814,14 @@ else() OPTIONAL ) endif() + + # Dependency: AOM + if(HAVE_AOM AND AOM_BINARY AND D_PLATFORM_WINDOWS) + install( + FILES ${AOM_BINARY} + DESTINATION "data/" COMPONENT StreamFX + ) + endif() elseif(D_PLATFORM_LINUX) install( TARGETS ${PROJECT_NAME} @@ -1807,6 +1865,14 @@ else() OPTIONAL ) endif() + + # Dependency: AOM + if(HAVE_AOM AND AOM_BINARY AND D_PLATFORM_WINDOWS) + install( + FILES ${AOM_BINARY} + DESTINATION "data/obs-plugins/${PROJECT_NAME}/" COMPONENT StreamFX + ) + endif() elseif(D_PLATFORM_LINUX) if(STRUCTURE_PACKAGEMANAGER) install( diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 17c52f49..be9d3ef3 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -67,6 +67,66 @@ UI.Updater.Menu.Channel="Update Channel" UI.Updater.Menu.Channel.Release="Release" UI.Updater.Menu.Channel.Testing="Testing" +# Encoder/AOM-AV1 +Encoder.AOM.AV1="AOM AV1 (direct)" +Encoder.AOM.AV1.Encoder="Encoder" +Encoder.AOM.AV1.Encoder.Usage="Usage" +Encoder.AOM.AV1.Encoder.Usage.GoodQuality="Good Quality" +Encoder.AOM.AV1.Encoder.Usage.RealTime="Real Time" +Encoder.AOM.AV1.Encoder.Usage.AllIntra="All Intra-Frame" +Encoder.AOM.AV1.Encoder.CPUUsage="CPU Usage" +Encoder.AOM.AV1.Encoder.CPUUsage.0="Placebo" +Encoder.AOM.AV1.Encoder.CPUUsage.1="Very Slow" +Encoder.AOM.AV1.Encoder.CPUUsage.2="Slower" +Encoder.AOM.AV1.Encoder.CPUUsage.3="Slow" +Encoder.AOM.AV1.Encoder.CPUUsage.4="Medium" +Encoder.AOM.AV1.Encoder.CPUUsage.5="Fast" +Encoder.AOM.AV1.Encoder.CPUUsage.6="Faster" +Encoder.AOM.AV1.Encoder.CPUUsage.7="Very Fast" +Encoder.AOM.AV1.Encoder.CPUUsage.8="Super Fast" +Encoder.AOM.AV1.Encoder.CPUUsage.9="Ultra Fast" +Encoder.AOM.AV1.Encoder.Profile="Profile" +Encoder.AOM.AV1.KeyFrames="Key-Frame" +Encoder.AOM.AV1.KeyFrames.IntervalType="Interval Type" +Encoder.AOM.AV1.KeyFrames.IntervalType.Frames="Frames" +Encoder.AOM.AV1.KeyFrames.IntervalType.Seconds="Seconds" +Encoder.AOM.AV1.KeyFrames.Interval="Interval" +Encoder.AOM.AV1.RateControl="Rate Control" +Encoder.AOM.AV1.RateControl.Mode="Mode" +Encoder.AOM.AV1.RateControl.Mode.CBR="Constant Bitrate (CBR)" +Encoder.AOM.AV1.RateControl.Mode.VBR="Variable Bitrate (VBR)" +Encoder.AOM.AV1.RateControl.Mode.CQ="Constrained Quality (CQ)" +Encoder.AOM.AV1.RateControl.Mode.Q="Constant Quality (Q)" +Encoder.AOM.AV1.RateControl.LookAhead="Look-Ahead" +Encoder.AOM.AV1.RateControl.Limits="Limits" +Encoder.AOM.AV1.RateControl.Limits.Bitrate="Bitrate" +Encoder.AOM.AV1.RateControl.Limits.Bitrate.Undershoot="Bitrate Undershoot" +Encoder.AOM.AV1.RateControl.Limits.Bitrate.Overshoot="Bitrate Overshoot" +Encoder.AOM.AV1.RateControl.Limits.Quality="Quality" +Encoder.AOM.AV1.RateControl.Limits.Quantizer.Minimum="Minimum Quantizer" +Encoder.AOM.AV1.RateControl.Limits.Quantizer.Maximum="Maximum Quantizer" +Encoder.AOM.AV1.RateControl.Buffer="Buffer" +Encoder.AOM.AV1.RateControl.Buffer.Size="Size" +Encoder.AOM.AV1.RateControl.Buffer.Size.Initial="Initial Size" +Encoder.AOM.AV1.RateControl.Buffer.Size.Optimal="Optimal Size" +Encoder.AOM.AV1.Advanced="Advanced" +Encoder.AOM.AV1.Advanced.Threads="Threads" +Encoder.AOM.AV1.Advanced.RowMultiThreading="Per-Row Multi-Threading" +Encoder.AOM.AV1.Advanced.Tile.Columns="Tile Columns" +Encoder.AOM.AV1.Advanced.Tile.Rows="Tile Rows" +Encoder.AOM.AV1.Advanced.Tune="Tune" +Encoder.AOM.AV1.Advanced.Tune.Metric="Metric" +Encoder.AOM.AV1.Advanced.Tune.Metric.PSNR="PSNR" +Encoder.AOM.AV1.Advanced.Tune.Metric.SSIM="SSIM" +Encoder.AOM.AV1.Advanced.Tune.Metric.VMAF.WithPreprocessing="VMAF (with pre-processing)" +Encoder.AOM.AV1.Advanced.Tune.Metric.VMAF.WithoutPreprocessing="VMAF (without pre-processing)" +Encoder.AOM.AV1.Advanced.Tune.Metric.VMAF.MaxGain="VMAF (NegMaxGain)" +Encoder.AOM.AV1.Advanced.Tune.Metric.VMAF.NegMaxGain="VMAF (MaxGain)" +Encoder.AOM.AV1.Advanced.Tune.Metric.Butteraugli="Butteraugli" +Encoder.AOM.AV1.Advanced.Tune.Content="Content" +Encoder.AOM.AV1.Advanced.Tune.Content.Screen="Screen" +Encoder.AOM.AV1.Advanced.Tune.Content.Film="Film" + # Blur Blur.Type.Box="Box" Blur.Type.BoxLinear="Box Linear" @@ -299,6 +359,13 @@ Source.Mirror.Source.Audio.Layout.QuadraphonicLFE="Quadraphonic With LFE" Source.Mirror.Source.Audio.Layout.Surround="Surround" Source.Mirror.Source.Audio.Layout.FullSurround="Full Surround" +# Codec: AV1 +Codec.AV1="AV1" +Codec.AV1.Profile="Profile" +Codec.AV1.Profile.Main="Main" +Codec.AV1.Profile.High="High" +Codec.AV1.Profile.Professional="Professional" + # Codec: H264 Codec.H264="H264" Codec.H264.Profile="Profile" diff --git a/source/encoders/codecs/av1.cpp b/source/encoders/codecs/av1.cpp new file mode 100644 index 00000000..48b5aa33 --- /dev/null +++ b/source/encoders/codecs/av1.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "av1.hpp" + +const char* streamfx::encoder::codec::av1::profile_to_string(profile p) +{ + switch (p) { + case profile::MAIN: + return D_TRANSLATE(S_CODEC_AV1_PROFILE ".Main"); + case profile::HIGH: + return D_TRANSLATE(S_CODEC_AV1_PROFILE ".High"); + case profile::PROFESSIONAL: + return D_TRANSLATE(S_CODEC_AV1_PROFILE ".Professional"); + default: + return "Unknown"; + } +} diff --git a/source/encoders/codecs/av1.hpp b/source/encoders/codecs/av1.hpp new file mode 100644 index 00000000..8a460d9b --- /dev/null +++ b/source/encoders/codecs/av1.hpp @@ -0,0 +1,36 @@ +// Copyright (c) 2019 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "common.hpp" + +#define S_CODEC_AV1 "Codec.AV1" +#define S_CODEC_AV1_PROFILE "Codec.AV1.Profile" + +namespace streamfx::encoder::codec::av1 { + enum class profile { + MAIN = 0, + HIGH = 1, + PROFESSIONAL = 2, + UNKNOWN = -1, + }; + + const char* profile_to_string(profile p); +} // namespace streamfx::encoder::codec::av1 diff --git a/source/encoders/encoder-aom-av1.cpp b/source/encoders/encoder-aom-av1.cpp new file mode 100644 index 00000000..5de2de5c --- /dev/null +++ b/source/encoders/encoder-aom-av1.cpp @@ -0,0 +1,1683 @@ +// Copyright (c) 2021 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "encoder-aom-av1.hpp" +#include +#include +#include "util/util-logging.hpp" + +#ifdef _DEBUG +#define ST_PREFIX "<%s> " +#define D_LOG_ERROR(x, ...) P_LOG_ERROR(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_WARNING(x, ...) P_LOG_WARN(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_INFO(x, ...) P_LOG_INFO(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_DEBUG(x, ...) P_LOG_DEBUG(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#else +#define ST_PREFIX " " +#define D_LOG_ERROR(...) P_LOG_ERROR(ST_PREFIX __VA_ARGS__) +#define D_LOG_WARNING(...) P_LOG_WARN(ST_PREFIX __VA_ARGS__) +#define D_LOG_INFO(...) P_LOG_INFO(ST_PREFIX __VA_ARGS__) +#define D_LOG_DEBUG(...) P_LOG_DEBUG(ST_PREFIX __VA_ARGS__) +#endif + +#define ST_I18N "Encoder.AOM.AV1" + +// Preset +#define ST_I18N_ENCODER ST_I18N ".Encoder" +#define ST_I18N_ENCODER_USAGE ST_I18N_ENCODER ".Usage" +#define ST_I18N_ENCODER_USAGE_GOODQUALITY ST_I18N_ENCODER_USAGE ".GoodQuality" +#define ST_I18N_ENCODER_USAGE_REALTIME ST_I18N_ENCODER_USAGE ".RealTime" +#define ST_I18N_ENCODER_USAGE_ALLINTRA ST_I18N_ENCODER_USAGE ".AllIntra" +#define ST_KEY_ENCODER_USAGE "Encoder.Usage" +#define ST_I18N_ENCODER_CPUUSAGE ST_I18N_ENCODER ".CPUUsage" +#define ST_I18N_ENCODER_CPUUSAGE_0 ST_I18N_ENCODER ".CPUUsage.0" +#define ST_I18N_ENCODER_CPUUSAGE_1 ST_I18N_ENCODER ".CPUUsage.1" +#define ST_I18N_ENCODER_CPUUSAGE_2 ST_I18N_ENCODER ".CPUUsage.2" +#define ST_I18N_ENCODER_CPUUSAGE_3 ST_I18N_ENCODER ".CPUUsage.3" +#define ST_I18N_ENCODER_CPUUSAGE_4 ST_I18N_ENCODER ".CPUUsage.4" +#define ST_I18N_ENCODER_CPUUSAGE_5 ST_I18N_ENCODER ".CPUUsage.5" +#define ST_I18N_ENCODER_CPUUSAGE_6 ST_I18N_ENCODER ".CPUUsage.6" +#define ST_I18N_ENCODER_CPUUSAGE_7 ST_I18N_ENCODER ".CPUUsage.7" +#define ST_I18N_ENCODER_CPUUSAGE_8 ST_I18N_ENCODER ".CPUUsage.8" +#define ST_I18N_ENCODER_CPUUSAGE_9 ST_I18N_ENCODER ".CPUUsage.9" +#define ST_KEY_ENCODER_CPUUSAGE "Encoder.CPUUsage" +#define ST_KEY_ENCODER_PROFILE "Encoder.Profile" + +// Rate Control +#define ST_I18N_RATECONTROL ST_I18N ".RateControl" +#define ST_I18N_RATECONTROL_MODE ST_I18N_RATECONTROL ".Mode" +#define ST_I18N_RATECONTROL_MODE_CBR ST_I18N_RATECONTROL_MODE ".CBR" +#define ST_I18N_RATECONTROL_MODE_VBR ST_I18N_RATECONTROL_MODE ".VBR" +#define ST_I18N_RATECONTROL_MODE_CQ ST_I18N_RATECONTROL_MODE ".CQ" +#define ST_I18N_RATECONTROL_MODE_Q ST_I18N_RATECONTROL_MODE ".Q" +#define ST_KEY_RATECONTROL_MODE "RateControl.Mode" +#define ST_I18N_RATECONTROL_LOOKAHEAD ST_I18N_RATECONTROL ".LookAhead" +#define ST_KEY_RATECONTROL_LOOKAHEAD "RateControl.LookAhead" +#define ST_I18N_RATECONTROL_LIMITS ST_I18N_RATECONTROL ".Limits" +#define ST_I18N_RATECONTROL_LIMITS_BITRATE ST_I18N_RATECONTROL_LIMITS ".Bitrate" +#define ST_KEY_RATECONTROL_LIMITS_BITRATE "RateControl.Limits.Bitrate" +#define ST_I18N_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT ST_I18N_RATECONTROL_LIMITS_BITRATE ".Undershoot" +#define ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT "RateControl.Limits.Bitrate.Undershoot" +#define ST_I18N_RATECONTROL_LIMITS_BITRATE_OVERSHOOT ST_I18N_RATECONTROL_LIMITS_BITRATE ".Overshoot" +#define ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT "RateControl.Limits.Bitrate.Overshoot" +#define ST_I18N_RATECONTROL_LIMITS_QUALITY ST_I18N_RATECONTROL_LIMITS ".Quality" +#define ST_KEY_RATECONTROL_LIMITS_QUALITY "RateControl.Limits.Quality" +#define ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MINIMUM ST_I18N_RATECONTROL_LIMITS ".Quantizer.Minimum" +#define ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM "RateControl.Limits.Quantizer.Minimum" +#define ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM ST_I18N_RATECONTROL_LIMITS ".Quantizer.Maximum" +#define ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM "RateControl.Limits.Quantizer.Maximum" +#define ST_I18N_RATECONTROL_BUFFER ST_I18N_RATECONTROL ".Buffer" +#define ST_I18N_RATECONTROL_BUFFER_SIZE ST_I18N_RATECONTROL_BUFFER ".Size" +#define ST_KEY_RATECONTROL_BUFFER_SIZE "RateControl.Buffer.Size" +#define ST_I18N_RATECONTROL_BUFFER_SIZE_INITIAL ST_I18N_RATECONTROL_BUFFER_SIZE ".Initial" +#define ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL "RateControl.Buffer.Size.Initial" +#define ST_I18N_RATECONTROL_BUFFER_SIZE_OPTIMAL ST_I18N_RATECONTROL_BUFFER_SIZE ".Optimal" +#define ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL "RateControl.Buffer.Size.Optimal" + +// Key-Frames +#define ST_I18N_KEYFRAMES ST_I18N ".KeyFrames" +#define ST_I18N_KEYFRAMES_INTERVALTYPE ST_I18N_KEYFRAMES ".IntervalType" +#define ST_I18N_KEYFRAMES_INTERVALTYPE_SECONDS ST_I18N_KEYFRAMES_INTERVALTYPE ".Seconds" +#define ST_I18N_KEYFRAMES_INTERVALTYPE_FRAMES ST_I18N_KEYFRAMES_INTERVALTYPE ".Frames" +#define ST_KEY_KEYFRAMES_INTERVALTYPE "KeyFrames.IntervalType" +#define ST_I18N_KEYFRAMES_INTERVAL ST_I18N_KEYFRAMES ".Interval" +#define ST_KEY_KEYFRAMES_INTERVAL_SECONDS "KeyFrames.Interval.Seconds" +#define ST_KEY_KEYFRAMES_INTERVAL_FRAMES "KeyFrames.Interval.Frames" + +// Advanced +#define ST_I18N_ADVANCED ST_I18N ".Advanced" +#define ST_I18N_ADVANCED_THREADS ST_I18N_ADVANCED ".Threads" +#define ST_KEY_ADVANCED_THREADS "Advanced.Threads" +#define ST_I18N_ADVANCED_ROWMULTITHREADING ST_I18N_ADVANCED ".RowMultiThreading" +#define ST_KEY_ADVANCED_ROWMULTITHREADING "Advanced.RowMultiThreading" +#define ST_I18N_ADVANCED_TILE_COLUMNS ST_I18N_ADVANCED ".Tile.Columns" +#define ST_KEY_ADVANCED_TILE_COLUMNS "Advanced.Tile.Columns" +#define ST_I18N_ADVANCED_TILE_ROWS ST_I18N_ADVANCED ".Tile.Rows" +#define ST_KEY_ADVANCED_TILE_ROWS "Advanced.Tile.Rows" +#define ST_I18N_ADVANCED_TUNE ST_I18N_ADVANCED ".Tune" +#define ST_I18N_ADVANCED_TUNE_METRIC ST_I18N_ADVANCED_TUNE ".Metric" +#define ST_I18N_ADVANCED_TUNE_METRIC_PSNR ST_I18N_ADVANCED_TUNE_METRIC ".PSNR" +#define ST_I18N_ADVANCED_TUNE_METRIC_SSIM ST_I18N_ADVANCED_TUNE_METRIC ".SSIM" +#define ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHPREPROCESSING ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.WithPreprocessing" +#define ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHOUTPREPROCESSING ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.WithoutPreProcessing" +#define ST_I18N_ADVANCED_TUNE_METRIC_VMAFMAXGAIN ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.MaxGain" +#define ST_I18N_ADVANCED_TUNE_METRIC_VMAFNEGMAXGAIN ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.NegMaxGain" +#define ST_I18N_ADVANCED_TUNE_METRIC_BUTTERAUGLI ST_I18N_ADVANCED_TUNE_METRIC ".Butteraugli" +#define ST_KEY_ADVANCED_TUNE_METRIC "Advanced.Tune.Metric" +#define ST_I18N_ADVANCED_TUNE_CONTENT ST_I18N_ADVANCED_TUNE ".Content" +#define ST_I18N_ADVANCED_TUNE_CONTENT_SCREEN ST_I18N_ADVANCED_TUNE_CONTENT ".Screen" +#define ST_I18N_ADVANCED_TUNE_CONTENT_FILM ST_I18N_ADVANCED_TUNE_CONTENT ".Film" +#define ST_KEY_ADVANCED_TUNE_CONTENT "Advanced.Tune.Content" + +using namespace streamfx::encoder::aom::av1; + +static constexpr std::string_view HELP_URL = "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-AOM-AV1"; + +const char* obs_video_format_to_string(video_format format) +{ + switch (format) { + case VIDEO_FORMAT_I420: + return "I420"; + case VIDEO_FORMAT_NV12: + return "NV12"; + case VIDEO_FORMAT_YVYU: + return "YVYU"; + case VIDEO_FORMAT_YUY2: + return "YUY"; + case VIDEO_FORMAT_UYVY: + return "UYVY"; + case VIDEO_FORMAT_RGBA: + return "RGBA"; + case VIDEO_FORMAT_BGRA: + return "BGRA"; + case VIDEO_FORMAT_BGRX: + return "BGRX"; + case VIDEO_FORMAT_Y800: + return "Y800"; + case VIDEO_FORMAT_I444: + return "I444"; + case VIDEO_FORMAT_BGR3: + return "BGR3"; + case VIDEO_FORMAT_I422: + return "I422"; + case VIDEO_FORMAT_I40A: + return "I40A"; + case VIDEO_FORMAT_I42A: + return "I42A"; + case VIDEO_FORMAT_YUVA: + return "YUVA"; + case VIDEO_FORMAT_AYUV: + return "AYUV"; + default: + return "Unknown"; + } +} + +const char* aom_color_format_to_string(aom_img_fmt format) +{ + switch (format) { + case AOM_IMG_FMT_AOMYV12: + return "AOM-YV12"; + case AOM_IMG_FMT_AOMI420: + return "AOM-I420"; + case AOM_IMG_FMT_YV12: + return "YV12"; + case AOM_IMG_FMT_I420: + return "I420"; + case AOM_IMG_FMT_I422: + return "I422"; + case AOM_IMG_FMT_I444: + return "I444"; + case AOM_IMG_FMT_YV1216: + return "YV12-16"; + case AOM_IMG_FMT_I42016: + return "I420-16"; + case AOM_IMG_FMT_I42216: + return "I422-16"; + case AOM_IMG_FMT_I44416: + return "I444-16"; + default: + return "Unknown"; + } +} + +const char* aom_color_trc_to_string(aom_transfer_characteristics_t trc) +{ + switch (trc) { + case AOM_CICP_TC_BT_709: + return "Bt.709"; + case AOM_CICP_TC_BT_470_M: + return "Bt.407 M"; + case AOM_CICP_TC_BT_470_B_G: + return "Bt.407 B/G"; + case AOM_CICP_TC_BT_601: + return "Bt.601"; + case AOM_CICP_TC_SMPTE_240: + return "SMPTE 240 M"; + case AOM_CICP_TC_LINEAR: + return "Linear"; + case AOM_CICP_TC_LOG_100: + return "Logarithmic (100:1 range)"; + case AOM_CICP_TC_LOG_100_SQRT10: + return "Logarithmic (100*sqrt(10):1 range)"; + case AOM_CICP_TC_IEC_61966: + return "IEC 61966-2-4"; + case AOM_CICP_TC_BT_1361: + return "Bt.1361"; + case AOM_CICP_TC_SRGB: + return "sRGB"; + case AOM_CICP_TC_BT_2020_10_BIT: + return "Bt.2020 10b"; + case AOM_CICP_TC_BT_2020_12_BIT: + return "Bt.2020 12b"; + case AOM_CICP_TC_SMPTE_2084: + return "Bt.2100 PQ"; + case AOM_CICP_TC_SMPTE_428: + return "SMPTE ST 428"; + case AOM_CICP_TC_HLG: + return "Bt.2100 HLG"; + default: + return "Unknown"; + } +} + +const char* aom_rc_mode_to_string(aom_rc_mode mode) +{ + switch (mode) { + case AOM_VBR: + return "Variable Bitrate (VBR)"; + case AOM_CBR: + return "Constant Bitrate (CBR)"; + case AOM_CQ: + return "Constrained Quality (CQ)"; + case AOM_Q: + return "Constant Quality (Q)"; + default: + return "Unknown"; + } +} + +const char* aom_kf_mode_to_string(aom_kf_mode mode) +{ + switch (mode) { + case AOM_KF_AUTO: + return "Automatic"; + case AOM_KF_DISABLED: + return "Disabled"; + default: + return "Unknown"; + } +} + +const char* aom_tune_metric_to_string(aom_tune_metric tune) +{ + switch (tune) { + case AOM_TUNE_PSNR: + return "PSNR"; + case AOM_TUNE_SSIM: + return "SSIM"; + case AOM_TUNE_VMAF_WITH_PREPROCESSING: + return "VMAF w/ pre-processing"; + case AOM_TUNE_VMAF_WITHOUT_PREPROCESSING: + return "VMAF w/o pre-processing"; + case AOM_TUNE_VMAF_MAX_GAIN: + return "VMAF max. gain"; + case AOM_TUNE_VMAF_NEG_MAX_GAIN: + return "VMAF negative max. gain"; + case AOM_TUNE_BUTTERAUGLI: + return "Butteraugli"; + default: + return "Unknown"; + } +} + +const char* aom_tune_content_to_string(aom_tune_content tune) +{ + switch (tune) { + case AOM_CONTENT_DEFAULT: + return "Default"; + case AOM_CONTENT_FILM: + return "Film"; + case AOM_CONTENT_SCREEN: + return "Screen"; + default: + return "Unknown"; + } +} + +aom_av1_instance::aom_av1_instance(obs_data_t* settings, obs_encoder_t* self, bool is_hw) + : obs::encoder_instance(settings, self, is_hw), _factory(aom_av1_factory::get()), _iface(nullptr), _ctx(), _cfg(), + _image_index(0), _images(), _global_headers(nullptr), _initialized(false), _settings() +{ + if (is_hw) { + throw std::runtime_error("Hardware encoding isn't even registered, how did you get here?"); + } + + // Retrieve encoder interface. + _iface = _factory->libaom_codec_av1_cx(); + if (!_iface) { + throw std::runtime_error("AOM library does not provide AV1 encoder."); + } + +#ifdef ENABLE_PROFILING + // Profilers + _profiler_copy = streamfx::util::profiler::create(); + _profiler_encode = streamfx::util::profiler::create(); + _profiler_packet = streamfx::util::profiler::create(); +#endif + + { // Generate Static Configuration + { // OBS Information + video_scale_info ovsi; + video_t* video = obs_encoder_video(_self); + const struct video_output_info* video_info = video_output_get_info(video); + + ovsi.colorspace = video_info->colorspace; + ovsi.format = video_info->format; + ovsi.range = video_info->range; + get_video_info(&ovsi); + + // Video + _settings.width = static_cast(obs_encoder_get_width(_self)); + _settings.height = static_cast(obs_encoder_get_height(_self)); + _settings.fps.num = static_cast(video_info->fps_num); + _settings.fps.den = static_cast(video_info->fps_den); + + // Color Format + switch (ovsi.format) { + case VIDEO_FORMAT_I420: + _settings.color_format = AOM_IMG_FMT_I420; + break; + case VIDEO_FORMAT_I422: + _settings.color_format = AOM_IMG_FMT_I422; + break; + case VIDEO_FORMAT_I444: + _settings.color_format = AOM_IMG_FMT_I444; + break; + default: + throw std::runtime_error("Something went wrong figuring out our color format."); + } + + // Color Space + switch (ovsi.colorspace) { + case VIDEO_CS_601: + _settings.color_primaries = AOM_CICP_CP_BT_601; + _settings.color_trc = AOM_CICP_TC_BT_601; + _settings.color_matrix = AOM_CICP_MC_BT_601; + break; + case VIDEO_CS_709: + _settings.color_primaries = AOM_CICP_CP_BT_709; + _settings.color_trc = AOM_CICP_TC_BT_709; + _settings.color_matrix = AOM_CICP_MC_BT_709; + break; + case VIDEO_CS_SRGB: + _settings.color_primaries = AOM_CICP_CP_BT_709; + _settings.color_trc = AOM_CICP_TC_SRGB; + _settings.color_matrix = AOM_CICP_MC_BT_709; + break; + } + + // Color Range + switch (ovsi.range) { + case VIDEO_RANGE_FULL: + _settings.color_range = AOM_CR_FULL_RANGE; + break; + case VIDEO_RANGE_PARTIAL: + _settings.color_range = AOM_CR_STUDIO_RANGE; + break; + } + + // Monochrome + _settings.monochrome = (video_info->format == VIDEO_FORMAT_Y800); + } + + { // Encoder + _settings.profile = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_PROFILE)); + if (_settings.profile == codec::av1::profile::UNKNOWN) { + // Resolve the automatic profile to a proper value. + bool need_professional = (_settings.color_format == VIDEO_FORMAT_I422); + bool need_high = (_settings.color_format == VIDEO_FORMAT_I444) || _settings.monochrome; + + if (need_professional) { + _settings.profile = codec::av1::profile::PROFESSIONAL; + } else if (need_high) { + _settings.profile = codec::av1::profile::HIGH; + } else { + _settings.profile = codec::av1::profile::MAIN; + } + } + } + + { // Rate Control + _settings.rc_mode = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); + _settings.rc_lookahead = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LOOKAHEAD)); + } + + { // Threading + if (auto threads = obs_data_get_int(settings, ST_KEY_ADVANCED_THREADS); threads > 0) { + _settings.threads = static_cast(threads); + } else { + _settings.threads = std::thread::hardware_concurrency(); + } + _settings.rowmultithreading = + static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_ROWMULTITHREADING)); + } + + { // Tiling + _settings.tile_columns = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_COLUMNS)); + _settings.tile_rows = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_ROWS)); + } + + { // Tuning + _settings.tune_metric = + static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TUNE_METRIC)); + _settings.tune_content = + static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TUNE_CONTENT)); + } + } + + // Apply Settings + update(settings); + + // Initialize Encoder + if (auto error = _factory->libaom_codec_enc_init_ver(&_ctx, _iface, &_cfg, 0, AOM_ENCODER_ABI_VERSION); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + D_LOG_ERROR("Failed to initialize codec, unexpected error: %s (code %" PRIu32 ")", errstr, error); + throw std::runtime_error(errstr); + } + + { // Apply Static Control Settings + + { // Color Information +#ifdef AOM_CTRL_AV1E_SET_COLOR_PRIMARIES + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_COLOR_PRIMARIES, _settings.color_primaries); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_COLOR_PRIMARIES", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#else + D_LOG_ERROR("AOM library was built without AV1E_SET_COLOR_PRIMARIES, behavior is unknown."); +#endif + +#ifdef AOM_CTRL_AV1E_SET_TRANSFER_CHARACTERISTICS + if (auto error = + _factory->libaom_codec_control(&_ctx, AV1E_SET_TRANSFER_CHARACTERISTICS, _settings.color_trc); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_TRANSFER_CHARACTERISTICS", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#else + D_LOG_ERROR("AOM library was built without AV1E_SET_TRANSFER_CHARACTERISTICS, behavior is unknown."); +#endif + +#ifdef AOM_CTRL_AV1E_SET_MATRIX_COEFFICIENTS + if (auto error = + _factory->libaom_codec_control(&_ctx, AV1E_SET_MATRIX_COEFFICIENTS, _settings.color_matrix); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_MATRIX_COEFFICIENTS", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#else + D_LOG_ERROR("AOM library was built without AV1E_SET_MATRIX_COEFFICIENTS, behavior is unknown."); +#endif + +#ifdef AOM_CTRL_AV1E_SET_COLOR_RANGE + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_COLOR_RANGE, _settings.color_range); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_COLOR_RANGE", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#else + D_LOG_ERROR("AOM library was built without AV1_SET_COLOR_RANGE, behavior is unknown."); +#endif + +#ifdef AOM_CTRL_AV1E_SET_CHROMA_SAMPLE_POSITION + // !TODO: Consider making this user-controlled. At the moment, this follows the H.264 chroma standard. + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_CHROMA_SAMPLE_POSITION, AOM_CSP_VERTICAL); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_CHROMA_SAMPLE_POSITION", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_RENDER_SIZE + int32_t size[2] = {_settings.width, _settings.height}; + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_RENDER_SIZE, &size); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_RENDER_SIZE", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } +#endif + } + + // Apply Dynamic Control Settings + if (!update(settings)) { + throw std::runtime_error("Unexpected error during configuration."); + } + } + + // Preallocate global headers. + _global_headers = _factory->libaom_codec_get_global_headers(&_ctx); + + // Allocate frames. + _images.resize(_cfg.g_threads); + for (auto& image : _images) { + _factory->libaom_img_alloc(&image, _settings.color_format, _settings.width, _settings.height, 8); + + // Color Information. + image.fmt = _settings.color_format; + image.cp = _settings.color_primaries; + image.tc = _settings.color_trc; + image.mc = _settings.color_matrix; + image.range = _settings.color_range; + image.monochrome = _settings.monochrome ? 1 : 0; + image.csp = AOM_CSP_VERTICAL; // !TODO: Consider making this user-controlled. + + // Size + image.r_w = image.d_w; + image.r_h = image.d_h; + image.r_w = image.w; + image.r_h = image.h; + } + + // Log Settings + log(); + + // Signal to future update() calls that we are fully initialized. + _initialized = true; +} + +aom_av1_instance::~aom_av1_instance() +{ +#ifdef ENABLE_PROFILING + // Profiling + D_LOG_INFO("Timings | Avg. µs | 99.9ile µs | 99.0ile µs | 95.0ile µs | Samples ", ""); + D_LOG_INFO("--------+---------------+---------------+---------------+---------------+----------", ""); + D_LOG_INFO("Copy | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, + _profiler_copy->average_duration() / 1000., + std::chrono::duration_cast(_profiler_copy->percentile(0.999)).count(), + std::chrono::duration_cast(_profiler_copy->percentile(0.990)).count(), + std::chrono::duration_cast(_profiler_copy->percentile(0.950)).count(), + _profiler_copy->count()); + D_LOG_INFO("Encode | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, + _profiler_encode->average_duration() / 1000., + std::chrono::duration_cast(_profiler_encode->percentile(0.999)).count(), + std::chrono::duration_cast(_profiler_encode->percentile(0.990)).count(), + std::chrono::duration_cast(_profiler_encode->percentile(0.950)).count(), + _profiler_encode->count()); + D_LOG_INFO("Packet | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, + _profiler_packet->average_duration() / 1000., + std::chrono::duration_cast(_profiler_packet->percentile(0.999)).count(), + std::chrono::duration_cast(_profiler_packet->percentile(0.990)).count(), + std::chrono::duration_cast(_profiler_packet->percentile(0.950)).count(), + _profiler_packet->count()); +#endif + + // Deallocate global buffer. + if (_global_headers) { + /* Breaks heap + if (_global_headers->buf) { + free(_global_headers->buf); + _global_headers->buf = nullptr; + } + free(_global_headers); + _global_headers = nullptr; + */ + } + + // Deallocate frames. + for (auto& image : _images) { + _factory->libaom_img_free(&image); + } + _images.clear(); + + // Destroy encoder. + _factory->libaom_codec_destroy(&_ctx); +} + +void aom_av1_instance::migrate(obs_data_t* settings, uint64_t version) {} + +bool aom_av1_instance::update(obs_data_t* settings) +{ + video_t* obsVideo = obs_encoder_video(_self); + const struct video_output_info* obsVideoInfo = video_output_get_info(obsVideo); + uint32_t obsFPSnum = obsVideoInfo->fps_num; + uint32_t obsFPSden = obsVideoInfo->fps_den; + bool obsMonochrome = (obsVideoInfo->format == VIDEO_FORMAT_Y800); + +#define SET_IF_NOT_DEFAULT(X, Y) \ + if (X != -1) { \ + Y = static_cast(X); \ + } + + { // Generate Dynamic Settings + + { // Encoder + _settings.preset = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_CPUUSAGE)); + } + + { // Rate Control + _settings.rc_bitrate = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE)); + _settings.rc_bitrate_overshoot = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT)); + _settings.rc_bitrate_undershoot = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT)); + _settings.rc_quality = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY)); + _settings.rc_quantizer_min = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM)); + _settings.rc_quantizer_max = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM)); + _settings.rc_buffer_ms = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE)); + _settings.rc_buffer_initial_ms = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL)); + _settings.rc_buffer_optimal_ms = + static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL)); + } + + { // Key-Frames + int64_t kf_type = obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE); + bool is_seconds = (kf_type == 0); + + _settings.kf_mode = AOM_KF_AUTO; + if (is_seconds) { + _settings.kf_distance_max = static_cast( + std::lround(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) + * static_cast(obsFPSnum) / static_cast(obsFPSden))); + } else { + _settings.kf_distance_max = + static_cast(obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES)); + } + _settings.kf_distance_min = _settings.kf_distance_max; + } + + // All-Intra requires us to never set Key-Frames. + if (_cfg.g_usage == AOM_USAGE_ALL_INTRA) { + _settings.rc_lookahead = 0; + _settings.kf_mode = AOM_KF_DISABLED; + _settings.kf_distance_min = 0; + _settings.kf_distance_max = 0; + } + } + + { // Configuration. + + { // Usage and Defaults + _cfg.g_usage = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_USAGE)); + _factory->libaom_codec_enc_config_default(_iface, &_cfg, _cfg.g_usage); + } + + { // Frame Information + // Size + _cfg.g_w = _settings.width; + _cfg.g_h = _settings.height; + + // Time Base (Rate is inverted Time Base) + _cfg.g_timebase.num = _settings.fps.den; + _cfg.g_timebase.den = _settings.fps.num; + + // !INFO: Whenever OBS decides to support anything but 8-bits, let me know. + _cfg.g_bit_depth = AOM_BITS_8; + _cfg.g_input_bit_depth = AOM_BITS_8; + + // Monochrome color + _cfg.monochrome = _settings.monochrome ? 1 : 0; + } + + { // Encoder + + // AV1 Profile + _cfg.g_profile = static_cast(_settings.profile); + } + + { // Rate Control + // Mode + _cfg.rc_end_usage = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); + + // Look-Ahead + SET_IF_NOT_DEFAULT(_settings.rc_lookahead, _cfg.g_lag_in_frames); + + // Limits + SET_IF_NOT_DEFAULT(_settings.rc_bitrate, _cfg.rc_target_bitrate); + SET_IF_NOT_DEFAULT(_settings.rc_bitrate_overshoot, _cfg.rc_overshoot_pct); + SET_IF_NOT_DEFAULT(_settings.rc_bitrate_undershoot, _cfg.rc_undershoot_pct); + SET_IF_NOT_DEFAULT(_settings.rc_quantizer_min, _cfg.rc_min_quantizer); + SET_IF_NOT_DEFAULT(_settings.rc_quantizer_max, _cfg.rc_max_quantizer); + + // Buffer + SET_IF_NOT_DEFAULT(_settings.rc_buffer_ms, _cfg.rc_buf_sz); + SET_IF_NOT_DEFAULT(_settings.rc_buffer_initial_ms, _cfg.rc_buf_initial_sz); + SET_IF_NOT_DEFAULT(_settings.rc_buffer_optimal_ms, _cfg.rc_buf_optimal_sz); + } + + { // Key-Frames + SET_IF_NOT_DEFAULT(_settings.kf_mode, _cfg.kf_mode); + SET_IF_NOT_DEFAULT(_settings.kf_distance_min, _cfg.kf_min_dist); + SET_IF_NOT_DEFAULT(_settings.kf_distance_max, _cfg.kf_max_dist); + } + + { // Advanced + + // Single-Pass + _cfg.g_pass = AOM_RC_ONE_PASS; + + // Threading + SET_IF_NOT_DEFAULT(_settings.threads, _cfg.g_threads); + } + + // TODO: Future + //_cfg.rc_resize_mode = 0; // "RESIZE_NONE + //_cfg.rc_resize_denominator = ?; + //_cfg.rc_resize_kf_denominator = ?; + //_cfg.rc_superres_mode = AOM_SUPERRES_NONE; + //_cfg.rc_superres_denominator = ?; + //_cfg.rc_superres_kf_denominator = ?; + //_cfg.rc_superres_qthresh = ?; + //_cfg.rc_superres_kf_qtresh = ?; + //_cfg.rc_dropframe_thresh = ?; + //_cfg.fwd_kf_enabled = ?; + //_cfg.g_forced_max_frame_width = ?; + //_cfg.g_forced_max_frame_height = ?; + //_cfg.g_error_resilient = ?; + //_cfg.g_lag_in_frames = ?; + //_cfg.sframe_dist = ?; + //_cfg.sframe_mode = 0; + //_cfg.large_scale_tile = 0; + //_cfg.full_still_picture_hdr = ?; + //_cfg.save_as_annexb = ?; + //_cfg.encoder_cfg = ?; + + // Apply configuration + if (_initialized) { + if (auto error = _factory->libaom_codec_enc_config_set(&_ctx, &_cfg); error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing configuration: %s (code %" PRIu32 ")%s%s%s%s", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + return false; + } + } + } + + if (_ctx.iface) { // Control + + { // Encoder +#ifdef AOM_CTRL_AOME_SET_CPUUSED + if (_settings.preset != -1) { + if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_CPUUSED, _settings.preset); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AOME_SET_CPUUSED", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif + } + + { // Rate Control +#ifdef AOM_CTRL_AOME_SET_CQ_LEVEL + if ((_settings.rc_quality != -1) && ((_settings.rc_mode == AOM_CQ) || (_settings.rc_mode == AOM_Q))) { + if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_CQ_LEVEL, _settings.rc_quality); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AOME_SET_CQ_LEVEL", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif + } + + { // Advanced +#ifdef AOM_CTRL_AV1E_SET_ROW_MT + if (_settings.rowmultithreading != -1) { + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_ROW_MT, _settings.rowmultithreading); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_ROW_MT", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_TILE_COLUMNS + if (_settings.tile_columns != -1) { + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TILE_COLUMNS, _settings.tile_columns); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_TILE_COLUMNS", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_TILE_ROWS + if (_settings.tile_rows != -1) { + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TILE_ROWS, _settings.tile_rows); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_TILE_ROWS", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif +#ifdef AOM_CTRL_AOME_SET_TUNING + if (_settings.tune_metric != -1) { + if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_TUNING, _settings.tune_metric); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AOME_SET_TUNING", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif +#ifdef AOM_CTRL_AV1E_SET_TUNE_CONTENT + if (_settings.tune_content != AOM_CONTENT_DEFAULT) { + if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TUNE_CONTENT, _settings.tune_content); + error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + const char* err = _factory->libaom_codec_error(&_ctx); + const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); + D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // + "AV1E_SET_TUNE_CONTENT", // + (errstr ? errstr : ""), error, // + (err ? "\n\tMessage: " : ""), (err ? err : ""), // + (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // + ); + } + } +#endif + } + } + +#undef SET_IF_NOT_DEFAULT + + // Log the changed settings. + if (_initialized) { + log(); + } + + return true; +} + +void aom_av1_instance::log() +{ + D_LOG_INFO("AOM AV1:", ""); + D_LOG_INFO(" Video: %" PRIu16 "x%" PRIu16 "@%1.2ffps (%" PRIu32 "/%" PRIu32 ")", _settings.width, _settings.height, + static_cast(_settings.fps.num) / static_cast(_settings.fps.den), _settings.fps.num, + _settings.fps.den); + D_LOG_INFO(" Color: %s/%s/%s%s", aom_color_format_to_string(_settings.color_format), + aom_color_trc_to_string(_settings.color_trc), + _settings.color_range == AOM_CR_FULL_RANGE ? "Full" : "Partial", + _settings.monochrome ? "/Monochrome" : ""); + + // Rate Control + D_LOG_INFO(" Rate Control: %s", aom_rc_mode_to_string(_settings.rc_mode)); + D_LOG_INFO(" Look-Ahead: %" PRId8, _settings.rc_lookahead); + D_LOG_INFO(" Buffers: %" PRId32 " ms / %" PRId32 " ms / %" PRId32 " ms", _settings.rc_buffer_ms, + _settings.rc_buffer_initial_ms, _settings.rc_buffer_optimal_ms); + D_LOG_INFO(" Bitrate: %" PRId32 " kbit/s (-%" PRId32 "%% - +%" PRId32 "%%)", _settings.rc_bitrate, + _settings.rc_bitrate_undershoot, _settings.rc_bitrate_overshoot); + D_LOG_INFO(" Quality: %" PRId8, _settings.rc_quality); + D_LOG_INFO(" Quantizer: %" PRId8 " - %" PRId8, _settings.rc_quantizer_min, _settings.rc_quantizer_max); + + // Key-Frames + D_LOG_INFO(" Key-Frames: %s", aom_kf_mode_to_string(_settings.kf_mode)); + D_LOG_INFO(" Distance: %" PRId32 " - %" PRId32 " frames", _settings.kf_distance_min, _settings.kf_distance_max); + + // Advanced + D_LOG_INFO(" Advanced: ", ""); + D_LOG_INFO(" Threads: %" PRId8, _settings.threads); + D_LOG_INFO(" Row-Multi-Threading: %s", _settings.rowmultithreading == -1 ? "Default" + : _settings.rowmultithreading == 1 ? "Enabled" + : "Disabled"); + D_LOG_INFO(" Tiling: %" PRId8 "x%" PRId8, _settings.tile_columns, _settings.tile_rows); + D_LOG_INFO(" Tune: %s (Metric), %s (Content)", aom_tune_metric_to_string(_settings.tune_metric), + aom_tune_content_to_string(_settings.tune_content)); +} + +bool aom_av1_instance::get_extra_data(uint8_t** extra_data, size_t* size) +{ + if (!_global_headers) { + return false; + } + + *extra_data = static_cast(_global_headers->buf); + *size = _global_headers->sz; + + return true; +} + +bool aom_av1_instance::get_sei_data(uint8_t** sei_data, size_t* size) +{ + return get_extra_data(sei_data, size); +} + +void aom_av1_instance::get_video_info(struct video_scale_info* info) +{ + // Fix up color format. + auto format = obs_encoder_get_preferred_video_format(_self); + if (format == VIDEO_FORMAT_NONE) { + format = info->format; + } + + switch (format) { + // Perfect matches. + case VIDEO_FORMAT_I444: // AOM_IMG_I444. + case VIDEO_FORMAT_I422: // AOM_IMG_I422. + case VIDEO_FORMAT_I420: // AOM_IMG_I420. + break; + + // 4:2:0 formats + case VIDEO_FORMAT_NV12: // Y, UV Interleaved. + case VIDEO_FORMAT_I40A: + D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I420'...", obs_video_format_to_string(format)); + info->format = VIDEO_FORMAT_I420; + break; + + // 4:2:2-like formats + case VIDEO_FORMAT_UYVY: + case VIDEO_FORMAT_YUY2: + case VIDEO_FORMAT_YVYU: + case VIDEO_FORMAT_I42A: + D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I422'...", obs_video_format_to_string(format)); + info->format = VIDEO_FORMAT_I422; + break; + + // 4:4:4 + case VIDEO_FORMAT_BGR3: + case VIDEO_FORMAT_BGRA: + case VIDEO_FORMAT_BGRX: + case VIDEO_FORMAT_RGBA: + case VIDEO_FORMAT_YUVA: + case VIDEO_FORMAT_AYUV: + case VIDEO_FORMAT_Y800: // Grayscale, no exact match. + D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I444'...", obs_video_format_to_string(format)); + info->format = VIDEO_FORMAT_I444; + break; + } + + // Fix up color space. + if (info->colorspace == VIDEO_CS_DEFAULT) { + info->colorspace = VIDEO_CS_SRGB; + } + + // Fix up color range. + if (info->range == VIDEO_RANGE_DEFAULT) { + info->range = VIDEO_RANGE_PARTIAL; + } +} + +bool streamfx::encoder::aom::av1::aom_av1_instance::encode_video(encoder_frame* frame, encoder_packet* packet, + bool* received_packet) +{ + // Retrieve current indexed image. + auto& image = _images.at(_image_index); + + { // Copy Image data. +#ifdef ENABLE_PROFILING + auto profile = _profiler_copy->track(); +#endif + std::memcpy(image.planes[AOM_PLANE_Y], frame->data[0], frame->linesize[0] * image.h); + if (image.fmt == AOM_IMG_FMT_I420) { + std::memcpy(image.planes[AOM_PLANE_U], frame->data[1], frame->linesize[1] * image.h / 2); + std::memcpy(image.planes[AOM_PLANE_V], frame->data[2], frame->linesize[2] * image.h / 2); + } else { + std::memcpy(image.planes[AOM_PLANE_U], frame->data[1], frame->linesize[1] * image.h); + std::memcpy(image.planes[AOM_PLANE_V], frame->data[2], frame->linesize[2] * image.h); + } + } + + { // Try to encode the new image. +#ifdef ENABLE_PROFILING + auto profile = _profiler_encode->track(); +#endif + aom_enc_frame_flags_t flags = 0; + if (_cfg.g_usage == AOM_USAGE_ALL_INTRA) { + flags = AOM_EFLAG_FORCE_KF; + } + if (auto error = _factory->libaom_codec_encode(&_ctx, &image, frame->pts, 1, flags); error != AOM_CODEC_OK) { + const char* errstr = _factory->libaom_codec_err_to_string(error); + D_LOG_ERROR("Encoding frame failed with error: %s (code %" PRIu32 ")\n%s\n%s", errstr, error, + _factory->libaom_codec_error(&_ctx), _factory->libaom_codec_error_detail(&_ctx)); + return false; + } else { + // Increment the image index. + _image_index = (_image_index++) % _images.size(); + } + } + + { // Get Packet +#ifdef ENABLE_PROFILING + auto profile = _profiler_packet->track(); +#endif + aom_codec_iter_t iter = NULL; + for (auto* pkt = _factory->libaom_codec_get_cx_data(&_ctx, &iter); pkt != nullptr; + pkt = _factory->libaom_codec_get_cx_data(&_ctx, &iter)) { +#ifdef _DEBUG + { + const char* kind = ""; + switch (pkt->kind) { + case AOM_CODEC_CX_FRAME_PKT: + kind = "Frame"; + break; + case AOM_CODEC_STATS_PKT: + kind = "Stats"; + break; + case AOM_CODEC_FPMB_STATS_PKT: + kind = "FPMB Stats"; + break; + case AOM_CODEC_PSNR_PKT: + kind = "PSNR"; + break; + case AOM_CODEC_CUSTOM_PKT: + kind = "Custom"; + break; + } + D_LOG_DEBUG("\tPacket: Kind=%s", kind) + } +#endif + + if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { + // Status + packet->type = OBS_ENCODER_VIDEO; + packet->keyframe = ((pkt->data.frame.flags & AOM_FRAME_IS_KEY) == AOM_FRAME_IS_KEY) + || (_cfg.g_usage == AOM_USAGE_ALL_INTRA); + if (packet->keyframe) { + // + packet->priority = 0; + packet->drop_priority = packet->priority; + } else if ((pkt->data.frame.flags & AOM_FRAME_IS_DROPPABLE) != AOM_FRAME_IS_DROPPABLE) { + // Dropping this frame breaks the bitstream. + packet->priority = -1; + packet->drop_priority = packet->priority; + } else { + // This frame can be dropped at will. + packet->priority = -2; + packet->drop_priority = packet->priority; + } + + // Data + packet->data = static_cast(pkt->data.frame.buf); + packet->size = pkt->data.frame.sz; + + // Timestamps + //TODO: Temporarily set both to the same until there is a way to figure out actual order. + packet->pts = pkt->data.frame.pts; + packet->dts = pkt->data.frame.pts; + + *received_packet = true; + } + + if (*received_packet == true) + break; + } + + if (!*received_packet) { + packet->type = OBS_ENCODER_VIDEO; + packet->data = nullptr; + packet->size = 0; + packet->pts = -1; + packet->dts = -1; +#ifdef _DEBUG + D_LOG_DEBUG("No Packet", ""); +#endif + // Not necessarily an error. + //return false; + } else { +#ifdef _DEBUG + D_LOG_DEBUG("Packet: Type=%s PTS=%06" PRId64 " DTS=%06" PRId64 " Size=%016" PRIuPTR "", + packet->keyframe ? "I" : "P", packet->pts, packet->dts, packet->size); +#endif + } + } + + return true; +} + +aom_av1_factory::aom_av1_factory() +{ + // Try and load the AOM library. + std::vector libs; + +#ifdef D_PLATFORM_WINDOWS + // Try loading from the data directory first. + libs.push_back(streamfx::data_file_path("aom.dll")); // MSVC (preferred) + libs.push_back(streamfx::data_file_path("libaom.dll")); // Cross-Compile + // In any other case, load the system-wide binary. + libs.push_back("aom.dll"); + libs.push_back("libaom.dll"); +#else + // Try loading from the data directory first. + libs.push_back(streamfx::data_file_path("libaom.so")); + // In any other case, load the system-wide binary. + libs.push_back("libaom"); +#endif + + for (auto lib : libs) { + try { + _library = streamfx::util::library::load(lib); + if (_library) + break; + } catch (...) { + D_LOG_WARNING("Loading of '%s' failed.", lib.generic_string().c_str()); + } + } + if (!_library) { + throw std::runtime_error("Unable to load AOM library."); + } + + // Load all necessary functions. +#define _LOAD_SYMBOL(X) \ + { \ + lib##X = reinterpret_cast(_library->load_symbol(std::string(#X))); \ + } +#define _LOAD_SYMBOL_(X, Y) \ + { \ + lib##X = reinterpret_cast(_library->load_symbol(std::string(Y))); \ + } + _LOAD_SYMBOL(aom_codec_version); + _LOAD_SYMBOL(aom_codec_version_str); + _LOAD_SYMBOL(aom_codec_version_extra_str); + _LOAD_SYMBOL(aom_codec_build_config); + _LOAD_SYMBOL(aom_codec_iface_name); + _LOAD_SYMBOL(aom_codec_err_to_string); + _LOAD_SYMBOL(aom_codec_error); + _LOAD_SYMBOL(aom_codec_error_detail); + _LOAD_SYMBOL(aom_codec_destroy); + _LOAD_SYMBOL(aom_codec_get_caps); + _LOAD_SYMBOL(aom_codec_control); + _LOAD_SYMBOL(aom_codec_set_option); + _LOAD_SYMBOL(aom_obu_type_to_string); + _LOAD_SYMBOL(aom_uleb_size_in_bytes); + _LOAD_SYMBOL(aom_uleb_decode); + _LOAD_SYMBOL(aom_uleb_encode); + _LOAD_SYMBOL(aom_uleb_encode_fixed_size); + _LOAD_SYMBOL(aom_img_alloc); + _LOAD_SYMBOL(aom_img_wrap); + _LOAD_SYMBOL(aom_img_alloc_with_border); + _LOAD_SYMBOL(aom_img_set_rect); + _LOAD_SYMBOL(aom_img_flip); + _LOAD_SYMBOL(aom_img_free); + _LOAD_SYMBOL(aom_img_plane_width); + _LOAD_SYMBOL(aom_img_plane_height); + _LOAD_SYMBOL(aom_img_add_metadata); + _LOAD_SYMBOL(aom_img_get_metadata); + _LOAD_SYMBOL(aom_img_num_metadata); + _LOAD_SYMBOL(aom_img_remove_metadata); + _LOAD_SYMBOL(aom_img_metadata_alloc); + _LOAD_SYMBOL(aom_img_metadata_free); + _LOAD_SYMBOL(aom_codec_enc_init_ver); + _LOAD_SYMBOL(aom_codec_enc_config_default); + _LOAD_SYMBOL(aom_codec_enc_config_set); + _LOAD_SYMBOL(aom_codec_get_global_headers); + _LOAD_SYMBOL(aom_codec_encode); + _LOAD_SYMBOL(aom_codec_set_cx_data_buf); + _LOAD_SYMBOL(aom_codec_get_cx_data); + _LOAD_SYMBOL(aom_codec_get_preview_frame); + _LOAD_SYMBOL(aom_codec_av1_cx); +#undef _LOAD_SYMBOL + + // Register encoder. + _info.id = S_PREFIX "aom-av1"; + _info.type = obs_encoder_type::OBS_ENCODER_VIDEO; + _info.codec = "av1"; + _info.caps = OBS_ENCODER_CAP_DYN_BITRATE; + + finish_setup(); +} + +aom_av1_factory::~aom_av1_factory() {} + +std::shared_ptr _aom_av1_factory_instance = nullptr; + +void aom_av1_factory::initialize() +try { + if (!_aom_av1_factory_instance) { + _aom_av1_factory_instance = std::make_shared(); + } +} catch (std::exception const& ex) { + D_LOG_ERROR("Failed to initialize AOM AV1 encoder: %s", ex.what()); +} + +void aom_av1_factory::finalize() +{ + _aom_av1_factory_instance.reset(); +} + +std::shared_ptr aom_av1_factory::get() +{ + return _aom_av1_factory_instance; +} + +const char* aom_av1_factory::get_name() +{ + return "AV1 (via AOM)"; +} + +void* aom_av1_factory::create(obs_data_t* settings, obs_encoder_t* encoder, bool is_hw) +{ + return new aom_av1_instance(settings, encoder, is_hw); +} + +void aom_av1_factory::get_defaults2(obs_data_t* settings) +{ + { // Presets + obs_data_set_default_int(settings, ST_KEY_ENCODER_USAGE, static_cast(AOM_USAGE_REALTIME)); + obs_data_set_default_int(settings, ST_KEY_ENCODER_CPUUSAGE, -1); + obs_data_set_default_int(settings, ST_KEY_ENCODER_PROFILE, + static_cast(codec::av1::profile::UNKNOWN)); + } + + { // Rate-Control + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_MODE, static_cast(AOM_CBR)); + + // Limits + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE, 6000); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM, -1); + + // Buffer + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL, -1); + obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL, -1); + } + + { // Key-Frame Options + obs_data_set_default_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE, 0); + obs_data_set_default_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS, 2.0); + obs_data_set_default_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES, 300); + } + + { // Advanced Options + obs_data_set_default_int(settings, ST_KEY_ADVANCED_THREADS, 0); + obs_data_set_default_int(settings, ST_KEY_ADVANCED_ROWMULTITHREADING, -1); + obs_data_set_default_int(settings, ST_KEY_ADVANCED_TILE_COLUMNS, -1); + obs_data_set_default_int(settings, ST_KEY_ADVANCED_TILE_ROWS, -1); + obs_data_set_default_int(settings, ST_KEY_ADVANCED_TUNE_METRIC, -1); + obs_data_set_default_int(settings, ST_KEY_ADVANCED_TUNE_CONTENT, static_cast(AOM_CONTENT_DEFAULT)); + } +} + +static bool modified_usage(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept +try { + bool is_all_intra = false; + if (obs_data_get_int(settings, ST_KEY_ENCODER_USAGE) == AOM_USAGE_ALL_INTRA) { + is_all_intra = true; + } + + // All-Intra does not support these. + obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LOOKAHEAD), !is_all_intra); + obs_property_set_visible(obs_properties_get(props, ST_I18N_KEYFRAMES), !is_all_intra); + + return true; +} catch (const std::exception& ex) { + DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static bool modified_ratecontrol_mode(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept +try { + bool is_bitrate_visible = false; + bool is_overundershoot_visible = false; + bool is_quality_visible = false; + + // Fix rate control mode selection if ALL_INTRA is selected. + if (obs_data_get_int(settings, ST_KEY_ENCODER_USAGE) == AOM_USAGE_ALL_INTRA) { + obs_data_set_int(settings, ST_KEY_RATECONTROL_MODE, static_cast(aom_rc_mode::AOM_Q)); + } + + { // Based on the Rate Control Mode, show and hide options. + auto mode = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); + if (mode == AOM_CBR) { + is_bitrate_visible = true; + is_overundershoot_visible = true; + } else if (mode == AOM_VBR) { + is_bitrate_visible = true; + is_overundershoot_visible = true; + } else if (mode == AOM_CQ) { + is_bitrate_visible = true; + is_overundershoot_visible = true; + is_quality_visible = true; + } else if (mode == AOM_Q) { + is_quality_visible = true; + } + + obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE), is_bitrate_visible); + obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT), + is_overundershoot_visible); + obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT), + is_overundershoot_visible); +#ifdef AOM_CTRL_AOME_SET_CQ_LEVEL + obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_QUALITY), is_quality_visible); +#endif + } + return true; +} catch (const std::exception& ex) { + DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept +try { + bool is_seconds = obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE) == 0; + obs_property_set_visible(obs_properties_get(props, ST_KEY_KEYFRAMES_INTERVAL_FRAMES), !is_seconds); + obs_property_set_visible(obs_properties_get(props, ST_KEY_KEYFRAMES_INTERVAL_SECONDS), is_seconds); + return true; +} catch (const std::exception& ex) { + DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); + return false; +} catch (...) { + DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + return false; +} + +obs_properties_t* aom_av1_factory::get_properties2(instance_t* data) +{ + obs_properties_t* props = obs_properties_create(); + +#ifdef ENABLE_FRONTEND + { + obs_properties_add_button2(props, S_MANUAL_OPEN, D_TRANSLATE(S_MANUAL_OPEN), aom_av1_factory::on_manual_open, + this); + } +#endif + + { // Presets + obs_properties_t* grp = obs_properties_create(); + obs_properties_add_group(props, ST_I18N_ENCODER, D_TRANSLATE(ST_I18N_ENCODER), OBS_GROUP_NORMAL, grp); + //obs_properties_add_group(props, S_CODEC_AV1, D_TRANSLATE(S_CODEC_AV1), OBS_GROUP_NORMAL, grp); + + { // Usage + auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_USAGE, D_TRANSLATE(ST_I18N_ENCODER_USAGE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(p, modified_usage); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_GOODQUALITY), + static_cast(AOM_USAGE_GOOD_QUALITY)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_REALTIME), + static_cast(AOM_USAGE_REALTIME)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_ALLINTRA), + static_cast(AOM_USAGE_ALL_INTRA)); + } + +#ifdef AOM_CTRL_AOME_SET_CPUUSED + { // CPU Usage + auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_CPUUSAGE, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_9), 9); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_8), 8); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_7), 7); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_6), 6); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_5), 5); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_4), 4); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_3), 3); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_2), 2); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_1), 1); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_0), 0); + } +#endif + + { // Profile + auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_PROFILE, D_TRANSLATE(S_CODEC_AV1_PROFILE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_AUTOMATIC), + static_cast(codec::av1::profile::UNKNOWN)); + obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::MAIN), + static_cast(codec::av1::profile::MAIN)); + obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::HIGH), + static_cast(codec::av1::profile::HIGH)); + obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::PROFESSIONAL), + static_cast(codec::av1::profile::PROFESSIONAL)); + } + } + + { // Rate Control Options + obs_properties_t* grp = obs_properties_create(); + obs_properties_add_group(props, ST_I18N_RATECONTROL, D_TRANSLATE(ST_I18N_RATECONTROL), OBS_GROUP_NORMAL, grp); + + { // Mode + auto p = obs_properties_add_list(grp, ST_KEY_RATECONTROL_MODE, D_TRANSLATE(ST_I18N_RATECONTROL_MODE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(p, modified_ratecontrol_mode); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_VBR), static_cast(AOM_VBR)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CBR), static_cast(AOM_CBR)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CQ), static_cast(AOM_CQ)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_Q), static_cast(AOM_Q)); + } + + { // Look-Ahead + auto p = + obs_properties_add_int(grp, ST_KEY_RATECONTROL_LOOKAHEAD, D_TRANSLATE(ST_I18N_RATECONTROL_LOOKAHEAD), + -1, std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " frames"); + } + + { // Limits + obs_properties_t* grp2 = obs_properties_create(); + obs_properties_add_group(grp, ST_I18N_RATECONTROL_LIMITS, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS), + OBS_GROUP_NORMAL, grp2); + + { // Bitrate + auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE), 0, + std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " kbit/s"); + } + + { // Bitrate Under/Overshoot + auto p1 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT), -1, + 100, 1); + auto p2 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_OVERSHOOT), -1, + 1000, 1); + obs_property_float_set_suffix(p1, " %"); + obs_property_float_set_suffix(p2, " %"); + } + +#ifdef AOM_CTRL_AOME_SET_CQ_LEVEL + { // Quality + auto p = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUALITY, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUALITY), -1, 63, 1); + } +#endif + + { // Quantizer + auto p1 = + obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MINIMUM), -1, 63, 1); + auto p2 = + obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM, + D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM), -1, 63, 1); + } + } + + { // Buffer + obs_properties_t* grp2 = obs_properties_create(); + obs_properties_add_group(grp, ST_I18N_RATECONTROL_BUFFER, D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER), + OBS_GROUP_NORMAL, grp2); + + { // Buffer Size + auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE, + D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE), -1, + std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " ms"); + } + + { // Initial Buffer Size + auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL, + D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE_INITIAL), -1, + std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " ms"); + } + + { // Optimal Buffer Size + auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL, + D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE_OPTIMAL), -1, + std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " ms"); + } + } + } + + { // Key-Frame Options + obs_properties_t* grp = obs_properties_create(); + obs_properties_add_group(props, ST_I18N_KEYFRAMES, D_TRANSLATE(ST_I18N_KEYFRAMES), OBS_GROUP_NORMAL, grp); + + { // Key-Frame Interval Type + auto p = + obs_properties_add_list(grp, ST_KEY_KEYFRAMES_INTERVALTYPE, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(p, modified_keyframes); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE_SECONDS), 0); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE_FRAMES), 1); + } + + { // Key-Frame Interval Seconds + auto p = obs_properties_add_float(grp, ST_KEY_KEYFRAMES_INTERVAL_SECONDS, + D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVAL), 0.00, + std::numeric_limits::max(), 0.01); + obs_property_float_set_suffix(p, " seconds"); + } + + { // Key-Frame Interval Frames + auto p = + obs_properties_add_int(grp, ST_KEY_KEYFRAMES_INTERVAL_FRAMES, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVAL), + 0, std::numeric_limits::max(), 1); + obs_property_int_set_suffix(p, " frames"); + } + } + + { // Advanced Options + obs_properties_t* grp = obs_properties_create(); + obs_properties_add_group(props, ST_I18N_ADVANCED, D_TRANSLATE(ST_I18N_ADVANCED), OBS_GROUP_NORMAL, grp); + + { // Threads + auto p = obs_properties_add_int(grp, ST_KEY_ADVANCED_THREADS, D_TRANSLATE(ST_I18N_ADVANCED_THREADS), 0, + std::numeric_limits::max(), 1); + } + +#ifdef AOM_CTRL_AV1E_SET_ROW_MT + { // Row-MT + auto p = streamfx::util::obs_properties_add_tristate(grp, ST_KEY_ADVANCED_ROWMULTITHREADING, + D_TRANSLATE(ST_I18N_ADVANCED_ROWMULTITHREADING)); + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_TILE_COLUMNS + { // Tile Columns + auto p = obs_properties_add_int_slider(grp, ST_KEY_ADVANCED_TILE_COLUMNS, + D_TRANSLATE(ST_I18N_ADVANCED_TILE_COLUMNS), -1, 6, 1); + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_TILE_ROWS + { // Tile Rows + auto p = obs_properties_add_int_slider(grp, ST_KEY_ADVANCED_TILE_ROWS, + D_TRANSLATE(ST_I18N_ADVANCED_TILE_ROWS), -1, 6, 1); + } +#endif + { + obs_properties_t* grp2 = obs_properties_create(); + obs_properties_add_group(grp, ST_I18N_ADVANCED_TUNE, D_TRANSLATE(ST_I18N_ADVANCED_TUNE), OBS_GROUP_NORMAL, + grp2); + +#ifdef AOM_CTRL_AOME_SET_TUNING + { // Tuning + auto p = obs_properties_add_list(grp2, ST_KEY_ADVANCED_TUNE_METRIC, + D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_PSNR), + static_cast(AOM_TUNE_PSNR)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_SSIM), + static_cast(AOM_TUNE_SSIM)); +#ifdef _DEBUG + // These have no actual use outside of debug builds, way too slow! + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHPREPROCESSING), + static_cast(AOM_TUNE_VMAF_WITH_PREPROCESSING)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHOUTPREPROCESSING), + static_cast(AOM_TUNE_VMAF_WITHOUT_PREPROCESSING)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFMAXGAIN), + static_cast(AOM_TUNE_VMAF_MAX_GAIN)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFNEGMAXGAIN), + static_cast(AOM_TUNE_VMAF_NEG_MAX_GAIN)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_BUTTERAUGLI), + static_cast(AOM_TUNE_BUTTERAUGLI)); +#endif + } +#endif + +#ifdef AOM_CTRL_AV1E_SET_TUNE_CONTENT + { // Content Tuning + auto p = obs_properties_add_list(grp2, ST_KEY_ADVANCED_TUNE_CONTENT, + D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), static_cast(AOM_CONTENT_DEFAULT)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_SCREEN), + static_cast(AOM_CONTENT_SCREEN)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_FILM), + static_cast(AOM_CONTENT_FILM)); + } +#endif + } + } + + return props; +} + +#ifdef ENABLE_FRONTEND +bool aom_av1_factory::on_manual_open(obs_properties_t* props, obs_property_t* property, void* data) +{ + streamfx::open_url(HELP_URL); + return false; +} +#endif diff --git a/source/encoders/encoder-aom-av1.hpp b/source/encoders/encoder-aom-av1.hpp new file mode 100644 index 00000000..ee9bc4cc --- /dev/null +++ b/source/encoders/encoder-aom-av1.hpp @@ -0,0 +1,196 @@ +// Copyright (c) 2021 Michael Fabian Dirks +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once +#include "common.hpp" +#include +#include +#include "encoders/codecs/av1.hpp" +#include "obs/obs-encoder-factory.hpp" +#include "util/util-library.hpp" +#include "util/util-profiler.hpp" + +#include + +namespace streamfx::encoder::aom::av1 { + class aom_av1_factory; + + class aom_av1_instance : public obs::encoder_instance { + std::shared_ptr _factory; + + aom_codec_iface_t* _iface; + aom_codec_ctx_t _ctx; + aom_codec_enc_cfg_t _cfg; + size_t _image_index; + std::vector _images; + aom_fixed_buf_t* _global_headers; + + bool _initialized; + struct { + // Video (All Static) + uint16_t width; + uint16_t height; + struct { + uint32_t num; + uint32_t den; + } fps; + + // Color (All Static) + aom_img_fmt color_format; + aom_color_primaries_t color_primaries; + aom_transfer_characteristics_t color_trc; + aom_matrix_coefficients_t color_matrix; + aom_color_range_t color_range; + bool monochrome; + + // Encoder + codec::av1::profile profile; // Static + int8_t preset; + + // Rate Control + aom_rc_mode rc_mode; // Static + int8_t rc_lookahead; // Static + int32_t rc_bitrate; + int32_t rc_bitrate_overshoot; + int32_t rc_bitrate_undershoot; + int8_t rc_quality; + int8_t rc_quantizer_min; + int8_t rc_quantizer_max; + int32_t rc_buffer_ms; + int32_t rc_buffer_initial_ms; + int32_t rc_buffer_optimal_ms; + + // Key-Frames + aom_kf_mode kf_mode; + int32_t kf_distance_min; + int32_t kf_distance_max; + + // Threads and Tiling (All Static) + int8_t threads; + int8_t rowmultithreading; + int8_t tile_columns; + int8_t tile_rows; + aom_tune_metric tune_metric; + aom_tune_content tune_content; + } _settings; + +#ifdef ENABLE_PROFILING + std::shared_ptr _profiler_copy; + std::shared_ptr _profiler_encode; + std::shared_ptr _profiler_packet; +#endif + + public: + aom_av1_instance(obs_data_t* settings, obs_encoder_t* self, bool is_hw); + virtual ~aom_av1_instance(); + + virtual void migrate(obs_data_t* settings, uint64_t version); + + virtual bool update(obs_data_t* settings); + + void log(); + + virtual bool get_extra_data(uint8_t** extra_data, size_t* size); + + virtual bool get_sei_data(uint8_t** sei_data, size_t* size); + + virtual void get_video_info(struct video_scale_info* info); + + virtual bool encode_video(encoder_frame* frame, encoder_packet* packet, bool* received_packet); + }; + + class aom_av1_factory : public obs::encoder_factory { + std::shared_ptr<::streamfx::util::library> _library; + + public: + aom_av1_factory(); + ~aom_av1_factory(); + + const char* get_name() override; + + void* create(obs_data_t* settings, obs_encoder_t* encoder, bool is_hw) override; + + void get_defaults2(obs_data_t* data) override; + + obs_properties_t* get_properties2(instance_t* data) override; + +#ifdef ENABLE_FRONTEND + static bool on_manual_open(obs_properties_t* props, obs_property_t* property, void* data); +#endif + + public: + // aom_codec.h + decltype(&aom_codec_version) libaom_codec_version; + decltype(&aom_codec_version_str) libaom_codec_version_str; + decltype(&aom_codec_version_extra_str) libaom_codec_version_extra_str; + decltype(&aom_codec_build_config) libaom_codec_build_config; + decltype(&aom_codec_iface_name) libaom_codec_iface_name; + decltype(&aom_codec_err_to_string) libaom_codec_err_to_string; + decltype(&aom_codec_error) libaom_codec_error; + decltype(&aom_codec_error_detail) libaom_codec_error_detail; + decltype(&aom_codec_destroy) libaom_codec_destroy; + decltype(&aom_codec_get_caps) libaom_codec_get_caps; + decltype(&aom_codec_control) libaom_codec_control; + decltype(&aom_codec_set_option) libaom_codec_set_option; + decltype(&aom_obu_type_to_string) libaom_obu_type_to_string; + + // aom_integer.h + decltype(&aom_uleb_size_in_bytes) libaom_uleb_size_in_bytes; + decltype(&aom_uleb_decode) libaom_uleb_decode; + decltype(&aom_uleb_encode) libaom_uleb_encode; + decltype(&aom_uleb_encode_fixed_size) libaom_uleb_encode_fixed_size; + + // aom_image.h + decltype(&aom_img_alloc) libaom_img_alloc; + decltype(&aom_img_wrap) libaom_img_wrap; + decltype(&aom_img_alloc_with_border) libaom_img_alloc_with_border; + decltype(&aom_img_set_rect) libaom_img_set_rect; + decltype(&aom_img_flip) libaom_img_flip; + decltype(&aom_img_free) libaom_img_free; + decltype(&aom_img_plane_width) libaom_img_plane_width; + decltype(&aom_img_plane_height) libaom_img_plane_height; + decltype(&aom_img_add_metadata) libaom_img_add_metadata; + decltype(&aom_img_get_metadata) libaom_img_get_metadata; + decltype(&aom_img_num_metadata) libaom_img_num_metadata; + decltype(&aom_img_remove_metadata) libaom_img_remove_metadata; + decltype(&aom_img_metadata_alloc) libaom_img_metadata_alloc; + decltype(&aom_img_metadata_free) libaom_img_metadata_free; + + // aom_encoder.h + decltype(&aom_codec_enc_init_ver) libaom_codec_enc_init_ver; + decltype(&aom_codec_enc_config_default) libaom_codec_enc_config_default; + decltype(&aom_codec_enc_config_set) libaom_codec_enc_config_set; + decltype(&aom_codec_get_global_headers) libaom_codec_get_global_headers; + decltype(&aom_codec_encode) libaom_codec_encode; + decltype(&aom_codec_set_cx_data_buf) libaom_codec_set_cx_data_buf; + decltype(&aom_codec_get_cx_data) libaom_codec_get_cx_data; + decltype(&aom_codec_get_preview_frame) libaom_codec_get_preview_frame; + + // aomcx.h + decltype(&aom_codec_av1_cx) libaom_codec_av1_cx; + + public: // Singleton + static void initialize(); + + static void finalize(); + + static std::shared_ptr get(); + }; +} // namespace streamfx::encoder::aom::av1 diff --git a/source/plugin.cpp b/source/plugin.cpp index 55bb572c..bc75c71d 100644 --- a/source/plugin.cpp +++ b/source/plugin.cpp @@ -28,6 +28,9 @@ #include "nvidia/cuda/nvidia-cuda-obs.hpp" #endif +#ifdef ENABLE_ENCODER_AOM_AV1 +#include "encoders/encoder-aom-av1.hpp" +#endif #ifdef ENABLE_ENCODER_FFMPEG #include "encoders/encoder-ffmpeg.hpp" #endif @@ -129,6 +132,9 @@ try { // Encoders { +#ifdef ENABLE_ENCODER_AOM_AV1 + streamfx::encoder::aom::av1::aom_av1_factory::initialize(); +#endif #ifdef ENABLE_ENCODER_FFMPEG using namespace streamfx::encoder::ffmpeg; ffmpeg_manager::initialize(); @@ -259,6 +265,9 @@ try { { #ifdef ENABLE_ENCODER_FFMPEG streamfx::encoder::ffmpeg::ffmpeg_manager::finalize(); +#endif +#ifdef ENABLE_ENCODER_AOM_AV1 + streamfx::encoder::aom::av1::aom_av1_factory::finalize(); #endif }