/** * transmitter.c — OFDM-передатчик с RS(255,223) FEC для PlutoSDR * * ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА (кабель + аттенюатор): * cat test.bin | ./transmitter -f 915000000 -r 1920000 -b 1500000 -g -40 -a 0.2 -p 0 -c rs8 -u local: */ #include "common.h" #include "group_fec.h" #include "frame_tx.h" #include "feedback.h" // приём STATUS обратного канала (этап 2, §10 п.7) // Параметры передатчика по умолчанию #define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ #define DEFAULT_AMP 0.2f // Амплитуда сигнала #define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс #define BUF_SAMPLES 16384 // Размер буфера в отсчётах #define MAX_PAYLOAD GF_CHUNK // 1024: размер чанка данных (== GF_CHUNK) #define FB_RX_GAIN 50.0 // Усиление RX обратного канала (ручное), §10 п.7 // Внешняя переменная флага завершения (определена в common.c) extern volatile sig_atomic_t stop; // Сигнатура кадров данных/паритета GF (см. frame_tx.h — тип кадра различает // hdr[0..1]; END/STATUS обратного канала используют другие сигнатуры, §10 п.7). static const uint8_t SIG_DATA[2] = { HDR_SIGNATURE_0, HDR_SIGNATURE_1 }; static const uint8_t SIG_END[2] = { HDR_SIGNATURE_0, HDR_SIG_END_1 }; // Параметры EOF-хендшейка (этап 3). После EOF A шлёт END каждые END_PERIOD_MS и // ждёт STATUS с флагом COMPLETE. В этой фазе эфир 915 почти пуст (END ≈ 1.4 мс // на 100 мс), поэтому fb-RX платы A больше не глохнет в собственные бёрсты и // слышит практически все STATUS (плато 50% из §12.8 — эффект duty 57%). #define END_PERIOD_MS 100 #define END_TIMEOUT_MS 10000 // 100 попыток; молчание = честный отказ (exit 2) static int64_t now_ms(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (int64_t)t.tv_sec * 1000 + t.tv_nsec / 1000000; } // --- Обратный канал: приём STATUS на 868 (этап 2, §10 п.7) -------------- // Отдельный поток на ядре 1 (данные TX — ядро 0). Свой синхронизатор с // header_len 12 и свой RS-декодер. Слушает rxfb — второй cf-девайс, открытый // pluto_init_fdd. Декодированный STATUS публикуется снимком для main: пока его // читает только END-фаза (этап 3a), ретрансмит по NACK — этап 3b. #define FB_RX_BUF_SAMPLES 16384 static struct iio_buffer *g_fbrx_buf = NULL; static ofdmflexframesync g_fbrx_fs = NULL; static fec g_fbrx_dec = NULL; static volatile sig_atomic_t fb_stop = 0; static uint64_t g_fb_status_rx = 0; // сколько STATUS декодировано (для итога) // Снимок последнего STATUS для main (этап 3). STATUS кумулятивен — это состояние // RX целиком, а не событие, поэтому очередь не нужна: latest-wins. Пишет поток // fb_rx, читает main; TX-буфером по-прежнему владеет ТОЛЬКО main (передавать из // callback нельзя — это ядро 1 и чужой буфер). static pthread_mutex_t g_fb_mtx = PTHREAD_MUTEX_INITIALIZER; static fb_status_t g_fb_st; static int g_fb_fresh = 0; // 1 = снимок не прочитан main'ом // COMPLETE — отдельная защёлка, а не поле снимка: снимок «съедает» arq_service, // и подтверждение, пришедшее в том же STATUS, потерялось бы. Флаг монотонный — // once complete, always complete. static volatile sig_atomic_t g_fb_complete = 0; // Забрать свежий снимок. Возврат 1 — *out заполнен, 0 — нового STATUS не было. static int fb_take(fb_status_t *out) { int fresh; pthread_mutex_lock(&g_fb_mtx); fresh = g_fb_fresh; if (fresh) { *out = g_fb_st; g_fb_fresh = 0; } pthread_mutex_unlock(&g_fb_mtx); return fresh; } // callback синхронизатора обратного канала: отсев по сигнатуре F0 55, RS+CRC // декод, распаковка, лог и публикация снимка для main. static int fb_callback(unsigned char *hdr, int hdr_valid, unsigned char *pay, unsigned int pay_len, int pay_valid, framesyncstats_s stats, void *user) { (void)pay_valid; (void)stats; (void)user; if (!hdr_valid) return 0; if (hdr[0] != FB_SIG_0 || hdr[1] != FB_SIG_1) return 0; // не STATUS static uint8_t dec[RS_ENC * 2]; // STATUS ≤ 1 RS-блок, запас на будущее size_t dlen = 0; if (!phy_rx_decode(hdr, pay, pay_len, g_fbrx_dec, dec, sizeof(dec), &dlen)) return 0; // RS/CRC не прошли — молча дропаем fb_status_t st; if (fb_status_unpack(dec, dlen, &st) != 0) return 0; g_fb_status_rx++; if (st.flags & FB_FLAG_COMPLETE) g_fb_complete = 1; pthread_mutex_lock(&g_fb_mtx); g_fb_st = st; g_fb_fresh = 1; pthread_mutex_unlock(&g_fb_mtx); fprintf(stderr, "\n[FB] highest=%u rx_frames=%u overruns=%u%s%s nack=%u{", st.highest_seq, st.rx_frames, st.overruns, (st.flags & FB_FLAG_END_SEEN) ? " END" : "", (st.flags & FB_FLAG_COMPLETE) ? " COMPLETE" : "", st.nack_cnt); for (int i = 0; i < st.nack_cnt && i < 8; i++) fprintf(stderr, "%s%u", i ? "," : "", st.nack[i]); if (st.nack_cnt > 8) fprintf(stderr, ",…"); fprintf(stderr, "}\n"); return 0; } static void *fb_rx_thread(void *arg) { (void)arg; pin_to_cpu(1); static liquid_float_complex conv[FB_RX_BUF_SAMPLES]; while (!fb_stop && !stop) { ssize_t nb = iio_buffer_refill(g_fbrx_buf); if (nb < 0) { if (fb_stop || stop) break; usleep(500); continue; } int16_t *bb = (int16_t *)iio_buffer_start(g_fbrx_buf); int ns = (iio_buffer_end(g_fbrx_buf) - iio_buffer_start(g_fbrx_buf)) / (int)sizeof(int16_t) / 2; if (ns > FB_RX_BUF_SAMPLES) ns = FB_RX_BUF_SAMPLES; for (int i = 0; i < ns; i++) conv[i] = ((float)bb[2*i] / 2048.0f) + ((float)bb[2*i+1] / 2048.0f) * _Complex_I; ofdmflexframesync_execute(g_fbrx_fs, conv, ns); } return NULL; } // --- TX-окно и ARQ (этап 3b) -------------------------------------------- // Копия каждого переданного кадра: без неё дослать нечего — stdin уже прочитан // и не перематывается. Кольцо по seq % TXWIN, хранит СЫРОЙ payload (до RS/OFDM): // phy_tx_frame детерминирован, поэтому повтор уходит в эфир байт-в-байт. // TXWIN(2048) > RB_CAP(1024) приёмника — кадр, который B ещё может принять в // своё окно переупорядочивания, гарантированно есть у нас в окне. #define TXWIN 2048 #define ARQ_RETX_MS 400 // per-seq пауза между повторами одного seq. // 400 = петля «потеря → детект (~10 мс) → слот STATUS (~50 мс) → доставка при // p≈0.5 (~100–300 мс) → ретрансмит (~30 мс) → подтверждение (~150–250 мс)». // Меньше — дубли жгут эфир (кадр 9.3 мс); больше — дыра дольше держит окно B. typedef struct { uint32_t seq; int64_t last_ms; // когда seq последний раз уходил в эфир uint16_t len; uint8_t hdr11; // флаги group-FEC (0 = legacy) uint8_t valid; uint8_t pay[GF_PAR_PAY]; // 1026: паритетный кадр GF — самый большой } txslot_t; static txslot_t txwin[TXWIN]; // ~2.1 МБ .bss (из 1 ГБ платы) static int g_arq_on = 0; // ARQ включён (требует -F: без обратного канала // NACK взять неоткуда). Работает и с group-FEC. static uint64_t g_retx = 0; // отправлено ретрансмитов static uint64_t g_retx_old = 0; // NACK на seq, вышедший из окна (≈21 с эфира) // Последний известный список дыр B. Живёт между снимками: пока не пришёл новый // STATUS, дожимаем прежний — от дублей защищает ARQ_RETX_MS, а не свежесть. static uint32_t g_nack[FB_MAX_NACK]; static int g_nack_cnt = 0; // Передать НОВЫЙ кадр данных/паритета, попутно сохранив его в окне. static void tx_send_data(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) { txslot_t *s = &txwin[seq % TXWIN]; s->seq = seq; s->hdr11 = hdr11; s->len = (uint16_t)n; s->valid = 1; memcpy(s->pay, pl, n); phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq, hdr11, pl, n, amp, pause_us); s->last_ms = now_ms(); // после отправки: паузу считаем от конца кадра } // Досылка по NACK. budget — потолок ретрансмитов за один вызов: очередь дыр // не должна вытеснять новые данные (голодание фронта). Вызывается ТОЛЬКО из // main — TX-буфер один и владеет им этот поток. static void arq_service(ofdmflexframegen fg, struct iio_buffer *buf, fec enc, float amp, int pause_us, int budget) { if (!g_arq_on) return; fb_status_t st; if (fb_take(&st)) { // свежий снимок → обновить список дыр g_nack_cnt = st.nack_cnt < FB_MAX_NACK ? st.nack_cnt : FB_MAX_NACK; memcpy(g_nack, st.nack, (size_t)g_nack_cnt * sizeof(g_nack[0])); } int64_t t = now_ms(); for (int i = 0; i < g_nack_cnt && budget > 0; i++) { uint32_t seq = g_nack[i]; txslot_t *s = &txwin[seq % TXWIN]; if (!s->valid || s->seq != seq) { // окно провернулось — дослать нечем g_retx_old++; // → END-таймаут, честный отказ continue; } if (t - s->last_ms < ARQ_RETX_MS) continue; // повтор ещё в полёте phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq, s->hdr11, s->pay, s->len, amp, pause_us); t = now_ms(); s->last_ms = t; g_retx++; budget--; } } // EOF-хендшейк (этап 3, ядро 0/main): шлём END с итогом передачи, пока B не // подтвердит COMPLETE флагом в STATUS. Возврат 0 — подтверждено, 2 — таймаут или // прерывание: на той стороне файл неполон. Честный отказ вместо тихой дыры // (техдолг #6) — раньше A просто закрывал буфер и рапортовал успех. static int end_phase(ofdmflexframegen fg, struct iio_buffer *buf, fec enc, float amp, int pause_us, uint32_t total_seq, uint64_t total_bytes) { fb_end_t e = { .total_seq = total_seq, .total_bytes = total_bytes }; uint8_t pay[FB_END_LEN]; size_t n = fb_end_pack(&e, pay); uint32_t eseq = 0; // свой счётчик: RX ветвится по сигнатуре ДО // seq-детектора данных → дыр от END не будет int64_t t0 = now_ms(); int sent = 0; fprintf(stderr, "[ARQ] END: кадров %u, байт %llu — жду COMPLETE (таймаут %d мс)\n", total_seq, (unsigned long long)total_bytes, END_TIMEOUT_MS); while (!stop && !g_fb_complete && now_ms() - t0 < END_TIMEOUT_MS) { phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_END, eseq++, 0, pay, n, amp, 0); sent++; // Бюджет выше, чем в data-фазе: новых данных больше нет, эфир свободен, // и закрыть остаток дыр надо именно сейчас — таймаут тикает. arq_service(fg, buf, enc, amp, pause_us, 4); int64_t w = now_ms(); while (!stop && !g_fb_complete && now_ms() - w < END_PERIOD_MS) usleep(5000); // пауза между END: опрос защёлки, не sleep } if (g_fb_complete) { fprintf(stderr, "[ARQ] COMPLETE подтверждён: END × %d, ретрансмитов %llu, %lld мс\n", sent, (unsigned long long)g_retx, (long long)(now_ms() - t0)); return 0; } fprintf(stderr, "[ARQ] таймаут COMPLETE: END × %d за %lld мс — приём НЕ подтверждён\n", sent, (long long)(now_ms() - t0)); return 2; } 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, group_fec = 0; long long fb_freq = 0; // 0 = симплекс; иначе FDD, LO обратного RX // Обработка аргументов командной строки int opt; while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:F:Gh")) != -1) { switch (opt) { case 'f': freq = atoll(optarg); break; case 'r': rate = atoll(optarg); break; case 'b': bw = atoll(optarg); break; case 'g': gain = atof(optarg); break; case 'a': amp = atof(optarg); break; case 'p': pause_us = atoi(optarg); break; case 'u': uri = optarg; break; case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break; case 'F': fb_freq = atoll(optarg); 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, " -F ЧАСТОТА FDD: частота обратного RX в Гц (§10 п.7). Без -F — симплекс\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); return 0; default: return 1; } } // Настройка обработчиков сигналов 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, "Дуплекс: %s\n", fb_freq ? "FDD (обратный канал: приём STATUS на 868)" : "симплекс"); if (fb_freq) fprintf(stderr, "Обр. RX LO: %.1f МГц\n", fb_freq / 1e6); fprintf(stderr, "URI: %s\n", uri); fprintf(stderr, "═══════════════════════════════════════════════════\n"); // Инициализация PlutoSDR. С -F открываем оба cf-девайса и настраиваем оба // LO (обратный RX пока только сконфигурирован; приём по нему — этап 2). struct iio_context *ctx = NULL; struct iio_device *phy = NULL, *tx = NULL, *rxfb = NULL; if (fb_freq) { if (pluto_init_fdd(uri, &ctx, &phy, &tx, &rxfb) != 0) return 1; if (pluto_configure_fdd(phy, freq, fb_freq, rate, bw, gain, FB_RX_GAIN) != 0) { LOG_ERROR("Настройка FDD-радиотракта не удалась"); iio_context_destroy(ctx); return 1; } } else { if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1; if (pluto_configure(phy, freq, rate, bw, gain, 1) != 0) { LOG_ERROR("Настройка радиотракта не удалась"); iio_context_destroy(ctx); return 1; } } // Включение каналов передатчика iio_channel_enable(iio_device_find_channel(tx, "voltage0", true)); iio_channel_enable(iio_device_find_channel(tx, "voltage1", true)); // Создание буфера передачи struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false); if (!buf) { LOG_ERROR("Ошибка создания буфера передачи"); iio_context_destroy(ctx); return 1; } // Инициализация FEC и OFDM fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL; ofdmflexframegenprops_s fgp; ofdmflexframegenprops_init_default(&fgp); fgp.check = LIQUID_CRC_32; ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp); // Наш заголовок 12 Б; по умолчанию liquid шлёт 8 — байты 8..11 (nblocks, // last_block_bytes) без этого в эфир не уходят. RX выставляет то же. ofdmflexframegen_set_header_len(fg, HDR_SIZE); // Обратный канал (этап 2): поток fb_rx слушает STATUS на 868 (rxfb). Пока // только логирует. Данные TX уводим на ядро 0, fb_rx — на ядро 1 (§affinity). pthread_t fb_tid; int fb_started = 0; if (fb_freq) { iio_channel_enable(iio_device_find_channel(rxfb, "voltage0", false)); iio_channel_enable(iio_device_find_channel(rxfb, "voltage1", false)); g_fbrx_buf = iio_device_create_buffer(rxfb, FB_RX_BUF_SAMPLES, false); if (!g_fbrx_buf) { LOG_ERROR("Буфер обратного канала не создан — STATUS не принимаются"); } else { g_fbrx_fs = ofdmflexframesync_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, fb_callback, NULL); ofdmflexframesync_set_header_len(g_fbrx_fs, HDR_SIZE); g_fbrx_dec = fec_create(LIQUID_FEC_RS_M8, NULL); pin_to_cpu(0); // TX-данные — ядро 0 pthread_create(&fb_tid, NULL, fb_rx_thread, NULL); fb_started = 1; // Этап 3c: ARQ работает и с group-FEC. Ретрансмит кадра старой группы // больше не ломает сборку на RX (правило base < g.base в group_route), // а дыры, закрытые паритетом, RX снимает с NACK сам — досылать их не // придётся. GF и ARQ дополняют друг друга: GF гасит одиночные потери // без задержки, ARQ добирает то, что паритету не по силам. g_arq_on = 1; fprintf(stderr, "Обратный канал: слушаю STATUS на %.1f МГц (ядро 1)\n", fb_freq / 1e6); fprintf(stderr, "ARQ: вкл (окно %d кадров, повтор не чаще %d мс)\n", TXWIN, ARQ_RETX_MS); } } // Счётчики статистики uint64_t frames = 0, bytes_tx = 0; uint32_t seq = 0; time_t last = time(NULL); fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n"); // Основной цикл передачи. 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; } tx_send_data(fg, buf, enc, seq++, 0, raw, n, amp, pause_us); frames++; bytes_tx += n; // Досылка вперемешку с новыми данными: дыра закрывается, пока B ещё // держит её в окне переупорядочивания. Бюджет 2 — потолок, чтобы // очередь NACK не вытеснила фронт передачи. arq_service(fg, buf, enc, amp, pause_us, 2); if (time(NULL) > last) { fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт", (unsigned long long)frames, (unsigned long long)bytes_tx); fflush(stderr); last = time(NULL); } } } 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; 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_send_data(fg, buf, enc, seq++, GF_FLAG_MODE | i, gdata[i], glen[i], amp, pause_us); frames++; bytes_tx += glen[i]; arq_service(fg, buf, enc, amp, pause_us, 2); // как в legacy } // паритеты 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_send_data(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_send_data(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | GF_FLAG_Q | (k + 1), par, GF_PAR_PAY, amp, pause_us); frames += 2; arq_service(fg, buf, enc, amp, pause_us, 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); } } } } fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n", (unsigned long long)frames, (unsigned long long)bytes_tx); // EOF-хендшейк (этап 3). Только при живом обратном канале: без него COMPLETE // услышать нечем и END-фаза всегда упиралась бы в таймаут. Симплекс идёт // прежним путём — молча в дренаж, exit 0 (регрессия §12 не меняется). int rc = 0; if (fb_started) rc = end_phase(fg, buf, enc, amp, pause_us, seq, bytes_tx); // Дренаж: выпихнуть последний кадр из kernel-буферов DMA перед закрытием. // Без паузы iio_buffer_destroy() срезает хвост файла в эфире (техдолг #5). { int16_t *out = (int16_t *)iio_buffer_start(buf); memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t)); iio_buffer_push(buf); usleep(100000); // ~4 kernel-буфера при 1.92 MSPS ≈ 35 мс, с запасом } // Остановить обратный канал: fb_rx выходит по fb_stop в течение одного // refill (~8.5 мс на 16384 сэмпл @ 1.92 Msps), затем join. if (fb_started) { fb_stop = 1; pthread_join(fb_tid, NULL); fprintf(stderr, "Обратный канал: принято STATUS: %llu\n", (unsigned long long)g_fb_status_rx); fprintf(stderr, "ARQ: ретрансмитов: %llu | NACK вне окна: %llu\n", (unsigned long long)g_retx, (unsigned long long)g_retx_old); } // Освобождение ресурсов if (enc) fec_destroy(enc); ofdmflexframegen_destroy(fg); iio_buffer_destroy(buf); if (g_fbrx_fs) ofdmflexframesync_destroy(g_fbrx_fs); if (g_fbrx_dec) fec_destroy(g_fbrx_dec); if (g_fbrx_buf) iio_buffer_destroy(g_fbrx_buf); iio_context_destroy(ctx); return rc; }