mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(tci): дефекты живого прогона — length аудио, маршруты тапов, MOX, рекордер, EOF сокета
Восемь дефектов, найденных прогоном настоящего TCI-клиента (три приёмника: NFM, DIGU, FMRAW) и его отчётом. 1. UI доп. панорам не перерисовывался: rfSliceState рассылался, но ветки в MainForm.OnControllerState не было (частоту несёт отдельный rfSliceFreq). 2. Stream.length у аудио — сэмплы НА КАНАЛ (§4.3), у IQ — вещественные отсчёты (§3.4: комплексных = length/channels). Было ×каналы везде, у стерео получалось вдвое больше. Развилка в TCIFillHeader + разбор TX-аудио в HandleBinary. 3+4. Дыры в маршрутах аудио движка: demod-тап звался только для DMR/FMRAW (у DIGU не было RX_AUDIO), а пост-громкостный — только для нецифровых (у FMRAW не было LINEOUT). Плюс мьют слайса больше не убивает RX_AUDIO: движку сообщают SetAudioTapsActive. 5. Клиент, поставивший TRX, уходил — MOX оставался. FTrxOwner + StopTxOf; TCIMicRequested снимается и по окончании любой передачи. 6. Гонка снятия IQ-тапа: SetIQTap(nil) возвращался раньше, чем DSP-поток выходил из вызова. FIQTapLock (порядок FSliceLock → FIQTapLock). 7. Рекордер был кольцом «последние N секунд», а §4.3 говорит про МАКСИМАЛЬНОЕ время записи с удалением по истечении. Переделан в линейный буфер с окном по часам от START. 8. TCIServer.HandleClient считал recv = 0 таймаутом: ноль — это EOF, errno при нём не трогается и несёт EAGAIN от прошлого истёкшего TCI_POLL_MS. Обычный TCP-разрыв без close-кадра не освобождал слот до остановки сервера, и после нескольких аварийных отключений новые клиенты упирались в TCI_MAX_CLIENTS. Теперь R = 0 рвёт связь безусловно, errno спрашивается только при R < 0. Попутно: MainForm.RecreateDSPEngine (смена sample rate до START) терял внутренние колбэки контроллера — введён AttachEngineCallbacks. Стенд test/tci заведён в репозиторий (run.sh, 126/126 зелёных, включая сквозной прогон через живой WDSP), доп. проверки на оба пути отключения клиента. doc/TCI.md приведена в соответствие: правило про recv = 0 в §1.1, единицы Stream.length, линейный буфер рекордера. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+15
-4
@@ -87,7 +87,8 @@ type
|
||||
Format: LongWord; // TTCISampleType
|
||||
Codec: LongWord; // всегда 0 (сжатие не реализовано)
|
||||
CRC: LongWord; // всегда 0
|
||||
DataLength: LongWord; // количество вещественных отсчётов в data[]
|
||||
DataLength: LongWord; // IQ — вещественных отсчётов, аудио — на канал
|
||||
// (см. TCIFillHeader)
|
||||
StreamType: LongWord; // TTCIStreamType
|
||||
Channels: LongWord;
|
||||
Reserv: array[0..7] of LongWord;
|
||||
@@ -133,8 +134,14 @@ function TCIDefaultAudioSamples(RateHz: Integer): Integer;
|
||||
{ Сколько сэмплов на канал влезает в блок с таким форматом и числом каналов. }
|
||||
function TCIMaxBlockSamples(T: TTCISampleType; Channels: Integer): Integer;
|
||||
|
||||
{ Заголовок блока. Count — сэмплов НА КАНАЛ; в DataLength уходит, как велит
|
||||
протокол, количество ВЕЩЕСТВЕННЫХ отсчётов, то есть Count × Channels. }
|
||||
{ Заголовок блока. Count — сэмплов НА КАНАЛ. В DataLength уходит то, что для
|
||||
этого типа потока велит документ, а он для IQ и для аудио говорит РАЗНОЕ:
|
||||
• IQ (§3.4): «количество вещественных отсчётов… количество комплексных
|
||||
вычисляется как Stream.length/Stream.channels» ⇒ Count × Channels;
|
||||
• аудио (§4.3, AUDIO_STREAM_SAMPLES): «arg1 — количество сэмплов,
|
||||
указываемое в поле Stream.length», по умолчанию 2048 при 48 кГц ⇒ Count,
|
||||
то есть сэмплов НА КАНАЛ. Сходится и с потолком data[16384]:
|
||||
2048 × 2 канала × float32 — ровно 16384 байта. }
|
||||
procedure TCIFillHeader(out H: TTCIStreamHeader; Kind: TTCIStreamType;
|
||||
Rx, RateHz: Integer; T: TTCISampleType; Count, Channels: Integer);
|
||||
|
||||
@@ -392,7 +399,11 @@ begin
|
||||
H.Receiver := LongWord(Rx);
|
||||
H.SampleRate := LongWord(RateHz);
|
||||
H.Format := LongWord(Ord(T));
|
||||
H.DataLength := LongWord(Count * Channels);
|
||||
// ★Единицы length разные у IQ и у аудио — см. шапку объявления.
|
||||
if Kind = tstIQ then
|
||||
H.DataLength := LongWord(Count * Channels)
|
||||
else
|
||||
H.DataLength := LongWord(Count);
|
||||
H.StreamType := LongWord(Ord(Kind));
|
||||
H.Channels := LongWord(Channels);
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user