Две ошибки раздельного звука

Первая: при выключенных дорожках картинки не играло вообще ничего. Поток
картинки опустевал, уплощение возвращало неудачу, и предпросмотр
останавливался вместе со звуком, которого выключение не касалось.

Теперь длина берётся по обоим видам дорожек сразу, и недостающее
заполняется чёрным и тишиной. Оба потока всегда одной длины.

Вторая: звук был слышен там, где на монтаже его нет. mpv добавляет
внешний список кусков ещё одной дорожкой, а какую играть, решает сам, и
мог взять звук, встроенный в саму картинку.

Теперь после загрузки внешняя дорожка выбирается явно. Если её не
оказалось, звук выключается совсем: играть вместо него звук из картинки
нельзя — на монтаже его там нет.

Проверено: при выключенных дорожках картинки получается чёрный кадр и
сохранившийся звук (-21 dB); внешний список кусков выбирается верно и в
ту, и в другую сторону.
This commit is contained in:
chaiman 2026-08-20 11:00:41 +03:00
commit cf20a0164e
3 changed files with 91 additions and 13 deletions

View file

@ -63,9 +63,17 @@ type
Дорожки другого вида в проходе не участвуют вовсе — ни как источники,
ни как границы кусков. }
{ AMinLength — до какой длины дотянуть поток. Нужно, чтобы картинка и звук
всегда были одной длины: если выключить все дорожки картинки, поток
картинки опустеет, и без этого предпросмотр погас бы вместе со звуком.
Недостающее заполняется чёрным и тишиной. }
function FlattenStream(AProject: TProject; AKind: TTrackKind;
const AMinLength: Double;
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean;
{ Длина слышимого материала одного вида дорожек. }
function AudibleEnd(AProject: TProject; AKind: TTrackKind): Double;
{ Оба потока разом. Список того, что надо создать, общий: чёрные вставки
годятся и картинке, и звуку — в них есть и то, и другое. }
function FlattenProject(AProject: TProject;
@ -203,7 +211,28 @@ begin
end;
end;
function AudibleEnd(AProject: TProject; AKind: TTrackKind): Double;
var
T: Integer;
Track: TTrack;
begin
Result := 0;
if AProject = nil then
Exit;
for T := 0 to AProject.TrackCount - 1 do
begin
Track := AProject.Tracks[T];
if Track.Kind <> AKind then
Continue;
if not AProject.TrackAudible(Track) then
Continue;
if Track.EndTime > Result then
Result := Track.EndTime;
end;
end;
function FlattenStream(AProject: TProject; AKind: TTrackKind;
const AMinLength: Double;
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean;
var
Bounds: array of Double;
@ -334,10 +363,13 @@ begin
N := 0;
for T := 0 to AProject.TrackCount - 1 do
Inc(N, AProject.Tracks[T].Count * 2);
SetLength(Bounds, N + 2);
SetLength(Bounds, N + 3);
BoundCount := 0;
AddBoundary(Bounds, BoundCount, 0);
{ Конец задан снаружи: оба потока должны быть одной длины. }
if AMinLength > 0 then
AddBoundary(Bounds, BoundCount, AMinLength);
for T := 0 to AProject.TrackCount - 1 do
begin
Track := AProject.Tracks[T];
@ -600,15 +632,20 @@ function FlattenProject(AProject: TProject;
var
MissA: TGenRequests;
I: Integer;
Total: Double;
begin
AAudio := nil;
MissA := nil;
Result := FlattenStream(AProject, tkVideo, AVideo, AMissing);
{ Длина берётся по обоим видам дорожек сразу: иначе выключенная
картинка укоротила бы ролик, хотя звук ещё идёт. }
Total := Max(AudibleEnd(AProject, tkVideo), AudibleEnd(AProject, tkAudio));
if Total <= 0 then
Exit;
Result := FlattenStream(AProject, tkVideo, Total, AVideo, AMissing);
if not Result then
Exit;
{ Звук уплощается всегда, даже если звуковых дорожек нет: тогда выйдет
один кусок тишины на всю длину, и это правильный ответ. }
FlattenStream(AProject, tkAudio, AAudio, MissA);
FlattenStream(AProject, tkAudio, Total, AAudio, MissA);
for I := 0 to High(MissA) do
begin

View file

@ -81,6 +81,13 @@ type
procedure PumpEvents(Sender: TObject);
procedure HandlePropertyChange(AProp: Pmpv_event_property);
procedure HandleLogMessage(AMsg: Pmpv_event_log_message);
{ Выбирает внешнюю звуковую дорожку, если она есть.
Без этого mpv играет звук, встроенный в саму картинку: внешний файл
он лишь добавляет ещё одной дорожкой, а выбирает по своему
усмотрению. Получалось, что звук слышен там, где на монтаже его нет
вовсе — он приходил из видеофайла, а не с звуковой дорожки. }
procedure SelectExternalAudio;
function SetOption(const AName, AValue: string): Integer;
public
constructor Create(AVideoParent: TWinControl);
@ -737,6 +744,38 @@ begin
end;
end;
procedure TMpvPlayer.SelectExternalAudio;
var
Count, I: Integer;
Kind, Ext, Id: string;
begin
if FHandle = nil then
Exit;
{ Своего звука нет — пусть играет как есть. }
if FAudioSource = '' then
Exit;
Count := StrToIntDef(GetPropString('track-list/count'), 0);
for I := 0 to Count - 1 do
begin
Kind := GetPropString(Format('track-list/%d/type', [I]));
if Kind <> 'audio' then
Continue;
Ext := GetPropString(Format('track-list/%d/external', [I]));
if (Ext <> 'yes') and (Ext <> 'true') then
Continue;
Id := GetPropString(Format('track-list/%d/id', [I]));
if Id <> '' then
mpv_set_property_string(FHandle, 'aid', PAnsiChar(Id));
Exit;
end;
{ Внешней дорожки не нашлось — значит наш список кусков не открылся.
Играть вместо него звук из картинки нельзя: на монтаже его там нет. }
mpv_set_property_string(FHandle, 'aid', 'no');
end;
procedure TMpvPlayer.HandleLogMessage(AMsg: Pmpv_event_log_message);
begin
if Assigned(FOnLog) then
@ -766,6 +805,8 @@ begin
HandlePropertyChange(Pmpv_event_property(Ev^.data));
MPV_EVENT_LOG_MESSAGE:
HandleLogMessage(Pmpv_event_log_message(Ev^.data));
MPV_EVENT_FILE_LOADED:
SelectExternalAudio;
MPV_EVENT_SHUTDOWN:
begin
FPump.Enabled := False;

View file

@ -564,7 +564,7 @@ begin
try
Proj.AddClip(Proj.Tracks[0], Media, 10, 5, 0);
Proj.AddClip(Proj.Tracks[0], Media, 15, 5, 5);
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 1, 'склеились в один кусок');
if Length(Segs) = 1 then
@ -583,7 +583,7 @@ begin
try
Proj.AddClip(Proj.Tracks[0], Media, 100, 5, 0);
Proj.AddClip(Proj.Tracks[0], Media, 200, 5, 5);
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 2, 'остались двумя кусками');
finally
@ -598,7 +598,7 @@ begin
{ Дорожка 0 — верхняя, она побеждает. }
Proj.AddClip(Proj.Tracks[1], Media, 0, 30, 0);
Proj.AddClip(Proj.Tracks[0], Media, 100, 10, 10);
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 3, 'получилось три куска');
if Length(Segs) = 3 then
@ -622,7 +622,7 @@ begin
try
Proj.AddClip(Proj.Tracks[0], Media, 0, 5, 3);
Proj.AddClip(Proj.Tracks[0], Media, 50, 5, 12);
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 4, 'четыре куска: чёрное, видео, чёрное, видео');
if Length(Segs) = 4 then
@ -652,7 +652,7 @@ begin
try
Proj.AddClip(Proj.Tracks[1], Media, 0, 10, 0);
Proj.AddClip(Proj.Tracks[0], Media, 200, 10, 0);
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 1, 'остался один кусок');
if Length(Segs) = 1 then
@ -669,7 +669,7 @@ begin
Proj.AddClip(Proj.Tracks[1], Media, 0, 10, 0);
Proj.AddClip(Proj.Tracks[0], Media, 200, 10, 0);
Proj.Tracks[0].Muted := True;
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 1, 'остался один кусок');
if Length(Segs) = 1 then
@ -689,7 +689,7 @@ begin
Proj.Tracks[0][0].EdgeIn := edPrecise;
Proj.RippleMode := rmAllTracks;
Proj.RippleEnabled := True;
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
Check(Length(Segs) = 3, 'до записи три куска');
Check(Proj.SaveToFile(TempFile, Err), 'записан: ' + Err);
finally
@ -709,7 +709,7 @@ begin
Check(Proj.RippleEnabled, 'состояние схлопывания сохранилось');
if Proj.TrackCount = 2 then
Check(Proj.Tracks[0][0].EdgeIn = edPrecise, 'признак точного реза сохранился');
FlattenStream(Proj, tkVideo, Segs, Missing);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
DumpSegments(Segs);
Check(Length(Segs) = 3, 'после чтения снова три куска');
if Length(Segs) = 3 then