diff --git a/README.md b/README.md index e462982..2465c7e 100644 --- a/README.md +++ b/README.md @@ -840,6 +840,14 @@ Wine с новой библиотекой не грузилось ничего. привычный RGB. Записанный по привычке код даёт цвет с переставленными красным и синим. +**Разбор HEVC на процессоре — самое дорогое в сведении слоёв.** Не +фильтры, как кажется: на десяти секундах 2688x1512 цепочка со сведением +стоит 13.5 с, а без всяких фильтров — 12.7 с. Всё время уходит на разбор +исходника. Ключ `-hwaccel cuda` перед входом убирает его почти целиком: +затухание на семьдесят секунд собирается 18.3 с вместо 75.9 с, картинка +бит в бит та же. Просить его стоит только там, где кодирует та же карта +(выбран NVENC): иначе на машине без неё пришлось бы ловить отказ. + **Список недостающего — не список нужного.** Недостающее у нас — это то, чего нет на диске. Но у куска, который делается прямо сейчас, файл уже есть: ffmpeg создаёт его в первую же секунду и пишет до конца. Проверка diff --git a/src/uFfmpegCmd.pas b/src/uFfmpegCmd.pas index bb7735d..d9846ca 100644 --- a/src/uFfmpegCmd.pas +++ b/src/uFfmpegCmd.pas @@ -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]); diff --git a/src/uGenerate.pas b/src/uGenerate.pas index 3b74307..cc7ff28 100644 --- a/src/uGenerate.pas +++ b/src/uGenerate.pas @@ -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; diff --git a/tests/lfmcheck/lfmcheck.lpr b/tests/lfmcheck/lfmcheck.lpr index b9d1dca..2d41043 100644 --- a/tests/lfmcheck/lfmcheck.lpr +++ b/tests/lfmcheck/lfmcheck.lpr @@ -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;