Вставки тишины для звука делаются без картинки
Звук пропадал кусками, и причина оказалась не в громкости и не в раскладке дорожек: обе были верны. Дыры на звуковой дорожке заполнялись тем же чёрным клипом, что и дыры на видеодорожке, а опорные кадры в нём стоят через 4.171 с. Перемотка внутри такой вставки откатывается к ближайшему опорному кадру, дорожка начинает отдавать звук на несколько секунд раньше соседних, смешивание равняется по ней — и содержимое соседней дорожки в это окно не попадает. Замерено на файле, без запуска окна. Смешивание трёх дорожек проекта: старт 0 с: -23.3 dB старт 24 с: -91.0 dB (полная тишина) старт 12 с: -10.1 dB старт 30 с: -18.0 dB При этом та же дорожка A2 отдельно на 24-й секунде даёт -11.7 dB, то есть содержимое на месте. Смешивание A2 с A4 работает, A1 с A2 — нет: у A1 в этой точке идёт длинная чёрная вставка, и перемотка в ней откатывается на 3.1 с назад. Теперь у звуковых дорожек своя вставка — anullsrc в звуковом кодеке проекта, без видеопотока. В звуке опорным является каждый кадр, и отката нет. После правки во всех проверенных точках звук есть: 0 с -24.1 7 с -19.0 12 с -11.9 18 с -17.5 24 с -11.7 25 с -9.6 30 с -9.5 40 с -15.6 60 с -19.5 72 с -15.9 Сборка ролика от этого не страдает: она декодирует куски в отсчёты, и файл без картинки ей подходит так же. Заодно в служебную программу добавлена команда audio: раскладка звуковых дорожек проекта на куски вместе со строками edl://. Ею это и было найдено.
This commit is contained in:
parent
11075d6b52
commit
ad88683e8b
4 changed files with 141 additions and 2 deletions
|
|
@ -184,6 +184,13 @@ function ArgsBlackClip(const ADst: string; ADuration: Double;
|
|||
const AVideoCodec, APixFmt, AAudioCodec: string;
|
||||
ASampleRate, AChannels: Integer): TArgs;
|
||||
|
||||
{ Вставка тишины для звуковой дорожки: только звук, без картинки.
|
||||
|
||||
Отдельно от чёрного клипа, потому что перемотка внутри видео
|
||||
откатывается к опорному кадру, а в звуке опорным является каждый. }
|
||||
function ArgsSilenceClip(const ADst: string; ADuration: Double;
|
||||
const AAudioCodec: string; ASampleRate, AChannels: Integer): TArgs;
|
||||
|
||||
{ Кадр-миниатюра для списка файлов проекта.
|
||||
|
||||
Кадр берётся не с нуля: у многих съёмок первые секунды это крышка
|
||||
|
|
@ -869,6 +876,32 @@ begin
|
|||
Result := Concat(Result, [ADst]);
|
||||
end;
|
||||
|
||||
function ArgsSilenceClip(const ADst: string; ADuration: Double;
|
||||
const AAudioCodec: string; ASampleRate, AChannels: Integer): TArgs;
|
||||
var
|
||||
Codec: string;
|
||||
Ch: Integer;
|
||||
begin
|
||||
Codec := AAudioCodec;
|
||||
if Codec = '' then
|
||||
Codec := 'aac';
|
||||
Ch := AChannels;
|
||||
if Ch < 1 then
|
||||
Ch := 2;
|
||||
|
||||
Result := Concat(CommonFfmpegPrefix, [
|
||||
'-f', 'lavfi',
|
||||
'-i', Format('anullsrc=channel_layout=%s:sample_rate=%d',
|
||||
[IfThen(Ch = 1, 'mono', 'stereo'), ASampleRate]),
|
||||
'-c:a', Codec,
|
||||
'-ac', IntToStr(Ch),
|
||||
'-ar', IntToStr(ASampleRate),
|
||||
'-t', SecondsToStr(ADuration)
|
||||
]);
|
||||
Result := Concat(Result, ProgressArgs);
|
||||
Result := Concat(Result, [ADst]);
|
||||
end;
|
||||
|
||||
function ArgsThumbnail(const ASrc, ADst: string; const ATime: Double;
|
||||
AWidth, AHeight: Integer): TArgs;
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -39,7 +39,19 @@ uses
|
|||
|
||||
type
|
||||
{ Что нужно создать, чтобы кусок стал воспроизводимым файлом. }
|
||||
TGenKind = (gkNone, gkBlack, gkImage);
|
||||
{ gkSilence — вставка тишины без картинки, только для звуковых дорожек.
|
||||
|
||||
Отдельный вид, а не чёрный клип, потому что перемотка внутри чёрного
|
||||
клипа откатывается к ближайшему опорному кадру, а те стоят через
|
||||
четыре секунды. Дорожка после этого отдаёт звук на несколько секунд
|
||||
раньше соседних, смешивание равняется по ней, и содержимое соседней
|
||||
дорожки в это окно не попадает — звук пропадал целыми кусками.
|
||||
Замерено: на 24-й секунде смешивание трёх дорожек давало -91 dB, то
|
||||
есть полную тишину, при том что каждая дорожка по отдельности звучала.
|
||||
Со звуковой вставкой в той же точке -11.7 dB.
|
||||
|
||||
В звуковом файле опорным является каждый кадр, и такой беды нет. }
|
||||
TGenKind = (gkNone, gkBlack, gkImage, gkSilence);
|
||||
|
||||
TGenRequest = record
|
||||
Kind: TGenKind;
|
||||
|
|
@ -150,6 +162,8 @@ begin
|
|||
case AReq.Kind of
|
||||
gkBlack:
|
||||
Name := Format('black_%d.mkv', [Ms]);
|
||||
gkSilence:
|
||||
Name := Format('silence_%d.m4a', [Ms]);
|
||||
gkImage:
|
||||
begin
|
||||
Key := AReq.SourcePath + '|' + IntToStr(Ms);
|
||||
|
|
@ -731,7 +745,8 @@ var
|
|||
Seg.Volume := 1;
|
||||
Seg.Kind := skBlack;
|
||||
Seg.Duration := ALen;
|
||||
Req.Kind := gkBlack;
|
||||
{ Тишина без картинки: см. пояснение у gkSilence. }
|
||||
Req.Kind := gkSilence;
|
||||
Req.SourcePath := '';
|
||||
Req.Duration := BucketFor(ALen);
|
||||
Req.TargetPath := GeneratedPath(AProject.CacheDir, Req);
|
||||
|
|
|
|||
|
|
@ -175,6 +175,9 @@ begin
|
|||
gkImage:
|
||||
FStageText := Format('Делаю клип из картинки %d из %d',
|
||||
[I + 1, Length(FRequests)]);
|
||||
gkSilence:
|
||||
FStageText := Format('Делаю вставку тишины %d из %d',
|
||||
[I + 1, Length(FRequests)]);
|
||||
end;
|
||||
Publish;
|
||||
|
||||
|
|
@ -196,6 +199,10 @@ begin
|
|||
FMaster.Width, FMaster.Height, FMaster.FpsNum, FMaster.FpsDen,
|
||||
FMaster.VideoCodec, FMaster.PixFmt, AudioCodec,
|
||||
FMaster.SampleRate, FMaster.Channels);
|
||||
gkSilence:
|
||||
Args := ArgsSilenceClip(FRequests[I].TargetPath,
|
||||
FRequests[I].Duration, AudioCodec,
|
||||
FMaster.SampleRate, FMaster.Channels);
|
||||
else
|
||||
Continue;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
render <проект.nlve> <выход>
|
||||
уплотить проект, создать вставки, собрать ролик
|
||||
и сверить число кадров
|
||||
audio <проект.nlve>
|
||||
разложить звуковые дорожки проекта на куски и
|
||||
напечатать их вместе со строками edl://
|
||||
buildcut <проект.nlve> <видео>
|
||||
проект из одного файла с точным резом в заведомо
|
||||
неопорной точке
|
||||
|
|
@ -858,6 +861,85 @@ begin
|
|||
WriteLn(' ', FThread.PendingLog);
|
||||
end;
|
||||
|
||||
|
||||
{ Раскладка звуковых дорожек.
|
||||
|
||||
Нужна потому, что «звука нет вот здесь» глазами не разобрать: громкость
|
||||
может быть верной, а кусок в этом месте — ссылаться не туда. Здесь
|
||||
печатается ровно то, что уходит в плеер: место на монтаже, файл, место
|
||||
в файле, длина. }
|
||||
procedure CmdAudio;
|
||||
var
|
||||
Proj: TProject;
|
||||
Segs: TFlatSegmentArray;
|
||||
Missing: TGenRequests;
|
||||
Err: string;
|
||||
I, J: Integer;
|
||||
Total, At: Double;
|
||||
Kind: string;
|
||||
begin
|
||||
Proj := TProject.Create;
|
||||
try
|
||||
if not Proj.LoadFromFile(ExpandFileName(ParamStr(2)), Err) then
|
||||
begin
|
||||
WriteLn('Не удалось прочитать проект: ', Err);
|
||||
Halt(1);
|
||||
end;
|
||||
|
||||
Total := Max(AudibleEnd(Proj, tkVideo), AudibleEnd(Proj, tkAudio));
|
||||
WriteLn('проект: ', Proj.FileName);
|
||||
WriteLn('длина слышимого: ', SecondsToStr(Total), ' с');
|
||||
WriteLn;
|
||||
|
||||
for I := 0 to Proj.TrackCount - 1 do
|
||||
begin
|
||||
if Proj.Tracks[I].Kind <> tkAudio then
|
||||
Continue;
|
||||
WriteLn('=== ', Proj.Tracks[I].Name, ' ===');
|
||||
if not Proj.TrackAudible(Proj.Tracks[I]) then
|
||||
begin
|
||||
WriteLn(' не слышна (заглушена или пуста)');
|
||||
WriteLn;
|
||||
Continue;
|
||||
end;
|
||||
if Proj.Tracks[I].Count = 0 then
|
||||
begin
|
||||
WriteLn(' клипов нет');
|
||||
WriteLn;
|
||||
Continue;
|
||||
end;
|
||||
if not FlattenAudioTrack(Proj, Proj.Tracks[I], Total, Segs, Missing) then
|
||||
begin
|
||||
WriteLn(' разложить не удалось');
|
||||
WriteLn;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
At := 0;
|
||||
for J := 0 to High(Segs) do
|
||||
begin
|
||||
if Segs[J].Kind = skBlack then
|
||||
Kind := 'тишина'
|
||||
else
|
||||
Kind := 'звук ';
|
||||
WriteLn(Format(' [%2d] %s монтаж %8s..%8s длина %8s из %8s %s',
|
||||
[J, Kind, SecondsToStr(At), SecondsToStr(At + Segs[J].Duration),
|
||||
SecondsToStr(Segs[J].Duration), SecondsToStr(Segs[J].SrcStart),
|
||||
ExtractFileName(Segs[J].FilePath)]));
|
||||
if (Segs[J].Kind = skBlack) and (not FileExists(Segs[J].FilePath)) then
|
||||
WriteLn(' ВНИМАНИЕ: вставки тишины нет на диске');
|
||||
At := At + Segs[J].Duration;
|
||||
end;
|
||||
WriteLn(' всего по дорожке: ', SecondsToStr(At), ' с');
|
||||
WriteLn;
|
||||
WriteLn(' ', BuildEdlUri(Segs));
|
||||
WriteLn;
|
||||
end;
|
||||
finally
|
||||
Proj.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CmdRender;
|
||||
var
|
||||
Proj: TProject;
|
||||
|
|
@ -999,6 +1081,8 @@ begin
|
|||
CmdBuild
|
||||
else if (Cmd = 'buildcut') and (ParamCount >= 3) then
|
||||
CmdBuildCut
|
||||
else if (Cmd = 'audio') and (ParamCount >= 2) then
|
||||
CmdAudio
|
||||
else if (Cmd = 'render') and (ParamCount >= 3) then
|
||||
CmdRender
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue