bump version, merge graf's changes

This commit is contained in:
matty 2024-03-08 18:28:45 -05:00
parent dd700ffeaa
commit c50d491ed4
2 changed files with 123 additions and 204 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "peertube-plugin-nctv-nvenc-transcode", "name": "peertube-plugin-nctv-nvenc-transcode",
"version": "1.0.3", "version": "1.0.4",
"license": "MIT", "license": "MIT",
"description": "Plugin that adds transcode profiles which use NVIDIA NVENC for hardware acceleration", "description": "Plugin that adds transcode profiles which use NVIDIA NVENC for hardware acceleration",
"engine": { "engine": {

View File

@ -1,177 +1,135 @@
import { PluginSettingsManager, PluginTranscodingManager } from "@peertube/peertube-types" "use strict";
import { EncoderOptions, EncoderOptionsBuilderParams, RegisterServerOptions, VideoResolution } from "@peertube/peertube-types" Object.defineProperty(exports, "__esModule", { value: true });
import { Logger } from 'winston' exports.unregister = exports.register = void 0;
let logger;
let logger : Logger let transcodingManager;
let transcodingManager : PluginTranscodingManager const DEFAULT_HARDWARE_DECODE = false;
const DEFAULT_VOD_QUALITY = "p4";
const DEFAULT_HARDWARE_DECODE : boolean = false const DEFAULT_LIVE_QUALITY = "ull";
const DEFAULT_VOD_QUALITY : number = 15 const DEFAULT_BITRATES = new Map([
const DEFAULT_LIVE_QUALITY : number = 7 [0, 64 * 1000],
const DEFAULT_BITRATES : Map<VideoResolution, number> = new Map([ [144, 320 * 1000],
[VideoResolution.H_NOVIDEO, 64 * 1000], [360, 780 * 1000],
[VideoResolution.H_144P, 320 * 1000], [480, 1500 * 1000],
[VideoResolution.H_360P, 780 * 1000], [720, 2800 * 1000],
[VideoResolution.H_480P, 1500 * 1000], [1080, 5200 * 1000],
[VideoResolution.H_720P, 2800 * 1000], [1440, 10000 * 1000],
[VideoResolution.H_1080P, 5200 * 1000], [2160, 22000 * 1000]
[VideoResolution.H_1440P, 10_000 * 1000], ]);
[VideoResolution.H_4K, 22_000 * 1000] let pluginSettings = {
])
interface PluginSettings {
hardwareDecode : boolean
vodQuality: number
liveQuality: number
baseBitrate: Map<VideoResolution, number>
}
let pluginSettings : PluginSettings = {
hardwareDecode: DEFAULT_HARDWARE_DECODE, hardwareDecode: DEFAULT_HARDWARE_DECODE,
vodQuality: DEFAULT_VOD_QUALITY, vodQuality: DEFAULT_VOD_QUALITY,
liveQuality: DEFAULT_LIVE_QUALITY, liveQuality: DEFAULT_LIVE_QUALITY,
baseBitrate: new Map(DEFAULT_BITRATES) baseBitrate: new Map(DEFAULT_BITRATES)
} };
let latestStreamNum = 9999;
let latestStreamNum = 9999 async function register({ settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting }) {
logger = peertubeHelpers.logger;
export async function register({settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting} :RegisterServerOptions) { transcodingManager = transcode;
logger = peertubeHelpers.logger
transcodingManager = transcode
logger.info("Registering peertube-plugin-nctv-nvenc-transcode"); logger.info("Registering peertube-plugin-nctv-nvenc-transcode");
const encoder = 'h264_nvenc';
const encoder = 'h264_nvenc' const profileName = 'nctv-nvenc';
const profileName = 'nvenc' transcodingManager.addVODProfile(encoder, profileName, vodBuilder);
transcodingManager.addVODEncoderPriority('video', encoder, 1000);
// Add trasncoding profiles transcodingManager.addLiveProfile(encoder, profileName, liveBuilder);
transcodingManager.addVODProfile(encoder, profileName, vodBuilder) transcodingManager.addLiveEncoderPriority('video', encoder, 1000);
transcodingManager.addVODEncoderPriority('video', encoder, 1000) await loadSettings(settingsManager);
transcodingManager.addLiveProfile(encoder, profileName, liveBuilder)
transcodingManager.addLiveEncoderPriority('video', encoder, 1000)
// Load existing settings and default to constants if not present
await loadSettings(settingsManager)
registerSetting({ registerSetting({
name: 'hardware-decode', name: 'hardware-decode',
label: 'Hardware decode', label: 'Hardware decode',
type: 'input-checkbox', type: 'input-checkbox',
descriptionHTML: 'Use hardware video decoder instead of software decoder. This will slightly improve performance but may cause some issues with some videos. If you encounter issues, disable this option and restart failed jobs.', descriptionHTML: 'Use hardware video decoder instead of software decoder. This will slightly improve performance but may cause some issues with some videos. If you encounter issues, disable this option and restart failed jobs.',
default: DEFAULT_HARDWARE_DECODE, default: DEFAULT_HARDWARE_DECODE,
private: false private: false
}) });
registerSetting({ registerSetting({
name: 'vod-quality', name: 'vod-quality',
label: 'VOD Quality', label: 'VOD Quality',
type: 'select', type: 'select',
options: [ options: [
{ label: 'fastest', value: '12' }, { label: 'fastest', value: 'p1' },
{ label: 'faster', value: '13' }, { label: 'faster', value: 'p2' },
{ label: 'fast', value: '14' }, { label: 'fast', value: 'p3' },
{ label: 'medium (default)', value: '15' }, { label: 'medium (default)', value: 'p4' },
{ label: 'slow', value: '16' }, { label: 'slow', value: 'p5' },
{ label: 'slower', value: '17' }, { label: 'slower', value: 'p6' },
{ label: 'slowest', value: '18' }, { label: 'slowest', value: 'p7' }
{ label: 'nctv', value: 'p7' }
], ],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. Slower speed mean better quality. Faster speed mean lower quality. This setting is hardware dependent, you may need to experiment to find the best value for your hardware.', descriptionHTML: 'This parameter controls the speed / quality tradeoff. Slower speed mean better quality. Faster speed mean lower quality. This setting is hardware dependent, you may need to experiment to find the best value for your hardware.',
default: DEFAULT_VOD_QUALITY.toString(), default: DEFAULT_VOD_QUALITY.toString(),
private: false private: false
}) });
registerSetting({ registerSetting({
name: 'live-quality', name: 'live-quality',
label: 'Live Quality', label: 'Live Quality',
type: 'select', type: 'select',
options: [ options: [
{ label: 'low latency (default)', value: '7' }, { label: 'low latency (default)', value: 'll' },
{ label: 'low latency high quality', value: '8' }, { label: 'low latency high quality', value: 'hq' },
{ label: 'low latency high performance', value: '9' } { label: 'low latency high performance', value: 'ull' }
], ],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.', descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.',
default: DEFAULT_LIVE_QUALITY.toString(), default: DEFAULT_LIVE_QUALITY.toString(),
private: false private: false
}) });
registerSetting({ registerSetting({
name: 'base-bitrate-description', name: 'base-bitrate-description',
label: 'Base bitrate', label: 'Base bitrate',
type: 'html', type: 'html',
html: '', html: '',
descriptionHTML: `The base bitrate for video in bits. We take the min bitrate between the bitrate setting and video bitrate.<br/>This is the bitrate used when the video is transcoded at 30 FPS. The bitrate will be scaled linearly between this value and the maximum bitrate when the video is transcoded at 60 FPS. Wrong values are replaced by default values.`, descriptionHTML: `The base bitrate for video in bits. We take the min bitrate between the bitrate setting and video bitrate.<br/>This is the bitrate used when the video is transcoded at 30 FPS. The bitrate will be scaled linearly between this value and the maximum bitrate when the video is transcoded at 60 FPS. Wrong values are replaced by default values.`,
private: true, private: true,
}) });
for (const [resolution, bitrate] of pluginSettings.baseBitrate) { for (const [resolution, bitrate] of pluginSettings.baseBitrate) {
logger.info("registering bitrate setting: "+ bitrate.toString()) logger.info("registering bitrate setting: " + bitrate.toString());
registerSetting({ registerSetting({
name: `base-bitrate-${resolution}`, name: `base-bitrate-${resolution}`,
label: `Base bitrate for ${printResolution(resolution)}`, label: `Base bitrate for ${printResolution(resolution)}`,
type: 'input', type: 'input',
default: DEFAULT_BITRATES.get(resolution)?.toString(), default: DEFAULT_BITRATES.get(resolution)?.toString(),
descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`, descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`,
private: false private: false
}) });
} }
settingsManager.onSettingsChange(async (settings) => { settingsManager.onSettingsChange(async (settings) => {
loadSettings(settingsManager) loadSettings(settingsManager);
}) });
} }
exports.register = register;
export async function unregister() { async function unregister() {
logger.info("Unregistering peertube-plugin-nctv-nvenc-transcode") logger.info("Unregistering peertube-plugin-hardware-encode");
transcodingManager.removeAllProfilesAndEncoderPriorities() transcodingManager.removeAllProfilesAndEncoderPriorities();
return true return true;
} }
exports.unregister = unregister;
async function loadSettings(settingsManager: PluginSettingsManager) { async function loadSettings(settingsManager) {
pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') == "true" pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') == "true";
pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality') as string) || DEFAULT_VOD_QUALITY pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality')) || DEFAULT_VOD_QUALITY;
pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality') as string) || DEFAULT_LIVE_QUALITY pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality')) || DEFAULT_LIVE_QUALITY;
for (const [resolution, bitrate] of DEFAULT_BITRATES) { for (const [resolution, bitrate] of DEFAULT_BITRATES) {
const key = `base-bitrate-${resolution}` const key = `base-bitrate-${resolution}`;
const storedValue = await settingsManager.getSetting(key) as string const storedValue = await settingsManager.getSetting(key);
pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate) pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate);
logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`) logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`);
} }
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`);
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`) logger.info(`VOD Quality: ${pluginSettings.vodQuality}`);
logger.info(`VOD Quality: ${pluginSettings.vodQuality}`) logger.info(`Live Quality: ${pluginSettings.liveQuality}`);
logger.info(`Live Quality: ${pluginSettings.liveQuality}`)
} }
function printResolution(resolution) {
function printResolution(resolution : VideoResolution) : string {
switch (resolution) { switch (resolution) {
case VideoResolution.H_NOVIDEO: return 'audio only' case 0: return 'audio only';
case VideoResolution.H_144P: case 144:
case VideoResolution.H_360P: case 360:
case VideoResolution.H_480P: case 480:
case VideoResolution.H_720P: case 720:
case VideoResolution.H_1080P: case 1080:
case VideoResolution.H_1440P: case 1440:
return `${resolution}p` return `${resolution}p`;
case VideoResolution.H_4K: return '4K' case 2160: return '4K';
default: return 'Unknown';
default: return 'Unknown'
} }
} }
function buildInitOptions() { function buildInitOptions() {
if (pluginSettings.hardwareDecode) { if (pluginSettings.hardwareDecode) {
return [ return [
@ -185,106 +143,67 @@ function buildInitOptions() {
]; ];
} }
} }
async function vodBuilder(params) {
async function vodBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> { const { resolution, fps, streamNum, inputBitrate } = params;
const { resolution, fps, streamNum, inputBitrate } = params const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}` let targetBitrate = getTargetBitrate(resolution, fps);
let targetBitrate = getTargetBitrate(resolution, fps) let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
if (targetBitrate > inputBitrate) { if (targetBitrate > inputBitrate) {
targetBitrate = inputBitrate targetBitrate = inputBitrate;
} }
logger.info(`Building encoder options, received ${JSON.stringify(params)}`);
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
if (shouldInitVaapi && streamNum != undefined) { if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum latestStreamNum = streamNum;
} }
// You can also return a promise let options = {
let options : EncoderOptions = {
scaleFilter: { scaleFilter: {
// software decode requires specifying pixel format for hardware filter and upload it to GPU
// name: pluginSettings.hardwareDecode ? 'scale_vaapi' : 'format=nv12,hwupload,scale_vaapi'
name: 'scale' name: 'scale'
}, },
inputOptions: shouldInitVaapi ? buildInitOptions() : [], inputOptions: shouldInitVaapi ? buildInitOptions() : [],
outputOptions: [ outputOptions: [
`-preset ${pluginSettings.vodQuality}`, `-preset ${pluginSettings.vodQuality}`,
`-b:v${streamSuffix} ${targetBitrate}`, `-b:v${streamSuffix} ${targetBitrate}`,
`-c:v${streamSuffix} h264_nvenc`, `-bufsize ${targetBitrate * 2}`
// `-bufsize ${targetBitrate * 2}`,
`-bf 4`,
`-cq 18`
] ]
} };
logger.info(`EncoderOptions: ${JSON.stringify(options)}`) logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
return options return options;
} }
async function liveBuilder(params) {
const { resolution, fps, streamNum, inputBitrate } = params;
async function liveBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> { const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
const { resolution, fps, streamNum, inputBitrate } = params let targetBitrate = getTargetBitrate(resolution, fps);
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}` let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
let targetBitrate = getTargetBitrate(resolution, fps)
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
if (targetBitrate > inputBitrate) { if (targetBitrate > inputBitrate) {
targetBitrate = inputBitrate targetBitrate = inputBitrate;
} }
logger.info(`Building encoder options, received ${JSON.stringify(params)}`);
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
if (shouldInitVaapi && streamNum != undefined) { if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum latestStreamNum = streamNum;
} }
// You can also return a promise
const options = { const options = {
scaleFilter: { scaleFilter: {
// name: pluginSettings.hardwareDecode ? 'scale_vaapi' : 'format=nv12,hwupload,scale_vaapi' name: 'scale'
name: 'scale' },
}, inputOptions: shouldInitVaapi ? buildInitOptions() : [],
inputOptions: shouldInitVaapi ? buildInitOptions() : [], outputOptions: [
outputOptions: [ `-tune ${pluginSettings.liveQuality}`,
`-preset ${pluginSettings.liveQuality}`, `-r:v${streamSuffix} ${fps}`,
// `-r:v${streamSuffix} ${fps}`, `-profile:v${streamSuffix} main`,
`-profile:v${streamSuffix} high`, // `-level:v${streamSuffix} `,
// `-g:v${streamSuffix} ${fps*2}`, `-g:v${streamSuffix} ${fps * 2}`,
`-c:v${streamSuffix} h264_nvenc`, `-b:v${streamSuffix} ${targetBitrate}`,
`-b:v${streamSuffix} ${targetBitrate}`, `-bufsize ${targetBitrate * 2}`
// `-bufsize ${targetBitrate * 2}`, ]
`-cq 18`, };
// `-async 1`, logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
`-bf 4` return options;
]
}
logger.info(`EncoderOptions: ${JSON.stringify(options)}`)
return options
} }
function getTargetBitrate(resolution, fps) {
/** const baseBitrate = pluginSettings.baseBitrate.get(resolution) || 0;
* Calculate the target bitrate based on video resolution and FPS. const maxBitrate = baseBitrate * 1.4;
* const maxBitrateDifference = maxBitrate - baseBitrate;
* The calculation is based on two values: const maxFpsDifference = 60 - 30;
* Bitrate at VideoTranscodingFPS.AVERAGE is always the same as return Math.floor(baseBitrate + (fps - 30) * (maxBitrateDifference / maxFpsDifference));
* getBaseBitrate(). Bitrate at VideoTranscodingFPS.MAX is always }
* getBaseBitrate() * 1.4. All other values are calculated linearly //# sourceMappingURL=main.js.map
* between these two points.
*/
function getTargetBitrate (resolution : VideoResolution, fps : number) : number {
const baseBitrate = pluginSettings.baseBitrate.get(resolution) || 0
// The maximum bitrate, used when fps === VideoTranscodingFPS.MAX
// Based on numbers from Youtube, 60 fps bitrate divided by 30 fps bitrate:
// 720p: 2600 / 1750 = 1.49
// 1080p: 4400 / 3300 = 1.33
const maxBitrate = baseBitrate * 1.4
const maxBitrateDifference = maxBitrate - baseBitrate
const maxFpsDifference = 60 - 30
// For 1080p video with default settings, this results in the following formula:
// 3300 + (x - 30) * (1320/30)
// Example outputs:
// 1080p10: 2420 kbps, 1080p30: 3300 kbps, 1080p60: 4620 kbps
// 720p10: 1283 kbps, 720p30: 1750 kbps, 720p60: 2450 kbps
return Math.floor(baseBitrate + (fps - 30) * (maxBitrateDifference / maxFpsDifference))
}