1
0
mirror of https://github.com/immich-app/immich.git synced 2025-02-02 18:25:38 +02:00

added a configuration option to select the dri node in transcoding (#6376)

* added a configuration option to select the dri node in transcoding

* chore: open api

* refactor: get hawrdware device

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
t4keda 2024-01-30 02:40:02 +01:00 committed by GitHub
parent 68f8525eb1
commit 76f8d030ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 140 additions and 4 deletions

View File

@ -17,6 +17,7 @@ Name | Type | Description | Notes
**gopSize** | **int** | |
**maxBitrate** | **String** | |
**npl** | **int** | |
**preferredHwDevice** | **String** | |
**preset** | **String** | |
**refs** | **int** | |
**targetAudioCodec** | [**AudioCodec**](AudioCodec.md) | |

View File

@ -22,6 +22,7 @@ class SystemConfigFFmpegDto {
required this.gopSize,
required this.maxBitrate,
required this.npl,
required this.preferredHwDevice,
required this.preset,
required this.refs,
required this.targetAudioCodec,
@ -52,6 +53,8 @@ class SystemConfigFFmpegDto {
int npl;
String preferredHwDevice;
String preset;
int refs;
@ -83,6 +86,7 @@ class SystemConfigFFmpegDto {
other.gopSize == gopSize &&
other.maxBitrate == maxBitrate &&
other.npl == npl &&
other.preferredHwDevice == preferredHwDevice &&
other.preset == preset &&
other.refs == refs &&
other.targetAudioCodec == targetAudioCodec &&
@ -106,6 +110,7 @@ class SystemConfigFFmpegDto {
(gopSize.hashCode) +
(maxBitrate.hashCode) +
(npl.hashCode) +
(preferredHwDevice.hashCode) +
(preset.hashCode) +
(refs.hashCode) +
(targetAudioCodec.hashCode) +
@ -118,7 +123,7 @@ 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, 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, 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>{};
@ -131,6 +136,7 @@ class SystemConfigFFmpegDto {
json[r'gopSize'] = this.gopSize;
json[r'maxBitrate'] = this.maxBitrate;
json[r'npl'] = this.npl;
json[r'preferredHwDevice'] = this.preferredHwDevice;
json[r'preset'] = this.preset;
json[r'refs'] = this.refs;
json[r'targetAudioCodec'] = this.targetAudioCodec;
@ -161,6 +167,7 @@ class SystemConfigFFmpegDto {
gopSize: mapValueOfType<int>(json, r'gopSize')!,
maxBitrate: mapValueOfType<String>(json, r'maxBitrate')!,
npl: mapValueOfType<int>(json, r'npl')!,
preferredHwDevice: mapValueOfType<String>(json, r'preferredHwDevice')!,
preset: mapValueOfType<String>(json, r'preset')!,
refs: mapValueOfType<int>(json, r'refs')!,
targetAudioCodec: AudioCodec.fromJson(json[r'targetAudioCodec'])!,
@ -227,6 +234,7 @@ class SystemConfigFFmpegDto {
'gopSize',
'maxBitrate',
'npl',
'preferredHwDevice',
'preset',
'refs',
'targetAudioCodec',

View File

@ -61,6 +61,11 @@ void main() {
// TODO
});
// String preferredHwDevice
test('to test the property `preferredHwDevice`', () async {
// TODO
});
// String preset
test('to test the property `preset`', () async {
// TODO

View File

@ -9422,6 +9422,9 @@
"npl": {
"type": "integer"
},
"preferredHwDevice": {
"type": "string"
},
"preset": {
"type": "string"
},
@ -9463,6 +9466,7 @@
"gopSize",
"maxBitrate",
"npl",
"preferredHwDevice",
"preset",
"refs",
"targetAudioCodec",

View File

@ -3712,6 +3712,12 @@ export interface SystemConfigFFmpegDto {
* @memberof SystemConfigFFmpegDto
*/
'npl': number;
/**
*
* @type {string}
* @memberof SystemConfigFFmpegDto
*/
'preferredHwDevice': string;
/**
*
* @type {string}

View File

@ -1380,6 +1380,43 @@ describe(MediaService.name, () => {
);
});
it('should set options for qsv with custom dri node', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV },
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
{ key: SystemConfigKey.FFMPEG_PREFERRED_HW_DEVICE, value: '/dev/dri/renderD128' },
]);
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: ['-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', '-filter_hw_device hw'],
outputOptions: [
`-c:v h264_qsv`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-bf 7',
'-refs 5',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
'-preset 7',
'-global_quality 23',
'-maxrate 10000k',
'-bufsize 20000k',
],
twoPass: false,
},
);
});
it('should omit preset for qsv if invalid', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
@ -1613,6 +1650,40 @@ describe(MediaService.name, () => {
);
});
it('should select specific gpu node if selected', async () => {
storageMock.readdir.mockResolvedValue(['renderD129', 'card1', 'card0', 'renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI },
{ key: SystemConfigKey.FFMPEG_PREFERRED_HW_DEVICE, value: '/dev/dri/renderD128' },
]);
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: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
outputOptions: [
`-c:v h264_vaapi`,
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-g 256',
'-v verbose',
'-vf format=nv12,hwupload,scale_vaapi=-2:720',
'-compression_level 7',
'-qp 23',
'-global_quality 23',
'-rc_mode 1',
],
twoPass: false,
},
);
});
it('should fallback to sw transcoding if hw transcoding fails', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);

View File

@ -285,6 +285,20 @@ export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
}
return this.config.gopSize;
}
getPreferredHardwareDevice(): string | null {
const device = this.config.preferredHwDevice;
if (device === 'auto') {
return null;
}
const deviceName = device.replace('/dev/dri/', '');
if (!this.devices.includes(deviceName)) {
throw new Error(`Device '${device}' does not exist`);
}
return device;
}
}
export class ThumbnailConfig extends BaseConfig {
@ -463,7 +477,14 @@ export class QSVConfig extends BaseHWConfig {
if (!this.devices.length) {
throw Error('No QSV device found');
}
return ['-init_hw_device qsv=hw', '-filter_hw_device hw'];
let qsvString = '';
const hwDevice = this.getPreferredHardwareDevice();
if (hwDevice !== null) {
qsvString = `,child_device=${hwDevice}`;
}
return [`-init_hw_device qsv=hw${qsvString}`, '-filter_hw_device hw'];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
@ -527,9 +548,15 @@ export class QSVConfig extends BaseHWConfig {
export class VAAPIConfig extends BaseHWConfig {
getBaseInputOptions() {
if (this.devices.length === 0) {
throw Error('No VAAPI device found');
throw new Error('No VAAPI device found');
}
return [`-init_hw_device vaapi=accel:/dev/dri/${this.devices[0]}`, '-filter_hw_device accel'];
let hwDevice = this.getPreferredHardwareDevice();
if (hwDevice === null) {
hwDevice = `/dev/dri/${this.devices[0]}`;
}
return [`-init_hw_device vaapi=accel:${hwDevice}`, '-filter_hw_device accel'];
}
getFilterOptions(videoStream: VideoStreamInfo) {

View File

@ -78,6 +78,9 @@ export class SystemConfigFFmpegDto {
@IsBoolean()
twoPass!: boolean;
@IsString()
preferredHwDevice!: string;
@IsEnum(TranscodePolicy)
@ApiProperty({ enumName: 'TranscodePolicy', enum: TranscodePolicy })
transcode!: TranscodePolicy;

View File

@ -43,6 +43,7 @@ export const defaults = Object.freeze<SystemConfig>({
temporalAQ: false,
cqMode: CQMode.AUTO,
twoPass: false,
preferredHwDevice: 'auto',
transcode: TranscodePolicy.REQUIRED,
tonemap: ToneMapping.HABLE,
accel: TranscodeHWAccel.DISABLED,

View File

@ -55,6 +55,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
temporalAQ: false,
cqMode: CQMode.AUTO,
twoPass: false,
preferredHwDevice: 'auto',
transcode: TranscodePolicy.REQUIRED,
accel: TranscodeHWAccel.DISABLED,
tonemap: ToneMapping.HABLE,

View File

@ -30,6 +30,7 @@ export enum SystemConfigKey {
FFMPEG_TEMPORAL_AQ = 'ffmpeg.temporalAQ',
FFMPEG_CQ_MODE = 'ffmpeg.cqMode',
FFMPEG_TWO_PASS = 'ffmpeg.twoPass',
FFMPEG_PREFERRED_HW_DEVICE = 'ffmpeg.preferredHwDevice',
FFMPEG_TRANSCODE = 'ffmpeg.transcode',
FFMPEG_ACCEL = 'ffmpeg.accel',
FFMPEG_TONEMAP = 'ffmpeg.tonemap',
@ -176,6 +177,7 @@ export interface SystemConfig {
temporalAQ: boolean;
cqMode: CQMode;
twoPass: boolean;
preferredHwDevice: string;
transcode: TranscodePolicy;
accel: TranscodeHWAccel;
tonemap: ToneMapping;

View File

@ -282,6 +282,13 @@
bind:checked={config.ffmpeg.temporalAQ}
isEdited={config.ffmpeg.temporalAQ !== savedConfig.ffmpeg.temporalAQ}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="PREFERRED HARDWARE DEVICE FOR TRANSCODING"
desc="Applies only to VAAPI and QSV. Sets the dri node used for hardware transcoding. Set to 'auto' to let immich decide for you"
bind:value={config.ffmpeg.preferredHwDevice}
isEdited={config.ffmpeg.preferredHwDevice !== savedConfig.ffmpeg.preferredHwDevice}
/>
</div>
</SettingAccordion>