fix(server): convert to nv12 before hwupload for VAAPI with software decoding (#32474)
* fix(server): convert to nv12 before hwupload for VAAPI with software decoding * test(server): cover nv12 conversion for VAAPI sw decode * output nv12 directly --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> (cherry picked from commit 4bdc4480b2771a5a0dfed26e1149d680f6c204d9)
This commit is contained in:
@@ -3099,7 +3099,7 @@ describe(MediaService.name, () => {
|
|||||||
'-v',
|
'-v',
|
||||||
'verbose',
|
'verbose',
|
||||||
'-vf',
|
'-vf',
|
||||||
'hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12',
|
'format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12',
|
||||||
'-preset',
|
'-preset',
|
||||||
'7',
|
'7',
|
||||||
'-global_quality:v',
|
'-global_quality:v',
|
||||||
@@ -3116,6 +3116,24 @@ describe(MediaService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should tone-map to nv12 before upload for sw decode qsv', async () => {
|
||||||
|
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.videoStreamHDR });
|
||||||
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
|
ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, accelDecode: false },
|
||||||
|
});
|
||||||
|
await sut.handleVideoConversion({ id: 'video-id' });
|
||||||
|
expect(mocks.media.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
'-vf',
|
||||||
|
'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=nv12,hwupload=extra_hw_frames=64',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set options for qsv with custom dri node with sw decode', async () => {
|
it('should set options for qsv with custom dri node with sw decode', async () => {
|
||||||
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer });
|
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer });
|
||||||
mocks.systemMetadata.get.mockResolvedValue({
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
@@ -3347,7 +3365,7 @@ describe(MediaService.name, () => {
|
|||||||
'-v',
|
'-v',
|
||||||
'verbose',
|
'verbose',
|
||||||
'-vf',
|
'-vf',
|
||||||
'hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12',
|
'format=nv12,hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12',
|
||||||
'-compression_level',
|
'-compression_level',
|
||||||
'7',
|
'7',
|
||||||
'-rc_mode',
|
'-rc_mode',
|
||||||
@@ -3358,6 +3376,39 @@ describe(MediaService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should convert to nv12 before upload for sw decode vaapi without scaling', async () => {
|
||||||
|
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer });
|
||||||
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
|
ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: false, targetResolution: 'original' },
|
||||||
|
});
|
||||||
|
await sut.handleVideoConversion({ id: 'video-id' });
|
||||||
|
expect(mocks.media.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({
|
||||||
|
outputOptions: expect.arrayContaining(['-vf', 'format=nv12,hwupload=extra_hw_frames=64']),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tone-map to nv12 before upload for sw decode vaapi', async () => {
|
||||||
|
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.videoStreamHDR });
|
||||||
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
|
ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: false },
|
||||||
|
});
|
||||||
|
await sut.handleVideoConversion({ id: 'video-id' });
|
||||||
|
expect(mocks.media.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
'-vf',
|
||||||
|
'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=nv12,hwupload=extra_hw_frames=64',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set vbr options for vaapi when max bitrate is enabled', async () => {
|
it('should set vbr options for vaapi when max bitrate is enabled', async () => {
|
||||||
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer });
|
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer });
|
||||||
mocks.systemMetadata.get.mockResolvedValue({
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
|
|||||||
@@ -399,13 +399,13 @@ export class BaseConfig implements VideoCodecSWConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getToneMapping(videoStream: VideoStreamInfo) {
|
getToneMapping(videoStream: VideoStreamInfo, format = 'yuv420p') {
|
||||||
if (!this.shouldToneMap(videoStream)) {
|
if (!this.shouldToneMap(videoStream)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { primaries, transfer, matrix } = this.getColors();
|
const { primaries, transfer, matrix } = this.getColors();
|
||||||
const options = `tonemapx=tonemap=${this.config.tonemap}:desat=0:p=${primaries}:t=${transfer}:m=${matrix}:r=pc:peak=100:format=yuv420p`;
|
const options = `tonemapx=tonemap=${this.config.tonemap}:desat=0:p=${primaries}:t=${transfer}:m=${matrix}:r=pc:peak=100:format=${format}`;
|
||||||
return [options];
|
return [options];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,6 +818,15 @@ export class QsvSwDecodeConfig extends BaseHWConfig {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToneMapping(videoStream: VideoStreamInfo) {
|
||||||
|
// Output nv12 when tone-mapping instead of having ffmpeg convert from yuv420p
|
||||||
|
const options = super.getToneMapping(videoStream, 'nv12');
|
||||||
|
if (options.length === 0 && videoStream.pixelFormat !== 'nv12') {
|
||||||
|
options.push('format=nv12');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
getPresetOptions() {
|
getPresetOptions() {
|
||||||
let presetIndex = this.getPresetIndex();
|
let presetIndex = this.getPresetIndex();
|
||||||
if (presetIndex < 0) {
|
if (presetIndex < 0) {
|
||||||
@@ -952,6 +961,15 @@ export class VaapiSwDecodeConfig extends BaseHWConfig {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToneMapping(videoStream: VideoStreamInfo) {
|
||||||
|
// some drivers (e.g. Mesa radeonsi on AMD) reject yuv420p surfaces and FFmpeg doesn't auto-convert for VAAPI
|
||||||
|
const options = super.getToneMapping(videoStream, 'nv12');
|
||||||
|
if (options.length === 0 && videoStream.pixelFormat !== 'nv12') {
|
||||||
|
options.push('format=nv12');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
getPresetOptions() {
|
getPresetOptions() {
|
||||||
let presetIndex = this.getPresetIndex();
|
let presetIndex = this.getPresetIndex();
|
||||||
if (presetIndex < 0) {
|
if (presetIndex < 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user