mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix(tci): Stream.length — вещественные отсчёты всего блока, а не на канал
MSHV не декодировал FT4 по TCI (по виртуальному кабелю — декодировал).
Регрессия из dc6f996: там length аудио был переведён на «сэмплы на канал»
по формулировке §4.3, а живые клиенты считают по нему БАЙТЫ блока
(network.cpp: `int cr2 = pStream->length*bit_s;` с шагом `chan*bit_s`,
на передаче `quint32 cr3 = pStream->length*bit_s;`). При Channels=2
(умолчание MSHV) клиент разбирал половину каждого блока: звук с дырами
50%, водопад шире и грязнее, декодер разваливался. В моно дефекта не
видно — единицы совпадают, поэтому первый прогон стенда увёл в сторону.
Правка верна и по документу, а не только по клиенту: §3.4 после IQ
(«количество вещественных отсчётов… комплексных = length/channels»)
говорит «аудиопоток приёмника ПОЛНОСТЬЮ ПОВТОРЯЕТ IQ поток» и
перечисляет ровно три отличия — каналы, формат сэмплов, число сэмплов в
пакете. Единиц length среди них нет, поле в struct Stream одно.
Развилка по типу потока была вычитана из воздуха.
§4.3 путает две величины, и её формулировка верна лишь для моно:
AUDIO_STREAM_SAMPLES — кадры НА КАНАЛ, Stream.length — отсчёты ВСЕГО
блока. Что arg1 считает кадры, видно из самой §4.3 дважды: минимум
512/256/128/100 на 48/24/12/8 кГц даёт обещанные «не меньше 10 мс»
только при счёте на канал, и потолок data[16384] = 2048 × 2 × float32.
TX_CHRONO замыкает круг: клиент шлёт столько отсчётов, сколько названо
в length маркера, и возвращает то же число обратно.
Размер блока не менялся (2048@48к = 42.7 мс). Гипотеза про клиппинг
тапа RX_AUDIO проверена замером и снята: пик 0.115.
Стенд: проверки length переписаны на инвариант «байт = length × размер
отсчёта»; новый test/tci/ft4_bench.py снимает поток TCI и PipeWire-
источник одновременно, режет на нарезки FT4 по общим часам и гоняет
через настоящий jt9 --ft4 (блок разбирает КАК MSHV — этим и поймал).
Проверено вживую: MSHV декодирует FT4 по TCI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+29
-15
@@ -87,8 +87,8 @@ type
|
||||
Format: LongWord; // TTCISampleType
|
||||
Codec: LongWord; // всегда 0 (сжатие не реализовано)
|
||||
CRC: LongWord; // всегда 0
|
||||
DataLength: LongWord; // IQ — вещественных отсчётов, аудио — на канал
|
||||
// (см. TCIFillHeader)
|
||||
DataLength: LongWord; // вещественных отсчётов всего блока
|
||||
// (комплексных/на канал = length/channels)
|
||||
StreamType: LongWord; // TTCIStreamType
|
||||
Channels: LongWord;
|
||||
Reserv: array[0..7] of LongWord;
|
||||
@@ -134,14 +134,30 @@ function TCIDefaultAudioSamples(RateHz: Integer): Integer;
|
||||
{ Сколько сэмплов на канал влезает в блок с таким форматом и числом каналов. }
|
||||
function TCIMaxBlockSamples(T: TTCISampleType; Channels: Integer): Integer;
|
||||
|
||||
{ Заголовок блока. 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 байта. }
|
||||
{ Заголовок блока. Count — сэмплов НА КАНАЛ, а в DataLength всегда уходит
|
||||
Count × Channels: length измеряется ВЕЩЕСТВЕННЫМИ ОТСЧЁТАМИ всего блока, и
|
||||
для IQ, и для аудио.
|
||||
|
||||
Для IQ так прямо написано в §3.4 («количество комплексных вычисляется как
|
||||
Stream.length/Stream.channels»), а для аудио формулировка §4.3 про
|
||||
AUDIO_STREAM_SAMPLES («arg1 — количество сэмплов, указываемое в поле
|
||||
Stream.length») однажды уже увела нас в «сэмплы на канал». ★Так делать
|
||||
нельзя, и вот доказательство из живого клиента — MSHV, network.cpp:
|
||||
|
||||
int cr2 = (int)pStream->length*bit_s; // сколько БАЙТ разбирать
|
||||
for (int i = 0; i < cr2; i+=chan*bit_s) // шаг = кадр всех каналов
|
||||
...
|
||||
quint32 cr3 = pStream->length*bit_s; //plength = plength*channels*4-bytes
|
||||
|
||||
То есть клиент считает по length число байт блока. Стоит объявить length «на
|
||||
канал» — и у стерео он разбирает ровно половину блока, а вторую выбрасывает:
|
||||
звук идёт с дырами в 50%, водопад становится шире и грязнее, FT4/FT8 не
|
||||
декодируются вовсе. Замерено стендом test/tci/ft4_bench.py: 50% темпа набора
|
||||
звука и 1649 не до конца разобранных блоков за минуту.
|
||||
|
||||
Размер блока при этом остаётся прежним: AUDIO_STREAM_SAMPLES — сэмплы НА
|
||||
КАНАЛ (2048 при 48 кГц = 42.7 мс, как у ExpertSDR3), и потолок data[16384]
|
||||
сходится ровно: 2048 × 2 канала × float32. }
|
||||
procedure TCIFillHeader(out H: TTCIStreamHeader; Kind: TTCIStreamType;
|
||||
Rx, RateHz: Integer; T: TTCISampleType; Count, Channels: Integer);
|
||||
|
||||
@@ -399,11 +415,9 @@ begin
|
||||
H.Receiver := LongWord(Rx);
|
||||
H.SampleRate := LongWord(RateHz);
|
||||
H.Format := LongWord(Ord(T));
|
||||
// ★Единицы length разные у IQ и у аудио — см. шапку объявления.
|
||||
if Kind = tstIQ then
|
||||
H.DataLength := LongWord(Count * Channels)
|
||||
else
|
||||
H.DataLength := LongWord(Count);
|
||||
// ★length — вещественные отсчёты всего блока, у всех типов потока одинаково
|
||||
// (см. шапку объявления: «на канал» ломает реальных клиентов).
|
||||
H.DataLength := LongWord(Count * Channels);
|
||||
H.StreamType := LongWord(Ord(Kind));
|
||||
H.Channels := LongWord(Channels);
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user