Imported Upstream version 0.13.2+dsfg1
This commit is contained in:
commit
fb3990e9e5
2036 changed files with 287360 additions and 0 deletions
34
plugins/obs-ffmpeg/CMakeLists.txt
Normal file
34
plugins/obs-ffmpeg/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
project(obs-ffmpeg)
|
||||
|
||||
if(MSVC)
|
||||
set(obs-ffmpeg_PLATFORM_DEPS
|
||||
w32-pthreads)
|
||||
endif()
|
||||
|
||||
find_package(FFmpeg REQUIRED
|
||||
COMPONENTS avcodec avfilter avdevice avutil swscale avformat swresample)
|
||||
include_directories(${FFMPEG_INCLUDE_DIRS})
|
||||
|
||||
set(obs-ffmpeg_HEADERS
|
||||
obs-ffmpeg-formats.h
|
||||
obs-ffmpeg-compat.h
|
||||
closest-pixel-format.h)
|
||||
set(obs-ffmpeg_SOURCES
|
||||
obs-ffmpeg.c
|
||||
obs-ffmpeg-aac.c
|
||||
obs-ffmpeg-output.c
|
||||
obs-ffmpeg-mux.c
|
||||
obs-ffmpeg-source.c)
|
||||
|
||||
add_library(obs-ffmpeg MODULE
|
||||
${obs-ffmpeg_HEADERS}
|
||||
${obs-ffmpeg_SOURCES})
|
||||
target_link_libraries(obs-ffmpeg
|
||||
libobs
|
||||
libff
|
||||
${obs-ffmpeg_PLATFORM_DEPS}
|
||||
${FFMPEG_LIBRARIES})
|
||||
|
||||
install_obs_plugin_with_data(obs-ffmpeg data)
|
||||
|
||||
add_subdirectory(ffmpeg-mux)
|
||||
135
plugins/obs-ffmpeg/closest-pixel-format.h
Normal file
135
plugins/obs-ffmpeg/closest-pixel-format.h
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
const enum AVPixelFormat *cur_formats = formats;
|
||||
|
||||
while (*cur_formats != AV_PIX_FMT_NONE) {
|
||||
enum AVPixelFormat avail_format = *cur_formats;
|
||||
|
||||
if (best_format == avail_format)
|
||||
return best_format;
|
||||
|
||||
cur_formats++;
|
||||
}
|
||||
|
||||
best++;
|
||||
}
|
||||
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
static inline enum AVPixelFormat get_closest_format(
|
||||
enum AVPixelFormat format,
|
||||
const enum AVPixelFormat *formats)
|
||||
{
|
||||
enum AVPixelFormat best_format = AV_PIX_FMT_NONE;
|
||||
|
||||
if (!formats || formats[0] == AV_PIX_FMT_NONE)
|
||||
return format;
|
||||
|
||||
switch ((int)format) {
|
||||
|
||||
case AV_PIX_FMT_YUV444P:
|
||||
best_format = get_best_format(i444_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
best_format = get_best_format(i420_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_NV12:
|
||||
best_format = get_best_format(nv12_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_YUYV422:
|
||||
best_format = get_best_format(yuy2_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_UYVY422:
|
||||
best_format = get_best_format(uyvy_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_RGBA:
|
||||
best_format = get_best_format(rgba_formats, formats);
|
||||
break;
|
||||
case AV_PIX_FMT_BGRA:
|
||||
best_format = get_best_format(bgra_formats, formats);
|
||||
break;
|
||||
}
|
||||
|
||||
return (best_format == AV_PIX_FMT_NONE) ? formats[0] : best_format;
|
||||
}
|
||||
6
plugins/obs-ffmpeg/data/locale/ar-SA.ini
Normal file
6
plugins/obs-ffmpeg/data/locale/ar-SA.ini
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
FFmpegOutput="مخرج FFmpeg"
|
||||
FFmpegAAC="ترميز AAC الافتراضي لـFFmpeg"
|
||||
Bitrate="معدل النقل"
|
||||
|
||||
|
||||
|
||||
5
plugins/obs-ffmpeg/data/locale/bg-BG.ini
Normal file
5
plugins/obs-ffmpeg/data/locale/bg-BG.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FFmpegOutput="FFmpeg изход"
|
||||
Bitrate="Битрейт"
|
||||
|
||||
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/ca-ES.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/ca-ES.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="Sortida FFmpeg"
|
||||
FFmpegAAC="Codificador FFmpeg AAC predeterminat"
|
||||
Bitrate="Taxa de bits"
|
||||
|
||||
FFmpegSource="Font multimèdia"
|
||||
LocalFile="Fitxer local"
|
||||
Looping="Bucle"
|
||||
Input="Entrada"
|
||||
InputFormat="Format d'entrada"
|
||||
ForceFormat="Força la conversió de format"
|
||||
HardwareDecode="Usa la descodificació per maquinari si és disponible"
|
||||
ClearOnMediaEnd="Amaga l'origen en acabar la reproducció"
|
||||
Advanced="Avançat"
|
||||
AudioBufferSize="Mida de la memòria intermèdia d'àudio (em fotogrames)"
|
||||
VideoBufferSize="Mida de la memòria intermèdia de vídeo (en fotogrames)"
|
||||
FrameDropping="Nivell de pèrdua de quadres"
|
||||
DiscardNone="Cap"
|
||||
DiscardDefault="Per defecte (paquets invàlids)"
|
||||
DiscardNonRef="Quadres de no-referència"
|
||||
DiscardBiDir="Quadres bidireccionals"
|
||||
DiscardNonIntra="Quadres no-interiors"
|
||||
DiscardNonKey="Quadres no-clau"
|
||||
DiscardAll="Tots els marcs (aneu amb compte!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/cs-CZ.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/cs-CZ.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="Výstup FFmpegu"
|
||||
FFmpegAAC="Výchozí FFmpeg AAC enkodér"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Médium"
|
||||
LocalFile="Místní soubor"
|
||||
Looping="Opakovat"
|
||||
Input="Vstup"
|
||||
InputFormat="Formát vstupu"
|
||||
ForceFormat="Nařídit převod formátu"
|
||||
HardwareDecode="Použít hardwarové dekódování, pokud je k dispozici"
|
||||
ClearOnMediaEnd="Skrýt zdroj po skončení přehrávání"
|
||||
Advanced="Pokročilé"
|
||||
AudioBufferSize="Velikost bufferu zvuku (snímky)"
|
||||
VideoBufferSize="Velikost bufferu videa (snímky)"
|
||||
FrameDropping="Úroveň ztrácení snímků"
|
||||
DiscardNone="Žádné"
|
||||
DiscardDefault="Výchozí (chybné packety)"
|
||||
DiscardNonRef="Snímky bez reference"
|
||||
DiscardBiDir="Oboustranné snímky"
|
||||
DiscardNonIntra="Snímky mimo rámec"
|
||||
DiscardNonKey="Ne-klíčové snímky"
|
||||
DiscardAll="Všechny snímky (Pozor!)"
|
||||
RestartWhenActivated="Restartovat přehrávání poté, co je zdroj aktivován"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Všechny mediální soubory"
|
||||
MediaFileFilter.VideoFiles="Video soubory"
|
||||
MediaFileFilter.AudioFiles="Zvukové soubory"
|
||||
MediaFileFilter.AllFiles="Všechny soubory"
|
||||
|
||||
21
plugins/obs-ffmpeg/data/locale/da-DK.ini
Normal file
21
plugins/obs-ffmpeg/data/locale/da-DK.ini
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
FFmpegOutput="FFmpeg Output"
|
||||
FFmpegAAC="FFmpeg Standard AAC Encoder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Mediekilde"
|
||||
LocalFile="Lokal fil"
|
||||
Looping="Gentagelse"
|
||||
Input="Input"
|
||||
InputFormat="Input format"
|
||||
HardwareDecode="Brug hardwareafkodning når tilgængelige"
|
||||
ClearOnMediaEnd="Skjul kilde når afspilning slutter"
|
||||
Advanced="Avanceret"
|
||||
AudioBufferSize="Audio bufferstørrelse (frames)"
|
||||
VideoBufferSize="Video bufferstørrelse (frames)"
|
||||
DiscardNone="Ingen"
|
||||
DiscardBiDir="Tovejs frames"
|
||||
DiscardNonIntra="Non-Intra frames"
|
||||
DiscardNonKey="Non-Key Frames"
|
||||
DiscardAll="Alle frames (pas på!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/de-DE.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/de-DE.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg Ausgabe"
|
||||
FFmpegAAC="FFmpeg Standard AAC Encoder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Medienquelle"
|
||||
LocalFile="Lokale Datei"
|
||||
Looping="Endlosschleife"
|
||||
Input="Eingabe"
|
||||
InputFormat="Eingabeformat"
|
||||
ForceFormat="Erzwinge Formatkonvertierung"
|
||||
HardwareDecode="Verwende Hardwaredecodierung, falls verfügbar"
|
||||
ClearOnMediaEnd="Quelle verbergen, wenn Wiedergabe endet"
|
||||
Advanced="Erweitert"
|
||||
AudioBufferSize="Audiopuffergröße (Frames)"
|
||||
VideoBufferSize="Videopuffergröße (Frames)"
|
||||
FrameDropping="Frame Dropping Level"
|
||||
DiscardNone="Keine"
|
||||
DiscardDefault="Standard (ungültige Pakete)"
|
||||
DiscardNonRef="Non-Reference Frames"
|
||||
DiscardBiDir="Bi-Directional Frames"
|
||||
DiscardNonIntra="Non-Intra Frames"
|
||||
DiscardNonKey="Non-Key Frames"
|
||||
DiscardAll="Alle Frames (Vorsicht!)"
|
||||
RestartWhenActivated="Wiedergabe erneut starten, wenn Quelle aktiviert wird"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Alle Mediendateien"
|
||||
MediaFileFilter.VideoFiles="Video-Dateien"
|
||||
MediaFileFilter.AudioFiles="Audio-Dateien"
|
||||
MediaFileFilter.AllFiles="Alle Dateien"
|
||||
|
||||
18
plugins/obs-ffmpeg/data/locale/el-GR.ini
Normal file
18
plugins/obs-ffmpeg/data/locale/el-GR.ini
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
FFmpegOutput="Έξοδος FFmpeg"
|
||||
FFmpegAAC="FFmpeg προεπιλεγμένος κωδικοποιητής AAC"
|
||||
Bitrate="Ρυθμός μετάδοσης bit"
|
||||
|
||||
LocalFile="Τοπικό αρχείο"
|
||||
Looping="Επανάληψη"
|
||||
Input="Είσοδος"
|
||||
InputFormat="Μορφή Εισόδου"
|
||||
ForceFormat="Εξαναγκασμός μετατροπής μορφής"
|
||||
HardwareDecode="Χρήση αποκωδικοποίησης υλικού όταν είναι διαθέσιμη"
|
||||
ClearOnMediaEnd="Απόκρυψη πηγής όταν τελειώνει η αναπαραγωγή"
|
||||
Advanced="Σύνθετες επιλογές"
|
||||
FrameDropping="Επίπεδο Ρίψης Καρέ"
|
||||
DiscardNone="Κανένα"
|
||||
DiscardDefault="Προεπιλογή (άκυρα πακέτα)"
|
||||
DiscardAll="Όλα τα καρέ (Προσοχή!)"
|
||||
|
||||
|
||||
29
plugins/obs-ffmpeg/data/locale/en-US.ini
Normal file
29
plugins/obs-ffmpeg/data/locale/en-US.ini
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
FFmpegOutput="FFmpeg Output"
|
||||
FFmpegAAC="FFmpeg Default AAC Encoder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Media Source"
|
||||
LocalFile="Local File"
|
||||
Looping="Loop"
|
||||
Input="Input"
|
||||
InputFormat="Input Format"
|
||||
ForceFormat="Force format conversion"
|
||||
HardwareDecode="Use hardware decoding when available"
|
||||
ClearOnMediaEnd="Hide source when playback ends"
|
||||
Advanced="Advanced"
|
||||
AudioBufferSize="Audio Buffer Size (frames)"
|
||||
VideoBufferSize="Video Buffer Size (frames)"
|
||||
FrameDropping="Frame Dropping Level"
|
||||
DiscardNone="None"
|
||||
DiscardDefault="Default (Invalid Packets)"
|
||||
DiscardNonRef="Non-Reference Frames"
|
||||
DiscardBiDir="Bi-Directional Frames"
|
||||
DiscardNonIntra="Non-Intra Frames"
|
||||
DiscardNonKey="Non-Key Frames"
|
||||
DiscardAll="All Frames (Careful!)"
|
||||
RestartWhenActivated="Restart playback when source becomes active"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="All Media Files"
|
||||
MediaFileFilter.VideoFiles="Video Files"
|
||||
MediaFileFilter.AudioFiles="Audio Files"
|
||||
MediaFileFilter.AllFiles="All Files"
|
||||
30
plugins/obs-ffmpeg/data/locale/es-ES.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/es-ES.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="Salida de FFmpeg"
|
||||
FFmpegAAC="Codificador AAC FFmpeg predeterminado"
|
||||
Bitrate="Tasa de bits"
|
||||
|
||||
FFmpegSource="Fuente multimedia"
|
||||
LocalFile="Archivo local"
|
||||
Looping="Bucle"
|
||||
Input="Entrada"
|
||||
InputFormat="Formato de entrada"
|
||||
ForceFormat="Forzar la conversión de formato"
|
||||
HardwareDecode="Utilizar la decodificación por hardware cuando esté disponible"
|
||||
ClearOnMediaEnd="Ocultar la fuente cuando finaliza la reproducción"
|
||||
Advanced="Avanzado"
|
||||
AudioBufferSize="Tamaño del buffer de audio (cuadros)"
|
||||
VideoBufferSize="Tamaño del buffer de vídeo (cuadros)"
|
||||
FrameDropping="Nivel de omisión de cuadros"
|
||||
DiscardNone="Ninguno"
|
||||
DiscardDefault="Por defecto (paquetes no válidos)"
|
||||
DiscardNonRef="Fotogramas referenciar"
|
||||
DiscardBiDir="Fotogramas bidireccionales"
|
||||
DiscardNonIntra="Fotogramas no intra-frame"
|
||||
DiscardNonKey="Fotogramas no claves"
|
||||
DiscardAll="Todos los fotogramas (¡Cuidado!)"
|
||||
RestartWhenActivated="Reiniciar la reproducción cuando la fuente esté activa"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Todos los archivos multimedia"
|
||||
MediaFileFilter.VideoFiles="Archivos de vídeo"
|
||||
MediaFileFilter.AudioFiles="Archivos de audio"
|
||||
MediaFileFilter.AllFiles="Todos los Archivos"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/eu-ES.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/eu-ES.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg Irteera"
|
||||
FFmpegAAC="FFmpeg Berezko AAC Kodeatzailea"
|
||||
Bitrate="Bitneurria"
|
||||
|
||||
FFmpegSource="Multimedia Iturburua"
|
||||
LocalFile="Tokiko Agiria"
|
||||
Looping="Onartu begizta"
|
||||
Input="Sarrera"
|
||||
InputFormat="Sarrera Heuskarria"
|
||||
ForceFormat="Behartu heuskarri bihurketa"
|
||||
HardwareDecode="Erabili hardware dekodeaketa eskuragarri dagoenean"
|
||||
ClearOnMediaEnd="Ezkutatu iturburua irakurketa amaitutakoan"
|
||||
Advanced="Aurreratua"
|
||||
AudioBufferSize="Audio Buffer Neurria (frameak)"
|
||||
VideoBufferSize="Bideo Buffer Neurria (frameak)"
|
||||
FrameDropping="Frame Erortze Maila"
|
||||
DiscardNone="Ezer ez"
|
||||
DiscardDefault="Berezkoa (Pakete Baliogabeak)"
|
||||
DiscardNonRef="Ez-Xehetasun Frameak"
|
||||
DiscardBiDir="Bi-Norabideko Frameak"
|
||||
DiscardNonIntra="Ez-Intra Frameak"
|
||||
DiscardNonKey="Ez-Giltza Frameak"
|
||||
DiscardAll="Frame Guztiak (Kontuz!)"
|
||||
RestartWhenActivated="Berrabiarazi irakurketa iturburua gaitua bihurtzerakoan"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Multimedia Agiri Guztiak"
|
||||
MediaFileFilter.VideoFiles="Bideo Agiriak"
|
||||
MediaFileFilter.AudioFiles="Audio Agiriak"
|
||||
MediaFileFilter.AllFiles="Agiri Guztiak"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/fi-FI.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/fi-FI.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg ulostulo"
|
||||
FFmpegAAC="FFmpeg oletus AAC-enkooderi"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Lisää media"
|
||||
LocalFile="Paikallinen tiedosto"
|
||||
Looping="Toista jatkuvasti"
|
||||
Input="Sisääntulo"
|
||||
InputFormat="Sisääntulon muoto"
|
||||
ForceFormat="Pakota muodon muuntaminen"
|
||||
HardwareDecode="Käytä laitteistoa purkamiseen, kun mahdollista"
|
||||
ClearOnMediaEnd="Piilota lähde kun toisto päättyy"
|
||||
Advanced="Lisäasetukset"
|
||||
AudioBufferSize="Äänipuskurin koko (ruutua)"
|
||||
VideoBufferSize="Videopuskurin koko (ruutua)"
|
||||
FrameDropping="Frame Dropping -taso"
|
||||
DiscardNone="Ei mikään"
|
||||
DiscardDefault="Oletus (virheelliset paketit)"
|
||||
DiscardNonRef="Non-Reference Frames"
|
||||
DiscardBiDir="Bi-Directional Frames"
|
||||
DiscardNonIntra="Non-Intra Frames"
|
||||
DiscardNonKey="Non-Key Frames"
|
||||
DiscardAll="All Frames (Varoitus!)"
|
||||
RestartWhenActivated="Aloita toisto uudelleen kun lähde aktivoituu"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Kaikki mediatiedostot"
|
||||
MediaFileFilter.VideoFiles="Videotiedostot"
|
||||
MediaFileFilter.AudioFiles="Äänitiedostot"
|
||||
MediaFileFilter.AllFiles="Kaikki tiedostot"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/fr-FR.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/fr-FR.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="Sortie FFmpeg"
|
||||
FFmpegAAC="Encodeur AAC FFmpeg par défaut"
|
||||
Bitrate="Débit"
|
||||
|
||||
FFmpegSource="Source média"
|
||||
LocalFile="Fichier local"
|
||||
Looping="En boucle"
|
||||
Input="Entrée"
|
||||
InputFormat="Format d'entrée"
|
||||
ForceFormat="Forcer la conversion du format"
|
||||
HardwareDecode="Utiliser le décodage matériel si possible"
|
||||
ClearOnMediaEnd="Cacher la source lorsque la lecture est finie"
|
||||
Advanced="Options avancées"
|
||||
AudioBufferSize="Taille du tampon audio (en images)"
|
||||
VideoBufferSize="Taille du tampon vidéo (en images)"
|
||||
FrameDropping="Niveau de perte d'images"
|
||||
DiscardNone="Aucune"
|
||||
DiscardDefault="Par défaut (paquets invalides)"
|
||||
DiscardNonRef="Images non-références"
|
||||
DiscardBiDir="Images bidirectionnelles"
|
||||
DiscardNonIntra="Images non-intra"
|
||||
DiscardNonKey="Images non-clés"
|
||||
DiscardAll="Toutes les images (Attention !)"
|
||||
RestartWhenActivated="Reprendre la lecture quand la source est active"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Tous les fichiers multimédias"
|
||||
MediaFileFilter.VideoFiles="Fichiers vidéo"
|
||||
MediaFileFilter.AudioFiles="Fichiers audio"
|
||||
MediaFileFilter.AllFiles="Tous les fichiers"
|
||||
|
||||
23
plugins/obs-ffmpeg/data/locale/gl-ES.ini
Normal file
23
plugins/obs-ffmpeg/data/locale/gl-ES.ini
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
FFmpegOutput="Saída de FFmpeg"
|
||||
FFmpegAAC="Codificador AAC FFmpeg predefinido"
|
||||
Bitrate="Velocidade de bits"
|
||||
|
||||
FFmpegSource="Fonte multimedia"
|
||||
LocalFile="Ficheiro local"
|
||||
Looping="Bucle"
|
||||
Input="Entrada"
|
||||
InputFormat="Formato de entrada"
|
||||
ForceFormat="Forzar a conversión do formato"
|
||||
HardwareDecode="Utilizar a descodificación por hárdware cando estiver dispoñible"
|
||||
ClearOnMediaEnd="Agochar a fonte cando a reprodución remata"
|
||||
Advanced="Avanzado"
|
||||
AudioBufferSize="Tamaño do búfer de audio (marcos)"
|
||||
VideoBufferSize="Tamaño do búfer de vídeo (marcos)"
|
||||
FrameDropping="Nivel de omisión de marcos"
|
||||
DiscardNone="Ningún"
|
||||
DiscardDefault="Predefinido (paquetes non válidos)"
|
||||
DiscardNonRef="Marcos sen referencias"
|
||||
DiscardBiDir="Marcos bidireccionais"
|
||||
DiscardAll="Todos os marcos (con tino!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/hr-HR.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/hr-HR.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg izlaz"
|
||||
FFmpegAAC="FFmpeg podrazumevani AAC enkoder"
|
||||
Bitrate="Protok"
|
||||
|
||||
FFmpegSource="Medija izvor"
|
||||
LocalFile="Lokalna datoteka"
|
||||
Looping="Ponavljanje"
|
||||
Input="Ulaz"
|
||||
InputFormat="Format ulaza"
|
||||
ForceFormat="Prisilno pretvaranje formata"
|
||||
HardwareDecode="Koristi hardversko enkodiranje kada je dostupno"
|
||||
ClearOnMediaEnd="Sakrij izvor kada se reprodukcija završi"
|
||||
Advanced="Napredno"
|
||||
AudioBufferSize="Veličina zvučnog bafera (u frejmovima)"
|
||||
VideoBufferSize="Veličina video bafera (u frejmovima)"
|
||||
FrameDropping="Nivo ispuštanja frejmova"
|
||||
DiscardNone="Nijedan"
|
||||
DiscardDefault="Podrazumevano (neispravni paketi)"
|
||||
DiscardNonRef="Frejmovi bez reference"
|
||||
DiscardBiDir="Dvosmerni frejmovi"
|
||||
DiscardNonIntra="Ne-intra frejmovi"
|
||||
DiscardNonKey="Frejmovi koji nisu ključni"
|
||||
DiscardAll="Svi frejmovi (oprezno!)"
|
||||
RestartWhenActivated="Ponovi reprodukciju kada izvor postane aktivan"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Sve medija datoteke"
|
||||
MediaFileFilter.VideoFiles="Video datoteke"
|
||||
MediaFileFilter.AudioFiles="Zvučne datoteke"
|
||||
MediaFileFilter.AllFiles="Sve datoteke"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/hu-HU.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/hu-HU.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg Kimenet"
|
||||
FFmpegAAC="FFmpeg alapértelmezett AAC kódoló"
|
||||
Bitrate="Bitráta"
|
||||
|
||||
FFmpegSource="Média Forrás"
|
||||
LocalFile="Helyi Fájl"
|
||||
Looping="Ismétlés"
|
||||
Input="Bemenet"
|
||||
InputFormat="Bemeneti Formátum"
|
||||
ForceFormat="Formátum átváltás kényszerítése"
|
||||
HardwareDecode="Hardveres dekódolás használata, ha rendelkezésre áll"
|
||||
ClearOnMediaEnd="Forrás elrejtése a lejátszás végeztével"
|
||||
Advanced="Haladó"
|
||||
AudioBufferSize="Audio Puffer Méret (képkockák)"
|
||||
VideoBufferSize="Video Puffer Méret (képkockák)"
|
||||
FrameDropping="Képkocka Ejtésszint"
|
||||
DiscardNone="Semmi"
|
||||
DiscardDefault="Alapértelmezett (Érvénytelen Csomagok)"
|
||||
DiscardNonRef="Nem Referencia Kockák"
|
||||
DiscardBiDir="Kétirányú Kockák"
|
||||
DiscardNonIntra="Nem Belső Kockák"
|
||||
DiscardNonKey="Nem Kulcskockák"
|
||||
DiscardAll="Összes Kocka (Óvatosan!)"
|
||||
RestartWhenActivated="Lejátszás újraindítása, ha a forrás aktivizálódik"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Minden Média Fájl"
|
||||
MediaFileFilter.VideoFiles="Video Fájlok"
|
||||
MediaFileFilter.AudioFiles="Hang Fájlok"
|
||||
MediaFileFilter.AllFiles="Minden Fájl"
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/it-IT.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/it-IT.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="Uscita FFmpeg"
|
||||
FFmpegAAC="Codificatore FFmpeg predefinito AAC"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Origine multimediale"
|
||||
LocalFile="File locale"
|
||||
Looping="Ripeti"
|
||||
Input="Input"
|
||||
InputFormat="Formato di input"
|
||||
ForceFormat="Forza conversione di formato"
|
||||
HardwareDecode="Utilizza la decodifica hardware quando disponibile"
|
||||
ClearOnMediaEnd="Nascondi la fonte quando termina la riproduzione"
|
||||
Advanced="Avanzate"
|
||||
AudioBufferSize="Dimensione del Buffer audio (fotogrammi)"
|
||||
VideoBufferSize="Dimensioni del Buffer video (fotogrammi)"
|
||||
FrameDropping="Frame Dropping Level"
|
||||
DiscardNone="Nessuno"
|
||||
DiscardDefault="Predefinito (pacchetti non validi)"
|
||||
DiscardNonRef="Frame non di riferimento"
|
||||
DiscardBiDir="Frame bi-direzionali"
|
||||
DiscardNonIntra="Frame non interposti"
|
||||
DiscardNonKey="Frame non di chiave"
|
||||
DiscardAll="Tutti i Frame (opzione per utenti più esperti)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/ja-JP.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/ja-JP.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg の出力"
|
||||
FFmpegAAC="FFmpeg 既定のAAC エンコーダー"
|
||||
Bitrate="ビットレート"
|
||||
|
||||
FFmpegSource="メディアソース"
|
||||
LocalFile="ローカルファイル"
|
||||
Looping="繰り返し"
|
||||
Input="入力"
|
||||
InputFormat="入力フォーマット"
|
||||
ForceFormat="強制的にフォーマットを変換"
|
||||
HardwareDecode="可能な場合ハードウェアデコードを使用"
|
||||
ClearOnMediaEnd="再生終了時にソースを非表示にする"
|
||||
Advanced="高度な設定"
|
||||
AudioBufferSize="音声バッファーサイズ(フレーム)"
|
||||
VideoBufferSize="映像バッファーサイズ(フレーム)"
|
||||
FrameDropping="フレームドロップレベル"
|
||||
DiscardNone="なし"
|
||||
DiscardDefault="既定(無効なパケット)"
|
||||
DiscardNonRef="非参照フレーム"
|
||||
DiscardBiDir="双方向フレーム"
|
||||
DiscardNonIntra="非イントラフレーム"
|
||||
DiscardNonKey="非キーフレーム"
|
||||
DiscardAll="すべてのフレーム(注意!)"
|
||||
RestartWhenActivated="ソースがアクティブになったときに再生を再開する"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="すべてのメディアファイル"
|
||||
MediaFileFilter.VideoFiles="ビデオファイル"
|
||||
MediaFileFilter.AudioFiles="オーディオファイル"
|
||||
MediaFileFilter.AllFiles="すべてのファイル"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/ko-KR.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/ko-KR.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg 출력"
|
||||
FFmpegAAC="FFmpeg 기본 AAC 인코더"
|
||||
Bitrate="비트레이트"
|
||||
|
||||
FFmpegSource="미디어 소스"
|
||||
LocalFile="로컬 파일"
|
||||
Looping="반복"
|
||||
Input="입력"
|
||||
InputFormat="입력 형식"
|
||||
ForceFormat="강제 형식 전환"
|
||||
HardwareDecode="가능한 경우 하드웨어 디코딩 사용"
|
||||
ClearOnMediaEnd="재생이 끝나면 소스를 숨기기"
|
||||
Advanced="고급"
|
||||
AudioBufferSize="오디오 버퍼 크기 (프레임)"
|
||||
VideoBufferSize="비디오 버퍼 크기 (프레임)"
|
||||
FrameDropping="프레임 드롭 수준"
|
||||
DiscardNone="없음"
|
||||
DiscardDefault="기본 (유효하지 않은 패킷)"
|
||||
DiscardNonRef="비 참조 프레임"
|
||||
DiscardBiDir="양방향 프레임"
|
||||
DiscardNonIntra="비 내부 프레임"
|
||||
DiscardNonKey="비 키 프레임"
|
||||
DiscardAll="모든 프레임 (주의!)"
|
||||
RestartWhenActivated="소스가 활성화될 때 재생을 다시 시작"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="모든 미디어 파일"
|
||||
MediaFileFilter.VideoFiles="비디오 파일"
|
||||
MediaFileFilter.AudioFiles="오디오 파일"
|
||||
MediaFileFilter.AllFiles="모든 파일"
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/nb-NO.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/nb-NO.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="FFmpeg utdata"
|
||||
FFmpegAAC="Standard FFmpeg AAC-koder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Mediekilde"
|
||||
LocalFile="Lokal fil"
|
||||
Looping="Repeter"
|
||||
Input="Inndata"
|
||||
InputFormat="Inndataformat"
|
||||
ForceFormat="Tving formatkonvertering"
|
||||
HardwareDecode="Bruk maskinvaredekoding når tilgjengelig"
|
||||
ClearOnMediaEnd="Skjul kilde når avspilling ender"
|
||||
Advanced="Avansert"
|
||||
AudioBufferSize="Lydbufferstørrelsen (i bilder)"
|
||||
VideoBufferSize="Videobufferstørrelsen (i bilder)"
|
||||
FrameDropping="Bildeforkastingsnivå"
|
||||
DiscardNone="Ingen"
|
||||
DiscardDefault="Standard (ugyldige pakker)"
|
||||
DiscardNonRef="Ikkereferansebilder"
|
||||
DiscardBiDir="Toveisbilder"
|
||||
DiscardNonIntra="Non-intra bilder"
|
||||
DiscardNonKey="Ikkenøkkelbilder"
|
||||
DiscardAll="Alle bilder (forsiktig!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/nl-NL.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/nl-NL.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg-uitvoer"
|
||||
FFmpegAAC="FFmpeg Standaard AAC Encoder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Mediabron"
|
||||
LocalFile="Lokaal bestand"
|
||||
Looping="Herhalen"
|
||||
Input="Invoer"
|
||||
InputFormat="Invoerformaat"
|
||||
ForceFormat="Forceer formaatconversie"
|
||||
HardwareDecode="Gebruik hardware-decoding wanneer mogelijk"
|
||||
ClearOnMediaEnd="Verberg de bron na het afspelen"
|
||||
Advanced="Geavanceerd"
|
||||
AudioBufferSize="Audio Buffergrootte (frames)"
|
||||
VideoBufferSize="Video Buffergrootte (frames)"
|
||||
FrameDropping="Frame-Dropping Niveau"
|
||||
DiscardNone="Geen"
|
||||
DiscardDefault="Standaard (Ongeldige Pakketten)"
|
||||
DiscardNonRef="Niet-Reference Frames"
|
||||
DiscardBiDir="Bi-Directionele Frames"
|
||||
DiscardNonIntra="Niet-Intra Frames"
|
||||
DiscardNonKey="Niet-Key Frames"
|
||||
DiscardAll="Alle Frames (Voorzichtig!)"
|
||||
RestartWhenActivated="Opnieuw starten met afspelen zodra de bron actief wordt"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Alle mediabestanden"
|
||||
MediaFileFilter.VideoFiles="Videobestanden"
|
||||
MediaFileFilter.AudioFiles="Audiobestanden"
|
||||
MediaFileFilter.AllFiles="Alle bestanden"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/pl-PL.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/pl-PL.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="Wyjście FFmpeg"
|
||||
FFmpegAAC="Domyślny enkoder AAC w FFmpeg"
|
||||
Bitrate="Przepływność bitowa"
|
||||
|
||||
FFmpegSource="Źródło danych"
|
||||
LocalFile="Plik lokalny"
|
||||
Looping="Pętla"
|
||||
Input="Wejście"
|
||||
InputFormat="Format wejściowy"
|
||||
ForceFormat="Wymuś konwersję formatu"
|
||||
HardwareDecode="Użyj sprzętowego dekodowania gdy to możliwe"
|
||||
ClearOnMediaEnd="Ukryj źródło po zakończeniu odtwarzania"
|
||||
Advanced="Zaawansowane"
|
||||
AudioBufferSize="Bufor audio (w klatkach)"
|
||||
VideoBufferSize="Bufor video (w klatkach)"
|
||||
FrameDropping="Poziom gubienia klatek"
|
||||
DiscardNone="Bez gubienia"
|
||||
DiscardDefault="Domyślny (Nieprawidłowe pakiety)"
|
||||
DiscardNonRef="Klatki niereferencyjne"
|
||||
DiscardBiDir="Klatki dwukierunkowe (bi-directional)"
|
||||
DiscardNonIntra="Klatki niewewnętrzne (non-intra)"
|
||||
DiscardNonKey="Klatki niekluczowe (non-key)"
|
||||
DiscardAll="Wszystkie klatki (Ostrożnie!)"
|
||||
RestartWhenActivated="Zrestartuj odtwarzanie, gdy źródła będą aktywne"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Wszystkie pliki multimedialne"
|
||||
MediaFileFilter.VideoFiles="Pliki video"
|
||||
MediaFileFilter.AudioFiles="Pliki audio"
|
||||
MediaFileFilter.AllFiles="Wszystkie pliki"
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/pt-BR.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/pt-BR.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="Saída do FFmpeg"
|
||||
FFmpegAAC="Codificador AAC Padrão do FFmpeg"
|
||||
Bitrate="Taxa de Bits"
|
||||
|
||||
FFmpegSource="Fonte de mídia"
|
||||
LocalFile="Arquivo Local"
|
||||
Looping="Loop"
|
||||
Input="Entrada"
|
||||
InputFormat="Formato de entrada"
|
||||
ForceFormat="Forçar conversão de formato"
|
||||
HardwareDecode="Utilizar descodificação de hardware quando disponível"
|
||||
ClearOnMediaEnd="Ocultar fonte quando a reprodução terminar"
|
||||
Advanced="Avançado"
|
||||
AudioBufferSize="Tamanho do buffer de áudio (frames)"
|
||||
VideoBufferSize="Tamanho de Buffer do vídeo (frames)"
|
||||
FrameDropping="Nível de frame drop"
|
||||
DiscardNone="Nenhum"
|
||||
DiscardDefault="Padrão (pacotes inválidos)"
|
||||
DiscardNonRef="Sem frames de referencia"
|
||||
DiscardBiDir="Frames Bi-direcionais"
|
||||
DiscardNonIntra="Sem intra-frames"
|
||||
DiscardNonKey="Sem keyframes"
|
||||
DiscardAll="Todos os frames(cuidado!)"
|
||||
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/pt-PT.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/pt-PT.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="Saída de FFmpeg"
|
||||
FFmpegAAC="FFmpeg Codificador AAC padrão"
|
||||
Bitrate="Taxa de Bits (Bitrate)"
|
||||
|
||||
FFmpegSource="Fonte de multimédia"
|
||||
LocalFile="Ficheiro local"
|
||||
Looping="Repetir"
|
||||
Input="Entrada"
|
||||
InputFormat="Formato de entrada"
|
||||
ForceFormat="Forçar conversão de formato"
|
||||
HardwareDecode="Utilizar descodificação de hardware quando disponível"
|
||||
ClearOnMediaEnd="Ocultar fonte quando a reprodução terminar"
|
||||
Advanced="Avançado"
|
||||
AudioBufferSize="Tamanho do buffer de áudio (fotogramas)"
|
||||
VideoBufferSize="Tamanho do buffer de vídeo (fotogramas)"
|
||||
FrameDropping="Nível de perda de fotogramas"
|
||||
DiscardNone="Nenhum"
|
||||
DiscardDefault="Padrão (pacotes inválidos)"
|
||||
DiscardNonRef="Fotogramas sem referência"
|
||||
DiscardBiDir="Fotogramas bidirecionais"
|
||||
DiscardNonIntra="Fotogramas não internos"
|
||||
DiscardNonKey="Fotogramas não registados"
|
||||
DiscardAll="Todos os fotogramas (cuidado!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/ro-RO.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/ro-RO.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="Ieşire FFmpeg"
|
||||
FFmpegAAC="Codare AAC implicită pentru FFmpeg"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Sursa Media"
|
||||
LocalFile="Fişier Local"
|
||||
Looping="Loop/Buclă"
|
||||
Input="Intrare"
|
||||
InputFormat="Format de intrare"
|
||||
ForceFormat="Obliga conversia formatului"
|
||||
HardwareDecode="Foloseste decodarea hardware cand e disponibila"
|
||||
ClearOnMediaEnd="Ascunde sursă atunci când se termină redarea"
|
||||
Advanced="Avansat"
|
||||
AudioBufferSize="Dimensiune tampon/buffer audio (cadre)"
|
||||
VideoBufferSize="Dimensiune tampon/buffer video (cadre)"
|
||||
FrameDropping="Nivelul de pierdere a frame-urilor"
|
||||
DiscardNone="Nici unul"
|
||||
DiscardDefault="Implicit (pachetele invalide)"
|
||||
DiscardNonRef="Frame-urile fara referinte"
|
||||
DiscardBiDir="Frame-urile Bi-directionale"
|
||||
DiscardNonIntra="Frame-urile Non-Intra"
|
||||
DiscardNonKey="Frame-urile Non-Key"
|
||||
DiscardAll="Toate frame-urile (Atentie!)"
|
||||
RestartWhenActivated="Reporniţi redarea când sursa devine activă"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Toate fişierele Media"
|
||||
MediaFileFilter.VideoFiles="Fişiere video"
|
||||
MediaFileFilter.AudioFiles="Fişiere audio"
|
||||
MediaFileFilter.AllFiles="Toate fişierele"
|
||||
|
||||
29
plugins/obs-ffmpeg/data/locale/ru-RU.ini
Normal file
29
plugins/obs-ffmpeg/data/locale/ru-RU.ini
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
FFmpegOutput="Вывод FFmpeg"
|
||||
FFmpegAAC="Стандартный AAC-кодер FFmpeg"
|
||||
Bitrate="Битрейт"
|
||||
|
||||
FFmpegSource="Источник медиа"
|
||||
LocalFile="Локальный файл"
|
||||
Looping="Повтор"
|
||||
Input="Ввод"
|
||||
InputFormat="Формат ввода"
|
||||
ForceFormat="Принудительно конвертировать формат"
|
||||
HardwareDecode="Использовать аппаратное декодирование при наличии"
|
||||
ClearOnMediaEnd="Скрывать источник, когда воспроизведение заканчивается"
|
||||
Advanced="Дополнительно"
|
||||
AudioBufferSize="Размер аудиобуфера (в кадрах)"
|
||||
VideoBufferSize="Размер видеобуфера (в кадрах)"
|
||||
FrameDropping="Уровень пропуска кадров"
|
||||
DiscardNone="Нет"
|
||||
DiscardDefault="По умолчанию (неисправные пакеты)"
|
||||
DiscardNonRef="Неопорные кадры"
|
||||
DiscardBiDir="Двунаправленные кадры"
|
||||
DiscardNonIntra="Невнутренние кадры"
|
||||
DiscardNonKey="Неключевые кадры"
|
||||
DiscardAll="Все кадры (осторожно!)"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Все медиа-файлы"
|
||||
MediaFileFilter.VideoFiles="Видеофайлы"
|
||||
MediaFileFilter.AudioFiles="Аудиофайлы"
|
||||
MediaFileFilter.AllFiles="Все файлы"
|
||||
|
||||
9
plugins/obs-ffmpeg/data/locale/sk-SK.ini
Normal file
9
plugins/obs-ffmpeg/data/locale/sk-SK.ini
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
FFmpegOutput="Výstup FFmpeg"
|
||||
FFmpegAAC="Predvolený FFmpeg AAC enkodér"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
Looping="Slučka"
|
||||
Advanced="Rozšírené"
|
||||
DiscardNone="Žiadny"
|
||||
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/sl-SI.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/sl-SI.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="FFmpeg izhod"
|
||||
FFmpegAAC="FFmpeg Prevzeti AAC Encoder"
|
||||
Bitrate="Bitrate"
|
||||
|
||||
FFmpegSource="Medijski Vir"
|
||||
LocalFile="Lokalna Datoteka"
|
||||
Looping="Ponavljaj"
|
||||
Input="Vhod"
|
||||
InputFormat="Format vnosa"
|
||||
ForceFormat="Prisili spremembo formata"
|
||||
HardwareDecode="Uporabi strojno pospeševanje, ko je na voljo"
|
||||
ClearOnMediaEnd="Skri vir, ko se predvajanje konča"
|
||||
Advanced="Napredno"
|
||||
AudioBufferSize="Velikost Zvočnega medpomnilnika (frames)"
|
||||
VideoBufferSize="Velikost video medpomnolnika (frames)"
|
||||
FrameDropping="Raven izpuščanja framov"
|
||||
DiscardNone="Nobeno"
|
||||
DiscardDefault="Prevzeto (Neveljavni Paketi)"
|
||||
DiscardNonRef="Nereferenčne Frame"
|
||||
DiscardBiDir="Dvosmerne Frame"
|
||||
DiscardNonIntra="Non-Intra Frames"
|
||||
DiscardNonKey="Non-Key Frames"
|
||||
DiscardAll="Vse Frame (Pazite!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/sr-CS.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/sr-CS.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg izlaz"
|
||||
FFmpegAAC="FFmpeg podrazumevani AAC enkoder"
|
||||
Bitrate="Protok"
|
||||
|
||||
FFmpegSource="Medija izvor"
|
||||
LocalFile="Lokalna datoteka"
|
||||
Looping="Ponavljanje"
|
||||
Input="Ulaz"
|
||||
InputFormat="Format ulaza"
|
||||
ForceFormat="Prisilno pretvaranje formata"
|
||||
HardwareDecode="Koristi hardversko enkodiranje kada je dostupno"
|
||||
ClearOnMediaEnd="Sakrij izvor kada se reprodukcija završi"
|
||||
Advanced="Napredno"
|
||||
AudioBufferSize="Veličina zvučnog bafera (u frejmovima)"
|
||||
VideoBufferSize="Veličina video bafera (u frejmovima)"
|
||||
FrameDropping="Nivo ispuštanja frejmova"
|
||||
DiscardNone="Nijedan"
|
||||
DiscardDefault="Podrazumevano (neispravni paketi)"
|
||||
DiscardNonRef="Frejmovi bez reference"
|
||||
DiscardBiDir="Dvosmerni frejmovi"
|
||||
DiscardNonIntra="Ne-intra frejmovi"
|
||||
DiscardNonKey="Frejmovi koji nisu ključni"
|
||||
DiscardAll="Svi frejmovi (oprezno!)"
|
||||
RestartWhenActivated="Ponovi reprodukciju kada izvor postane aktivan"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Sve medija datoteke"
|
||||
MediaFileFilter.VideoFiles="Video datoteke"
|
||||
MediaFileFilter.AudioFiles="Zvučne datoteke"
|
||||
MediaFileFilter.AllFiles="Sve datoteke"
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/sr-SP.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/sr-SP.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg излаз"
|
||||
FFmpegAAC="FFmpeg подразумевани AAC енкодер"
|
||||
Bitrate="Проток"
|
||||
|
||||
FFmpegSource="Медија извор"
|
||||
LocalFile="Локална датотека"
|
||||
Looping="Понављање"
|
||||
Input="Улаз"
|
||||
InputFormat="Формат улаза"
|
||||
ForceFormat="Присилно претварање формата"
|
||||
HardwareDecode="Користи хардверско енкодирање када је доступно"
|
||||
ClearOnMediaEnd="Сакриј извор када се репродукција заврши"
|
||||
Advanced="Напредно"
|
||||
AudioBufferSize="Величина звучног бафера (у фрејмовима)"
|
||||
VideoBufferSize="Величина видео бафера (у фрејмовима)"
|
||||
FrameDropping="Ниво испуштања фрејмова"
|
||||
DiscardNone="Ниједан"
|
||||
DiscardDefault="Подразумевано (неисправни фрејмови)"
|
||||
DiscardNonRef="Фрејмови без референце"
|
||||
DiscardBiDir="Двосмерни фрејмови"
|
||||
DiscardNonIntra="Не-интра фрејмови"
|
||||
DiscardNonKey="Фрејмови који нису кључни"
|
||||
DiscardAll="Сви фрејмови (опрезно!)"
|
||||
RestartWhenActivated="Понови репродукцију када извор постане активан"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="Све медија датотеке"
|
||||
MediaFileFilter.VideoFiles="Видео датотеке"
|
||||
MediaFileFilter.AudioFiles="Звучне датотеке"
|
||||
MediaFileFilter.AllFiles="Све датотеке"
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/sv-SE.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/sv-SE.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="FFmpeg-utmatning"
|
||||
FFmpegAAC="AAC-kodare (FFmpeg standard)"
|
||||
Bitrate="Bithastighet"
|
||||
|
||||
FFmpegSource="Mediakälla"
|
||||
LocalFile="Lokal fil"
|
||||
Looping="Upprepa"
|
||||
Input="Infoga"
|
||||
InputFormat="Inmatningsformat"
|
||||
ForceFormat="Tvinga formatkonvertering"
|
||||
HardwareDecode="Använda hårdvareavkodning när tillgängligt"
|
||||
ClearOnMediaEnd="Dölja källa när uppspelningen slutar"
|
||||
Advanced="Avancerat"
|
||||
AudioBufferSize="Ljudbuffertstorlek (bilder)"
|
||||
VideoBufferSize="Videobuffertstorlek (bilder)"
|
||||
FrameDropping="Bild droppnivå"
|
||||
DiscardNone="Ingen"
|
||||
DiscardDefault="Standard (ogiltig paket)"
|
||||
DiscardNonRef="Icke-referensbildrutor"
|
||||
DiscardBiDir="Dubbelriktade bilder"
|
||||
DiscardNonIntra="Icke-Intra bilder"
|
||||
DiscardNonKey="Icke-nyckel bild"
|
||||
DiscardAll="Alla bilder (Varsam!)"
|
||||
|
||||
|
||||
4
plugins/obs-ffmpeg/data/locale/th-TH.ini
Normal file
4
plugins/obs-ffmpeg/data/locale/th-TH.ini
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Bitrate="บิตเรท"
|
||||
|
||||
|
||||
|
||||
25
plugins/obs-ffmpeg/data/locale/tr-TR.ini
Normal file
25
plugins/obs-ffmpeg/data/locale/tr-TR.ini
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FFmpegOutput="FFmpeg Çıkışı"
|
||||
FFmpegAAC="FFmpeg Varsayılan AAC Kodlayıcı"
|
||||
Bitrate="Bit hızı"
|
||||
|
||||
FFmpegSource="Ortam Kaynağı"
|
||||
LocalFile="Yerel Dosya"
|
||||
Looping="Döngü"
|
||||
Input="Giriş"
|
||||
InputFormat="Giriş Biçimi"
|
||||
ForceFormat="Dosya biçim dönüşümünü zorla"
|
||||
HardwareDecode="Kullanılabilir ise, donanım kod çözmeyi kullan"
|
||||
ClearOnMediaEnd="Kayıttan yürütme bittiğinde kaynağı gizle"
|
||||
Advanced="Gelişmiş"
|
||||
AudioBufferSize="Ses Arabellek Boyutu (kare)"
|
||||
VideoBufferSize="Video Arabellek Boyutu (kare)"
|
||||
FrameDropping="Kare Düşüş Seviyesi"
|
||||
DiscardNone="Hiçbiri"
|
||||
DiscardDefault="Varsayılan (Geçersiz Paketler)"
|
||||
DiscardNonRef="Referanssız Kareler"
|
||||
DiscardBiDir="Çift-Yönlü Kareler"
|
||||
DiscardNonIntra="Intra Olmayan Kareler"
|
||||
DiscardNonKey="Anahtar Olmayan Kareler"
|
||||
DiscardAll="Tüm Kareler (Dikkatli Olun!)"
|
||||
|
||||
|
||||
30
plugins/obs-ffmpeg/data/locale/zh-CN.ini
Normal file
30
plugins/obs-ffmpeg/data/locale/zh-CN.ini
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FFmpegOutput="FFmpeg 输出"
|
||||
FFmpegAAC="FFmpeg 默认 AAC 编码器"
|
||||
Bitrate="比特率"
|
||||
|
||||
FFmpegSource="媒体源"
|
||||
LocalFile="本地文件"
|
||||
Looping="循环"
|
||||
Input="输入"
|
||||
InputFormat="输入格式"
|
||||
ForceFormat="强制格式转换"
|
||||
HardwareDecode="在可用时使用硬件解码"
|
||||
ClearOnMediaEnd="当播放结束时隐藏源"
|
||||
Advanced="高级"
|
||||
AudioBufferSize="音频缓冲区大小(帧)"
|
||||
VideoBufferSize="视频缓冲区大小(帧)"
|
||||
FrameDropping="帧丢失等级"
|
||||
DiscardNone="无"
|
||||
DiscardDefault="默认(无效数据包)"
|
||||
DiscardNonRef="非参考帧"
|
||||
DiscardBiDir="双向帧"
|
||||
DiscardNonIntra="非intra帧"
|
||||
DiscardNonKey="非关键帧"
|
||||
DiscardAll="所有的帧(小心!)"
|
||||
RestartWhenActivated="当源变为活动状态时重新启动播放"
|
||||
|
||||
MediaFileFilter.AllMediaFiles="所有媒体文件"
|
||||
MediaFileFilter.VideoFiles="视频文件"
|
||||
MediaFileFilter.AudioFiles="音频文件"
|
||||
MediaFileFilter.AllFiles="所有文件"
|
||||
|
||||
19
plugins/obs-ffmpeg/data/locale/zh-TW.ini
Normal file
19
plugins/obs-ffmpeg/data/locale/zh-TW.ini
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
FFmpegOutput="FFmpeg 輸出"
|
||||
FFmpegAAC="FFmpeg 預設 AAC 編碼器"
|
||||
Bitrate="位元率"
|
||||
|
||||
FFmpegSource="媒體來源"
|
||||
LocalFile="本機檔案"
|
||||
Input="輸入"
|
||||
InputFormat="輸入格式"
|
||||
ForceFormat="強制格式轉換"
|
||||
HardwareDecode="盡可能使用硬體解碼"
|
||||
ClearOnMediaEnd="當播放結束時隱藏來源"
|
||||
Advanced="進階"
|
||||
AudioBufferSize="音訊緩衝區大小 (幀)"
|
||||
VideoBufferSize="影像緩衝區大小 (幀)"
|
||||
FrameDropping="掉幀程度"
|
||||
DiscardNone="無"
|
||||
DiscardDefault="預設 (無效封包)"
|
||||
|
||||
|
||||
26
plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt
Normal file
26
plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
project(ffmpeg-mux)
|
||||
|
||||
find_package(FFmpeg REQUIRED
|
||||
COMPONENTS avcodec avutil avformat)
|
||||
include_directories(${FFMPEG_INCLUDE_DIRS})
|
||||
|
||||
set(ffmpeg-mux_SOURCES
|
||||
ffmpeg-mux.c)
|
||||
|
||||
set(ffmpeg-mux_HEADERS
|
||||
ffmpeg-mux.h)
|
||||
|
||||
add_executable(ffmpeg-mux
|
||||
${ffmpeg-mux_SOURCES}
|
||||
${ffmpeg-mux_HEADERS})
|
||||
|
||||
target_link_libraries(ffmpeg-mux
|
||||
${FFMPEG_LIBRARIES})
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(ffmpeg-mux
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "ffmpeg-mux${_output_suffix}")
|
||||
endif()
|
||||
|
||||
install_obs_datatarget(ffmpeg-mux "obs-plugins/obs-ffmpeg")
|
||||
664
plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c
Normal file
664
plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c
Normal file
|
|
@ -0,0 +1,664 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Hugh Bailey <obs.jim@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#define inline __inline
|
||||
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ffmpeg-mux.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
struct resize_buf {
|
||||
uint8_t *buf;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
static inline void resize_buf_resize(struct resize_buf *rb, size_t size)
|
||||
{
|
||||
if (!rb->buf) {
|
||||
rb->buf = malloc(size);
|
||||
rb->size = size;
|
||||
rb->capacity = size;
|
||||
} else {
|
||||
if (rb->capacity < size) {
|
||||
size_t capx2 = rb->capacity * 2;
|
||||
size_t new_cap = capx2 > size ? capx2 : size;
|
||||
rb->buf = realloc(rb->buf, new_cap);
|
||||
rb->capacity = new_cap;
|
||||
}
|
||||
|
||||
rb->size = size;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void resize_buf_free(struct resize_buf *rb)
|
||||
{
|
||||
free(rb->buf);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
struct main_params {
|
||||
char *file;
|
||||
int has_video;
|
||||
int tracks;
|
||||
char *vcodec;
|
||||
int vbitrate;
|
||||
int gop;
|
||||
int width;
|
||||
int height;
|
||||
int fps_num;
|
||||
int fps_den;
|
||||
char *acodec;
|
||||
char *muxer_settings;
|
||||
};
|
||||
|
||||
struct audio_params {
|
||||
char *name;
|
||||
int abitrate;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
};
|
||||
|
||||
struct header {
|
||||
uint8_t *data;
|
||||
int size;
|
||||
};
|
||||
|
||||
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;
|
||||
char error[4096];
|
||||
};
|
||||
|
||||
static void header_free(struct header *header)
|
||||
{
|
||||
free(header->data);
|
||||
}
|
||||
|
||||
static void free_avformat(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
if (ffm->output) {
|
||||
if ((ffm->output->oformat->flags & AVFMT_NOFILE) == 0)
|
||||
avio_close(ffm->output->pb);
|
||||
|
||||
avformat_free_context(ffm->output);
|
||||
ffm->output = NULL;
|
||||
}
|
||||
|
||||
if (ffm->audio_streams) {
|
||||
free(ffm->audio_streams);
|
||||
}
|
||||
|
||||
ffm->video_stream = NULL;
|
||||
ffm->audio_streams = NULL;
|
||||
ffm->num_audio_streams = 0;
|
||||
}
|
||||
|
||||
static void ffmpeg_mux_free(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
if (ffm->initialized) {
|
||||
av_write_trailer(ffm->output);
|
||||
}
|
||||
|
||||
free_avformat(ffm);
|
||||
|
||||
header_free(&ffm->video_header);
|
||||
|
||||
if (ffm->audio_header) {
|
||||
for (int i = 0; i < ffm->params.tracks; i++) {
|
||||
header_free(&ffm->audio_header[i]);
|
||||
}
|
||||
|
||||
free(ffm->audio_header);
|
||||
}
|
||||
|
||||
if (ffm->audio) {
|
||||
free(ffm->audio);
|
||||
}
|
||||
|
||||
memset(ffm, 0, sizeof(*ffm));
|
||||
}
|
||||
|
||||
static bool get_opt_str(int *p_argc, char ***p_argv, char **str,
|
||||
const char *opt)
|
||||
{
|
||||
int argc = *p_argc;
|
||||
char **argv = *p_argv;
|
||||
|
||||
if (!argc) {
|
||||
printf("Missing expected option: '%s'\n", opt);
|
||||
return false;
|
||||
}
|
||||
|
||||
(*p_argc)--;
|
||||
(*p_argv)++;
|
||||
*str = argv[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_opt_int(int *p_argc, char ***p_argv, int *i, const char *opt)
|
||||
{
|
||||
char *str;
|
||||
|
||||
if (!get_opt_str(p_argc, p_argv, &str, opt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*i = atoi(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_audio_params(struct audio_params *audio, int *argc,
|
||||
char ***argv)
|
||||
{
|
||||
if (!get_opt_str(argc, argv, &audio->name, "audio track name"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, &audio->abitrate, "audio bitrate"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, &audio->sample_rate, "audio sample rate"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, &audio->channels, "audio channels"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_params(int *argc, char ***argv, struct main_params *params,
|
||||
struct audio_params **p_audio)
|
||||
{
|
||||
struct audio_params *audio = NULL;
|
||||
|
||||
if (!get_opt_str(argc, argv, ¶ms->file, "file name"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->has_video, "video track count"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->tracks, "audio track count"))
|
||||
return false;
|
||||
|
||||
if (params->has_video > 1 || params->has_video < 0) {
|
||||
puts("Invalid number of video tracks\n");
|
||||
return false;
|
||||
}
|
||||
if (params->tracks < 0) {
|
||||
puts("Invalid number of audio tracks\n");
|
||||
return false;
|
||||
}
|
||||
if (params->has_video == 0 && params->tracks == 0) {
|
||||
puts("Must have at least 1 audio track or 1 video track\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params->has_video) {
|
||||
if (!get_opt_str(argc, argv, ¶ms->vcodec, "video codec"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->vbitrate,"video bitrate"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->width, "video width"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->height, "video height"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->fps_num, "video fps num"))
|
||||
return false;
|
||||
if (!get_opt_int(argc, argv, ¶ms->fps_den, "video fps den"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params->tracks) {
|
||||
if (!get_opt_str(argc, argv, ¶ms->acodec, "audio codec"))
|
||||
return false;
|
||||
|
||||
audio = calloc(1, sizeof(*audio) * params->tracks);
|
||||
|
||||
for (int i = 0; i < params->tracks; i++) {
|
||||
if (!get_audio_params(&audio[i], argc, argv)) {
|
||||
free(audio);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*p_audio = audio;
|
||||
|
||||
get_opt_str(argc, argv, ¶ms->muxer_settings, "muxer settings");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool new_stream(struct ffmpeg_mux *ffm, AVStream **stream,
|
||||
const char *name, enum AVCodecID *id)
|
||||
{
|
||||
const AVCodecDescriptor *desc = avcodec_descriptor_get_by_name(name);
|
||||
AVCodec *codec;
|
||||
|
||||
if (!desc) {
|
||||
printf("Couldn't find encoder '%s'\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
*id = desc->id;
|
||||
|
||||
codec = avcodec_find_encoder(desc->id);
|
||||
if (!codec) {
|
||||
printf("Couldn't create encoder");
|
||||
return false;
|
||||
}
|
||||
|
||||
*stream = avformat_new_stream(ffm->output, codec);
|
||||
if (!*stream) {
|
||||
printf("Couldn't create stream for encoder '%s'\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
(*stream)->id = ffm->output->nb_streams-1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void create_video_stream(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
AVCodecContext *context;
|
||||
void *extradata = NULL;
|
||||
|
||||
if (!new_stream(ffm, &ffm->video_stream, ffm->params.vcodec,
|
||||
&ffm->output->oformat->video_codec))
|
||||
return;
|
||||
|
||||
if (ffm->video_header.size) {
|
||||
extradata = av_memdup(ffm->video_header.data,
|
||||
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->extradata_size = ffm->video_header.size;
|
||||
context->time_base =
|
||||
(AVRational){ffm->params.fps_den, ffm->params.fps_num};
|
||||
|
||||
ffm->video_stream->time_base = context->time_base;
|
||||
|
||||
if (ffm->output->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
context->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
static void create_audio_stream(struct ffmpeg_mux *ffm, int idx)
|
||||
{
|
||||
AVCodecContext *context;
|
||||
AVStream *stream;
|
||||
void *extradata = NULL;
|
||||
|
||||
if (!new_stream(ffm, &stream, ffm->params.acodec,
|
||||
&ffm->output->oformat->audio_codec))
|
||||
return;
|
||||
|
||||
ffm->audio_streams[idx] = stream;
|
||||
|
||||
av_dict_set(&stream->metadata, "title", ffm->audio[idx].name, 0);
|
||||
|
||||
stream->time_base = (AVRational){1, ffm->audio[idx].sample_rate};
|
||||
|
||||
if (ffm->audio_header[idx].size) {
|
||||
extradata = av_memdup(ffm->audio_header[idx].data,
|
||||
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->extradata_size = ffm->audio_header[idx].size;
|
||||
context->channel_layout =
|
||||
av_get_default_channel_layout(context->channels);
|
||||
|
||||
if (ffm->output->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
context->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
ffm->num_audio_streams++;
|
||||
}
|
||||
|
||||
static bool init_streams(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
create_video_stream(ffm);
|
||||
|
||||
if (ffm->params.tracks) {
|
||||
ffm->audio_streams =
|
||||
calloc(1, ffm->params.tracks * sizeof(void*));
|
||||
|
||||
for (int i = 0; i < ffm->params.tracks; i++)
|
||||
create_audio_stream(ffm, i);
|
||||
}
|
||||
|
||||
if (!ffm->video_stream && !ffm->num_audio_streams)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_header(struct header *header, uint8_t *data, size_t size)
|
||||
{
|
||||
header->size = (int)size;
|
||||
header->data = malloc(size);
|
||||
memcpy(header->data, data, size);
|
||||
}
|
||||
|
||||
static void ffmpeg_mux_header(struct ffmpeg_mux *ffm, uint8_t *data,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t safe_read(void *vdata, size_t size)
|
||||
{
|
||||
uint8_t *data = vdata;
|
||||
size_t total = size;
|
||||
|
||||
while (size > 0) {
|
||||
size_t in_size = fread(data, 1, size, stdin);
|
||||
if (in_size == 0)
|
||||
return 0;
|
||||
|
||||
size -= in_size;
|
||||
data += in_size;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static bool ffmpeg_mux_get_header(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
struct ffm_packet_info info = {0};
|
||||
|
||||
bool success = safe_read(&info, sizeof(info)) == sizeof(info);
|
||||
if (success) {
|
||||
uint8_t *data = malloc(info.size);
|
||||
|
||||
if (safe_read(data, info.size) == info.size) {
|
||||
ffmpeg_mux_header(ffm, data, &info);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline bool ffmpeg_mux_get_extra_data(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
if (ffm->params.has_video) {
|
||||
if (!ffmpeg_mux_get_header(ffm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ffm->params.tracks; i++) {
|
||||
if (!ffmpeg_mux_get_header(ffm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
static inline int open_output_file(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
AVOutputFormat *format = ffm->output->oformat;
|
||||
int ret;
|
||||
|
||||
if ((format->flags & AVFMT_NOFILE) == 0) {
|
||||
ret = avio_open(&ffm->output->pb, ffm->params.file,
|
||||
AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
printf("Couldn't open '%s', %s",
|
||||
ffm->params.file, av_err2str(ret));
|
||||
return FFM_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(ffm->output->filename, ffm->params.file,
|
||||
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))) {
|
||||
printf("Failed to parse muxer settings: %s\n%s",
|
||||
av_err2str(ret), ffm->params.muxer_settings);
|
||||
|
||||
av_dict_free(&dict);
|
||||
}
|
||||
|
||||
if (av_dict_count(dict) > 0) {
|
||||
printf("Using muxer settings:");
|
||||
|
||||
AVDictionaryEntry *entry = NULL;
|
||||
while ((entry = av_dict_get(dict, "", entry,
|
||||
AV_DICT_IGNORE_SUFFIX)))
|
||||
printf("\n\t%s=%s", entry->key, entry->value);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
ret = avformat_write_header(ffm->output, &dict);
|
||||
if (ret < 0) {
|
||||
printf("Error opening '%s': %s",
|
||||
ffm->params.file, av_err2str(ret));
|
||||
|
||||
av_dict_free(&dict);
|
||||
|
||||
return ret == -22 ? FFM_UNSUPPORTED : FFM_ERROR;
|
||||
}
|
||||
|
||||
av_dict_free(&dict);
|
||||
|
||||
return FFM_SUCCESS;
|
||||
}
|
||||
|
||||
static int ffmpeg_mux_init_context(struct ffmpeg_mux *ffm)
|
||||
{
|
||||
AVOutputFormat *output_format;
|
||||
int ret;
|
||||
|
||||
output_format = av_guess_format(NULL, ffm->params.file, NULL);
|
||||
if (output_format == NULL) {
|
||||
printf("Couldn't find an appropriate muxer for '%s'\n",
|
||||
ffm->params.file);
|
||||
return FFM_ERROR;
|
||||
}
|
||||
|
||||
ret = avformat_alloc_output_context2(&ffm->output, output_format,
|
||||
NULL, NULL);
|
||||
if (ret < 0) {
|
||||
printf("Couldn't initialize output context: %s\n",
|
||||
av_err2str(ret));
|
||||
return FFM_ERROR;
|
||||
}
|
||||
|
||||
ffm->output->oformat->video_codec = AV_CODEC_ID_NONE;
|
||||
ffm->output->oformat->audio_codec = AV_CODEC_ID_NONE;
|
||||
|
||||
if (!init_streams(ffm)) {
|
||||
free_avformat(ffm);
|
||||
return FFM_ERROR;
|
||||
}
|
||||
|
||||
ret = open_output_file(ffm);
|
||||
if (ret != FFM_SUCCESS) {
|
||||
free_avformat(ffm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return FFM_SUCCESS;
|
||||
}
|
||||
|
||||
static int ffmpeg_mux_init_internal(struct ffmpeg_mux *ffm, int argc,
|
||||
char *argv[])
|
||||
{
|
||||
argc--;
|
||||
argv++;
|
||||
if (!init_params(&argc, &argv, &ffm->params, &ffm->audio))
|
||||
return FFM_ERROR;
|
||||
|
||||
if (ffm->params.tracks) {
|
||||
ffm->audio_header =
|
||||
calloc(1, sizeof(struct header) * ffm->params.tracks);
|
||||
}
|
||||
|
||||
av_register_all();
|
||||
|
||||
if (!ffmpeg_mux_get_extra_data(ffm))
|
||||
return FFM_ERROR;
|
||||
|
||||
/* ffmpeg does not have a way of telling what's supported
|
||||
* for a given output format, so we try each possibility */
|
||||
return ffmpeg_mux_init_context(ffm);
|
||||
}
|
||||
|
||||
static int ffmpeg_mux_init(struct ffmpeg_mux *ffm, int argc, char *argv[])
|
||||
{
|
||||
int ret = ffmpeg_mux_init_internal(ffm, argc, argv);
|
||||
if (ret != FFM_SUCCESS) {
|
||||
ffmpeg_mux_free(ffm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ffm->initialized = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int get_index(struct ffmpeg_mux *ffm,
|
||||
struct ffm_packet_info *info)
|
||||
{
|
||||
if (info->type == FFM_PACKET_VIDEO) {
|
||||
if (ffm->video_stream) {
|
||||
return ffm->video_stream->id;
|
||||
}
|
||||
} else {
|
||||
if ((int)info->index < ffm->num_audio_streams) {
|
||||
return ffm->audio_streams[info->index]->id;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline AVStream *get_stream(struct ffmpeg_mux *ffm, int idx)
|
||||
{
|
||||
return ffm->output->streams[idx];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static inline bool ffmpeg_mux_packet(struct ffmpeg_mux *ffm, uint8_t *buf,
|
||||
struct ffm_packet_info *info)
|
||||
{
|
||||
int idx = get_index(ffm, info);
|
||||
AVPacket packet = {0};
|
||||
|
||||
/* The muxer might not support video/audio, or multiple audio tracks */
|
||||
if (idx == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
av_init_packet(&packet);
|
||||
|
||||
packet.data = buf;
|
||||
packet.size = (int)info->size;
|
||||
packet.stream_index = idx;
|
||||
packet.pts = rescale_ts(ffm, info->pts, idx);
|
||||
packet.dts = rescale_ts(ffm, info->dts, idx);
|
||||
|
||||
if (info->keyframe)
|
||||
packet.flags = AV_PKT_FLAG_KEY;
|
||||
|
||||
return av_interleaved_write_frame(ffm->output, &packet) >= 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct ffm_packet_info info = {0};
|
||||
struct ffmpeg_mux ffm = {0};
|
||||
struct resize_buf rb = {0};
|
||||
bool fail = false;
|
||||
int ret;
|
||||
|
||||
#ifdef _WIN32
|
||||
_setmode(_fileno(stdin), O_BINARY);
|
||||
#endif
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
|
||||
ret = ffmpeg_mux_init(&ffm, argc, argv);
|
||||
if (ret != FFM_SUCCESS) {
|
||||
puts("Couldn't initialize muxer");
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (!fail && safe_read(&info, sizeof(info)) == sizeof(info)) {
|
||||
resize_buf_resize(&rb, info.size);
|
||||
|
||||
if (safe_read(rb.buf, info.size) == info.size) {
|
||||
ffmpeg_mux_packet(&ffm, rb.buf, &info);
|
||||
} else {
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
ffmpeg_mux_free(&ffm);
|
||||
resize_buf_free(&rb);
|
||||
return 0;
|
||||
}
|
||||
37
plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.h
Normal file
37
plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Hugh Bailey <obs.jim@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum ffm_packet_type {
|
||||
FFM_PACKET_VIDEO,
|
||||
FFM_PACKET_AUDIO
|
||||
};
|
||||
|
||||
#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;
|
||||
enum ffm_packet_type type;
|
||||
bool keyframe;
|
||||
};
|
||||
300
plugins/obs-ffmpeg/obs-ffmpeg-aac.c
Normal file
300
plugins/obs-ffmpeg/obs-ffmpeg-aac.c
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <util/base.h>
|
||||
#include <util/circlebuf.h>
|
||||
#include <util/darray.h>
|
||||
#include <obs-module.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "obs-ffmpeg-formats.h"
|
||||
#include "obs-ffmpeg-compat.h"
|
||||
|
||||
#define do_log(level, format, ...) \
|
||||
blog(level, "[FFmpeg aac encoder: '%s'] " format, \
|
||||
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__)
|
||||
|
||||
struct aac_encoder {
|
||||
obs_encoder_t *encoder;
|
||||
|
||||
AVCodec *aac;
|
||||
AVCodecContext *context;
|
||||
|
||||
uint8_t *samples[MAX_AV_PLANES];
|
||||
AVFrame *aframe;
|
||||
int64_t total_samples;
|
||||
|
||||
DARRAY(uint8_t) packet_buffer;
|
||||
|
||||
size_t audio_planes;
|
||||
size_t audio_size;
|
||||
|
||||
int frame_size; /* pretty much always 1024 for AAC */
|
||||
int frame_size_bytes;
|
||||
};
|
||||
|
||||
static const char *aac_getname(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
return obs_module_text("FFmpegAAC");
|
||||
}
|
||||
|
||||
static void aac_destroy(void *data)
|
||||
{
|
||||
struct aac_encoder *enc = data;
|
||||
|
||||
if (enc->samples[0])
|
||||
av_freep(&enc->samples[0]);
|
||||
if (enc->context)
|
||||
avcodec_close(enc->context);
|
||||
if (enc->aframe)
|
||||
av_frame_free(&enc->aframe);
|
||||
|
||||
da_free(enc->packet_buffer);
|
||||
bfree(enc);
|
||||
}
|
||||
|
||||
static bool initialize_codec(struct aac_encoder *enc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
enc->aframe = av_frame_alloc();
|
||||
if (!enc->aframe) {
|
||||
warn("Failed to allocate audio frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avcodec_open2(enc->context, enc->aac, NULL);
|
||||
if (ret < 0) {
|
||||
warn("Failed to open AAC codec: %s", av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
enc->frame_size = enc->context->frame_size;
|
||||
if (!enc->frame_size)
|
||||
enc->frame_size = 1024;
|
||||
|
||||
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);
|
||||
if (ret < 0) {
|
||||
warn("Failed to create audio buffer: %s", av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void init_sizes(struct aac_encoder *enc, audio_t *audio)
|
||||
{
|
||||
const struct audio_output_info *aoi;
|
||||
enum audio_format format;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder)
|
||||
{
|
||||
struct aac_encoder *enc;
|
||||
int bitrate = (int)obs_data_get_int(settings, "bitrate");
|
||||
audio_t *audio = obs_encoder_audio(encoder);
|
||||
|
||||
avcodec_register_all();
|
||||
|
||||
enc = bzalloc(sizeof(struct aac_encoder));
|
||||
enc->encoder = encoder;
|
||||
enc->aac = avcodec_find_encoder(AV_CODEC_ID_AAC);
|
||||
|
||||
blog(LOG_INFO, "---------------------------------");
|
||||
|
||||
if (!enc->aac) {
|
||||
warn("Couldn't find encoder");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!bitrate) {
|
||||
warn("Invalid bitrate specified");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enc->context = avcodec_alloc_context3(enc->aac);
|
||||
if (!enc->context) {
|
||||
warn("Failed to create codec context");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
enc->context->bit_rate = bitrate * 1000;
|
||||
enc->context->channels = (int)audio_output_get_channels(audio);
|
||||
enc->context->sample_rate = audio_output_get_sample_rate(audio);
|
||||
enc->context->sample_fmt = enc->aac->sample_fmts ?
|
||||
enc->aac->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
|
||||
|
||||
/* if using FFmpeg's AAC encoder, at least set a cutoff value
|
||||
* (recommended by konverter) */
|
||||
if (strcmp(enc->aac->name, "aac") == 0) {
|
||||
int cutoff1 = 4000 + (int)enc->context->bit_rate / 8;
|
||||
int cutoff2 = 12000 + (int)enc->context->bit_rate / 8;
|
||||
int cutoff3 = enc->context->sample_rate / 2;
|
||||
int cutoff;
|
||||
|
||||
cutoff = MIN(cutoff1, cutoff2);
|
||||
cutoff = MIN(cutoff, cutoff3);
|
||||
enc->context->cutoff = cutoff;
|
||||
}
|
||||
|
||||
info("bitrate: %d, channels: %d",
|
||||
enc->context->bit_rate / 1000, enc->context->channels);
|
||||
|
||||
init_sizes(enc, audio);
|
||||
|
||||
/* enable experimental FFmpeg encoder if the only one available */
|
||||
enc->context->strict_std_compliance = -2;
|
||||
|
||||
enc->context->flags = CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
if (initialize_codec(enc))
|
||||
return enc;
|
||||
|
||||
fail:
|
||||
aac_destroy(enc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool do_aac_encode(struct aac_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;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
enc->total_samples += enc->frame_size;
|
||||
|
||||
ret = avcodec_encode_audio2(enc->context, &avpacket, enc->aframe,
|
||||
&got_packet);
|
||||
if (ret < 0) {
|
||||
warn("avcodec_encode_audio2 failed: %s", av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
*received_packet = !!got_packet;
|
||||
if (!got_packet)
|
||||
return true;
|
||||
|
||||
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->data = enc->packet_buffer.array;
|
||||
packet->size = avpacket.size;
|
||||
packet->type = OBS_ENCODER_AUDIO;
|
||||
packet->timebase_num = 1;
|
||||
packet->timebase_den = (int32_t)enc->context->sample_rate;
|
||||
av_free_packet(&avpacket);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool aac_encode(void *data, struct encoder_frame *frame,
|
||||
struct encoder_packet *packet, bool *received_packet)
|
||||
{
|
||||
struct aac_encoder *enc = data;
|
||||
|
||||
for (size_t i = 0; i < enc->audio_planes; i++)
|
||||
memcpy(enc->samples[i], frame->data[i], enc->frame_size_bytes);
|
||||
|
||||
return do_aac_encode(enc, packet, received_packet);
|
||||
}
|
||||
|
||||
static void aac_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_int(settings, "bitrate", 128);
|
||||
}
|
||||
|
||||
static obs_properties_t *aac_properties(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
obs_properties_add_int(props, "bitrate",
|
||||
obs_module_text("Bitrate"), 32, 320, 32);
|
||||
return props;
|
||||
}
|
||||
|
||||
static bool aac_extra_data(void *data, uint8_t **extra_data, size_t *size)
|
||||
{
|
||||
struct aac_encoder *enc = data;
|
||||
|
||||
*extra_data = enc->context->extradata;
|
||||
*size = enc->context->extradata_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void aac_audio_info(void *data, struct audio_convert_info *info)
|
||||
{
|
||||
struct aac_encoder *enc = data;
|
||||
info->format = convert_ffmpeg_sample_format(enc->context->sample_fmt);
|
||||
}
|
||||
|
||||
static size_t aac_frame_size(void *data)
|
||||
{
|
||||
struct aac_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 = aac_destroy,
|
||||
.encode = aac_encode,
|
||||
.get_frame_size = aac_frame_size,
|
||||
.get_defaults = aac_defaults,
|
||||
.get_properties = aac_properties,
|
||||
.get_extra_data = aac_extra_data,
|
||||
.get_audio_info = aac_audio_info
|
||||
};
|
||||
22
plugins/obs-ffmpeg/obs-ffmpeg-compat.h
Normal file
22
plugins/obs-ffmpeg/obs-ffmpeg-compat.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
/* LIBAVCODEC_VERSION_CHECK checks for the right version of libav and FFmpeg
|
||||
* 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 ) ) )
|
||||
|
||||
#if !LIBAVCODEC_VERSION_CHECK(54, 28, 0, 59, 100)
|
||||
# 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
|
||||
#endif
|
||||
|
||||
61
plugins/obs-ffmpeg/obs-ffmpeg-formats.h
Normal file
61
plugins/obs-ffmpeg/obs-ffmpeg-formats.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
static inline int64_t rescale_ts(int64_t val, AVCodecContext *context,
|
||||
AVRational new_base)
|
||||
{
|
||||
return av_rescale_q_rnd(val, context->time_base, new_base,
|
||||
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
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_NONE:
|
||||
default: return VIDEO_FORMAT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* shouldn't get here */
|
||||
return AUDIO_FORMAT_16BIT;
|
||||
}
|
||||
398
plugins/obs-ffmpeg/obs-ffmpeg-mux.c
Normal file
398
plugins/obs-ffmpeg/obs-ffmpeg-mux.c
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2015 by Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-avc.h>
|
||||
#include <util/dstr.h>
|
||||
#include <util/pipe.h>
|
||||
#include "ffmpeg-mux/ffmpeg-mux.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#define do_log(level, format, ...) \
|
||||
blog(level, "[ffmpeg muxer: '%s'] " format, \
|
||||
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__)
|
||||
|
||||
struct ffmpeg_muxer {
|
||||
obs_output_t *output;
|
||||
os_process_pipe_t *pipe;
|
||||
struct dstr path;
|
||||
bool sent_headers;
|
||||
bool active;
|
||||
bool capturing;
|
||||
};
|
||||
|
||||
static const char *ffmpeg_mux_getname(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
return obs_module_text("FFmpegMuxer");
|
||||
}
|
||||
|
||||
static void ffmpeg_mux_destroy(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
os_process_pipe_destroy(stream->pipe);
|
||||
dstr_free(&stream->path);
|
||||
bfree(stream);
|
||||
}
|
||||
|
||||
static void *ffmpeg_mux_create(obs_data_t *settings, obs_output_t *output)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = bzalloc(sizeof(*stream));
|
||||
stream->output = output;
|
||||
|
||||
UNUSED_PARAMETER(settings);
|
||||
return stream;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef _WIN64
|
||||
#define FFMPEG_MUX "ffmpeg-mux64.exe"
|
||||
#else
|
||||
#define FFMPEG_MUX "ffmpeg-mux32.exe"
|
||||
#endif
|
||||
#else
|
||||
#define FFMPEG_MUX "ffmpeg-mux"
|
||||
#endif
|
||||
|
||||
/* 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)
|
||||
{
|
||||
obs_data_t *settings = obs_encoder_get_settings(vencoder);
|
||||
int bitrate = (int)obs_data_get_int(settings, "bitrate");
|
||||
video_t *video = obs_get_video();
|
||||
const struct video_output_info *info = video_output_get_info(video);
|
||||
|
||||
obs_data_release(settings);
|
||||
|
||||
dstr_catf(cmd, "%s %d %d %d %d %d ",
|
||||
"h264",
|
||||
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)
|
||||
{
|
||||
obs_data_t *settings = obs_encoder_get_settings(aencoder);
|
||||
int bitrate = (int)obs_data_get_int(settings, "bitrate");
|
||||
audio_t *audio = obs_get_audio();
|
||||
struct dstr name = {0};
|
||||
|
||||
obs_data_release(settings);
|
||||
|
||||
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_free(&name);
|
||||
}
|
||||
|
||||
static void log_muxer_params(struct ffmpeg_muxer *stream, const char *settings)
|
||||
{
|
||||
int ret;
|
||||
|
||||
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);
|
||||
|
||||
av_dict_free(&dict);
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_dict_count(dict) > 0) {
|
||||
struct dstr str = {0};
|
||||
|
||||
AVDictionaryEntry *entry = NULL;
|
||||
while ((entry = av_dict_get(dict, "", entry,
|
||||
AV_DICT_IGNORE_SUFFIX)))
|
||||
dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
|
||||
|
||||
info("Using muxer settings:%s", str.array);
|
||||
dstr_free(&str);
|
||||
}
|
||||
|
||||
av_dict_free(&dict);
|
||||
}
|
||||
|
||||
static void add_muxer_params(struct dstr *cmd, struct ffmpeg_muxer *stream)
|
||||
{
|
||||
obs_data_t *settings = obs_output_get_settings(stream->output);
|
||||
struct dstr mux = {0};
|
||||
|
||||
dstr_copy(&mux, obs_data_get_string(settings, "muxer_settings"));
|
||||
|
||||
log_muxer_params(stream, mux.array);
|
||||
|
||||
dstr_replace(&mux, "\"", "\\\"");
|
||||
obs_data_release(settings);
|
||||
|
||||
dstr_catf(cmd, "\"%s\" ", mux.array ? mux.array : "");
|
||||
|
||||
dstr_free(&mux);
|
||||
}
|
||||
|
||||
static void build_command_line(struct ffmpeg_muxer *stream, struct dstr *cmd)
|
||||
{
|
||||
obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output);
|
||||
obs_encoder_t *aencoders[MAX_AUDIO_MIXES];
|
||||
int num_tracks = 0;
|
||||
|
||||
for (;;) {
|
||||
obs_encoder_t *aencoder = obs_output_get_audio_encoder(
|
||||
stream->output, num_tracks);
|
||||
if (!aencoder)
|
||||
break;
|
||||
|
||||
aencoders[num_tracks] = aencoder;
|
||||
num_tracks++;
|
||||
}
|
||||
|
||||
dstr_init_move_array(cmd, obs_module_file(FFMPEG_MUX));
|
||||
dstr_insert_ch(cmd, 0, '\"');
|
||||
dstr_cat(cmd, "\" \"");
|
||||
dstr_cat_dstr(cmd, &stream->path);
|
||||
dstr_catf(cmd, "\" %d %d ", vencoder ? 1 : 0, num_tracks);
|
||||
|
||||
if (vencoder)
|
||||
add_video_encoder_params(stream, cmd, vencoder);
|
||||
|
||||
if (num_tracks) {
|
||||
dstr_cat(cmd, "aac ");
|
||||
|
||||
for (int i = 0; i < num_tracks; i++) {
|
||||
add_audio_encoder_params(cmd, aencoders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
add_muxer_params(cmd, stream);
|
||||
}
|
||||
|
||||
static bool ffmpeg_mux_start(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
obs_data_t *settings;
|
||||
struct dstr cmd;
|
||||
const char *path;
|
||||
|
||||
if (!obs_output_can_begin_data_capture(stream->output, 0))
|
||||
return false;
|
||||
if (!obs_output_initialize_encoders(stream->output, 0))
|
||||
return false;
|
||||
|
||||
settings = obs_output_get_settings(stream->output);
|
||||
path = obs_data_get_string(settings, "path");
|
||||
dstr_copy(&stream->path, path);
|
||||
dstr_replace(&stream->path, "\"", "\"\"");
|
||||
obs_data_release(settings);
|
||||
|
||||
build_command_line(stream, &cmd);
|
||||
stream->pipe = os_process_pipe_create(cmd.array, "w");
|
||||
dstr_free(&cmd);
|
||||
|
||||
if (!stream->pipe) {
|
||||
warn("Failed to create process pipe");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* write headers and start capture */
|
||||
stream->active = true;
|
||||
stream->capturing = true;
|
||||
obs_output_begin_data_capture(stream->output, 0);
|
||||
|
||||
info("Writing file '%s'...", stream->path.array);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int deactivate(struct ffmpeg_muxer *stream)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (stream->active) {
|
||||
ret = os_process_pipe_destroy(stream->pipe);
|
||||
stream->pipe = NULL;
|
||||
|
||||
stream->active = false;
|
||||
stream->sent_headers = false;
|
||||
|
||||
info("Output of file '%s' stopped", stream->path.array);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ffmpeg_mux_stop(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
|
||||
if (stream->capturing) {
|
||||
obs_output_end_data_capture(stream->output);
|
||||
stream->capturing = false;
|
||||
}
|
||||
|
||||
deactivate(stream);
|
||||
}
|
||||
|
||||
static void signal_failure(struct ffmpeg_muxer *stream)
|
||||
{
|
||||
int ret = deactivate(stream);
|
||||
int code;
|
||||
|
||||
switch (ret) {
|
||||
case FFM_UNSUPPORTED: code = OBS_OUTPUT_UNSUPPORTED; break;
|
||||
default: code = OBS_OUTPUT_ERROR;
|
||||
}
|
||||
|
||||
obs_output_signal_stop(stream->output, code);
|
||||
stream->capturing = false;
|
||||
}
|
||||
|
||||
static bool write_packet(struct ffmpeg_muxer *stream,
|
||||
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
|
||||
};
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = os_process_pipe_write(stream->pipe, packet->data, packet->size);
|
||||
if (ret != packet->size) {
|
||||
warn("os_process_pipe_write for packet data failed");
|
||||
signal_failure(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send_audio_headers(struct ffmpeg_muxer *stream,
|
||||
obs_encoder_t *aencoder, size_t idx)
|
||||
{
|
||||
struct encoder_packet packet = {
|
||||
.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);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
obs_encoder_get_extra_data(vencoder, &packet.data, &packet.size);
|
||||
return write_packet(stream, &packet);
|
||||
}
|
||||
|
||||
static bool send_headers(struct ffmpeg_muxer *stream)
|
||||
{
|
||||
obs_encoder_t *aencoder;
|
||||
size_t idx = 0;
|
||||
|
||||
if (!send_video_headers(stream))
|
||||
return false;
|
||||
|
||||
do {
|
||||
aencoder = obs_output_get_audio_encoder(stream->output, idx);
|
||||
if (aencoder) {
|
||||
if (!send_audio_headers(stream, aencoder, idx)) {
|
||||
return false;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
} while (aencoder);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ffmpeg_mux_data(void *data, struct encoder_packet *packet)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
|
||||
if (!stream->active)
|
||||
return;
|
||||
|
||||
if (!stream->sent_headers) {
|
||||
if (!send_headers(stream))
|
||||
return;
|
||||
|
||||
stream->sent_headers = true;
|
||||
}
|
||||
|
||||
write_packet(stream, packet);
|
||||
}
|
||||
|
||||
static obs_properties_t *ffmpeg_mux_properties(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
obs_properties_add_text(props, "path",
|
||||
obs_module_text("FilePath"),
|
||||
OBS_TEXT_DEFAULT);
|
||||
return props;
|
||||
}
|
||||
|
||||
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,
|
||||
.encoded_packet = ffmpeg_mux_data,
|
||||
.get_properties = ffmpeg_mux_properties
|
||||
};
|
||||
1032
plugins/obs-ffmpeg/obs-ffmpeg-output.c
Normal file
1032
plugins/obs-ffmpeg/obs-ffmpeg-output.c
Normal file
File diff suppressed because it is too large
Load diff
653
plugins/obs-ffmpeg/obs-ffmpeg-source.c
Normal file
653
plugins/obs-ffmpeg/obs-ffmpeg-source.c
Normal file
|
|
@ -0,0 +1,653 @@
|
|||
/*
|
||||
* Copyright (c) 2015 John R. Bradley <jrb@turrettech.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <util/platform.h>
|
||||
#include <util/dstr.h>
|
||||
|
||||
#include "obs-ffmpeg-compat.h"
|
||||
#include "obs-ffmpeg-formats.h"
|
||||
|
||||
#include <libff/ff-demuxer.h>
|
||||
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
#define FF_LOG(level, format, ...) \
|
||||
blog(level, "[Media Source]: " format, ##__VA_ARGS__)
|
||||
#define FF_LOG_S(source, level, format, ...) \
|
||||
blog(level, "[Media Source '%s']: " format, \
|
||||
obs_source_get_name(source), ##__VA_ARGS__)
|
||||
#define FF_BLOG(level, format, ...) \
|
||||
FF_LOG_S(s->source, level, format, ##__VA_ARGS__)
|
||||
|
||||
static bool video_frame(struct ff_frame *frame, void *opaque);
|
||||
static bool video_format(AVCodecContext *codec_context, void *opaque);
|
||||
|
||||
struct ffmpeg_source {
|
||||
struct ff_demuxer *demuxer;
|
||||
struct SwsContext *sws_ctx;
|
||||
int sws_width;
|
||||
int sws_height;
|
||||
enum AVPixelFormat sws_format;
|
||||
uint8_t *sws_data;
|
||||
int sws_linesize;
|
||||
obs_source_t *source;
|
||||
|
||||
char *input;
|
||||
char *input_format;
|
||||
enum AVDiscard frame_drop;
|
||||
int audio_buffer_size;
|
||||
int video_buffer_size;
|
||||
bool is_advanced;
|
||||
bool is_looping;
|
||||
bool is_forcing_scale;
|
||||
bool is_hw_decoding;
|
||||
bool is_clear_on_media_end;
|
||||
bool restart_on_activate;
|
||||
};
|
||||
|
||||
static bool set_obs_frame_colorprops(struct ff_frame *frame,
|
||||
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
|
||||
{
|
||||
enum AVColorSpace frame_cs = av_frame_get_colorspace(frame->frame);
|
||||
enum video_colorspace obs_cs;
|
||||
|
||||
switch(frame_cs) {
|
||||
case AVCOL_SPC_BT709: obs_cs = VIDEO_CS_709; break;
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_BT470BG: obs_cs = VIDEO_CS_601; break;
|
||||
case AVCOL_SPC_UNSPECIFIED: obs_cs = VIDEO_CS_DEFAULT; break;
|
||||
default:
|
||||
FF_BLOG(LOG_WARNING, "frame using an unsupported colorspace %d",
|
||||
frame_cs);
|
||||
obs_cs = VIDEO_CS_DEFAULT;
|
||||
}
|
||||
|
||||
enum video_range_type range;
|
||||
obs_frame->format = ffmpeg_to_obs_video_format(frame->frame->format);
|
||||
obs_frame->full_range =
|
||||
frame->frame->color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
range = obs_frame->full_range ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
|
||||
|
||||
if (!video_format_get_parameters(obs_cs,
|
||||
range, obs_frame->color_matrix,
|
||||
obs_frame->color_range_min,
|
||||
obs_frame->color_range_max)) {
|
||||
FF_BLOG(LOG_ERROR, "Failed to get video format "
|
||||
"parameters for video format %u",
|
||||
obs_cs);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update_sws_context(struct ffmpeg_source *s, AVFrame *frame)
|
||||
{
|
||||
if (frame->width != s->sws_width
|
||||
|| frame->height != s->sws_height
|
||||
|| frame->format != s->sws_format) {
|
||||
if (s->sws_ctx != NULL)
|
||||
sws_freeContext(s->sws_ctx);
|
||||
|
||||
if (frame->width <= 0 || frame->height <= 0) {
|
||||
FF_BLOG(LOG_ERROR, "unable to create a sws "
|
||||
"context that has a width(%d) or "
|
||||
"height(%d) of zero.", frame->width,
|
||||
frame->height);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->sws_ctx = sws_getContext(
|
||||
frame->width,
|
||||
frame->height,
|
||||
frame->format,
|
||||
frame->width,
|
||||
frame->height,
|
||||
AV_PIX_FMT_BGRA,
|
||||
SWS_BILINEAR,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
if (s->sws_ctx == NULL) {
|
||||
FF_BLOG(LOG_ERROR, "unable to create sws "
|
||||
"context with src{w:%d,h:%d,f:%d}->"
|
||||
"dst{w:%d,h:%d,f:%d}",
|
||||
frame->width, frame->height,
|
||||
frame->format, frame->width,
|
||||
frame->height, AV_PIX_FMT_BGRA);
|
||||
goto fail;
|
||||
|
||||
}
|
||||
|
||||
if (s->sws_data != NULL)
|
||||
bfree(s->sws_data);
|
||||
s->sws_data = bzalloc(frame->width * frame->height * 4);
|
||||
if (s->sws_data == NULL) {
|
||||
FF_BLOG(LOG_ERROR, "unable to allocate sws "
|
||||
"pixel data with size %d",
|
||||
frame->width * frame->height * 4);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->sws_linesize = frame->width * 4;
|
||||
s->sws_width = frame->width;
|
||||
s->sws_height = frame->height;
|
||||
s->sws_format = frame->format;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
if (s->sws_ctx != NULL)
|
||||
sws_freeContext(s->sws_ctx);
|
||||
s->sws_ctx = NULL;
|
||||
|
||||
if (s->sws_data)
|
||||
bfree(s->sws_data);
|
||||
s->sws_data = NULL;
|
||||
|
||||
s->sws_linesize = 0;
|
||||
s->sws_width = 0;
|
||||
s->sws_height = 0;
|
||||
s->sws_format = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool video_frame_scale(struct ff_frame *frame,
|
||||
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
|
||||
{
|
||||
if (!update_sws_context(s, frame->frame))
|
||||
return false;
|
||||
|
||||
sws_scale(
|
||||
s->sws_ctx,
|
||||
(uint8_t const *const *)frame->frame->data,
|
||||
frame->frame->linesize,
|
||||
0,
|
||||
frame->frame->height,
|
||||
&s->sws_data,
|
||||
&s->sws_linesize
|
||||
);
|
||||
|
||||
obs_frame->data[0] = s->sws_data;
|
||||
obs_frame->linesize[0] = s->sws_linesize;
|
||||
obs_frame->format = VIDEO_FORMAT_BGRA;
|
||||
|
||||
obs_source_output_video(s->source, obs_frame);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool video_frame_hwaccel(struct ff_frame *frame,
|
||||
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
|
||||
{
|
||||
// 4th plane is pixelbuf reference for mac
|
||||
for (int i = 0; i < 3; i++) {
|
||||
obs_frame->data[i] = frame->frame->data[i];
|
||||
obs_frame->linesize[i] = frame->frame->linesize[i];
|
||||
}
|
||||
|
||||
if (!set_obs_frame_colorprops(frame, s, obs_frame))
|
||||
return false;
|
||||
|
||||
obs_source_output_video(s->source, obs_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool video_frame_direct(struct ff_frame *frame,
|
||||
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_AV_PLANES; i++) {
|
||||
obs_frame->data[i] = frame->frame->data[i];
|
||||
obs_frame->linesize[i] = frame->frame->linesize[i];
|
||||
}
|
||||
|
||||
if (!set_obs_frame_colorprops(frame, s, obs_frame))
|
||||
return false;
|
||||
|
||||
obs_source_output_video(s->source, obs_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool video_frame(struct ff_frame *frame, void *opaque)
|
||||
{
|
||||
struct ffmpeg_source *s = opaque;
|
||||
struct obs_source_frame obs_frame = {0};
|
||||
uint64_t pts;
|
||||
|
||||
// Media ended
|
||||
if (frame == NULL) {
|
||||
if (s->is_clear_on_media_end)
|
||||
obs_source_output_video(s->source, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
pts = (uint64_t)(frame->pts * 1000000000.0L);
|
||||
|
||||
obs_frame.timestamp = pts;
|
||||
obs_frame.width = frame->frame->width;
|
||||
obs_frame.height = frame->frame->height;
|
||||
|
||||
enum video_format format =
|
||||
ffmpeg_to_obs_video_format(frame->frame->format);
|
||||
|
||||
if (s->is_forcing_scale || format == VIDEO_FORMAT_NONE)
|
||||
return video_frame_scale(frame, s, &obs_frame);
|
||||
else if (s->is_hw_decoding)
|
||||
return video_frame_hwaccel(frame, s, &obs_frame);
|
||||
else
|
||||
return video_frame_direct(frame, s, &obs_frame);
|
||||
}
|
||||
|
||||
static bool audio_frame(struct ff_frame *frame, void *opaque)
|
||||
{
|
||||
struct ffmpeg_source *s = opaque;
|
||||
|
||||
struct obs_source_audio audio_data = {0};
|
||||
|
||||
uint64_t pts;
|
||||
|
||||
// Media ended
|
||||
if (frame == NULL)
|
||||
return true;
|
||||
|
||||
pts = (uint64_t)(frame->pts * 1000000000.0L);
|
||||
|
||||
int channels = av_frame_get_channels(frame->frame);
|
||||
|
||||
for(int i = 0; i < channels; i++)
|
||||
audio_data.data[i] = frame->frame->data[i];
|
||||
|
||||
audio_data.samples_per_sec = frame->frame->sample_rate;
|
||||
audio_data.frames = frame->frame->nb_samples;
|
||||
audio_data.timestamp = pts;
|
||||
audio_data.format =
|
||||
convert_ffmpeg_sample_format(frame->frame->format);
|
||||
audio_data.speakers = channels;
|
||||
|
||||
obs_source_output_audio(s->source, &audio_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_local_file_modified(obs_properties_t *props,
|
||||
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 *local_file = obs_properties_get(props, "local_file");
|
||||
obs_property_t *looping = obs_properties_get(props, "looping");
|
||||
obs_property_set_visible(input, !enabled);
|
||||
obs_property_set_visible(input_format, !enabled);
|
||||
obs_property_set_visible(local_file, enabled);
|
||||
obs_property_set_visible(looping, enabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_advanced_modified(obs_properties_t *props,
|
||||
obs_property_t *prop, obs_data_t *settings)
|
||||
{
|
||||
UNUSED_PARAMETER(prop);
|
||||
|
||||
bool enabled = obs_data_get_bool(settings, "advanced");
|
||||
obs_property_t *fscale = obs_properties_get(props, "force_scale");
|
||||
obs_property_t *abuf = obs_properties_get(props, "audio_buffer_size");
|
||||
obs_property_t *vbuf = obs_properties_get(props, "video_buffer_size");
|
||||
obs_property_t *frame_drop = obs_properties_get(props, "frame_drop");
|
||||
obs_property_set_visible(fscale, enabled);
|
||||
obs_property_set_visible(abuf, enabled);
|
||||
obs_property_set_visible(vbuf, enabled);
|
||||
obs_property_set_visible(frame_drop, enabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ffmpeg_source_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_bool(settings, "is_local_file", true);
|
||||
obs_data_set_default_bool(settings, "looping", false);
|
||||
obs_data_set_default_bool(settings, "clear_on_media_end", true);
|
||||
obs_data_set_default_bool(settings, "restart_on_activate", true);
|
||||
obs_data_set_default_bool(settings, "force_scale", true);
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
obs_data_set_default_bool(settings, "hw_decode", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
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 obs_properties_t *ffmpeg_source_getproperties(void *data)
|
||||
{
|
||||
struct dstr filter = {0};
|
||||
UNUSED_PARAMETER(data);
|
||||
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
|
||||
|
||||
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_property_set_modified_callback(prop, is_local_file_modified);
|
||||
|
||||
dstr_copy(&filter, obs_module_text("MediaFileFilter.AllMediaFiles"));
|
||||
dstr_cat(&filter, media_filter);
|
||||
dstr_cat(&filter, obs_module_text("MediaFileFilter.VideoFiles"));
|
||||
dstr_cat(&filter, video_filter);
|
||||
dstr_cat(&filter, obs_module_text("MediaFileFilter.AudioFiles"));
|
||||
dstr_cat(&filter, audio_filter);
|
||||
dstr_cat(&filter, obs_module_text("MediaFileFilter.AllFiles"));
|
||||
dstr_cat(&filter, " (*.*)");
|
||||
|
||||
obs_properties_add_path(props, "local_file",
|
||||
obs_module_text("LocalFile"), OBS_PATH_FILE,
|
||||
filter.array, NULL);
|
||||
dstr_free(&filter);
|
||||
|
||||
obs_properties_add_bool(props, "looping", obs_module_text("Looping"));
|
||||
|
||||
obs_properties_add_bool(props, "restart_on_activate",
|
||||
obs_module_text("RestartWhenActivated"));
|
||||
|
||||
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_properties_add_bool(props, "hw_decode",
|
||||
obs_module_text("HardwareDecode"));
|
||||
|
||||
obs_properties_add_bool(props, "clear_on_media_end",
|
||||
obs_module_text("ClearOnMediaEnd"));
|
||||
|
||||
prop = obs_properties_add_bool(props, "advanced",
|
||||
obs_module_text("Advanced"));
|
||||
|
||||
obs_property_set_modified_callback(prop, is_advanced_modified);
|
||||
|
||||
obs_properties_add_bool(props, "force_scale",
|
||||
obs_module_text("ForceFormat"));
|
||||
|
||||
prop = obs_properties_add_int(props, "audio_buffer_size",
|
||||
obs_module_text("AudioBufferSize"), 1, 9999, 1);
|
||||
|
||||
obs_property_set_visible(prop, false);
|
||||
|
||||
prop = obs_properties_add_int(props, "video_buffer_size",
|
||||
obs_module_text("VideoBufferSize"), 1, 9999, 1);
|
||||
|
||||
obs_property_set_visible(prop, false);
|
||||
|
||||
prop = obs_properties_add_list(props, "frame_drop",
|
||||
obs_module_text("FrameDropping"), OBS_COMBO_TYPE_LIST,
|
||||
OBS_COMBO_FORMAT_INT);
|
||||
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardNone"),
|
||||
AVDISCARD_NONE);
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardDefault"),
|
||||
AVDISCARD_DEFAULT);
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardNonRef"),
|
||||
AVDISCARD_NONREF);
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardBiDir"),
|
||||
AVDISCARD_BIDIR);
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 67, 100)
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardNonIntra"),
|
||||
AVDISCARD_NONINTRA);
|
||||
#endif
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardNonKey"),
|
||||
AVDISCARD_NONKEY);
|
||||
obs_property_list_add_int(prop, obs_module_text("DiscardAll"),
|
||||
AVDISCARD_ALL);
|
||||
|
||||
obs_property_set_visible(prop, false);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
static const char *frame_drop_to_str(enum AVDiscard discard)
|
||||
{
|
||||
#define DISCARD_CASE(x) case AVDISCARD_ ## x: return "AVDISCARD_" #x
|
||||
switch (discard)
|
||||
{
|
||||
DISCARD_CASE(NONE);
|
||||
DISCARD_CASE(DEFAULT);
|
||||
DISCARD_CASE(NONREF);
|
||||
DISCARD_CASE(BIDIR);
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 67, 100)
|
||||
DISCARD_CASE(NONINTRA);
|
||||
#endif
|
||||
DISCARD_CASE(NONKEY);
|
||||
DISCARD_CASE(ALL);
|
||||
default: return "(Unknown)";
|
||||
};
|
||||
#undef DISCARD_CASE
|
||||
}
|
||||
|
||||
static void dump_source_info(struct ffmpeg_source *s, const char *input,
|
||||
const char *input_format, bool is_advanced)
|
||||
{
|
||||
FF_BLOG(LOG_INFO,
|
||||
"settings:\n"
|
||||
"\tinput: %s\n"
|
||||
"\tinput_format: %s\n"
|
||||
"\tis_looping: %s\n"
|
||||
"\tis_forcing_scale: %s\n"
|
||||
"\tis_hw_decoding: %s\n"
|
||||
"\tis_clear_on_media_end: %s\n"
|
||||
"\trestart_on_activate: %s",
|
||||
input ? input : "(null)",
|
||||
input_format ? input_format : "(null)",
|
||||
s->is_looping ? "yes" : "no",
|
||||
s->is_forcing_scale ? "yes" : "no",
|
||||
s->is_hw_decoding ? "yes" : "no",
|
||||
s->is_clear_on_media_end ? "yes" : "no",
|
||||
s->restart_on_activate ? "yes" : "no");
|
||||
|
||||
if (!is_advanced)
|
||||
return;
|
||||
|
||||
FF_BLOG(LOG_INFO,
|
||||
"advanced settings:\n"
|
||||
"\taudio_buffer_size: %d\n"
|
||||
"\tvideo_buffer_size: %d\n"
|
||||
"\tframe_drop: %s",
|
||||
s->audio_buffer_size,
|
||||
s->video_buffer_size,
|
||||
frame_drop_to_str(s->frame_drop));
|
||||
}
|
||||
|
||||
static void ffmpeg_source_start(struct ffmpeg_source *s)
|
||||
{
|
||||
if (s->demuxer != NULL)
|
||||
ff_demuxer_free(s->demuxer);
|
||||
|
||||
s->demuxer = ff_demuxer_init();
|
||||
s->demuxer->options.is_hw_decoding = s->is_hw_decoding;
|
||||
s->demuxer->options.is_looping = s->is_looping;
|
||||
|
||||
ff_demuxer_set_callbacks(&s->demuxer->video_callbacks,
|
||||
video_frame, NULL,
|
||||
NULL, NULL, NULL, s);
|
||||
|
||||
ff_demuxer_set_callbacks(&s->demuxer->audio_callbacks,
|
||||
audio_frame, NULL,
|
||||
NULL, NULL, NULL, s);
|
||||
|
||||
if (s->is_advanced) {
|
||||
s->demuxer->options.audio_frame_queue_size =
|
||||
s->audio_buffer_size;
|
||||
s->demuxer->options.video_frame_queue_size =
|
||||
s->video_buffer_size;
|
||||
s->demuxer->options.frame_drop = s->frame_drop;
|
||||
}
|
||||
|
||||
ff_demuxer_open(s->demuxer, s->input, s->input_format);
|
||||
}
|
||||
|
||||
static void ffmpeg_source_update(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct ffmpeg_source *s = data;
|
||||
|
||||
bool is_local_file = obs_data_get_bool(settings, "is_local_file");
|
||||
bool is_advanced = obs_data_get_bool(settings, "advanced");
|
||||
|
||||
char *input;
|
||||
char *input_format;
|
||||
|
||||
bfree(s->input);
|
||||
bfree(s->input_format);
|
||||
|
||||
if (is_local_file) {
|
||||
input = (char *)obs_data_get_string(settings, "local_file");
|
||||
input_format = NULL;
|
||||
s->is_looping = obs_data_get_bool(settings, "looping");
|
||||
} else {
|
||||
input = (char *)obs_data_get_string(settings, "input");
|
||||
input_format = (char *)obs_data_get_string(settings,
|
||||
"input_format");
|
||||
s->is_looping = false;
|
||||
}
|
||||
|
||||
s->input = input ? bstrdup(input) : NULL;
|
||||
s->input_format = input_format ? bstrdup(input_format) : NULL;
|
||||
s->is_advanced = is_advanced;
|
||||
s->is_hw_decoding = obs_data_get_bool(settings, "hw_decode");
|
||||
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_forcing_scale = true;
|
||||
|
||||
if (is_advanced) {
|
||||
s->audio_buffer_size = (int)obs_data_get_int(settings,
|
||||
"audio_buffer_size");
|
||||
s->video_buffer_size = (int)obs_data_get_int(settings,
|
||||
"video_buffer_size");
|
||||
s->frame_drop = (enum AVDiscard)obs_data_get_int(settings,
|
||||
"frame_drop");
|
||||
s->is_forcing_scale = obs_data_get_bool(settings,
|
||||
"force_scale");
|
||||
|
||||
if (s->audio_buffer_size < 1) {
|
||||
s->audio_buffer_size = 1;
|
||||
FF_BLOG(LOG_WARNING, "invalid audio_buffer_size %d",
|
||||
s->audio_buffer_size);
|
||||
}
|
||||
if (s->video_buffer_size < 1) {
|
||||
s->video_buffer_size = 1;
|
||||
FF_BLOG(LOG_WARNING, "invalid audio_buffer_size %d",
|
||||
s->audio_buffer_size);
|
||||
}
|
||||
|
||||
if (s->frame_drop < AVDISCARD_NONE ||
|
||||
s->frame_drop > AVDISCARD_ALL) {
|
||||
s->frame_drop = AVDISCARD_DEFAULT;
|
||||
FF_BLOG(LOG_WARNING, "invalid frame_drop %d",
|
||||
s->frame_drop);
|
||||
}
|
||||
}
|
||||
|
||||
dump_source_info(s, input, input_format, is_advanced);
|
||||
if (!s->restart_on_activate || obs_source_active(s->source))
|
||||
ffmpeg_source_start(s);
|
||||
}
|
||||
|
||||
static const char *ffmpeg_source_getname(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
return obs_module_text("FFMpegSource");
|
||||
}
|
||||
|
||||
static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source)
|
||||
{
|
||||
UNUSED_PARAMETER(settings);
|
||||
|
||||
struct ffmpeg_source *s = bzalloc(sizeof(struct ffmpeg_source));
|
||||
s->source = source;
|
||||
|
||||
ffmpeg_source_update(s, settings);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void ffmpeg_source_destroy(void *data)
|
||||
{
|
||||
struct ffmpeg_source *s = data;
|
||||
|
||||
if (s->demuxer)
|
||||
ff_demuxer_free(s->demuxer);
|
||||
|
||||
if (s->sws_ctx != NULL)
|
||||
sws_freeContext(s->sws_ctx);
|
||||
bfree(s->sws_data);
|
||||
bfree(s->input);
|
||||
bfree(s->input_format);
|
||||
bfree(s);
|
||||
}
|
||||
|
||||
static void ffmpeg_source_activate(void *data)
|
||||
{
|
||||
struct ffmpeg_source *s = data;
|
||||
|
||||
if (s->restart_on_activate)
|
||||
ffmpeg_source_start(s);
|
||||
}
|
||||
|
||||
static void ffmpeg_source_deactivate(void *data)
|
||||
{
|
||||
struct ffmpeg_source *s = data;
|
||||
|
||||
if (s->restart_on_activate) {
|
||||
if (s->demuxer != NULL) {
|
||||
ff_demuxer_free(s->demuxer);
|
||||
s->demuxer = NULL;
|
||||
|
||||
if (s->is_clear_on_media_end)
|
||||
obs_source_output_video(s->source, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
.get_properties = ffmpeg_source_getproperties,
|
||||
.activate = ffmpeg_source_activate,
|
||||
.deactivate = ffmpeg_source_deactivate,
|
||||
.update = ffmpeg_source_update
|
||||
};
|
||||
145
plugins/obs-ffmpeg/obs-ffmpeg.c
Normal file
145
plugins/obs-ffmpeg/obs-ffmpeg.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#include <obs-module.h>
|
||||
#include <util/darray.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <pthread.h>
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-ffmpeg", "en-US")
|
||||
|
||||
extern struct obs_source_info ffmpeg_source;
|
||||
extern struct obs_output_info ffmpeg_output;
|
||||
extern struct obs_output_info ffmpeg_muxer;
|
||||
extern struct obs_encoder_info aac_encoder_info;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
obs_register_encoder(&aac_encoder_info);
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload(void)
|
||||
{
|
||||
av_log_set_callback(av_log_default_callback);
|
||||
|
||||
#ifdef _WIN32
|
||||
pthread_mutex_destroy(&log_contexts_mutex);
|
||||
#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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue