Skip to content

Commit

Permalink
make video decoder and encoder configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
gael-connan-cybex committed Aug 15, 2024
1 parent 00acd52 commit 9fba1cd
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 27 deletions.
25 changes: 25 additions & 0 deletions app/Enums/Decoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Enums;

enum Decoder: string
{
//
case CPU = 'cpu';
case NVIDIA_CUDA = 'nvidia_cuda';

public function getInitialParameters(bool $forMp4Fallback = false): array
{
return array_merge(
match ($this) {
Decoder::NVIDIA_CUDA => [
'-hwaccel', 'cuda',
'-hwaccel_output_format','cuda',
'-extra_hw_frames', config('transmorpher.decoder.nvidia.hwFrames'),
],
default => []
},
config('transmorpher.initial_transcoding_parameters')
);
}
}
39 changes: 39 additions & 0 deletions app/Enums/Encoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Enums;

enum Encoder: string
{
case CPU_H264 = 'cpu_h264';
case CPU_HEVC = 'cpu_hevc';
case NVIDIA_H264 = 'nvidia_h264';
case NVIDIA_HEVC = 'nvidia_hevc';

public function getAdditionalParameters(bool $forMp4Fallback = false): array
{
return array_merge(
$forMp4Fallback ? ['-b:v', config('transmorpher.encoder.bitrate')] : [],
match ($this) {
Encoder::NVIDIA_H264 => [
'-c:v', 'h264_nvenc',
'-preset', config('transmorpher.encoder.nvidia.preset')
],
Encoder::NVIDIA_HEVC => [
// Fallback MP4 video should be h264
'-c:v', $forMp4Fallback ? 'h264_nvenc' : 'hevc_nvenc',
'-preset', config('transmorpher.encoder.nvidia.preset')
],
default => []
},
config('transmorpher.additional_transcoding_parameters')
);
}

public function getStreamingCodec(): string
{
return match ($this) {
Encoder::CPU_H264, Encoder::NVIDIA_H264 => 'x264',
Encoder::CPU_HEVC, Encoder::NVIDIA_HEVC => 'hevc',
};
}
}
5 changes: 3 additions & 2 deletions app/Enums/StreamingFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ enum StreamingFormat: string

/**
* @param StreamingMedia $video
* @param Encoder $encoder
*
* @return Streaming The video configured with the streaming format, codec and representations.
*/
public function configure(StreamingMedia $video): Streaming
public function configure(StreamingMedia $video, Encoder $encoder): Streaming
{
$format = $this->value;
$codec = config('transmorpher.video_codec');
$codec = $encoder->getStreamingCodec();

// GPU accelerated encoding cannot be set via $codec('h264_nvenc'). It may be set through the additional params.
return $video->$format()
Expand Down
23 changes: 15 additions & 8 deletions app/Jobs/TranscodeVideo.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Jobs;

use App\Enums\Decoder;
use App\Enums\Encoder;
use App\Enums\MediaStorage;
use App\Enums\ResponseState;
use App\Enums\StreamingFormat;
Expand Down Expand Up @@ -45,6 +47,9 @@ class TranscodeVideo implements ShouldQueue
protected Filesystem $derivativesDisk;
protected Filesystem $localDisk;

protected Decoder $decoder;
protected Encoder $encoder;

protected string $originalFilePath;
protected string $uploadToken;
// Videos stored in the cloud have to be downloaded for transcoding.
Expand All @@ -69,6 +74,8 @@ public function __construct(
\Log::info(sprintf('Constructing job for media %s and version %s with uploadToken %s.', $version->Media->identifier, $version->getKey(), $uploadSlot->token));
$this->originalFilePath = $version->originalFilePath();
$this->uploadToken = $this->uploadSlot->token;
$this->decoder = Decoder::from(config('transmorpher.decoder.name'));
$this->encoder = Encoder::from(config('transmorpher.encoder.name'));
}

/**
Expand Down Expand Up @@ -152,9 +159,9 @@ protected function transcodeVideo(): void
// Generate MP4.
$this->generateMp4($video);
// Generate HLS
$this->saveVideo(StreamingFormat::HLS->configure($video), StreamingFormat::HLS->value);
$this->saveVideo(StreamingFormat::HLS->configure($video, $this->encoder), StreamingFormat::HLS->value);
// Generate DASH
$this->saveVideo(StreamingFormat::DASH->configure($video), StreamingFormat::DASH->value);
$this->saveVideo(StreamingFormat::DASH->configure($video, $this->encoder), StreamingFormat::DASH->value);

$this->localDisk->delete($this->tempOriginalFilename);
$this->localDisk->deleteDirectory($this->getFfmpegTempDirectory());
Expand All @@ -174,7 +181,7 @@ protected function transcodeVideo(): void
protected function loadVideo(FFMpeg $ffmpeg): StreamingMedia
{
return $this->isLocalFilesystem($this->originalsDisk) ?
$ffmpeg->customInput($this->originalsDisk->path($this->originalFilePath), config('transmorpher.initial_transcoding_parameters'))
$ffmpeg->customInput($this->originalsDisk->path($this->originalFilePath), $this->decoder->getInitialParameters())
: $this->openFromCloud($ffmpeg);
}

Expand All @@ -200,7 +207,7 @@ protected function openFromCloud(FFMpeg $ffmpeg): StreamingMedia
{
$this->localDisk->writeStream($this->tempOriginalFilename, $this->originalsDisk->readStream($this->originalFilePath));

return $ffmpeg->customInput($this->localDisk->path($this->tempOriginalFilename), config('transmorpher.initial_transcoding_parameters'));
return $ffmpeg->customInput($this->localDisk->path($this->tempOriginalFilename), $this->decoder->getInitialParameters());
}

/**
Expand All @@ -225,7 +232,7 @@ protected function saveVideo(Streaming $video, string $format): void
{
$tempDerivativeFilePath = $this->getTempDerivativeFilePath($format);

\Log::info(sprintf('Generating %s for media %s and version %s.', strtoupper($format), $this->version->Media->identifier, $this->version->getKey()));
\Log::info(sprintf('Generating %s for media %s and version %s. Using: %s -> %s', strtoupper($format), $this->version->Media->identifier, $this->version->getKey(), $this->decoder->name, $this->encoder->name));
// Save to temporary folder first, to prevent race conditions when multiple versions are uploaded simultaneously.
if ($this->isLocalFilesystem($this->derivativesDisk)) {
$video->save($this->derivativesDisk->path($tempDerivativeFilePath));
Expand Down Expand Up @@ -259,12 +266,12 @@ protected function saveVideo(Streaming $video, string $format): void
protected function generateMp4(StreamingMedia $video): void
{
$tempMp4Filename = $this->getTempMp4Filename();
\Log::info(sprintf('Generating MP4 for media %s and version %s.', $this->version->Media->identifier, $this->version->getKey()));
\Log::info(sprintf('Generating MP4 for media %s and version %s. Using: %s -> %s', $this->version->Media->identifier, $this->version->getKey(), $this->decoder->name, $this->encoder->name));
// GPU accelerated encoding cannot be set via setVideoCodec(). h264_nvenc may be set through the additional params.
$video->save(
(new X264())
->setInitialParameters(config('transmorpher.initial_transcoding_parameters'))
->setAdditionalParameters(config('transmorpher.additional_transcoding_parameters')),
->setInitialParameters($this->decoder->getInitialParameters(forMp4Fallback: true))
->setAdditionalParameters($this->encoder->getAdditionalParameters(forMp4Fallback: true)),
$this->localDisk->path($tempMp4Filename)
);

Expand Down
64 changes: 47 additions & 17 deletions config/transmorpher.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,56 @@

/*
|--------------------------------------------------------------------------
| Video Codec
| Video Decoder
|--------------------------------------------------------------------------
|
| The codec used when transcoding videos to HLS and DASH.
| This does not affect the codec used for MP4, which is x264 since it's the only one supported by the PHP-FFmpeg package for MP4.
| The decoder used when transcoding videos.
|
| You can choose from:
| x264, hevc
| cpu, nvidia_cuda
|
| nvidia notes:
| - Requires the according hardware and driver setup on the host machine. See: https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC
| - GPU video decoding is experimental and unstable
*/
'video_codec' => 'x264',
'decoder' => [
'name' => env('TRANSMORPHER_VIDEO_DECODER', 'cpu'),
'nvidia' => [
'hwFrames' => env('TRANSMORPHER_VIDEO_DECODER_NVIDIA_HWFRAMES', 10),
],
],

/*
|--------------------------------------------------------------------------
| Video Encoder
|--------------------------------------------------------------------------
|
| The encoder used when transcoding videos.
|
| This controls the codec used when transcoding videos to HLS and DASH, as well as the device used.
| For the MP4 fallback file, h264 is used because
| - FFmpeg only supports hevc in MP4 files when encoding with a GPU.
| - h264 is the most widely supported codec, and this file is to be used when a client does not support HLS or DASH.
|
| You can choose from:
| cpu_h264, cpu_hevc, nvidia_h264, nvidia_hevc
|
| bitrate:
| - setting may be ignored for the DASH/HLS streaming formats.
| - For suitable bit rates, see: https://help.twitch.tv/s/article/broadcast-guidelines?language=de#recommended
|
| nvidia notes:
| - Requires the according hardware and driver setup on the host machine. See: https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC
| - Higher preset numbers are higher quality and slower. For an encoder specific list see: ffmpeg -h encoder=h264_nvenc
| - Further information: https://docs.nvidia.com/video-technologies/video-codec-sdk/pdf/Using_FFmpeg_with_NVIDIA_GPU_Hardware_Acceleration.pdf
*/
'encoder' => [
'name' => env('TRANSMORPHER_VIDEO_ENCODER', 'cpu_h264'),
'bitrate' => env('TRANSMORPHER_VIDEO_ENCODER_BITRATE', '6000k'),
'nvidia' => [
'preset' => env('TRANSMORPHER_VIDEO_ENCODER_NVIDIA_PRESET', 'p4'),
],
],

/*
|--------------------------------------------------------------------------
Expand All @@ -116,12 +155,10 @@
|--------------------------------------------------------------------------
|
| These parameters will be added to the FFmpeg transcoding command before the input parameter.
| They take precedence over other options in this file.
*/
'initial_transcoding_parameters' => [
// GPU decoding has issues
// '-hwaccel', 'cuda',
// '-hwaccel_output_format','cuda',
// '-extra_hw_frames', '10',
//
],

/*
Expand All @@ -130,21 +167,14 @@
|--------------------------------------------------------------------------
|
| These parameters will be added to the FFmpeg transcoding command.
| They take precedence over other options in this file.
|
| -dn: omit data streams (e.g. timecodes). Transcoding sometimes failed when data streams were not omitted.
| -map -0:t?: omit attachments (e.g. metadata files). Metadata should not be publicly available.
| -sn: omit subtitles. Subtitles would need an encoder configuration for DASH, and possibly HLS.
*/
'additional_transcoding_parameters' => [
'-dn', '-map', '-0:t?', '-sn',

// GPU encoding
// https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC
// only sets the encoder
'-c:v', 'h264_nvenc',
// https://docs.nvidia.com/video-technologies/video-codec-sdk/pdf/Using_FFmpeg_with_NVIDIA_GPU_Hardware_Acceleration.pdf
// p4 is the default, medium quality preset
// '-preset', 'p4',
],

/*
Expand Down

0 comments on commit 9fba1cd

Please sign in to comment.