mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix(tci): SAVE отдаёт писателю сами куски записи, а не сплошную копию
Потолок 128 МБ всё ещё пробивался примерно вдвое на пике: Take собирал
линейную копию ДО освобождения FChunks, поэтому при полном бюджете рядом
жили ~128 МБ кусков и ~128 МБ копии, а счётчик показывал 128. Передача
резерва писателю (9b77809) закрывала учёт, но не сам пик.
Копии больше нет вовсе. Take отдаёт куски КАК ЕСТЬ — новый TTCIRecTake
(Chunks + Chunk + Count + Reserved), — и вместе с ними уезжает их место в
бюджете целиком. TTCIWavWriter пишет куски в файл подряд: в WAV сэмплы и
так лежат встык, а хвост последнего куска за Count просто не наш. Место
отпускается в ReleaseData, ровно один раз на любом пути выхода Execute.
Продовый путь теперь не выделяет под запись ни одного лишнего байта:
TTCIPcm остался только внутри куска. Склейка нужна одному стенду, чтобы
проверять порядок и уровень, — она и живёт в стенде (FlatTake).
Стенд 199/199: 2.5 с записи отдаются ТРЕМЯ кусками по секунде (свёрнутая
копия дала бы один), счёт бюджета при Take не меняется, резерва хватает
на отданные куски, склеенные куски дают непрерывный звук, писатель
возвращает резерв по окончании. Проверено, что копирующая реализация Take
краснит четыре проверки. GUI (--ws=qt6) и демон зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+97
-18
@@ -550,6 +550,26 @@ end;
|
||||
C. Рекордер и WAV
|
||||
═══════════════════════════════════════════════════════════════════════════ }
|
||||
|
||||
function FlatTake(const T: TTCIRecTake): TTCIPcm;
|
||||
// Склейка кусков записи в один буфер. Живёт ТОЛЬКО в стенде: продовый путь
|
||||
// сплошной копии не делает вовсе — писатель пишет куски подряд, иначе на
|
||||
// полном бюджете рядом жили бы куски и их копия, то есть двойной пик.
|
||||
var C, n, Left: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
if T.Count <= 0 then Exit;
|
||||
SetLength(Result, T.Count * 2);
|
||||
Left := T.Count;
|
||||
for C := 0 to High(T.Chunks) do
|
||||
begin
|
||||
if Left <= 0 then Break;
|
||||
n := T.Chunk;
|
||||
if n > Left then n := Left;
|
||||
Move(T.Chunks[C][0], Result[(T.Count - Left) * 2], n * 2 * SizeOf(SmallInt));
|
||||
Dec(Left, n);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TestRecorder;
|
||||
var
|
||||
Rec: TTCIRecorder;
|
||||
@@ -562,7 +582,8 @@ var
|
||||
Sz: LongWord;
|
||||
W: TTCIWavWriter;
|
||||
Waited: Integer;
|
||||
Was, Held: Int64;
|
||||
Was: Int64;
|
||||
Tk: TTCIRecTake;
|
||||
begin
|
||||
WriteLn('C. Рекордер линейного выхода');
|
||||
|
||||
@@ -578,29 +599,69 @@ begin
|
||||
Rec.Feed(L, R, 8192);
|
||||
Inc(Total, 8192);
|
||||
end;
|
||||
Data := Rec.Take(Held);
|
||||
Tk := Rec.Take; Data := FlatTake(Tk);
|
||||
Check('рекордер: длина по максимуму', Length(Data) = 48000 * 2,
|
||||
IntToStr(Length(Data)));
|
||||
// ★Take отдаёт данные ВМЕСТЕ с их местом в бюджете: копия живёт дальше в
|
||||
// писателе, и до конца записи она обязана оставаться учтённой. Иначе SAVE
|
||||
// был бы дырой в потолке — куски вернулись бы сразу, а копия висела бы
|
||||
// неучтённой, и очередь сохранений на медленном диске росла бы без границ.
|
||||
Check('рекордер: Take передаёт резерв размером с копию',
|
||||
Held = Int64(Length(Data)) * SizeOf(SmallInt),
|
||||
IntToStr(Held));
|
||||
Check('рекордер: после Take занят ровно резерв копии',
|
||||
TCIRecBudgetUsed = Was + Held,
|
||||
IntToStr(TCIRecBudgetUsed - Was));
|
||||
TCIRecBudgetFree(Held); // как это сделает писатель
|
||||
// ★Куски отдаются КАК ЕСТЬ: сплошной копии нет ни на миг, поэтому и пика
|
||||
// в два бюджета нет. Резерв переезжает вместе с ними — целиком.
|
||||
Check('рекордер: Take отдаёт сами куски, не копию',
|
||||
(Length(Tk.Chunks) > 0) and (Tk.Count = Length(Data) div 2),
|
||||
IntToStr(Length(Tk.Chunks)));
|
||||
Check('рекордер: резерв переехал целиком, счёт не изменился',
|
||||
TCIRecBudgetUsed = Was + Tk.Reserved,
|
||||
IntToStr(TCIRecBudgetUsed - Was) + '/' + IntToStr(Tk.Reserved));
|
||||
Check('рекордер: резерва хватает на отданные куски',
|
||||
Tk.Reserved >= Int64(Length(Data)) * SizeOf(SmallInt),
|
||||
IntToStr(Tk.Reserved));
|
||||
TCIRecBudgetFree(Tk.Reserved); // как это сделает писатель
|
||||
Tk.Chunks := nil;
|
||||
Check('рекордер: возврат резерва закрывает счёт',
|
||||
TCIRecBudgetUsed = Was, IntToStr(TCIRecBudgetUsed - Was));
|
||||
Check('рекордер: уровень сохранён',
|
||||
(Data[0] = Round(0.25 * 32767)) and (Data[1] = -Round(0.25 * 32767)));
|
||||
Check('рекордер: Take завершает запись', Length(Rec.Take(Held)) = 0);
|
||||
Check('рекордер: Take завершает запись', Length(FlatTake(Rec.Take)) = 0);
|
||||
finally
|
||||
Rec.Free;
|
||||
end;
|
||||
|
||||
// ★Отдаются именно куски, а не свёрнутая в один буфер копия: 2.5 секунды
|
||||
// при куске в секунду — это ТРИ куска. Если бы Take собирал сплошную копию
|
||||
// (как было), кусок пришёл бы один, а пик памяти на полном бюджете вырос бы
|
||||
// вдвое: 128 МБ кусков и 128 МБ копии рядом, при счётчике в 128.
|
||||
Was := TCIRecBudgetUsed;
|
||||
Rec := TTCIRecorder.Create(0, 48000, 3, nil);
|
||||
try
|
||||
for i := 0 to 8191 do begin L[i] := 0.25; R[i] := -0.25; end;
|
||||
Total := 0;
|
||||
while Total < 120000 do // 2.5 с при 48 кГц
|
||||
begin
|
||||
Rec.Feed(L, R, 8192);
|
||||
Inc(Total, 8192);
|
||||
end;
|
||||
Tk := Rec.Take;
|
||||
Check('рекордер: кусков ровно по секундам записи',
|
||||
(Tk.Chunk = 48000) and (Length(Tk.Chunks) = 3) and
|
||||
(Tk.Count >= 120000),
|
||||
IntToStr(Length(Tk.Chunks)) + ' × ' + IntToStr(Tk.Chunk));
|
||||
Check('рекордер: сплошной копии не появилось',
|
||||
TCIRecBudgetUsed = Was + Tk.Reserved,
|
||||
IntToStr(TCIRecBudgetUsed - Was));
|
||||
Data := FlatTake(Tk);
|
||||
Check('рекордер: куски склеиваются в непрерывный звук',
|
||||
(Length(Data) = Tk.Count * 2) and
|
||||
(Data[0] = Round(0.25 * 32767)) and
|
||||
(Data[Length(Data) - 2] = Round(0.25 * 32767)));
|
||||
Tk.Chunks := nil;
|
||||
TCIRecBudgetFree(Tk.Reserved);
|
||||
finally
|
||||
Rec.Free;
|
||||
end;
|
||||
Check('рекордер: счёт закрыт', TCIRecBudgetUsed = Was);
|
||||
|
||||
// Порядок отсчётов: первым обязан идти самый ПЕРВЫЙ записанный.
|
||||
Rec := TTCIRecorder.Create(0, 1000, 1, nil); // ёмкость 1000 отсчётов
|
||||
try
|
||||
@@ -610,7 +671,7 @@ begin
|
||||
R[0] := 0;
|
||||
Rec.Feed(L, R, 1);
|
||||
end;
|
||||
Data := Rec.Take(Held);
|
||||
Tk := Rec.Take; Data := FlatTake(Tk);
|
||||
Check('рекордер: с начала записи, а не с конца',
|
||||
(Length(Data) = 2000) and (Data[0] = 0) and
|
||||
(Abs(Data[1998] - Round((999 / 4000.0) * 32767)) <= 1),
|
||||
@@ -625,7 +686,7 @@ begin
|
||||
try
|
||||
for i := 0 to 8191 do begin L[i] := 0.25; R[i] := -0.25; end;
|
||||
Rec.Feed(L, R, 8192);
|
||||
Check('рекордер: до срока запись есть', Length(Rec.Take(Held)) > 0);
|
||||
Check('рекордер: до срока запись есть', Length(FlatTake(Rec.Take)) > 0);
|
||||
finally
|
||||
Rec.Free;
|
||||
end;
|
||||
@@ -633,9 +694,9 @@ begin
|
||||
try
|
||||
Rec.Feed(L, R, 8192);
|
||||
Sleep(1100); // окно закрылось
|
||||
Check('рекордер: после срока запись удалена', Length(Rec.Take(Held)) = 0);
|
||||
Check('рекордер: после срока запись удалена', Length(FlatTake(Rec.Take)) = 0);
|
||||
Rec.Feed(L, R, 8192); // и новое аудио уже не принимает
|
||||
Check('рекордер: истёкший не оживает', Length(Rec.Take(Held)) = 0);
|
||||
Check('рекордер: истёкший не оживает', Length(FlatTake(Rec.Take)) = 0);
|
||||
finally
|
||||
Rec.Free;
|
||||
end;
|
||||
@@ -668,7 +729,7 @@ begin
|
||||
try
|
||||
Rec.Feed(L, R, 8192);
|
||||
Check('бюджет: при отказе запись не рушится, а стоит пустой',
|
||||
Length(Rec.Take(Held)) = 0);
|
||||
Length(FlatTake(Rec.Take)) = 0);
|
||||
finally
|
||||
Rec.Free;
|
||||
end;
|
||||
@@ -697,7 +758,13 @@ begin
|
||||
DeleteFile(Path);
|
||||
SetLength(Data, 2000);
|
||||
for i := 0 to 1999 do Data[i] := i * 8;
|
||||
W := TTCIWavWriter.Create(Path, Data, 48000, 0);
|
||||
Tk.Chunks := nil;
|
||||
SetLength(Tk.Chunks, 1);
|
||||
Tk.Chunks[0] := Data;
|
||||
Tk.Chunk := Length(Data) div 2;
|
||||
Tk.Count := Tk.Chunk;
|
||||
Tk.Reserved := 0;
|
||||
W := TTCIWavWriter.Create(Path, Tk, 48000);
|
||||
Waited := 0;
|
||||
while (not FileExists(Path)) and (Waited < 2000) do
|
||||
begin
|
||||
@@ -733,7 +800,13 @@ begin
|
||||
Was := TCIRecBudgetUsed;
|
||||
Check('WAV: резерв под писателя взят', TCIRecBudgetTake(4096));
|
||||
DeleteFile(Path);
|
||||
TTCIWavWriter.Create(Path, Data, 48000, 4096);
|
||||
Tk.Chunks := nil;
|
||||
SetLength(Tk.Chunks, 1);
|
||||
Tk.Chunks[0] := Data;
|
||||
Tk.Chunk := Length(Data) div 2;
|
||||
Tk.Count := Tk.Chunk;
|
||||
Tk.Reserved := 4096;
|
||||
TTCIWavWriter.Create(Path, Tk, 48000);
|
||||
Waited := 0;
|
||||
while (TCIRecBudgetUsed <> Was) and (Waited < 2000) do
|
||||
begin
|
||||
@@ -748,7 +821,13 @@ begin
|
||||
// файл обязан остаться прежним.
|
||||
SetLength(Data, 10);
|
||||
for i := 0 to 9 do Data[i] := 1;
|
||||
TTCIWavWriter.Create(Path, Data, 48000, 0);
|
||||
Tk.Chunks := nil;
|
||||
SetLength(Tk.Chunks, 1);
|
||||
Tk.Chunks[0] := Data;
|
||||
Tk.Chunk := 5;
|
||||
Tk.Count := 5;
|
||||
Tk.Reserved := 0;
|
||||
TTCIWavWriter.Create(Path, Tk, 48000);
|
||||
Sleep(200);
|
||||
FS := TFileStream.Create(Path, fmOpenRead);
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user