mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-11-06 19:03:33 +01:00
273 lines
25 KiB
PHP
273 lines
25 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Domain\Stream\StreamProcess;
|
|
use XcVm\Tests\Support\InstallSchema;
|
|
|
|
/**
|
|
* A transcode profile's free-text values (scaling, aspect ratio, the GPU
|
|
* resize) reach ffmpeg as they were typed, each inside the one argument its
|
|
* option takes: the profile page stores them quoted for the shell
|
|
* (ProfileService::process), and the logo filter quotes the scaling it reads
|
|
* from the profile (StreamProcess::buildLogoFilterOptions). Its codecs, preset
|
|
* and video profile are names, stored and written as plain text: a profile
|
|
* is saved only when each is one (a video profile may carry its level).
|
|
*
|
|
* The form is saved in a child PHP into the install schema's `profiles`
|
|
* table, and the stored options are run through buildLive() and /bin/sh with
|
|
* a program in ffmpeg's place that records what it received. What it
|
|
* receives for ordinary values is kept here as it has always been (EXPECTED).
|
|
*/
|
|
final class AuditStreamCmdProfileOptionsTest extends TestCase {
|
|
/** The profile page's fields, as it posts them empty. */
|
|
private const FORM = [
|
|
'profile_name' => 'Profile', 'gpu_device' => '0', 'software_decoding' => '0', 'resize' => '', 'deint' => '0',
|
|
'video_codec_gpu' => '', 'preset_' => '', 'preset_h264' => '', 'preset_hevc' => '', 'video_profile_' => '', 'video_profile_h264' => '', 'video_profile_hevc' => '',
|
|
'video_codec_cpu' => '', 'preset_cpu' => '', 'video_profile_cpu' => '', 'audio_codec' => '',
|
|
'video_bitrate' => '', 'audio_bitrate' => '', 'min_tolerance' => '', 'max_tolerance' => '', 'buffer_size' => '', 'crf_value' => '',
|
|
'aspect_ratio' => '', 'framerate' => '', 'samplerate' => '', 'audio_channels' => '', 'threads' => '',
|
|
'logo_path' => '', 'logo_pos' => '', 'scaling' => '',
|
|
];
|
|
|
|
private const CPU = ['video_codec_cpu' => 'libx264', 'preset_cpu' => 'veryfast', 'video_profile_cpu' => 'main', 'audio_codec' => 'aac', 'video_bitrate' => '2500'];
|
|
|
|
private const GPU = ['gpu_device' => '1_0', 'video_codec_gpu' => 'h264_nvenc', 'preset_h264' => 'fast', 'video_profile_h264' => 'high', 'audio_codec' => 'aac'];
|
|
|
|
private const LOGO = ['logo_path' => '/home/xc_vm/logos/my logo.png', 'logo_pos' => '20:30'];
|
|
|
|
private string $rDir;
|
|
|
|
private TestDb $rDb;
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
foreach (['SERVER_ID' => 1, 'STREAMS_PATH' => '/tmp/xcvm-test-streams/', 'DELAY_PATH' => '/tmp/xcvm-test-delay/'] as $rName => $rValue) {
|
|
if (!defined($rName)) {
|
|
define($rName, $rValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
$this->rDir = sys_get_temp_dir() . '/xcvm-profile-options-' . bin2hex(random_bytes(4)) . '/';
|
|
mkdir($this->rDir, 0700, true);
|
|
// Records its arguments, one per NUL.
|
|
file_put_contents($this->rDir . 'program', "#!/bin/sh\nfor a in \"\$@\"; do printf '%s\\000' \"\$a\"; done > " . escapeshellarg($this->rDir . 'argv') . "\n");
|
|
chmod($this->rDir . 'program', 0755);
|
|
$this->rDb = new TestDb();
|
|
$this->rDb->exec(InstallSchema::table('profiles'));
|
|
file_put_contents($this->rDir . 'save.php', <<<'PHP'
|
|
<?php
|
|
use XcVm\Core\Database\DatabaseHandler;
|
|
use XcVm\Domain\Stream\ProfileService;
|
|
use XcVm\Infrastructure\Database\DatabaseFactory;
|
|
|
|
foreach (['STATUS_FAILURE' => 0, 'STATUS_SUCCESS' => 1, 'STATUS_INVALID_INPUT' => 34] as $rName => $rValue) {
|
|
define($rName, $rValue);
|
|
}
|
|
require getenv('XCVM_TEST_BOOTSTRAP');
|
|
DatabaseFactory::set(new class(TestDb::connect(getenv('XCVM_TEST_SCHEMA'))) extends DatabaseHandler {
|
|
public function __construct(\PDO $rPdo) {
|
|
$this->dbh = $rPdo;
|
|
}
|
|
});
|
|
$rStatuses = [];
|
|
foreach (json_decode((string) file_get_contents(__DIR__ . '/forms.json'), true) as $rForm) {
|
|
$rStatuses[] = ProfileService::process($rForm)['status'];
|
|
}
|
|
echo "\nSAVED " . json_encode($rStatuses) . "\n";
|
|
PHP);
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
exec('rm -rf ' . escapeshellarg($this->rDir));
|
|
}
|
|
|
|
/**
|
|
* Post the profile page's form, once per form given.
|
|
*
|
|
* @param list<array<string, string>> $rForms
|
|
* @return list<int> The status each save answered.
|
|
*/
|
|
private function statuses(array $rForms): array {
|
|
file_put_contents($this->rDir . 'forms.json', json_encode(array_map(static fn(array $rForm): array => $rForm + self::FORM, $rForms)));
|
|
$rEnv = TestDb::env() + ['XCVM_TEST_BOOTSTRAP' => dirname(__DIR__) . '/bootstrap.php', 'XCVM_TEST_SCHEMA' => $this->rDb->schema(), 'PATH' => (string) getenv('PATH')];
|
|
$rProc = proc_open([PHP_BINARY, $this->rDir . 'save.php'], [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $rPipes, $this->rDir, $rEnv);
|
|
$this->assertIsResource($rProc);
|
|
$rOut = (string) stream_get_contents($rPipes[1]) . (string) stream_get_contents($rPipes[2]);
|
|
fclose($rPipes[1]);
|
|
fclose($rPipes[2]);
|
|
proc_close($rProc);
|
|
$this->assertSame(1, preg_match('/^SAVED (\[[\d,]*\])$/m', $rOut, $rMatch), $rOut);
|
|
return json_decode($rMatch[1], true);
|
|
}
|
|
|
|
/**
|
|
* Save the profile page's form, in place of any profile before it.
|
|
*
|
|
* @return array<int|string, mixed> The options the profile now stores.
|
|
*/
|
|
private function save(array $rForm): array {
|
|
$this->rDb->exec('DELETE FROM `profiles`');
|
|
$this->assertSame([1], $this->statuses([$rForm]));
|
|
$this->rDb->query('SELECT `profile_options` FROM `profiles`;');
|
|
return json_decode((string) $this->rDb->get_row()['profile_options'], true);
|
|
}
|
|
|
|
/**
|
|
* What the program receives for a live stream with this profile, on a
|
|
* source with H.264 video: the line is built for the fanout supervisor,
|
|
* so it has no redirect or background tail.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
private function received(array $rForm): array {
|
|
$rLine = (new ReflectionMethod(StreamProcess::class, 'buildLive'))->invoke(null, [
|
|
'stream' => [
|
|
'stream_info' => [
|
|
'custom_ffmpeg' => '', 'stream_all' => 0, 'custom_map' => '', 'type_key' => 'live', 'gen_timestamps' => 0, 'read_native' => 0,
|
|
'enable_transcode' => 1, 'transcode_profile_id' => 1, 'transcode_attributes' => '[]', 'profile_options' => json_encode($this->save($rForm)),
|
|
'delay_minutes' => 0, 'rtmp_output' => 0, 'external_push' => '[]',
|
|
],
|
|
'server_info' => ['parent_id' => 0, 'server_id' => 1],
|
|
'stream_arguments' => [],
|
|
],
|
|
'settings' => ['ffmpeg_warnings' => 0, 'read_native_hls' => 0, 'dts_legacy_ffmpeg' => 0, 'ignore_keyframes' => 0],
|
|
'servers' => [1 => ['rtmp_port' => 1935]],
|
|
'streamID' => 42, 'streamSource' => 'http://src.example/live.ts', 'source' => 'http://src.example/live.ts', 'protocol' => 'http', 'fetchOptions' => '',
|
|
'ffprobe' => ['container' => 'mpegts', 'codecs' => ['video' => ['codec_name' => 'h264'], 'audio' => ['codec_name' => 'aac']]],
|
|
'segmentSettings' => ['seg_time' => 6, 'seg_list_size' => 8, 'seg_delete_threshold' => 4],
|
|
'externalPush' => [], 'probesize' => 1000000, 'analyseDuration' => 500000, 'llod' => false, 'loopback' => false, 'segmentStart' => 0, 'delayActive' => false,
|
|
'ffmpegCpu' => $this->rDir . 'program', 'ffmpegGpu' => $this->rDir . 'program', 'supervised' => true,
|
|
]);
|
|
$rProcess = proc_open(['/bin/sh', '-c', $rLine], [0 => ['file', '/dev/null', 'r'], 1 => ['file', '/dev/null', 'w'], 2 => ['file', '/dev/null', 'w']], $rPipes, $this->rDir);
|
|
proc_close($rProcess);
|
|
$rFile = $this->rDir . 'argv';
|
|
$rArgv = file_exists($rFile) ? explode("\0", (string) file_get_contents($rFile), -1) : [];
|
|
@unlink($rFile);
|
|
return $rArgv;
|
|
}
|
|
|
|
// ── ordinary values: the program receives what it always has ───────────
|
|
|
|
public static function ordinaryProfiles(): array {
|
|
return [
|
|
'a CPU profile with a scaling and an aspect' => [self::CPU + ['scaling' => '1280:720', 'aspect_ratio' => '16:9', 'framerate' => '25', 'threads' => '0']],
|
|
'a CPU profile with a scaling expression' => [self::CPU + ['scaling' => 'trunc(iw/2)*2:-2', 'aspect_ratio' => '4:3']],
|
|
'a CPU profile deinterlacing' => [self::CPU + ['yadif_filter' => '1']],
|
|
'a GPU profile resizing' => [self::GPU + ['resize' => '1280x720']],
|
|
'a GPU profile deinterlacing' => [self::GPU + ['deint' => '2']],
|
|
'a CPU profile with a logo, a scaling and deinterlacing' => [self::CPU + self::LOGO + ['scaling' => '1280:720', 'yadif_filter' => '1']],
|
|
'a CPU profile with a logo and a scaling expression' => [self::CPU + self::LOGO + ['scaling' => 'trunc(iw/2)*2:-2']],
|
|
'a GPU profile with a logo and a resize' => [self::GPU + self::LOGO + ['resize' => '1280:720']],
|
|
// The page's video profiles carry their level: two options from the one value.
|
|
'a CPU profile with a level' => [['video_profile_cpu' => 'main -level 4.0'] + self::CPU],
|
|
'a GPU profile with a level' => [['video_codec_gpu' => 'hevc_nvenc', 'preset_hevc' => 'slow', 'video_profile_hevc' => 'main10 -level 5.1'] + self::GPU],
|
|
];
|
|
}
|
|
|
|
/** What the program receives, with this run's own directories named so it reads the same wherever they are. */
|
|
private function ordinary(array $rForm): array {
|
|
return str_replace([STREAMS_PATH, DELAY_PATH], ['<streams>/', '<delay>/'], $this->received($rForm));
|
|
}
|
|
|
|
#[DataProvider('ordinaryProfiles')]
|
|
public function testAnOrdinaryProfileReachesTheProgramUnchanged(array $rForm): void {
|
|
$this->assertArrayHasKey($this->dataName(), self::EXPECTED);
|
|
$this->assertSame(self::EXPECTED[$this->dataName()], $this->ordinary($rForm));
|
|
}
|
|
|
|
public function testEveryExpectedProfileHasItsCase(): void {
|
|
$this->assertSame(array_keys(self::ordinaryProfiles()), array_keys(self::EXPECTED));
|
|
}
|
|
|
|
// ── any value: one argument, as typed ──────────────────────────────────
|
|
|
|
public static function freeTextValues(): array {
|
|
return [
|
|
'an aspect with a space' => [self::CPU, 'aspect_ratio', '16:9 -vn'],
|
|
'an aspect with shell syntax' => [self::CPU, 'aspect_ratio', '16:9" \'q\' $HOME; echo ran'],
|
|
'a scaling with a space' => [self::CPU, 'scaling', '1280:720 -an'],
|
|
'a scaling expression in quotes' => [self::CPU, 'scaling', "'min(1280,iw)':-2"],
|
|
'a scaling with one quote' => [self::CPU, 'scaling', "1280:720' -an"],
|
|
'a GPU resize with a space' => [self::GPU, 'resize', '1280x720 -an'],
|
|
// parseTranscode() gathers every -filter_complex "..." it finds in an option's text.
|
|
'an aspect naming a filter' => [self::CPU, 'aspect_ratio', '16:9 -filter_complex "nullsrc"'],
|
|
'a scaling naming a filter' => [self::CPU, 'scaling', '1280:720 -filter_complex "$(touch ran-1)"'],
|
|
'a GPU resize naming a filter' => [self::GPU, 'resize', '1280x720 -filter_complex "nullsrc"'],
|
|
'a scaling beside a logo, with a double quote' => [self::CPU + self::LOGO, 'scaling', '1280:720" -an "'],
|
|
'a scaling beside a logo, with shell syntax' => [self::CPU + self::LOGO, 'scaling', '$(touch ran-1):`touch ran-2`\\'],
|
|
'a scaling beside a logo, deinterlaced, with a double quote' => [self::CPU + self::LOGO + ['yadif_filter' => '1'], 'scaling', '1280:720" -an "'],
|
|
'a GPU resize beside a logo, with shell syntax' => [self::GPU + self::LOGO, 'resize', '$(touch ran-1)" -an "'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('freeTextValues')]
|
|
public function testAFreeTextValueIsOneArgumentAsTyped(array $rForm, string $rField, string $rValue): void {
|
|
// What the program receives for a plain word in that field, with the word exchanged for the value.
|
|
$rExpected = str_replace('VALUE', $rValue, $this->received($rForm + [$rField => 'VALUE']));
|
|
$this->assertContains('-i', $rExpected, 'the program ran');
|
|
$this->assertSame($rExpected, $this->received($rForm + [$rField => $rValue]));
|
|
$this->assertSame([], glob($this->rDir . 'ran-*'), 'nothing but the program ran');
|
|
}
|
|
|
|
// ── a codec, a preset, a video profile: a name, a profile with its level ─
|
|
|
|
public static function valuesThatAreNotAName(): array {
|
|
return [
|
|
'a video codec with a space' => [self::CPU, 'video_codec_cpu', 'libx264 -an'],
|
|
'an audio codec with a space' => [self::CPU, 'audio_codec', 'aac -vn'],
|
|
'a preset with a space' => [self::CPU, 'preset_cpu', 'veryfast -f mpegts'],
|
|
'a video profile with an option that is not its level' => [self::CPU, 'video_profile_cpu', 'main -an'],
|
|
'a video profile with an option after its level' => [self::CPU, 'video_profile_cpu', 'main -level 4.0 -an'],
|
|
'a GPU codec with a space' => [self::GPU, 'video_codec_gpu', 'h264_nvenc -an'],
|
|
'a GPU preset with a space' => [self::GPU, 'preset_h264', 'fast -an'],
|
|
'a GPU video profile with a space' => [self::GPU, 'video_profile_h264', 'high -an'],
|
|
'a GPU codec with a space, decoding in software' => [self::GPU + ['software_decoding' => '1'], 'video_codec_gpu', 'h264_nvenc -an'],
|
|
'an audio codec naming a filter' => [self::CPU, 'audio_codec', 'aac -filter_complex "nullsrc"'],
|
|
'a video codec that ends its line' => [self::CPU, 'video_codec_cpu', "libx264\n"],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('valuesThatAreNotAName')]
|
|
public function testACodecPresetOrVideoProfileThatIsNotANameIsNotSaved(array $rForm, string $rField, string $rValue): void {
|
|
$this->assertSame([34], $this->statuses([[$rField => $rValue] + $rForm]));
|
|
$this->rDb->query('SELECT COUNT(*) AS `saved` FROM `profiles`;');
|
|
$this->assertSame(0, (int) $this->rDb->get_row()['saved']);
|
|
}
|
|
|
|
public function testEveryNameTheProfilePageOffersIsSaved(): void {
|
|
// Each select of the page with the values it lists, and a form in which the save reads that select.
|
|
$rUsedBy = [
|
|
'video_codec_cpu' => [], 'preset_cpu' => [], 'video_profile_cpu' => [], 'audio_codec' => [],
|
|
'video_codec_gpu' => self::GPU, 'preset_h264' => self::GPU, 'video_profile_h264' => self::GPU,
|
|
'preset_hevc' => ['video_codec_gpu' => 'hevc_nvenc'] + self::GPU, 'video_profile_hevc' => ['video_codec_gpu' => 'hevc_nvenc'] + self::GPU,
|
|
];
|
|
$rView = (string) file_get_contents(dirname(__DIR__, 2) . '/src/Public/Views/admin/profile.php');
|
|
preg_match_all('/<select id="(' . implode('|', array_keys($rUsedBy)) . ')"[^>]*>\s*<\?php foreach \(\[(.*?)\] as /s', $rView, $rSelects, PREG_SET_ORDER);
|
|
$this->assertSame(array_keys($rUsedBy), array_values(array_intersect(array_keys($rUsedBy), array_column($rSelects, 1))), 'every select was found');
|
|
$rForms = [];
|
|
foreach ($rSelects as [, $rField, $rList]) {
|
|
preg_match_all("/'([^']*)' =>/", $rList, $rValues);
|
|
foreach ($rValues[1] as $rValue) {
|
|
$rForms[$rField . '=' . $rValue] = [$rField => $rValue] + $rUsedBy[$rField];
|
|
}
|
|
}
|
|
$this->assertArrayHasKey('video_profile_hevc=rext -level 6.2', $rForms);
|
|
$this->assertSame(array_fill_keys(array_keys($rForms), 1), array_combine(array_keys($rForms), $this->statuses(array_values($rForms))));
|
|
}
|
|
|
|
private const EXPECTED = [
|
|
'a CPU profile with a scaling and an aspect' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-sn', '-threads', '0', '-vf', 'scale=1280:720', '-aspect', '16:9', '-r', '25', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a CPU profile with a scaling expression' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-sn', '-aspect', '4:3', '-vf', 'scale=trunc(iw/2)*2:-2', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a CPU profile deinterlacing' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-vf', 'yadif', '-sn', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a GPU profile resizing' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-hwaccel', 'cuvid', '-hwaccel_device', '0', '-resize', '1280x720', '-c:v', 'h264_cuvid', '-gpu', '0', '-drop_second_field', '1', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-gpu', '0', '-sn', '-vf', 'scale=1280x720', '-acodec', 'aac', '-profile:v', 'high', '-preset', 'fast', '-vcodec', 'h264_nvenc', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a GPU profile deinterlacing' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-hwaccel', 'cuvid', '-hwaccel_device', '0', '-deint', '2', '-c:v', 'h264_cuvid', '-gpu', '0', '-drop_second_field', '1', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-gpu', '0', '-sn', '-vf', 'yadif', '-acodec', 'aac', '-profile:v', 'high', '-preset', 'fast', '-vcodec', 'h264_nvenc', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a CPU profile with a logo, a scaling and deinterlacing' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-i', '/home/xc_vm/logos/my logo.png', '-filter_complex', '[0:v]yadif,scale=1280:720[bg]; [1:v]scale=250:-1[logo]; [bg][logo]overlay=20:30', '-max_muxing_queue_size', '1024', '-sn', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a CPU profile with a logo and a scaling expression' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-i', '/home/xc_vm/logos/my logo.png', '-filter_complex', '[0:v]scale=trunc(iw/2)*2:-2[bg]; [1:v]scale=250:-1[logo]; [bg][logo]overlay=20:30', '-max_muxing_queue_size', '1024', '-sn', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a GPU profile with a logo and a resize' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-hwaccel', 'cuvid', '-hwaccel_device', '0', '-resize', '1280:720', '-c:v', 'h264_cuvid', '-gpu', '0', '-drop_second_field', '1', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-i', '/home/xc_vm/logos/my logo.png', '-filter_complex', '[0:v]scale=1280:720[bg]; [1:v]scale=250:-1[logo]; [bg][logo]overlay=20:30', '-max_muxing_queue_size', '1024', '-gpu', '0', '-sn', '-acodec', 'aac', '-preset', 'fast', '-profile:v', 'high', '-vcodec', 'h264_nvenc', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a CPU profile with a level' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-sn', '-b:v', '2500k', '-acodec', 'aac', '-profile:v', 'main', '-level', '4.0', '-preset', 'veryfast', '-vcodec', 'libx264', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
'a GPU profile with a level' => ['-y', '-nostdin', '-hide_banner', '-loglevel', 'error', '-err_detect', 'ignore_err', '-thread_queue_size', '1024', '-hwaccel', 'cuvid', '-hwaccel_device', '0', '-c:v', 'h264_cuvid', '-gpu', '0', '-drop_second_field', '1', '-start_at_zero', '-copyts', '-vsync', '0', '-correct_ts_overflow', '0', '-avoid_negative_ts', 'disabled', '-max_interleave_delta', '0', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-probesize', '1000000', '-analyzeduration', '500000', '-progress', '<streams>/42_.progress', '-i', 'http://src.example/live.ts', '-max_muxing_queue_size', '1024', '-gpu', '0', '-sn', '-acodec', 'aac', '-preset', 'slow', '-profile:v', 'main10', '-level', '5.1', '-vcodec', 'hevc_nvenc', '-individual_header_trailer', '0', '-f', 'hls', '-hls_init_time', '2', '-hls_time', '6', '-hls_list_size', '8', '-hls_delete_threshold', '4', '-hls_flags', 'delete_segments+discont_start+omit_endlist', '-hls_segment_type', 'mpegts', '-hls_segment_filename', '<streams>/42_%d.ts', '<streams>/42_.m3u8'],
|
|
];
|
|
}
|