feat(server): advanced settings for transcoding (#3775)

* set stream with `-map` flag

* updated tests

* fixed audio stream mapping

* added bframe setting to config

* updated api

* added b-frame option in dashboard

* updated tests and formatting

* "Advanced" section for FFmpeg with extra options

* updated api

* updated tests and formatting

* styling

* made vp9 bitstream filters conditional on b-frames

* fixed gop size condition

* add cq override

* simplified isEdited conditions

* simplified conditional flow for cq mode

* fixed dto

* clarified cq mode in description

* formatting

* added npl setting

* Adjusted b-frame title and description

* fixed rebase

* changed defaults for pascal compatibility, added temporal aq setting

* updated api

* added temporal aq to ui

* polished dashboard

* formatting
This commit is contained in:
Mert 2023-09-02 21:22:42 -04:00 committed by GitHub
parent 67ac686704
commit f8ff342852
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 797 additions and 188 deletions

View file

@ -5415,6 +5415,14 @@
],
"type": "string"
},
"CQMode": {
"enum": [
"auto",
"cqp",
"icq"
],
"type": "string"
},
"ChangePasswordDto": {
"properties": {
"newPassword": {
@ -7001,15 +7009,30 @@
"accel": {
"$ref": "#/components/schemas/TranscodeHWAccel"
},
"bframes": {
"type": "integer"
},
"cqMode": {
"$ref": "#/components/schemas/CQMode"
},
"crf": {
"type": "integer"
},
"gopSize": {
"type": "integer"
},
"maxBitrate": {
"type": "string"
},
"npl": {
"type": "integer"
},
"preset": {
"type": "string"
},
"refs": {
"type": "integer"
},
"targetAudioCodec": {
"$ref": "#/components/schemas/AudioCodec"
},
@ -7019,6 +7042,9 @@
"targetVideoCodec": {
"$ref": "#/components/schemas/VideoCodec"
},
"temporalAQ": {
"type": "boolean"
},
"threads": {
"type": "integer"
},
@ -7037,12 +7063,18 @@
"threads",
"targetVideoCodec",
"targetAudioCodec",
"bframes",
"refs",
"gopSize",
"npl",
"cqMode",
"transcode",
"accel",
"tonemap",
"preset",
"targetResolution",
"maxBitrate",
"temporalAQ",
"twoPass"
],
"type": "object"

View file

@ -311,10 +311,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf format=yuv420p',
'-preset ultrafast',
@ -350,10 +352,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf format=yuv420p',
'-preset ultrafast',
@ -374,10 +378,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -401,10 +407,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf format=yuv420p',
'-preset ultrafast',
@ -426,10 +434,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=720:-2,format=yuv420p',
'-preset ultrafast',
@ -451,10 +461,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -476,10 +488,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -525,10 +539,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -555,10 +571,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -582,10 +600,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -611,10 +631,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 vp9',
'-c:a:0 aac',
'-c:v vp9',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-cpu-used 5',
@ -642,10 +664,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 vp9',
'-c:a:0 aac',
'-c:v vp9',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-cpu-used 2',
@ -672,10 +696,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 vp9',
'-c:a:0 aac',
'-c:v vp9',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-row-mt 1',
@ -701,10 +727,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 vp9',
'-c:a:0 aac',
'-c:v vp9',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-cpu-used 5',
@ -729,10 +757,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -757,10 +787,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -785,10 +817,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 hevc',
'-c:a:0 aac',
'-c:v hevc',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -816,10 +850,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 hevc',
'-c:a:0 aac',
'-c:v hevc',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -878,17 +914,15 @@ describe(MediaService.name, () => {
outputOptions: [
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
`-c:v:0 h264_nvenc`,
'-c:a:0 aac',
`-c:v h264_nvenc`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload_cuda,scale_cuda=-2:720',
'-preset p1',
@ -918,17 +952,15 @@ describe(MediaService.name, () => {
outputOptions: [
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
`-c:v:0 h264_nvenc`,
'-c:a:0 aac',
`-c:v h264_nvenc`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload_cuda,scale_cuda=-2:720',
'-preset p1',
@ -954,17 +986,15 @@ describe(MediaService.name, () => {
outputOptions: [
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
`-c:v:0 h264_nvenc`,
'-c:a:0 aac',
`-c:v h264_nvenc`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload_cuda,scale_cuda=-2:720',
'-preset p1',
@ -991,17 +1021,15 @@ describe(MediaService.name, () => {
outputOptions: [
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
`-c:v:0 h264_nvenc`,
'-c:a:0 aac',
`-c:v h264_nvenc`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload_cuda,scale_cuda=-2:720',
'-cq:v 23',
@ -1024,17 +1052,15 @@ describe(MediaService.name, () => {
outputOptions: [
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
`-c:v:0 h264_nvenc`,
'-c:a:0 aac',
`-c:v h264_nvenc`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload_cuda,scale_cuda=-2:720',
'-preset p1',
@ -1060,14 +1086,15 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
outputOptions: [
'-g 256',
'-extbrc 1',
'-refs 5',
'-bf 7',
`-c:v:0 h264_qsv`,
'-c:a:0 aac',
`-c:v h264_qsv`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-bf 7',
'-refs 5',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
'-preset 7',
@ -1095,14 +1122,15 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
outputOptions: [
'-g 256',
'-extbrc 1',
'-refs 5',
'-bf 7',
`-c:v:0 h264_qsv`,
'-c:a:0 aac',
`-c:v h264_qsv`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-bf 7',
'-refs 5',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
'-global_quality 23',
@ -1127,14 +1155,15 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
outputOptions: [
'-g 256',
'-extbrc 1',
'-refs 5',
'-bf 7',
`-c:v:0 vp9_qsv`,
'-c:a:0 aac',
`-c:v vp9_qsv`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-bf 7',
'-refs 5',
'-g 256',
'-low_power 1',
'-v verbose',
'-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
@ -1170,10 +1199,13 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
outputOptions: [
`-c:v:0 h264_vaapi`,
'-c:a:0 aac',
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-compression_level 7',
@ -1199,10 +1231,13 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
outputOptions: [
`-c:v:0 h264_vaapi`,
'-c:a:0 aac',
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-compression_level 7',
@ -1230,10 +1265,13 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
outputOptions: [
`-c:v:0 h264_vaapi`,
'-c:a:0 aac',
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-qp 23',
@ -1257,10 +1295,13 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/card1', '-filter_hw_device accel'],
outputOptions: [
`-c:v:0 h264_vaapi`,
'-c:a:0 aac',
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-compression_level 7',
@ -1280,10 +1321,13 @@ describe(MediaService.name, () => {
{
inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD129', '-filter_hw_device accel'],
outputOptions: [
`-c:v:0 h264_vaapi`,
'-c:a:0 aac',
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-compression_level 7',
@ -1310,10 +1354,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf scale=-2:720,format=yuv420p',
'-preset ultrafast',
@ -1345,10 +1391,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
'-preset ultrafast',
@ -1370,10 +1418,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
'-preset ultrafast',
@ -1395,10 +1445,12 @@ describe(MediaService.name, () => {
{
inputOptions: [],
outputOptions: [
'-c:v:0 h264',
'-c:a:0 aac',
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
'-vf zscale=t=linear:npl=250,tonemap=mobius:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
'-preset ultrafast',

View file

@ -1,4 +1,4 @@
import { ToneMapping, TranscodeHWAccel, VideoCodec } from '@app/infra/entities';
import { CQMode, ToneMapping, TranscodeHWAccel, VideoCodec } from '@app/infra/entities';
import { SystemConfigFFmpegDto } from '../system-config/dto';
import {
AudioStreamInfo,
@ -9,9 +9,10 @@ import {
VideoStreamInfo,
} from './media.repository';
class BaseConfig implements VideoCodecSWConfig {
presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
constructor(protected config: SystemConfigFFmpegDto) {}
getOptions(videoStream: VideoStreamInfo, audioStream: AudioStreamInfo) {
getOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = {
inputOptions: this.getBaseInputOptions(),
outputOptions: this.getBaseOutputOptions(videoStream, audioStream).concat('-v verbose'),
@ -32,15 +33,30 @@ class BaseConfig implements VideoCodecSWConfig {
return [];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream: AudioStreamInfo) {
return [
`-c:v:${videoStream.index} ${this.getVideoCodec()}`,
`-c:a:${audioStream.index} ${this.getAudioCodec()}`,
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = [
`-c:v ${this.getVideoCodec()}`,
`-c:a ${this.getAudioCodec()}`,
// Makes a second pass moving the moov atom to the
// beginning of the file for improved playback speed.
'-movflags faststart',
'-fps_mode passthrough',
// explicitly selects the video stream instead of leaving it up to FFmpeg
`-map 0:${videoStream.index}`,
];
if (audioStream) {
options.push(`-map 0:${audioStream.index}`);
}
if (this.getBFrames() > -1) {
options.push(`-bf ${this.getBFrames()}`);
}
if (this.getRefs() > 0) {
options.push(`-refs ${this.getRefs()}`);
}
if (this.getGopSize() > 0) {
options.push(`-g ${this.getGopSize()}`);
}
return options;
}
getFilterOptions(videoStream: VideoStreamInfo) {
@ -72,12 +88,12 @@ class BaseConfig implements VideoCodecSWConfig {
} else if (bitrates.max > 0) {
// -bufsize is the peak possible bitrate at any moment, while -maxrate is the max rolling average bitrate
return [
`-crf ${this.config.crf}`,
`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`,
`-maxrate ${bitrates.max}${bitrates.unit}`,
`-bufsize ${bitrates.max * 2}${bitrates.unit}`,
];
} else {
return [`-crf ${this.config.crf}`];
return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`];
}
}
@ -149,8 +165,7 @@ class BaseConfig implements VideoCodecSWConfig {
}
getPresetIndex() {
const presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
return presets.indexOf(this.config.preset);
return this.presets.indexOf(this.config.preset);
}
getColors() {
@ -161,14 +176,20 @@ class BaseConfig implements VideoCodecSWConfig {
};
}
getNPL() {
if (this.config.npl <= 0) {
// since hable already outputs a darker image, we use a lower npl value for it
return this.config.tonemap === ToneMapping.HABLE ? 100 : 250;
} else {
return this.config.npl;
}
}
getToneMapping() {
const colors = this.getColors();
// npl stands for nominal peak luminance
// lower npl values result in brighter output (compensating for dimmer screens)
// since hable already outputs a darker image, we use a lower npl value for it
const npl = this.config.tonemap === ToneMapping.HABLE ? 100 : 250;
return [
`zscale=t=linear:npl=${npl}`,
`zscale=t=linear:npl=${this.getNPL()}`,
`tonemap=${this.config.tonemap}:desat=0`,
`zscale=p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:range=pc`,
];
@ -181,6 +202,22 @@ class BaseConfig implements VideoCodecSWConfig {
getVideoCodec(): string {
return this.config.targetVideoCodec;
}
getBFrames() {
return this.config.bframes;
}
getRefs() {
return this.config.refs;
}
getGopSize() {
return this.config.gopSize;
}
useCQP() {
return this.config.cqMode === CQMode.CQP;
}
}
export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
@ -216,6 +253,13 @@ export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
getVideoCodec(): string {
return `${this.config.targetVideoCodec}_${this.config.accel}`;
}
getGopSize() {
if (this.config.gopSize <= 0) {
return 256;
}
return this.config.gopSize;
}
}
export class ThumbnailConfig extends BaseConfig {
@ -294,7 +338,7 @@ export class VP9Config extends BaseConfig {
];
}
return [`-crf ${this.config.crf}`, `-b:v ${bitrates.max}${bitrates.unit}`];
return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, `-b:v ${bitrates.max}${bitrates.unit}`];
}
getThreadOptions() {
@ -311,20 +355,23 @@ export class NVENCConfig extends BaseHWConfig {
return ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream: AudioStreamInfo) {
return [
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = [
// below settings recommended from https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#command-line-for-latency-tolerant-high-quality-transcoding
'-tune hq',
'-qmin 0',
'-g 250',
'-bf 3',
'-b_ref_mode middle',
'-temporal-aq 1',
'-rc-lookahead 20',
'-i_qfactor 0.75',
'-b_qfactor 1.1',
...super.getBaseOutputOptions(videoStream, audioStream),
];
if (this.getBFrames() > 0) {
options.push('-b_ref_mode middle');
options.push('-b_qfactor 1.1');
}
if (this.config.temporalAQ) {
options.push('-temporal-aq 1');
}
return options;
}
getFilterOptions(videoStream: VideoStreamInfo) {
@ -369,6 +416,14 @@ export class NVENCConfig extends BaseHWConfig {
getThreadOptions() {
return [];
}
getRefs() {
const bframes = this.getBFrames();
if (bframes > 0 && bframes < 3 && this.config.refs < 3) {
return 0;
}
return this.config.refs;
}
}
export class QSVConfig extends BaseHWConfig {
@ -379,15 +434,8 @@ export class QSVConfig extends BaseHWConfig {
return ['-init_hw_device qsv=hw', '-filter_hw_device hw'];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream: AudioStreamInfo) {
// recommended from https://github.com/intel/media-delivery/blob/master/doc/benchmarks/intel-iris-xe-max-graphics/intel-iris-xe-max-graphics.md
const options = [
'-g 256',
'-extbrc 1',
'-refs 5',
'-bf 7',
...super.getBaseOutputOptions(videoStream, audioStream),
];
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = super.getBaseOutputOptions(videoStream, audioStream);
// VP9 requires enabling low power mode https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/33583803e107b6d532def0f9d949364b01b6ad5a
if (this.config.targetVideoCodec === VideoCodec.VP9) {
options.push('-low_power 1');
@ -415,11 +463,7 @@ export class QSVConfig extends BaseHWConfig {
getBitrateOptions() {
const options = [];
if (this.config.targetVideoCodec !== VideoCodec.VP9) {
options.push(`-global_quality ${this.config.crf}`);
} else {
options.push(`-q:v ${this.config.crf}`);
}
options.push(`-${this.useCQP() ? 'q:v' : 'global_quality'} ${this.config.crf}`);
const bitrates = this.getBitrateDistribution();
if (bitrates.max > 0) {
options.push(`-maxrate ${bitrates.max}${bitrates.unit}`);
@ -427,6 +471,25 @@ export class QSVConfig extends BaseHWConfig {
}
return options;
}
// recommended from https://github.com/intel/media-delivery/blob/master/doc/benchmarks/intel-iris-xe-max-graphics/intel-iris-xe-max-graphics.md
getBFrames() {
if (this.config.bframes < 0) {
return 7;
}
return this.config.bframes;
}
getRefs() {
if (this.config.refs <= 0) {
return 5;
}
return this.config.refs;
}
useCQP() {
return this.config.cqMode === CQMode.CQP || this.config.targetVideoCodec === VideoCodec.VP9;
}
}
export class VAAPIConfig extends BaseHWConfig {
@ -458,16 +521,30 @@ export class VAAPIConfig extends BaseHWConfig {
getBitrateOptions() {
const bitrates = this.getBitrateDistribution();
const options = [];
if (this.config.targetVideoCodec === VideoCodec.VP9) {
options.push('-bsf:v vp9_raw_reorder,vp9_superframe');
}
// VAAPI doesn't allow setting both quality and max bitrate
if (bitrates.max > 0) {
return [
options.push(
`-b:v ${bitrates.target}${bitrates.unit}`,
`-maxrate ${bitrates.max}${bitrates.unit}`,
`-minrate ${bitrates.min}${bitrates.unit}`,
'-rc_mode 3',
]; // variable bitrate
); // variable bitrate
} else if (this.useCQP()) {
options.push(`-qp ${this.config.crf}`, `-global_quality ${this.config.crf}`, '-rc_mode 1');
} else {
return [`-qp ${this.config.crf}`, `-global_quality ${this.config.crf}`, '-rc_mode 1']; // constant quality
options.push(`-global_quality ${this.config.crf}`, '-rc_mode 4');
}
return options;
}
useCQP() {
return this.config.cqMode !== CQMode.ICQ || this.config.targetVideoCodec === VideoCodec.VP9;
}
}

View file

@ -1,4 +1,4 @@
import { AudioCodec, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@app/infra/entities';
import { AudioCodec, CQMode, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsEnum, IsInt, IsString, Max, Min } from 'class-validator';
@ -34,6 +34,39 @@ export class SystemConfigFFmpegDto {
@IsString()
maxBitrate!: string;
@IsInt()
@Min(-1)
@Max(16)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
bframes!: number;
@IsInt()
@Min(0)
@Max(6)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
refs!: number;
@IsInt()
@Min(0)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
gopSize!: number;
@IsInt()
@Min(0)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
npl!: number;
@IsBoolean()
temporalAQ!: boolean;
@IsEnum(CQMode)
@ApiProperty({ enumName: 'CQMode', enum: CQMode })
cqMode!: CQMode;
@IsBoolean()
twoPass!: boolean;

View file

@ -1,5 +1,6 @@
import {
AudioCodec,
CQMode,
SystemConfig,
SystemConfigEntity,
SystemConfigKey,
@ -30,6 +31,12 @@ export const defaults = Object.freeze<SystemConfig>({
targetAudioCodec: AudioCodec.AAC,
targetResolution: '720',
maxBitrate: '0',
bframes: -1,
refs: 0,
gopSize: 0,
npl: 0,
temporalAQ: false,
cqMode: CQMode.AUTO,
twoPass: false,
transcode: TranscodePolicy.REQUIRED,
tonemap: ToneMapping.HABLE,

View file

@ -1,5 +1,6 @@
import {
AudioCodec,
CQMode,
SystemConfig,
SystemConfigEntity,
SystemConfigKey,
@ -41,6 +42,12 @@ const updatedConfig = Object.freeze<SystemConfig>({
targetResolution: '720',
targetVideoCodec: VideoCodec.H264,
maxBitrate: '0',
bframes: -1,
refs: 0,
gopSize: 0,
npl: 0,
temporalAQ: false,
cqMode: CQMode.AUTO,
twoPass: false,
transcode: TranscodePolicy.REQUIRED,
accel: TranscodeHWAccel.DISABLED,

View file

@ -21,6 +21,12 @@ export enum SystemConfigKey {
FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec',
FFMPEG_TARGET_RESOLUTION = 'ffmpeg.targetResolution',
FFMPEG_MAX_BITRATE = 'ffmpeg.maxBitrate',
FFMPEG_BFRAMES = 'ffmpeg.bframes',
FFMPEG_REFS = 'ffmpeg.refs',
FFMPEG_GOP_SIZE = 'ffmpeg.gopSize',
FFMPEG_NPL = 'ffmpeg.npl',
FFMPEG_TEMPORAL_AQ = 'ffmpeg.temporalAQ',
FFMPEG_CQ_MODE = 'ffmpeg.cqMode',
FFMPEG_TWO_PASS = 'ffmpeg.twoPass',
FFMPEG_TRANSCODE = 'ffmpeg.transcode',
FFMPEG_ACCEL = 'ffmpeg.accel',
@ -105,6 +111,12 @@ export enum ToneMapping {
DISABLED = 'disabled',
}
export enum CQMode {
AUTO = 'auto',
CQP = 'cqp',
ICQ = 'icq',
}
export interface SystemConfig {
ffmpeg: {
crf: number;
@ -114,6 +126,12 @@ export interface SystemConfig {
targetAudioCodec: AudioCodec;
targetResolution: string;
maxBitrate: string;
bframes: number;
refs: number;
gopSize: number;
npl: number;
temporalAQ: boolean;
cqMode: CQMode;
twoPass: boolean;
transcode: TranscodePolicy;
accel: TranscodeHWAccel;

View file

@ -32,7 +32,6 @@ export class MediaRepository implements IMediaRepository {
async probe(input: string): Promise<VideoInfo> {
const results = await probe(input);
return {
format: {
formatName: results.format.format_name,

View file

@ -20,7 +20,7 @@ const probeStubDefaultVideoStream: VideoStreamInfo[] = [
];
const probeStubDefaultAudioStream: AudioStreamInfo[] = [
{ index: 0, codecName: 'aac', codecType: 'audio', frameCount: 100 },
{ index: 1, codecName: 'aac', codecType: 'audio', frameCount: 100 },
];
const probeStubDefault: VideoInfo = {
@ -119,7 +119,7 @@ export const probeStub = {
}),
audioStreamMp3: Object.freeze<VideoInfo>({
...probeStubDefault,
audioStreams: [{ index: 0, codecType: 'audio', codecName: 'aac', frameCount: 100 }],
audioStreams: [{ index: 1, codecType: 'audio', codecName: 'aac', frameCount: 100 }],
}),
matroskaContainer: Object.freeze<VideoInfo>({
...probeStubDefault,