Compare commits
61 Commits
606d1d6377
...
cq-config
| Author | SHA1 | Date | |
|---|---|---|---|
| 7314c18ca4 | |||
| 7761d12bc8 | |||
| d09a3d43b2 | |||
| 5e809bc053 | |||
| 2168bc72d0 | |||
| 0b066aaa3a | |||
| a91b8b2a9a | |||
| d2bacfdb9c | |||
| 27ce7b5659 | |||
| 17eafa8872 | |||
| d82f43cea5 | |||
| 1e39ac631d | |||
| 3339d58a93 | |||
| fcc8ecb3ee | |||
| 5a20d190b1 | |||
| 2ef8b2deec | |||
| 6a150618b9 | |||
| 6640ba5c25 | |||
| 6a47bd9e77 | |||
| bdcdb2e8e5 | |||
| 356caf37fd | |||
| 8483a37bae | |||
| 940ca6d751 | |||
| 9848e0de9f | |||
| de17b72171 | |||
| c92e017680 | |||
| 4b8b0e4888 | |||
| b24c0e4ef7 | |||
| ae2c910f20 | |||
| ee56666c33 | |||
| 35ffb74383 | |||
| 84cc3a58cd | |||
| 4c9ee97fb9 | |||
| 8fda9f4435 | |||
| 72d52d1580 | |||
| 1bd02a4902 | |||
| 21b28b35f2 | |||
| 92b9e78076 | |||
| dff32f1466 | |||
| 1e7cc6961d | |||
| 70a974335f | |||
| 38936ff6ef | |||
| 506c47f3db | |||
| e6e4097b5f | |||
| 39d08a8592 | |||
| 2a087cc3c0 | |||
| 7d6779ecd6 | |||
| 3981e65bad | |||
| dba92b3043 | |||
| 2bab551803 | |||
| 29d44fc31a | |||
| 9c7a2de2e9 | |||
| acaf777612 | |||
| aa9d12c3e9 | |||
| c50d491ed4 | |||
| dd700ffeaa | |||
| 5c954b4c0d | |||
| 2d0694dc8c | |||
| 33da091ba7 | |||
| b77a9d6866 | |||
| 4a96d3c212 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
# dist/
|
||||
354
dist/main.js
vendored
Normal file
354
dist/main.js
vendored
Normal file
@ -0,0 +1,354 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.unregister = exports.register = void 0;
|
||||
let logger;
|
||||
let transcodingManager;
|
||||
const DEFAULT_HARDWARE_DECODE = false;
|
||||
const DEFAULT_VOD_QUALITY = "p7";
|
||||
const DEFAULT_HEVC_PROFILE = "main10";
|
||||
const DEFAULT_LIVE_QUALITY = "hq";
|
||||
const DEFAULT_HEVC_ENABLED = false;
|
||||
const DEFAULT_CQ_H264 = 26;
|
||||
const DEFAULT_CQ_HEVC = 28;
|
||||
const DEFAULT_BITRATES = new Map([
|
||||
[0, 64 * 1000],
|
||||
[144, 320 * 1000],
|
||||
[360, 780 * 1000],
|
||||
[480, 1500 * 1000],
|
||||
[720, 2800 * 1000],
|
||||
[1080, 5200 * 1000],
|
||||
[1440, 10000 * 1000],
|
||||
[2160, 22000 * 1000]
|
||||
]);
|
||||
let pluginSettings = {
|
||||
hardwareDecode: DEFAULT_HARDWARE_DECODE,
|
||||
vodQuality: DEFAULT_VOD_QUALITY,
|
||||
liveQuality: DEFAULT_LIVE_QUALITY,
|
||||
hevcEnabled: DEFAULT_HEVC_ENABLED,
|
||||
hevcProfile: DEFAULT_HEVC_PROFILE,
|
||||
cqH264: DEFAULT_CQ_H264,
|
||||
cqHEVC: DEFAULT_CQ_HEVC,
|
||||
baseBitrate: new Map(DEFAULT_BITRATES)
|
||||
};
|
||||
let latestStreamNum = 9999;
|
||||
async function register({ settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting }) {
|
||||
logger = peertubeHelpers.logger;
|
||||
transcodingManager = transcode;
|
||||
logger.info("Registering peertube-plugin-nctv-hardware-encode");
|
||||
const encoder = 'h264_nvenc';
|
||||
const hevc = 'hevc_nvenc';
|
||||
const profileName = 'nctv-nvenc';
|
||||
const hevcProfile = 'nctv-hevc';
|
||||
transcodingManager.addVODProfile(encoder, profileName, vodBuilder);
|
||||
transcodingManager.addVODEncoderPriority('video', encoder, 1000);
|
||||
|
||||
transcodingManager.addLiveProfile(encoder, profileName, liveBuilder);
|
||||
transcodingManager.addLiveEncoderPriority('video', encoder, 1000);
|
||||
|
||||
transcodingManager.addVODProfile(hevc, hevcProfile, hevcVODBuilder);
|
||||
transcodingManager.addVODEncoderPriority('video', hevc, 900);
|
||||
|
||||
transcodingManager.addLiveProfile(hevc, hevcProfile, hevcLiveBuilder);
|
||||
transcodingManager.addLiveEncoderPriority('video', hevc, 900);
|
||||
await loadSettings(settingsManager);
|
||||
registerSetting({
|
||||
name: 'hardware-decode',
|
||||
label: 'Hardware decode',
|
||||
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.',
|
||||
default: DEFAULT_HARDWARE_DECODE,
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'hevc-enabled',
|
||||
label: 'Enable H265 NVENC',
|
||||
type: 'input-checkbox',
|
||||
descriptionHTML: 'Enables H265 NVENC (experimental)',
|
||||
default: DEFAULT_HEVC_ENABLED,
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'cq-h264',
|
||||
label: 'CQ Value for H264_nvenc',
|
||||
type: 'input',
|
||||
descriptionHTML: 'Sets the -cq value for h264_nvenc encoder. Valid values are between 0 and 51 (lossess and AIDS, respectively)',
|
||||
default: DEFAULT_CQ_H264,
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'cq-hevc',
|
||||
label: 'CQ Value for hevc_nvenc',
|
||||
type: 'input',
|
||||
descriptionHTML: 'Sets the -cq value for hevc_nvenc encoder. Valid values are between 0 and 51 (lossess and AIDS, respectively)',
|
||||
default: DEFAULT_CQ_HEVC,
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'vod-quality',
|
||||
label: 'VOD Quality',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'fastest', value: 'p1' },
|
||||
{ label: 'faster', value: 'p2' },
|
||||
{ label: 'fast', value: 'p3' },
|
||||
{ label: 'medium (default)', value: 'p4' },
|
||||
{ label: 'slow', value: 'p5' },
|
||||
{ label: 'slower', value: 'p6' },
|
||||
{ label: 'slowest', 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.',
|
||||
default: DEFAULT_VOD_QUALITY.toString(),
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'hevc-profile',
|
||||
label: 'HEVC Profile',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'main', value: 'main' },
|
||||
{ label: 'main10 (default)', value: 'main10' },
|
||||
{ label: 'rext', value: 'rext' }
|
||||
],
|
||||
descriptionHTML: 'Set the HEVC profile',
|
||||
default: DEFAULT_HEVC_PROFILE.toString(),
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'live-quality',
|
||||
label: 'Live Quality',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'low latency', value: 'll' },
|
||||
{ label: 'high quality (default)', value: 'hq' },
|
||||
{ label: 'low latency high performance', value: 'ull' }
|
||||
],
|
||||
descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.',
|
||||
default: DEFAULT_LIVE_QUALITY.toString(),
|
||||
private: false
|
||||
});
|
||||
registerSetting({
|
||||
name: 'base-bitrate-description',
|
||||
label: 'Base bitrate',
|
||||
type: '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.`,
|
||||
private: true,
|
||||
});
|
||||
for (const [resolution, bitrate] of pluginSettings.baseBitrate) {
|
||||
logger.info("registering bitrate setting: " + bitrate.toString());
|
||||
registerSetting({
|
||||
name: `base-bitrate-${resolution}`,
|
||||
label: `Base bitrate for ${printResolution(resolution)}`,
|
||||
type: 'input',
|
||||
default: DEFAULT_BITRATES.get(resolution)?.toString(),
|
||||
descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`,
|
||||
private: false
|
||||
});
|
||||
}
|
||||
settingsManager.onSettingsChange(async (settings) => {
|
||||
loadSettings(settingsManager);
|
||||
});
|
||||
}
|
||||
exports.register = register;
|
||||
async function unregister() {
|
||||
logger.info("Unregistering peertube-plugin-nctv-hardware-encode");
|
||||
transcodingManager.removeAllProfilesAndEncoderPriorities();
|
||||
return true;
|
||||
}
|
||||
exports.unregister = unregister;
|
||||
async function loadSettings(settingsManager) {
|
||||
pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') || DEFAULT_HARDWARE_DECODE;
|
||||
pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality')) || DEFAULT_VOD_QUALITY;
|
||||
pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality')) || DEFAULT_LIVE_QUALITY;
|
||||
pluginSettings.hevcProfile = parseInt(await settingsManager.getSetting('hevc-profile')) || DEFAULT_HEVC_PROFILE;
|
||||
pluginSettings.hevcEnabled = await settingsManager.getSetting('hevc-enabled') || DEFAULT_HEVC_ENABLED;
|
||||
pluginSettings.cqH264 = parseInt(await settingsManager.getSetting('cq-h264')) || DEFAULT_CQ_H264;
|
||||
pluginSettings.cqHEVC = parseInt(await settingsManager.getSetting('cq-hevc')) || DEFAULT_CQ_HEVC;
|
||||
|
||||
for (const [resolution, bitrate] of DEFAULT_BITRATES) {
|
||||
const key = `base-bitrate-${resolution}`;
|
||||
const storedValue = await settingsManager.getSetting(key);
|
||||
pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate);
|
||||
logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`);
|
||||
}
|
||||
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`);
|
||||
logger.info(`VOD Quality: ${pluginSettings.vodQuality}`);
|
||||
logger.info(`Live Quality: ${pluginSettings.liveQuality}`);
|
||||
logger.info(`HEVC enabled: ${pluginSettings.hevcEnabled}`);
|
||||
logger.info(`HEVC profile: ${pluginSettings.hevcProfile}`);
|
||||
}
|
||||
function printResolution(resolution) {
|
||||
switch (resolution) {
|
||||
case 0: return 'audio only';
|
||||
case 144:
|
||||
case 360:
|
||||
case 480:
|
||||
case 720:
|
||||
case 1080:
|
||||
case 1440:
|
||||
return `${resolution}p`;
|
||||
case 2160: return '4K';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
}
|
||||
function buildInitOptions() {
|
||||
if (pluginSettings.hardwareDecode) {
|
||||
return [
|
||||
'-hwaccel cuda',
|
||||
'-hwaccel_output_format cuda'
|
||||
];
|
||||
}
|
||||
else {
|
||||
return [
|
||||
'-hwaccel cuda'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
//H264_NVENC VOD BUILDER
|
||||
|
||||
async function vodBuilder(params) {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params;
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
|
||||
let targetBitrate = getTargetBitrate(resolution, fps);
|
||||
// let targetBitrate = inputBitrate;
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate;
|
||||
}
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}, HEVC: ${pluginSettings.hevcEnabled}`);
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum;
|
||||
}
|
||||
let options = {
|
||||
scaleFilter: {
|
||||
name: 'scale'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-preset ${pluginSettings.vodQuality}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-bufsize ${targetBitrate * 2}`,
|
||||
`-profile:v${streamSuffix} high`,
|
||||
`-cq ${pluginSettings.cqH264}`,
|
||||
`-bf 4`
|
||||
]
|
||||
};
|
||||
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
|
||||
return options;
|
||||
}
|
||||
|
||||
//HEVC VOD builder
|
||||
async function hevcVODBuilder(params) {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params;
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
|
||||
let targetBitrate = getTargetBitrate(resolution, fps);
|
||||
// let targetBitrate = inputBitrate;
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate;
|
||||
}
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}, HEVC: ${pluginSettings.hevcEnabled}`);
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum;
|
||||
}
|
||||
let options = {
|
||||
scaleFilter: {
|
||||
name: 'scale'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-preset ${pluginSettings.vodQuality}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-bufsize ${targetBitrate * 2}`,
|
||||
`-profile:v${streamSuffix} ${pluginSettings.hevcProfile}`,
|
||||
`-cq ${pluginSettings.cqHEVC}`
|
||||
]
|
||||
};
|
||||
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
|
||||
return options;
|
||||
|
||||
}
|
||||
|
||||
//HEVC Live builder
|
||||
|
||||
async function hevcLiveBuilder(params) {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params;
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
|
||||
let targetBitrate = getTargetBitrate(resolution, fps);
|
||||
// let targetBitrate = inputBitrate;
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate;
|
||||
}
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}, HEVC: ${pluginSettings.hevcEnabled}`);
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum;
|
||||
}
|
||||
|
||||
let options = {
|
||||
scaleFilter: {
|
||||
name: 'scale'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-tune ${pluginSettings.liveQuality}`,
|
||||
`-r:v${streamSuffix} ${fps}`,
|
||||
`-profile:v${streamSuffix} ${pluginSettings.hevcProfile}`,
|
||||
`-g:v${streamSuffix} ${fps * 2}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-cq ${pluginSettings.cqHEVC}`,
|
||||
`-bufsize ${targetBitrate * 2}`
|
||||
]
|
||||
};
|
||||
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
|
||||
return options;
|
||||
}
|
||||
|
||||
//H264 Live builder
|
||||
|
||||
async function liveBuilder(params) {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params;
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
|
||||
let targetBitrate = getTargetBitrate(resolution, fps);
|
||||
// let targetBitrate = inputBitrate;
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate;
|
||||
}
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}, HEVC: ${pluginSettings.hevcEnabled}`);
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum;
|
||||
}
|
||||
|
||||
let options = {
|
||||
scaleFilter: {
|
||||
name: 'scale'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-tune ${pluginSettings.liveQuality}`,
|
||||
`-r:v${streamSuffix} ${fps}`,
|
||||
`-profile:v${streamSuffix} high`,
|
||||
`-cq ${pluginSettings.cqH264}`,
|
||||
`-g:v${streamSuffix} ${fps * 2}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-bufsize ${targetBitrate * 2}`,
|
||||
`-bf 4`
|
||||
]
|
||||
};
|
||||
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
|
||||
return options;
|
||||
}
|
||||
function getTargetBitrate(resolution, fps) {
|
||||
const baseBitrate = pluginSettings.baseBitrate.get(resolution) || 0;
|
||||
const maxBitrate = baseBitrate * 1.4;
|
||||
const maxBitrateDifference = maxBitrate - baseBitrate;
|
||||
const maxFpsDifference = 60 - 30;
|
||||
return Math.floor(baseBitrate + (fps - 30) * (maxBitrateDifference / maxFpsDifference));
|
||||
}
|
||||
//# sourceMappingURL=main.js.map
|
||||
1
dist/main.js.map
vendored
Normal file
1
dist/main.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6603
package-lock.json
generated
6603
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "peertube-plugin-nctv-nvenc-transcode",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.1",
|
||||
"license": "MIT",
|
||||
"description": "Plugin that adds transcode profiles which use NVIDIA NVENC for hardware acceleration",
|
||||
"engine": {
|
||||
@ -11,7 +11,7 @@
|
||||
"plugin"
|
||||
],
|
||||
"homepage": "https://gitea.nicecrew.digital/matty/peertube-plugin-nctv-nvenc-transcode",
|
||||
"author": "ryanho",
|
||||
"author": "matty",
|
||||
"bugs": "https://gitea.nicecrew.digital/matty/peertube-plugin-nctv-nvenc-transcode/issues",
|
||||
"library": "./dist/main.js",
|
||||
"files": [
|
||||
@ -30,6 +30,6 @@
|
||||
"devDependencies": {
|
||||
"@peertube/peertube-types": "^5.1.0",
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
"typescript": "^5.1.6"
|
||||
"typescript": "^5.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
283
src/main.ts
283
src/main.ts
@ -1,283 +0,0 @@
|
||||
import { PluginSettingsManager, PluginTranscodingManager } from "@peertube/peertube-types"
|
||||
import { EncoderOptions, EncoderOptionsBuilderParams, RegisterServerOptions, VideoResolution } from "@peertube/peertube-types"
|
||||
import { Logger } from 'winston'
|
||||
|
||||
let logger : Logger
|
||||
let transcodingManager : PluginTranscodingManager
|
||||
|
||||
const DEFAULT_HARDWARE_DECODE : boolean = false
|
||||
const DEFAULT_VOD_QUALITY : number = 15
|
||||
const DEFAULT_LIVE_QUALITY : number = 7
|
||||
const DEFAULT_BITRATES : Map<VideoResolution, number> = new Map([
|
||||
[VideoResolution.H_NOVIDEO, 64 * 1000],
|
||||
[VideoResolution.H_144P, 320 * 1000],
|
||||
[VideoResolution.H_360P, 780 * 1000],
|
||||
[VideoResolution.H_480P, 1500 * 1000],
|
||||
[VideoResolution.H_720P, 2800 * 1000],
|
||||
[VideoResolution.H_1080P, 5200 * 1000],
|
||||
[VideoResolution.H_1440P, 10_000 * 1000],
|
||||
[VideoResolution.H_4K, 22_000 * 1000]
|
||||
])
|
||||
|
||||
interface PluginSettings {
|
||||
hardwareDecode : boolean
|
||||
vodQuality: number
|
||||
liveQuality: number
|
||||
baseBitrate: Map<VideoResolution, number>
|
||||
}
|
||||
let pluginSettings : PluginSettings = {
|
||||
hardwareDecode: DEFAULT_HARDWARE_DECODE,
|
||||
vodQuality: DEFAULT_VOD_QUALITY,
|
||||
liveQuality: DEFAULT_LIVE_QUALITY,
|
||||
baseBitrate: new Map(DEFAULT_BITRATES)
|
||||
}
|
||||
|
||||
let latestStreamNum = 9999
|
||||
|
||||
export async function register({settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting} :RegisterServerOptions) {
|
||||
logger = peertubeHelpers.logger
|
||||
transcodingManager = transcode
|
||||
|
||||
logger.info("Registering peertube-plugin-hardware-encode");
|
||||
|
||||
const encoder = 'h264_nvenc'
|
||||
const profileName = 'nvenc'
|
||||
|
||||
// Add trasncoding profiles
|
||||
transcodingManager.addVODProfile(encoder, profileName, vodBuilder)
|
||||
transcodingManager.addVODEncoderPriority('video', encoder, 1000)
|
||||
|
||||
transcodingManager.addLiveProfile(encoder, profileName, liveBuilder)
|
||||
transcodingManager.addLiveEncoderPriority('video', encoder, 1000)
|
||||
|
||||
// Load existing settings and default to constants if not present
|
||||
await loadSettings(settingsManager)
|
||||
|
||||
registerSetting({
|
||||
name: 'hardware-decode',
|
||||
label: 'Hardware decode',
|
||||
|
||||
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.',
|
||||
|
||||
default: DEFAULT_HARDWARE_DECODE,
|
||||
private: false
|
||||
})
|
||||
registerSetting({
|
||||
name: 'vod-quality',
|
||||
label: 'VOD Quality',
|
||||
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'fastest', value: '12' },
|
||||
{ label: 'faster', value: '13' },
|
||||
{ label: 'fast', value: '14' },
|
||||
{ label: 'medium (default)', value: '15' },
|
||||
{ label: 'slow', value: '16' },
|
||||
{ label: 'slower', value: '17' },
|
||||
{ label: 'slowest', value: '18' },
|
||||
{ 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.',
|
||||
|
||||
default: DEFAULT_VOD_QUALITY.toString(),
|
||||
private: false
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'live-quality',
|
||||
label: 'Live Quality',
|
||||
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'low latency (default)', value: '7' },
|
||||
{ label: 'low latency high quality', value: '8' },
|
||||
{ label: 'low latency high performance', value: '9' }
|
||||
],
|
||||
|
||||
descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.',
|
||||
|
||||
default: DEFAULT_LIVE_QUALITY.toString(),
|
||||
private: false
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'base-bitrate-description',
|
||||
label: 'Base bitrate',
|
||||
|
||||
type: '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.`,
|
||||
|
||||
private: true,
|
||||
})
|
||||
for (const [resolution, bitrate] of pluginSettings.baseBitrate) {
|
||||
logger.info("registering bitrate setting: "+ bitrate.toString())
|
||||
registerSetting({
|
||||
name: `base-bitrate-${resolution}`,
|
||||
label: `Base bitrate for ${printResolution(resolution)}`,
|
||||
|
||||
type: 'input',
|
||||
|
||||
default: DEFAULT_BITRATES.get(resolution)?.toString(),
|
||||
descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`,
|
||||
|
||||
private: false
|
||||
})
|
||||
}
|
||||
|
||||
settingsManager.onSettingsChange(async (settings) => {
|
||||
loadSettings(settingsManager)
|
||||
})
|
||||
}
|
||||
|
||||
export async function unregister() {
|
||||
logger.info("Unregistering peertube-plugin-hardware-encode")
|
||||
transcodingManager.removeAllProfilesAndEncoderPriorities()
|
||||
return true
|
||||
}
|
||||
|
||||
async function loadSettings(settingsManager: PluginSettingsManager) {
|
||||
pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') == "true"
|
||||
pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality') as string) || DEFAULT_VOD_QUALITY
|
||||
pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality') as string) || DEFAULT_LIVE_QUALITY
|
||||
|
||||
for (const [resolution, bitrate] of DEFAULT_BITRATES) {
|
||||
const key = `base-bitrate-${resolution}`
|
||||
const storedValue = await settingsManager.getSetting(key) as string
|
||||
pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate)
|
||||
logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`)
|
||||
}
|
||||
|
||||
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`)
|
||||
logger.info(`VOD Quality: ${pluginSettings.vodQuality}`)
|
||||
logger.info(`Live Quality: ${pluginSettings.liveQuality}`)
|
||||
}
|
||||
|
||||
function printResolution(resolution : VideoResolution) : string {
|
||||
switch (resolution) {
|
||||
case VideoResolution.H_NOVIDEO: return 'audio only'
|
||||
case VideoResolution.H_144P:
|
||||
case VideoResolution.H_360P:
|
||||
case VideoResolution.H_480P:
|
||||
case VideoResolution.H_720P:
|
||||
case VideoResolution.H_1080P:
|
||||
case VideoResolution.H_1440P:
|
||||
return `${resolution}p`
|
||||
case VideoResolution.H_4K: return '4K'
|
||||
|
||||
default: return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
function buildInitOptions() {
|
||||
if (pluginSettings.hardwareDecode) {
|
||||
return [
|
||||
'-vcodec h264_nvenc'
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
'-vcodec h264_nvenc'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async function vodBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`
|
||||
let targetBitrate = getTargetBitrate(resolution, fps)
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
|
||||
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate
|
||||
}
|
||||
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
|
||||
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum
|
||||
}
|
||||
// You can also return a promise
|
||||
let options : EncoderOptions = {
|
||||
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'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-preset ${pluginSettings.vodQuality}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-bufsize ${targetBitrate * 2}`,
|
||||
`-bf 4`
|
||||
]
|
||||
}
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`)
|
||||
return options
|
||||
}
|
||||
|
||||
|
||||
async function liveBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> {
|
||||
const { resolution, fps, streamNum, inputBitrate } = params
|
||||
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`
|
||||
let targetBitrate = getTargetBitrate(resolution, fps)
|
||||
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
|
||||
|
||||
if (targetBitrate > inputBitrate) {
|
||||
targetBitrate = inputBitrate
|
||||
}
|
||||
|
||||
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
|
||||
|
||||
if (shouldInitVaapi && streamNum != undefined) {
|
||||
latestStreamNum = streamNum
|
||||
}
|
||||
|
||||
// You can also return a promise
|
||||
const options = {
|
||||
scaleFilter: {
|
||||
// name: pluginSettings.hardwareDecode ? 'scale_vaapi' : 'format=nv12,hwupload,scale_vaapi'
|
||||
name: 'scale'
|
||||
},
|
||||
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
|
||||
outputOptions: [
|
||||
`-preset ${pluginSettings.liveQuality}`,
|
||||
`-r:v${streamSuffix} ${fps}`,
|
||||
`-profile:v${streamSuffix} high`,
|
||||
`-g:v${streamSuffix} ${fps*2}`,
|
||||
`-b:v${streamSuffix} ${targetBitrate}`,
|
||||
`-bufsize ${targetBitrate * 2}`,
|
||||
`-bf 4`
|
||||
]
|
||||
}
|
||||
logger.info(`EncoderOptions: ${JSON.stringify(options)}`)
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the target bitrate based on video resolution and FPS.
|
||||
*
|
||||
* The calculation is based on two values:
|
||||
* Bitrate at VideoTranscodingFPS.AVERAGE is always the same as
|
||||
* getBaseBitrate(). Bitrate at VideoTranscodingFPS.MAX is always
|
||||
* getBaseBitrate() * 1.4. All other values are calculated linearly
|
||||
* 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))
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/node16/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node", // Tell tsc to look in node_modules for modules
|
||||
"strict": true, // That implies alwaysStrict, noImplicitAny, noImplicitThis
|
||||
|
||||
"alwaysStrict": true, // should already be true because of strict:true
|
||||
"noImplicitAny": true, // should already be true because of strict:true
|
||||
"noImplicitThis": true, // should already be true because of strict:true
|
||||
"noImplicitReturns": true,
|
||||
"strictBindCallApply": true, // should already be true because of strict:true
|
||||
"noUnusedLocals": true,
|
||||
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
|
||||
"outDir": "../dist/",
|
||||
"paths": {}
|
||||
},
|
||||
"include": [
|
||||
"./**/*"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
Reference in New Issue
Block a user