mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix(tci,cat): резерв бюджета переезжает писателю; строгий разбор полей ZZ-команд
Три замечания по79132f1. 1. [P2] Лимит памяти рекордеров обходился при SAVE. Take собирал сплошную копию записи, а DropData тут же возвращал исходные куски в бюджет — копия жила дальше в асинхронном TTCIWavWriter уже неучтённой. Один SAVE поднимал настоящее потребление примерно вдвое, а на медленном или зависшем сетевом каталоге очередь writer-потоков и их буферов росла мимо потолка вовсе. Теперь копия НАСЛЕДУЕТ резерв кусков, из которых собрана: Take отдаёт его out-параметром Reserved, писатель держит до конца записи и отпускает в ReleaseData — ровно один раз, сколько бы путей выхода ни было у Execute. Новых денег у бюджета копия не берёт, так что потолок теперь считает и очередь сохранений тоже. 2. [P2] Пять ZZ-команд проверяли длину поля, но не содержимое: StrToIntDef(s, 0) превращал любую нечисловую пару символов в индекс 0. ZZBSxx; переключал диапазон на нулевой вместо ?;, ZZBMxx; и ZZAUxx;/ZZBPxx; двигали VFO, ZZFIxx; выбирал фильтр 0. Разбор приведён к идиоме ZZFL/ZZFH: TryStrToInt, иначе ошибка формата. 3. [P3] Сообщение об отказе запуска TCI звало в Settings → Advanced, а настройки там уже не живут — вкладка CAT (переезд был вde0830f). Стенд 194/194: Take отдаёт резерв размером с копию, после Take занят ровно он, писатель возвращает его по окончании. Без фикса первая проверка краснеет. GUI (--ws=qt6) и демон зелёные. doc/TCI.md §2.5 и сводка стенда, doc/CAT_STATUS.md — таблица разбора. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+44
-11
@@ -214,8 +214,12 @@ type
|
||||
destructor Destroy; override;
|
||||
procedure Feed(const L, R: array of Single; N: Integer);
|
||||
{ Забрать накопленное и завершить запись. nil — либо не записано ничего,
|
||||
либо окно уже истекло (по документу это одно и то же: записи нет). }
|
||||
function Take: TTCIPcm;
|
||||
либо окно уже истекло (по документу это одно и то же: записи нет).
|
||||
★Reserved — сколько байт бюджета УХОДИТ ВМЕСТЕ С ДАННЫМИ: копия живёт
|
||||
дальше в писателе, и пока он её не отпустит, она обязана оставаться
|
||||
учтённой. Вызывающий передаёт это число писателю (или возвращает сам
|
||||
через TCIRecBudgetFree, если писателя не будет). }
|
||||
function Take(out Reserved: Int64): TTCIPcm;
|
||||
{ Окно закрылось по часам. Спрашивает тик сервера: у мёртвого приёмника
|
||||
Feed не зовут вовсе, и без этого вопроса память жила бы до Stop. }
|
||||
function Expired: Boolean;
|
||||
@@ -225,17 +229,23 @@ type
|
||||
end;
|
||||
|
||||
{ Писатель WAV в своём потоке: файл до 60 МБ, а зовут сохранение из тика
|
||||
сервера — блокировать его на секунду диска нельзя. Данные забирает себе. }
|
||||
сервера — блокировать его на секунду диска нельзя. Данные забирает себе
|
||||
ВМЕСТЕ С ИХ РЕЗЕРВОМ в общем бюджете (AReserved из TTCIRecorder.Take) и
|
||||
отпускает его, только когда данные больше не нужны. Пока писатель ждёт
|
||||
медленный диск, эти байты остаются занятыми — так потолок и держит
|
||||
очередь сохранений, а не только сами записи. }
|
||||
TTCIWavWriter = class(TThread)
|
||||
private
|
||||
FPath: string;
|
||||
FData: TTCIPcm;
|
||||
FRate: Integer;
|
||||
FReserved: Int64;
|
||||
procedure ReleaseData;
|
||||
protected
|
||||
procedure Execute; override;
|
||||
public
|
||||
constructor Create(const APath: string; const AData: TTCIPcm;
|
||||
ARateHz: Integer);
|
||||
ARateHz: Integer; AReserved: Int64);
|
||||
end;
|
||||
|
||||
{ Коэффициент прореживания SrcRate → WantRate: наибольший целый делитель,
|
||||
@@ -843,11 +853,13 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TTCIRecorder.Take: TTCIPcm;
|
||||
function TTCIRecorder.Take(out Reserved: Int64): TTCIPcm;
|
||||
var
|
||||
C, n, Left: Integer;
|
||||
Copy_: Int64;
|
||||
begin
|
||||
Result := nil;
|
||||
Result := nil;
|
||||
Reserved := 0;
|
||||
FLock.Enter;
|
||||
try
|
||||
// Проверка срока и здесь: аудио могло не идти вовсе (мьют, стоящий
|
||||
@@ -857,6 +869,13 @@ begin
|
||||
// Склейка кусков в один буфер — единственное копирование за всю запись, и
|
||||
// оно идёт уже вне DSP-потока: SAVE вынимает рекордер из таблицы раньше,
|
||||
// чем зовёт Take, так что кормить его больше некому.
|
||||
// ★Копия НЕ берёт у бюджета новых денег и НЕ отпускает старых: она
|
||||
// наследует резерв кусков, из которых собрана. Иначе SAVE был бы дырой в
|
||||
// потолке — DropData возвращал бы куски сразу, а копия жила бы дальше в
|
||||
// писателе неучтённой, и на медленном (или зависшем) сетевом каталоге
|
||||
// writer-потоки копились бы без всякой границы.
|
||||
Copy_ := Int64(FCount) * 2 * SizeOf(SmallInt);
|
||||
if Copy_ > FBytes then Copy_ := FBytes; // не отдать больше, чем занято
|
||||
SetLength(Result, FCount * 2);
|
||||
Left := FCount;
|
||||
for C := 0 to High(FChunks) do
|
||||
@@ -867,6 +886,10 @@ begin
|
||||
Move(FChunks[C][0], Result[(FCount - Left) * 2], n * 2 * SizeOf(SmallInt));
|
||||
Dec(Left, n);
|
||||
end;
|
||||
// Из общего резерва оставляем ровно то, что перешло в копию, остальное
|
||||
// (хвост последнего куска) возвращаем — этим и займётся DropData.
|
||||
Reserved := Copy_;
|
||||
Dec(FBytes, Copy_);
|
||||
DropData; // SAVE завершает запись (§4.3)
|
||||
finally
|
||||
FLock.Leave;
|
||||
@@ -878,16 +901,26 @@ end;
|
||||
═══════════════════════════════════════════════════════════════════════════ }
|
||||
|
||||
constructor TTCIWavWriter.Create(const APath: string;
|
||||
const AData: TTCIPcm; ARateHz: Integer);
|
||||
const AData: TTCIPcm; ARateHz: Integer; AReserved: Int64);
|
||||
begin
|
||||
inherited Create(True);
|
||||
FreeOnTerminate := True;
|
||||
FPath := APath;
|
||||
FData := AData;
|
||||
FRate := ARateHz;
|
||||
FPath := APath;
|
||||
FData := AData;
|
||||
FRate := ARateHz;
|
||||
FReserved := AReserved;
|
||||
Start;
|
||||
end;
|
||||
|
||||
procedure TTCIWavWriter.ReleaseData;
|
||||
// Данные и их место в бюджете уходят вместе и ровно один раз — сколько бы
|
||||
// путей выхода ни было у Execute.
|
||||
begin
|
||||
FData := nil;
|
||||
TCIRecBudgetFree(FReserved);
|
||||
FReserved := 0;
|
||||
end;
|
||||
|
||||
procedure TTCIWavWriter.Execute;
|
||||
// Заголовок собираем в буфере: WAV — это фиксированные 44 байта, и городить
|
||||
// два десятка отдельных Write ради них незачем (а строковые литералы в
|
||||
@@ -954,7 +987,7 @@ begin
|
||||
// клиенту уже некому: команда давно подтверждена. Молчим, но и не падаем:
|
||||
// исключение из потока утащило бы за собой процесс.
|
||||
end;
|
||||
FData := nil;
|
||||
ReleaseData;
|
||||
end;
|
||||
|
||||
{ ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user