group-FEC 16+2: стирающее восстановление кадров без обратного канала (§10 п.6)
Групповой FEC над GF(256), RAID-6-стиль: 16 кадров данных + P(⊕) + Q(Σgⁱ·dᵢ)
закрывают до 2 стёртых кадров на группу. Потеря (пропуск seq или битый CRC) —
это стирание с известной позицией, поэтому 2 паритета оптимальны (overhead 12.5%,
~99% передач 10 МБ байт-в-байт при PER 0.26%).
- src/group_fec.{c,h}: GF(256) polynom 0x11d, gf_encode/gf_recover + self-test
(make gftest, нативный cc). Восстановление 1 стирания через P или Q, 2 — P+Q.
- transmitter.c: флаг -G, вынесен tx_frame(); копит группу, шлёт k данных +
P/Q с флагами в hdr[11]. Мета паритета: k′ + длина хвостового кадра.
- receiver.c: групповой ассемблер в потоке C, автодетект по hdr[11], финализация
группы на смене базы seq и на выходе потока. Счётчики Rec/GrpF в статистике.
- hdr[11] (был резерв): bit7 режим, bit6 паритет, bit5 Q, bits4-0 idx. RS/CRC
слой прозрачен — геометрия кадра не тронута, PHY-бенчмарк не требуется.
- test_file_link.sh: GROUPFEC=1 по умолчанию (TX -G, RX автодетект).
README §12.7 + перенумерация роадмапа — отдельным коммитом после верификации.
This commit is contained in:
@@ -6,28 +6,95 @@
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "group_fec.h"
|
||||
|
||||
// Параметры передатчика по умолчанию
|
||||
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
|
||||
#define DEFAULT_AMP 0.2f // Амплитуда сигнала
|
||||
#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс
|
||||
#define BUF_SAMPLES 16384 // Размер буфера в отсчётах
|
||||
#define MAX_PAYLOAD 1024 // Максимальный размер полезной нагрузки
|
||||
#define MAX_PAYLOAD GF_CHUNK // 1024: размер чанка данных (== GF_CHUNK)
|
||||
|
||||
// Внешняя переменная флага завершения (определена в common.c)
|
||||
extern volatile sig_atomic_t stop;
|
||||
|
||||
// Кодирование и отправка одного кадра: CRC-поверх-RS (техдолг #3) → заголовок
|
||||
// 12 Б → OFDM-сборка → push_partial → пауза. hdr11 несёт флаги group-FEC
|
||||
// (0 для legacy). pl/n — полезная нагрузка (данные ≤1024 или паритет 1026).
|
||||
static void tx_frame(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
|
||||
uint32_t seq, uint8_t hdr11,
|
||||
const uint8_t *pl, size_t n, float amp, int pause_us) {
|
||||
uint8_t *tx_payload = NULL;
|
||||
size_t tx_len = 0;
|
||||
uint8_t hdr[HDR_SIZE] = {0};
|
||||
|
||||
if (enc) {
|
||||
// CRC32 поверх исходных данных ДО RS: fec_decode() liquid не сообщает о
|
||||
// неисправимых блоках — единственный надёжный критерий на RX (техдолг #3).
|
||||
uint8_t framed[GF_PAR_PAY + 4]; // ≥ данные+4 и ≥ паритет 1026+4
|
||||
memcpy(framed, pl, n);
|
||||
unsigned int crc = crc_generate_key(LIQUID_CRC_32, pl, n);
|
||||
framed[n] = (crc >> 24) & 0xFF;
|
||||
framed[n + 1] = (crc >> 16) & 0xFF;
|
||||
framed[n + 2] = (crc >> 8) & 0xFF;
|
||||
framed[n + 3] = crc & 0xFF;
|
||||
size_t framed_n = n + 4;
|
||||
|
||||
size_t total_blocks = (framed_n + RS_DATA - 1) / RS_DATA;
|
||||
tx_len = total_blocks * RS_ENC;
|
||||
tx_payload = malloc(tx_len);
|
||||
for (size_t b = 0; b < total_blocks; b++) {
|
||||
uint8_t blk[RS_DATA] = {0};
|
||||
size_t bytes = (b == total_blocks - 1) ? framed_n - b * RS_DATA : RS_DATA;
|
||||
memcpy(blk, framed + b * RS_DATA, bytes);
|
||||
fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
|
||||
}
|
||||
hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF;
|
||||
hdr[10] = framed_n % RS_DATA;
|
||||
} else {
|
||||
tx_len = n;
|
||||
tx_payload = malloc(tx_len);
|
||||
memcpy(tx_payload, pl, n);
|
||||
}
|
||||
|
||||
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
|
||||
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
|
||||
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
|
||||
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
|
||||
hdr[11] = hdr11; // group-FEC флаги (0 = legacy)
|
||||
|
||||
ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len);
|
||||
free(tx_payload);
|
||||
|
||||
int16_t *out = (int16_t *)iio_buffer_start(buf);
|
||||
memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t));
|
||||
int idx = 0, done = 0;
|
||||
float complex symb[OFDM_M + OFDM_CP];
|
||||
while (!done && (idx + OFDM_M + OFDM_CP) <= BUF_SAMPLES) {
|
||||
done = ofdmflexframegen_write(fg, symb, OFDM_M + OFDM_CP);
|
||||
for (int i = 0; i < OFDM_M + OFDM_CP; i++) {
|
||||
out[2 * idx] = (int16_t)(crealf(symb[i]) * 32767 * amp);
|
||||
out[2 * idx + 1] = (int16_t)(cimagf(symb[i]) * 32767 * amp);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
// Только фактически заполненные idx сэмплов (техдолг #5): иначе мёртвый эфир
|
||||
// и сбитый межкадровый тайминг. push_partial даёт ту же длину, что write.
|
||||
if (idx > 0) iio_buffer_push_partial(buf, idx);
|
||||
if (pause_us > 0) usleep(pause_us);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Инициализация параметров
|
||||
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
|
||||
double gain = DEFAULT_GAIN;
|
||||
float amp = DEFAULT_AMP;
|
||||
const char *uri = TX_URI_DEFAULT;
|
||||
int pause_us = DEFAULT_PAUSE, use_fec = 1;
|
||||
int pause_us = DEFAULT_PAUSE, use_fec = 1, group_fec = 0;
|
||||
|
||||
// Обработка аргументов командной строки
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:h")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:Gh")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f': freq = atoll(optarg); break;
|
||||
case 'r': rate = atoll(optarg); break;
|
||||
@@ -37,12 +104,14 @@ int main(int argc, char *argv[]) {
|
||||
case 'p': pause_us = atoi(optarg); break;
|
||||
case 'u': uri = optarg; break;
|
||||
case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break;
|
||||
case 'G': group_fec = 1; break;
|
||||
case 'h':
|
||||
print_usage(argv[0], "ПЕРЕДАТЧИК");
|
||||
fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ TX:\n");
|
||||
fprintf(stderr, " -g УСИЛЕНИЕ Усиление в дБ (по умолчанию: %.0f)\n", DEFAULT_GAIN);
|
||||
fprintf(stderr, " -a АМПЛИТ. Амплитуда сигнала (по умолчанию: %.2f)\n", DEFAULT_AMP);
|
||||
fprintf(stderr, " -p ПАУЗА Пауза между кадрами в мкс (по умолчанию: %d)\n", DEFAULT_PAUSE);
|
||||
fprintf(stderr, " -G Групповой стирающий FEC 16+2 (§10 п.6)\n");
|
||||
fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
|
||||
fprintf(stderr, " cat test.bin | %s -f %lld -r %lld -b %lld -c rs8 > /dev/null\n",
|
||||
argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW);
|
||||
@@ -53,12 +122,14 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Настройка обработчиков сигналов
|
||||
setup_signal_handlers();
|
||||
if (group_fec) gf_init();
|
||||
|
||||
// Вывод конфигурации
|
||||
print_config("ПЕРЕДАТЧИК", freq, rate, bw, use_fec);
|
||||
fprintf(stderr, "Усиление: %.0f дБ\n", gain);
|
||||
fprintf(stderr, "Амплитуда: %.2f\n", amp);
|
||||
fprintf(stderr, "Пауза: %d мкс\n", pause_us);
|
||||
fprintf(stderr, "Group-FEC: %s\n", group_fec ? "16+2 (§10 п.6)" : "выкл");
|
||||
fprintf(stderr, "URI: %s\n", uri);
|
||||
fprintf(stderr, "═══════════════════════════════════════════════════\n");
|
||||
|
||||
@@ -104,95 +175,70 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n");
|
||||
|
||||
// Основной цикл передачи
|
||||
while (!stop) {
|
||||
uint8_t raw[MAX_PAYLOAD];
|
||||
size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
|
||||
if (!n) {
|
||||
if (feof(stdin) || stop) break;
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t *tx_payload = NULL;
|
||||
size_t tx_len = 0;
|
||||
uint8_t hdr[HDR_SIZE] = {0};
|
||||
|
||||
// Кодирование данных
|
||||
if (enc) {
|
||||
// CRC32 поверх исходных данных ДО RS: fec_decode() liquid не
|
||||
// сообщает о неисправимых блоках, поэтому единственный надёжный
|
||||
// критерий целостности на RX — этот CRC поверх декода (техдолг #3).
|
||||
uint8_t framed[MAX_PAYLOAD + 4];
|
||||
memcpy(framed, raw, n);
|
||||
unsigned int crc = crc_generate_key(LIQUID_CRC_32, raw, n);
|
||||
framed[n] = (crc >> 24) & 0xFF;
|
||||
framed[n + 1] = (crc >> 16) & 0xFF;
|
||||
framed[n + 2] = (crc >> 8) & 0xFF;
|
||||
framed[n + 3] = crc & 0xFF;
|
||||
size_t framed_n = n + 4;
|
||||
|
||||
size_t total_blocks = (framed_n + RS_DATA - 1) / RS_DATA;
|
||||
tx_len = total_blocks * RS_ENC;
|
||||
tx_payload = malloc(tx_len);
|
||||
|
||||
for (size_t b = 0; b < total_blocks; b++) {
|
||||
uint8_t blk[RS_DATA] = {0};
|
||||
size_t bytes = (b == total_blocks - 1) ? framed_n - b * RS_DATA : RS_DATA;
|
||||
memcpy(blk, framed + b * RS_DATA, bytes);
|
||||
fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
|
||||
// Основной цикл передачи. Legacy: кадр = чанк ≤1024 Б. Group-FEC: копим до
|
||||
// 16 чанков, затем шлём k данных + 2 паритета (P, Q) с флагами в hdr[11].
|
||||
if (!group_fec) {
|
||||
while (!stop) {
|
||||
uint8_t raw[MAX_PAYLOAD];
|
||||
size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
|
||||
if (!n) {
|
||||
if (feof(stdin) || stop) break;
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Заголовок: len = исходные n (без 4 байт CRC), nblocks/last по framed
|
||||
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
|
||||
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
|
||||
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
|
||||
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
|
||||
hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF;
|
||||
hdr[10] = framed_n % RS_DATA; hdr[11] = 0;
|
||||
} else {
|
||||
tx_len = n;
|
||||
tx_payload = malloc(tx_len);
|
||||
memcpy(tx_payload, raw, n);
|
||||
|
||||
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
|
||||
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
|
||||
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
|
||||
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
|
||||
}
|
||||
|
||||
// Сборка OFDM-кадра
|
||||
ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len);
|
||||
free(tx_payload);
|
||||
|
||||
// Формирование выходного сигнала
|
||||
int16_t *out = (int16_t *)iio_buffer_start(buf);
|
||||
memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t));
|
||||
int idx = 0, done = 0;
|
||||
float complex symb[OFDM_M + OFDM_CP];
|
||||
|
||||
while (!done && (idx + OFDM_M + OFDM_CP) <= BUF_SAMPLES) {
|
||||
done = ofdmflexframegen_write(fg, symb, OFDM_M + OFDM_CP);
|
||||
for (int i = 0; i < OFDM_M + OFDM_CP; i++) {
|
||||
out[2 * idx] = (int16_t)(crealf(symb[i]) * 32767 * amp);
|
||||
out[2 * idx + 1] = (int16_t)(cimagf(symb[i]) * 32767 * amp);
|
||||
idx++;
|
||||
tx_frame(fg, buf, enc, seq++, 0, raw, n, amp, pause_us);
|
||||
frames++; bytes_tx += n;
|
||||
if (time(NULL) > last) {
|
||||
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
|
||||
(unsigned long long)frames, (unsigned long long)bytes_tx);
|
||||
fflush(stderr);
|
||||
last = time(NULL);
|
||||
}
|
||||
}
|
||||
// Пушим только фактически заполненные idx сэмплов, а не весь буфер
|
||||
// 16384 (техдолг #5): иначе ~35% мёртвого эфира на кадр и сбитый
|
||||
// межкадровый тайминг. push_partial даёт ту же длину, что write.
|
||||
if (idx > 0) iio_buffer_push_partial(buf, idx);
|
||||
} else {
|
||||
static uint8_t gdata[GF_K][GF_CHUNK]; // накопитель группы
|
||||
const uint8_t *gptr[GF_K];
|
||||
uint16_t glen[GF_K];
|
||||
int k = 0, done_reading = 0;
|
||||
|
||||
// Обновление статистики
|
||||
frames++; bytes_tx += n; seq++;
|
||||
if (time(NULL) > last) {
|
||||
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
|
||||
(unsigned long long)frames, (unsigned long long)bytes_tx);
|
||||
fflush(stderr);
|
||||
last = time(NULL);
|
||||
while (!stop && !done_reading) {
|
||||
size_t r = fread(gdata[k], 1, GF_CHUNK, stdin);
|
||||
if (r > 0) {
|
||||
if (r < GF_CHUNK) memset(gdata[k] + r, 0, GF_CHUNK - r); // паддинг под GF
|
||||
glen[k] = (uint16_t)r;
|
||||
gptr[k] = gdata[k];
|
||||
k++;
|
||||
}
|
||||
if (r < GF_CHUNK) done_reading = 1; // fread добирает до конца → короткий = EOF
|
||||
|
||||
if (k == GF_K || (done_reading && k > 0)) {
|
||||
// k кадров данных: idx=0..k-1
|
||||
for (int i = 0; i < k; i++) {
|
||||
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | i,
|
||||
gdata[i], glen[i], amp, pause_us);
|
||||
frames++; bytes_tx += glen[i];
|
||||
}
|
||||
// паритеты P (idx=k) и Q (idx=k+1); мета = k′ + длина хвоста
|
||||
uint8_t P[GF_CHUNK], Q[GF_CHUNK], par[GF_PAR_PAY];
|
||||
gf_encode(gptr, k, P, Q);
|
||||
uint16_t meta = GF_META_PACK(k, glen[k - 1]);
|
||||
par[0] = meta >> 8; par[1] = meta & 0xFF;
|
||||
memcpy(par + GF_META, P, GF_CHUNK);
|
||||
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | k,
|
||||
par, GF_PAR_PAY, amp, pause_us);
|
||||
memcpy(par + GF_META, Q, GF_CHUNK);
|
||||
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | GF_FLAG_Q | (k + 1),
|
||||
par, GF_PAR_PAY, amp, pause_us);
|
||||
frames += 2;
|
||||
k = 0;
|
||||
if (time(NULL) > last) {
|
||||
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт (group-FEC)",
|
||||
(unsigned long long)frames, (unsigned long long)bytes_tx);
|
||||
fflush(stderr);
|
||||
last = time(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pause_us > 0) usleep(pause_us);
|
||||
}
|
||||
|
||||
fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n",
|
||||
|
||||
Reference in New Issue
Block a user