Imported Upstream version 0.13.2+dsfg1

This commit is contained in:
Sebastian Ramacher 2016-02-24 00:16:51 +01:00
commit fb3990e9e5
2036 changed files with 287360 additions and 0 deletions

View 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)

View 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;
}

View file

@ -0,0 +1,6 @@
FFmpegOutput="مخرج FFmpeg"
FFmpegAAC="ترميز AAC الافتراضي لـFFmpeg"
Bitrate="معدل النقل"

View file

@ -0,0 +1,5 @@
FFmpegOutput="FFmpeg изход"
Bitrate="Битрейт"

View 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!)"

View 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"

View 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å!)"

View 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"

View file

@ -0,0 +1,18 @@
FFmpegOutput="Έξοδος FFmpeg"
FFmpegAAC="FFmpeg προεπιλεγμένος κωδικοποιητής AAC"
Bitrate="Ρυθμός μετάδοσης bit"
LocalFile="Τοπικό αρχείο"
Looping="Επανάληψη"
Input="Είσοδος"
InputFormat="Μορφή Εισόδου"
ForceFormat="Εξαναγκασμός μετατροπής μορφής"
HardwareDecode="Χρήση αποκωδικοποίησης υλικού όταν είναι διαθέσιμη"
ClearOnMediaEnd="Απόκρυψη πηγής όταν τελειώνει η αναπαραγωγή"
Advanced="Σύνθετες επιλογές"
FrameDropping="Επίπεδο Ρίψης Καρέ"
DiscardNone="Κανένα"
DiscardDefault="Προεπιλογή (άκυρα πακέτα)"
DiscardAll="Όλα τα καρέ (Προσοχή!)"

View 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"

View 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"

View 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"

View 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"

View 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"

View 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!)"

View 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"

View 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"

View 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)"

View 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="すべてのファイル"

View 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="모든 파일"

View 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!)"

View 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"

View 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"

View 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!)"

View 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!)"

View 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"

View 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="Все файлы"

View 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"

View 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!)"

View 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"

View 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="Све датотеке"

View 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!)"

View file

@ -0,0 +1,4 @@
Bitrate="บิตเรท"

View 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!)"

View 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="所有文件"

View file

@ -0,0 +1,19 @@
FFmpegOutput="FFmpeg 輸出"
FFmpegAAC="FFmpeg 預設 AAC 編碼器"
Bitrate="位元率"
FFmpegSource="媒體來源"
LocalFile="本機檔案"
Input="輸入"
InputFormat="輸入格式"
ForceFormat="強制格式轉換"
HardwareDecode="盡可能使用硬體解碼"
ClearOnMediaEnd="當播放結束時隱藏來源"
Advanced="進階"
AudioBufferSize="音訊緩衝區大小 (幀)"
VideoBufferSize="影像緩衝區大小 (幀)"
FrameDropping="掉幀程度"
DiscardNone="無"
DiscardDefault="預設 (無效封包)"

View 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")

View 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, &params->file, "file name"))
return false;
if (!get_opt_int(argc, argv, &params->has_video, "video track count"))
return false;
if (!get_opt_int(argc, argv, &params->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, &params->vcodec, "video codec"))
return false;
if (!get_opt_int(argc, argv, &params->vbitrate,"video bitrate"))
return false;
if (!get_opt_int(argc, argv, &params->width, "video width"))
return false;
if (!get_opt_int(argc, argv, &params->height, "video height"))
return false;
if (!get_opt_int(argc, argv, &params->fps_num, "video fps num"))
return false;
if (!get_opt_int(argc, argv, &params->fps_den, "video fps den"))
return false;
}
if (params->tracks) {
if (!get_opt_str(argc, argv, &params->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, &params->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;
}

View 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;
};

View 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
};

View 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

View 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;
}

View 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
};

File diff suppressed because it is too large Load diff

View 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
};

View 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);
}