mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	feat(server): hardware HDR tonemapping for RKMPP (#7655)
* feat(server): hardware HDR tonemapping for RKMPP * review feedback
This commit is contained in:
		
							parent
							
								
									ba55e867e0
								
							
						
					
					
						commit
						3f1d37e556
					
				@ -1,9 +1,9 @@
 | 
				
			|||||||
version: "3.8"
 | 
					version: "3.8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Configurations for hardware-accelerated transcoding 
 | 
					# Configurations for hardware-accelerated transcoding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# If using Unraid or another platform that doesn't allow multiple Compose files,
 | 
					# 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-microservices service in the docker-compose.yml file.
 | 
					# into the immich-microservices service in the docker-compose.yml file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.
 | 
					# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.
 | 
				
			||||||
@ -38,6 +38,10 @@ services:
 | 
				
			|||||||
      - /dev/dri:/dev/dri
 | 
					      - /dev/dri:/dev/dri
 | 
				
			||||||
      - /dev/dma_heap:/dev/dma_heap
 | 
					      - /dev/dma_heap:/dev/dma_heap
 | 
				
			||||||
      - /dev/mpp_service:/dev/mpp_service
 | 
					      - /dev/mpp_service:/dev/mpp_service
 | 
				
			||||||
 | 
					      #- /dev/mali0:/dev/mali0 # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      #- /etc/OpenCL:/etc/OpenCL:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
 | 
				
			||||||
 | 
					      #- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  vaapi:
 | 
					  vaapi:
 | 
				
			||||||
    devices:
 | 
					    devices:
 | 
				
			||||||
 | 
				
			|||||||
@ -42,6 +42,18 @@ You do not need to redo any transcoding jobs after enabling hardware acceleratio
 | 
				
			|||||||
  - If you have an 11th gen CPU or older, then you may need to follow [these][jellyfin-lp] instructions as Low-Power mode is required
 | 
					  - If you have an 11th gen CPU or older, then you may need to follow [these][jellyfin-lp] instructions as Low-Power mode is required
 | 
				
			||||||
  - Additionally, if the server specifically has an 11th gen CPU and is running kernel 5.15 (shipped with Ubuntu 22.04 LTS), then you will need to upgrade this kernel (from [Jellyfin docs][jellyfin-kernel-bug])
 | 
					  - Additionally, if the server specifically has an 11th gen CPU and is running kernel 5.15 (shipped with Ubuntu 22.04 LTS), then you will need to upgrade this kernel (from [Jellyfin docs][jellyfin-kernel-bug])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### RKMPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For RKMPP to work:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- You must have a supported Rockchip ARM SoC.
 | 
				
			||||||
 | 
					- Only RK3588 supports hardware tonemapping, other SoCs use slower software tonemapping while still using hardware encoding.
 | 
				
			||||||
 | 
					- Tonemapping requires `/usr/lib/aarch64-linux-gnu/libmali.so.1` to be present on your host system. Install [`libmali-valhall-g610-g6p0-gbm`][libmali-rockchip] and modify the [`hwaccel.transcoding.yml`][hw-file] file:
 | 
				
			||||||
 | 
					  - under `rkmpp` uncomment the 3 lines required for OpenCL tonemapping by removing the `#` symbol at the beginning of each line
 | 
				
			||||||
 | 
					  - `- /dev/mali0:/dev/mali0`
 | 
				
			||||||
 | 
					  - `- /etc/OpenCL:/etc/OpenCL:ro`
 | 
				
			||||||
 | 
					  - `- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Setup
 | 
					## Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Basic Setup
 | 
					#### Basic Setup
 | 
				
			||||||
@ -106,3 +118,4 @@ Once this is done, you can continue to step 3 of "Basic Setup".
 | 
				
			|||||||
[nvcr]: https://github.com/NVIDIA/nvidia-container-runtime/
 | 
					[nvcr]: https://github.com/NVIDIA/nvidia-container-runtime/
 | 
				
			||||||
[jellyfin-lp]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#configure-and-verify-lp-mode-on-linux
 | 
					[jellyfin-lp]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#configure-and-verify-lp-mode-on-linux
 | 
				
			||||||
[jellyfin-kernel-bug]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#known-issues-and-limitations
 | 
					[jellyfin-kernel-bug]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#known-issues-and-limitations
 | 
				
			||||||
 | 
					[libmali-rockchip]: https://github.com/tsukumijima/libmali-rockchip/releases
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ import {
 | 
				
			|||||||
  personStub,
 | 
					  personStub,
 | 
				
			||||||
  probeStub,
 | 
					  probeStub,
 | 
				
			||||||
} from '@test';
 | 
					} from '@test';
 | 
				
			||||||
 | 
					import { Stats } from 'node:fs';
 | 
				
			||||||
import { JobName } from '../job';
 | 
					import { JobName } from '../job';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  IAssetRepository,
 | 
					  IAssetRepository,
 | 
				
			||||||
@ -1853,6 +1854,41 @@ describe(MediaService.name, () => {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should set OpenCL tonemapping options for rkmpp when OpenCL is available', 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_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: ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'],
 | 
				
			||||||
 | 
					          outputOptions: [
 | 
				
			||||||
 | 
					            `-c:v h264_rkmpp`,
 | 
				
			||||||
 | 
					            '-c:a copy',
 | 
				
			||||||
 | 
					            '-movflags faststart',
 | 
				
			||||||
 | 
					            '-fps_mode passthrough',
 | 
				
			||||||
 | 
					            '-map 0:0',
 | 
				
			||||||
 | 
					            '-map 0:1',
 | 
				
			||||||
 | 
					            '-g 256',
 | 
				
			||||||
 | 
					            '-v verbose',
 | 
				
			||||||
 | 
					            '-vf scale_rkrga=-2:720:format=p010:afbc=1,hwmap=derive_device=opencl:mode=read,tonemap_opencl=format=nv12:r=pc:p=bt709:t=bt709:m=bt709:tonemap=hable:desat=0,hwmap=derive_device=rkmpp:mode=write:reverse=1,format=drm_prime',
 | 
				
			||||||
 | 
					            '-level 51',
 | 
				
			||||||
 | 
					            '-rc_mode CQP',
 | 
				
			||||||
 | 
					            '-qp_init 30',
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          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 () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -47,6 +47,7 @@ export class MediaService {
 | 
				
			|||||||
  private logger = new ImmichLogger(MediaService.name);
 | 
					  private logger = new ImmichLogger(MediaService.name);
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private storageCore: StorageCore;
 | 
					  private storageCore: StorageCore;
 | 
				
			||||||
 | 
					  private hasOpenCL?: boolean = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
					    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
				
			||||||
@ -456,8 +457,19 @@ export class MediaService {
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      case TranscodeHWAccel.RKMPP: {
 | 
					      case TranscodeHWAccel.RKMPP: {
 | 
				
			||||||
 | 
					        if (this.hasOpenCL === undefined) {
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            const maliIcdStat = await this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd');
 | 
				
			||||||
 | 
					            const maliDeviceStat = await this.storageRepository.stat('/dev/mali0');
 | 
				
			||||||
 | 
					            this.hasOpenCL = maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice();
 | 
				
			||||||
 | 
					          } catch {
 | 
				
			||||||
 | 
					            this.logger.warn('OpenCL not available for transcoding, using CPU instead.');
 | 
				
			||||||
 | 
					            this.hasOpenCL = false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        devices = await this.storageRepository.readdir('/dev/dri');
 | 
					        devices = await this.storageRepository.readdir('/dev/dri');
 | 
				
			||||||
        handler = new RKMPPConfig(config, devices);
 | 
					        handler = new RKMPPConfig(config, devices, this.hasOpenCL);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      default: {
 | 
					      default: {
 | 
				
			||||||
 | 
				
			|||||||
@ -608,6 +608,17 @@ export class VAAPIConfig extends BaseHWConfig {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class RKMPPConfig extends BaseHWConfig {
 | 
					export class RKMPPConfig extends BaseHWConfig {
 | 
				
			||||||
 | 
					  private hasOpenCL: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    protected config: SystemConfigFFmpegDto,
 | 
				
			||||||
 | 
					    devices: string[] = [],
 | 
				
			||||||
 | 
					    hasOpenCL: boolean = false,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    super(config, devices);
 | 
				
			||||||
 | 
					    this.hasOpenCL = hasOpenCL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  eligibleForTwoPass(): boolean {
 | 
					  eligibleForTwoPass(): boolean {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -616,19 +627,25 @@ export class RKMPPConfig extends BaseHWConfig {
 | 
				
			|||||||
    if (this.devices.length === 0) {
 | 
					    if (this.devices.length === 0) {
 | 
				
			||||||
      throw new Error('No RKMPP device found');
 | 
					      throw new Error('No RKMPP device found');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.shouldToneMap(videoStream)) {
 | 
					    return this.shouldToneMap(videoStream) && !this.hasOpenCL
 | 
				
			||||||
      // disable hardware decoding
 | 
					      ? [] // disable hardware decoding & filters
 | 
				
			||||||
      return [];
 | 
					      : ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'];
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga'];
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getFilterOptions(videoStream: VideoStreamInfo) {
 | 
					  getFilterOptions(videoStream: VideoStreamInfo) {
 | 
				
			||||||
    if (this.shouldToneMap(videoStream)) {
 | 
					    if (this.shouldToneMap(videoStream)) {
 | 
				
			||||||
      // use software filter options
 | 
					      if (!this.hasOpenCL) {
 | 
				
			||||||
      return super.getFilterOptions(videoStream);
 | 
					        return super.getFilterOptions(videoStream);
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
    if (this.shouldScale(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 [`scale_rkrga=${this.getScaling(videoStream)}:format=nv12:afbc=1`];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [];
 | 
					    return [];
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user