mirror of
https://github.com/drowe67/codec2
synced 2026-08-14 19:31:34 -04:00
467 lines
18 KiB
C
467 lines
18 KiB
C
/*---------------------------------------------------------------------------*\
|
|
|
|
FILE........: freedv_data_raw_tx.c
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: May 2020
|
|
|
|
Demonstrates transmitting frames of raw data bytes (instead of
|
|
compressed speech) using the FreeDV API.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
Copyright (C) 2020 David Rowe
|
|
|
|
All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License version 2.1, as
|
|
published by the Free Software Foundation. This program is
|
|
distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <getopt.h>
|
|
|
|
#include "freedv_api.h"
|
|
#include "fsk.h"
|
|
#include "ofdm_internal.h"
|
|
#include "ldpc_codes.h"
|
|
|
|
size_t send_preamble(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out);
|
|
size_t send_modulated_data(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out, uint8_t bytes_in[]);
|
|
size_t send_postamble(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out);
|
|
size_t send_silence(FILE *fout, size_t shorts_per_sample, size_t samples_delay);
|
|
void comp_to_short(short mod_out_short[], COMP mod_out_comp[], int n_mod_out);
|
|
|
|
int main(int argc, char *argv[]) {
|
|
FILE *fin, *fout;
|
|
char codename[80] = "H_256_512_4";
|
|
struct freedv_advanced adv = {0,2,100,8000,1000,200, codename};
|
|
struct freedv *freedv;
|
|
int mode;
|
|
int use_clip, use_txbpf, testframes, Ntestframes = 0;
|
|
int use_complex = 0;
|
|
float amp = FSK_SCALE;
|
|
size_t shorts_per_sample = 1;
|
|
int Nbursts = 1, sequence_numbers = 0;
|
|
int inter_burst_delay_ms = 0;
|
|
int postdelay_ms = 0;
|
|
uint8_t source_byte = 0;
|
|
|
|
use_clip = -1; use_txbpf = -1; testframes = 0;
|
|
int framesperburst = 1;
|
|
int quiet = 0;
|
|
|
|
int o = 0;
|
|
int opt_idx = 0;
|
|
while( o != -1 ){
|
|
static struct option long_opts[] = {
|
|
{"testframes", required_argument, 0, 't'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{"txbpf", required_argument, 0, 'b'},
|
|
{"clip", required_argument, 0, 'l'},
|
|
{"Fs", required_argument, 0, 'f'},
|
|
{"Rs", required_argument, 0, 'r'},
|
|
{"tone1", required_argument, 0, '1'},
|
|
{"shift", required_argument, 0, 's'},
|
|
{"bursts", required_argument, 0, 'e'},
|
|
{"framesperburst", required_argument, 0, 'g'},
|
|
{"delay", required_argument, 0, 'j'},
|
|
{"postdelay", required_argument, 0, 'k'},
|
|
{"seq", no_argument, 0, 'd'},
|
|
{"source", required_argument, 0, 'i'},
|
|
{"amp", required_argument, 0, 'a'},
|
|
{"quiet", no_argument, 0, 'q'},
|
|
{"complexout", no_argument, 0, 'c'},
|
|
{"code", required_argument, 0, 'o'},
|
|
{"listcodes", no_argument, 0, 'x'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
o = getopt_long(argc,argv,"a:cdt:hb:l:e:f:g:r:1:s:m:qi:o:x",long_opts,&opt_idx);
|
|
|
|
switch(o) {
|
|
case 'a':
|
|
amp = atof(optarg)/2.0;
|
|
break;
|
|
case 'b':
|
|
use_txbpf = atoi(optarg);
|
|
break;
|
|
case 'c':
|
|
use_complex = 1;
|
|
shorts_per_sample = 2;
|
|
break;
|
|
case 'd':
|
|
sequence_numbers = 1;
|
|
break;
|
|
case 'i':
|
|
source_byte = strtol(optarg, NULL, 0);
|
|
fprintf(stderr,"source byte: 0x%02x\n", source_byte);
|
|
break;
|
|
case 'e':
|
|
Nbursts = atoi(optarg);
|
|
break;
|
|
case 'g':
|
|
framesperburst = atoi(optarg);
|
|
break;
|
|
case 'j':
|
|
inter_burst_delay_ms = atoi(optarg);
|
|
break;
|
|
case 'k':
|
|
postdelay_ms = atoi(optarg);
|
|
break;
|
|
case 't':
|
|
testframes = 1;
|
|
Ntestframes = atoi(optarg);
|
|
break;
|
|
case 'l':
|
|
use_clip = atoi(optarg);
|
|
break;
|
|
case 'm':
|
|
adv.M = atoi(optarg);
|
|
break;
|
|
case 'q':
|
|
quiet = 1;
|
|
break;
|
|
case 'f':
|
|
adv.Fs = atoi(optarg);
|
|
break;
|
|
case 'r':
|
|
adv.Rs = atoi(optarg);
|
|
break;
|
|
case '1':
|
|
adv.first_tone = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
adv.tone_spacing = atoi(optarg);
|
|
break;
|
|
case 'o':
|
|
if (ldpc_codes_find(optarg) == -1) {
|
|
fprintf(stderr, "%s not found, try --listcodes\n", optarg);
|
|
exit(1);
|
|
}
|
|
strcpy(codename, optarg);
|
|
break;
|
|
case 'x':
|
|
ldpc_codes_list();
|
|
exit(0);
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
goto helpmsg;
|
|
break;
|
|
}
|
|
}
|
|
int dx = optind;
|
|
|
|
if (argc < 4) {
|
|
helpmsg:
|
|
fprintf(stderr, "\nusage: %s [options] FSK_LDPC|DATAC0|... InputBinaryDataFile OutputModemRawFile\n"
|
|
"\n"
|
|
" --testframes T send a total of T test frames (T should equal B*N)\n"
|
|
" --bursts B send B bursts of N testframes (default 1)\n"
|
|
" --framesperburst N burst mode, N frames per burst (default 1)\n"
|
|
" --delay ms testframe inter-burst delay in ms\n"
|
|
" --postdelay ms additional delay at end of run in ms\n"
|
|
" -c complex signed 16 bit output format (default real)\n"
|
|
" --clip 0|1 clipping for reduced PAPR\n"
|
|
" --txbpf 0|1 bandpass filter\n"
|
|
" --seq send packet sequence numbers (breaks testframe BER counting)\n"
|
|
" --source Byte insert a (non-zero) source address att byte[0]\n"
|
|
" --complexout complex sample output (default real)\n"
|
|
" --quiet\n"
|
|
"\n"
|
|
"For FSK_LDPC only:\n\n"
|
|
" -a amp maximum amplitude of FSK signal\n"
|
|
" -m 2|4 number of FSK tones\n"
|
|
" --Fs FreqHz sample rate (default 8000)\n"
|
|
" --Rs FreqHz symbol rate (default 100)\n"
|
|
" --tone1 FreqHz freq of first tone (default 1000)\n"
|
|
" --shift FreqHz shift between tones (default 200)\n\n"
|
|
" --code CodeName LDPC code (defaults (512,256)\n"
|
|
" --listcodes list available LDPC codes\n\n"
|
|
, argv[0]);
|
|
fprintf(stderr, "example: $ %s --testframes 6 --bursts 3 --framesperburst 2 datac0 /dev/zero samples.s16\n", argv[0]);
|
|
fprintf(stderr, "example: $ %s -c --testframes 10 FSK_LDPC /dev/zero samples.iq16\n\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
if( (argc - dx) < 3) {
|
|
fprintf(stderr, "too few arguments.\n");
|
|
goto helpmsg;
|
|
}
|
|
|
|
mode = -1;
|
|
if (!strcmp(argv[dx],"FSK_LDPC") || !strcmp(argv[dx],"fsk_ldpc")) mode = FREEDV_MODE_FSK_LDPC;
|
|
if (!strcmp(argv[dx],"DATAC0") || !strcmp(argv[dx],"datac0")) mode = FREEDV_MODE_DATAC0;
|
|
if (!strcmp(argv[dx],"DATAC1") || !strcmp(argv[dx],"datac1")) mode = FREEDV_MODE_DATAC1;
|
|
if (!strcmp(argv[dx],"DATAC3") || !strcmp(argv[dx],"datac3")) mode = FREEDV_MODE_DATAC3;
|
|
if (!strcmp(argv[dx],"DATAC4") || !strcmp(argv[dx],"datac4")) mode = FREEDV_MODE_DATAC4;
|
|
if (!strcmp(argv[dx],"DATAC13") || !strcmp(argv[dx],"datac13")) mode = FREEDV_MODE_DATAC13;
|
|
if (mode == -1) {
|
|
fprintf(stderr, "Error: in mode: %s", argv[dx]);
|
|
exit(1);
|
|
}
|
|
|
|
if (strcmp(argv[dx+1], "-") == 0) fin = stdin;
|
|
else if ( (fin = fopen(argv[dx+1],"rb")) == NULL ) {
|
|
fprintf(stderr, "Error opening input file of bytes: %s: %s.\n", argv[dx+1], strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if (strcmp(argv[dx+2], "-") == 0) fout = stdout;
|
|
else if ( (fout = fopen(argv[dx+2],"wb")) == NULL ) {
|
|
fprintf(stderr, "Error opening output modem sample file: %s: %s.\n", argv[dx+2], strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if (mode != FREEDV_MODE_FSK_LDPC)
|
|
freedv = freedv_open(mode);
|
|
else
|
|
freedv = freedv_open_advanced(mode, &adv);
|
|
|
|
assert(freedv != NULL);
|
|
|
|
/* these are optional ------------------ */
|
|
if (use_clip != -1) freedv_set_clip(freedv, use_clip);
|
|
if (use_txbpf != -1) freedv_set_tx_bpf(freedv, use_txbpf);
|
|
freedv_set_tx_amp(freedv, amp);
|
|
|
|
/* Data modes have a multiple of 8 payload bits/frame */
|
|
int bytes_per_modem_frame = freedv_get_bits_per_modem_frame(freedv)/8;
|
|
int payload_bytes_per_modem_frame = bytes_per_modem_frame;
|
|
payload_bytes_per_modem_frame -= 2; /* 16 bits used for the CRC */
|
|
if (!quiet) fprintf(stderr, "payload bytes_per_modem_frame: %d ", payload_bytes_per_modem_frame);
|
|
assert((freedv_get_bits_per_modem_frame(freedv) % 8) == 0);
|
|
int n_mod_out = freedv_get_n_tx_modem_samples(freedv);
|
|
uint8_t bytes_in[bytes_per_modem_frame];
|
|
|
|
if (mode == FREEDV_MODE_FSK_LDPC) {
|
|
if (!quiet) fprintf(stderr, "Frequency: Fs: %4.1f Hz Rs: %5.0f Hz Tone1: %5.0f Hz Shift: %5.0f Hz M: %d \n",
|
|
(float)adv.Fs, (float)adv.Rs, (float)adv.first_tone, (float)adv.tone_spacing, adv.M);
|
|
|
|
if (adv.tone_spacing < adv.Rs) {
|
|
fprintf(stderr, "Need shift: %d > Rs: %d\n", adv.tone_spacing, adv.Rs);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* a few sanity checks */
|
|
if (testframes) {
|
|
if (Ntestframes != framesperburst*Nbursts) {
|
|
if (!quiet) fprintf(stderr, "Adjusting testframes to equal framesperburst*bursts\n");
|
|
Ntestframes = framesperburst*Nbursts;
|
|
}
|
|
} else {
|
|
if (framesperburst != 1) {
|
|
fprintf(stderr, "Only one frame per burst currently supported in stdin mode\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int frames = 0;
|
|
size_t on_samples = 0;
|
|
size_t off_samples = 0;
|
|
|
|
/* initial silence */
|
|
|
|
int samples_delay = 0;
|
|
if (inter_burst_delay_ms) {
|
|
/* user defined inter-burst delay */
|
|
samples_delay = FREEDV_FS_8000*inter_burst_delay_ms/1000;
|
|
}
|
|
else {
|
|
/* just enough silence at the end of burst to allow demod to complete processing */
|
|
samples_delay = 2*freedv_get_n_nom_modem_samples(freedv);
|
|
}
|
|
off_samples += send_silence(fout, shorts_per_sample, samples_delay);
|
|
|
|
/* --------- Test Frame Mode --------------------------------------------------*/
|
|
|
|
if (testframes) {
|
|
|
|
/* generate a fixed test frame */
|
|
uint8_t testframe_bytes[bytes_per_modem_frame];
|
|
memset(testframe_bytes, 0, bytes_per_modem_frame);
|
|
int bits_per_frame = freedv_get_bits_per_modem_frame(freedv);
|
|
uint8_t testframe_bits[bits_per_frame];
|
|
ofdm_generate_payload_data_bits(testframe_bits, bits_per_frame);
|
|
freedv_pack(testframe_bytes, testframe_bits, bits_per_frame);
|
|
if (!quiet) fprintf(stderr, "\n");
|
|
|
|
for(int b=0; b<Nbursts; b++) {
|
|
on_samples += send_preamble(freedv, fout, use_complex, n_mod_out);
|
|
|
|
for(int fpb=0; fpb<framesperburst; fpb++) {
|
|
memcpy(bytes_in, testframe_bytes, bytes_per_modem_frame);
|
|
if (source_byte) bytes_in[0] = source_byte;
|
|
if (sequence_numbers) bytes_in[1] = (frames+1) & 0xff;
|
|
|
|
/* The raw data modes requires a CRC in the last two bytes */
|
|
uint16_t crc16 = freedv_gen_crc16(bytes_in, payload_bytes_per_modem_frame);
|
|
bytes_in[bytes_per_modem_frame-2] = crc16 >> 8;
|
|
bytes_in[bytes_per_modem_frame-1] = crc16 & 0xff;
|
|
|
|
on_samples += send_modulated_data(freedv, fout, use_complex, n_mod_out, bytes_in);
|
|
|
|
/* if using pipes we don't want the usual buffering to occur */
|
|
if (fout == stdout) fflush(stdout);
|
|
|
|
frames++;
|
|
}
|
|
|
|
on_samples += send_postamble(freedv, fout, use_complex, n_mod_out);
|
|
|
|
int samples_delay = 0;
|
|
if (inter_burst_delay_ms) {
|
|
/* user defined inter-burst delay */
|
|
samples_delay = FREEDV_FS_8000*inter_burst_delay_ms/1000;
|
|
}
|
|
else {
|
|
/* just enough silence at the end of burst to allow demod to complete processing */
|
|
samples_delay = 2*freedv_get_n_nom_modem_samples(freedv);
|
|
}
|
|
off_samples += send_silence(fout, shorts_per_sample, samples_delay);
|
|
}
|
|
|
|
} else {
|
|
|
|
/* --------- modulate data from stdin mode --------------------------------------------------*/
|
|
|
|
while (fread(bytes_in, sizeof(uint8_t), payload_bytes_per_modem_frame, fin) == payload_bytes_per_modem_frame) {
|
|
on_samples += send_preamble(freedv, fout, use_complex, n_mod_out);
|
|
|
|
if (source_byte) bytes_in[0] = source_byte;
|
|
if (sequence_numbers) bytes_in[1] = (frames+1) & 0xff;
|
|
|
|
/* The raw data modes requires a CRC in the last two bytes */
|
|
uint16_t crc16 = freedv_gen_crc16(bytes_in, payload_bytes_per_modem_frame);
|
|
bytes_in[bytes_per_modem_frame-2] = crc16 >> 8;
|
|
bytes_in[bytes_per_modem_frame-1] = crc16 & 0xff;
|
|
|
|
on_samples += send_modulated_data(freedv, fout, use_complex, n_mod_out, bytes_in);
|
|
|
|
/* if using pipes we don't want the usual buffering to occur */
|
|
if (fout == stdout) fflush(stdout);
|
|
|
|
on_samples += send_postamble(freedv, fout, use_complex, n_mod_out);
|
|
|
|
int samples_delay = 0;
|
|
if (inter_burst_delay_ms) {
|
|
/* user defined inter-burst delay */
|
|
samples_delay = FREEDV_FS_8000*inter_burst_delay_ms/1000;
|
|
}
|
|
else {
|
|
/* just enough silence at the end of burst to allow demod to complete processing */
|
|
samples_delay = 2*freedv_get_n_nom_modem_samples(freedv);
|
|
}
|
|
off_samples += send_silence(fout, shorts_per_sample, samples_delay);
|
|
frames++;
|
|
}
|
|
}
|
|
|
|
/* optional silence at the end of run */
|
|
if (postdelay_ms) {
|
|
int samples_delay = FREEDV_FS_8000*postdelay_ms/1000;
|
|
if (!quiet) fprintf(stderr, "postdelay: %d %d\n", postdelay_ms, samples_delay);
|
|
off_samples += send_silence(fout, shorts_per_sample, samples_delay);
|
|
}
|
|
|
|
/* SNR offset to use in channel simulator to account for on/off time of burst signal */
|
|
float mark_space_ratio = (float)on_samples/(on_samples+off_samples);
|
|
float mark_space_SNR_offset = 10*log10(mark_space_ratio);
|
|
if (!quiet) fprintf(stderr, "mark:space: %3.2f SNR offset: %5.2f\n", mark_space_ratio, mark_space_SNR_offset);
|
|
|
|
freedv_close(freedv);
|
|
fclose(fin);
|
|
fclose(fout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
size_t send_preamble(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out) {
|
|
short mod_out_short[2*n_mod_out];
|
|
int shorts_per_sample = 1;
|
|
int n_preamble = 0;
|
|
|
|
if (use_complex == 0) {
|
|
n_preamble = freedv_rawdatapreambletx(freedv, mod_out_short);
|
|
} else {
|
|
COMP mod_out_comp[n_mod_out];
|
|
n_preamble = freedv_rawdatapreamblecomptx(freedv, mod_out_comp);
|
|
comp_to_short(mod_out_short, mod_out_comp, n_preamble);
|
|
shorts_per_sample = 2;
|
|
}
|
|
assert(n_preamble == freedv_get_n_tx_preamble_modem_samples(freedv));
|
|
assert(n_preamble <= n_mod_out);
|
|
fwrite(mod_out_short, sizeof(short), shorts_per_sample*n_preamble, fout);
|
|
return n_preamble;
|
|
}
|
|
|
|
|
|
size_t send_modulated_data(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out, uint8_t bytes_in[]) {
|
|
short mod_out_short[2*n_mod_out];
|
|
int shorts_per_sample = 1;
|
|
|
|
if (use_complex == 0) {
|
|
freedv_rawdatatx(freedv, mod_out_short, bytes_in);
|
|
} else {
|
|
COMP mod_out_comp[n_mod_out];
|
|
freedv_rawdatacomptx(freedv, mod_out_comp, bytes_in);
|
|
comp_to_short(mod_out_short, mod_out_comp, n_mod_out);
|
|
shorts_per_sample = 2;
|
|
}
|
|
fwrite(mod_out_short, sizeof(short), shorts_per_sample*n_mod_out, fout);
|
|
return n_mod_out;
|
|
}
|
|
|
|
|
|
size_t send_postamble(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out) {
|
|
short mod_out_short[2*n_mod_out];
|
|
int shorts_per_sample = 1;
|
|
int n_postamble = 0;
|
|
|
|
if (use_complex == 0) {
|
|
n_postamble = freedv_rawdatapostambletx(freedv, mod_out_short);
|
|
} else {
|
|
COMP mod_out_comp[n_mod_out];
|
|
n_postamble = freedv_rawdatapostamblecomptx(freedv, mod_out_comp);
|
|
comp_to_short(mod_out_short, mod_out_comp, n_postamble);
|
|
shorts_per_sample = 2;
|
|
}
|
|
assert(n_postamble == freedv_get_n_tx_postamble_modem_samples(freedv));
|
|
assert(n_postamble <= n_mod_out);
|
|
fwrite(mod_out_short, sizeof(short), shorts_per_sample*n_postamble, fout);
|
|
return n_postamble;
|
|
}
|
|
|
|
|
|
size_t send_silence(FILE *fout, size_t shorts_per_sample, size_t samples_delay) {
|
|
size_t n = shorts_per_sample*samples_delay;
|
|
short sil_short[n];
|
|
for(int i=0; i<n; i++) sil_short[i] = 0;
|
|
fwrite(sil_short, sizeof(short), n, fout);
|
|
return samples_delay;
|
|
}
|
|
|
|
|
|
void comp_to_short(short mod_out_short[], COMP mod_out_comp[], int n_mod_out) {
|
|
for(int i=0; i<n_mod_out; i++) {
|
|
mod_out_short[2*i] = (short)(mod_out_comp[i].real);
|
|
mod_out_short[2*i+1] = (short)(mod_out_comp[i].imag);
|
|
}
|
|
}
|
|
|