Сведение слоёв распаковывает исходник видеокартой

Затухание в полторы-две минуты собиралось минутами, и всё это время
предпросмотр ждёт. Виноваты оказались не фильтры: на десяти секундах
2688x1512 цепочка со сведением стоит 13.5 с, а вовсе без фильтров —
12.7 с. Время уходит на разбор HEVC процессором.

Ключ -hwaccel cuda перед входом убирает почти всё: настоящее затухание
на семьдесят секунд собирается 18.3 с вместо 75.9 с, картинка бит в бит
та же (одинаковый md5 потока, 4196 кадров там и там).

Просится он только там, где кодировать будет та же карта — то есть
выбран NVENC. На машине без неё ключ не появляется вовсе. Сверх того у
подготовки вставок есть откат: не пошло — повторяем на процессоре, как
это уже делает склейка с перекодированием.

Сборка ролика пока по-старому: там кусок с наложением идёт в общий
поток, и повторить его после отказа посреди работы нельзя — данные уже
ушли в трубу. Это отдельная задача.
This commit is contained in:
4aiman 2026-10-01 15:11:43 +03:00
commit 8d1cb9af5c
4 changed files with 109 additions and 5 deletions

View file

@ -840,6 +840,14 @@ Wine с новой библиотекой не грузилось ничего.
привычный RGB. Записанный по привычке код даёт цвет с переставленными
красным и синим.
**Разбор HEVC на процессоре — самое дорогое в сведении слоёв.** Не
фильтры, как кажется: на десяти секундах 2688x1512 цепочка со сведением
стоит 13.5 с, а без всяких фильтров — 12.7 с. Всё время уходит на разбор
исходника. Ключ `-hwaccel cuda` перед входом убирает его почти целиком:
затухание на семьдесят секунд собирается 18.3 с вместо 75.9 с, картинка
бит в бит та же. Просить его стоит только там, где кодирует та же карта
(выбран NVENC): иначе на машине без неё пришлось бы ловить отказ.
**Список недостающего — не список нужного.** Недостающее у нас — это то,
чего нет на диске. Но у куска, который делается прямо сейчас, файл уже
есть: ffmpeg создаёт его в первую же секунду и пишет до конца. Проверка

View file

@ -198,13 +198,19 @@ function ArgsEncodeAudio(const ASource, ATarget: string;
ASubFile — разметка текста, которую надо вжечь в этот кусок. Пусто — не
вжигать. Без этого текст пропадал везде, где есть наложение: кусок шёл
этой веткой, а она про субтитры не знала. }
этой веткой, а она про субтитры не знала.
AHwDecode — распаковывать исходники видеокартой. На длинном затухании
это разница между тремя минутами и одной: замерено на HEVC 2688x1512,
13.5 с против 3.3 с на десяти секундах материала, картинка бит в бит
та же. Признак необязательный: не пойдёт — вызывающий повторит без
него, как это уже делает склейка с перекодированием. }
function ArgsRenderLayers(const ALayers: array of TSegmentLayer;
const ATarget: string; const ADuration: Double;
AWidth, AHeight, AFpsNum, AFpsDen: Integer;
const APixFmt, ACodec: string; AIsTs: Boolean;
ABitrate: Int64 = 0; const ASubFile: string = '';
AAt: Double = NotPiped): TArgs;
AAt: Double = NotPiped; AHwDecode: Boolean = False): TArgs;
{ Сшивание готовой картинки с готовым звуком. Оба потока копируются.
@ -357,11 +363,27 @@ function ArgsRenderLayers(const ALayers: array of TSegmentLayer;
const ATarget: string; const ADuration: Double;
AWidth, AHeight, AFpsNum, AFpsDen: Integer;
const APixFmt, ACodec: string; AIsTs: Boolean;
ABitrate: Int64; const ASubFile: string; AAt: Double): TArgs;
ABitrate: Int64; const ASubFile: string; AAt: Double;
AHwDecode: Boolean): TArgs;
var
I: Integer;
Graph, Fit, Src, OutLabel: string;
Crossfade: Boolean;
{ Распаковка на видеокарте. Ключ входной, поэтому ставится перед
каждым входом отдельно.
Просим её только там, где кодировать будет та же карта: раз NVENC
выбран, значит карта есть и драйвер её видит. На машине без неё ключ
не появится вовсе, и просить откат будет не о чем. }
function HwArgs: TArgs;
begin
if AHwDecode and (Pos('nvenc', PreferredEncoder(ACodec)) > 0) then
Result := ['-hwaccel', 'cuda']
else
Result := nil;
end;
begin
Result := ['-hide_banner', '-nostdin', '-loglevel', 'warning', '-y'];
@ -379,7 +401,7 @@ begin
'-t', SecondsToStr(ADuration),
'-i', ALayers[I].FilePath])
else
Result := Concat(Result,
Result := Concat(Concat(Result, HwArgs),
['-ss', SecondsToStr(ALayers[I].SrcStart),
'-t', SecondsToStr(ADuration),
'-i', ALayers[I].FilePath]);

View file

@ -262,7 +262,8 @@ begin
Args := ArgsRenderLayers(FRequests[I].Layers,
FRequests[I].TargetPath, FRequests[I].Duration,
FMaster.Width, FMaster.Height, FMaster.FpsNum, FMaster.FpsDen,
FMaster.PixFmt, FMaster.VideoCodec, False, FMaster.VideoBitrate);
FMaster.PixFmt, FMaster.VideoCodec, False, FMaster.VideoBitrate,
'', NotPiped, True);
else
Continue;
end;
@ -270,6 +271,24 @@ begin
Log('> ' + CommandToString(FfmpegExe, Args));
Res := RunProcess(FfmpegExe, Args, nil, @ErrorLine, @CancelQuery);
{ Распаковка на видеокарте не пошла — повторяем на процессоре.
Она даёт вчетверо на длинном затухании, но зависит от драйвера и
от того, знаком ли видеокарте кодек исходника. Отказываться из-за
неё от сборки нельзя: медленно лучше, чем никак. }
if (FRequests[I].Kind = gkLayers) and (not Res.Cancelled) and
(Res.Started and (Res.ExitCode <> 0)) then
begin
Log('Распаковка на видеокарте не пошла, повторяю на процессоре.');
Args := ArgsRenderLayers(FRequests[I].Layers,
FRequests[I].TargetPath, FRequests[I].Duration,
FMaster.Width, FMaster.Height, FMaster.FpsNum, FMaster.FpsDen,
FMaster.PixFmt, FMaster.VideoCodec, False, FMaster.VideoBitrate,
'', NotPiped, False);
Log('> ' + CommandToString(FfmpegExe, Args));
Res := RunProcess(FfmpegExe, Args, nil, @ErrorLine, @CancelQuery);
end;
if Res.Cancelled then
begin
FCancelled := True;

View file

@ -15,6 +15,7 @@ uses
{ Interfaces нужен, чтобы связалась вся LCL и заработала загрузка
файлов форм. }
Interfaces, Forms, Controls, Classes, SysUtils, Math, TypInfo, uTheme,
uFfmpegCmd, uAppTypes,
uMediaProbe, uProject, uTransform, uTimelineCtrl,
uPanCropForm, uDepsForm, uMismatchForm, uProgressForm, uProjectInfoForm,
uRemuxForm;
@ -844,6 +845,59 @@ begin
TransformFitsOutputCrop(T));
end;
{ Сведение слоёв просит распаковку у видеокарты.
На длинном затухании это разница между тремя минутами и одной, а
ключ входной: ffmpeg принимает его только перед тем входом, к которому
он относится. Забыть его легко и незаметно — команда останется рабочей,
просто медленной. }
procedure CheckLayerHwDecode;
var
Layers: array[0..0] of TSegmentLayer;
A: TArgs;
I, Hw, Input: Integer;
begin
WriteLn;
WriteLn('Распаковка слоёв видеокартой');
WriteLn;
Layers[0] := Default(TSegmentLayer);
Layers[0].FilePath := '/несуществующий/видео.mp4';
Layers[0].SrcStart := 10;
Layers[0].FadeFrom := 1;
Layers[0].FadeTo := 0;
A := ArgsRenderLayers(Layers, '/выход.mkv', 70, 2688, 1512, 60000, 1001,
'yuv420p', 'hevc', False, 0, '', NotPiped, False);
Hw := -1;
for I := 0 to High(A) do
if A[I] = '-hwaccel' then
Hw := I;
CheckTrue('без просьбы ключа нет', Hw < 0);
A := ArgsRenderLayers(Layers, '/выход.mkv', 70, 2688, 1512, 60000, 1001,
'yuv420p', 'hevc', False, 0, '', NotPiped, True);
Hw := -1;
Input := -1;
for I := 0 to High(A) do
begin
if A[I] = '-hwaccel' then
Hw := I;
if (A[I] = '-i') and (Input < 0) then
Input := I;
end;
{ Просьба выполняется только там, где кодировать будет та же карта. На
машине без неё проверять нечего, и это не поломка. }
if Pos('nvenc', PreferredEncoder('hevc')) = 0 then
WriteLn(' -- видеокарты для кодирования нет, проверять нечего')
else
begin
CheckTrue('по просьбе ключ появляется', Hw >= 0);
CheckTrue('и стоит перед входом, а не после', (Hw >= 0) and (Hw < Input));
end;
end;
procedure CheckButtonClicks;
var
Btn: TButtonProbe;
@ -904,6 +958,7 @@ begin
CheckSplitAllTracks;
CheckGapTolerance;
CheckSourceRect;
CheckLayerHwDecode;
CheckButtonClicks;
WriteLn;