separate configs for hw/sw

This commit is contained in:
mertalev
2024-05-14 19:54:53 -04:00
parent a2b7403978
commit b6808c1675
3 changed files with 122 additions and 84 deletions
+37 -2
View File
@@ -1393,10 +1393,15 @@ describe(MediaService.name, () => {
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/as/se/asset-id.mp4', 'upload/encoded-video/user-id/as/se/asset-id.mp4',
{ {
inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), inputOptions: expect.arrayContaining([
'-hwaccel cuda',
'-hwaccel_output_format cuda',
'-noautorotate',
'-threads 1',
]),
outputOptions: expect.arrayContaining([ outputOptions: expect.arrayContaining([
expect.stringContaining( expect.stringContaining(
'hwupload=derive_device=vulkan,scale_vulkan=w=1280:h=720,libplacebo=color_primaries=bt709:color_trc=bt709:colorspace=bt709:deband=true:deband_iterations=3:deband_radius=8:deband_threshold=6:downscaler=none:format=yuv420p:tonemapping=clip:upscaler=none,hwupload=derive_device=cuda', 'scale_cuda=-2:720,hwupload=derive_device=vulkan,libplacebo=color_primaries=bt709:color_trc=bt709:colorspace=bt709:deband=true:deband_iterations=3:deband_radius=8:deband_threshold=6:downscaler=none:format=yuv420p:tonemapping=clip:upscaler=none,hwupload=derive_device=cuda',
), ),
]), ]),
twoPass: false, twoPass: false,
@@ -1739,6 +1744,7 @@ describe(MediaService.name, () => {
it('should set options for rkmpp', async () => { it('should set options for rkmpp', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']); storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([ configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP }, { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
@@ -1772,6 +1778,7 @@ describe(MediaService.name, () => {
it('should set vbr options for rkmpp when max bitrate is enabled', async () => { it('should set vbr options for rkmpp when max bitrate is enabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']); storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9); mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
configMock.load.mockResolvedValue([ configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP }, { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
@@ -1794,6 +1801,7 @@ describe(MediaService.name, () => {
it('should set cqp options for rkmpp when max bitrate is disabled', async () => { it('should set cqp options for rkmpp when max bitrate is disabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']); storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([ configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP }, { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
@@ -1867,6 +1875,33 @@ describe(MediaService.name, () => {
}, },
); );
}); });
it('should use software decoding and tone-mapping if opencl is not available', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => false, isCharacterDevice: () => false });
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/encoded-video/user-id/as/se/asset-id.mp4',
{
inputOptions: [],
outputOptions: expect.arrayContaining([
expect.stringContaining(
'zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
),
]),
twoPass: false,
},
);
});
}); });
it('should tonemap when policy is required and video is hdr', async () => { it('should tonemap when policy is required and video is hdr', async () => {
+9 -4
View File
@@ -36,9 +36,11 @@ import {
AV1Config, AV1Config,
H264Config, H264Config,
HEVCConfig, HEVCConfig,
NVENCConfig, NvencHwDecodeConfig,
NvencSwDecodeConfig,
QSVConfig, QSVConfig,
RKMPPConfig, RkmppHwDecodeConfig,
RkmppSwDecodeConfig,
ThumbnailConfig, ThumbnailConfig,
VAAPIConfig, VAAPIConfig,
VP9Config, VP9Config,
@@ -495,7 +497,7 @@ export class MediaService {
let handler: VideoCodecHWConfig; let handler: VideoCodecHWConfig;
switch (config.accel) { switch (config.accel) {
case TranscodeHWAccel.NVENC: { case TranscodeHWAccel.NVENC: {
handler = new NVENCConfig(config); handler = config.accelDecode ? new NvencHwDecodeConfig(config) : new NvencSwDecodeConfig(config);
break; break;
} }
case TranscodeHWAccel.QSV: { case TranscodeHWAccel.QSV: {
@@ -507,7 +509,10 @@ export class MediaService {
break; break;
} }
case TranscodeHWAccel.RKMPP: { case TranscodeHWAccel.RKMPP: {
handler = new RKMPPConfig(config, await this.getDevices(), await this.hasOpenCL()); handler =
config.accelDecode && (await this.hasOpenCL())
? new RkmppHwDecodeConfig(config, await this.getDevices())
: new RkmppSwDecodeConfig(config, await this.getDevices());
break; break;
} }
default: { default: {
+76 -78
View File
@@ -442,17 +442,13 @@ export class AV1Config extends BaseConfig {
} }
} }
export class NVENCConfig extends BaseHWConfig { export class NvencSwDecodeConfig extends BaseHWConfig {
getSupportedCodecs() { getSupportedCodecs() {
return [VideoCodec.H264, VideoCodec.HEVC, VideoCodec.AV1]; return [VideoCodec.H264, VideoCodec.HEVC, VideoCodec.AV1];
} }
getBaseInputOptions() { getBaseInputOptions() {
if (!this.config.accelDecode) { return ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'];
return ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda', ...this.getInputThreadOptions()];
}
return ['-hwaccel cuda', '-hwaccel_output_format cuda', ...this.getInputThreadOptions()];
} }
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
@@ -474,48 +470,15 @@ export class NVENCConfig extends BaseHWConfig {
} }
getFilterOptions(videoStream: VideoStreamInfo) { getFilterOptions(videoStream: VideoStreamInfo) {
const options = []; const options = this.getToneMapping(videoStream);
if (!this.config.accelDecode) { options.push('format=nv12', 'hwupload_cuda');
options.push(...this.getToneMapping(videoStream), 'format=nv12', 'hwupload_cuda');
if (this.shouldScale(videoStream)) { if (this.shouldScale(videoStream)) {
options.push(`scale_cuda=${this.getScaling(videoStream)}`); options.push(`scale_cuda=${this.getScaling(videoStream)}`);
} }
return options; return options;
} }
options.push('hwupload=derive_device=vulkan');
if (this.shouldScale(videoStream)) {
const { width, height } = this.getSize(videoStream);
options.push(`scale_vulkan=w=${width}:h=${height}`);
}
options.push(...this.getToneMapping(videoStream), 'hwupload=derive_device=cuda');
return options;
}
getToneMapping(videoStream: VideoStreamInfo) {
if (!this.config.accelDecode) {
return super.getToneMapping(videoStream);
}
const colors = this.getColors();
const libplaceboOptions = [
`color_primaries=${colors.primaries}`,
`color_trc=${colors.transfer}`,
`colorspace=${colors.matrix}`,
'deband=true',
'deband_iterations=3',
'deband_radius=8',
'deband_threshold=6',
'downscaler=none',
'format=yuv420p',
`tonemapping=${this.shouldToneMap(videoStream) ? this.config.tonemap : 'clip'}`,
'upscaler=none',
];
return [`libplacebo=${libplaceboOptions.join(':')}`];
}
getPresetOptions() { getPresetOptions() {
let presetIndex = this.getPresetIndex(); let presetIndex = this.getPresetIndex();
if (presetIndex < 0) { if (presetIndex < 0) {
@@ -545,11 +508,7 @@ export class NVENCConfig extends BaseHWConfig {
} }
} }
getInputThreadOptions() { getThreadOptions() {
return [`-threads ${this.config.threads <= 0 ? 1 : this.config.threads}`];
}
getOutputThreadOptions() {
return []; return [];
} }
@@ -562,6 +521,48 @@ export class NVENCConfig extends BaseHWConfig {
} }
} }
export class NvencHwDecodeConfig extends NvencSwDecodeConfig {
getBaseInputOptions() {
return ['-hwaccel cuda', '-hwaccel_output_format cuda', '-noautorotate', ...this.getInputThreadOptions()];
}
getFilterOptions(videoStream: VideoStreamInfo) {
const options = [];
if (this.shouldScale(videoStream)) {
options.push(`scale_cuda=${this.getScaling(videoStream)}`);
}
options.push('hwupload=derive_device=vulkan', ...this.getToneMapping(videoStream), 'hwupload=derive_device=cuda');
return options;
}
getToneMapping(videoStream: VideoStreamInfo) {
const colors = this.getColors();
const libplaceboOptions = [
`color_primaries=${colors.primaries}`,
`color_trc=${colors.transfer}`,
`colorspace=${colors.matrix}`,
'deband=true',
'deband_iterations=3',
'deband_radius=8',
'deband_threshold=6',
'downscaler=none',
'format=yuv420p',
`tonemapping=${this.shouldToneMap(videoStream) ? this.config.tonemap : 'clip'}`,
'upscaler=none',
];
return [`libplacebo=${libplaceboOptions.join(':')}`];
}
getInputThreadOptions() {
return [`-threads ${this.config.threads <= 0 ? 1 : this.config.threads}`];
}
getOutputThreadOptions() {
return [];
}
}
export class QSVConfig extends BaseHWConfig { export class QSVConfig extends BaseHWConfig {
getBaseInputOptions() { getBaseInputOptions() {
if (this.devices.length === 0) { if (this.devices.length === 0) {
@@ -705,51 +706,22 @@ export class VAAPIConfig extends BaseHWConfig {
} }
} }
export class RKMPPConfig extends BaseHWConfig { export class RkmppSwDecodeConfig extends BaseHWConfig {
private hasOpenCL: boolean;
constructor( constructor(
protected config: SystemConfigFFmpegDto, protected config: SystemConfigFFmpegDto,
devices: string[] = [], devices: string[] = [],
hasOpenCL: boolean = false,
) { ) {
super(config, devices); super(config, devices);
this.hasOpenCL = hasOpenCL;
} }
eligibleForTwoPass(): boolean { eligibleForTwoPass(): boolean {
return false; return false;
} }
getBaseInputOptions(videoStream: VideoStreamInfo) { getBaseInputOptions(): string[] {
if (this.devices.length === 0) { if (this.devices.length === 0) {
throw new Error('No RKMPP device found'); throw new Error('No RKMPP device found');
} }
return !this.config.accelDecode || (this.shouldToneMap(videoStream) && !this.hasOpenCL)
? [] // disable hardware decoding & filters
: ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'];
}
getFilterOptions(videoStream: VideoStreamInfo) {
if (!this.config.accelDecode) {
return super.getFilterOptions(videoStream);
}
if (this.shouldToneMap(videoStream)) {
if (!this.hasOpenCL) {
return super.getFilterOptions(videoStream);
}
const colors = this.getColors();
return [
`scale_rkrga=${this.getScaling(videoStream)}:format=p010:afbc=1`,
'hwmap=derive_device=opencl:mode=read',
`tonemap_opencl=format=nv12:r=pc:p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:tonemap=${this.config.tonemap}:desat=0`,
'hwmap=derive_device=rkmpp:mode=write:reverse=1',
'format=drm_prime',
];
} else if (this.shouldScale(videoStream)) {
return [`scale_rkrga=${this.getScaling(videoStream)}:format=nv12:afbc=1`];
}
return []; return [];
} }
@@ -787,3 +759,29 @@ export class RKMPPConfig extends BaseHWConfig {
return `${this.config.targetVideoCodec}_rkmpp`; return `${this.config.targetVideoCodec}_rkmpp`;
} }
} }
export class RkmppHwDecodeConfig extends RkmppSwDecodeConfig {
getBaseInputOptions() {
if (this.devices.length === 0) {
throw new Error('No RKMPP device found');
}
return ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'];
}
getFilterOptions(videoStream: VideoStreamInfo) {
if (this.shouldToneMap(videoStream)) {
const colors = this.getColors();
return [
`scale_rkrga=${this.getScaling(videoStream)}:format=p010:afbc=1`,
'hwmap=derive_device=opencl:mode=read',
`tonemap_opencl=format=nv12:r=pc:p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:tonemap=${this.config.tonemap}:desat=0`,
'hwmap=derive_device=rkmpp:mode=write:reverse=1',
'format=drm_prime',
];
} else if (this.shouldScale(videoStream)) {
return [`scale_rkrga=${this.getScaling(videoStream)}:format=nv12:afbc=1`];
}
return [];
}
}