Compare commits

...

14 Commits

Author SHA1 Message Date
mertalev
ea33cd9515 remove version flag 2024-05-14 20:40:36 -04:00
mertalev
e9ceb8b017 fix mutating config 2024-05-14 20:40:36 -04:00
mertalev
94030d809f add hw decode toggle 2024-05-14 20:40:36 -04:00
mertalev
4db0717a08 update api 2024-05-14 20:40:36 -04:00
mertalev
b6808c1675 separate configs for hw/sw 2024-05-14 20:40:36 -04:00
mertalev
a2b7403978 fix software tone-mapping not being applied 2024-05-14 20:40:36 -04:00
mertalev
1556d978ed toggle for hardware decoding, software / hardware decoding for nvenc and rkmpp 2024-05-14 20:40:36 -04:00
mertalev
48a71ac4d9 refactor 2024-05-14 20:38:08 -04:00
mertalev
adf620331c tweak settings 2024-05-14 20:38:08 -04:00
mertalev
c92637df80 update nvenc options 2024-05-14 20:38:08 -04:00
mertalev
3356b023c5 tweaks 2024-05-14 20:38:08 -04:00
mertalev
a2e8b657e6 libplacebo for nvenc
update dockerfile
2024-05-14 20:38:08 -04:00
mertalev
f420befc15 use arrayContaining 2024-05-14 20:37:56 -04:00
renovate[bot]
efb844c6cd fix(deps): update dependency @zoom-image/svelte to v0.2.12 (#9487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 19:33:53 -04:00
17 changed files with 275 additions and 68 deletions

1
base-images Submodule

Submodule base-images added at d0d3ab018c

View File

@@ -1,9 +1,7 @@
version: "3.8"
# Configurations for hardware-accelerated machine learning
# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# you can inline the config for a backend by copying its contents
# into the immich-machine-learning service in the docker-compose.yml file.
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
@@ -30,7 +28,7 @@ services:
openvino:
device_cgroup_rules:
- "c 189:* rmw"
- 'c 189:* rmw'
devices:
- /dev/dri:/dev/dri
volumes:

View File

@@ -1,5 +1,3 @@
version: "3.8"
# Configurations for hardware-accelerated transcoding
# If using Unraid or another platform that doesn't allow multiple Compose files,
@@ -12,6 +10,9 @@ services:
cpu: {}
nvenc:
runtime: nvidia
environment:
- DISPLAY:$DISPLAY
deploy:
resources:
reservations:
@@ -22,6 +23,8 @@ services:
- gpu
- compute
- video
- display
- graphics
quicksync:
devices:

View File

@@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**accel** | [**TranscodeHWAccel**](TranscodeHWAccel.md) | |
**accelDecode** | **bool** | |
**acceptedAudioCodecs** | [**List<AudioCodec>**](AudioCodec.md) | | [default to const []]
**acceptedVideoCodecs** | [**List<VideoCodec>**](VideoCodec.md) | | [default to const []]
**bframes** | **int** | |

View File

@@ -14,6 +14,7 @@ class SystemConfigFFmpegDto {
/// Returns a new [SystemConfigFFmpegDto] instance.
SystemConfigFFmpegDto({
required this.accel,
required this.accelDecode,
this.acceptedAudioCodecs = const [],
this.acceptedVideoCodecs = const [],
required this.bframes,
@@ -37,6 +38,8 @@ class SystemConfigFFmpegDto {
TranscodeHWAccel accel;
bool accelDecode;
List<AudioCodec> acceptedAudioCodecs;
List<VideoCodec> acceptedVideoCodecs;
@@ -87,6 +90,7 @@ class SystemConfigFFmpegDto {
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto &&
other.accel == accel &&
other.accelDecode == accelDecode &&
_deepEquality.equals(other.acceptedAudioCodecs, acceptedAudioCodecs) &&
_deepEquality.equals(other.acceptedVideoCodecs, acceptedVideoCodecs) &&
other.bframes == bframes &&
@@ -111,6 +115,7 @@ class SystemConfigFFmpegDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(accel.hashCode) +
(accelDecode.hashCode) +
(acceptedAudioCodecs.hashCode) +
(acceptedVideoCodecs.hashCode) +
(bframes.hashCode) +
@@ -132,11 +137,12 @@ class SystemConfigFFmpegDto {
(twoPass.hashCode);
@override
String toString() => 'SystemConfigFFmpegDto[accel=$accel, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'accel'] = this.accel;
json[r'accelDecode'] = this.accelDecode;
json[r'acceptedAudioCodecs'] = this.acceptedAudioCodecs;
json[r'acceptedVideoCodecs'] = this.acceptedVideoCodecs;
json[r'bframes'] = this.bframes;
@@ -168,6 +174,7 @@ class SystemConfigFFmpegDto {
return SystemConfigFFmpegDto(
accel: TranscodeHWAccel.fromJson(json[r'accel'])!,
accelDecode: mapValueOfType<bool>(json, r'accelDecode')!,
acceptedAudioCodecs: AudioCodec.listFromJson(json[r'acceptedAudioCodecs']),
acceptedVideoCodecs: VideoCodec.listFromJson(json[r'acceptedVideoCodecs']),
bframes: mapValueOfType<int>(json, r'bframes')!,
@@ -235,6 +242,7 @@ class SystemConfigFFmpegDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'accel',
'accelDecode',
'acceptedAudioCodecs',
'acceptedVideoCodecs',
'bframes',

View File

@@ -21,6 +21,11 @@ void main() {
// TODO
});
// bool accelDecode
test('to test the property `accelDecode`', () async {
// TODO
});
// List<AudioCodec> acceptedAudioCodecs (default value: const [])
test('to test the property `acceptedAudioCodecs`', () async {
// TODO

View File

@@ -10002,6 +10002,9 @@
"accel": {
"$ref": "#/components/schemas/TranscodeHWAccel"
},
"accelDecode": {
"type": "boolean"
},
"acceptedAudioCodecs": {
"items": {
"$ref": "#/components/schemas/AudioCodec"
@@ -10077,6 +10080,7 @@
},
"required": [
"accel",
"accelDecode",
"acceptedAudioCodecs",
"acceptedVideoCodecs",
"bframes",

View File

@@ -863,6 +863,7 @@ export type AssetFullSyncDto = {
};
export type SystemConfigFFmpegDto = {
accel: TranscodeHWAccel;
accelDecode: boolean;
acceptedAudioCodecs: AudioCodec[];
acceptedVideoCodecs: VideoCodec[];
bframes: number;

View File

@@ -97,6 +97,7 @@ export interface SystemConfig {
preferredHwDevice: string;
transcode: TranscodePolicy;
accel: TranscodeHWAccel;
accelDecode: false;
tonemap: ToneMapping;
};
job: Record<ConcurrentQueueName, { concurrency: number }>;
@@ -224,6 +225,7 @@ export const defaults = Object.freeze<SystemConfig>({
transcode: TranscodePolicy.REQUIRED,
tonemap: ToneMapping.HABLE,
accel: TranscodeHWAccel.DISABLED,
accelDecode: false,
},
job: {
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },

View File

@@ -132,6 +132,9 @@ export class SystemConfigFFmpegDto {
@ApiProperty({ enumName: 'TranscodeHWAccel', enum: TranscodeHWAccel })
accel!: TranscodeHWAccel;
@ValidateBoolean()
accelDecode!: boolean;
@IsEnum(ToneMapping)
@ApiProperty({ enumName: 'ToneMapping', enum: ToneMapping })
tonemap!: ToneMapping;

View File

@@ -43,6 +43,7 @@ export const SystemConfigKey = {
FFMPEG_PREFERRED_HW_DEVICE: 'ffmpeg.preferredHwDevice',
FFMPEG_TRANSCODE: 'ffmpeg.transcode',
FFMPEG_ACCEL: 'ffmpeg.accel',
FFMPEG_ACCEL_DECODE: 'ffmpeg.accelDecode',
FFMPEG_TONEMAP: 'ffmpeg.tonemap',
JOB_THUMBNAIL_GENERATION_CONCURRENCY: 'job.thumbnailGeneration.concurrency',

View File

@@ -1381,6 +1381,57 @@ describe(MediaService.name, () => {
);
});
it('should use hardware decoding for nvenc if enabled', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
]);
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: expect.arrayContaining([
'-hwaccel cuda',
'-hwaccel_output_format cuda',
'-noautorotate',
'-threads 1',
]),
outputOptions: expect.arrayContaining([
expect.stringContaining(
'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,
},
);
});
it('should use hardware tone-mapping for nvenc if hardware decoding is enabled and should tone map', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
]);
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: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']),
outputOptions: expect.arrayContaining([
expect.stringContaining(
'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=hable:upscaler=none,hwupload=derive_device=cuda',
),
]),
twoPass: false,
},
);
});
it('should set options for qsv', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
@@ -1693,8 +1744,12 @@ describe(MediaService.name, () => {
it('should set options for rkmpp', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP }]);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
@@ -1723,9 +1778,11 @@ describe(MediaService.name, () => {
it('should set vbr options for rkmpp when max bitrate is enabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
]);
@@ -1744,9 +1801,11 @@ describe(MediaService.name, () => {
it('should set cqp options for rkmpp when max bitrate is disabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
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' },
]);
@@ -1769,6 +1828,7 @@ describe(MediaService.name, () => {
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' },
]);
@@ -1788,6 +1848,60 @@ describe(MediaService.name, () => {
},
);
});
it('should use software decoding and tone-mapping if hardware decoding is disabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: false },
{ 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 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 () => {

View File

@@ -36,9 +36,11 @@ import {
AV1Config,
H264Config,
HEVCConfig,
NVENCConfig,
NvencHwDecodeConfig,
NvencSwDecodeConfig,
QSVConfig,
RKMPPConfig,
RkmppHwDecodeConfig,
RkmppSwDecodeConfig,
ThumbnailConfig,
VAAPIConfig,
VP9Config,
@@ -361,8 +363,7 @@ export class MediaService {
`Error occurred during transcoding. Retrying with ${config.accel.toUpperCase()} acceleration disabled.`,
);
}
config.accel = TranscodeHWAccel.DISABLED;
transcodeOptions = await this.getCodecConfig(config).then((c) =>
transcodeOptions = await this.getCodecConfig({ ...config, accel: TranscodeHWAccel.DISABLED }).then((c) =>
c.getOptions(target, mainVideoStream, mainAudioStream),
);
await this.mediaRepository.transcode(input, output, transcodeOptions);
@@ -495,7 +496,7 @@ export class MediaService {
let handler: VideoCodecHWConfig;
switch (config.accel) {
case TranscodeHWAccel.NVENC: {
handler = new NVENCConfig(config);
handler = config.accelDecode ? new NvencHwDecodeConfig(config) : new NvencSwDecodeConfig(config);
break;
}
case TranscodeHWAccel.QSV: {
@@ -507,7 +508,10 @@ export class MediaService {
break;
}
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;
}
default: {

View File

@@ -65,6 +65,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
preferredHwDevice: 'auto',
transcode: TranscodePolicy.REQUIRED,
accel: TranscodeHWAccel.DISABLED,
accelDecode: false,
tonemap: ToneMapping.HABLE,
},
logging: {

View File

@@ -26,14 +26,18 @@ class BaseConfig implements VideoCodecSWConfig {
}
}
options.outputOptions.push(...this.getPresetOptions(), ...this.getThreadOptions(), ...this.getBitrateOptions());
options.outputOptions.push(
...this.getPresetOptions(),
...this.getOutputThreadOptions(),
...this.getBitrateOptions(),
);
return options;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getBaseInputOptions(videoStream: VideoStreamInfo): string[] {
return [];
return this.getInputThreadOptions();
}
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
@@ -80,11 +84,7 @@ class BaseConfig implements VideoCodecSWConfig {
options.push(`scale=${this.getScaling(videoStream)}`);
}
if (this.shouldToneMap(videoStream)) {
options.push(...this.getToneMapping());
}
options.push('format=yuv420p');
options.push(...this.getToneMapping(videoStream), 'format=yuv420p');
return options;
}
@@ -112,7 +112,11 @@ class BaseConfig implements VideoCodecSWConfig {
}
}
getThreadOptions(): Array<string> {
getInputThreadOptions(): Array<string> {
return [];
}
getOutputThreadOptions(): Array<string> {
if (this.config.threads <= 0) {
return [];
}
@@ -218,7 +222,11 @@ class BaseConfig implements VideoCodecSWConfig {
}
}
getToneMapping() {
getToneMapping(videoStream: VideoStreamInfo) {
if (!this.shouldToneMap(videoStream)) {
return [];
}
const colors = this.getColors();
return [
@@ -348,8 +356,8 @@ export class ThumbnailConfig extends BaseConfig {
}
export class H264Config extends BaseConfig {
getThreadOptions() {
const options = super.getThreadOptions();
getOutputThreadOptions() {
const options = super.getOutputThreadOptions();
if (this.config.threads === 1) {
options.push('-x264-params frame-threads=1:pools=none');
}
@@ -359,8 +367,8 @@ export class H264Config extends BaseConfig {
}
export class HEVCConfig extends BaseConfig {
getThreadOptions() {
const options = super.getThreadOptions();
getOutputThreadOptions() {
const options = super.getOutputThreadOptions();
if (this.config.threads === 1) {
options.push('-x265-params frame-threads=1:pools=none');
}
@@ -391,8 +399,8 @@ export class VP9Config extends BaseConfig {
return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, `-b:v ${bitrates.max}${bitrates.unit}`];
}
getThreadOptions() {
return ['-row-mt 1', ...super.getThreadOptions()];
getOutputThreadOptions() {
return ['-row-mt 1', ...super.getOutputThreadOptions()];
}
eligibleForTwoPass() {
@@ -425,7 +433,7 @@ export class AV1Config extends BaseConfig {
return options;
}
getThreadOptions() {
getOutputThreadOptions() {
return []; // Already set above with svtav1-params
}
@@ -434,7 +442,7 @@ export class AV1Config extends BaseConfig {
}
}
export class NVENCConfig extends BaseHWConfig {
export class NvencSwDecodeConfig extends BaseHWConfig {
getSupportedCodecs() {
return [VideoCodec.H264, VideoCodec.HEVC, VideoCodec.AV1];
}
@@ -462,7 +470,7 @@ export class NVENCConfig extends BaseHWConfig {
}
getFilterOptions(videoStream: VideoStreamInfo) {
const options = this.shouldToneMap(videoStream) ? this.getToneMapping() : [];
const options = this.getToneMapping(videoStream);
options.push('format=nv12', 'hwupload_cuda');
if (this.shouldScale(videoStream)) {
options.push(`scale_cuda=${this.getScaling(videoStream)}`);
@@ -513,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 {
getBaseInputOptions() {
if (this.devices.length === 0) {
@@ -538,7 +588,7 @@ export class QSVConfig extends BaseHWConfig {
}
getFilterOptions(videoStream: VideoStreamInfo) {
const options = this.shouldToneMap(videoStream) ? this.getToneMapping() : [];
const options = this.getToneMapping(videoStream);
options.push('format=nv12', 'hwupload=extra_hw_frames=64');
if (this.shouldScale(videoStream)) {
options.push(`scale_qsv=${this.getScaling(videoStream)}`);
@@ -604,7 +654,7 @@ export class VAAPIConfig extends BaseHWConfig {
}
getFilterOptions(videoStream: VideoStreamInfo) {
const options = this.shouldToneMap(videoStream) ? this.getToneMapping() : [];
const options = this.getToneMapping(videoStream);
options.push('format=nv12', 'hwupload');
if (this.shouldScale(videoStream)) {
options.push(`scale_vaapi=${this.getScaling(videoStream)}`);
@@ -656,47 +706,22 @@ export class VAAPIConfig extends BaseHWConfig {
}
}
export class RKMPPConfig extends BaseHWConfig {
private hasOpenCL: boolean;
export class RkmppSwDecodeConfig extends BaseHWConfig {
constructor(
protected config: SystemConfigFFmpegDto,
devices: string[] = [],
hasOpenCL: boolean = false,
) {
super(config, devices);
this.hasOpenCL = hasOpenCL;
}
eligibleForTwoPass(): boolean {
return false;
}
getBaseInputOptions(videoStream: VideoStreamInfo) {
getBaseInputOptions(): string[] {
if (this.devices.length === 0) {
throw new Error('No RKMPP device found');
}
return this.shouldToneMap(videoStream) && !this.hasOpenCL
? [] // disable hardware decoding & filters
: ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'];
}
getFilterOptions(videoStream: VideoStreamInfo) {
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 [];
}
@@ -734,3 +759,29 @@ export class RKMPPConfig extends BaseHWConfig {
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 [];
}
}

14
web/package-lock.json generated
View File

@@ -2806,9 +2806,9 @@
"dev": true
},
"node_modules/@zoom-image/core": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.34.1.tgz",
"integrity": "sha512-IHh5TSp/PGvBZs8plQ+ERDz2NXoZ52v+8JUMFNkvqRSYAVW87xuSup1JESKdp72qLaXWAGUfTqvqFlzmC+/37g==",
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.34.2.tgz",
"integrity": "sha512-VI/T2fokW25y0U+5wTWb1nWUEaMlR9Uhx4SCCzcmBP6EIOUw4XnyoWfyALb+NDyTD7KtMi8y6MCXdODVQtpRSQ==",
"dependencies": {
"@namnode/store": "^0.1.0"
},
@@ -2818,11 +2818,11 @@
}
},
"node_modules/@zoom-image/svelte": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.11.tgz",
"integrity": "sha512-cdq43YTEuOV0LmkHlddSvA8UG6USlYqv7BRTDwyGH6jWHGMJudhqvBiikvA93gfyND538qagacN9jAbWto3ASQ==",
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.12.tgz",
"integrity": "sha512-f8FesLWZuIrogjcGOUAsar8SHNuimFB2Kqf6WsgsYH8b6Px322X2ipmcpqchWKyxAzOiT77qRhM88io+9NmrxQ==",
"dependencies": {
"@zoom-image/core": "0.34.1"
"@zoom-image/core": "0.34.2"
},
"funding": {
"type": "github",

View File

@@ -276,6 +276,15 @@
isEdited={config.ffmpeg.accel !== savedConfig.ffmpeg.accel}
/>
<SettingSwitch
id="hardware-decoding"
title="HARDWARE DECODING"
{disabled}
subtitle="Applies only to NVENC and RKMPP. Whether to use hardware decoding for transcoding. May not work on all devices or videos."
bind:checked={config.ffmpeg.accelDecode}
isEdited={config.ffmpeg.accelDecode !== savedConfig.ffmpeg.accelDecode}
/>
<SettingSelect
label="CONSTANT QUALITY MODE"
desc="ICQ is better than CQP, but some hardware acceleration devices do not support this mode. Setting this option will prefer the specified mode when using quality-based encoding. Ignored by NVENC as it does not support ICQ."
@@ -297,6 +306,7 @@
bind:checked={config.ffmpeg.temporalAQ}
isEdited={config.ffmpeg.temporalAQ !== savedConfig.ffmpeg.temporalAQ}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="PREFERRED HARDWARE DEVICE"