New upstream version 24.0.1+dfsg1

This commit is contained in:
Sebastian Ramacher 2019-09-22 23:19:10 +02:00
parent b14f9eae6d
commit 5a730d6ec3
842 changed files with 42245 additions and 33385 deletions

View file

@ -5,10 +5,20 @@ if(MSVC)
w32-pthreads)
endif()
option(ENABLE_FFMPEG_LOGGING "Enables obs-ffmpeg logging" OFF)
find_package(FFmpeg REQUIRED
COMPONENTS avcodec avfilter avdevice avutil swscale avformat swresample)
include_directories(${FFMPEG_INCLUDE_DIRS})
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/obs-ffmpeg-config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/obs-ffmpeg-config.h")
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(obs-ffmpeg_config_HEADERS
"${CMAKE_CURRENT_BINARY_DIR}/obs-ffmpeg-config.h")
set(obs-ffmpeg_HEADERS
obs-ffmpeg-formats.h
obs-ffmpeg-compat.h
@ -29,6 +39,11 @@ if(UNIX AND NOT APPLE)
${LIBVA_LBRARIES})
endif()
if(ENABLE_FFMPEG_LOGGING)
list(APPEND obs-ffmpeg_SOURCES
obs-ffmpeg-logging.c)
endif()
if(WIN32)
list(APPEND obs-ffmpeg_SOURCES
jim-nvenc.c
@ -38,6 +53,7 @@ if(WIN32)
endif()
add_library(obs-ffmpeg MODULE
${obs-ffmpeg_config_HEADERS}
${obs-ffmpeg_HEADERS}
${obs-ffmpeg_SOURCES})
target_link_libraries(obs-ffmpeg

View file

@ -1,82 +1,42 @@
#pragma once
static const enum AVPixelFormat i420_formats[] = {
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_YUV422P,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_NV21,
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_YUV422P,
AV_PIX_FMT_YUV444P, AV_PIX_FMT_NONE};
static const enum AVPixelFormat nv12_formats[] = {
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE};
static const enum AVPixelFormat i444_formats[] = {
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGRA,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_YUV444P, AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA,
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21, AV_PIX_FMT_NONE};
static const enum AVPixelFormat yuy2_formats[] = {
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE};
static const enum AVPixelFormat uyvy_formats[] = {
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_UYVY422, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_NONE};
static const enum AVPixelFormat rgba_formats[] = {
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGRA,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21, AV_PIX_FMT_NONE};
static const enum AVPixelFormat bgra_formats[] = {
AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGBA,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_UYVY422,
AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21,
AV_PIX_FMT_NONE
};
AV_PIX_FMT_BGRA, AV_PIX_FMT_RGBA, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUYV422, AV_PIX_FMT_UYVY422, AV_PIX_FMT_NV12,
AV_PIX_FMT_NV21, AV_PIX_FMT_NONE};
static enum AVPixelFormat get_best_format(
const enum AVPixelFormat *best,
const enum AVPixelFormat *formats)
static enum AVPixelFormat get_best_format(const enum AVPixelFormat *best,
const enum AVPixelFormat *formats)
{
while (*best != AV_PIX_FMT_NONE) {
enum AVPixelFormat best_format = *best;
@ -97,9 +57,8 @@ static enum AVPixelFormat get_best_format(
return AV_PIX_FMT_NONE;
}
static inline enum AVPixelFormat get_closest_format(
enum AVPixelFormat format,
const enum AVPixelFormat *formats)
static inline enum AVPixelFormat
get_closest_format(enum AVPixelFormat format, const enum AVPixelFormat *formats)
{
enum AVPixelFormat best_format = AV_PIX_FMT_NONE;

View file

@ -24,7 +24,6 @@ LocalFile="Локален файл"
Looping="Преповтаряне"
Input="Вход"
InputFormat="Формат за вход"
BufferingMB="Мрежова буферизация (MB)"
HardwareDecode="Използване на хардуерно декодиране, ако е налично"
Advanced="Разширено"
RestartWhenActivated="Възобновява възпроизвеждането когато източникът е активен"
@ -32,7 +31,6 @@ CloseFileWhenInactive="Затваряне на файла при неактив
ColorRange.Auto="Автоматично"
ColorRange.Partial="Частично"
ColorRange.Full="Пълно"
SpeedPercentage="Скорост (процент)"

View file

@ -29,7 +29,7 @@ LocalFile="Fitxer local"
Looping="Bucle"
Input="Entrada"
InputFormat="Format d'entrada"
BufferingMB="Memòria intermèdia de xarxa (MB)"
BufferingMB="Memòria intermèdia de xarxa"
HardwareDecode="Usa la descodificació per maquinari si és disponible"
ClearOnMediaEnd="No mostris res quan acabi la reproducció"
Advanced="Avançat"
@ -41,7 +41,7 @@ ColorRange.Auto="Automàtic"
ColorRange.Partial="Parcial"
ColorRange.Full="Màxim"
RestartMedia="Reinicia els mitjans"
SpeedPercentage="Velocitat (percentatge)"
SpeedPercentage="Velocitat"
Seekable="Cercable"
MediaFileFilter.AllMediaFiles="Tots els arxius multimèdia"

View file

@ -29,7 +29,7 @@ LocalFile="Místní soubor"
Looping="Opakovat"
Input="Vstup"
InputFormat="Formát vstupu"
BufferingMB="Vyrovnávací paměť pro síť (MB)"
BufferingMB="Vyrovnávací paměť pro síť"
HardwareDecode="Použít hardwarové dekódování, pokud je k dispozici"
ClearOnMediaEnd="Po skončení přehrávání nezobrazovat nic"
Advanced="Pokročilé"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatický"
ColorRange.Partial="Částečný"
ColorRange.Full="Celkový"
RestartMedia="Restartovat mediální zdroj"
SpeedPercentage="Rychlost (procenta)"
SpeedPercentage="Rychlost"
Seekable="Posouvatelné"
MediaFileFilter.AllMediaFiles="Všechny mediální soubory"

View file

@ -29,7 +29,7 @@ LocalFile="Lokal fil"
Looping="Gentagelse"
Input="Input"
InputFormat="Inputformat"
BufferingMB="Netværksbuffering (MB)"
BufferingMB="Netværksbuffering"
HardwareDecode="Benyt hardwareafkodning, når tilgængelig"
ClearOnMediaEnd="Vis intet, når afspilning afsluttes"
Advanced="Avanceret"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Delvis"
ColorRange.Full="Fuld"
RestartMedia="Genstart Media"
SpeedPercentage="Hastighed (procent)"
SpeedPercentage="Hastighed"
Seekable="Søgbar"
MediaFileFilter.AllMediaFiles="Alle mediefiler"

View file

@ -1,16 +1,16 @@
FFmpegOutput="FFmpeg-Ausgabe"
FFmpegAAC="FFmpeg-Standard-AAC-Kodierer"
FFmpegOpus="FFmpeg-Opus-Kodierer"
FFmpegOutput="FFmpegAusgabe"
FFmpegAAC="FFmpegStandardAACKodierer"
FFmpegOpus="FFmpegOpusKodierer"
Bitrate="Bitrate"
MaxBitrate="Max. Bitrate"
Preset="Voreinstellung"
RateControl="Qualitäts Regulierungsmethode"
KeyframeIntervalSec="Keyframeintervall in Sek. (0 = auto)"
KeyframeIntervalSec="Keyframeintervall in Sek. (0=automatisch)"
Lossless="Verlustfrei"
BFrames="Max. B-Frames"
BFrames="Max. BFrames"
NVENC.Use2Pass="Two-Pass-Kodierung verwenden"
NVENC.Use2Pass="TwoPassKodierung verwenden"
NVENC.Preset.default="Leistung"
NVENC.Preset.hq="Qualität"
NVENC.Preset.hp="Max. Leistung"
@ -18,30 +18,30 @@ NVENC.Preset.mq="Max. Qualität"
NVENC.Preset.ll="Niedrige Latenz"
NVENC.Preset.llhq="Niedrige Latenz + Qualität"
NVENC.Preset.llhp="Niedrige Latenz + Leistung"
NVENC.LookAhead="Look-ahead"
NVENC.LookAhead.ToolTip="Aktiviert dynamische B-Frames.\n\nWenn deaktiviert, wird der Kodierer immer die Anzahl der B-Frames verwenden, die in der „Max B-Frames“-Einstellung angegeben sind.\n\nWenn aktiviert, wird er die visuelle Qualität erhöhen, indem nur so viele B-Frames verwendet werden wie benötigt, bis zum Maximum,\nzu den Kosten einer erhöhten GPU-Nutzung."
NVENC.PsychoVisualTuning="Psycho-visuelle Optimierung"
NVENC.PsychoVisualTuning.ToolTip="Aktiviert Kodierereinstellungen, die die Verwendung der Bitrate für eine erhöhte wahrgenommene visuelle Qualität optimieren,\ninsbesondere in Situationen mit hoher Bewegung, zu Kosten einer erhöhten GPU-Nutzung."
NVENC.CQLevel="CQ-Level"
NVENC.LookAhead="Lookahead"
NVENC.LookAhead.ToolTip="Aktiviert dynamische BFrames.\n\nWenn deaktiviert, wird der Kodierer immer die Anzahl der BFrames verwenden, die in der „Max BFrames“Einstellung angegeben sind.\n\nWenn aktiviert, wird er die visuelle Qualität erhöhen, indem nur so viele BFrames verwendet werden wie benötigt, bis zum Maximum,\nzu den Kosten einer erhöhten GPUNutzung."
NVENC.PsychoVisualTuning="Psycho Visual Tuning"
NVENC.PsychoVisualTuning.ToolTip="Aktiviert Kodierereinstellungen, die die Verwendung der Bitrate für eine erhöhte wahrgenommene visuelle Qualität optimieren,\ninsbesondere in Situationen mit hoher Bewegung, zu Kosten einer erhöhten GPUNutzung."
NVENC.CQLevel="CQLevel"
FFmpegSource="Medienquelle"
LocalFile="Lokale Datei"
Looping="Endlosschleife"
Input="Eingabe"
InputFormat="Eingabeformat"
BufferingMB="Netzwerkpufferung (MB)"
BufferingMB="Netzwerkpufferung"
HardwareDecode="Hardwaredekodierung verwenden, falls verfügbar"
ClearOnMediaEnd="Nichts anzeigen, wenn Wiedergabe endet"
Advanced="Erweitert"
RestartWhenActivated="Wiedergabe erneut starten, wenn Quelle aktiviert wird"
CloseFileWhenInactive="Datei schließen, wenn inaktiv"
CloseFileWhenInactive.ToolTip="Schließt die Datei, wenn die Quelle im Stream oder der Aufnahme nicht angezeigt wird.\nDies ermöglicht, dass die Datei geändert werden kann, wenn die Quelle nicht aktiv ist,\n aber es gibt wahrscheinlich etwas Startverzögerung, wenn die Quelle reaktiviert wird."
ColorRange="YUV-Farbmatrix"
CloseFileWhenInactive.ToolTip="Schließt die Datei, wenn die Quelle im Stream oder der Aufnahme nicht angezeigt wird.\nDies ermöglicht, dass die Datei geändert werden kann.\nDafür können aber Startverzögerungen auftreten, wenn die Quelle reaktiviert wird."
ColorRange="YUVFarbmatrix"
ColorRange.Auto="Automatisch"
ColorRange.Partial="Teilweise"
ColorRange.Full="Voll"
RestartMedia="Medium neustarten"
SpeedPercentage="Geschwindigkeit (Prozent)"
SpeedPercentage="Geschwindigkeit"
Seekable="Durch­such­bar"
MediaFileFilter.AllMediaFiles="Alle Mediendateien"
@ -49,10 +49,10 @@ MediaFileFilter.VideoFiles="Videodateien"
MediaFileFilter.AudioFiles="Audiodateien"
MediaFileFilter.AllFiles="Alle Dateien"
ReplayBuffer="Replaypuffer"
ReplayBuffer="ReplayPuffer"
ReplayBuffer.Save="Replay speichern"
HelperProcessFailed="Der Aufnahmehelferprozeß kann nicht gestartet werden. Überprüfen Sie, ob OBS-Dateien nicht von einer Drittanbieter Antiviren- / Sicherheitssoftware blockiert oder entfernt wurden."
HelperProcessFailed="Der AufnahmeHelferProzess kann nicht gestartet werden. Überprüfen Sie, ob OBSDateien nicht von einer DrittanbieterAntiviren/Sicherheitssoftware blockiert oder entfernt werden."
UnableToWritePath="Kann nicht zu %1 schreiben. Vergewissern Sie sich, dass Sie einen Aufnahmepfad verwenden, für das Ihr Benutzerkonto Schreibrechte hat und dass genügend Speicherplatz zur Verfügung steht."
WarnWindowsDefender="Wenn Windows-10-Ransomware-Schutz aktiviert ist, kann dies auch den Fehler auslösen. Versuchen Sie, den überwachten Ordnerzugriff in Windows-Sicherheit → Viren- & Bedrohungsschutz auszuschalten."
WarnWindowsDefender="Wenn Windows10RansomwareSchutz aktiviert ist, kann dies auch den Fehler auslösen. Versuchen Sie, den überwachten Ordnerzugriff in WindowsSicherheit → Viren & Bedrohungsschutz auszuschalten."

View file

@ -16,7 +16,6 @@ LocalFile="Τοπικό αρχείο"
Looping="Επανάληψη"
Input="Είσοδος"
InputFormat="Μορφή Εισόδου"
BufferingMB="Μέγεθος προσωρινης αποθήκευσης Δικτύου (MB)"
HardwareDecode="Χρήση αποκωδικοποίησης υλικού όταν είναι διαθέσιμη"
Advanced="Σύνθετες επιλογές"
RestartWhenActivated="Επανεκκίνηση της αναπαραγωγής όταν η πηγή γίνεται ξανά ενεργή"
@ -27,7 +26,6 @@ ColorRange.Auto="Αυτόματο"
ColorRange.Partial="Μερικός"
ColorRange.Full="Πλήρης"
RestartMedia="Επανεκκίνηση Πολυμέσων"
SpeedPercentage="Ταχύτητα (τοις εκατό)"
Seekable="Παρεχόμενη"
MediaFileFilter.AllMediaFiles="Όλα τα αρχεία πολυμέσων"

View file

@ -29,7 +29,7 @@ LocalFile="Local File"
Looping="Loop"
Input="Input"
InputFormat="Input Format"
BufferingMB="Network Buffering (MB)"
BufferingMB="Network Buffering"
HardwareDecode="Use hardware decoding when available"
ClearOnMediaEnd="Show nothing when playback ends"
Advanced="Advanced"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Partial"
ColorRange.Full="Full"
RestartMedia="Restart Media"
SpeedPercentage="Speed (percent)"
SpeedPercentage="Speed"
Seekable="Seekable"
MediaFileFilter.AllMediaFiles="All Media Files"

View file

@ -29,7 +29,7 @@ LocalFile="Archivo local"
Looping="Bucle"
Input="Entrada"
InputFormat="Formato de entrada"
BufferingMB="Almacenamiento búfer de Red (MB)"
BufferingMB="Buffering de la red"
HardwareDecode="Utilizar la decodificación por hardware cuando esté disponible"
ClearOnMediaEnd="No mostrar nada al terminar la reproducción"
Advanced="Avanzado"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatico"
ColorRange.Partial="Parcial"
ColorRange.Full="Completo"
RestartMedia="Reiniciar Medio"
SpeedPercentage="Velocidad (porcentaje)"
SpeedPercentage="Velocidad"
Seekable="Buscable"
MediaFileFilter.AllMediaFiles="Todos los archivos multimedia"

View file

@ -29,7 +29,7 @@ LocalFile="Tokiko fitxategia"
Looping="Begizta"
Input="Sarrera"
InputFormat="Sarrera formatua"
BufferingMB="Sareko bufferreratzea (MB)"
BufferingMB="Sareko bufferreratzea"
HardwareDecode="Erabili hardware deskodeketa eskuragarri dagoenean"
ClearOnMediaEnd="Erreprodukzioa bukatzean ez erakutsi ezer"
Advanced="Aurreratua"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Partziala"
ColorRange.Full="Osoa"
RestartMedia="Berrabiarazi euskarria"
SpeedPercentage="Abiadura (ehunekoa)"
SpeedPercentage="Abiadura"
Seekable="Bilagai"
MediaFileFilter.AllMediaFiles="Multimedia-fitxategi guztiak"

View file

@ -24,12 +24,10 @@ LocalFile="فایل محلی"
Looping="چرخه"
Input="ورودی"
InputFormat="فرمت های ورودی"
BufferingMB="شبکه بافری (مگابایت)"
ColorRange.Auto="خودکار"
ColorRange.Partial="جزئی"
ColorRange.Full="کامل"
RestartMedia="راه اندازی مجدد رسانه ها"
SpeedPercentage="سرعت (درصد)"
Seekable="جستجوگر"
MediaFileFilter.AllMediaFiles="تمامی فایل های رسانه"

View file

@ -29,7 +29,6 @@ LocalFile="Paikallinen tiedosto"
Looping="Toista jatkuvasti"
Input="Sisääntulo"
InputFormat="Sisääntulon muoto"
BufferingMB="Verkon puskurointi (MB)"
HardwareDecode="Käytä laitteistotason purkua, kun mahdollista"
ClearOnMediaEnd="Älä näytä mitään kun toisto päättyy"
Advanced="Lisäasetukset"
@ -41,7 +40,6 @@ ColorRange.Auto="Automaattinen"
ColorRange.Partial="Osittainen"
ColorRange.Full="Täysi"
RestartMedia="Uudelleenkäynnistä media"
SpeedPercentage="Nopeus (prosentti)"
Seekable="Haettava"
MediaFileFilter.AllMediaFiles="Kaikki mediatiedostot"

View file

@ -16,7 +16,6 @@ LocalFile="Ang File na lokal"
Looping="I-Loop"
Input="Ang Input"
InputFormat="Ang Format ng Input"
BufferingMB="Nag buffering ang Network (MB)"
HardwareDecode="Gamitin ang hardware decoding kapag magagamit"
Advanced="I-Advanced"
RestartWhenActivated="Simulan mulang ang playback kapag aktibo ang pinagmulan"

View file

@ -29,7 +29,7 @@ LocalFile="Fichier local"
Looping="En boucle"
Input="Entrée"
InputFormat="Format d'entrée"
BufferingMB="Mémoire tampon réseau (Mo)"
BufferingMB="Tampon réseau (Mo)"
HardwareDecode="Utiliser le décodage matériel si possible"
ClearOnMediaEnd="Ne rien afficher lorsque la lecture se termine"
Advanced="Options avancées"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Partielle"
ColorRange.Full="Complète"
RestartMedia="Reprendre depuis le début"
SpeedPercentage="Vitesse (pourcentage)"
SpeedPercentage="Vitesse"
Seekable="Navigable"
MediaFileFilter.AllMediaFiles="Tous les fichiers multimédias"

View file

@ -25,7 +25,6 @@ LocalFile="Faidhle ionadail"
Looping="Lùb"
Input="Ion-chur"
InputFormat="Fòrmat an ion-chuir"
BufferingMB="Bufair an lìonraidh (MB)"
HardwareDecode="Cleachd dì-chòdachadh bathair-chruaidh ma bhios e ri fhaighinn"
Advanced="Adhartach"
RestartWhenActivated="Ath-thòisich a chluiche nuair a thig gnìomh on tùs"
@ -35,7 +34,6 @@ ColorRange.Auto="Fèin-obrachail"
ColorRange.Partial="Leth-phàirteach"
ColorRange.Full="Làn"
RestartMedia="Ath-thòisich am meadhan"
SpeedPercentage="Luaths (sa cheud)"
Seekable="Gabhaidh sireadh ann"
MediaFileFilter.AllMediaFiles="A h-uile faidhle meadhain"

View file

@ -1,17 +1,58 @@
FFmpegOutput="Saída de FFmpeg"
FFmpegAAC="Codificador AAC FFmpeg predefinido"
Bitrate="Velocidade de bits"
FFmpegAAC="Codificador AAC de FFmpeg predeterminado"
FFmpegOpus="Codificador Opus de FFmpeg"
Bitrate="Taxa de bits"
MaxBitrate="Taxa de bits máxima"
Preset="Preaxuste"
RateControl="Control da taxa"
KeyframeIntervalSec="Intervalo de fotogramas clave (segundos, 0=auto)"
Lossless="Sen perdas"
BFrames="Máximo de B-frames"
NVENC.Use2Pass="Usar codificación en dúas pasadas"
NVENC.Preset.default="Rendemento"
NVENC.Preset.hq="Calidade"
NVENC.Preset.hp="Máximo rendemento"
NVENC.Preset.mq="Máxima Calidade"
NVENC.Preset.ll="Baixa latencia"
NVENC.Preset.llhq="Calidade de baixa Latencia"
NVENC.Preset.llhp="Rendemento de baixa Latencia"
NVENC.LookAhead="Previsión"
NVENC.LookAhead.ToolTip="Activar B-frames dinámicos.\n\nSe está desactivado, o codificador empregará sempre o número de B-frames especificados na configuración «Máximo de B-frames».\n\nSe está activado, aumentará a calidade visual empregando só a cantidade de B-frames necesarios, ata o máximo,\nco custo do aumento de emprego da GPU."
NVENC.PsychoVisualTuning="Sintonización psico visual"
NVENC.PsychoVisualTuning.ToolTip="Activa os axustes do codificador que optimizan o uso da taxa de bits para aumentar a calidade visual percibida,\nespecialmente nas situacións con alto movemento, a costa de incrementar a utilización da GPU."
NVENC.CQLevel="Nivel de cuantificación constante"
FFmpegSource="Fonte multimedia"
LocalFile="Ficheiro local"
Looping="Bucle"
Input="Entrada"
InputFormat="Formato de entrada"
BufferingMB="Memoria intermedia de rede"
HardwareDecode="Utilizar a descodificación por hárdware cando estiver dispoñible"
ClearOnMediaEnd="Non amosar nada ao rematar a reprodución"
Advanced="Avanzado"
RestartWhenActivated="Reiniciar a reprodución cando fonte estea activa"
CloseFileWhenInactive="Pechar o ficheiro cando estea inactivo"
CloseFileWhenInactive.ToolTip="Pecha o ficheiro cando a fonte non se está a amosar na emisión ou na\ngravación. Isto permite cambiar o ficheiro cando a fonte non está activa,\nmais pode haber algún atraso de inicio cando se reactive a fonte."
ColorRange="Gama de cor YUV"
ColorRange.Auto="Automático"
ColorRange.Partial="Parcial"
ColorRange.Full="Total"
RestartMedia="Reiniciar multimedia"
SpeedPercentage="Velocidade"
Seekable="Buscábel"
MediaFileFilter.AllMediaFiles="Todos os ficheiros multimedia"
MediaFileFilter.VideoFiles="Ficheiros de vídeo"
MediaFileFilter.AudioFiles="Ficheiros de son"
MediaFileFilter.AllFiles="Todos os ficheiros"
ReplayBuffer="Reproducir a memoria intermedia"
ReplayBuffer.Save="Gardar a reprodución"
HelperProcessFailed="Non foi posíbel iniciar o proceso do axudante de gravación. Comprobe que os ficheiros do OBS non foron bloqueados ou eliminados por un antivirus ou software de seguridade de terceiros."
UnableToWritePath="Non foi posíbel escribir en %1. Asegúrese de que está a usar unha ruta de gravación na que a súa conta de usuario teña permisos de escritura e que haxa espazo abondo no disco."
WarnWindowsDefender="Se a protección do Ransomware do Windows está activada tamén pode causar este erro. Tente desactivar o acceso controlado ao cartafol nos axustes de seguridade do Windows / virus e protección contra ameazas."

View file

@ -29,7 +29,7 @@ LocalFile="Helyi fájl"
Looping="Ismétlés"
Input="Bemenet"
InputFormat="Bemeneti formátum"
BufferingMB="Hálózati pufferelés (MB)"
BufferingMB="Hálózati pufferelés"
HardwareDecode="Hardveres dekódolás használata, ha rendelkezésre áll"
ClearOnMediaEnd="Semmit se mutasson, a lejátszás végeztével"
Advanced="Haladó"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Részleges"
ColorRange.Full="Teljes"
RestartMedia="Media újraindítása"
SpeedPercentage="Sebesség (százalékos)"
SpeedPercentage="Sebesség"
Seekable="Kereshető"
MediaFileFilter.AllMediaFiles="Minden médiafájl"

View file

@ -29,7 +29,7 @@ LocalFile="File locale"
Looping="Ripeti"
Input="Input"
InputFormat="Formato dell'input"
BufferingMB="Buffering della rete (in MB)"
BufferingMB="Buffering della rete"
HardwareDecode="Utilizza la decodifica hardware quando disponibile"
ClearOnMediaEnd="Non mostrare nulla quando la riproduzione finisce"
Advanced="Avanzate"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatico"
ColorRange.Partial="Parziale"
ColorRange.Full="Intero"
RestartMedia="Riavvia media dall'inizio"
SpeedPercentage="Velocità (in percentuale)"
SpeedPercentage="Velocità"
Seekable="Ricercabile"
MediaFileFilter.AllMediaFiles="Tutti i file multimediali"

View file

@ -29,7 +29,7 @@ LocalFile="ローカルファイル"
Looping="繰り返し"
Input="入力"
InputFormat="入力フォーマット"
BufferingMB="ネットワークバッファリング (MB)"
BufferingMB="ネットワークバッファリング"
HardwareDecode="可能な場合ハードウェアデコードを使用"
ClearOnMediaEnd="再生終了時に何も表示しない"
Advanced="高度な設定"
@ -41,7 +41,7 @@ ColorRange.Auto="自動"
ColorRange.Partial="一部"
ColorRange.Full="全部"
RestartMedia="メディアを再開する"
SpeedPercentage="速度 (パーセント)"
SpeedPercentage="速度"
Seekable="シーク可能"
MediaFileFilter.AllMediaFiles="すべてのメディアファイル"

View file

@ -29,7 +29,7 @@ LocalFile="ადგილობრივი ფაილი"
Looping="დაუსრულებლად გამეორება"
Input="შეტანა"
InputFormat="შეტანის ფორმატი"
BufferingMB="ქსელის ბუფერიზაცია (მბაიტი)"
BufferingMB="ქსელის ბუფერიზაცია"
HardwareDecode="აპარატურული დაშიფვრის გამოყენება, ხელმისაწვდომობის შემთხვევაში"
ClearOnMediaEnd="აღარაფერი გამოჩნდეს, ჩვენების დასრულების შემდგომ"
Advanced="გაფართოებული"
@ -41,7 +41,7 @@ ColorRange.Auto="ავტომატური"
ColorRange.Partial="ნაწილობრივი"
ColorRange.Full="სრული"
RestartMedia="მასალის ხელახლა გაშვება"
SpeedPercentage="სიჩქარე (პროცენტი)"
SpeedPercentage="სიჩქარე"
Seekable="გადახვევით"
MediaFileFilter.AllMediaFiles="ყველა მასალა"

View file

@ -29,7 +29,7 @@ LocalFile="로컬 파일"
Looping="반복"
Input="입력"
InputFormat="입력 형식"
BufferingMB="네트워크 버퍼링 (MB)"
BufferingMB="네트워크 버퍼링"
HardwareDecode="가능한 경우 하드웨어 디코딩 사용"
ClearOnMediaEnd="재생이 끝나면 아무 것도 표시하지 않기"
Advanced="고급"
@ -41,7 +41,7 @@ ColorRange.Auto="자동"
ColorRange.Partial="부분"
ColorRange.Full="전체"
RestartMedia="미디어 다시재생"
SpeedPercentage="속도 (백분율)"
SpeedPercentage="속도"
Seekable="탐색 가능"
MediaFileFilter.AllMediaFiles="모든 미디어 파일"

View file

@ -29,7 +29,6 @@ LocalFile="Lokal fil"
Looping="Repeter"
Input="Inngang"
InputFormat="Inngangsformat"
BufferingMB="Nettverksbuffer (Mb)"
HardwareDecode="Bruk maskinvaredekoding når tilgjengelig"
ClearOnMediaEnd="Vis ingenting når avspillingen slutter"
Advanced="Avansert"
@ -41,7 +40,6 @@ ColorRange.Auto="Automatisk"
ColorRange.Partial="Delvis"
ColorRange.Full="Hel"
RestartMedia="Start media på nytt"
SpeedPercentage="Fart (prosent)"
Seekable="Søkbar"
MediaFileFilter.AllMediaFiles="Alle mediefiler"

View file

@ -29,7 +29,7 @@ LocalFile="Lokaal bestand"
Looping="Herhalen"
Input="Invoer"
InputFormat="Invoerformaat"
BufferingMB="Netwerk Buffering (MB)"
BufferingMB="Netwerkbuffering"
HardwareDecode="Gebruik hardware-decoding wanneer mogelijk"
ClearOnMediaEnd="Toon niets wanneer het afspelen eindigt"
Advanced="Geavanceerd"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatisch"
ColorRange.Partial="Gedeeltelijk"
ColorRange.Full="Volledig"
RestartMedia="Media herstarten"
SpeedPercentage="Snelheid (percentage)"
SpeedPercentage="Snelheid"
Seekable="Zoekbaar"
MediaFileFilter.AllMediaFiles="Alle mediabestanden"

View file

@ -29,7 +29,7 @@ LocalFile="Plik lokalny"
Looping="Pętla"
Input="Wejście"
InputFormat="Format wejściowy"
BufferingMB="Bufor sieciowy (MB)"
BufferingMB="Rozmiar bufora"
HardwareDecode="Użyj sprzętowego dekodowania gdy to możliwe"
ClearOnMediaEnd="Po zakończeniu odtwarzania nie pokazuj nic"
Advanced="Zaawansowane"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatycznie"
ColorRange.Partial="Częściowy"
ColorRange.Full="Pełny"
RestartMedia="Zrestartuj plik audio-wideo"
SpeedPercentage="Szybkość (procent)"
SpeedPercentage="Szybkość"
Seekable="Przeszukiwalny"
MediaFileFilter.AllMediaFiles="Wszystkie pliki multimedialne"

View file

@ -29,7 +29,7 @@ LocalFile="Arquivo Local"
Looping="Loop"
Input="Entrada"
InputFormat="Formato de entrada"
BufferingMB="Buffer de Rede (MB)"
BufferingMB="Buffering de rede"
HardwareDecode="Utilizar descodificação de hardware quando disponível"
ClearOnMediaEnd="Não mostrar nada quando terminar a reprodução"
Advanced="Avançado"
@ -41,7 +41,7 @@ ColorRange.Auto="Auto"
ColorRange.Partial="Parcial"
ColorRange.Full="Completo"
RestartMedia="Reiniciar Mídia"
SpeedPercentage="Velocidade (percentagem)"
SpeedPercentage="Velocidade"
Seekable="Procurável"
MediaFileFilter.AllMediaFiles="Todos Arquivos de Mídia"

View file

@ -30,7 +30,6 @@ ColorRange="Gama de cor YUV"
ColorRange.Auto="Auto"
ColorRange.Partial="Parcial"
ColorRange.Full="Completo"
SpeedPercentage="Velocidade (percentagem)"
MediaFileFilter.AllMediaFiles="Todos os Arquivos de Media"
MediaFileFilter.VideoFiles="Arquivos de Vídeo"

View file

@ -1,6 +1,7 @@
FFmpegOutput="Ieșire FFmpeg"
FFmpegAAC="Codificator AAC implicit FFmpeg"
Bitrate="Rată de biți"
MaxBitrate="Rată de biți maximă"
Preset="Presetare"
RateControl="Controlul ratei"
KeyframeIntervalSec="Interval de cadre cheie (secunde, 0=auto)"
@ -19,12 +20,11 @@ LocalFile="Fișier local"
Looping="Buclă"
Input="Intrare"
InputFormat="Format de intrare"
BufferingMB="Zonă tampon pentru rețea (MB)"
HardwareDecode="Folosește decodarea hardware când este disponibilă"
Advanced="Avansat"
RestartWhenActivated="Repornește redarea când sursa devine activă"
ColorRange="Gamă de culori YUV"
ColorRange.Auto="Auto"
ColorRange.Auto="Automată"
ColorRange.Partial="Parțială"
ColorRange.Full="Completă"

View file

@ -29,7 +29,7 @@ LocalFile="Локальный файл"
Looping="Повтор"
Input="Ввод"
InputFormat="Формат ввода"
BufferingMB="Сетевая буферизация (МБ)"
BufferingMB="Сетевая буферизация"
HardwareDecode="Использовать аппаратное декодирование при наличии"
ClearOnMediaEnd="Ничего не показывать, когда воспроизведение заканчивается"
Advanced="Дополнительно"
@ -41,7 +41,7 @@ ColorRange.Auto="Автоматически"
ColorRange.Partial="Частичный"
ColorRange.Full="Полный"
RestartMedia="Перезапустить медиа"
SpeedPercentage="Скорость (проценты)"
SpeedPercentage="Скорость"
Seekable="Перематываемый"
MediaFileFilter.AllMediaFiles="Все медиа-файлы"

View file

@ -16,7 +16,6 @@ LocalFile="Lokálny súbor"
Looping="Slučka"
Input="Vstup"
InputFormat="Vstupný formát"
BufferingMB="Sieťové zapisovanie do medzipamäte (MB)"
HardwareDecode="Použiť hardvérové dekódovanie podľa dostupnosti"
Advanced="Rozšírené"
RestartWhenActivated="Obnoviť prehrávanie pri aktivovaní zdroja"
@ -27,7 +26,6 @@ ColorRange.Auto="Automaticky"
ColorRange.Partial="Čiastočný"
ColorRange.Full="Plný"
RestartMedia="Reštartuj mediálny zdroj"
SpeedPercentage="Rýchlosť (v percentách)"
Seekable="Posúvateľný"
MediaFileFilter.AllMediaFiles="Všetky mediálne súbory"

View file

@ -1,17 +1,58 @@
FFmpegOutput="FFmpeg izhod"
FFmpegAAC="FFmpeg Prevzeti AAC Encoder"
Bitrate="Bitrate"
FFmpegOutput="Izhod FFmpeg"
FFmpegAAC="Privzeti kodirnik AAC FFmpeg"
FFmpegOpus="Kodirnik Opus FFmpeg"
Bitrate="Bitna hitrost"
MaxBitrate="Največja bitna hitrost"
Preset="Prednastavitev"
RateControl="Nadzor hitrosti"
KeyframeIntervalSec="Razmik med ključnimi sličicami (s, 0=samodejno)"
Lossless="Brezizgubno"
BFrames="Največje št. sličic B"
NVENC.Use2Pass="Uporabi kodiranje z dvema prehodoma"
NVENC.Preset.default="Zmogljivost"
NVENC.Preset.hq="Kakovost"
NVENC.Preset.hp="Največja zmogljivost"
NVENC.Preset.mq="Najboljša kakovost"
NVENC.Preset.ll="Nizka zakasnitev"
NVENC.Preset.llhq="Kakovost z nizko zakasnitvijo"
NVENC.Preset.llhp="Zmogljivost z nizko zakasnitvijo"
NVENC.LookAhead="Predvidevanje"
NVENC.LookAhead.ToolTip="Omogoči dinamične sličice B.\n\nČe je onemogočeno, bo kodirnik vedno uporabil število sličic B, ki so navedene v nastavitvi 'Največje št. sličic B'.\n\nČe je omogočeno, bo vidna kakovost izboljšana, ker bo uporabljeno samo toliko sličic B, kot je potrebno, do\nnajvečjega števila na račun dodatne porabe GPE-ja."
NVENC.PsychoVisualTuning="Psiho-vidno uglaševanje"
NVENC.PsychoVisualTuning.ToolTip="Omogoči nastavitve kodirnika, ki optimizirajo uporabo bitne hitrosti za povečano zaznavno vidno kakovost,\nposebno v razmerah z veliko gibanja na račun povečane porabe GPE-ja."
NVENC.CQLevel="Raven CQ"
FFmpegSource="Medijski Vir"
LocalFile="Lokalna Datoteka"
FFmpegSource="Predstavnostni vir"
LocalFile="Lokalna datoteka"
Looping="Ponavljaj"
Input="Vhod"
InputFormat="Format vnosa"
HardwareDecode="Uporabi strojno pospeševanje, ko je na voljo"
InputFormat="Oblika vhoda"
BufferingMB="Omrežno medpomnenje"
HardwareDecode="Uporabi strojno odkodiranje, ko je na voljo"
ClearOnMediaEnd="Ne prikaži ničesar, ko se predvajanje konča"
Advanced="Napredno"
RestartWhenActivated="Ponovno predvajaj, ki vir postane dejaven"
CloseFileWhenInactive="Zapri datoteko, ko ni dejaven"
CloseFileWhenInactive.ToolTip="Zapre datoteko, ki vir ni prikazan v pretoku ali posnetku. To omogoča\nspreminjanje datoteke, ko vir ni dejaven, vendar lahko pride do zakasnitve zagona,\nko vir ponovno postane dejaven."
ColorRange="Barvni razpon YUV"
ColorRange.Auto="Samodejno"
ColorRange.Partial="Delno"
ColorRange.Full="Polno"
RestartMedia="Pon. zaženi predstavnost"
SpeedPercentage="Hitrost"
Seekable="Omogoči iskanje"
MediaFileFilter.AllMediaFiles="Vse predstavne datoteke"
MediaFileFilter.VideoFiles="Videodatoteke"
MediaFileFilter.AudioFiles="Zvočne datoteke"
MediaFileFilter.AllFiles="Vse datoteke"
ReplayBuffer="Medpomn. za pon. predv."
ReplayBuffer.Save="Shrani pon. predv."
HelperProcessFailed="Pomožnega opravila za snemanje ni bilo mogoče začeti. Preverite, da protivirusni/varnostni program ni blokiral ali odstranil datotek OBS."
UnableToWritePath="Ni mogoče pisati v %1. Prepričajte se, da uporabljate pot za snemanje, na katero lahko vaš uporabniški račun zapisuje in da imate dovolj prostora na disku."
WarnWindowsDefender="To napako lahko povzroči tudi omogočena zaščita sistema Windows 10 proti izsiljevanju. V nastavitvah za varnost/zaščito proti virusom in grožnjami sistema Windows poizkusite izklopiti nadziran dostop do map."

View file

@ -29,7 +29,6 @@ LocalFile="Lokalna datoteka"
Looping="Ponavljanje"
Input="Ulaz"
InputFormat="Format ulaza"
BufferingMB="Baferovanje mreže (MB)"
HardwareDecode="Koristi hardversko enkodiranje kada je dostupno"
Advanced="Napredno"
RestartWhenActivated="Ponovi reprodukciju kada izvor postane aktivan"
@ -40,7 +39,6 @@ ColorRange.Auto="Automatski"
ColorRange.Partial="Delimični"
ColorRange.Full="Potpuni"
RestartMedia="Restartuj medij"
SpeedPercentage="Brzina (procenat)"
Seekable="Pretraživanje"
MediaFileFilter.AllMediaFiles="Sve medija datoteke"

View file

@ -29,7 +29,6 @@ LocalFile="Локална датотека"
Looping="Понављање"
Input="Улаз"
InputFormat="Формат улаза"
BufferingMB="Баферовање мреже (мегабајти)"
HardwareDecode="Користи хардверско енкодирање када је доступно"
Advanced="Напредно"
RestartWhenActivated="Понови репродукцију када извор постане активан"
@ -40,7 +39,6 @@ ColorRange.Auto="Аутоматски"
ColorRange.Partial="Делимични"
ColorRange.Full="Потпуни"
RestartMedia="Рестартуј медиј"
SpeedPercentage="Брзина (проценат)"
Seekable="Претраживање"
MediaFileFilter.AllMediaFiles="Све медија датотеке"

View file

@ -29,7 +29,7 @@ LocalFile="Lokal fil"
Looping="Upprepa"
Input="Infoga"
InputFormat="Inmatningsformat"
BufferingMB="Nätverksbuffring (MB)"
BufferingMB="Nätverksbuffring"
HardwareDecode="Använda hårdvareavkodning när tillgängligt"
ClearOnMediaEnd="Visa ingenting när uppspelningen slutar"
Advanced="Avancerat"
@ -41,7 +41,7 @@ ColorRange.Auto="Automatisk"
ColorRange.Partial="Delvis"
ColorRange.Full="Full"
RestartMedia="Starta om media"
SpeedPercentage="Hastighet (procent)"
SpeedPercentage="Hastighet"
Seekable="Sökbar"
MediaFileFilter.AllMediaFiles="Alla mediafiler"

View file

@ -16,7 +16,6 @@ LocalFile="Ang Lokal na File"
Looping="Silo"
Input="Pampasok"
InputFormat="Pampasok na Format"
BufferingMB="Ang Network Buffering (MB)"
HardwareDecode="Gamitin ang hardware sa pag-decode kapag itong magagamit na"
Advanced="Nauuna"
RestartWhenActivated="I-restart ang playback kapag ang pinagmulan ay naging aktibo na"

View file

@ -25,7 +25,6 @@ LocalFile="Yerel Dosya"
Looping="Döngü"
Input="Giriş"
InputFormat="Giriş Biçimi"
BufferingMB="Ağ Arabelleğe Alma (MB)"
HardwareDecode="Kullanılabilir ise, donanım kod çözmeyi kullan"
Advanced="Gelişmiş"
RestartWhenActivated="Yeniden oynatmayı kaynak etkin olduğunda yeniden başlat"
@ -36,7 +35,7 @@ ColorRange.Auto="Otomatik"
ColorRange.Partial="Kısmi"
ColorRange.Full="Tam"
RestartMedia="Ortamı Yeniden Başlat"
SpeedPercentage="Hız (yüzde)"
SpeedPercentage="Hız"
Seekable="Aranabilir"
MediaFileFilter.AllMediaFiles="Tüm Medya Dosyaları"

View file

@ -29,7 +29,7 @@ LocalFile="Локальний файл"
Looping="Циклічно відтворювати"
Input="Вхід"
InputFormat="Вхідний формат"
BufferingMB="Буферизація мережевого контенту (МБ)"
BufferingMB="Буферизація мережевого контенту"
HardwareDecode="Використовувати апаратне декодування, за наявності"
ClearOnMediaEnd="Не показувати джерело, коли відтворення завершено"
Advanced="Розширені параметри"
@ -41,7 +41,7 @@ ColorRange.Auto="Автовизначення"
ColorRange.Partial="Частковий"
ColorRange.Full="Повний"
RestartMedia="Перезапустити медіа"
SpeedPercentage="Швидкість (відсотків)"
SpeedPercentage="Швидкість"
Seekable="HTTP з перемотуванням"
MediaFileFilter.AllMediaFiles="Файли мультимедіа"

View file

@ -29,7 +29,7 @@ LocalFile="本地文件"
Looping="循环"
Input="输入"
InputFormat="输入格式"
BufferingMB="网络缓冲 (MB)"
BufferingMB="网络缓冲"
HardwareDecode="在可用时使用硬件解码"
ClearOnMediaEnd="播放结束时不显示任何内容"
Advanced="高级"
@ -41,7 +41,7 @@ ColorRange.Auto="自动"
ColorRange.Partial="局部"
ColorRange.Full="全部"
RestartMedia="重新启动媒体"
SpeedPercentage="速度(百分比)"
SpeedPercentage="速度"
Seekable="可搜索"
MediaFileFilter.AllMediaFiles="所有媒体文件"

View file

@ -29,7 +29,7 @@ LocalFile="本機檔案"
Looping="循環"
Input="輸入"
InputFormat="輸入格式"
BufferingMB="網路緩衝 (MB)"
BufferingMB="網路緩衝"
HardwareDecode="盡可能使用硬體解碼"
ClearOnMediaEnd="播放結束時不顯示任何內容"
Advanced="進階"
@ -41,7 +41,7 @@ ColorRange.Auto="自動"
ColorRange.Partial="部分"
ColorRange.Full="全部"
RestartMedia="重新播放媒體"
SpeedPercentage="速度 (百分比)"
SpeedPercentage="速度"
Seekable="可查找"
MediaFileFilter.AllMediaFiles="所有媒體檔案"

View file

@ -0,0 +1,3 @@
Language: Cpp
SortIncludes: false
DisableFormat: true

View file

@ -39,8 +39,8 @@
struct resize_buf {
uint8_t *buf;
size_t size;
size_t capacity;
size_t size;
size_t capacity;
};
static inline void resize_buf_resize(struct resize_buf *rb, size_t size)
@ -96,15 +96,15 @@ struct header {
};
struct ffmpeg_mux {
AVFormatContext *output;
AVStream *video_stream;
AVStream **audio_streams;
struct main_params params;
struct audio_params *audio;
struct header video_header;
struct header *audio_header;
int num_audio_streams;
bool initialized;
AVFormatContext *output;
AVStream *video_stream;
AVStream **audio_streams;
struct main_params params;
struct audio_params *audio;
struct header video_header;
struct header *audio_header;
int num_audio_streams;
bool initialized;
char error[4096];
};
@ -158,7 +158,7 @@ static void ffmpeg_mux_free(struct ffmpeg_mux *ffm)
}
static bool get_opt_str(int *p_argc, char ***p_argv, char **str,
const char *opt)
const char *opt)
{
int argc = *p_argc;
char **argv = *p_argv;
@ -187,7 +187,7 @@ static bool get_opt_int(int *p_argc, char ***p_argv, int *i, const char *opt)
}
static bool get_audio_params(struct audio_params *audio, int *argc,
char ***argv)
char ***argv)
{
if (!get_opt_str(argc, argv, &audio->name, "audio track name"))
return false;
@ -201,7 +201,7 @@ static bool get_audio_params(struct audio_params *audio, int *argc,
}
static bool init_params(int *argc, char ***argv, struct main_params *params,
struct audio_params **p_audio)
struct audio_params **p_audio)
{
struct audio_params *audio = NULL;
@ -228,7 +228,8 @@ static bool init_params(int *argc, char ***argv, struct main_params *params,
if (params->has_video) {
if (!get_opt_str(argc, argv, &params->vcodec, "video codec"))
return false;
if (!get_opt_int(argc, argv, &params->vbitrate,"video bitrate"))
if (!get_opt_int(argc, argv, &params->vbitrate,
"video bitrate"))
return false;
if (!get_opt_int(argc, argv, &params->width, "video width"))
return false;
@ -262,7 +263,7 @@ static bool init_params(int *argc, char ***argv, struct main_params *params,
}
static bool new_stream(struct ffmpeg_mux *ffm, AVStream **stream,
const char *name, enum AVCodecID *id)
const char *name, enum AVCodecID *id)
{
const AVCodecDescriptor *desc = avcodec_descriptor_get_by_name(name);
AVCodec *codec;
@ -282,11 +283,12 @@ static bool new_stream(struct ffmpeg_mux *ffm, AVStream **stream,
*stream = avformat_new_stream(ffm->output, codec);
if (!*stream) {
fprintf(stderr, "Couldn't create stream for encoder '%s'\n", name);
fprintf(stderr, "Couldn't create stream for encoder '%s'\n",
name);
return false;
}
(*stream)->id = ffm->output->nb_streams-1;
(*stream)->id = ffm->output->nb_streams - 1;
return true;
}
@ -296,21 +298,21 @@ static void create_video_stream(struct ffmpeg_mux *ffm)
void *extradata = NULL;
if (!new_stream(ffm, &ffm->video_stream, ffm->params.vcodec,
&ffm->output->oformat->video_codec))
&ffm->output->oformat->video_codec))
return;
if (ffm->video_header.size) {
extradata = av_memdup(ffm->video_header.data,
ffm->video_header.size);
ffm->video_header.size);
}
context = ffm->video_stream->codec;
context->bit_rate = ffm->params.vbitrate * 1000;
context->width = ffm->params.width;
context->height = ffm->params.height;
context->coded_width = ffm->params.width;
context->coded_height = ffm->params.height;
context->extradata = extradata;
context = ffm->video_stream->codec;
context->bit_rate = ffm->params.vbitrate * 1000;
context->width = ffm->params.width;
context->height = ffm->params.height;
context->coded_width = ffm->params.width;
context->coded_height = ffm->params.height;
context->extradata = extradata;
context->extradata_size = ffm->video_header.size;
context->time_base =
(AVRational){ffm->params.fps_den, ffm->params.fps_num};
@ -329,7 +331,7 @@ static void create_audio_stream(struct ffmpeg_mux *ffm, int idx)
void *extradata = NULL;
if (!new_stream(ffm, &stream, ffm->params.acodec,
&ffm->output->oformat->audio_codec))
&ffm->output->oformat->audio_codec))
return;
ffm->audio_streams[idx] = stream;
@ -340,19 +342,19 @@ static void create_audio_stream(struct ffmpeg_mux *ffm, int idx)
if (ffm->audio_header[idx].size) {
extradata = av_memdup(ffm->audio_header[idx].data,
ffm->audio_header[idx].size);
ffm->audio_header[idx].size);
}
context = stream->codec;
context->bit_rate = ffm->audio[idx].abitrate * 1000;
context->channels = ffm->audio[idx].channels;
context->sample_rate = ffm->audio[idx].sample_rate;
context->sample_fmt = AV_SAMPLE_FMT_S16;
context->time_base = stream->time_base;
context->extradata = extradata;
context = stream->codec;
context->bit_rate = ffm->audio[idx].abitrate * 1000;
context->channels = ffm->audio[idx].channels;
context->sample_rate = ffm->audio[idx].sample_rate;
context->sample_fmt = AV_SAMPLE_FMT_S16;
context->time_base = stream->time_base;
context->extradata = extradata;
context->extradata_size = ffm->audio_header[idx].size;
context->channel_layout =
av_get_default_channel_layout(context->channels);
av_get_default_channel_layout(context->channels);
//AVlib default channel layout for 4 channels is 4.0 ; fix for quad
if (context->channels == 4)
context->channel_layout = av_get_channel_layout("quad");
@ -372,7 +374,7 @@ static bool init_streams(struct ffmpeg_mux *ffm)
if (ffm->params.tracks) {
ffm->audio_streams =
calloc(1, ffm->params.tracks * sizeof(void*));
calloc(1, ffm->params.tracks * sizeof(void *));
for (int i = 0; i < ffm->params.tracks; i++)
create_audio_stream(ffm, i);
@ -392,20 +394,20 @@ static void set_header(struct header *header, uint8_t *data, size_t size)
}
static void ffmpeg_mux_header(struct ffmpeg_mux *ffm, uint8_t *data,
struct ffm_packet_info *info)
struct ffm_packet_info *info)
{
if (info->type == FFM_PACKET_VIDEO) {
set_header(&ffm->video_header, data, (size_t)info->size);
} else {
set_header(&ffm->audio_header[info->index], data,
(size_t)info->size);
(size_t)info->size);
}
}
static size_t safe_read(void *vdata, size_t size)
{
uint8_t *data = vdata;
size_t total = size;
size_t total = size;
while (size > 0) {
size_t in_size = fread(data, 1, size, stdin);
@ -470,20 +472,20 @@ static inline int open_output_file(struct ffmpeg_mux *ffm)
AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Couldn't open '%s', %s",
ffm->params.file, av_err2str(ret));
ffm->params.file, av_err2str(ret));
return FFM_ERROR;
}
}
strncpy(ffm->output->filename, ffm->params.file,
sizeof(ffm->output->filename));
sizeof(ffm->output->filename));
ffm->output->filename[sizeof(ffm->output->filename) - 1] = 0;
AVDictionary *dict = NULL;
if ((ret = av_dict_parse_string(&dict, ffm->params.muxer_settings,
"=", " ", 0))) {
if ((ret = av_dict_parse_string(&dict, ffm->params.muxer_settings, "=",
" ", 0))) {
fprintf(stderr, "Failed to parse muxer settings: %s\n%s",
av_err2str(ret), ffm->params.muxer_settings);
av_err2str(ret), ffm->params.muxer_settings);
av_dict_free(&dict);
}
@ -493,7 +495,7 @@ static inline int open_output_file(struct ffmpeg_mux *ffm)
AVDictionaryEntry *entry = NULL;
while ((entry = av_dict_get(dict, "", entry,
AV_DICT_IGNORE_SUFFIX)))
AV_DICT_IGNORE_SUFFIX)))
printf("\n\t%s=%s", entry->key, entry->value);
printf("\n");
@ -501,8 +503,8 @@ static inline int open_output_file(struct ffmpeg_mux *ffm)
ret = avformat_write_header(ffm->output, &dict);
if (ret < 0) {
fprintf(stderr, "Error opening '%s': %s",
ffm->params.file, av_err2str(ret));
fprintf(stderr, "Error opening '%s': %s", ffm->params.file,
av_err2str(ret));
av_dict_free(&dict);
@ -522,15 +524,15 @@ static int ffmpeg_mux_init_context(struct ffmpeg_mux *ffm)
output_format = av_guess_format(NULL, ffm->params.file, NULL);
if (output_format == NULL) {
fprintf(stderr, "Couldn't find an appropriate muxer for '%s'\n",
ffm->params.file);
ffm->params.file);
return FFM_ERROR;
}
ret = avformat_alloc_output_context2(&ffm->output, output_format,
NULL, NULL);
ret = avformat_alloc_output_context2(&ffm->output, output_format, NULL,
NULL);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize output context: %s\n",
av_err2str(ret));
av_err2str(ret));
return FFM_ERROR;
}
@ -552,7 +554,7 @@ static int ffmpeg_mux_init_context(struct ffmpeg_mux *ffm)
}
static int ffmpeg_mux_init_internal(struct ffmpeg_mux *ffm, int argc,
char *argv[])
char *argv[])
{
argc--;
argv++;
@ -564,7 +566,9 @@ static int ffmpeg_mux_init_internal(struct ffmpeg_mux *ffm, int argc,
calloc(1, sizeof(struct header) * ffm->params.tracks);
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
if (!ffmpeg_mux_get_extra_data(ffm))
return FFM_ERROR;
@ -587,7 +591,7 @@ static int ffmpeg_mux_init(struct ffmpeg_mux *ffm, int argc, char *argv[])
}
static inline int get_index(struct ffmpeg_mux *ffm,
struct ffm_packet_info *info)
struct ffm_packet_info *info)
{
if (info->type == FFM_PACKET_VIDEO) {
if (ffm->video_stream) {
@ -612,12 +616,12 @@ static inline int64_t rescale_ts(struct ffmpeg_mux *ffm, int64_t val, int idx)
AVStream *stream = get_stream(ffm, idx);
return av_rescale_q_rnd(val / stream->codec->time_base.num,
stream->codec->time_base, stream->time_base,
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
stream->codec->time_base, stream->time_base,
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
}
static inline bool ffmpeg_mux_packet(struct ffmpeg_mux *ffm, uint8_t *buf,
struct ffm_packet_info *info)
struct ffm_packet_info *info)
{
int idx = get_index(ffm, info);
AVPacket packet = {0};
@ -627,7 +631,7 @@ static inline bool ffmpeg_mux_packet(struct ffmpeg_mux *ffm, uint8_t *buf,
return true;
}
av_init_packet(&packet);
av_init_packet(&packet);
packet.data = buf;
packet.size = (int)info->size;
@ -660,16 +664,16 @@ int main(int argc, char *argv[])
SetErrorMode(SEM_FAILCRITICALERRORS);
argv = malloc(argc * sizeof(char*));
argv = malloc(argc * sizeof(char *));
for (int i = 0; i < argc; i++) {
size_t len = wcslen(argv_w[i]);
int size;
size = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], (int)len,
NULL, 0, NULL, NULL);
NULL, 0, NULL, NULL);
argv[i] = malloc(size + 1);
WideCharToMultiByte(CP_UTF8, 0, argv_w[i], (int)len, argv[i],
size + 1, NULL, NULL);
size + 1, NULL, NULL);
argv[i][size] = 0;
}

View file

@ -20,18 +20,18 @@
enum ffm_packet_type {
FFM_PACKET_VIDEO,
FFM_PACKET_AUDIO
FFM_PACKET_AUDIO,
};
#define FFM_SUCCESS 0
#define FFM_ERROR -1
#define FFM_SUCCESS 0
#define FFM_ERROR -1
#define FFM_UNSUPPORTED -2
struct ffm_packet_info {
int64_t pts;
int64_t dts;
uint32_t size;
uint32_t index;
int64_t pts;
int64_t dts;
uint32_t size;
uint32_t index;
enum ffm_packet_type type;
bool keyframe;
bool keyframe;
};

View file

@ -7,16 +7,16 @@ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
NV_ENCODE_API_FUNCTION_LIST nv = {NV_ENCODE_API_FUNCTION_LIST_VER};
NV_CREATE_INSTANCE_FUNC nv_create_instance = NULL;
#define error(format, ...) \
blog(LOG_ERROR, "[jim-nvenc] " format, ##__VA_ARGS__)
#define error(format, ...) blog(LOG_ERROR, "[jim-nvenc] " format, ##__VA_ARGS__)
static inline bool nv_failed(NVENCSTATUS err, const char *func, const char *call)
static inline bool nv_failed(NVENCSTATUS err, const char *func,
const char *call)
{
if (err == NV_ENC_SUCCESS)
return false;
error("%s: %s failed: %d (%s)", func, call, (int)err,
nv_error_name(err));
nv_error_name(err));
return true;
}
@ -24,7 +24,7 @@ static inline bool nv_failed(NVENCSTATUS err, const char *func, const char *call
bool load_nvenc_lib(void)
{
if (sizeof(void*) == 8) {
if (sizeof(void *) == 8) {
nvenc_lib = os_dlopen("nvEncodeAPI64.dll");
} else {
nvenc_lib = os_dlopen("nvEncodeAPI.dll");
@ -42,40 +42,41 @@ static void *load_nv_func(const char *func)
return func_ptr;
}
typedef NVENCSTATUS (NVENCAPI *NV_MAX_VER_FUNC)(uint32_t*);
typedef NVENCSTATUS(NVENCAPI *NV_MAX_VER_FUNC)(uint32_t *);
const char *nv_error_name(NVENCSTATUS err)
{
#define RETURN_CASE(x) \
case x: return #x
case x: \
return #x
switch (err) {
RETURN_CASE(NV_ENC_SUCCESS);
RETURN_CASE(NV_ENC_ERR_NO_ENCODE_DEVICE);
RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_DEVICE);
RETURN_CASE(NV_ENC_ERR_INVALID_ENCODERDEVICE);
RETURN_CASE(NV_ENC_ERR_INVALID_DEVICE);
RETURN_CASE(NV_ENC_ERR_DEVICE_NOT_EXIST);
RETURN_CASE(NV_ENC_ERR_INVALID_PTR);
RETURN_CASE(NV_ENC_ERR_INVALID_EVENT);
RETURN_CASE(NV_ENC_ERR_INVALID_PARAM);
RETURN_CASE(NV_ENC_ERR_INVALID_CALL);
RETURN_CASE(NV_ENC_ERR_OUT_OF_MEMORY);
RETURN_CASE(NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_PARAM);
RETURN_CASE(NV_ENC_ERR_LOCK_BUSY);
RETURN_CASE(NV_ENC_ERR_NOT_ENOUGH_BUFFER);
RETURN_CASE(NV_ENC_ERR_INVALID_VERSION);
RETURN_CASE(NV_ENC_ERR_MAP_FAILED);
RETURN_CASE(NV_ENC_ERR_NEED_MORE_INPUT);
RETURN_CASE(NV_ENC_ERR_ENCODER_BUSY);
RETURN_CASE(NV_ENC_ERR_EVENT_NOT_REGISTERD);
RETURN_CASE(NV_ENC_ERR_GENERIC);
RETURN_CASE(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY);
RETURN_CASE(NV_ENC_ERR_UNIMPLEMENTED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_REGISTER_FAILED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_REGISTERED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_MAPPED);
RETURN_CASE(NV_ENC_SUCCESS);
RETURN_CASE(NV_ENC_ERR_NO_ENCODE_DEVICE);
RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_DEVICE);
RETURN_CASE(NV_ENC_ERR_INVALID_ENCODERDEVICE);
RETURN_CASE(NV_ENC_ERR_INVALID_DEVICE);
RETURN_CASE(NV_ENC_ERR_DEVICE_NOT_EXIST);
RETURN_CASE(NV_ENC_ERR_INVALID_PTR);
RETURN_CASE(NV_ENC_ERR_INVALID_EVENT);
RETURN_CASE(NV_ENC_ERR_INVALID_PARAM);
RETURN_CASE(NV_ENC_ERR_INVALID_CALL);
RETURN_CASE(NV_ENC_ERR_OUT_OF_MEMORY);
RETURN_CASE(NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
RETURN_CASE(NV_ENC_ERR_UNSUPPORTED_PARAM);
RETURN_CASE(NV_ENC_ERR_LOCK_BUSY);
RETURN_CASE(NV_ENC_ERR_NOT_ENOUGH_BUFFER);
RETURN_CASE(NV_ENC_ERR_INVALID_VERSION);
RETURN_CASE(NV_ENC_ERR_MAP_FAILED);
RETURN_CASE(NV_ENC_ERR_NEED_MORE_INPUT);
RETURN_CASE(NV_ENC_ERR_ENCODER_BUSY);
RETURN_CASE(NV_ENC_ERR_EVENT_NOT_REGISTERD);
RETURN_CASE(NV_ENC_ERR_GENERIC);
RETURN_CASE(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY);
RETURN_CASE(NV_ENC_ERR_UNIMPLEMENTED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_REGISTER_FAILED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_REGISTERED);
RETURN_CASE(NV_ENC_ERR_RESOURCE_NOT_MAPPED);
}
#undef RETURN_CASE
@ -91,8 +92,8 @@ static inline bool init_nvenc_internal(void)
return success;
initialized = true;
NV_MAX_VER_FUNC nv_max_ver = (NV_MAX_VER_FUNC)
load_nv_func("NvEncodeAPIGetMaxSupportedVersion");
NV_MAX_VER_FUNC nv_max_ver = (NV_MAX_VER_FUNC)load_nv_func(
"NvEncodeAPIGetMaxSupportedVersion");
if (!nv_max_ver) {
return false;
}
@ -102,16 +103,16 @@ static inline bool init_nvenc_internal(void)
return false;
}
uint32_t cur_ver =
(NVENCAPI_MAJOR_VERSION << 4) | NVENCAPI_MINOR_VERSION;
uint32_t cur_ver = (NVENCAPI_MAJOR_VERSION << 4) |
NVENCAPI_MINOR_VERSION;
if (cur_ver > ver) {
error("Current driver version does not support this NVENC "
"version, please upgrade your driver");
"version, please upgrade your driver");
return false;
}
nv_create_instance = (NV_CREATE_INSTANCE_FUNC)
load_nv_func("NvEncodeAPICreateInstance");
nv_create_instance = (NV_CREATE_INSTANCE_FUNC)load_nv_func(
"NvEncodeAPICreateInstance");
if (!nv_create_instance) {
return false;
}

View file

@ -12,17 +12,16 @@
#define EXTRA_BUFFERS 5
#define do_log(level, format, ...) \
#define do_log(level, format, ...) \
blog(level, "[jim-nvenc: '%s'] " format, \
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
#define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
#define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
#define error_hr(msg) \
error("%s: %s: 0x%08lX", __FUNCTION__, msg, (uint32_t)hr);
#define error_hr(msg) error("%s: %s: 0x%08lX", __FUNCTION__, msg, (uint32_t)hr);
struct nv_bitstream;
struct nv_texture;
@ -39,57 +38,57 @@ struct handle_tex {
struct nvenc_data {
obs_encoder_t *encoder;
void *session;
void *session;
NV_ENC_INITIALIZE_PARAMS params;
NV_ENC_CONFIG config;
size_t buf_count;
size_t output_delay;
size_t buffers_queued;
size_t next_bitstream;
size_t cur_bitstream;
bool encode_started;
bool first_packet;
bool can_change_bitrate;
bool bframes;
NV_ENC_CONFIG config;
size_t buf_count;
size_t output_delay;
size_t buffers_queued;
size_t next_bitstream;
size_t cur_bitstream;
bool encode_started;
bool first_packet;
bool can_change_bitrate;
bool bframes;
DARRAY(struct nv_bitstream) bitstreams;
DARRAY(struct nv_texture) textures;
DARRAY(struct handle_tex) input_textures;
struct circlebuf dts_list;
DARRAY(struct nv_texture) textures;
DARRAY(struct handle_tex) input_textures;
struct circlebuf dts_list;
DARRAY(uint8_t) packet_data;
int64_t packet_pts;
bool packet_keyframe;
int64_t packet_pts;
bool packet_keyframe;
ID3D11Device *device;
ID3D11Device *device;
ID3D11DeviceContext *context;
uint32_t cx;
uint32_t cy;
uint8_t *header;
size_t header_size;
size_t header_size;
uint8_t *sei;
size_t sei_size;
size_t sei_size;
};
/* ------------------------------------------------------------------------- */
/* Bitstream Buffer */
struct nv_bitstream {
void *ptr;
void *ptr;
HANDLE event;
};
static inline bool nv_failed(struct nvenc_data *enc, NVENCSTATUS err,
const char *func, const char *call)
const char *func, const char *call)
{
if (err == NV_ENC_SUCCESS)
return false;
error("%s: %s failed: %d (%s)", func, call, (int)err,
nv_error_name(err));
nv_error_name(err));
return true;
}
@ -97,7 +96,8 @@ static inline bool nv_failed(struct nvenc_data *enc, NVENCSTATUS err,
static bool nv_bitstream_init(struct nvenc_data *enc, struct nv_bitstream *bs)
{
NV_ENC_CREATE_BITSTREAM_BUFFER buf = {NV_ENC_CREATE_BITSTREAM_BUFFER_VER};
NV_ENC_CREATE_BITSTREAM_BUFFER buf = {
NV_ENC_CREATE_BITSTREAM_BUFFER_VER};
NV_ENC_EVENT_PARAMS params = {NV_ENC_EVENT_PARAMS_VER};
HANDLE event = NULL;
@ -126,7 +126,7 @@ fail:
}
if (buf.bitstreamBuffer) {
nv.nvEncDestroyBitstreamBuffer(enc->session,
buf.bitstreamBuffer);
buf.bitstreamBuffer);
}
return false;
}
@ -147,9 +147,9 @@ static void nv_bitstream_free(struct nvenc_data *enc, struct nv_bitstream *bs)
/* Texture Resource */
struct nv_texture {
void *res;
void *res;
ID3D11Texture2D *tex;
void *mapped_res;
void *mapped_res;
};
static bool nv_texture_init(struct nvenc_data *enc, struct nv_texture *nvtex)
@ -159,13 +159,13 @@ static bool nv_texture_init(struct nvenc_data *enc, struct nv_texture *nvtex)
HRESULT hr;
D3D11_TEXTURE2D_DESC desc = {0};
desc.Width = enc->cx;
desc.Height = enc->cy;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_NV12;
desc.SampleDesc.Count = 1;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
desc.Width = enc->cx;
desc.Height = enc->cy;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_NV12;
desc.SampleDesc.Count = 1;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
hr = device->lpVtbl->CreateTexture2D(device, &desc, NULL, &tex);
if (FAILED(hr)) {
@ -176,11 +176,11 @@ static bool nv_texture_init(struct nvenc_data *enc, struct nv_texture *nvtex)
tex->lpVtbl->SetEvictionPriority(tex, DXGI_RESOURCE_PRIORITY_MAXIMUM);
NV_ENC_REGISTER_RESOURCE res = {NV_ENC_REGISTER_RESOURCE_VER};
res.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
res.resourceToRegister = tex;
res.width = enc->cx;
res.height = enc->cy;
res.bufferFormat = NV_ENC_BUFFER_FORMAT_NV12;
res.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
res.resourceToRegister = tex;
res.width = enc->cx;
res.height = enc->cy;
res.bufferFormat = NV_ENC_BUFFER_FORMAT_NV12;
if (NV_FAILED(nv.nvEncRegisterResource(enc->session, &res))) {
tex->lpVtbl->Release(tex);
@ -197,7 +197,7 @@ static void nv_texture_free(struct nvenc_data *enc, struct nv_texture *nvtex)
if (nvtex->res) {
if (nvtex->mapped_res) {
nv.nvEncUnmapInputResource(enc->session,
nvtex->mapped_res);
nvtex->mapped_res);
}
nv.nvEncUnregisterResource(enc->session, nvtex->res);
nvtex->tex->lpVtbl->Release(nvtex->tex);
@ -235,13 +235,16 @@ static bool nvenc_update(void *data, obs_data_t *settings)
int bitrate = (int)obs_data_get_int(settings, "bitrate");
enc->config.rcParams.averageBitRate = bitrate * 1000;
enc->config.rcParams.maxBitRate = bitrate * 1000;
enc->config.rcParams.maxBitRate = bitrate * 1000;
NV_ENC_RECONFIGURE_PARAMS params = {0};
params.version = NV_ENC_RECONFIGURE_PARAMS_VER;
params.reInitEncodeParams = enc->params;
params.version = NV_ENC_RECONFIGURE_PARAMS_VER;
params.reInitEncodeParams = enc->params;
params.resetEncoder = 1;
params.forceIDR = 1;
if (FAILED(nv.nvEncReconfigureEncoder(enc->session, &params))) {
if (NV_FAILED(nv.nvEncReconfigureEncoder(enc->session,
&params))) {
return false;
}
}
@ -261,28 +264,28 @@ static HANDLE get_lib(struct nvenc_data *enc, const char *lib)
return mod;
}
typedef HRESULT (WINAPI *CREATEDXGIFACTORY1PROC)(REFIID, void **);
typedef HRESULT(WINAPI *CREATEDXGIFACTORY1PROC)(REFIID, void **);
static bool init_d3d11(struct nvenc_data *enc, obs_data_t *settings)
{
HMODULE dxgi = get_lib(enc, "DXGI.dll");
HMODULE d3d11 = get_lib(enc, "D3D11.dll");
CREATEDXGIFACTORY1PROC create_dxgi;
HMODULE dxgi = get_lib(enc, "DXGI.dll");
HMODULE d3d11 = get_lib(enc, "D3D11.dll");
CREATEDXGIFACTORY1PROC create_dxgi;
PFN_D3D11_CREATE_DEVICE create_device;
IDXGIFactory1 *factory;
IDXGIAdapter *adapter;
ID3D11Device *device;
ID3D11DeviceContext *context;
HRESULT hr;
IDXGIFactory1 *factory;
IDXGIAdapter *adapter;
ID3D11Device *device;
ID3D11DeviceContext *context;
HRESULT hr;
if (!dxgi || !d3d11) {
return false;
}
create_dxgi = (CREATEDXGIFACTORY1PROC)GetProcAddress(dxgi,
"CreateDXGIFactory1");
create_device = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(d3d11,
"D3D11CreateDevice");
create_dxgi = (CREATEDXGIFACTORY1PROC)GetProcAddress(
dxgi, "CreateDXGIFactory1");
create_device = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(
d3d11, "D3D11CreateDevice");
if (!create_dxgi || !create_device) {
error("Failed to load D3D11/DXGI procedures");
@ -302,8 +305,8 @@ static bool init_d3d11(struct nvenc_data *enc, obs_data_t *settings)
return false;
}
hr = create_device(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0,
NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context);
hr = create_device(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, NULL, 0,
D3D11_SDK_VERSION, &device, NULL, &context);
adapter->lpVtbl->Release(adapter);
if (FAILED(hr)) {
error_hr("D3D11CreateDevice failed");
@ -317,8 +320,8 @@ static bool init_d3d11(struct nvenc_data *enc, obs_data_t *settings)
static bool init_session(struct nvenc_data *enc)
{
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS params =
{NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS params = {
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
params.device = enc->device;
params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX;
params.apiVersion = NVENCAPI_VERSION;
@ -384,19 +387,19 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
}
if (astrcmpi(rc, "lossless") == 0) {
nv_preset = hp
? NV_ENC_PRESET_LOSSLESS_HP_GUID
: NV_ENC_PRESET_LOSSLESS_DEFAULT_GUID;
nv_preset = hp ? NV_ENC_PRESET_LOSSLESS_HP_GUID
: NV_ENC_PRESET_LOSSLESS_DEFAULT_GUID;
}
/* -------------------------- */
/* get preset default config */
NV_ENC_PRESET_CONFIG preset_config =
{NV_ENC_PRESET_CONFIG_VER, {NV_ENC_CONFIG_VER}};
NV_ENC_PRESET_CONFIG preset_config = {NV_ENC_PRESET_CONFIG_VER,
{NV_ENC_CONFIG_VER}};
err = nv.nvEncGetEncodePresetConfig(enc->session,
NV_ENC_CODEC_H264_GUID, nv_preset, &preset_config);
NV_ENC_CODEC_H264_GUID, nv_preset,
&preset_config);
if (nv_failed(enc, err, __FUNCTION__, "nvEncGetEncodePresetConfig")) {
return false;
}
@ -406,9 +409,8 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
enc->config = preset_config.presetCfg;
uint32_t gop_size = (keyint_sec)
? keyint_sec * voi->fps_num / voi->fps_den
: 250;
uint32_t gop_size =
(keyint_sec) ? keyint_sec * voi->fps_num / voi->fps_den : 250;
NV_ENC_INITIALIZE_PARAMS *params = &enc->params;
NV_ENC_CONFIG *config = &enc->config;
@ -461,9 +463,8 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
enc->can_change_bitrate =
nv_get_cap(enc, NV_ENC_CAPS_SUPPORT_DYN_BITRATE_CHANGE);
config->rcParams.rateControlMode = twopass
? NV_ENC_PARAMS_RC_VBR_HQ
: NV_ENC_PARAMS_RC_VBR;
config->rcParams.rateControlMode = twopass ? NV_ENC_PARAMS_RC_VBR_HQ
: NV_ENC_PARAMS_RC_VBR;
if (astrcmpi(rc, "cqp") == 0 || astrcmpi(rc, "lossless") == 0) {
if (astrcmpi(rc, "lossless") == 0)
@ -480,9 +481,9 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
} else if (astrcmpi(rc, "vbr") != 0) { /* CBR by default */
h264_config->outputBufferingPeriodSEI = 1;
config->rcParams.rateControlMode = twopass
? NV_ENC_PARAMS_RC_2_PASS_QUALITY
: NV_ENC_PARAMS_RC_CBR;
config->rcParams.rateControlMode =
twopass ? NV_ENC_PARAMS_RC_2_PASS_QUALITY
: NV_ENC_PARAMS_RC_CBR;
}
h264_config->outputPictureTimingSEI = 1;
@ -508,7 +509,7 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
}
enc->buf_count = config->frameIntervalP +
config->rcParams.lookaheadDepth + EXTRA_BUFFERS;
config->rcParams.lookaheadDepth + EXTRA_BUFFERS;
enc->output_delay = enc->buf_count - 1;
info("settings:\n"
@ -524,12 +525,8 @@ static bool init_encoder(struct nvenc_data *enc, obs_data_t *settings)
"\tb-frames: %d\n"
"\tlookahead: %s\n"
"\tpsycho_aq: %s\n",
rc, bitrate, cqp, gop_size,
preset, profile,
enc->cx, enc->cy,
twopass ? "true" : "false",
bf,
lookahead ? "true" : "false",
rc, bitrate, cqp, gop_size, preset, profile, enc->cx, enc->cy,
twopass ? "true" : "false", bf, lookahead ? "true" : "false",
psycho_aq ? "true" : "false");
return true;
@ -662,12 +659,13 @@ static void nvenc_destroy(void *data)
}
static ID3D11Texture2D *get_tex_from_handle(struct nvenc_data *enc,
uint32_t handle, IDXGIKeyedMutex **km_out)
uint32_t handle,
IDXGIKeyedMutex **km_out)
{
ID3D11Device *device = enc->device;
ID3D11Device *device = enc->device;
IDXGIKeyedMutex *km;
ID3D11Texture2D *input_tex;
HRESULT hr;
HRESULT hr;
for (size_t i = 0; i < enc->input_textures.num; i++) {
struct handle_tex *ht = &enc->input_textures.array[i];
@ -678,15 +676,16 @@ static ID3D11Texture2D *get_tex_from_handle(struct nvenc_data *enc,
}
hr = device->lpVtbl->OpenSharedResource(device,
(HANDLE)(uintptr_t)handle,
&IID_ID3D11Texture2D, &input_tex);
(HANDLE)(uintptr_t)handle,
&IID_ID3D11Texture2D,
&input_tex);
if (FAILED(hr)) {
error_hr("OpenSharedResource failed");
return NULL;
}
hr = input_tex->lpVtbl->QueryInterface(input_tex, &IID_IDXGIKeyedMutex,
&km);
&km);
if (FAILED(hr)) {
error_hr("QueryInterface(IDXGIKeyedMutex) failed");
input_tex->lpVtbl->Release(input_tex);
@ -694,7 +693,7 @@ static ID3D11Texture2D *get_tex_from_handle(struct nvenc_data *enc,
}
input_tex->lpVtbl->SetEvictionPriority(input_tex,
DXGI_RESOURCE_PRIORITY_MAXIMUM);
DXGI_RESOURCE_PRIORITY_MAXIMUM);
*km_out = km;
@ -717,15 +716,15 @@ static bool get_encoded_packet(struct nvenc_data *enc, bool finalize)
size_t count = finalize ? enc->buffers_queued : 1;
for (size_t i = 0; i < count; i++) {
size_t cur_bs_idx = enc->cur_bitstream;
struct nv_bitstream *bs = &enc->bitstreams.array[cur_bs_idx];
struct nv_texture *nvtex = &enc->textures.array[cur_bs_idx];
size_t cur_bs_idx = enc->cur_bitstream;
struct nv_bitstream *bs = &enc->bitstreams.array[cur_bs_idx];
struct nv_texture *nvtex = &enc->textures.array[cur_bs_idx];
/* ---------------- */
NV_ENC_LOCK_BITSTREAM lock = {NV_ENC_LOCK_BITSTREAM_VER};
lock.outputBitstream = bs->ptr;
lock.doNotWait = false;
lock.outputBitstream = bs->ptr;
lock.doNotWait = false;
if (NV_FAILED(nv.nvEncLockBitstream(s, &lock))) {
return false;
@ -736,19 +735,17 @@ static bool get_encoded_packet(struct nvenc_data *enc, bool finalize)
size_t size;
enc->first_packet = false;
obs_extract_avc_headers(
lock.bitstreamBufferPtr,
lock.bitstreamSizeInBytes,
&new_packet, &size,
&enc->header, &enc->header_size,
&enc->sei, &enc->sei_size);
obs_extract_avc_headers(lock.bitstreamBufferPtr,
lock.bitstreamSizeInBytes,
&new_packet, &size,
&enc->header, &enc->header_size,
&enc->sei, &enc->sei_size);
da_copy_array(enc->packet_data, new_packet, size);
bfree(new_packet);
} else {
da_copy_array(enc->packet_data,
lock.bitstreamBufferPtr,
lock.bitstreamSizeInBytes);
da_copy_array(enc->packet_data, lock.bitstreamBufferPtr,
lock.bitstreamSizeInBytes);
}
enc->packet_pts = (int64_t)lock.outputTimeStamp;
@ -781,18 +778,19 @@ static bool get_encoded_packet(struct nvenc_data *enc, bool finalize)
}
static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
uint64_t lock_key, uint64_t *next_key,
struct encoder_packet *packet, bool *received_packet)
uint64_t lock_key, uint64_t *next_key,
struct encoder_packet *packet,
bool *received_packet)
{
struct nvenc_data *enc = data;
ID3D11Device *device = enc->device;
struct nvenc_data *enc = data;
ID3D11Device *device = enc->device;
ID3D11DeviceContext *context = enc->context;
ID3D11Texture2D *input_tex;
ID3D11Texture2D *output_tex;
IDXGIKeyedMutex *km;
struct nv_texture *nvtex;
ID3D11Texture2D *input_tex;
ID3D11Texture2D *output_tex;
IDXGIKeyedMutex *km;
struct nv_texture *nvtex;
struct nv_bitstream *bs;
NVENCSTATUS err;
NVENCSTATUS err;
if (handle == GS_INVALID_HANDLE) {
error("Encode failed: bad texture handle");
@ -800,10 +798,10 @@ static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
return false;
}
bs = &enc->bitstreams.array[enc->next_bitstream];
bs = &enc->bitstreams.array[enc->next_bitstream];
nvtex = &enc->textures.array[enc->next_bitstream];
input_tex = get_tex_from_handle(enc, handle, &km);
input_tex = get_tex_from_handle(enc, handle, &km);
output_tex = nvtex->tex;
if (!input_tex) {
@ -823,9 +821,8 @@ static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
km->lpVtbl->AcquireSync(km, lock_key, INFINITE);
context->lpVtbl->CopyResource(context,
(ID3D11Resource *)output_tex,
(ID3D11Resource *)input_tex);
context->lpVtbl->CopyResource(context, (ID3D11Resource *)output_tex,
(ID3D11Resource *)input_tex);
km->lpVtbl->ReleaseSync(km, *next_key);
@ -833,7 +830,7 @@ static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
/* map output tex so nvenc can use it */
NV_ENC_MAP_INPUT_RESOURCE map = {NV_ENC_MAP_INPUT_RESOURCE_VER};
map.registeredResource = nvtex->res;
map.registeredResource = nvtex->res;
if (NV_FAILED(nv.nvEncMapInputResource(enc->session, &map))) {
return false;
}
@ -844,15 +841,15 @@ static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
/* do actual encode call */
NV_ENC_PIC_PARAMS params = {0};
params.version = NV_ENC_PIC_PARAMS_VER;
params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
params.inputBuffer = nvtex->mapped_res;
params.bufferFmt = NV_ENC_BUFFER_FORMAT_NV12;
params.inputTimeStamp = (uint64_t)pts;
params.inputWidth = enc->cx;
params.inputHeight = enc->cy;
params.outputBitstream = bs->ptr;
params.completionEvent = bs->event;
params.version = NV_ENC_PIC_PARAMS_VER;
params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
params.inputBuffer = nvtex->mapped_res;
params.bufferFmt = NV_ENC_BUFFER_FORMAT_NV12;
params.inputTimeStamp = (uint64_t)pts;
params.inputWidth = enc->cx;
params.inputHeight = enc->cy;
params.outputBitstream = bs->ptr;
params.completionEvent = bs->event;
err = nv.nvEncEncodePicture(enc->session, &params);
if (err != NV_ENC_SUCCESS && err != NV_ENC_ERR_NEED_MORE_INPUT) {
@ -886,11 +883,11 @@ static bool nvenc_encode_tex(void *data, uint32_t handle, int64_t pts,
dts -= packet->timebase_num;
*received_packet = true;
packet->data = enc->packet_data.array;
packet->size = enc->packet_data.num;
packet->type = OBS_ENCODER_VIDEO;
packet->pts = enc->packet_pts;
packet->dts = dts;
packet->data = enc->packet_data.array;
packet->size = enc->packet_data.num;
packet->type = OBS_ENCODER_VIDEO;
packet->pts = enc->packet_pts;
packet->dts = dts;
packet->keyframe = enc->packet_keyframe;
} else {
*received_packet = false;
@ -911,7 +908,7 @@ static bool nvenc_extra_data(void *data, uint8_t **header, size_t *size)
}
*header = enc->header;
*size = enc->header_size;
*size = enc->header_size;
return true;
}
@ -923,23 +920,23 @@ static bool nvenc_sei_data(void *data, uint8_t **sei, size_t *size)
return false;
}
*sei = enc->sei;
*sei = enc->sei;
*size = enc->sei_size;
return true;
}
struct obs_encoder_info nvenc_info = {
.id = "jim_nvenc",
.codec = "h264",
.type = OBS_ENCODER_VIDEO,
.caps = OBS_ENCODER_CAP_PASS_TEXTURE,
.get_name = nvenc_get_name,
.create = nvenc_create,
.destroy = nvenc_destroy,
.update = nvenc_update,
.encode_texture = nvenc_encode_tex,
.get_defaults = nvenc_defaults,
.get_properties = nvenc_properties,
.get_extra_data = nvenc_extra_data,
.get_sei_data = nvenc_sei_data,
.id = "jim_nvenc",
.codec = "h264",
.type = OBS_ENCODER_VIDEO,
.caps = OBS_ENCODER_CAP_PASS_TEXTURE | OBS_ENCODER_CAP_DYN_BITRATE,
.get_name = nvenc_get_name,
.create = nvenc_create,
.destroy = nvenc_destroy,
.update = nvenc_update,
.encode_texture = nvenc_encode_tex,
.get_defaults = nvenc_defaults,
.get_properties = nvenc_properties,
.get_extra_data = nvenc_extra_data,
.get_sei_data = nvenc_sei_data,
};

View file

@ -4,9 +4,10 @@
#include <windows.h>
#include <obs-module.h>
#include "nvEncodeAPI.h"
#include "external/nvEncodeAPI.h"
typedef NVENCSTATUS (NVENCAPI *NV_CREATE_INSTANCE_FUNC)(NV_ENCODE_API_FUNCTION_LIST*);
typedef NVENCSTATUS(NVENCAPI *NV_CREATE_INSTANCE_FUNC)(
NV_ENCODE_API_FUNCTION_LIST *);
extern const char *nv_error_name(NVENCSTATUS err);
extern NV_ENCODE_API_FUNCTION_LIST nv;

View file

@ -26,68 +26,82 @@
#include "obs-ffmpeg-formats.h"
#include "obs-ffmpeg-compat.h"
#define do_log(level, format, ...) \
blog(level, "[FFmpeg %s encoder: '%s'] " format, \
enc->type, \
obs_encoder_get_name(enc->encoder), \
##__VA_ARGS__)
#define do_log(level, format, ...) \
blog(level, "[FFmpeg %s encoder: '%s'] " format, enc->type, \
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
struct enc_encoder {
obs_encoder_t *encoder;
obs_encoder_t *encoder;
const char *type;
const char *type;
AVCodec *codec;
AVCodecContext *context;
AVCodec *codec;
AVCodecContext *context;
uint8_t *samples[MAX_AV_PLANES];
AVFrame *aframe;
int64_t total_samples;
uint8_t *samples[MAX_AV_PLANES];
AVFrame *aframe;
int64_t total_samples;
DARRAY(uint8_t) packet_buffer;
DARRAY(uint8_t) packet_buffer;
size_t audio_planes;
size_t audio_size;
size_t audio_planes;
size_t audio_size;
int frame_size; /* pretty much always 1024 for AAC */
int frame_size_bytes;
int frame_size; /* pretty much always 1024 for AAC */
int frame_size_bytes;
};
static inline uint64_t convert_speaker_layout(enum speaker_layout layout)
{
switch (layout) {
case SPEAKERS_UNKNOWN: return 0;
case SPEAKERS_MONO: return AV_CH_LAYOUT_MONO;
case SPEAKERS_STEREO: return AV_CH_LAYOUT_STEREO;
case SPEAKERS_2POINT1: return AV_CH_LAYOUT_SURROUND;
case SPEAKERS_4POINT0: return AV_CH_LAYOUT_4POINT0;
case SPEAKERS_4POINT1: return AV_CH_LAYOUT_4POINT1;
case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1_BACK;
case SPEAKERS_7POINT1: return AV_CH_LAYOUT_7POINT1;
case SPEAKERS_UNKNOWN:
return 0;
case SPEAKERS_MONO:
return AV_CH_LAYOUT_MONO;
case SPEAKERS_STEREO:
return AV_CH_LAYOUT_STEREO;
case SPEAKERS_2POINT1:
return AV_CH_LAYOUT_SURROUND;
case SPEAKERS_4POINT0:
return AV_CH_LAYOUT_4POINT0;
case SPEAKERS_4POINT1:
return AV_CH_LAYOUT_4POINT1;
case SPEAKERS_5POINT1:
return AV_CH_LAYOUT_5POINT1_BACK;
case SPEAKERS_7POINT1:
return AV_CH_LAYOUT_7POINT1;
}
/* shouldn't get here */
return 0;
}
static inline enum speaker_layout convert_ff_channel_layout(uint64_t channel_layout)
static inline enum speaker_layout
convert_ff_channel_layout(uint64_t channel_layout)
{
switch (channel_layout) {
case AV_CH_LAYOUT_MONO: return SPEAKERS_MONO;
case AV_CH_LAYOUT_STEREO: return SPEAKERS_STEREO;
case AV_CH_LAYOUT_SURROUND: return SPEAKERS_2POINT1;
case AV_CH_LAYOUT_4POINT0: return SPEAKERS_4POINT0;
case AV_CH_LAYOUT_4POINT1: return SPEAKERS_4POINT1;
case AV_CH_LAYOUT_5POINT1_BACK: return SPEAKERS_5POINT1;
case AV_CH_LAYOUT_7POINT1: return SPEAKERS_7POINT1;
case AV_CH_LAYOUT_MONO:
return SPEAKERS_MONO;
case AV_CH_LAYOUT_STEREO:
return SPEAKERS_STEREO;
case AV_CH_LAYOUT_SURROUND:
return SPEAKERS_2POINT1;
case AV_CH_LAYOUT_4POINT0:
return SPEAKERS_4POINT0;
case AV_CH_LAYOUT_4POINT1:
return SPEAKERS_4POINT1;
case AV_CH_LAYOUT_5POINT1_BACK:
return SPEAKERS_5POINT1;
case AV_CH_LAYOUT_7POINT1:
return SPEAKERS_7POINT1;
}
/* shouldn't get here */
return SPEAKERS_UNKNOWN;
return SPEAKERS_UNKNOWN;
}
static const char *aac_getname(void *unused)
@ -121,7 +135,7 @@ static bool initialize_codec(struct enc_encoder *enc)
{
int ret;
enc->aframe = av_frame_alloc();
enc->aframe = av_frame_alloc();
if (!enc->aframe) {
warn("Failed to allocate audio frame");
return false;
@ -144,7 +158,7 @@ static bool initialize_codec(struct enc_encoder *enc)
enc->frame_size_bytes = enc->frame_size * (int)enc->audio_size;
ret = av_samples_alloc(enc->samples, NULL, enc->context->channels,
enc->frame_size, enc->context->sample_fmt, 0);
enc->frame_size, enc->context->sample_fmt, 0);
if (ret < 0) {
warn("Failed to create audio buffer: %s", av_err2str(ret));
return false;
@ -158,11 +172,11 @@ static void init_sizes(struct enc_encoder *enc, audio_t *audio)
const struct audio_output_info *aoi;
enum audio_format format;
aoi = audio_output_get_info(audio);
aoi = audio_output_get_info(audio);
format = convert_ffmpeg_sample_format(enc->context->sample_fmt);
enc->audio_planes = get_audio_planes(format, aoi->speakers);
enc->audio_size = get_audio_size(format, aoi->speakers, 1);
enc->audio_size = get_audio_size(format, aoi->speakers, 1);
}
#ifndef MIN
@ -170,22 +184,24 @@ static void init_sizes(struct enc_encoder *enc, audio_t *audio)
#endif
static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder,
const char *type, const char *alt)
const char *type, const char *alt)
{
struct enc_encoder *enc;
int bitrate = (int)obs_data_get_int(settings, "bitrate");
audio_t *audio = obs_encoder_audio(encoder);
int bitrate = (int)obs_data_get_int(settings, "bitrate");
audio_t *audio = obs_encoder_audio(encoder);
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
avcodec_register_all();
#endif
enc = bzalloc(sizeof(struct enc_encoder));
enc = bzalloc(sizeof(struct enc_encoder));
enc->encoder = encoder;
enc->codec = avcodec_find_encoder_by_name(type);
enc->type = type;
enc->codec = avcodec_find_encoder_by_name(type);
enc->type = type;
if (!enc->codec && alt) {
enc->codec = avcodec_find_encoder_by_name(alt);
enc->type = alt;
enc->type = alt;
}
blog(LOG_INFO, "---------------------------------");
@ -206,14 +222,15 @@ static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder,
goto fail;
}
enc->context->bit_rate = bitrate * 1000;
enc->context->bit_rate = bitrate * 1000;
const struct audio_output_info *aoi;
aoi = audio_output_get_info(audio);
enc->context->channels = (int)audio_output_get_channels(audio);
enc->context->channels = (int)audio_output_get_channels(audio);
enc->context->channel_layout = convert_speaker_layout(aoi->speakers);
enc->context->sample_rate = audio_output_get_sample_rate(audio);
enc->context->sample_fmt = enc->codec->sample_fmts ?
enc->codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
enc->context->sample_fmt = enc->codec->sample_fmts
? enc->codec->sample_fmts[0]
: AV_SAMPLE_FMT_FLTP;
/* check to make sure sample rate is supported */
if (enc->codec->supported_samplerates) {
@ -239,9 +256,9 @@ static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder,
}
info("bitrate: %" PRId64 ", channels: %d, channel_layout: %x\n",
(int64_t)enc->context->bit_rate / 1000,
(int)enc->context->channels,
(unsigned int)enc->context->channel_layout);
(int64_t)enc->context->bit_rate / 1000,
(int)enc->context->channels,
(unsigned int)enc->context->channel_layout);
init_sizes(enc, audio);
@ -268,22 +285,23 @@ static void *opus_create(obs_data_t *settings, obs_encoder_t *encoder)
return enc_create(settings, encoder, "libopus", "opus");
}
static bool do_encode(struct enc_encoder *enc,
struct encoder_packet *packet, bool *received_packet)
static bool do_encode(struct enc_encoder *enc, struct encoder_packet *packet,
bool *received_packet)
{
AVRational time_base = {1, enc->context->sample_rate};
AVPacket avpacket = {0};
int got_packet;
int ret;
AVPacket avpacket = {0};
int got_packet;
int ret;
enc->aframe->nb_samples = enc->frame_size;
enc->aframe->pts = av_rescale_q(enc->total_samples,
(AVRational){1, enc->context->sample_rate},
enc->context->time_base);
enc->aframe->pts = av_rescale_q(
enc->total_samples, (AVRational){1, enc->context->sample_rate},
enc->context->time_base);
ret = avcodec_fill_audio_frame(enc->aframe, enc->context->channels,
enc->context->sample_fmt, enc->samples[0],
enc->frame_size_bytes * enc->context->channels, 1);
ret = avcodec_fill_audio_frame(
enc->aframe, enc->context->channels, enc->context->sample_fmt,
enc->samples[0], enc->frame_size_bytes * enc->context->channels,
1);
if (ret < 0) {
warn("avcodec_fill_audio_frame failed: %s", av_err2str(ret));
return false;
@ -302,7 +320,7 @@ static bool do_encode(struct enc_encoder *enc,
ret = 0;
#else
ret = avcodec_encode_audio2(enc->context, &avpacket, enc->aframe,
&got_packet);
&got_packet);
#endif
if (ret < 0) {
warn("avcodec_encode_audio2 failed: %s", av_err2str(ret));
@ -316,8 +334,8 @@ static bool do_encode(struct enc_encoder *enc,
da_resize(enc->packet_buffer, 0);
da_push_back_array(enc->packet_buffer, avpacket.data, avpacket.size);
packet->pts = rescale_ts(avpacket.pts, enc->context, time_base);
packet->dts = rescale_ts(avpacket.dts, enc->context, time_base);
packet->pts = rescale_ts(avpacket.pts, enc->context, time_base);
packet->dts = rescale_ts(avpacket.dts, enc->context, time_base);
packet->data = enc->packet_buffer.array;
packet->size = avpacket.size;
packet->type = OBS_ENCODER_AUDIO;
@ -328,7 +346,7 @@ static bool do_encode(struct enc_encoder *enc,
}
static bool enc_encode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
struct encoder_packet *packet, bool *received_packet)
{
struct enc_encoder *enc = data;
@ -348,8 +366,8 @@ static obs_properties_t *enc_properties(void *unused)
UNUSED_PARAMETER(unused);
obs_properties_t *props = obs_properties_create();
obs_properties_add_int(props, "bitrate",
obs_module_text("Bitrate"), 64, 1024, 32);
obs_properties_add_int(props, "bitrate", obs_module_text("Bitrate"), 64,
1024, 32);
return props;
}
@ -358,7 +376,7 @@ static bool enc_extra_data(void *data, uint8_t **extra_data, size_t *size)
struct enc_encoder *enc = data;
*extra_data = enc->context->extradata;
*size = enc->context->extradata_size;
*size = enc->context->extradata_size;
return true;
}
@ -367,41 +385,42 @@ static void enc_audio_info(void *data, struct audio_convert_info *info)
struct enc_encoder *enc = data;
info->format = convert_ffmpeg_sample_format(enc->context->sample_fmt);
info->samples_per_sec = (uint32_t)enc->context->sample_rate;
info->speakers = convert_ff_channel_layout(enc->context->channel_layout);
info->speakers =
convert_ff_channel_layout(enc->context->channel_layout);
}
static size_t enc_frame_size(void *data)
{
struct enc_encoder *enc =data;
struct enc_encoder *enc = data;
return enc->frame_size;
}
struct obs_encoder_info aac_encoder_info = {
.id = "ffmpeg_aac",
.type = OBS_ENCODER_AUDIO,
.codec = "AAC",
.get_name = aac_getname,
.create = aac_create,
.destroy = enc_destroy,
.encode = enc_encode,
.id = "ffmpeg_aac",
.type = OBS_ENCODER_AUDIO,
.codec = "AAC",
.get_name = aac_getname,
.create = aac_create,
.destroy = enc_destroy,
.encode = enc_encode,
.get_frame_size = enc_frame_size,
.get_defaults = enc_defaults,
.get_defaults = enc_defaults,
.get_properties = enc_properties,
.get_extra_data = enc_extra_data,
.get_audio_info = enc_audio_info
.get_audio_info = enc_audio_info,
};
struct obs_encoder_info opus_encoder_info = {
.id = "ffmpeg_opus",
.type = OBS_ENCODER_AUDIO,
.codec = "opus",
.get_name = opus_getname,
.create = opus_create,
.destroy = enc_destroy,
.encode = enc_encode,
.id = "ffmpeg_opus",
.type = OBS_ENCODER_AUDIO,
.codec = "opus",
.get_name = opus_getname,
.create = opus_create,
.destroy = enc_destroy,
.encode = enc_encode,
.get_frame_size = enc_frame_size,
.get_defaults = enc_defaults,
.get_defaults = enc_defaults,
.get_properties = enc_properties,
.get_extra_data = enc_extra_data,
.get_audio_info = enc_audio_info
.get_audio_info = enc_audio_info,
};

View file

@ -6,18 +6,20 @@
* a is the major version
* b and c the minor and micro versions of libav
* d and e the minor and micro versions of FFmpeg */
#define LIBAVCODEC_VERSION_CHECK( a, b, c, d, e ) \
( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \
(LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) )
#define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \
((LIBAVCODEC_VERSION_MICRO < 100 && \
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c)) || \
(LIBAVCODEC_VERSION_MICRO >= 100 && \
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e)))
#if !LIBAVCODEC_VERSION_CHECK(54, 28, 0, 59, 100)
# define avcodec_free_frame av_freep
#define avcodec_free_frame av_freep
#endif
#if LIBAVCODEC_VERSION_INT < 0x371c01
# define av_frame_alloc avcodec_alloc_frame
# define av_frame_unref avcodec_get_frame_defaults
# define av_frame_free avcodec_free_frame
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_unref avcodec_get_frame_defaults
#define av_frame_free avcodec_free_frame
#endif
#if LIBAVCODEC_VERSION_MAJOR >= 57

View file

@ -0,0 +1,17 @@
#ifndef ON
#define ON 1
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef OFF
#define OFF 0
#endif
#ifndef FALSE
#define FALSE 0
#endif
#define ENABLE_FFMPEG_LOGGING @ENABLE_FFMPEG_LOGGING@

View file

@ -1,61 +1,110 @@
#pragma once
static inline int64_t rescale_ts(int64_t val, AVCodecContext *context,
AVRational new_base)
AVRational new_base)
{
return av_rescale_q_rnd(val, context->time_base, new_base,
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
}
static inline enum AVPixelFormat obs_to_ffmpeg_video_format(
enum video_format format)
static inline enum AVPixelFormat
obs_to_ffmpeg_video_format(enum video_format format)
{
switch (format) {
case VIDEO_FORMAT_NONE: return AV_PIX_FMT_NONE;
case VIDEO_FORMAT_I444: return AV_PIX_FMT_YUV444P;
case VIDEO_FORMAT_I420: return AV_PIX_FMT_YUV420P;
case VIDEO_FORMAT_NV12: return AV_PIX_FMT_NV12;
case VIDEO_FORMAT_YVYU: return AV_PIX_FMT_NONE;
case VIDEO_FORMAT_YUY2: return AV_PIX_FMT_YUYV422;
case VIDEO_FORMAT_UYVY: return AV_PIX_FMT_UYVY422;
case VIDEO_FORMAT_RGBA: return AV_PIX_FMT_RGBA;
case VIDEO_FORMAT_BGRA: return AV_PIX_FMT_BGRA;
case VIDEO_FORMAT_BGRX: return AV_PIX_FMT_BGRA;
case VIDEO_FORMAT_Y800: return AV_PIX_FMT_GRAY8;
case VIDEO_FORMAT_I444:
return AV_PIX_FMT_YUV444P;
case VIDEO_FORMAT_I420:
return AV_PIX_FMT_YUV420P;
case VIDEO_FORMAT_NV12:
return AV_PIX_FMT_NV12;
case VIDEO_FORMAT_YUY2:
return AV_PIX_FMT_YUYV422;
case VIDEO_FORMAT_UYVY:
return AV_PIX_FMT_UYVY422;
case VIDEO_FORMAT_RGBA:
return AV_PIX_FMT_RGBA;
case VIDEO_FORMAT_BGRA:
return AV_PIX_FMT_BGRA;
case VIDEO_FORMAT_BGRX:
return AV_PIX_FMT_BGRA;
case VIDEO_FORMAT_Y800:
return AV_PIX_FMT_GRAY8;
case VIDEO_FORMAT_BGR3:
return AV_PIX_FMT_BGR24;
case VIDEO_FORMAT_I422:
return AV_PIX_FMT_YUV422P;
case VIDEO_FORMAT_I40A:
return AV_PIX_FMT_YUVA420P;
case VIDEO_FORMAT_I42A:
return AV_PIX_FMT_YUVA422P;
case VIDEO_FORMAT_YUVA:
return AV_PIX_FMT_YUVA444P;
case VIDEO_FORMAT_NONE:
case VIDEO_FORMAT_YVYU:
case VIDEO_FORMAT_AYUV:
/* not supported by FFmpeg */
return AV_PIX_FMT_NONE;
}
return AV_PIX_FMT_NONE;
}
static inline enum video_format ffmpeg_to_obs_video_format(
enum AVPixelFormat format)
static inline enum video_format
ffmpeg_to_obs_video_format(enum AVPixelFormat format)
{
switch (format) {
case AV_PIX_FMT_YUV444P: return VIDEO_FORMAT_I444;
case AV_PIX_FMT_YUV420P: return VIDEO_FORMAT_I420;
case AV_PIX_FMT_NV12: return VIDEO_FORMAT_NV12;
case AV_PIX_FMT_YUYV422: return VIDEO_FORMAT_YUY2;
case AV_PIX_FMT_UYVY422: return VIDEO_FORMAT_UYVY;
case AV_PIX_FMT_RGBA: return VIDEO_FORMAT_RGBA;
case AV_PIX_FMT_BGRA: return VIDEO_FORMAT_BGRA;
case AV_PIX_FMT_GRAY8: return VIDEO_FORMAT_Y800;
case AV_PIX_FMT_YUV444P:
return VIDEO_FORMAT_I444;
case AV_PIX_FMT_YUV420P:
return VIDEO_FORMAT_I420;
case AV_PIX_FMT_NV12:
return VIDEO_FORMAT_NV12;
case AV_PIX_FMT_YUYV422:
return VIDEO_FORMAT_YUY2;
case AV_PIX_FMT_UYVY422:
return VIDEO_FORMAT_UYVY;
case AV_PIX_FMT_RGBA:
return VIDEO_FORMAT_RGBA;
case AV_PIX_FMT_BGRA:
return VIDEO_FORMAT_BGRA;
case AV_PIX_FMT_GRAY8:
return VIDEO_FORMAT_Y800;
case AV_PIX_FMT_BGR24:
return VIDEO_FORMAT_BGR3;
case AV_PIX_FMT_YUV422P:
return VIDEO_FORMAT_I422;
case AV_PIX_FMT_YUVA420P:
return VIDEO_FORMAT_I40A;
case AV_PIX_FMT_YUVA422P:
return VIDEO_FORMAT_I42A;
case AV_PIX_FMT_YUVA444P:
return VIDEO_FORMAT_YUVA;
case AV_PIX_FMT_NONE:
default: return VIDEO_FORMAT_NONE;
default:
return VIDEO_FORMAT_NONE;
}
}
static inline enum audio_format convert_ffmpeg_sample_format(
enum AVSampleFormat format)
static inline enum audio_format
convert_ffmpeg_sample_format(enum AVSampleFormat format)
{
switch ((uint32_t)format) {
case AV_SAMPLE_FMT_U8: return AUDIO_FORMAT_U8BIT;
case AV_SAMPLE_FMT_S16: return AUDIO_FORMAT_16BIT;
case AV_SAMPLE_FMT_S32: return AUDIO_FORMAT_32BIT;
case AV_SAMPLE_FMT_FLT: return AUDIO_FORMAT_FLOAT;
case AV_SAMPLE_FMT_U8P: return AUDIO_FORMAT_U8BIT_PLANAR;
case AV_SAMPLE_FMT_S16P: return AUDIO_FORMAT_16BIT_PLANAR;
case AV_SAMPLE_FMT_S32P: return AUDIO_FORMAT_32BIT_PLANAR;
case AV_SAMPLE_FMT_FLTP: return AUDIO_FORMAT_FLOAT_PLANAR;
case AV_SAMPLE_FMT_U8:
return AUDIO_FORMAT_U8BIT;
case AV_SAMPLE_FMT_S16:
return AUDIO_FORMAT_16BIT;
case AV_SAMPLE_FMT_S32:
return AUDIO_FORMAT_32BIT;
case AV_SAMPLE_FMT_FLT:
return AUDIO_FORMAT_FLOAT;
case AV_SAMPLE_FMT_U8P:
return AUDIO_FORMAT_U8BIT_PLANAR;
case AV_SAMPLE_FMT_S16P:
return AUDIO_FORMAT_16BIT_PLANAR;
case AV_SAMPLE_FMT_S32P:
return AUDIO_FORMAT_32BIT_PLANAR;
case AV_SAMPLE_FMT_FLTP:
return AUDIO_FORMAT_FLOAT_PLANAR;
}
/* shouldn't get here */

View file

@ -0,0 +1,138 @@
#include <obs-module.h>
#include <util/darray.h>
#include <util/threading.h>
#include <libavutil/log.h>
static DARRAY(struct log_context {
void *context;
char str[4096];
int print_prefix;
} *) active_log_contexts;
static DARRAY(struct log_context *) cached_log_contexts;
pthread_mutex_t log_contexts_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct log_context *create_or_fetch_log_context(void *context)
{
pthread_mutex_lock(&log_contexts_mutex);
for (size_t i = 0; i < active_log_contexts.num; i++) {
if (context == active_log_contexts.array[i]->context) {
pthread_mutex_unlock(&log_contexts_mutex);
return active_log_contexts.array[i];
}
}
struct log_context *new_log_context = NULL;
size_t cnt = cached_log_contexts.num;
if (!!cnt) {
new_log_context = cached_log_contexts.array[cnt - 1];
da_pop_back(cached_log_contexts);
}
if (!new_log_context)
new_log_context = bzalloc(sizeof(struct log_context));
new_log_context->context = context;
new_log_context->str[0] = '\0';
new_log_context->print_prefix = 1;
da_push_back(active_log_contexts, &new_log_context);
pthread_mutex_unlock(&log_contexts_mutex);
return new_log_context;
}
static void destroy_log_context(struct log_context *log_context)
{
pthread_mutex_lock(&log_contexts_mutex);
da_erase_item(active_log_contexts, &log_context);
da_push_back(cached_log_contexts, &log_context);
pthread_mutex_unlock(&log_contexts_mutex);
}
static void ffmpeg_log_callback(void *context, int level, const char *format,
va_list args)
{
if (format == NULL)
return;
struct log_context *log_context = create_or_fetch_log_context(context);
char *str = log_context->str;
av_log_format_line(context, level, format, args, str + strlen(str),
(int)(sizeof(log_context->str) - strlen(str)),
&log_context->print_prefix);
int obs_level;
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
obs_level = LOG_ERROR;
break;
case AV_LOG_ERROR:
case AV_LOG_WARNING:
obs_level = LOG_WARNING;
break;
case AV_LOG_INFO:
case AV_LOG_VERBOSE:
obs_level = LOG_INFO;
break;
case AV_LOG_DEBUG:
default:
obs_level = LOG_DEBUG;
}
if (!log_context->print_prefix)
return;
char *str_end = str + strlen(str) - 1;
while (str < str_end) {
if (*str_end != '\n')
break;
*str_end-- = '\0';
}
if (str_end <= str)
goto cleanup;
blog(obs_level, "[ffmpeg] %s", str);
cleanup:
destroy_log_context(log_context);
}
static bool logging_initialized = false;
void obs_ffmpeg_load_logging(void)
{
da_init(active_log_contexts);
da_init(cached_log_contexts);
if (pthread_mutex_init(&log_contexts_mutex, NULL) == 0) {
av_log_set_callback(ffmpeg_log_callback);
logging_initialized = true;
}
}
void obs_ffmpeg_unload_logging(void)
{
if (!logging_initialized)
return;
logging_initialized = false;
av_log_set_callback(av_log_default_callback);
pthread_mutex_destroy(&log_contexts_mutex);
for (size_t i = 0; i < active_log_contexts.num; i++) {
bfree(active_log_contexts.array[i]);
}
for (size_t i = 0; i < cached_log_contexts.num; i++) {
bfree(cached_log_contexts.array[i]);
}
da_free(active_log_contexts);
da_free(cached_log_contexts);
}

View file

@ -32,38 +32,38 @@
#include <libavformat/avformat.h>
#define do_log(level, format, ...) \
#define do_log(level, format, ...) \
blog(level, "[ffmpeg muxer: '%s'] " format, \
obs_output_get_name(stream->output), ##__VA_ARGS__)
obs_output_get_name(stream->output), ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
struct ffmpeg_muxer {
obs_output_t *output;
obs_output_t *output;
os_process_pipe_t *pipe;
int64_t stop_ts;
uint64_t total_bytes;
struct dstr path;
bool sent_headers;
volatile bool active;
volatile bool stopping;
volatile bool capturing;
int64_t stop_ts;
uint64_t total_bytes;
struct dstr path;
bool sent_headers;
volatile bool active;
volatile bool stopping;
volatile bool capturing;
/* replay buffer */
struct circlebuf packets;
int64_t cur_size;
int64_t cur_time;
int64_t max_size;
int64_t max_time;
int64_t save_ts;
int keyframes;
obs_hotkey_id hotkey;
struct circlebuf packets;
int64_t cur_size;
int64_t cur_time;
int64_t max_size;
int64_t max_time;
int64_t save_ts;
int keyframes;
obs_hotkey_id hotkey;
DARRAY(struct encoder_packet) mux_packets;
pthread_t mux_thread;
bool mux_thread_joinable;
volatile bool muxing;
pthread_t mux_thread;
bool mux_thread_joinable;
volatile bool muxing;
};
static const char *ffmpeg_mux_getname(void *type)
@ -136,7 +136,7 @@ static inline bool active(struct ffmpeg_muxer *stream)
/* TODO: allow codecs other than h264 whenever we start using them */
static void add_video_encoder_params(struct ffmpeg_muxer *stream,
struct dstr *cmd, obs_encoder_t *vencoder)
struct dstr *cmd, obs_encoder_t *vencoder)
{
obs_data_t *settings = obs_encoder_get_settings(vencoder);
int bitrate = (int)obs_data_get_int(settings, "bitrate");
@ -145,13 +145,10 @@ static void add_video_encoder_params(struct ffmpeg_muxer *stream,
obs_data_release(settings);
dstr_catf(cmd, "%s %d %d %d %d %d ",
obs_encoder_get_codec(vencoder),
bitrate,
obs_output_get_width(stream->output),
obs_output_get_height(stream->output),
(int)info->fps_num,
(int)info->fps_den);
dstr_catf(cmd, "%s %d %d %d %d %d ", obs_encoder_get_codec(vencoder),
bitrate, obs_output_get_width(stream->output),
obs_output_get_height(stream->output), (int)info->fps_num,
(int)info->fps_den);
}
static void add_audio_encoder_params(struct dstr *cmd, obs_encoder_t *aencoder)
@ -166,11 +163,9 @@ static void add_audio_encoder_params(struct dstr *cmd, obs_encoder_t *aencoder)
dstr_copy(&name, obs_encoder_get_name(aencoder));
dstr_replace(&name, "\"", "\"\"");
dstr_catf(cmd, "\"%s\" %d %d %d ",
name.array,
bitrate,
(int)obs_encoder_get_sample_rate(aencoder),
(int)audio_output_get_channels(audio));
dstr_catf(cmd, "\"%s\" %d %d %d ", name.array, bitrate,
(int)obs_encoder_get_sample_rate(aencoder),
(int)audio_output_get_channels(audio));
dstr_free(&name);
}
@ -181,8 +176,8 @@ static void log_muxer_params(struct ffmpeg_muxer *stream, const char *settings)
AVDictionary *dict = NULL;
if ((ret = av_dict_parse_string(&dict, settings, "=", " ", 0))) {
warn("Failed to parse muxer settings: %s\n%s",
av_err2str(ret), settings);
warn("Failed to parse muxer settings: %s\n%s", av_err2str(ret),
settings);
av_dict_free(&dict);
return;
@ -193,7 +188,7 @@ static void log_muxer_params(struct ffmpeg_muxer *stream, const char *settings)
AVDictionaryEntry *entry = NULL;
while ((entry = av_dict_get(dict, "", entry,
AV_DICT_IGNORE_SUFFIX)))
AV_DICT_IGNORE_SUFFIX)))
dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
info("Using muxer settings:%s", str.array);
@ -221,7 +216,7 @@ static void add_muxer_params(struct dstr *cmd, struct ffmpeg_muxer *stream)
}
static void build_command_line(struct ffmpeg_muxer *stream, struct dstr *cmd,
const char *path)
const char *path)
{
obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output);
obs_encoder_t *aencoders[MAX_AUDIO_MIXES];
@ -229,7 +224,7 @@ static void build_command_line(struct ffmpeg_muxer *stream, struct dstr *cmd,
for (;;) {
obs_encoder_t *aencoder = obs_output_get_audio_encoder(
stream->output, num_tracks);
stream->output, num_tracks);
if (!aencoder)
break;
@ -289,7 +284,7 @@ static bool ffmpeg_mux_start(void *data)
if (!test_file) {
struct dstr error_message;
dstr_init_copy(&error_message,
obs_module_text("UnableToWritePath"));
obs_module_text("UnableToWritePath"));
#ifdef _WIN32
// special warning for Windows 10 users about Defender
struct win_version_info ver;
@ -297,12 +292,11 @@ static bool ffmpeg_mux_start(void *data)
if (ver.major >= 10) {
dstr_cat(&error_message, "\n\n");
dstr_cat(&error_message,
obs_module_text("WarnWindowsDefender"));
obs_module_text("WarnWindowsDefender"));
}
#endif
dstr_replace(&error_message, "%1", path);
obs_output_set_last_error(stream->output,
error_message.array);
obs_output_set_last_error(stream->output, error_message.array);
dstr_free(&error_message);
obs_data_release(settings);
return false;
@ -315,8 +309,8 @@ static bool ffmpeg_mux_start(void *data)
obs_data_release(settings);
if (!stream->pipe) {
obs_output_set_last_error(stream->output,
obs_module_text("HelperProcessFailed"));
obs_output_set_last_error(
stream->output, obs_module_text("HelperProcessFailed"));
warn("Failed to create process pipe");
return false;
}
@ -375,19 +369,22 @@ static void signal_failure(struct ffmpeg_muxer *stream)
size_t len;
len = os_process_pipe_read_err(stream->pipe, (uint8_t *)error,
sizeof(error) - 1);
sizeof(error) - 1);
if (len > 0) {
error[len] = 0;
warn ("ffmpeg-mux: %s", error);
obs_output_set_last_error (stream->output, error);
warn("ffmpeg-mux: %s", error);
obs_output_set_last_error(stream->output, error);
}
ret = deactivate(stream, 0);
switch (ret) {
case FFM_UNSUPPORTED: code = OBS_OUTPUT_UNSUPPORTED; break;
default: code = OBS_OUTPUT_ERROR;
case FFM_UNSUPPORTED:
code = OBS_OUTPUT_UNSUPPORTED;
break;
default:
code = OBS_OUTPUT_ERROR;
}
obs_output_signal_stop(stream->output, code);
@ -395,22 +392,21 @@ static void signal_failure(struct ffmpeg_muxer *stream)
}
static bool write_packet(struct ffmpeg_muxer *stream,
struct encoder_packet *packet)
struct encoder_packet *packet)
{
bool is_video = packet->type == OBS_ENCODER_VIDEO;
size_t ret;
struct ffm_packet_info info = {
.pts = packet->pts,
.dts = packet->dts,
.size = (uint32_t)packet->size,
.index = (int)packet->track_idx,
.type = is_video ? FFM_PACKET_VIDEO : FFM_PACKET_AUDIO,
.keyframe = packet->keyframe
};
struct ffm_packet_info info = {.pts = packet->pts,
.dts = packet->dts,
.size = (uint32_t)packet->size,
.index = (int)packet->track_idx,
.type = is_video ? FFM_PACKET_VIDEO
: FFM_PACKET_AUDIO,
.keyframe = packet->keyframe};
ret = os_process_pipe_write(stream->pipe, (const uint8_t*)&info,
sizeof(info));
ret = os_process_pipe_write(stream->pipe, (const uint8_t *)&info,
sizeof(info));
if (ret != sizeof(info)) {
warn("os_process_pipe_write for info structure failed");
signal_failure(stream);
@ -429,13 +425,10 @@ static bool write_packet(struct ffmpeg_muxer *stream,
}
static bool send_audio_headers(struct ffmpeg_muxer *stream,
obs_encoder_t *aencoder, size_t idx)
obs_encoder_t *aencoder, size_t idx)
{
struct encoder_packet packet = {
.type = OBS_ENCODER_AUDIO,
.timebase_den = 1,
.track_idx = idx
};
.type = OBS_ENCODER_AUDIO, .timebase_den = 1, .track_idx = idx};
obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size);
return write_packet(stream, &packet);
@ -445,10 +438,8 @@ static bool send_video_headers(struct ffmpeg_muxer *stream)
{
obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output);
struct encoder_packet packet = {
.type = OBS_ENCODER_VIDEO,
.timebase_den = 1
};
struct encoder_packet packet = {.type = OBS_ENCODER_VIDEO,
.timebase_den = 1};
obs_encoder_get_extra_data(vencoder, &packet.data, &packet.size);
return write_packet(stream, &packet);
@ -511,9 +502,8 @@ static obs_properties_t *ffmpeg_mux_properties(void *unused)
obs_properties_t *props = obs_properties_create();
obs_properties_add_text(props, "path",
obs_module_text("FilePath"),
OBS_TEXT_DEFAULT);
obs_properties_add_text(props, "path", obs_module_text("FilePath"),
OBS_TEXT_DEFAULT);
return props;
}
@ -524,18 +514,17 @@ static uint64_t ffmpeg_mux_total_bytes(void *data)
}
struct obs_output_info ffmpeg_muxer = {
.id = "ffmpeg_muxer",
.flags = OBS_OUTPUT_AV |
OBS_OUTPUT_ENCODED |
OBS_OUTPUT_MULTI_TRACK,
.get_name = ffmpeg_mux_getname,
.create = ffmpeg_mux_create,
.destroy = ffmpeg_mux_destroy,
.start = ffmpeg_mux_start,
.stop = ffmpeg_mux_stop,
.id = "ffmpeg_muxer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_CAN_PAUSE,
.get_name = ffmpeg_mux_getname,
.create = ffmpeg_mux_create,
.destroy = ffmpeg_mux_destroy,
.start = ffmpeg_mux_start,
.stop = ffmpeg_mux_stop,
.encoded_packet = ffmpeg_mux_data,
.get_total_bytes= ffmpeg_mux_total_bytes,
.get_properties = ffmpeg_mux_properties
.get_total_bytes = ffmpeg_mux_total_bytes,
.get_properties = ffmpeg_mux_properties,
};
/* ------------------------------------------------------------------------ */
@ -547,15 +536,27 @@ static const char *replay_buffer_getname(void *type)
}
static void replay_buffer_hotkey(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
obs_hotkey_t *hotkey, bool pressed)
{
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(hotkey);
UNUSED_PARAMETER(pressed);
if (!pressed)
return;
struct ffmpeg_muxer *stream = data;
if (os_atomic_load_bool(&stream->active))
if (os_atomic_load_bool(&stream->active)) {
obs_encoder_t *vencoder =
obs_output_get_video_encoder(stream->output);
if (obs_encoder_paused(vencoder)) {
info("Could not save buffer because encoders paused");
return;
}
stream->save_ts = os_gettime_ns() / 1000LL;
}
}
static void save_replay_proc(void *data, calldata_t *cd)
@ -577,15 +578,15 @@ static void *replay_buffer_create(obs_data_t *settings, obs_output_t *output)
struct ffmpeg_muxer *stream = bzalloc(sizeof(*stream));
stream->output = output;
stream->hotkey = obs_hotkey_register_output(output,
"ReplayBuffer.Save",
obs_module_text("ReplayBuffer.Save"),
replay_buffer_hotkey, stream);
stream->hotkey =
obs_hotkey_register_output(output, "ReplayBuffer.Save",
obs_module_text("ReplayBuffer.Save"),
replay_buffer_hotkey, stream);
proc_handler_t *ph = obs_output_get_proc_handler(output);
proc_handler_add(ph, "void save()", save_replay_proc, stream);
proc_handler_add(ph, "void get_last_replay(out string path)",
get_last_replay, stream);
get_last_replay, stream);
return stream;
}
@ -653,7 +654,7 @@ static inline void purge(struct ffmpeg_muxer *stream)
for (;;) {
circlebuf_peek_front(&stream->packets, &pkt,
sizeof(pkt));
sizeof(pkt));
if (pkt.type == OBS_ENCODER_VIDEO && pkt.keyframe)
return;
@ -663,14 +664,14 @@ static inline void purge(struct ffmpeg_muxer *stream)
}
static inline void replay_buffer_purge(struct ffmpeg_muxer *stream,
struct encoder_packet *pkt)
struct encoder_packet *pkt)
{
if (stream->max_size) {
if (!stream->packets.size || stream->keyframes <= 2)
return;
while ((stream->cur_size + (int64_t)pkt->size) >
stream->max_size)
stream->max_size)
purge(stream);
}
@ -682,8 +683,8 @@ static inline void replay_buffer_purge(struct ffmpeg_muxer *stream,
}
static void insert_packet(struct darray *array, struct encoder_packet *packet,
int64_t video_offset, int64_t *audio_offsets,
int64_t video_dts_offset, int64_t *audio_dts_offsets)
int64_t video_offset, int64_t *audio_offsets,
int64_t video_dts_offset, int64_t *audio_dts_offsets)
{
struct encoder_packet pkt;
DARRAY(struct encoder_packet) packets;
@ -725,7 +726,7 @@ static void *replay_buffer_mux_thread(void *data)
if (!send_headers(stream)) {
warn("Could not write headers for file '%s'",
stream->path.array);
stream->path.array);
goto error;
}
@ -780,9 +781,9 @@ static void replay_buffer_save(struct ffmpeg_muxer *stream)
}
}
insert_packet(&stream->mux_packets.da, pkt,
video_offset, audio_offsets,
video_dts_offset, audio_dts_offsets);
insert_packet(&stream->mux_packets.da, pkt, video_offset,
audio_offsets, video_dts_offset,
audio_dts_offsets);
}
/* ---------------------------- */
@ -809,7 +810,8 @@ static void replay_buffer_save(struct ffmpeg_muxer *stream)
os_atomic_set_bool(&stream->muxing, true);
stream->mux_thread_joinable = pthread_create(&stream->mux_thread, NULL,
replay_buffer_mux_thread, stream) == 0;
replay_buffer_mux_thread,
stream) == 0;
}
static void deactivate_replay_buffer(struct ffmpeg_muxer *stream, int code)
@ -883,16 +885,15 @@ static void replay_buffer_defaults(obs_data_t *s)
}
struct obs_output_info replay_buffer = {
.id = "replay_buffer",
.flags = OBS_OUTPUT_AV |
OBS_OUTPUT_ENCODED |
OBS_OUTPUT_MULTI_TRACK,
.get_name = replay_buffer_getname,
.create = replay_buffer_create,
.destroy = replay_buffer_destroy,
.start = replay_buffer_start,
.stop = ffmpeg_mux_stop,
.id = "replay_buffer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_CAN_PAUSE,
.get_name = replay_buffer_getname,
.create = replay_buffer_create,
.destroy = replay_buffer_destroy,
.start = replay_buffer_start,
.stop = ffmpeg_mux_stop,
.encoded_packet = replay_buffer_data,
.get_total_bytes= ffmpeg_mux_total_bytes,
.get_defaults = replay_buffer_defaults
.get_total_bytes = ffmpeg_mux_total_bytes,
.get_defaults = replay_buffer_defaults,
};

View file

@ -28,33 +28,33 @@
#include "obs-ffmpeg-formats.h"
#define do_log(level, format, ...) \
#define do_log(level, format, ...) \
blog(level, "[NVENC encoder: '%s'] " format, \
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
struct nvenc_encoder {
obs_encoder_t *encoder;
obs_encoder_t *encoder;
AVCodec *nvenc;
AVCodecContext *context;
AVCodec *nvenc;
AVCodecContext *context;
AVFrame *vframe;
AVFrame *vframe;
DARRAY(uint8_t) buffer;
DARRAY(uint8_t) buffer;
uint8_t *header;
size_t header_size;
uint8_t *header;
size_t header_size;
uint8_t *sei;
size_t sei_size;
uint8_t *sei;
size_t sei_size;
int height;
bool first_packet;
bool initialized;
int height;
bool first_packet;
bool initialized;
};
static const char *nvenc_getname(void *unused)
@ -65,8 +65,7 @@ static const char *nvenc_getname(void *unused)
static inline bool valid_format(enum video_format format)
{
return format == VIDEO_FORMAT_I420 ||
format == VIDEO_FORMAT_NV12 ||
return format == VIDEO_FORMAT_I420 || format == VIDEO_FORMAT_NV12 ||
format == VIDEO_FORMAT_I444;
}
@ -78,8 +77,8 @@ static void nvenc_video_info(void *data, struct video_scale_info *info)
pref_format = obs_encoder_get_preferred_video_format(enc->encoder);
if (!valid_format(pref_format)) {
pref_format = valid_format(info->format) ?
info->format : VIDEO_FORMAT_NV12;
pref_format = valid_format(info->format) ? info->format
: VIDEO_FORMAT_NV12;
}
info->format = pref_format;
@ -117,12 +116,7 @@ static bool nvenc_init_codec(struct nvenc_encoder *enc)
return true;
}
enum RC_MODE {
RC_MODE_CBR,
RC_MODE_VBR,
RC_MODE_CQP,
RC_MODE_LOSSLESS
};
enum RC_MODE { RC_MODE_CBR, RC_MODE_VBR, RC_MODE_CQP, RC_MODE_LOSSLESS };
static bool nvenc_update(void *data, obs_data_t *settings)
{
@ -177,10 +171,10 @@ static bool nvenc_update(void *data, obs_data_t *settings)
cqp = 0;
bool hp = (astrcmpi(preset, "hp") == 0 ||
astrcmpi(preset, "llhp") == 0);
astrcmpi(preset, "llhp") == 0);
av_opt_set(enc->context->priv_data, "preset",
hp ? "losslesshp" : "lossless", 0);
hp ? "losslesshp" : "lossless", 0);
} else if (astrcmpi(rc, "vbr") != 0) { /* CBR by default */
av_opt_set_int(enc->context->priv_data, "cbr", true, 0);
@ -189,7 +183,6 @@ static bool nvenc_update(void *data, obs_data_t *settings)
cqp = 0;
}
av_opt_set(enc->context->priv_data, "level", "auto", 0);
av_opt_set_int(enc->context->priv_data, "2pass", twopass, 0);
av_opt_set_int(enc->context->priv_data, "gpu", gpu, 0);
@ -200,15 +193,17 @@ static bool nvenc_update(void *data, obs_data_t *settings)
enc->context->height = obs_encoder_get_height(enc->encoder);
enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num};
enc->context->pix_fmt = obs_to_ffmpeg_video_format(info.format);
enc->context->colorspace = info.colorspace == VIDEO_CS_709 ?
AVCOL_SPC_BT709 : AVCOL_SPC_BT470BG;
enc->context->color_range = info.range == VIDEO_RANGE_FULL ?
AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
enc->context->colorspace = info.colorspace == VIDEO_CS_709
? AVCOL_SPC_BT709
: AVCOL_SPC_BT470BG;
enc->context->color_range = info.range == VIDEO_RANGE_FULL
? AVCOL_RANGE_JPEG
: AVCOL_RANGE_MPEG;
enc->context->max_b_frames = bf;
if (keyint_sec)
enc->context->gop_size = keyint_sec * voi->fps_num /
voi->fps_den;
enc->context->gop_size =
keyint_sec * voi->fps_num / voi->fps_den;
else
enc->context->gop_size = 250;
@ -226,16 +221,30 @@ static bool nvenc_update(void *data, obs_data_t *settings)
"\t2-pass: %s\n"
"\tb-frames: %d\n"
"\tGPU: %d\n",
rc, bitrate, cqp, enc->context->gop_size,
preset, profile,
rc, bitrate, cqp, enc->context->gop_size, preset, profile,
enc->context->width, enc->context->height,
twopass ? "true" : "false",
enc->context->max_b_frames,
gpu);
twopass ? "true" : "false", enc->context->max_b_frames, gpu);
return nvenc_init_codec(enc);
}
static bool nvenc_reconfigure(void *data, obs_data_t *settings)
{
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 19, 101)
struct nvenc_encoder *enc = data;
int bitrate = (int)obs_data_get_int(settings, "bitrate");
const char *rc = obs_data_get_string(settings, "rate_control");
bool cbr = astrcmpi(rc, "CBR") == 0;
bool vbr = astrcmpi(rc, "VBR") == 0;
if (cbr || vbr) {
enc->context->bit_rate = bitrate * 1000;
enc->context->rc_max_rate = bitrate * 1000;
}
#endif
return true;
}
static void nvenc_destroy(void *data)
{
struct nvenc_encoder *enc = data;
@ -250,7 +259,7 @@ static void nvenc_destroy(void *data)
break;
#else
if (avcodec_encode_video2(enc->context, &pkt, NULL,
&r_pkt) < 0)
&r_pkt) < 0)
break;
#endif
@ -273,7 +282,9 @@ static void *nvenc_create(obs_data_t *settings, obs_encoder_t *encoder)
{
struct nvenc_encoder *enc;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
avcodec_register_all();
#endif
enc = bzalloc(sizeof(*enc));
enc->encoder = encoder;
@ -306,33 +317,33 @@ fail:
}
static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame,
int height, enum AVPixelFormat format)
int height, enum AVPixelFormat format)
{
int h_chroma_shift, v_chroma_shift;
av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift, &v_chroma_shift);
av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift,
&v_chroma_shift);
for (int plane = 0; plane < MAX_AV_PLANES; plane++) {
if (!frame->data[plane])
continue;
int frame_rowsize = (int)frame->linesize[plane];
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ?
frame_rowsize : pic_rowsize;
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ? frame_rowsize
: pic_rowsize;
int plane_height = height >> (plane ? v_chroma_shift : 0);
for (int y = 0; y < plane_height; y++) {
int pos_frame = y * frame_rowsize;
int pos_pic = y * pic_rowsize;
int pos_pic = y * pic_rowsize;
memcpy(pic->data[plane] + pos_pic,
frame->data[plane] + pos_frame,
bytes);
frame->data[plane] + pos_frame, bytes);
}
}
}
static bool nvenc_encode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
struct encoder_packet *packet, bool *received_packet)
{
struct nvenc_encoder *enc = data;
AVPacket av_pkt = {0};
@ -355,7 +366,7 @@ static bool nvenc_encode(void *data, struct encoder_frame *frame,
ret = 0;
#else
ret = avcodec_encode_video2(enc->context, &av_pkt, enc->vframe,
&got_packet);
&got_packet);
#endif
if (ret < 0) {
warn("nvenc_encode: Error encoding: %s", av_err2str(ret));
@ -369,9 +380,9 @@ static bool nvenc_encode(void *data, struct encoder_frame *frame,
enc->first_packet = false;
obs_extract_avc_headers(av_pkt.data, av_pkt.size,
&new_packet, &size,
&enc->header, &enc->header_size,
&enc->sei, &enc->sei_size);
&new_packet, &size,
&enc->header, &enc->header_size,
&enc->sei, &enc->sei_size);
da_copy_array(enc->buffer, new_packet, size);
bfree(new_packet);
@ -409,7 +420,7 @@ void nvenc_defaults(obs_data_t *settings)
}
static bool rate_control_modified(obs_properties_t *ppts, obs_property_t *p,
obs_data_t *settings)
obs_data_t *settings)
{
const char *rc = obs_data_get_string(settings, "rate_control");
bool cqp = astrcmpi(rc, "CQP") == 0;
@ -441,35 +452,39 @@ obs_properties_t *nvenc_properties_internal(bool ffmpeg)
obs_property_t *p;
p = obs_properties_add_list(props, "rate_control",
obs_module_text("RateControl"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
obs_module_text("RateControl"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_list_add_string(p, "CBR", "CBR");
obs_property_list_add_string(p, "CQP", "CQP");
obs_property_list_add_string(p, "VBR", "VBR");
obs_property_list_add_string(p, obs_module_text("Lossless"),
"lossless");
"lossless");
obs_property_set_modified_callback(p, rate_control_modified);
p = obs_properties_add_int(props, "bitrate",
obs_module_text("Bitrate"), 50, 300000, 50);
p = obs_properties_add_int(props, "bitrate", obs_module_text("Bitrate"),
50, 300000, 50);
obs_property_int_set_suffix(p, " Kbps");
p = obs_properties_add_int(props, "max_bitrate",
obs_module_text("MaxBitrate"), 50, 300000, 50);
obs_module_text("MaxBitrate"), 50, 300000,
50);
obs_property_int_set_suffix(p, " Kbps");
obs_properties_add_int(props, "cqp", obs_module_text("NVENC.CQLevel"),
1, 30, 1);
1, 30, 1);
obs_properties_add_int(props, "keyint_sec",
obs_module_text("KeyframeIntervalSec"), 0, 10, 1);
obs_module_text("KeyframeIntervalSec"), 0, 10,
1);
p = obs_properties_add_list(props, "preset", obs_module_text("Preset"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
#define add_preset(val) \
#define add_preset(val) \
obs_property_list_add_string(p, obs_module_text("NVENC.Preset." val), \
val)
val)
add_preset("mq");
add_preset("hq");
add_preset("default");
@ -479,11 +494,12 @@ obs_properties_t *nvenc_properties_internal(bool ffmpeg)
add_preset("llhp");
#undef add_preset
p = obs_properties_add_list(props, "profile", obs_module_text("Profile"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
p = obs_properties_add_list(props, "profile",
obs_module_text("Profile"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
#define add_profile(val) \
obs_property_list_add_string(p, val, val)
#define add_profile(val) obs_property_list_add_string(p, val, val)
add_profile("high");
add_profile("main");
add_profile("baseline");
@ -491,20 +507,21 @@ obs_properties_t *nvenc_properties_internal(bool ffmpeg)
if (!ffmpeg) {
p = obs_properties_add_bool(props, "lookahead",
obs_module_text("NVENC.LookAhead"));
obs_property_set_long_description(p,
obs_module_text("NVENC.LookAhead.ToolTip"));
obs_module_text("NVENC.LookAhead"));
obs_property_set_long_description(
p, obs_module_text("NVENC.LookAhead.ToolTip"));
p = obs_properties_add_bool(props, "psycho_aq",
obs_module_text("NVENC.PsychoVisualTuning"));
obs_property_set_long_description(p,
obs_module_text("NVENC.PsychoVisualTuning.ToolTip"));
p = obs_properties_add_bool(
props, "psycho_aq",
obs_module_text("NVENC.PsychoVisualTuning"));
obs_property_set_long_description(
p, obs_module_text("NVENC.PsychoVisualTuning.ToolTip"));
}
obs_properties_add_int(props, "gpu", obs_module_text("GPU"), 0, 8, 1);
obs_properties_add_int(props, "bf", obs_module_text("BFrames"),
0, 4, 1);
obs_properties_add_int(props, "bf", obs_module_text("BFrames"), 0, 4,
1);
return props;
}
@ -526,7 +543,7 @@ static bool nvenc_extra_data(void *data, uint8_t **extra_data, size_t *size)
struct nvenc_encoder *enc = data;
*extra_data = enc->header;
*size = enc->header_size;
*size = enc->header_size;
return true;
}
@ -535,21 +552,23 @@ static bool nvenc_sei_data(void *data, uint8_t **extra_data, size_t *size)
struct nvenc_encoder *enc = data;
*extra_data = enc->sei;
*size = enc->sei_size;
*size = enc->sei_size;
return true;
}
struct obs_encoder_info nvenc_encoder_info = {
.id = "ffmpeg_nvenc",
.type = OBS_ENCODER_VIDEO,
.codec = "h264",
.get_name = nvenc_getname,
.create = nvenc_create,
.destroy = nvenc_destroy,
.encode = nvenc_encode,
.get_defaults = nvenc_defaults,
.id = "ffmpeg_nvenc",
.type = OBS_ENCODER_VIDEO,
.codec = "h264",
.get_name = nvenc_getname,
.create = nvenc_create,
.destroy = nvenc_destroy,
.encode = nvenc_encode,
.update = nvenc_reconfigure,
.get_defaults = nvenc_defaults,
.get_properties = nvenc_properties_ffmpeg,
.get_extra_data = nvenc_extra_data,
.get_sei_data = nvenc_sei_data,
.get_video_info = nvenc_video_info
.get_sei_data = nvenc_sei_data,
.get_video_info = nvenc_video_info,
.caps = OBS_ENCODER_CAP_DYN_BITRATE,
};

View file

@ -32,92 +32,92 @@
#include "obs-ffmpeg-compat.h"
struct ffmpeg_cfg {
const char *url;
const char *format_name;
const char *format_mime_type;
const char *muxer_settings;
int gop_size;
int video_bitrate;
int audio_bitrate;
const char *video_encoder;
int video_encoder_id;
const char *audio_encoder;
int audio_encoder_id;
const char *video_settings;
const char *audio_settings;
int audio_mix_count;
int audio_tracks;
const char *url;
const char *format_name;
const char *format_mime_type;
const char *muxer_settings;
int gop_size;
int video_bitrate;
int audio_bitrate;
const char *video_encoder;
int video_encoder_id;
const char *audio_encoder;
int audio_encoder_id;
const char *video_settings;
const char *audio_settings;
int audio_mix_count;
int audio_tracks;
enum AVPixelFormat format;
enum AVColorRange color_range;
enum AVColorSpace color_space;
int scale_width;
int scale_height;
int width;
int height;
enum AVColorRange color_range;
enum AVColorSpace color_space;
int scale_width;
int scale_height;
int width;
int height;
};
struct ffmpeg_data {
AVStream *video;
AVStream **audio_streams;
AVCodec *acodec;
AVCodec *vcodec;
AVFormatContext *output;
struct SwsContext *swscale;
AVStream *video;
AVStream **audio_streams;
AVCodec *acodec;
AVCodec *vcodec;
AVFormatContext *output;
struct SwsContext *swscale;
int64_t total_frames;
AVFrame *vframe;
int frame_size;
int64_t total_frames;
AVFrame *vframe;
int frame_size;
uint64_t start_timestamp;
uint64_t start_timestamp;
int64_t total_samples[MAX_AUDIO_MIXES];
uint32_t audio_samplerate;
enum audio_format audio_format;
size_t audio_planes;
size_t audio_size;
int num_audio_streams;
int64_t total_samples[MAX_AUDIO_MIXES];
uint32_t audio_samplerate;
enum audio_format audio_format;
size_t audio_planes;
size_t audio_size;
int num_audio_streams;
/* audio_tracks is a bitmask storing the indices of the mixes */
int audio_tracks;
struct circlebuf excess_frames[MAX_AUDIO_MIXES][MAX_AV_PLANES];
uint8_t *samples[MAX_AUDIO_MIXES][MAX_AV_PLANES];
AVFrame *aframe[MAX_AUDIO_MIXES];
int audio_tracks;
struct circlebuf excess_frames[MAX_AUDIO_MIXES][MAX_AV_PLANES];
uint8_t *samples[MAX_AUDIO_MIXES][MAX_AV_PLANES];
AVFrame *aframe[MAX_AUDIO_MIXES];
struct ffmpeg_cfg config;
struct ffmpeg_cfg config;
bool initialized;
bool initialized;
char *last_error;
char *last_error;
};
struct ffmpeg_output {
obs_output_t *output;
volatile bool active;
obs_output_t *output;
volatile bool active;
struct ffmpeg_data ff_data;
bool connecting;
pthread_t start_thread;
bool connecting;
pthread_t start_thread;
uint64_t total_bytes;
uint64_t total_bytes;
uint64_t audio_start_ts;
uint64_t video_start_ts;
uint64_t stop_ts;
volatile bool stopping;
uint64_t audio_start_ts;
uint64_t video_start_ts;
uint64_t stop_ts;
volatile bool stopping;
bool write_thread_active;
pthread_mutex_t write_mutex;
pthread_t write_thread;
os_sem_t *write_sem;
os_event_t *stop_event;
bool write_thread_active;
pthread_mutex_t write_mutex;
pthread_t write_thread;
os_sem_t *write_sem;
os_event_t *stop_event;
DARRAY(AVPacket) packets;
DARRAY(AVPacket) packets;
};
/* ------------------------------------------------------------------------- */
static void ffmpeg_output_set_last_error(struct ffmpeg_data *data,
const char *error)
const char *error)
{
if (data->last_error)
bfree(data->last_error);
@ -126,7 +126,7 @@ static void ffmpeg_output_set_last_error(struct ffmpeg_data *data,
}
void ffmpeg_log_error(int log_level, struct ffmpeg_data *data,
const char *format, ...)
const char *format, ...)
{
va_list args;
char out[4096];
@ -141,26 +141,27 @@ void ffmpeg_log_error(int log_level, struct ffmpeg_data *data,
}
static bool new_stream(struct ffmpeg_data *data, AVStream **stream,
AVCodec **codec, enum AVCodecID id, const char *name)
AVCodec **codec, enum AVCodecID id, const char *name)
{
*codec = (!!name && *name) ?
avcodec_find_encoder_by_name(name) :
avcodec_find_encoder(id);
*codec = (!!name && *name) ? avcodec_find_encoder_by_name(name)
: avcodec_find_encoder(id);
if (!*codec) {
ffmpeg_log_error(LOG_WARNING, data, "Couldn't find encoder '%s'",
avcodec_get_name(id));
ffmpeg_log_error(LOG_WARNING, data,
"Couldn't find encoder '%s'",
avcodec_get_name(id));
return false;
}
*stream = avformat_new_stream(data->output, *codec);
if (!*stream) {
ffmpeg_log_error(LOG_WARNING, data, "Couldn't create stream for encoder '%s'",
avcodec_get_name(id));
ffmpeg_log_error(LOG_WARNING, data,
"Couldn't create stream for encoder '%s'",
avcodec_get_name(id));
return false;
}
(*stream)->id = data->output->nb_streams-1;
(*stream)->id = data->output->nb_streams - 1;
return true;
}
@ -180,10 +181,11 @@ static bool parse_params(AVCodecContext *context, char **opts)
char *value;
*assign = 0;
value = assign+1;
value = assign + 1;
if (av_opt_set(context->priv_data, name, value, 0)) {
blog(LOG_WARNING, "Failed to set %s=%s", name, value);
blog(LOG_WARNING, "Failed to set %s=%s", name,
value);
ret = false;
}
}
@ -205,34 +207,39 @@ static bool open_video_codec(struct ffmpeg_data *data)
if (opts) {
// libav requires x264 parameters in a special format which may be non-obvious
if (!parse_params(context, opts) && strcmp(data->vcodec->name, "libx264") == 0)
blog(LOG_WARNING, "If you're trying to set x264 parameters, use x264-params=name=value:name=value");
if (!parse_params(context, opts) &&
strcmp(data->vcodec->name, "libx264") == 0)
blog(LOG_WARNING,
"If you're trying to set x264 parameters, use x264-params=name=value:name=value");
strlist_free(opts);
}
ret = avcodec_open2(context, data->vcodec, NULL);
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to open video codec: %s",
av_err2str(ret));
ffmpeg_log_error(LOG_WARNING, data,
"Failed to open video codec: %s",
av_err2str(ret));
return false;
}
data->vframe = av_frame_alloc();
if (!data->vframe) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to allocate video frame");
ffmpeg_log_error(LOG_WARNING, data,
"Failed to allocate video frame");
return false;
}
data->vframe->format = context->pix_fmt;
data->vframe->width = context->width;
data->vframe->width = context->width;
data->vframe->height = context->height;
data->vframe->colorspace = data->config.color_space;
data->vframe->color_range = data->config.color_range;
ret = av_frame_get_buffer(data->vframe, base_get_alignment());
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to allocate vframe: %s",
av_err2str(ret));
ffmpeg_log_error(LOG_WARNING, data,
"Failed to allocate vframe: %s",
av_err2str(ret));
return false;
}
@ -242,14 +249,13 @@ static bool open_video_codec(struct ffmpeg_data *data)
static bool init_swscale(struct ffmpeg_data *data, AVCodecContext *context)
{
data->swscale = sws_getContext(
data->config.width, data->config.height,
data->config.format,
data->config.scale_width, data->config.scale_height,
context->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
data->config.width, data->config.height, data->config.format,
data->config.scale_width, data->config.scale_height,
context->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);
if (!data->swscale) {
ffmpeg_log_error(LOG_WARNING, data, "Could not initialize swscale");
ffmpeg_log_error(LOG_WARNING, data,
"Could not initialize swscale");
return false;
}
@ -268,23 +274,23 @@ static bool create_video_stream(struct ffmpeg_data *data)
}
if (!new_stream(data, &data->video, &data->vcodec,
data->output->oformat->video_codec,
data->config.video_encoder))
data->output->oformat->video_codec,
data->config.video_encoder))
return false;
closest_format = get_closest_format(data->config.format,
data->vcodec->pix_fmts);
closest_format =
get_closest_format(data->config.format, data->vcodec->pix_fmts);
context = data->video->codec;
context->bit_rate = data->config.video_bitrate * 1000;
context->width = data->config.scale_width;
context->height = data->config.scale_height;
context->time_base = (AVRational){ ovi.fps_den, ovi.fps_num };
context->gop_size = data->config.gop_size;
context->pix_fmt = closest_format;
context->colorspace = data->config.color_space;
context->color_range = data->config.color_range;
context->thread_count = 0;
context = data->video->codec;
context->bit_rate = data->config.video_bitrate * 1000;
context->width = data->config.scale_width;
context->height = data->config.scale_height;
context->time_base = (AVRational){ovi.fps_den, ovi.fps_num};
context->gop_size = data->config.gop_size;
context->pix_fmt = closest_format;
context->colorspace = data->config.color_space;
context->color_range = data->config.color_range;
context->thread_count = 0;
data->video->time_base = context->time_base;
@ -294,8 +300,8 @@ static bool create_video_stream(struct ffmpeg_data *data)
if (!open_video_codec(data))
return false;
if (context->pix_fmt != data->config.format ||
data->config.width != data->config.scale_width ||
if (context->pix_fmt != data->config.format ||
data->config.width != data->config.scale_width ||
data->config.height != data->config.scale_height) {
if (!init_swscale(data, context))
@ -318,7 +324,8 @@ static bool open_audio_codec(struct ffmpeg_data *data, int idx)
data->aframe[idx] = av_frame_alloc();
if (!data->aframe[idx]) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to allocate audio frame");
ffmpeg_log_error(LOG_WARNING, data,
"Failed to allocate audio frame");
return false;
}
@ -331,18 +338,20 @@ static bool open_audio_codec(struct ffmpeg_data *data, int idx)
ret = avcodec_open2(context, data->acodec, NULL);
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to open audio codec: %s",
av_err2str(ret));
ffmpeg_log_error(LOG_WARNING, data,
"Failed to open audio codec: %s",
av_err2str(ret));
return false;
}
data->frame_size = context->frame_size ? context->frame_size : 1024;
ret = av_samples_alloc(data->samples[idx], NULL, context->channels,
data->frame_size, context->sample_fmt, 0);
data->frame_size, context->sample_fmt, 0);
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to create audio buffer: %s",
av_err2str(ret));
ffmpeg_log_error(LOG_WARNING, data,
"Failed to create audio buffer: %s",
av_err2str(ret));
return false;
}
@ -361,25 +370,26 @@ static bool create_audio_stream(struct ffmpeg_data *data, int idx)
}
if (!new_stream(data, &stream, &data->acodec,
data->output->oformat->audio_codec,
data->config.audio_encoder))
data->output->oformat->audio_codec,
data->config.audio_encoder))
return false;
data->audio_streams[idx] = stream;
context = data->audio_streams[idx]->codec;
context->bit_rate = data->config.audio_bitrate * 1000;
context->time_base = (AVRational){ 1, aoi.samples_per_sec };
context->channels = get_audio_channels(aoi.speakers);
context->sample_rate = aoi.samples_per_sec;
context->channel_layout =
av_get_default_channel_layout(context->channels);
context = data->audio_streams[idx]->codec;
context->bit_rate = data->config.audio_bitrate * 1000;
context->time_base = (AVRational){1, aoi.samples_per_sec};
context->channels = get_audio_channels(aoi.speakers);
context->sample_rate = aoi.samples_per_sec;
context->channel_layout =
av_get_default_channel_layout(context->channels);
//AVlib default channel layout for 5 channels is 5.0 ; fix for 4.1
if (aoi.speakers == SPEAKERS_4POINT1)
context->channel_layout = av_get_channel_layout("4.1");
context->sample_fmt = data->acodec->sample_fmts ?
data->acodec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
context->sample_fmt = data->acodec->sample_fmts
? data->acodec->sample_fmts[0]
: AV_SAMPLE_FMT_FLTP;
data->audio_streams[idx]->time_base = context->time_base;
@ -402,9 +412,10 @@ static inline bool init_streams(struct ffmpeg_data *data)
if (!create_video_stream(data))
return false;
if (format->audio_codec != AV_CODEC_ID_NONE && data->num_audio_streams) {
data->audio_streams = calloc(1,
data->num_audio_streams * sizeof(void*));
if (format->audio_codec != AV_CODEC_ID_NONE &&
data->num_audio_streams) {
data->audio_streams =
calloc(1, data->num_audio_streams * sizeof(void *));
for (int i = 0; i < data->num_audio_streams; i++) {
if (!create_audio_stream(data, i))
return false;
@ -420,10 +431,11 @@ static inline bool open_output_file(struct ffmpeg_data *data)
int ret;
AVDictionary *dict = NULL;
if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings,
"=", " ", 0))) {
ffmpeg_log_error(LOG_WARNING, data, "Failed to parse muxer settings: %s\n%s",
av_err2str(ret), data->config.muxer_settings);
if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "=",
" ", 0))) {
ffmpeg_log_error(LOG_WARNING, data,
"Failed to parse muxer settings: %s\n%s",
av_err2str(ret), data->config.muxer_settings);
av_dict_free(&dict);
return false;
@ -434,7 +446,7 @@ static inline bool open_output_file(struct ffmpeg_data *data)
AVDictionaryEntry *entry = NULL;
while ((entry = av_dict_get(dict, "", entry,
AV_DICT_IGNORE_SUFFIX)))
AV_DICT_IGNORE_SUFFIX)))
dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
blog(LOG_INFO, "Using muxer settings: %s", str.array);
@ -443,24 +455,24 @@ static inline bool open_output_file(struct ffmpeg_data *data)
if ((format->flags & AVFMT_NOFILE) == 0) {
ret = avio_open2(&data->output->pb, data->config.url,
AVIO_FLAG_WRITE, NULL, &dict);
AVIO_FLAG_WRITE, NULL, &dict);
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data,
"Couldn't open '%s', %s", data->config.url,
av_err2str(ret));
"Couldn't open '%s', %s",
data->config.url, av_err2str(ret));
av_dict_free(&dict);
return false;
}
}
strncpy(data->output->filename, data->config.url,
sizeof(data->output->filename));
sizeof(data->output->filename));
data->output->filename[sizeof(data->output->filename) - 1] = 0;
ret = avformat_write_header(data->output, &dict);
if (ret < 0) {
ffmpeg_log_error(LOG_WARNING, data, "Error opening '%s': %s",
data->config.url, av_err2str(ret));
data->config.url, av_err2str(ret));
return false;
}
@ -469,7 +481,7 @@ static inline bool open_output_file(struct ffmpeg_data *data)
AVDictionaryEntry *entry = NULL;
while ((entry = av_dict_get(dict, "", entry,
AV_DICT_IGNORE_SUFFIX)))
AV_DICT_IGNORE_SUFFIX)))
dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
blog(LOG_INFO, "Invalid muxer settings: %s", str.array);
@ -564,16 +576,14 @@ static enum AVCodecID get_codec_id(const char *name, int id)
static void set_encoder_ids(struct ffmpeg_data *data)
{
data->output->oformat->video_codec = get_codec_id(
data->config.video_encoder,
data->config.video_encoder_id);
data->config.video_encoder, data->config.video_encoder_id);
data->output->oformat->audio_codec = get_codec_id(
data->config.audio_encoder,
data->config.audio_encoder_id);
data->config.audio_encoder, data->config.audio_encoder_id);
}
static bool ffmpeg_data_init(struct ffmpeg_data *data,
struct ffmpeg_cfg *config)
struct ffmpeg_cfg *config)
{
bool is_rtmp = false;
@ -584,35 +594,36 @@ static bool ffmpeg_data_init(struct ffmpeg_data *data,
if (!config->url || !*config->url)
return false;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
avformat_network_init();
is_rtmp = (astrcmpi_n(config->url, "rtmp://", 7) == 0);
AVOutputFormat *output_format = av_guess_format(
is_rtmp ? "flv" : data->config.format_name,
data->config.url,
is_rtmp ? NULL : data->config.format_mime_type);
is_rtmp ? "flv" : data->config.format_name, data->config.url,
is_rtmp ? NULL : data->config.format_mime_type);
if (output_format == NULL) {
ffmpeg_log_error(LOG_WARNING, data,
ffmpeg_log_error(
LOG_WARNING, data,
"Couldn't find matching output format with "
"parameters: name=%s, url=%s, mime=%s",
safe_str(is_rtmp ?
"flv" : data->config.format_name),
safe_str(is_rtmp ? "flv" : data->config.format_name),
safe_str(data->config.url),
safe_str(is_rtmp ?
NULL : data->config.format_mime_type));
safe_str(is_rtmp ? NULL
: data->config.format_mime_type));
goto fail;
}
avformat_alloc_output_context2(&data->output, output_format,
NULL, NULL);
avformat_alloc_output_context2(&data->output, output_format, NULL,
NULL);
if (!data->output) {
ffmpeg_log_error(LOG_WARNING, data,
"Couldn't create avformat context");
"Couldn't create avformat context");
goto fail;
}
@ -653,7 +664,7 @@ static const char *ffmpeg_output_getname(void *unused)
}
static void ffmpeg_log_callback(void *param, int level, const char *format,
va_list args)
va_list args)
{
if (level <= AV_LOG_INFO)
blogva(LOG_DEBUG, format, args);
@ -707,27 +718,27 @@ static void ffmpeg_output_destroy(void *data)
}
static inline void copy_data(AVFrame *pic, const struct video_data *frame,
int height, enum AVPixelFormat format)
int height, enum AVPixelFormat format)
{
int h_chroma_shift, v_chroma_shift;
av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift, &v_chroma_shift);
av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift,
&v_chroma_shift);
for (int plane = 0; plane < MAX_AV_PLANES; plane++) {
if (!frame->data[plane])
continue;
int frame_rowsize = (int)frame->linesize[plane];
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ?
frame_rowsize : pic_rowsize;
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ? frame_rowsize
: pic_rowsize;
int plane_height = height >> (plane ? v_chroma_shift : 0);
for (int y = 0; y < plane_height; y++) {
int pos_frame = y * frame_rowsize;
int pos_pic = y * pic_rowsize;
int pos_pic = y * pic_rowsize;
memcpy(pic->data[plane] + pos_pic,
frame->data[plane] + pos_frame,
bytes);
frame->data[plane] + pos_frame, bytes);
}
}
}
@ -735,7 +746,7 @@ static inline void copy_data(AVFrame *pic, const struct video_data *frame,
static void receive_video(void *param, struct video_data *frame)
{
struct ffmpeg_output *output = param;
struct ffmpeg_data *data = &output->ff_data;
struct ffmpeg_data *data = &output->ff_data;
// codec doesn't support video or none configured
if (!data->video)
@ -754,17 +765,17 @@ static void receive_video(void *param, struct video_data *frame)
if (!!data->swscale)
sws_scale(data->swscale, (const uint8_t *const *)frame->data,
(const int*)frame->linesize,
0, data->config.height, data->vframe->data,
data->vframe->linesize);
(const int *)frame->linesize, 0, data->config.height,
data->vframe->data, data->vframe->linesize);
else
copy_data(data->vframe, frame, context->height, context->pix_fmt);
copy_data(data->vframe, frame, context->height,
context->pix_fmt);
#if LIBAVFORMAT_VERSION_MAJOR < 58
if (data->output->flags & AVFMT_RAWPICTURE) {
packet.flags |= AV_PKT_FLAG_KEY;
packet.stream_index = data->video->index;
packet.data = data->vframe->data[0];
packet.size = sizeof(AVPicture);
packet.flags |= AV_PKT_FLAG_KEY;
packet.stream_index = data->video->index;
packet.data = data->vframe->data[0];
packet.size = sizeof(AVPicture);
pthread_mutex_lock(&output->write_mutex);
da_push_back(output->packets, &packet);
@ -784,24 +795,26 @@ static void receive_video(void *param, struct video_data *frame)
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
ret = 0;
#else
ret = avcodec_encode_video2(context, &packet, data->vframe,
&got_packet);
ret = avcodec_encode_video2(context, &packet, data->vframe,
&got_packet);
#endif
if (ret < 0) {
blog(LOG_WARNING, "receive_video: Error encoding "
"video: %s", av_err2str(ret));
blog(LOG_WARNING,
"receive_video: Error encoding "
"video: %s",
av_err2str(ret));
//FIXME: stop the encode with an error
return;
}
if (!ret && got_packet && packet.size) {
packet.pts = rescale_ts(packet.pts, context,
data->video->time_base);
data->video->time_base);
packet.dts = rescale_ts(packet.dts, context,
data->video->time_base);
packet.duration = (int)av_rescale_q(packet.duration,
context->time_base,
data->video->time_base);
data->video->time_base);
packet.duration = (int)av_rescale_q(
packet.duration, context->time_base,
data->video->time_base);
pthread_mutex_lock(&output->write_mutex);
da_push_back(output->packets, &packet);
@ -815,7 +828,7 @@ static void receive_video(void *param, struct video_data *frame)
#endif
if (ret != 0) {
blog(LOG_WARNING, "receive_video: Error writing video: %s",
av_err2str(ret));
av_err2str(ret));
//FIXME: stop the encode with an error
}
@ -823,7 +836,7 @@ static void receive_video(void *param, struct video_data *frame)
}
static void encode_audio(struct ffmpeg_output *output, int idx,
struct AVCodecContext *context, size_t block_size)
struct AVCodecContext *context, size_t block_size)
{
struct ffmpeg_data *data = &output->ff_data;
@ -832,16 +845,19 @@ static void encode_audio(struct ffmpeg_output *output, int idx,
size_t total_size = data->frame_size * block_size * context->channels;
data->aframe[idx]->nb_samples = data->frame_size;
data->aframe[idx]->pts = av_rescale_q(data->total_samples[idx],
(AVRational){1, context->sample_rate},
context->time_base);
data->aframe[idx]->pts = av_rescale_q(
data->total_samples[idx], (AVRational){1, context->sample_rate},
context->time_base);
ret = avcodec_fill_audio_frame(data->aframe[idx], context->channels,
context->sample_fmt, data->samples[idx][0],
(int)total_size, 1);
context->sample_fmt,
data->samples[idx][0], (int)total_size,
1);
if (ret < 0) {
blog(LOG_WARNING, "encode_audio: avcodec_fill_audio_frame "
"failed: %s", av_err2str(ret));
blog(LOG_WARNING,
"encode_audio: avcodec_fill_audio_frame "
"failed: %s",
av_err2str(ret));
//FIXME: stop the encode with an error
return;
}
@ -859,11 +875,11 @@ static void encode_audio(struct ffmpeg_output *output, int idx,
ret = 0;
#else
ret = avcodec_encode_audio2(context, &packet, data->aframe[idx],
&got_packet);
&got_packet);
#endif
if (ret < 0) {
blog(LOG_WARNING, "encode_audio: Error encoding audio: %s",
av_err2str(ret));
av_err2str(ret));
//FIXME: stop the encode with an error
return;
}
@ -872,11 +888,12 @@ static void encode_audio(struct ffmpeg_output *output, int idx,
return;
packet.pts = rescale_ts(packet.pts, context,
data->audio_streams[idx]->time_base);
data->audio_streams[idx]->time_base);
packet.dts = rescale_ts(packet.dts, context,
data->audio_streams[idx]->time_base);
packet.duration = (int)av_rescale_q(packet.duration, context->time_base,
data->audio_streams[idx]->time_base);
data->audio_streams[idx]->time_base);
packet.duration =
(int)av_rescale_q(packet.duration, context->time_base,
data->audio_streams[idx]->time_base);
packet.stream_index = data->audio_streams[idx]->index;
pthread_mutex_lock(&output->write_mutex);
@ -885,34 +902,6 @@ static void encode_audio(struct ffmpeg_output *output, int idx,
os_sem_post(output->write_sem);
}
static bool prepare_audio(struct ffmpeg_data *data,
const struct audio_data *frame, struct audio_data *output)
{
*output = *frame;
if (frame->timestamp < data->start_timestamp) {
uint64_t duration = (uint64_t)frame->frames * 1000000000 /
(uint64_t)data->audio_samplerate;
uint64_t end_ts = (frame->timestamp + duration);
uint64_t cutoff;
if (end_ts <= data->start_timestamp)
return false;
cutoff = data->start_timestamp - frame->timestamp;
output->timestamp += cutoff;
cutoff = cutoff * (uint64_t)data->audio_samplerate /
1000000000;
for (size_t i = 0; i < data->audio_planes; i++)
output->data[i] += data->audio_size * (uint32_t)cutoff;
output->frames -= (uint32_t)cutoff;
}
return true;
}
/* Given a bitmask for the selected tracks and the mix index,
* this returns the stream index which will be passed to the muxer. */
static int get_track_order(int track_config, size_t mix_index)
@ -928,9 +917,9 @@ static int get_track_order(int track_config, size_t mix_index)
static void receive_audio(void *param, size_t mix_idx, struct audio_data *frame)
{
struct ffmpeg_output *output = param;
struct ffmpeg_data *data = &output->ff_data;
struct ffmpeg_data *data = &output->ff_data;
size_t frame_size_bytes;
struct audio_data in;
struct audio_data in = *frame;
int track_order;
// codec doesn't support audio or none configured
@ -948,8 +937,6 @@ static void receive_audio(void *param, size_t mix_idx, struct audio_data *frame)
if (!data->start_timestamp)
return;
if (!prepare_audio(data, frame, &in))
return;
if (!output->audio_start_ts)
output->audio_start_ts = in.timestamp;
@ -958,22 +945,24 @@ static void receive_audio(void *param, size_t mix_idx, struct audio_data *frame)
for (size_t i = 0; i < data->audio_planes; i++)
circlebuf_push_back(&data->excess_frames[track_order][i],
in.data[i], in.frames * data->audio_size);
in.data[i], in.frames * data->audio_size);
while (data->excess_frames[track_order][0].size >= frame_size_bytes) {
for (size_t i = 0; i < data->audio_planes; i++)
circlebuf_pop_front(&data->excess_frames[track_order][i],
data->samples[track_order][i],
frame_size_bytes);
circlebuf_pop_front(
&data->excess_frames[track_order][i],
data->samples[track_order][i],
frame_size_bytes);
encode_audio(output, track_order, context, data->audio_size);
}
}
static uint64_t get_packet_sys_dts(struct ffmpeg_output *output,
AVPacket *packet)
AVPacket *packet)
{
struct ffmpeg_data *data = &output->ff_data;
uint64_t pause_offset = obs_output_get_pause_offset(output->output);
uint64_t start_ts;
AVRational time_base;
@ -986,8 +975,9 @@ static uint64_t get_packet_sys_dts(struct ffmpeg_output *output,
start_ts = output->audio_start_ts;
}
return start_ts + (uint64_t)av_rescale_q(packet->dts,
time_base, (AVRational){1, 1000000000});
return start_ts + pause_offset +
(uint64_t)av_rescale_q(packet->dts, time_base,
(AVRational){1, 1000000000});
}
static int process_packet(struct ffmpeg_output *output)
@ -1026,8 +1016,8 @@ static int process_packet(struct ffmpeg_output *output)
if (ret < 0) {
av_free_packet(&packet);
ffmpeg_log_error(LOG_WARNING, &output->ff_data,
"receive_audio: Error writing packet: %s",
av_err2str(ret));
"receive_audio: Error writing packet: %s",
av_err2str(ret));
return ret;
}
@ -1064,7 +1054,7 @@ static void *write_thread(void *data)
}
static inline const char *get_string_or_null(obs_data_t *settings,
const char *name)
const char *name)
{
const char *value = obs_data_get_string(settings, name);
if (!value || !strlen(value))
@ -1099,34 +1089,36 @@ static bool try_connect(struct ffmpeg_output *output)
config.url = obs_data_get_string(settings, "url");
config.format_name = get_string_or_null(settings, "format_name");
config.format_mime_type = get_string_or_null(settings,
"format_mime_type");
config.format_mime_type =
get_string_or_null(settings, "format_mime_type");
config.muxer_settings = obs_data_get_string(settings, "muxer_settings");
config.video_bitrate = (int)obs_data_get_int(settings, "video_bitrate");
config.audio_bitrate = (int)obs_data_get_int(settings, "audio_bitrate");
config.gop_size = (int)obs_data_get_int(settings, "gop_size");
config.video_encoder = get_string_or_null(settings, "video_encoder");
config.video_encoder_id = (int)obs_data_get_int(settings,
"video_encoder_id");
config.video_encoder_id =
(int)obs_data_get_int(settings, "video_encoder_id");
config.audio_encoder = get_string_or_null(settings, "audio_encoder");
config.audio_encoder_id = (int)obs_data_get_int(settings,
"audio_encoder_id");
config.audio_encoder_id =
(int)obs_data_get_int(settings, "audio_encoder_id");
config.video_settings = obs_data_get_string(settings, "video_settings");
config.audio_settings = obs_data_get_string(settings, "audio_settings");
config.scale_width = (int)obs_data_get_int(settings, "scale_width");
config.scale_height = (int)obs_data_get_int(settings, "scale_height");
config.width = (int)obs_output_get_width(output->output);
config.width = (int)obs_output_get_width(output->output);
config.height = (int)obs_output_get_height(output->output);
config.format = obs_to_ffmpeg_video_format(
video_output_get_format(video));
config.format =
obs_to_ffmpeg_video_format(video_output_get_format(video));
config.audio_tracks = (int)obs_output_get_mixers(output->output);
config.audio_mix_count = get_audio_mix_count(config.audio_tracks);
if (format_is_yuv(voi->format)) {
config.color_range = voi->range == VIDEO_RANGE_FULL ?
AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
config.color_space = voi->colorspace == VIDEO_CS_709 ?
AVCOL_SPC_BT709 : AVCOL_SPC_BT470BG;
config.color_range = voi->range == VIDEO_RANGE_FULL
? AVCOL_RANGE_JPEG
: AVCOL_RANGE_MPEG;
config.color_space = voi->colorspace == VIDEO_CS_709
? AVCOL_SPC_BT709
: AVCOL_SPC_BT470BG;
} else {
config.color_range = AVCOL_RANGE_UNSPECIFIED;
config.color_space = AVCOL_SPC_RGB;
@ -1148,15 +1140,14 @@ static bool try_connect(struct ffmpeg_output *output)
if (!success) {
if (output->ff_data.last_error) {
obs_output_set_last_error(output->output,
output->ff_data.last_error);
output->ff_data.last_error);
}
ffmpeg_data_free(&output->ff_data);
return false;
}
struct audio_convert_info aci = {
.format = output->ff_data.audio_format
};
struct audio_convert_info aci = {.format =
output->ff_data.audio_format};
output->active = true;
@ -1166,8 +1157,8 @@ static bool try_connect(struct ffmpeg_output *output)
ret = pthread_create(&output->write_thread, NULL, write_thread, output);
if (ret != 0) {
ffmpeg_log_error(LOG_WARNING, &output->ff_data,
"ffmpeg_output_start: failed to create write "
"thread.");
"ffmpeg_output_start: failed to create write "
"thread.");
ffmpeg_output_full_stop(output);
return false;
}
@ -1185,7 +1176,7 @@ static void *start_thread(void *data)
if (!try_connect(output))
obs_output_signal_stop(output->output,
OBS_OUTPUT_CONNECT_FAILED);
OBS_OUTPUT_CONNECT_FAILED);
output->connecting = false;
return NULL;
@ -1244,7 +1235,7 @@ static void ffmpeg_deactivate(struct ffmpeg_output *output)
pthread_mutex_lock(&output->write_mutex);
for (size_t i = 0; i < output->packets.num; i++)
av_free_packet(output->packets.array+i);
av_free_packet(output->packets.array + i);
da_free(output->packets);
pthread_mutex_unlock(&output->write_mutex);
@ -1259,15 +1250,14 @@ static uint64_t ffmpeg_output_total_bytes(void *data)
}
struct obs_output_info ffmpeg_output = {
.id = "ffmpeg_output",
.flags = OBS_OUTPUT_AUDIO |
OBS_OUTPUT_VIDEO |
OBS_OUTPUT_MULTI_TRACK,
.get_name = ffmpeg_output_getname,
.create = ffmpeg_output_create,
.destroy = ffmpeg_output_destroy,
.start = ffmpeg_output_start,
.stop = ffmpeg_output_stop,
.id = "ffmpeg_output",
.flags = OBS_OUTPUT_AUDIO | OBS_OUTPUT_VIDEO | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_CAN_PAUSE,
.get_name = ffmpeg_output_getname,
.create = ffmpeg_output_create,
.destroy = ffmpeg_output_destroy,
.start = ffmpeg_output_start,
.stop = ffmpeg_output_stop,
.raw_video = receive_video,
.raw_audio2 = receive_audio,
.get_total_bytes = ffmpeg_output_total_bytes,

View file

@ -25,9 +25,9 @@
#define FF_LOG(level, format, ...) \
blog(level, "[Media Source]: " format, ##__VA_ARGS__)
#define FF_LOG_S(source, level, format, ...) \
#define FF_LOG_S(source, level, format, ...) \
blog(level, "[Media Source '%s']: " format, \
obs_source_get_name(source), ##__VA_ARGS__)
obs_source_get_name(source), ##__VA_ARGS__)
#define FF_BLOG(level, format, ...) \
FF_LOG_S(s->source, level, format, ##__VA_ARGS__)
@ -60,18 +60,19 @@ struct ffmpeg_source {
};
static bool is_local_file_modified(obs_properties_t *props,
obs_property_t *prop, obs_data_t *settings)
obs_property_t *prop, obs_data_t *settings)
{
UNUSED_PARAMETER(prop);
bool enabled = obs_data_get_bool(settings, "is_local_file");
obs_property_t *input = obs_properties_get(props, "input");
obs_property_t *input_format =obs_properties_get(props,
"input_format");
obs_property_t *input_format =
obs_properties_get(props, "input_format");
obs_property_t *local_file = obs_properties_get(props, "local_file");
obs_property_t *looping = obs_properties_get(props, "looping");
obs_property_t *buffering = obs_properties_get(props, "buffering_mb");
obs_property_t *close = obs_properties_get(props, "close_when_inactive");
obs_property_t *close =
obs_properties_get(props, "close_when_inactive");
obs_property_t *seekable = obs_properties_get(props, "seekable");
obs_property_t *speed = obs_properties_get(props, "speed_percent");
obs_property_set_visible(input, !enabled);
@ -103,8 +104,7 @@ static const char *media_filter =
" (*.mp4 *.ts *.mov *.flv *.mkv *.avi *.mp3 *.ogg *.aac *.wav *.gif *.webm);;";
static const char *video_filter =
" (*.mp4 *.ts *.mov *.flv *.mkv *.avi *.gif *.webm);;";
static const char *audio_filter =
" (*.mp3 *.aac *.ogg *.wav);;";
static const char *audio_filter = " (*.mp3 *.aac *.ogg *.wav);;";
static obs_properties_t *ffmpeg_source_getproperties(void *data)
{
@ -120,7 +120,7 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
obs_property_t *prop;
// use this when obs allows non-readonly paths
prop = obs_properties_add_bool(props, "is_local_file",
obs_module_text("LocalFile"));
obs_module_text("LocalFile"));
obs_property_set_modified_callback(prop, is_local_file_modified);
@ -144,53 +144,59 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
}
obs_properties_add_path(props, "local_file",
obs_module_text("LocalFile"), OBS_PATH_FILE,
filter.array, path.array);
obs_module_text("LocalFile"), OBS_PATH_FILE,
filter.array, path.array);
dstr_free(&filter);
dstr_free(&path);
prop = obs_properties_add_bool(props, "looping",
obs_module_text("Looping"));
obs_module_text("Looping"));
obs_properties_add_bool(props, "restart_on_activate",
obs_module_text("RestartWhenActivated"));
obs_module_text("RestartWhenActivated"));
obs_properties_add_int_slider(props, "buffering_mb",
obs_module_text("BufferingMB"),
1, 16, 1);
prop = obs_properties_add_int_slider(props, "buffering_mb",
obs_module_text("BufferingMB"), 1,
16, 1);
obs_property_int_set_suffix(prop, " MB");
obs_properties_add_text(props, "input",
obs_module_text("Input"), OBS_TEXT_DEFAULT);
obs_properties_add_text(props, "input", obs_module_text("Input"),
OBS_TEXT_DEFAULT);
obs_properties_add_text(props, "input_format",
obs_module_text("InputFormat"), OBS_TEXT_DEFAULT);
obs_module_text("InputFormat"),
OBS_TEXT_DEFAULT);
#ifndef __APPLE__
obs_properties_add_bool(props, "hw_decode",
obs_module_text("HardwareDecode"));
obs_module_text("HardwareDecode"));
#endif
obs_properties_add_bool(props, "clear_on_media_end",
obs_module_text("ClearOnMediaEnd"));
obs_module_text("ClearOnMediaEnd"));
prop = obs_properties_add_bool(props, "close_when_inactive",
obs_module_text("CloseFileWhenInactive"));
prop = obs_properties_add_bool(
props, "close_when_inactive",
obs_module_text("CloseFileWhenInactive"));
obs_property_set_long_description(prop,
obs_module_text("CloseFileWhenInactive.ToolTip"));
obs_property_set_long_description(
prop, obs_module_text("CloseFileWhenInactive.ToolTip"));
obs_properties_add_int_slider(props, "speed_percent",
obs_module_text("SpeedPercentage"), 1, 200, 1);
prop = obs_properties_add_int_slider(props, "speed_percent",
obs_module_text("SpeedPercentage"),
1, 200, 1);
obs_property_int_set_suffix(prop, "%");
prop = obs_properties_add_list(props, "color_range",
obs_module_text("ColorRange"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_module_text("ColorRange"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(prop, obs_module_text("ColorRange.Auto"),
VIDEO_RANGE_DEFAULT);
VIDEO_RANGE_DEFAULT);
obs_property_list_add_int(prop, obs_module_text("ColorRange.Partial"),
VIDEO_RANGE_PARTIAL);
VIDEO_RANGE_PARTIAL);
obs_property_list_add_int(prop, obs_module_text("ColorRange.Full"),
VIDEO_RANGE_FULL);
VIDEO_RANGE_FULL);
obs_properties_add_bool(props, "seekable", obs_module_text("Seekable"));
@ -198,26 +204,24 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
}
static void dump_source_info(struct ffmpeg_source *s, const char *input,
const char *input_format)
const char *input_format)
{
FF_BLOG(LOG_INFO,
"settings:\n"
"\tinput: %s\n"
"\tinput_format: %s\n"
"\tspeed: %d\n"
"\tis_looping: %s\n"
"\tis_hw_decoding: %s\n"
"\tis_clear_on_media_end: %s\n"
"\trestart_on_activate: %s\n"
"\tclose_when_inactive: %s",
input ? input : "(null)",
input_format ? input_format : "(null)",
s->speed_percent,
s->is_looping ? "yes" : "no",
s->is_hw_decoding ? "yes" : "no",
s->is_clear_on_media_end ? "yes" : "no",
s->restart_on_activate ? "yes" : "no",
s->close_when_inactive ? "yes" : "no");
"settings:\n"
"\tinput: %s\n"
"\tinput_format: %s\n"
"\tspeed: %d\n"
"\tis_looping: %s\n"
"\tis_hw_decoding: %s\n"
"\tis_clear_on_media_end: %s\n"
"\trestart_on_activate: %s\n"
"\tclose_when_inactive: %s",
input ? input : "(null)",
input_format ? input_format : "(null)", s->speed_percent,
s->is_looping ? "yes" : "no", s->is_hw_decoding ? "yes" : "no",
s->is_clear_on_media_end ? "yes" : "no",
s->restart_on_activate ? "yes" : "no",
s->close_when_inactive ? "yes" : "no");
}
static void get_frame(void *opaque, struct obs_source_frame *f)
@ -267,8 +271,7 @@ static void ffmpeg_source_open(struct ffmpeg_source *s)
.speed = s->speed_percent,
.force_range = s->range,
.hardware_decoding = s->is_hw_decoding,
.is_local_file = s->is_local_file || s->seekable
};
.is_local_file = s->is_local_file || s->seekable};
s->media_valid = mp_media_init(&s->media, &info);
}
@ -316,14 +319,14 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
input = (char *)obs_data_get_string(settings, "local_file");
input_format = NULL;
s->is_looping = obs_data_get_bool(settings, "looping");
s->close_when_inactive = obs_data_get_bool(settings,
"close_when_inactive");
s->close_when_inactive =
obs_data_get_bool(settings, "close_when_inactive");
obs_source_set_async_unbuffered(s->source, true);
} else {
input = (char *)obs_data_get_string(settings, "input");
input_format = (char *)obs_data_get_string(settings,
"input_format");
input_format =
(char *)obs_data_get_string(settings, "input_format");
s->is_looping = false;
s->close_when_inactive = true;
@ -335,12 +338,12 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
#ifndef __APPLE__
s->is_hw_decoding = obs_data_get_bool(settings, "hw_decode");
#endif
s->is_clear_on_media_end = obs_data_get_bool(settings,
"clear_on_media_end");
s->restart_on_activate = obs_data_get_bool(settings,
"restart_on_activate");
s->is_clear_on_media_end =
obs_data_get_bool(settings, "clear_on_media_end");
s->restart_on_activate =
obs_data_get_bool(settings, "restart_on_activate");
s->range = (enum video_range_type)obs_data_get_int(settings,
"color_range");
"color_range");
s->buffering_mb = (int)obs_data_get_int(settings, "buffering_mb");
s->speed_percent = (int)obs_data_get_int(settings, "speed_percent");
s->is_local_file = is_local_file;
@ -369,8 +372,8 @@ static const char *ffmpeg_source_getname(void *unused)
return obs_module_text("FFMpegSource");
}
static void restart_hotkey(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
static void restart_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
bool pressed)
{
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(hotkey);
@ -407,12 +410,12 @@ static void get_nb_frames(void *data, calldata_t *cd)
return;
}
int video_stream_index = av_find_best_stream(s->media.fmt,
AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int video_stream_index = av_find_best_stream(
s->media.fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_stream_index < 0) {
FF_BLOG(LOG_WARNING, "Getting number of frames failed: No "
"video stream in media file!");
"video stream in media file!");
calldata_set_int(cd, "num_frames", frames);
return;
}
@ -423,12 +426,12 @@ static void get_nb_frames(void *data, calldata_t *cd)
frames = stream->nb_frames;
} else {
FF_BLOG(LOG_DEBUG, "nb_frames not set, estimating using frame "
"rate and duration");
"rate and duration");
AVRational avg_frame_rate = stream->avg_frame_rate;
frames = (int64_t)ceil((double)s->media.fmt->duration /
(double)AV_TIME_BASE *
(double)avg_frame_rate.num /
(double)avg_frame_rate.den);
(double)AV_TIME_BASE *
(double)avg_frame_rate.num /
(double)avg_frame_rate.den);
}
calldata_set_int(cd, "num_frames", frames);
@ -441,17 +444,16 @@ static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source)
struct ffmpeg_source *s = bzalloc(sizeof(struct ffmpeg_source));
s->source = source;
s->hotkey = obs_hotkey_register_source(source,
"MediaSource.Restart",
obs_module_text("RestartMedia"),
restart_hotkey, s);
s->hotkey = obs_hotkey_register_source(source, "MediaSource.Restart",
obs_module_text("RestartMedia"),
restart_hotkey, s);
proc_handler_t *ph = obs_source_get_proc_handler(source);
proc_handler_add(ph, "void restart()", restart_proc, s);
proc_handler_add(ph, "void get_duration(out int duration)",
get_duration, s);
get_duration, s);
proc_handler_add(ph, "void get_nb_frames(out int num_frames)",
get_nb_frames, s);
get_nb_frames, s);
ffmpeg_source_update(s, settings);
return s;
@ -497,17 +499,17 @@ static void ffmpeg_source_deactivate(void *data)
}
struct obs_source_info ffmpeg_source = {
.id = "ffmpeg_source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO |
OBS_SOURCE_DO_NOT_DUPLICATE,
.get_name = ffmpeg_source_getname,
.create = ffmpeg_source_create,
.destroy = ffmpeg_source_destroy,
.get_defaults = ffmpeg_source_defaults,
.id = "ffmpeg_source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO |
OBS_SOURCE_DO_NOT_DUPLICATE,
.get_name = ffmpeg_source_getname,
.create = ffmpeg_source_create,
.destroy = ffmpeg_source_destroy,
.get_defaults = ffmpeg_source_defaults,
.get_properties = ffmpeg_source_getproperties,
.activate = ffmpeg_source_activate,
.deactivate = ffmpeg_source_deactivate,
.video_tick = ffmpeg_source_tick,
.update = ffmpeg_source_update
.activate = ffmpeg_source_activate,
.deactivate = ffmpeg_source_deactivate,
.video_tick = ffmpeg_source_tick,
.update = ffmpeg_source_update,
};

View file

@ -37,9 +37,9 @@
#include "obs-ffmpeg-formats.h"
#define do_log(level, format, ...) \
#define do_log(level, format, ...) \
blog(level, "[FFMPEG VAAPI encoder: '%s'] " format, \
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
@ -51,7 +51,7 @@ struct vaapi_encoder {
AVBufferRef *vadevice_ref;
AVBufferRef *vaframes_ref;
AVCodec * vaapi;
AVCodec *vaapi;
AVCodecContext *context;
AVFrame *vframe;
@ -59,12 +59,12 @@ struct vaapi_encoder {
DARRAY(uint8_t) buffer;
uint8_t *header;
size_t header_size;
size_t header_size;
uint8_t *sei;
size_t sei_size;
size_t sei_size;
int height;
int height;
bool first_packet;
bool initialized;
};
@ -83,7 +83,7 @@ static inline bool valid_format(enum video_format format)
static void vaapi_video_info(void *data, struct video_scale_info *info)
{
struct vaapi_encoder *enc = data;
enum video_format pref_format;
enum video_format pref_format;
pref_format = obs_encoder_get_preferred_video_format(enc->encoder);
@ -100,10 +100,10 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
int ret;
ret = av_hwdevice_ctx_create(&enc->vadevice_ref, AV_HWDEVICE_TYPE_VAAPI,
path, NULL, 0);
path, NULL, 0);
if (ret < 0) {
warn("Failed to create VAAPI device context: %s",
av_err2str(ret));
av_err2str(ret));
return false;
}
@ -114,11 +114,11 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
}
AVHWFramesContext *frames_ctx =
(AVHWFramesContext *)enc->vaframes_ref->data;
frames_ctx->format = AV_PIX_FMT_VAAPI;
frames_ctx->sw_format = AV_PIX_FMT_NV12;
frames_ctx->width = enc->context->width;
frames_ctx->height = enc->context->height;
(AVHWFramesContext *)enc->vaframes_ref->data;
frames_ctx->format = AV_PIX_FMT_VAAPI;
frames_ctx->sw_format = AV_PIX_FMT_NV12;
frames_ctx->width = enc->context->width;
frames_ctx->height = enc->context->height;
frames_ctx->initial_pool_size = 20;
ret = av_hwframe_ctx_init(enc->vaframes_ref);
@ -135,9 +135,9 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
}
enc->vframe->format = enc->context->pix_fmt;
enc->vframe->width = enc->context->width;
enc->vframe->width = enc->context->width;
enc->vframe->height = enc->context->height;
enc->vframe->colorspace = enc->context->colorspace;
enc->vframe->colorspace = enc->context->colorspace;
enc->vframe->color_range = enc->context->color_range;
ret = av_frame_get_buffer(enc->vframe, base_get_alignment());
@ -147,7 +147,7 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
}
/* 3. set up codec */
enc->context->pix_fmt = AV_PIX_FMT_VAAPI;
enc->context->pix_fmt = AV_PIX_FMT_VAAPI;
enc->context->hw_frames_ctx = av_buffer_ref(enc->vaframes_ref);
ret = avcodec_open2(enc->context, enc->vaapi, NULL);
@ -167,49 +167,49 @@ static bool vaapi_update(void *data, obs_data_t *settings)
const char *device = obs_data_get_string(settings, "vaapi_device");
int profile = (int)obs_data_get_int(settings, "profile");
int bf = (int)obs_data_get_int(settings, "bf");
int bf = (int)obs_data_get_int(settings, "bf");
int level = (int)obs_data_get_int(settings, "level");
int bitrate = (int)obs_data_get_int(settings, "bitrate");
int level = (int)obs_data_get_int(settings, "level");
int bitrate = (int)obs_data_get_int(settings, "bitrate");
int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec");
int qp = (int)obs_data_get_int(settings, "qp");
int qp = (int)obs_data_get_int(settings, "qp");
int quality = (int)obs_data_get_int(settings, "quality");
av_opt_set_int(enc->context->priv_data, "qp", qp, 0);
av_opt_set_int(enc->context->priv_data, "quality", quality, 0);
video_t * video = obs_encoder_video(enc->encoder);
const struct video_output_info *voi = video_output_get_info(video);
struct video_scale_info info;
video_t *video = obs_encoder_video(enc->encoder);
const struct video_output_info *voi = video_output_get_info(video);
struct video_scale_info info;
info.format = voi->format;
info.format = voi->format;
info.colorspace = voi->colorspace;
info.range = voi->range;
info.range = voi->range;
vaapi_video_info(enc, &info);
enc->context->profile = profile;
enc->context->profile = profile;
enc->context->max_b_frames = bf;
enc->context->level = level;
enc->context->bit_rate = bitrate * 1000;
enc->context->rc_max_rate = bitrate * 1000;
enc->context->level = level;
enc->context->bit_rate = bitrate * 1000;
enc->context->rc_max_rate = bitrate * 1000;
enc->context->width = obs_encoder_get_width(enc->encoder);
enc->context->width = obs_encoder_get_width(enc->encoder);
enc->context->height = obs_encoder_get_height(enc->encoder);
enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num};
enc->context->pix_fmt = obs_to_ffmpeg_video_format(info.format);
enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num};
enc->context->pix_fmt = obs_to_ffmpeg_video_format(info.format);
enc->context->colorspace = info.colorspace == VIDEO_CS_709
? AVCOL_SPC_BT709
: AVCOL_SPC_BT470BG;
? AVCOL_SPC_BT709
: AVCOL_SPC_BT470BG;
enc->context->color_range = info.range == VIDEO_RANGE_FULL
? AVCOL_RANGE_JPEG
: AVCOL_RANGE_MPEG;
? AVCOL_RANGE_JPEG
: AVCOL_RANGE_MPEG;
if (keyint_sec > 0) {
enc->context->gop_size =
keyint_sec * voi->fps_num / voi->fps_den;
keyint_sec * voi->fps_num / voi->fps_den;
} else {
enc->context->gop_size = 120;
}
@ -227,9 +227,9 @@ static bool vaapi_update(void *data, obs_data_t *settings)
"\twidth: %d\n"
"\theight: %d\n"
"\tb-frames: %d\n",
device, qp, quality, profile, level, bitrate,
enc->context->gop_size, enc->context->width,
enc->context->height, enc->context->max_b_frames);
device, qp, quality, profile, level, bitrate,
enc->context->gop_size, enc->context->width, enc->context->height,
enc->context->max_b_frames);
return vaapi_init_codec(enc, device);
}
@ -239,8 +239,8 @@ static void vaapi_destroy(void *data)
struct vaapi_encoder *enc = data;
if (enc->initialized) {
AVPacket pkt = {0};
int r_pkt = 1;
AVPacket pkt = {0};
int r_pkt = 1;
while (r_pkt) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
@ -248,7 +248,7 @@ static void vaapi_destroy(void *data)
break;
#else
if (avcodec_encode_video2(enc->context, &pkt, NULL,
&r_pkt) < 0)
&r_pkt) < 0)
break;
#endif
@ -272,9 +272,11 @@ static void vaapi_destroy(void *data)
static void *vaapi_create(obs_data_t *settings, obs_encoder_t *encoder)
{
struct vaapi_encoder *enc;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
avcodec_register_all();
#endif
enc = bzalloc(sizeof(*enc));
enc = bzalloc(sizeof(*enc));
enc->encoder = encoder;
int vaapi_codec = (int)obs_data_get_int(settings, "vaapi_codec");
@ -309,39 +311,39 @@ fail:
}
static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame,
int height, enum AVPixelFormat format)
int height, enum AVPixelFormat format)
{
int h_chroma_shift, v_chroma_shift;
av_pix_fmt_get_chroma_sub_sample(
format, &h_chroma_shift, &v_chroma_shift);
av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift,
&v_chroma_shift);
for (int plane = 0; plane < MAX_AV_PLANES; plane++) {
if (!frame->data[plane])
continue;
int frame_rowsize = (int)frame->linesize[plane];
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ? frame_rowsize
int pic_rowsize = pic->linesize[plane];
int bytes = frame_rowsize < pic_rowsize ? frame_rowsize
: pic_rowsize;
int plane_height = height >> (plane ? v_chroma_shift : 0);
for (int y = 0; y < plane_height; y++) {
int pos_frame = y * frame_rowsize;
int pos_pic = y * pic_rowsize;
int pos_pic = y * pic_rowsize;
memcpy(pic->data[plane] + pos_pic,
frame->data[plane] + pos_frame, bytes);
frame->data[plane] + pos_frame, bytes);
}
}
}
static bool vaapi_encode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
struct encoder_packet *packet, bool *received_packet)
{
struct vaapi_encoder *enc = data;
AVFrame * hwframe = NULL;
AVPacket av_pkt;
int got_packet;
int ret;
struct vaapi_encoder *enc = data;
AVFrame *hwframe = NULL;
AVPacket av_pkt;
int got_packet;
int ret;
hwframe = av_frame_alloc();
if (!hwframe) {
@ -352,28 +354,28 @@ static bool vaapi_encode(void *data, struct encoder_frame *frame,
ret = av_hwframe_get_buffer(enc->vaframes_ref, hwframe, 0);
if (ret < 0) {
warn("vaapi_encode: failed to get buffer for hw frame: %s",
av_err2str(ret));
av_err2str(ret));
goto fail;
}
copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt);
enc->vframe->pts = frame->pts;
hwframe->pts = frame->pts;
hwframe->width = enc->vframe->width;
hwframe->height = enc->vframe->height;
hwframe->pts = frame->pts;
hwframe->width = enc->vframe->width;
hwframe->height = enc->vframe->height;
ret = av_hwframe_transfer_data(hwframe, enc->vframe, 0);
if (ret < 0) {
warn("vaapi_encode: failed to upload hw frame: %s",
av_err2str(ret));
av_err2str(ret));
goto fail;
}
ret = av_frame_copy_props(hwframe, enc->vframe);
if (ret < 0) {
warn("vaapi_encode: failed to copy props to hw frame: %s",
av_err2str(ret));
av_err2str(ret));
goto fail;
}
@ -389,8 +391,8 @@ static bool vaapi_encode(void *data, struct encoder_frame *frame,
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
ret = 0;
#else
ret = avcodec_encode_video2(
enc->context, &av_pkt, hwframe, &got_packet);
ret = avcodec_encode_video2(enc->context, &av_pkt, hwframe,
&got_packet);
#endif
if (ret < 0) {
warn("vaapi_encode: Error encoding: %s", av_err2str(ret));
@ -400,13 +402,13 @@ static bool vaapi_encode(void *data, struct encoder_frame *frame,
if (got_packet && av_pkt.size) {
if (enc->first_packet) {
uint8_t *new_packet;
size_t size;
size_t size;
enc->first_packet = false;
obs_extract_avc_headers(av_pkt.data, av_pkt.size,
&new_packet, &size, &enc->header,
&enc->header_size, &enc->sei,
&enc->sei_size);
&new_packet, &size,
&enc->header, &enc->header_size,
&enc->sei, &enc->sei_size);
da_copy_array(enc->buffer, new_packet, size);
bfree(new_packet);
@ -414,11 +416,11 @@ static bool vaapi_encode(void *data, struct encoder_frame *frame,
da_copy_array(enc->buffer, av_pkt.data, av_pkt.size);
}
packet->pts = av_pkt.pts;
packet->dts = av_pkt.dts;
packet->data = enc->buffer.array;
packet->size = enc->buffer.num;
packet->type = OBS_ENCODER_VIDEO;
packet->pts = av_pkt.pts;
packet->dts = av_pkt.dts;
packet->data = enc->buffer.array;
packet->size = enc->buffer.num;
packet->type = OBS_ENCODER_VIDEO;
packet->keyframe = obs_avc_keyframe(packet->data, packet->size);
*received_packet = true;
} else {
@ -442,11 +444,11 @@ static void set_visible(obs_properties_t *ppts, const char *name, bool visible)
static void vaapi_defaults(obs_data_t *settings)
{
obs_data_set_default_string(
settings, "vaapi_device", "/dev/dri/renderD128");
obs_data_set_default_string(settings, "vaapi_device",
"/dev/dri/renderD128");
obs_data_set_default_int(settings, "vaapi_codec", AV_CODEC_ID_H264);
obs_data_set_default_int(settings, "profile",
FF_PROFILE_H264_CONSTRAINED_BASELINE);
FF_PROFILE_H264_CONSTRAINED_BASELINE);
obs_data_set_default_int(settings, "level", 40);
obs_data_set_default_int(settings, "bitrate", 2500);
obs_data_set_default_int(settings, "keyint_sec", 0);
@ -461,10 +463,11 @@ static obs_properties_t *vaapi_properties(void *unused)
UNUSED_PARAMETER(unused);
obs_properties_t *props = obs_properties_create();
obs_property_t * list;
obs_property_t *list;
list = obs_properties_add_list(props, "vaapi_device", "VAAPI Device",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
char path[32] = "/dev/dri/renderD1";
for (int i = 28;; i++) {
sprintf(path, "/dev/dri/renderD1%d", i);
@ -478,27 +481,29 @@ static obs_properties_t *vaapi_properties(void *unused)
}
list = obs_properties_add_list(props, "vaapi_codec", "VAAPI Codec",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, "H.264 (default)", AV_CODEC_ID_H264);
list = obs_properties_add_list(props, "level", "Level",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, "480p30 (3.0)", 30);
obs_property_list_add_int(list, "720p30/480p60 (3.1)", 31);
obs_property_list_add_int(
list, "Compatibility mode (4.0 default)", 40);
obs_property_list_add_int(list, "Compatibility mode (4.0 default)",
40);
obs_property_list_add_int(list, "720p60/1080p30 (4.1)", 41);
obs_property_list_add_int(list, "1080p60 (4.2)", 42);
obs_property_t *p;
p = obs_properties_add_int(props, "bitrate", obs_module_text("Bitrate"), 0,
300000, 50);
p = obs_properties_add_int(props, "bitrate", obs_module_text("Bitrate"),
0, 300000, 50);
obs_property_int_set_suffix(p, " Kbps");
obs_properties_add_int(props, "keyint_sec",
obs_module_text("Keyframe Interval (seconds)"), 0, 20,
1);
obs_module_text("Keyframe Interval (seconds)"),
0, 20, 1);
return props;
}
@ -508,7 +513,7 @@ static bool vaapi_extra_data(void *data, uint8_t **extra_data, size_t *size)
struct vaapi_encoder *enc = data;
*extra_data = enc->header;
*size = enc->header_size;
*size = enc->header_size;
return true;
}
@ -517,23 +522,23 @@ static bool vaapi_sei_data(void *data, uint8_t **extra_data, size_t *size)
struct vaapi_encoder *enc = data;
*extra_data = enc->sei;
*size = enc->sei_size;
*size = enc->sei_size;
return true;
}
struct obs_encoder_info vaapi_encoder_info = {
.id = "ffmpeg_vaapi",
.type = OBS_ENCODER_VIDEO,
.codec = "h264",
.get_name = vaapi_getname,
.create = vaapi_create,
.destroy = vaapi_destroy,
.encode = vaapi_encode,
.get_defaults = vaapi_defaults,
.id = "ffmpeg_vaapi",
.type = OBS_ENCODER_VIDEO,
.codec = "h264",
.get_name = vaapi_getname,
.create = vaapi_create,
.destroy = vaapi_destroy,
.encode = vaapi_encode,
.get_defaults = vaapi_defaults,
.get_properties = vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
};
#endif

View file

@ -1,11 +1,10 @@
#include <obs-module.h>
#include <util/darray.h>
#include <util/platform.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <pthread.h>
#include "obs-ffmpeg-config.h"
#ifdef _WIN32
#include <dxgi.h>
@ -20,10 +19,10 @@ MODULE_EXPORT const char *obs_module_description(void)
return "FFmpeg based sources/outputs/encoders";
}
extern struct obs_source_info ffmpeg_source;
extern struct obs_output_info ffmpeg_output;
extern struct obs_output_info ffmpeg_muxer;
extern struct obs_output_info replay_buffer;
extern struct obs_source_info ffmpeg_source;
extern struct obs_output_info ffmpeg_output;
extern struct obs_output_info ffmpeg_muxer;
extern struct obs_output_info replay_buffer;
extern struct obs_encoder_info aac_encoder_info;
extern struct obs_encoder_info opus_encoder_info;
extern struct obs_encoder_info nvenc_encoder_info;
@ -36,134 +35,16 @@ extern struct obs_encoder_info nvenc_encoder_info;
extern struct obs_encoder_info vaapi_encoder_info;
#endif
static DARRAY(struct log_context {
void *context;
char str[4096];
int print_prefix;
} *) active_log_contexts;
static DARRAY(struct log_context *) cached_log_contexts;
pthread_mutex_t log_contexts_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct log_context *create_or_fetch_log_context(void *context)
{
pthread_mutex_lock(&log_contexts_mutex);
for (size_t i = 0; i < active_log_contexts.num; i++) {
if (context == active_log_contexts.array[i]->context) {
pthread_mutex_unlock(&log_contexts_mutex);
return active_log_contexts.array[i];
}
}
struct log_context *new_log_context = NULL;
size_t cnt = cached_log_contexts.num;
if (!!cnt) {
new_log_context = cached_log_contexts.array[cnt - 1];
da_pop_back(cached_log_contexts);
}
if (!new_log_context)
new_log_context = bzalloc(sizeof(struct log_context));
new_log_context->context = context;
new_log_context->str[0] = '\0';
new_log_context->print_prefix = 1;
da_push_back(active_log_contexts, &new_log_context);
pthread_mutex_unlock(&log_contexts_mutex);
return new_log_context;
}
static void destroy_log_context(struct log_context *log_context)
{
pthread_mutex_lock(&log_contexts_mutex);
da_erase_item(active_log_contexts, &log_context);
da_push_back(cached_log_contexts, &log_context);
pthread_mutex_unlock(&log_contexts_mutex);
}
static void ffmpeg_log_callback(void* context, int level, const char* format,
va_list args)
{
if (format == NULL)
return;
struct log_context *log_context = create_or_fetch_log_context(context);
char *str = log_context->str;
av_log_format_line(context, level, format, args, str + strlen(str),
(int)(sizeof(log_context->str) - strlen(str)),
&log_context->print_prefix);
int obs_level;
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
obs_level = LOG_ERROR;
break;
case AV_LOG_ERROR:
case AV_LOG_WARNING:
obs_level = LOG_WARNING;
break;
case AV_LOG_INFO:
case AV_LOG_VERBOSE:
obs_level = LOG_INFO;
break;
case AV_LOG_DEBUG:
default:
obs_level = LOG_DEBUG;
}
if (!log_context->print_prefix)
return;
char *str_end = str + strlen(str) - 1;
while(str < str_end) {
if (*str_end != '\n')
break;
*str_end-- = '\0';
}
if (str_end <= str)
goto cleanup;
blog(obs_level, "[ffmpeg] %s", str);
cleanup:
destroy_log_context(log_context);
}
#ifndef __APPLE__
static const char *nvenc_check_name = "nvenc_check";
#ifdef _WIN32
static const wchar_t *blacklisted_adapters[] = {
L"720M",
L"730M",
L"740M",
L"745M",
L"820M",
L"830M",
L"840M",
L"845M",
L"920M",
L"930M",
L"940M",
L"945M",
L"1030",
L"MX110",
L"MX130",
L"MX150",
L"MX230",
L"MX250",
L"M520",
L"M500",
L"P500",
L"K620M"
L"720M", L"730M", L"740M", L"745M", L"820M", L"830M",
L"840M", L"845M", L"920M", L"930M", L"940M", L"945M",
L"1030", L"MX110", L"MX130", L"MX150", L"MX230", L"MX250",
L"M520", L"M500", L"P500", L"K620M",
};
static const size_t num_blacklisted =
@ -202,7 +83,7 @@ static bool is_blacklisted(const wchar_t *name)
return false;
}
typedef HRESULT (WINAPI *create_dxgi_proc)(const IID *, IDXGIFactory1 **);
typedef HRESULT(WINAPI *create_dxgi_proc)(const IID *, IDXGIFactory1 **);
static bool nvenc_device_available(void)
{
@ -226,7 +107,7 @@ static bool nvenc_device_available(void)
if (!create) {
create = (create_dxgi_proc)GetProcAddress(dxgi,
"CreateDXGIFactory1");
"CreateDXGIFactory1");
if (!create) {
return true;
}
@ -266,7 +147,9 @@ extern bool load_nvenc_lib(void);
static bool nvenc_supported(void)
{
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
profile_start(nvenc_check_name);
@ -319,13 +202,13 @@ extern void jim_nvenc_load(void);
extern void jim_nvenc_unload(void);
#endif
#if ENABLE_FFMPEG_LOGGING
extern void obs_ffmpeg_load_logging(void);
extern void obs_ffmpeg_unload_logging(void);
#endif
bool obs_module_load(void)
{
da_init(active_log_contexts);
da_init(cached_log_contexts);
//av_log_set_callback(ffmpeg_log_callback);
obs_register_source(&ffmpeg_source);
obs_register_output(&ffmpeg_output);
obs_register_output(&ffmpeg_muxer);
@ -348,28 +231,20 @@ bool obs_module_load(void)
obs_register_encoder(&vaapi_encoder_info);
}
#endif
#endif
#if ENABLE_FFMPEG_LOGGING
obs_ffmpeg_load_logging();
#endif
return true;
}
void obs_module_unload(void)
{
av_log_set_callback(av_log_default_callback);
#ifdef _WIN32
pthread_mutex_destroy(&log_contexts_mutex);
#if ENABLE_FFMPEG_LOGGING
obs_ffmpeg_unload_logging();
#endif
for (size_t i = 0; i < active_log_contexts.num; i++) {
bfree(active_log_contexts.array[i]);
}
for (size_t i = 0; i < cached_log_contexts.num; i++) {
bfree(cached_log_contexts.array[i]);
}
da_free(active_log_contexts);
da_free(cached_log_contexts);
#ifdef _WIN32
jim_nvenc_unload();
#endif