separate configs for hw/sw
This commit is contained in:
@@ -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 () => {
|
||||||
|
|||||||
@@ -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
@@ -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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user