diff --git a/src/misc/coders/Decoders/audio/Default.js b/src/misc/coders/Decoders/audio/Default.js index f9db3b7..a1d3cac 100644 --- a/src/misc/coders/Decoders/audio/Default.js +++ b/src/misc/coders/Decoders/audio/Default.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = []; + const mapping = { + global: [], + local: [], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/Default.js b/src/misc/coders/Decoders/video/Default.js index be7cc80..1dc1411 100644 --- a/src/misc/coders/Decoders/video/Default.js +++ b/src/misc/coders/Decoders/video/Default.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = []; + const mapping = { + global: [], + local: [], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/H264CUVID.js b/src/misc/coders/Decoders/video/H264CUVID.js index 887f48d..051fe18 100644 --- a/src/misc/coders/Decoders/video/H264CUVID.js +++ b/src/misc/coders/Decoders/video/H264CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'h264_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'h264_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/H264MMAL.js b/src/misc/coders/Decoders/video/H264MMAL.js index ec7a609..80feb94 100644 --- a/src/misc/coders/Decoders/video/H264MMAL.js +++ b/src/misc/coders/Decoders/video/H264MMAL.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'h264_mmal']; + const mapping = { + global: [], + local: ['-c:v', 'h264_mmal'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/HEVCCUVID.js b/src/misc/coders/Decoders/video/HEVCCUVID.js index 6731fb7..869f26a 100644 --- a/src/misc/coders/Decoders/video/HEVCCUVID.js +++ b/src/misc/coders/Decoders/video/HEVCCUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'hevc_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'hevc_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MJPEGCUVID.js b/src/misc/coders/Decoders/video/MJPEGCUVID.js index ee6827a..fc8706a 100644 --- a/src/misc/coders/Decoders/video/MJPEGCUVID.js +++ b/src/misc/coders/Decoders/video/MJPEGCUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mjpeg_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'mjpeg_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MPEG1CUVID.js b/src/misc/coders/Decoders/video/MPEG1CUVID.js index 0395336..cb2ecdf 100644 --- a/src/misc/coders/Decoders/video/MPEG1CUVID.js +++ b/src/misc/coders/Decoders/video/MPEG1CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mpeg1_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'mpeg1_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MPEG2CUVID.js b/src/misc/coders/Decoders/video/MPEG2CUVID.js index 9f1ee68..2d88baa 100644 --- a/src/misc/coders/Decoders/video/MPEG2CUVID.js +++ b/src/misc/coders/Decoders/video/MPEG2CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mpeg2_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'mpeg2_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MPEG2MMAL.js b/src/misc/coders/Decoders/video/MPEG2MMAL.js index 0c7f2c8..d2166c3 100644 --- a/src/misc/coders/Decoders/video/MPEG2MMAL.js +++ b/src/misc/coders/Decoders/video/MPEG2MMAL.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mpeg2_mmal']; + const mapping = { + global: [], + local: ['-c:v', 'mpeg2_mmal'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MPEG4CUVID.js b/src/misc/coders/Decoders/video/MPEG4CUVID.js index 065438f..b3da8b5 100644 --- a/src/misc/coders/Decoders/video/MPEG4CUVID.js +++ b/src/misc/coders/Decoders/video/MPEG4CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mpeg4_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'mpeg4_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/MPEG4MMAL.js b/src/misc/coders/Decoders/video/MPEG4MMAL.js index 6473f53..3e23ae4 100644 --- a/src/misc/coders/Decoders/video/MPEG4MMAL.js +++ b/src/misc/coders/Decoders/video/MPEG4MMAL.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'mpeg4_mmal']; + const mapping = { + global: [], + local: ['-c:v', 'mpeg4_mmal'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/NVDEC.js b/src/misc/coders/Decoders/video/NVDEC.js index 9194718..28b3216 100644 --- a/src/misc/coders/Decoders/video/NVDEC.js +++ b/src/misc/coders/Decoders/video/NVDEC.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-hwaccel', 'cuda']; + const mapping = { + global: [], + local: ['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/VC1CUVID.js b/src/misc/coders/Decoders/video/VC1CUVID.js index 12e50e3..b7da391 100644 --- a/src/misc/coders/Decoders/video/VC1CUVID.js +++ b/src/misc/coders/Decoders/video/VC1CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'vc1_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'vc1_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/VC1MMAL.js b/src/misc/coders/Decoders/video/VC1MMAL.js index 83cf83e..0c3fbad 100644 --- a/src/misc/coders/Decoders/video/VC1MMAL.js +++ b/src/misc/coders/Decoders/video/VC1MMAL.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'vc1_mmal']; + const mapping = { + global: [], + local: ['-c:v', 'vc1_mmal'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/VP8CUVID.js b/src/misc/coders/Decoders/video/VP8CUVID.js index 341ea3d..d298e8a 100644 --- a/src/misc/coders/Decoders/video/VP8CUVID.js +++ b/src/misc/coders/Decoders/video/VP8CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'vp8_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'vp8_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/VP9CUVID.js b/src/misc/coders/Decoders/video/VP9CUVID.js index f474ddd..5c8a409 100644 --- a/src/misc/coders/Decoders/video/VP9CUVID.js +++ b/src/misc/coders/Decoders/video/VP9CUVID.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-c:v', 'vp9_cuvid']; + const mapping = { + global: [], + local: ['-c:v', 'vp9_cuvid'], + }; return mapping; } diff --git a/src/misc/coders/Decoders/video/VideoToolbox.js b/src/misc/coders/Decoders/video/VideoToolbox.js index 760e6c8..1f77c30 100644 --- a/src/misc/coders/Decoders/video/VideoToolbox.js +++ b/src/misc/coders/Decoders/video/VideoToolbox.js @@ -9,7 +9,10 @@ function init(initialState) { } function createMapping(settings) { - const mapping = ['-hwaccel', 'videotoolbox']; + const mapping = { + global: [], + local: ['-hwaccel', 'videotoolbox'], + }; return mapping; } diff --git a/src/misc/coders/Encoders/audio/AAC.js b/src/misc/coders/Encoders/audio/AAC.js index 78e930a..a3eea58 100644 --- a/src/misc/coders/Encoders/audio/AAC.js +++ b/src/misc/coders/Encoders/audio/AAC.js @@ -28,12 +28,17 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; if (stream.codec === 'aac') { - mapping.push('-bsf:a', 'aac_adtstoasc'); + local.push('-bsf:a', 'aac_adtstoasc'); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/audio/AACAudioToolbox.js b/src/misc/coders/Encoders/audio/AACAudioToolbox.js index 0c47edb..f8e0525 100644 --- a/src/misc/coders/Encoders/audio/AACAudioToolbox.js +++ b/src/misc/coders/Encoders/audio/AACAudioToolbox.js @@ -28,12 +28,17 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; if (stream.codec === 'aac') { - mapping.push('-bsf:a', 'aac_adtstoasc'); + local.push('-bsf:a', 'aac_adtstoasc'); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/audio/Copy.js b/src/misc/coders/Encoders/audio/Copy.js index 533ed9a..d6f90cf 100644 --- a/src/misc/coders/Encoders/audio/Copy.js +++ b/src/misc/coders/Encoders/audio/Copy.js @@ -1,12 +1,18 @@ import React from 'react'; function createMapping(settings, stream) { - const mapping = ['-codec:a', 'copy']; + const local = ['-codec:a', 'copy']; /* if(stream.codec === 'aac') { - mapping.push('-bsf:a', 'aac_adtstoasc'); + local.push('-bsf:a', 'aac_adtstoasc'); } */ + + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/audio/Libopus.js b/src/misc/coders/Encoders/audio/Libopus.js index f118f9a..bb6d9d9 100644 --- a/src/misc/coders/Encoders/audio/Libopus.js +++ b/src/misc/coders/Encoders/audio/Libopus.js @@ -28,7 +28,12 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/audio/Libvorbis.js b/src/misc/coders/Encoders/audio/Libvorbis.js index 253875a..4ceafbc 100644 --- a/src/misc/coders/Encoders/audio/Libvorbis.js +++ b/src/misc/coders/Encoders/audio/Libvorbis.js @@ -28,7 +28,12 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/audio/MP3.js b/src/misc/coders/Encoders/audio/MP3.js index ed793b8..7a394c7 100644 --- a/src/misc/coders/Encoders/audio/MP3.js +++ b/src/misc/coders/Encoders/audio/MP3.js @@ -29,7 +29,12 @@ function createMapping(settings, stream) { } // '-qscale:a', '6' - const mapping = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/audio/None.js b/src/misc/coders/Encoders/audio/None.js index f8b7aa0..5f5c3fd 100644 --- a/src/misc/coders/Encoders/audio/None.js +++ b/src/misc/coders/Encoders/audio/None.js @@ -1,7 +1,12 @@ import React from 'react'; function createMapping(settings, stream) { - const mapping = ['-an']; + const local = ['-an']; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/audio/Opus.js b/src/misc/coders/Encoders/audio/Opus.js index 47ab8bf..17f62ed 100644 --- a/src/misc/coders/Encoders/audio/Opus.js +++ b/src/misc/coders/Encoders/audio/Opus.js @@ -34,12 +34,17 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'opus', '-b:a', `${settings.bitrate}k`, '-vbr', 'on', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'opus', '-b:a', `${settings.bitrate}k`, '-vbr', 'on', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; if (settings.delay !== 'auto') { - mapping.push('opus_delay', settings.delay); + local.push('opus_delay', settings.delay); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/audio/Vorbis.js b/src/misc/coders/Encoders/audio/Vorbis.js index d63e9bd..c484720 100644 --- a/src/misc/coders/Encoders/audio/Vorbis.js +++ b/src/misc/coders/Encoders/audio/Vorbis.js @@ -28,7 +28,12 @@ function createMapping(settings, stream) { layout = stream.layout; } - const mapping = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + const local = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`]; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/video/Copy.js b/src/misc/coders/Encoders/video/Copy.js index 95ebaaa..a162fa8 100644 --- a/src/misc/coders/Encoders/video/Copy.js +++ b/src/misc/coders/Encoders/video/Copy.js @@ -1,7 +1,12 @@ import React from 'react'; function createMapping(settings) { - const mapping = ['-codec:v', 'copy', '-vsync', '0', '-copyts', '-start_at_zero']; + const local = ['-codec:v', 'copy', '-vsync', 'passthrough', '-copyts', '-start_at_zero']; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/video/H264NVENC.js b/src/misc/coders/Encoders/video/H264NVENC.js index 5869602..5ccfb2e 100644 --- a/src/misc/coders/Encoders/video/H264NVENC.js +++ b/src/misc/coders/Encoders/video/H264NVENC.js @@ -24,7 +24,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'h264_nvenc', '-preset:v', @@ -44,21 +44,26 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } if (settings.level !== 'auto') { - mapping.push('-level:v', `${settings.level}`); + local.push('-level:v', `${settings.level}`); } if (settings.rc !== 'auto') { - mapping.push('-rc:v', `${settings.rc}`); + local.push('-rc:v', `${settings.rc}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/H264OMX.js b/src/misc/coders/Encoders/video/H264OMX.js index a0c0c0d..1aa08fc 100644 --- a/src/misc/coders/Encoders/video/H264OMX.js +++ b/src/misc/coders/Encoders/video/H264OMX.js @@ -17,7 +17,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'h264_omx', '-b:v', @@ -37,13 +37,18 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/H264V4L2M2M.js b/src/misc/coders/Encoders/video/H264V4L2M2M.js index 3868367..5604c54 100644 --- a/src/misc/coders/Encoders/video/H264V4L2M2M.js +++ b/src/misc/coders/Encoders/video/H264V4L2M2M.js @@ -65,7 +65,7 @@ Codec Controls */ function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'h264_v4l2m2m', '-b:v', @@ -83,13 +83,18 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/H264VAAPI.js b/src/misc/coders/Encoders/video/H264VAAPI.js index 4ba01c7..7014ade 100644 --- a/src/misc/coders/Encoders/video/H264VAAPI.js +++ b/src/misc/coders/Encoders/video/H264VAAPI.js @@ -24,7 +24,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-vaapi_device', '/dev/dri/renderD128', '-vf', @@ -46,13 +46,18 @@ function createMapping(settings) { '-g', `${settings.gop}`, '-vsync', - '1', + 'cfr', ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/H264VideoToolbox.js b/src/misc/coders/Encoders/video/H264VideoToolbox.js index 6d68d00..2ed00a0 100644 --- a/src/misc/coders/Encoders/video/H264VideoToolbox.js +++ b/src/misc/coders/Encoders/video/H264VideoToolbox.js @@ -22,7 +22,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'h264_videotoolbox', '-b:v', @@ -42,17 +42,22 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } if (settings.entropy !== 'default') { - mapping.push('-coder:v', `${settings.entropy}`); + local.push('-coder:v', `${settings.entropy}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/None.js b/src/misc/coders/Encoders/video/None.js index 6738f1b..6cd3632 100644 --- a/src/misc/coders/Encoders/video/None.js +++ b/src/misc/coders/Encoders/video/None.js @@ -1,7 +1,12 @@ import React from 'react'; function createMapping(settings, stream) { - const mapping = ['-vn']; + const local = ['-vn']; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/video/Raw.js b/src/misc/coders/Encoders/video/Raw.js index 61629aa..5c72db2 100644 --- a/src/misc/coders/Encoders/video/Raw.js +++ b/src/misc/coders/Encoders/video/Raw.js @@ -1,7 +1,12 @@ import React from 'react'; function createMapping(settings) { - const mapping = ['-codec:v', 'rawvideo']; + const local = ['-codec:v', 'rawvideo']; + + const mapping = { + global: [], + local: local, + }; return mapping; } diff --git a/src/misc/coders/Encoders/video/VP9.js b/src/misc/coders/Encoders/video/VP9.js index caa5696..0a2e4e9 100644 --- a/src/misc/coders/Encoders/video/VP9.js +++ b/src/misc/coders/Encoders/video/VP9.js @@ -16,7 +16,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'libvpx-vp9', '-b:v', @@ -34,9 +34,14 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/X264.js b/src/misc/coders/Encoders/video/X264.js index 6eaea11..ad3bbd5 100644 --- a/src/misc/coders/Encoders/video/X264.js +++ b/src/misc/coders/Encoders/video/X264.js @@ -23,7 +23,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'libx264', '-preset:v', @@ -43,17 +43,22 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } if (settings.tune !== 'none') { - mapping.push('-tune:v', `${settings.tune}`); + local.push('-tune:v', `${settings.tune}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/Encoders/video/X265.js b/src/misc/coders/Encoders/video/X265.js index ce055c2..019cc6e 100644 --- a/src/misc/coders/Encoders/video/X265.js +++ b/src/misc/coders/Encoders/video/X265.js @@ -23,7 +23,7 @@ function init(initialState) { } function createMapping(settings) { - const mapping = [ + const local = [ '-codec:v', 'libx265', '-preset:v', @@ -43,17 +43,22 @@ function createMapping(settings) { ]; if (settings.gop !== 'auto') { - mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); + local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`); } if (settings.profile !== 'auto') { - mapping.push('-profile:v', `${settings.profile}`); + local.push('-profile:v', `${settings.profile}`); } if (settings.tune !== 'none') { - mapping.push('-tune:v', `${settings.tune}`); + local.push('-tune:v', `${settings.tune}`); } + const mapping = { + global: [], + local: local, + }; + return mapping; } diff --git a/src/misc/coders/README.md b/src/misc/coders/README.md new file mode 100644 index 0000000..8ef022b --- /dev/null +++ b/src/misc/coders/README.md @@ -0,0 +1,118 @@ +# Decoders and Encoders + +Implementations of various decoders and encoder for audio and video. + +## Decoders + +Each decoder exports the same variables: + +``` +export { coder, name, codecs, type, hwaccel, defaults, Coder as component }; +``` + +| Variable | Description | +| -------- | -------------------------------------------------------------------- | +| coder | Name of the decoder in FFmpeg, e.g. `cuda`, `vc1_mmal`. | +| name | Name for the decoder as it will be displayed in the UI. | +| codecs | Array of codecs this coder supports, e.g. `['h264', 'h265']`. | +| type | Either `video` or `audio`. | +| hwaccel | Whether this codec uses hardware acceleration. | +| defaults | A function that returns the default settings and mapping. See below. | +| Coder | The React component. | + +### defaults + +The `defaults()` function returns the default settings and mappings for this decoder. It is an object of this shape: + +``` +{ + settings: {}, + mapping: { + global: [], + local: [], + }, +} +``` + +The `settings` is an object private to a coder and contains its settings as required for rendering the component +with options for this coder. The `mapping` object contains the FFmpeg command line options according to the settings. +It has a `global` array which contains all global options for this coder. _Each option (with its value) has to be +an array of its own_. The `local` array is an array of options for that input, e.g. + +``` +{ + settings: { + ... + }, + mapping: { + global: [ + ['-init_hw_device', 'vaapi=foo:/dev/dri/renderD128'], + ], + local: [ + '-hwaccel', 'vaapi', + '-hwaccel_output_format', 'vaapi', + '-hwaccel_device', 'foo', + ], + }, +} +``` + +Check out the existing decoders as examples for an implementation. + +## Encoders + +Each encoder exports the same variables: + +``` +export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as component }; +``` + +| Variable | Description | +| --------- | ---------------------------------------------------------------------- | +| coder | Name of the encoder in FFmpeg, e.g. `libx264`. | +| name | Name for the encoder as it will be displayed in the UI. | +| codec | Name of the codec, e.g. `h264`. | +| type | Either `video` or `audio`. | +| hwaccel | Whether this codec uses hardware acceleration. | +| summarize | A function that returns a string that summarizes the current settings. | +| defaults | A function that returns the default settings and mapping. See below. | +| Coder | The React component. | + +### defaults + +The `defaults()` function returns the default settings and mappings for this encoder. It is an object of this shape: + +``` +{ + settings: {}, + mapping: { + global: [], + local: [], + }, +} +``` + +The `settings` is an object private to a coder and contains its settings as required for rendering the component +with options for this coder. The `mapping` object contains the FFmpeg command line options according to the settings. +It has a `global` array which contains all global options for this coder. _Each option (with its value) has to be +an array of its own_. The `local` array is an array of options for that input, e.g. + +``` +{ + settings: { + ... + }, + mapping: { + global: [ + ['-init_hw_device', 'vaapi=foo:/dev/dri/renderD128'], + ], + local: [ + '-filter_hw_device', 'foo', + '-filter:v', 'format=nv12|vaapi,hwupload', + '-codec:v', 'h264_vaapi', + ], + }, +} +``` + +Check out the existing encoders as examples for an implementation. diff --git a/src/utils/metadata.js b/src/utils/metadata.js index 4a93366..d8b1154 100644 --- a/src/utils/metadata.js +++ b/src/utils/metadata.js @@ -93,13 +93,16 @@ data = { channels: '2', sampling: '44100' }, - mapping: [ - '-codec:a', 'aac', - '-b:a', '64k', - '-bsf:a', 'aac_adtstoasc', - '-shortest', - '-af', 'aresample=osr=44100:ocl=2' - ] + mapping: { + global: [], + local: [ + '-codec:a', 'aac', + '-b:a', '64k', + '-bsf:a', 'aac_adtstoasc', + '-shortest', + '-af', 'aresample=osr=44100:ocl=2' + ] + } }, decoder: null, }, @@ -110,12 +113,15 @@ data = { coder: 'copy', codec: 'h264', settings: {}, - mapping: [ - '-codec:v', 'copy', - '-vsync 0', - '-copyts', - '-start_at_zero', - ] + mapping: { + global: [], + local: [ + '-codec:v', 'copy', + '-vsync 0', + '-copyts', + '-start_at_zero', + ] + } }, decoder: null, }, @@ -133,19 +139,22 @@ data = { profile: 'auto', tune: 'zerolatency', }, - mapping: [ - '-codec:v', 'libx264', - '-preset:v', 'ultrafast', - '-b:v', '4096k', - '-maxrate', '4096k', - '-bufsize', '4096k', - '-r', '25', - '-g', '50', - '-pix_fmt', 'yuv420p', - '-vsync', '1', - '-profile:v', 'high', - '-tune:v', 'zerolatency', - ] + mapping: { + global: [], + local: [ + '-codec:v', 'libx264', + '-preset:v', 'ultrafast', + '-b:v', '4096k', + '-maxrate', '4096k', + '-bufsize', '4096k', + '-r', '25', + '-g', '50', + '-pix_fmt', 'yuv420p', + '-vsync', '1', + '-profile:v', 'high', + '-tune:v', 'zerolatency', + ] + } }, decoder: { coder: 'h264_cuvid', @@ -483,11 +492,7 @@ const mergeEgressMetadata = (metadata, base) => { const validateProfile = (sources, profile) => { let validVideo = false; - if (!('video' in profile)) { - profile.video = initProfile({}); - } else { - profile.video = initProfile(profile.video); - } + profile = initProfile(profile); if (profile.video.source !== -1 && profile.video.source < sources.length) { const source = sources[profile.video.source]; @@ -505,12 +510,6 @@ const validateProfile = (sources, profile) => { let validAudio = false; - if (!('audio' in profile)) { - profile.audio = initProfile({}); - } else { - profile.audio = initProfile(profile.audio); - } - if (profile.audio.source !== -1 && profile.audio.source < sources.length) { const source = sources[profile.audio.source]; @@ -547,6 +546,7 @@ const validateProfile = (sources, profile) => { const createInputsOutputs = (sources, profiles) => { const source2inputMap = new Map(); + let global = []; const inputs = []; const outputs = []; @@ -559,11 +559,13 @@ const createInputsOutputs = (sources, profiles) => { let index = -1; + global = [...global, ...profile.video.decoder.mapping.global]; + const source = sources[profile.video.source]; const stream = source.streams[profile.video.stream]; const input = source.inputs[stream.index]; - input.options = [...profile.video.decoder.mapping, ...input.options]; + input.options = [...profile.video.decoder.mapping.local, ...input.options]; const id = profile.video.source + ':' + stream.index; @@ -576,14 +578,18 @@ const createInputsOutputs = (sources, profiles) => { index = source2inputMap.get(id); - const options = ['-map', index + ':' + stream.stream, ...profile.video.encoder.mapping]; + global = [...global, ...profile.video.encoder.mapping.global]; + + const options = ['-map', index + ':' + stream.stream, ...profile.video.encoder.mapping.local]; if (profile.audio.encoder.coder !== 'none' && profile.audio.source !== -1 && profile.audio.stream !== -1) { + global = [...global, ...profile.audio.decoder.mapping.global]; + const source = sources[profile.audio.source]; const stream = source.streams[profile.audio.stream]; const input = source.inputs[stream.index]; - input.options = [...profile.audio.decoder.mapping, ...input.options]; + input.options = [...profile.audio.decoder.mapping.local, ...input.options]; const id = profile.audio.source + ':' + stream.index; @@ -594,7 +600,9 @@ const createInputsOutputs = (sources, profiles) => { index = source2inputMap.get(id); - options.push('-map', index + ':' + stream.stream, ...profile.audio.encoder.mapping); + global = [...global, ...profile.audio.encoder.mapping.global]; + + options.push('-map', index + ':' + stream.stream, ...profile.audio.encoder.mapping.local); } else { options.push('-an'); } @@ -605,7 +613,16 @@ const createInputsOutputs = (sources, profiles) => { }); } - return [inputs, outputs]; + // https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array + const uniqBy = (a, key) => { + return [...new Map(a.map((x) => [key(x), x])).values()]; + }; + + // global is an array of arrays. Here we remove duplicates and flatten it. + global = uniqBy(global, (x) => JSON.stringify(x.sort())); + global = global.reduce((acc, val) => acc.concat(val), []); + + return [global, inputs, outputs]; }; const createOutputStreams = (sources, profiles) => { @@ -717,17 +734,44 @@ const initProfile = (initialProfile) => { profile.video.encoder = { coder: 'none', settings: {}, - mapping: [], + mapping: {}, ...profile.video.encoder, }; + // mapping used to be an array for input/output specific options + if (Array.isArray(profile.video.encoder.mapping)) { + profile.video.encoder.mapping = { + global: [], + local: profile.video.encoder.mapping, + }; + } else { + profile.video.encoder.mapping = { + global: [], + local: [], + ...profile.video.encoder.mapping, + }; + } + profile.video.decoder = { coder: 'default', settings: {}, - mapping: [], + mapping: {}, ...profile.video.decoder, }; + if (Array.isArray(profile.video.decoder.mapping)) { + profile.video.decoder.mapping = { + global: [], + local: profile.video.decoder.mapping, + }; + } else { + profile.video.decoder.mapping = { + global: [], + local: [], + ...profile.video.decoder.mapping, + }; + } + profile.audio = { source: -1, stream: -1, @@ -739,17 +783,43 @@ const initProfile = (initialProfile) => { profile.audio.encoder = { coder: 'none', settings: {}, - mapping: [], + mapping: {}, ...profile.audio.encoder, }; + if (Array.isArray(profile.audio.encoder.mapping)) { + profile.audio.encoder.mapping = { + global: [], + local: profile.audio.encoder.mapping, + }; + } else { + profile.audio.encoder.mapping = { + global: [], + local: [], + ...profile.audio.encoder.mapping, + }; + } + profile.audio.decoder = { coder: 'default', settings: {}, - mapping: [], + mapping: {}, ...profile.audio.decoder, }; + if (Array.isArray(profile.audio.decoder.mapping)) { + profile.audio.decoder.mapping = { + global: [], + local: profile.audio.decoder.mapping, + }; + } else { + profile.audio.decoder.mapping = { + global: [], + local: [], + ...profile.audio.decoder.mapping, + }; + } + profile.custom = { selected: profile.audio.source === 1, stream: profile.audio.source === 1 ? -2 : profile.audio.stream, diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index e6127d1..390ac8f 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -1427,7 +1427,7 @@ class Restreamer { } // Upsert the ingest process - async UpsertIngest(channelid, inputs, outputs, control) { + async UpsertIngest(channelid, global, inputs, outputs, control) { const channel = this.GetChannel(channelid); if (channel === null) { return [null, { message: 'Unknown channel ID' }]; @@ -1439,7 +1439,7 @@ class Restreamer { reference: channel.channelid, input: [], output: [], - options: ['-err_detect', 'ignore_err'], + options: ['-err_detect', 'ignore_err', ...global], autostart: control.process.autostart, reconnect: control.process.reconnect, reconnect_delay_seconds: parseInt(control.process.delay), @@ -2195,7 +2195,7 @@ class Restreamer { } // Update an egress process - async UpdateEgress(channelid, id, inputs, outputs, control) { + async UpdateEgress(channelid, id, global, inputs, outputs, control) { const channel = this.GetChannel(channelid); if (channel === null) { return null; @@ -2226,7 +2226,7 @@ class Restreamer { }, ], output: [], - options: ['-err_detect', 'ignore_err'], + options: ['-err_detect', 'ignore_err', ...global], autostart: control.process.autostart, reconnect: control.process.reconnect, reconnect_delay_seconds: parseInt(control.process.delay), @@ -2252,7 +2252,7 @@ class Restreamer { } // Create an egress process - async CreateEgress(channelid, service, inputs, outputs, control) { + async CreateEgress(channelid, service, global, inputs, outputs, control) { const channel = this.GetChannel(channelid); if (channel === null) { return ['', { message: 'Unknown channel ID' }]; @@ -2269,7 +2269,7 @@ class Restreamer { this.SetChannelEgress(channelid, egress.id, egress); - const [, err] = await this.UpdateEgress(channelid, egress.id, inputs, outputs, control); + const [, err] = await this.UpdateEgress(channelid, egress.id, global, inputs, outputs, control); if (err !== null) { this.DeleteChannelEgress(channelid, egress.id); } diff --git a/src/views/Edit/Wizard/index.js b/src/views/Edit/Wizard/index.js index 76563fd..1cd580b 100644 --- a/src/views/Edit/Wizard/index.js +++ b/src/views/Edit/Wizard/index.js @@ -164,7 +164,7 @@ export default function Wizard(props) { const profiles = data.profiles; const control = data.control; - const [inputs, outputs] = M.createInputsOutputs(sources, profiles); + const [global, inputs, outputs] = M.createInputsOutputs(sources, profiles); if (inputs.length === 0 || outputs.length === 0) { notify.Dispatch('error', 'save:ingest', i18n._(t`The input profile is not complete. Please define a video and audio source.`)); @@ -173,7 +173,7 @@ export default function Wizard(props) { data.streams = M.createOutputStreams(sources, profiles); - const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control); + const [, err] = await props.restreamer.UpsertIngest(_channelid, global, inputs, outputs, control); if (err !== null) { notify.Dispatch('error', 'save:ingest', err.message); return false; diff --git a/src/views/Edit/index.js b/src/views/Edit/index.js index a390cd2..430a3c0 100644 --- a/src/views/Edit/index.js +++ b/src/views/Edit/index.js @@ -284,7 +284,7 @@ export default function Edit(props) { const profiles = $data.profiles; const control = $data.control; - const [inputs, outputs] = M.createInputsOutputs(sources, profiles); + const [global, inputs, outputs] = M.createInputsOutputs(sources, profiles); if (inputs.length === 0 || outputs.length === 0) { notify.Dispatch('error', 'save:ingest', i18n._(t`The input profile is not complete. Please define a video and audio source.`)); @@ -292,7 +292,7 @@ export default function Edit(props) { } // Create/update the ingest - const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control); + const [, err] = await props.restreamer.UpsertIngest(_channelid, global, inputs, outputs, control); if (err !== null) { notify.Dispatch('error', 'save:ingest', i18n._(t`Failed to update ingest process (${err.message})`)); return false; diff --git a/src/views/Publication/Add.js b/src/views/Publication/Add.js index 0887aaa..890cd20 100644 --- a/src/views/Publication/Add.js +++ b/src/views/Publication/Add.js @@ -176,9 +176,9 @@ export default function Add(props) { const handleServiceDone = async () => { setSaving(true); - const [inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs); + const [global, inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs); - const [id, err] = await props.restreamer.CreateEgress(_channelid, $service, inputs, outputs, $settings.control); + const [id, err] = await props.restreamer.CreateEgress(_channelid, $service, global, inputs, outputs, $settings.control); if (err !== null) { setSaving(false); notify.Dispatch('error', 'save:egress:' + $service, i18n._(t`Failed to create publication service (${err.message})`)); @@ -380,20 +380,12 @@ export default function Add(props) { - + - - {service.name} + + + {service.name} + v{service.version} @@ -435,20 +427,12 @@ export default function Add(props) { - + - - {service.name} + + + {service.name} + v{service.version} @@ -480,20 +464,12 @@ export default function Add(props) { - + - - {service.name} + + + {service.name} + v{service.version} diff --git a/src/views/Publication/Edit.js b/src/views/Publication/Edit.js index 31c4269..9c01c10 100644 --- a/src/views/Publication/Edit.js +++ b/src/views/Publication/Edit.js @@ -218,9 +218,9 @@ export default function Edit(props) { const handleServiceDone = async () => { setSaving(true); - const [inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs); + const [global, inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs); - const [, err] = await props.restreamer.UpdateEgress(_channelid, id, inputs, outputs, $settings.control); + const [, err] = await props.restreamer.UpdateEgress(_channelid, id, global, inputs, outputs, $settings.control); if (err !== null) { setSaving(false); notify.Dispatch('error', 'save:egress:' + _service, i18n._(t`Failed to store publication service (${err.message})`)); @@ -394,20 +394,12 @@ export default function Edit(props) { - + - - {$service.name} + + + {$service.name} + v{$service.version} @@ -443,20 +435,12 @@ export default function Edit(props) { - + - - {$service.name} + + + {$service.name} + v{$service.version} @@ -516,20 +500,12 @@ export default function Edit(props) { - + - - {$service.name} + + + {$service.name} + v{$service.version} diff --git a/src/views/Publication/helper.js b/src/views/Publication/helper.js index 0a2f1f0..f59826b 100644 --- a/src/views/Publication/helper.js +++ b/src/views/Publication/helper.js @@ -35,7 +35,7 @@ export function createSourcesFromStreams(streams) { * @returns */ export function createInputsOutputs(sources, profiles, outputs) { - const [inpts, outpts] = M.createInputsOutputs(sources, profiles); + const [global, inpts, outpts] = M.createInputsOutputs(sources, profiles); const out = []; @@ -58,7 +58,7 @@ export function createInputsOutputs(sources, profiles, outputs) { out.push(o); } - return [inpts, out]; + return [global, inpts, out]; } /** * validateRequirements validates the requirements object the each