mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-22 16:08:39 -05:00
2540 lines
92 KiB
C++
2540 lines
92 KiB
C++
/*---------------------------------------------------------------------------*\
|
|
|
|
FILE........: freedv_api.c
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: August 2014
|
|
|
|
Library of API functions that implement FreeDV "modes", useful for
|
|
embedding FreeDV in other programs.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
Copyright (C) 2014 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 <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#ifdef TT
|
|
#if defined(__APPLE__)
|
|
#include <malloc/malloc.h>
|
|
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
|
|
#include <sys/malloc.h>
|
|
#else
|
|
#include <malloc.h>
|
|
#endif /* __APPLE__ */
|
|
#endif
|
|
|
|
#include "fsk.h"
|
|
#include "fmfsk.h"
|
|
#include "codec2/codec2.h"
|
|
#include "codec2_fdmdv.h"
|
|
#include "fdmdv_internal.h"
|
|
#include "codec2/golay23.h"
|
|
#include "codec2/varicode.h"
|
|
#include "libfreedv.h"
|
|
#include "freedv_api_internal.h"
|
|
#include "freedv_vhf_framing.h"
|
|
#include "comp_prim.h"
|
|
#include "freedv_filter.h"
|
|
|
|
#include "codec2_ofdm.h"
|
|
#include "ofdm_internal.h"
|
|
#include "mpdecode_core.h"
|
|
#include "gp_interleaver.h"
|
|
#include "interldpc.h"
|
|
|
|
#define VERSION 12 /* The API version number. The first version
|
|
is 10. Increment if the API changes in a
|
|
way that would require changes by the API
|
|
user. */
|
|
/*
|
|
* Version 10 Initial version August 2, 2015.
|
|
* Version 11 September 2015
|
|
* Added: freedv_zero_total_bit_errors(), freedv_get_sync()
|
|
* Changed all input and output sample rates to 8000 sps. Rates for FREEDV_MODE_700 and 700B were 7500.
|
|
* Version 12 August 2018
|
|
* Added OFDM configuration switch structure
|
|
*/
|
|
|
|
/* experimentally derived fudge factors to normalise power across modes */
|
|
|
|
#define NORM_PWR_COHPSK 1.74
|
|
#define NORM_PWR_FSK 0.193
|
|
#define NORM_PWR_OFDM 1.00
|
|
|
|
namespace FreeDV
|
|
{
|
|
|
|
static struct OFDM_CONFIG *ofdm_config;
|
|
|
|
static int ofdm_bitsperframe;
|
|
static int ofdm_nuwbits;
|
|
static int ofdm_ntxtbits;
|
|
|
|
static const char *statemode[] = {
|
|
"search",
|
|
"trial",
|
|
"synced"
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_open
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Call this first to initialise. Returns NULL if initialisation fails
|
|
(e.g. out of memory or mode not supported).
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
struct freedv *freedv_open(int mode) {
|
|
return freedv_open_advanced(mode, NULL);
|
|
}
|
|
|
|
struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) {
|
|
struct freedv *f;
|
|
int Nc, codec2_mode, nbit, nbyte;
|
|
|
|
if ((mode != FREEDV_MODE_1600) && (mode != FREEDV_MODE_700) &&
|
|
(mode != FREEDV_MODE_700B) && (mode != FREEDV_MODE_2400A) &&
|
|
(mode != FREEDV_MODE_2400B) && (mode != FREEDV_MODE_800XA) &&
|
|
(mode != FREEDV_MODE_700C) && (mode != FREEDV_MODE_700D) )
|
|
return NULL;
|
|
|
|
f = (struct freedv*) malloc(sizeof(struct freedv));
|
|
if (f == NULL)
|
|
return NULL;
|
|
|
|
f->mode = mode;
|
|
f->verbose = 0;
|
|
f->test_frames = f->smooth_symbols = 0;
|
|
f->freedv_put_error_pattern = NULL;
|
|
f->error_pattern_callback_state = NULL;
|
|
f->n_protocol_bits = 0;
|
|
f->frames = 0;
|
|
|
|
/* Init states for this mode, and set up samples in/out -----------------------------------------*/
|
|
|
|
if (mode == FREEDV_MODE_1600) {
|
|
f->snr_squelch_thresh = 2.0;
|
|
f->squelch_en = 1;
|
|
Nc = 16;
|
|
f->tx_sync_bit = 0;
|
|
codec2_mode = CODEC2_MODE_1300;
|
|
f->fdmdv = fdmdv_create(Nc);
|
|
if (f->fdmdv == NULL)
|
|
return NULL;
|
|
golay23_init();
|
|
f->nin = FDMDV_NOM_SAMPLES_PER_FRAME;
|
|
f->n_nom_modem_samples = 2*FDMDV_NOM_SAMPLES_PER_FRAME;
|
|
f->n_nat_modem_samples = f->n_nom_modem_samples;
|
|
f->n_max_modem_samples = FDMDV_NOM_SAMPLES_PER_FRAME+FDMDV_MAX_SAMPLES_PER_FRAME;
|
|
f->modem_sample_rate = FS;
|
|
nbit = fdmdv_bits_per_frame(f->fdmdv);
|
|
f->fdmdv_bits = (int*) malloc(nbit*sizeof(int));
|
|
if (f->fdmdv_bits == NULL)
|
|
return NULL;
|
|
nbit = 2*fdmdv_bits_per_frame(f->fdmdv);
|
|
f->tx_bits = (int*) malloc(nbit*sizeof(int));
|
|
f->rx_bits = (int*) malloc(nbit*sizeof(int));
|
|
if ((f->tx_bits == NULL) || (f->rx_bits == NULL)) {
|
|
if (f->tx_bits != NULL) {
|
|
free(f->tx_bits);
|
|
f->tx_bits = NULL;
|
|
}
|
|
if (f->rx_bits != NULL) {
|
|
free(f->rx_bits);
|
|
f->rx_bits = NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
f->evenframe = 0;
|
|
f->sz_error_pattern = fdmdv_error_pattern_size(f->fdmdv);
|
|
}
|
|
|
|
if ((mode == FREEDV_MODE_700) || (mode == FREEDV_MODE_700B) || (mode == FREEDV_MODE_700C)) {
|
|
f->snr_squelch_thresh = 0.0;
|
|
f->squelch_en = 0;
|
|
switch(mode) {
|
|
case FREEDV_MODE_700:
|
|
codec2_mode = CODEC2_MODE_700;
|
|
break;
|
|
case FREEDV_MODE_700B:
|
|
codec2_mode = CODEC2_MODE_700B;
|
|
break;
|
|
case FREEDV_MODE_700C:
|
|
codec2_mode = CODEC2_MODE_700C;
|
|
break;
|
|
default:
|
|
codec2_mode = CODEC2_MODE_700C;
|
|
fprintf(stderr, "FreeDV::freedv_open_advanced: unknown mode default to FREEDV_MODE_700C");
|
|
}
|
|
|
|
f->cohpsk = cohpsk_create();
|
|
f->nin = COHPSK_NOM_SAMPLES_PER_FRAME;
|
|
f->n_nat_modem_samples = COHPSK_NOM_SAMPLES_PER_FRAME; // native modem samples as used by the modem
|
|
f->n_nom_modem_samples = f->n_nat_modem_samples * FS / COHPSK_FS; // number of samples after native samples are interpolated to 8000 sps
|
|
f->n_max_modem_samples = COHPSK_MAX_SAMPLES_PER_FRAME * FS / COHPSK_FS + 1;
|
|
f->modem_sample_rate = FS; /* note wierd sample rate tamed by interpolator */
|
|
f->clip = 1;
|
|
nbit = COHPSK_BITS_PER_FRAME;
|
|
f->tx_bits = (int*) malloc(nbit*sizeof(int));
|
|
if (f->tx_bits == NULL)
|
|
return NULL;
|
|
f->sz_error_pattern = cohpsk_error_pattern_size();
|
|
}
|
|
|
|
if (mode == FREEDV_MODE_700D) {
|
|
/*
|
|
TODO:
|
|
[ ] how to set up interleaver, prob init time option best, as many arrays depend on it
|
|
[ ] clip option? Haven't tried clipping OFDM waveform yet
|
|
[ ] support for uncoded and coded error patterns
|
|
*/
|
|
|
|
f->snr_squelch_thresh = 0.0;
|
|
f->squelch_en = 0;
|
|
codec2_mode = CODEC2_MODE_700C;
|
|
|
|
if ((ofdm_config = (struct OFDM_CONFIG *) calloc(1, sizeof (struct OFDM_CONFIG))) == NULL) {
|
|
if (f->tx_bits != NULL) {
|
|
free(f->tx_bits);
|
|
f->tx_bits = NULL;
|
|
}
|
|
if (f->rx_bits != NULL) {
|
|
free(f->rx_bits);
|
|
f->rx_bits = NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
f->ofdm = ofdm_create(ofdm_config);
|
|
free(ofdm_config);
|
|
|
|
/* Get a copy of the actual modem config */
|
|
ofdm_config = ofdm_get_config_param();
|
|
|
|
ofdm_bitsperframe = ofdm_get_bits_per_frame();
|
|
ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits;
|
|
ofdm_ntxtbits = ofdm_config->txtbits;
|
|
|
|
f->ldpc = (struct LDPC*) malloc(sizeof(struct LDPC));
|
|
|
|
if (f->ldpc == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
set_up_hra_112_112(f->ldpc, ofdm_config);
|
|
int coded_syms_per_frame = f->ldpc->coded_syms_per_frame;
|
|
|
|
if (adv == NULL) {
|
|
f->interleave_frames = 1;
|
|
} else {
|
|
assert((adv->interleave_frames >= 0) && (adv->interleave_frames <= 16));
|
|
f->interleave_frames = adv->interleave_frames;
|
|
}
|
|
|
|
f->modem_frame_count_tx = f->modem_frame_count_rx = 0;
|
|
|
|
f->codeword_symbols = (COMP*) malloc(sizeof(COMP)*f->interleave_frames*coded_syms_per_frame);
|
|
|
|
if (f->codeword_symbols == NULL) {return NULL;}
|
|
|
|
f->codeword_amps = (float*) malloc(sizeof(float)*f->interleave_frames*coded_syms_per_frame);
|
|
|
|
if (f->codeword_amps == NULL) {free(f->codeword_symbols); return NULL;}
|
|
|
|
for (int i=0; i<f->interleave_frames*coded_syms_per_frame; i++) {
|
|
f->codeword_symbols[i].real = 0.0;
|
|
f->codeword_symbols[i].imag = 0.0;
|
|
f->codeword_amps[i] = 0.0;
|
|
}
|
|
|
|
f->nin = ofdm_get_samples_per_frame();
|
|
f->n_nat_modem_samples = ofdm_get_samples_per_frame();
|
|
f->n_nom_modem_samples = ofdm_get_samples_per_frame();
|
|
f->n_max_modem_samples = ofdm_get_max_samples_per_frame();
|
|
f->modem_sample_rate = ofdm_config->fs;
|
|
f->clip = 0;
|
|
|
|
nbit = f->sz_error_pattern = ofdm_bitsperframe;
|
|
|
|
f->tx_bits = NULL; /* not used for 700D */
|
|
|
|
if (f->interleave_frames > 1) {
|
|
/* only allocate this array for interleaver sizes > 1 to save memory on SM1000 port */
|
|
f->mod_out = (COMP*) malloc(sizeof(COMP)*f->interleave_frames*f->n_nat_modem_samples);
|
|
if (f->mod_out == NULL) {
|
|
if (f->codeword_symbols != NULL) { free(f->codeword_symbols); }
|
|
return NULL;
|
|
}
|
|
|
|
for (int i=0; i<f->interleave_frames*f->n_nat_modem_samples; i++) {
|
|
f->mod_out[i].real = 0.0;
|
|
f->mod_out[i].imag = 0.0;
|
|
}
|
|
}
|
|
|
|
#ifndef __EMBEDDED__
|
|
/* tx BPF on by default, can't see any reason we'd want this off */
|
|
ofdm_set_tx_bpf(f->ofdm, 1);
|
|
#endif
|
|
}
|
|
|
|
if ((mode == FREEDV_MODE_2400A) || (mode == FREEDV_MODE_2400B)) {
|
|
|
|
/* Set up the C2 mode */
|
|
codec2_mode = CODEC2_MODE_1300;
|
|
/* Set the number of protocol bits */
|
|
f->n_protocol_bits = 20;
|
|
f->sz_error_pattern = 0;
|
|
f->ext_vco = 0;
|
|
}
|
|
|
|
if (mode == FREEDV_MODE_2400A) {
|
|
/* Create the framer|deframer */
|
|
f->deframer = fvhff_create_deframer(FREEDV_VHF_FRAME_A,0);
|
|
if(f->deframer == NULL)
|
|
return NULL;
|
|
|
|
f->fsk = fsk_create_hbr(48000,1200,10,4,1200,1200);
|
|
|
|
/* Note: fsk expects tx/rx bits as an array of uint8_ts, not ints */
|
|
f->tx_bits = (int*) malloc(f->fsk->Nbits*sizeof(uint8_t));
|
|
|
|
if(f->fsk == NULL){
|
|
fvhff_destroy_deframer(f->deframer);
|
|
return NULL;
|
|
}
|
|
|
|
f->n_nom_modem_samples = f->fsk->N;
|
|
f->n_max_modem_samples = f->fsk->N + (f->fsk->Ts);
|
|
f->n_nat_modem_samples = f->fsk->N;
|
|
f->nin = fsk_nin(f->fsk);
|
|
f->modem_sample_rate = 48000;
|
|
f->modem_symbol_rate = 1200;
|
|
/* Malloc something to appease freedv_init and freedv_destroy */
|
|
f->codec_bits = (int*) malloc(1);
|
|
}
|
|
|
|
if (mode == FREEDV_MODE_2400B) {
|
|
/* Create the framer|deframer */
|
|
f->deframer = fvhff_create_deframer(FREEDV_VHF_FRAME_A,1);
|
|
if(f->deframer == NULL) {
|
|
if (f->codec_bits != NULL) { free(f->codec_bits); }
|
|
return NULL;
|
|
}
|
|
|
|
f->fmfsk = fmfsk_create(48000,2400);
|
|
|
|
if(f->fmfsk == NULL){
|
|
fvhff_destroy_deframer(f->deframer);
|
|
return NULL;
|
|
}
|
|
/* Note: fsk expects tx/rx bits as an array of uint8_ts, not ints */
|
|
f->tx_bits = (int*) malloc(f->fmfsk->nbit*sizeof(uint8_t));
|
|
|
|
f->n_nom_modem_samples = f->fmfsk->N;
|
|
f->n_max_modem_samples = f->fmfsk->N + (f->fmfsk->Ts);
|
|
f->n_nat_modem_samples = f->fmfsk->N;
|
|
f->nin = fmfsk_nin(f->fmfsk);
|
|
f->modem_sample_rate = 48000;
|
|
/* Malloc something to appease freedv_init and freedv_destroy */
|
|
f->codec_bits = (int*) malloc(1);
|
|
}
|
|
|
|
if (mode == FREEDV_MODE_800XA) {
|
|
/* Create the framer|deframer */
|
|
f->deframer = fvhff_create_deframer(FREEDV_HF_FRAME_B,0);
|
|
if(f->deframer == NULL)
|
|
return NULL;
|
|
|
|
f->fsk = fsk_create_hbr(8000,400,10,4,800,400);
|
|
fsk_set_nsym(f->fsk,32);
|
|
|
|
/* Note: fsk expects tx/rx bits as an array of uint8_ts, not ints */
|
|
f->tx_bits = (int*) malloc(f->fsk->Nbits*sizeof(uint8_t));
|
|
|
|
if(f->fsk == NULL){
|
|
fvhff_destroy_deframer(f->deframer);
|
|
return NULL;
|
|
}
|
|
|
|
f->n_nom_modem_samples = f->fsk->N;
|
|
f->n_max_modem_samples = f->fsk->N + (f->fsk->Ts);
|
|
f->n_nat_modem_samples = f->fsk->N;
|
|
f->nin = fsk_nin(f->fsk);
|
|
f->modem_sample_rate = 8000;
|
|
f->modem_symbol_rate = 400;
|
|
|
|
/* Malloc something to appease freedv_init and freedv_destroy */
|
|
f->codec_bits = (int*) malloc(1);
|
|
|
|
f->n_protocol_bits = 0;
|
|
codec2_mode = CODEC2_MODE_700C;
|
|
fsk_stats_normalise_eye(f->fsk, 0);
|
|
f->sz_error_pattern = 0;
|
|
}
|
|
|
|
/* Init test frame states */
|
|
|
|
f->test_frames_diversity = 1;
|
|
f->test_frame_sync_state = 0;
|
|
f->test_frame_sync_state_upper = 0;
|
|
f->total_bits = 0;
|
|
f->total_bit_errors = 0;
|
|
f->total_bits_coded = 0;
|
|
f->total_bit_errors_coded = 0;
|
|
|
|
/* Init Codec 2 for this FreeDV mode ----------------------------------------------------*/
|
|
|
|
f->codec2 = codec2_create(codec2_mode);
|
|
if (f->codec2 == NULL)
|
|
return NULL;
|
|
|
|
/* work out how many codec 2 frames per mode frame, and number of
|
|
bytes of storage for packed and unpacket bits. TODO: do we really
|
|
need to work in packed bits at all? It's messy, chars would probably
|
|
be OK.... */
|
|
|
|
if ((mode == FREEDV_MODE_1600) || (mode == FREEDV_MODE_2400A) || (mode == FREEDV_MODE_2400B)) {
|
|
f->n_speech_samples = codec2_samples_per_frame(f->codec2);
|
|
f->n_codec_bits = codec2_bits_per_frame(f->codec2);
|
|
nbit = f->n_codec_bits;
|
|
nbyte = (nbit + 7) / 8;
|
|
} else if (mode == FREEDV_MODE_800XA) {
|
|
f->n_speech_samples = 2*codec2_samples_per_frame(f->codec2);
|
|
f->n_codec_bits = codec2_bits_per_frame(f->codec2);
|
|
nbit = f->n_codec_bits;
|
|
nbyte = (nbit + 7) / 8;
|
|
nbyte = nbyte*2;
|
|
nbit = 8*nbyte;
|
|
f->n_codec_bits = nbit;
|
|
} else if ((mode == FREEDV_MODE_700) || (mode == FREEDV_MODE_700B) || (mode == FREEDV_MODE_700C)) {
|
|
f->n_speech_samples = 2*codec2_samples_per_frame(f->codec2);
|
|
f->n_codec_bits = 2*codec2_bits_per_frame(f->codec2);
|
|
nbit = f->n_codec_bits;
|
|
nbyte = 2*((codec2_bits_per_frame(f->codec2) + 7) / 8);
|
|
} else /* mode == FREEDV_MODE_700D */ {
|
|
|
|
/* should be exactly an integer number ofCodec 2 frames in a OFDM modem frame */
|
|
|
|
assert((f->ldpc->data_bits_per_frame % codec2_bits_per_frame(f->codec2)) == 0);
|
|
|
|
int Ncodec2frames = f->ldpc->data_bits_per_frame/codec2_bits_per_frame(f->codec2);
|
|
|
|
f->n_speech_samples = Ncodec2frames*codec2_samples_per_frame(f->codec2);
|
|
f->n_codec_bits = f->interleave_frames*Ncodec2frames*codec2_bits_per_frame(f->codec2);
|
|
nbit = codec2_bits_per_frame(f->codec2);
|
|
nbyte = (nbit + 7) / 8;
|
|
nbyte = nbyte*Ncodec2frames*f->interleave_frames;
|
|
f->nbyte_packed_codec_bits = nbyte;
|
|
|
|
//fprintf(stderr, "Ncodec2frames: %d n_speech_samples: %d n_codec_bits: %d nbit: %d nbyte: %d\n",
|
|
// Ncodec2frames, f->n_speech_samples, f->n_codec_bits, nbit, nbyte);
|
|
|
|
f->packed_codec_bits_tx = (unsigned char*) malloc(nbyte*sizeof(char));
|
|
if (f->packed_codec_bits_tx == NULL) return(NULL);
|
|
f->codec_bits = NULL;
|
|
}
|
|
|
|
f->packed_codec_bits = (unsigned char*) malloc(nbyte*sizeof(char));
|
|
if (f->packed_codec_bits == NULL) return(NULL);
|
|
|
|
if (mode == FREEDV_MODE_1600)
|
|
f->codec_bits = (int*) malloc(nbit*sizeof(int));
|
|
|
|
if ((mode == FREEDV_MODE_700) || (mode == FREEDV_MODE_700B) || (mode == FREEDV_MODE_700C))
|
|
f->codec_bits = (int*) malloc(COHPSK_BITS_PER_FRAME*sizeof(int));
|
|
|
|
/* Note: VHF Framer/deframer goes directly from packed codec/vc/proto bits to filled frame */
|
|
|
|
if (f->packed_codec_bits == NULL) {
|
|
if (f->codec_bits != NULL) {free(f->codec_bits); f->codec_bits = NULL; }
|
|
return NULL;
|
|
}
|
|
|
|
/* Sample rate conversion for modes using COHPSK */
|
|
|
|
if ((mode == FREEDV_MODE_700) || (mode == FREEDV_MODE_700B) || (mode == FREEDV_MODE_700C) ) {
|
|
f->ptFilter7500to8000 = (struct quisk_cfFilter *) malloc(sizeof(struct quisk_cfFilter));
|
|
f->ptFilter8000to7500 = (struct quisk_cfFilter *) malloc(sizeof(struct quisk_cfFilter));
|
|
quisk_filt_cfInit(f->ptFilter8000to7500, quiskFilt120t480, sizeof(quiskFilt120t480)/sizeof(float));
|
|
quisk_filt_cfInit(f->ptFilter7500to8000, quiskFilt120t480, sizeof(quiskFilt120t480)/sizeof(float));
|
|
} else {
|
|
f->ptFilter7500to8000 = NULL;
|
|
f->ptFilter8000to7500 = NULL;
|
|
}
|
|
|
|
/* Varicode low bit rate text states */
|
|
|
|
varicode_decode_init(&f->varicode_dec_states, 1);
|
|
f->nvaricode_bits = 0;
|
|
f->varicode_bit_index = 0;
|
|
f->freedv_get_next_tx_char = NULL;
|
|
f->freedv_put_next_rx_char = NULL;
|
|
f->freedv_put_next_proto = NULL;
|
|
f->freedv_get_next_proto = NULL;
|
|
f->total_bit_errors = 0;
|
|
|
|
return f;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_close
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Frees up memory.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_close(struct freedv *freedv) {
|
|
assert(freedv != NULL);
|
|
|
|
free(freedv->packed_codec_bits);
|
|
free(freedv->codec_bits);
|
|
free(freedv->tx_bits);
|
|
if (freedv->mode == FREEDV_MODE_1600)
|
|
fdmdv_destroy(freedv->fdmdv);
|
|
if ((freedv->mode == FREEDV_MODE_700) || (freedv->mode == FREEDV_MODE_700B) || (freedv->mode == FREEDV_MODE_700C))
|
|
cohpsk_destroy(freedv->cohpsk);
|
|
if (freedv->mode == FREEDV_MODE_700D) {
|
|
free(freedv->packed_codec_bits_tx);
|
|
if (freedv->interleave_frames > 1)
|
|
free(freedv->mod_out);
|
|
free(freedv->codeword_symbols);
|
|
free(freedv->codeword_amps);
|
|
free(freedv->ldpc);
|
|
ofdm_destroy(freedv->ofdm);
|
|
}
|
|
if ((freedv->mode == FREEDV_MODE_2400A) || (freedv->mode == FREEDV_MODE_800XA)){
|
|
fsk_destroy(freedv->fsk);
|
|
fvhff_destroy_deframer(freedv->deframer);
|
|
}
|
|
|
|
if (freedv->mode == FREEDV_MODE_2400B){
|
|
fmfsk_destroy(freedv->fmfsk);
|
|
fvhff_destroy_deframer(freedv->deframer);
|
|
}
|
|
|
|
codec2_destroy(freedv->codec2);
|
|
if (freedv->ptFilter8000to7500) {
|
|
quisk_filt_destroy(freedv->ptFilter8000to7500);
|
|
free(freedv->ptFilter8000to7500);
|
|
freedv->ptFilter8000to7500 = NULL;
|
|
}
|
|
if (freedv->ptFilter7500to8000) {
|
|
quisk_filt_destroy(freedv->ptFilter7500to8000);
|
|
free(freedv->ptFilter7500to8000);
|
|
freedv->ptFilter7500to8000 = NULL;
|
|
}
|
|
free(freedv);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_tx
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Takes a frame of input speech samples, encodes and modulates them to
|
|
produce a frame of modem samples that can be sent to the
|
|
transmitter. See freedv_tx.c for an example.
|
|
|
|
speech_in[] is sampled at 8000 Hz, and the user must supply a block
|
|
of exactly freedv_get_n_speech_samples(). The speech_in[] level
|
|
should be such that the peak speech level is between +/- 16384 and
|
|
+/- 32767.
|
|
|
|
The FDM modem signal mod_out[] is sampled at 8000 Hz and is
|
|
freedv_get_n_nom_modem_samples() long. mod_out[] will be scaled
|
|
such that the peak level is just less than +/-32767.
|
|
|
|
The complex-valued output can directly drive an I/Q modulator to
|
|
produce a single sideband signal. To generate the other sideband,
|
|
take the complex conjugate of mod_out[].
|
|
|
|
The FreeDV 1600 modem has a high crest factor (around 12dB), however
|
|
the energy and duration of the peaks is small. FreeDV 1600 is
|
|
usually operated at a "backoff" of 8dB. Adjust the power amplifier
|
|
drive so that the average power is 8dB less than the peak power of
|
|
the PA. For example, on a radio rated at 100W PEP for SSB, the
|
|
average FreeDV power is typically 20W.
|
|
|
|
The FreeDV 700 modem has a crest factor of about 8dB (with
|
|
f->clip=1, the default), so if your PA can handle it, it can be
|
|
driven harder than FreeDV 1600. Caution - some PAs cannot handle a
|
|
high continuous power. A conservative level is 20W average for a
|
|
100W PEP rated PA.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/* real-valued short sample output, useful for going straight to DAC */
|
|
|
|
/* TX routines for 2400 FSK modes, after codec2 encoding */
|
|
static void freedv_tx_fsk_voice(struct freedv *f, short mod_out[]) {
|
|
int i;
|
|
float *tx_float; /* To hold on to modulated samps from fsk/fmfsk */
|
|
uint8_t vc_bits[2]; /* Varicode bits for 2400 framing */
|
|
uint8_t proto_bits[3]; /* Prococol bits for 2400 framing */
|
|
|
|
/* Frame for 2400A/B */
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_2400B){
|
|
/* Get varicode bits for TX and possibly ask for a new char */
|
|
/* 2 bits per 2400A/B frame, so this has to be done twice */
|
|
for(i=0;i<2;i++){
|
|
if (f->nvaricode_bits) {
|
|
vc_bits[i] = f->tx_varicode_bits[f->varicode_bit_index++];
|
|
f->nvaricode_bits--;
|
|
}
|
|
|
|
if (f->nvaricode_bits == 0) {
|
|
/* get new char and encode */
|
|
char s[2];
|
|
if (f->freedv_get_next_tx_char != NULL) {
|
|
s[0] = (*f->freedv_get_next_tx_char)(f->callback_state);
|
|
f->nvaricode_bits = varicode_encode(f->tx_varicode_bits, s, VARICODE_MAX_BITS, 1, 1);
|
|
f->varicode_bit_index = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the API user hasn't set up message callbacks, don't bother with varicode bits */
|
|
if(f->freedv_get_next_proto != NULL){
|
|
(*f->freedv_get_next_proto)(f->proto_callback_state,(char*)proto_bits);
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),proto_bits,vc_bits);
|
|
}else if(f->freedv_get_next_tx_char != NULL){
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,vc_bits);
|
|
}else {
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,NULL);
|
|
}
|
|
/* Frame for 800XA */
|
|
}else if(f->mode == FREEDV_MODE_800XA){
|
|
fvhff_frame_bits(FREEDV_HF_FRAME_B,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,NULL);
|
|
}
|
|
|
|
/* Allocate floating point buffer for FSK mod */
|
|
tx_float = (float*) malloc(sizeof(float)*f->n_nom_modem_samples);
|
|
|
|
/* do 4fsk mod */
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_800XA){
|
|
if (f->ext_vco) {
|
|
fsk_mod_ext_vco(f->fsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = (short)tx_float[i];
|
|
}
|
|
}
|
|
else {
|
|
fsk_mod(f->fsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = (short)(tx_float[i]*FSK_SCALE*NORM_PWR_FSK);
|
|
}
|
|
}
|
|
/* do me-fsk mod */
|
|
}else if(f->mode == FREEDV_MODE_2400B){
|
|
fmfsk_mod(f->fmfsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = (short)(tx_float[i]*FMFSK_SCALE);
|
|
}
|
|
}
|
|
|
|
free(tx_float);
|
|
}
|
|
|
|
/* TX routines for 2400 FSK modes, after codec2 encoding */
|
|
static void freedv_comptx_fsk_voice(struct freedv *f, COMP mod_out[]) {
|
|
int i;
|
|
float *tx_float; /* To hold on to modulated samps from fsk/fmfsk */
|
|
uint8_t vc_bits[2]; /* Varicode bits for 2400 framing */
|
|
uint8_t proto_bits[3]; /* Prococol bits for 2400 framing */
|
|
|
|
/* Frame for 2400A/B */
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_2400B){
|
|
/* Get varicode bits for TX and possibly ask for a new char */
|
|
/* 2 bits per 2400A/B frame, so this has to be done twice */
|
|
for(i=0;i<2;i++){
|
|
if (f->nvaricode_bits) {
|
|
vc_bits[i] = f->tx_varicode_bits[f->varicode_bit_index++];
|
|
f->nvaricode_bits--;
|
|
}
|
|
|
|
if (f->nvaricode_bits == 0) {
|
|
/* get new char and encode */
|
|
char s[2];
|
|
if (f->freedv_get_next_tx_char != NULL) {
|
|
s[0] = (*f->freedv_get_next_tx_char)(f->callback_state);
|
|
f->nvaricode_bits = varicode_encode(f->tx_varicode_bits, s, VARICODE_MAX_BITS, 1, 1);
|
|
f->varicode_bit_index = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the API user hasn't set up message callbacks, don't bother with varicode bits */
|
|
if(f->freedv_get_next_proto != NULL){
|
|
(*f->freedv_get_next_proto)(f->proto_callback_state,(char*)proto_bits);
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),proto_bits,vc_bits);
|
|
}else if(f->freedv_get_next_tx_char != NULL){
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,vc_bits);
|
|
}else {
|
|
fvhff_frame_bits(FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,NULL);
|
|
}
|
|
/* Frame for 800XA */
|
|
}else if(f->mode == FREEDV_MODE_800XA){
|
|
fvhff_frame_bits(FREEDV_HF_FRAME_B,(uint8_t*)(f->tx_bits),(uint8_t*)(f->packed_codec_bits),NULL,NULL);
|
|
}
|
|
|
|
/* Allocate floating point buffer for FSK mod */
|
|
tx_float = (float*) malloc(sizeof(float)*f->n_nom_modem_samples);
|
|
|
|
/* do 4fsk mod */
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_800XA){
|
|
fsk_mod_c(f->fsk,mod_out,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = fcmult(NORM_PWR_FSK,mod_out[i]);
|
|
}
|
|
/* do me-fsk mod */
|
|
}else if(f->mode == FREEDV_MODE_2400B){
|
|
fmfsk_mod(f->fmfsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i].real = (tx_float[i]);
|
|
}
|
|
}
|
|
|
|
free(tx_float);
|
|
}
|
|
|
|
|
|
/* TX routines for 2400 FSK modes, data channel */
|
|
static void freedv_tx_fsk_data(struct freedv *f, short mod_out[]) {
|
|
int i;
|
|
float *tx_float; /* To hold on to modulated samps from fsk/fmfsk */
|
|
|
|
if (f->mode != FREEDV_MODE_800XA)
|
|
fvhff_frame_data_bits(f->deframer, FREEDV_VHF_FRAME_A,(uint8_t*)(f->tx_bits));
|
|
else
|
|
fvhff_frame_data_bits(f->deframer, FREEDV_HF_FRAME_B,(uint8_t*)(f->tx_bits));
|
|
|
|
/* Allocate floating point buffer for FSK mod */
|
|
tx_float = (float*) malloc(sizeof(float)*f->n_nom_modem_samples);
|
|
|
|
/* do 4fsk mod */
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_800XA){
|
|
fsk_mod(f->fsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = (short)(tx_float[i]*FSK_SCALE);
|
|
}
|
|
/* do me-fsk mod */
|
|
}else if(f->mode == FREEDV_MODE_2400B){
|
|
fmfsk_mod(f->fmfsk,tx_float,(uint8_t*)(f->tx_bits));
|
|
/* Convert float samps to short */
|
|
for(i=0; i<f->n_nom_modem_samples; i++){
|
|
mod_out[i] = (short)(tx_float[i]*FMFSK_SCALE);
|
|
}
|
|
}
|
|
|
|
free(tx_float);
|
|
}
|
|
|
|
void freedv_tx(struct freedv *f, short mod_out[], short speech_in[]) {
|
|
assert(f != NULL);
|
|
COMP *tx_fdm = new COMP[f->n_nom_modem_samples];
|
|
int i;
|
|
assert((f->mode == FREEDV_MODE_1600) || (f->mode == FREEDV_MODE_700) ||
|
|
(f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C) ||
|
|
(f->mode == FREEDV_MODE_700D) ||
|
|
(f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) ||
|
|
(f->mode == FREEDV_MODE_800XA));
|
|
|
|
/* FSK and MEFSK/FMFSK modems work only on real samples. It's simpler to just
|
|
* stick them in the real sample tx/rx functions than to add a comp->real converter
|
|
* to comptx */
|
|
|
|
if ((f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA)){
|
|
/* 800XA has two codec frames per modem frame */
|
|
if(f->mode == FREEDV_MODE_800XA){
|
|
codec2_encode(f->codec2, &f->packed_codec_bits[0], &speech_in[ 0]);
|
|
codec2_encode(f->codec2, &f->packed_codec_bits[4], &speech_in[320]);
|
|
}else{
|
|
codec2_encode(f->codec2, f->packed_codec_bits, speech_in);
|
|
}
|
|
freedv_tx_fsk_voice(f, mod_out);
|
|
} else{
|
|
freedv_comptx(f, tx_fdm, speech_in);
|
|
for(i=0; i<f->n_nom_modem_samples; i++)
|
|
mod_out[i] = tx_fdm[i].real;
|
|
}
|
|
|
|
delete[] tx_fdm;
|
|
}
|
|
|
|
/* complex valued output, useful for suitable for single sided freq shifting */
|
|
|
|
static void freedv_comptx_fdmdv_1600(struct freedv *f, COMP mod_out[]) {
|
|
int bit, byte, i, j;
|
|
int bits_per_codec_frame, bits_per_modem_frame;
|
|
int data, codeword1, data_flag_index;
|
|
COMP *tx_fdm = new COMP[f->n_nat_modem_samples];
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bits_per_modem_frame = fdmdv_bits_per_frame(f->fdmdv);
|
|
|
|
/* unpack bits, MSB first */
|
|
|
|
bit = 7; byte = 0;
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
f->codec_bits[i] = (f->packed_codec_bits[byte] >> bit) & 0x1;
|
|
bit--;
|
|
if (bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
|
|
// spare bit in frame that codec defines. Use this 1
|
|
// bit/frame to send txt messages
|
|
|
|
data_flag_index = codec2_get_spare_bit_index(f->codec2);
|
|
|
|
if (f->nvaricode_bits) {
|
|
f->codec_bits[data_flag_index] = f->tx_varicode_bits[f->varicode_bit_index++];
|
|
f->nvaricode_bits--;
|
|
}
|
|
|
|
if (f->nvaricode_bits == 0) {
|
|
/* get new char and encode */
|
|
char s[2];
|
|
if (f->freedv_get_next_tx_char != NULL) {
|
|
s[0] = (*f->freedv_get_next_tx_char)(f->callback_state);
|
|
f->nvaricode_bits = varicode_encode(f->tx_varicode_bits, s, VARICODE_MAX_BITS, 1, 1);
|
|
f->varicode_bit_index = 0;
|
|
}
|
|
}
|
|
|
|
/* Protect first 12 out of first 16 excitation bits with (23,12) Golay Code:
|
|
|
|
0,1,2,3: v[0]..v[1]
|
|
4,5,6,7: MSB of pitch
|
|
11,12,13,14: MSB of energy
|
|
|
|
*/
|
|
|
|
data = 0;
|
|
for(i=0; i<8; i++) {
|
|
data <<= 1;
|
|
data |= f->codec_bits[i];
|
|
}
|
|
for(i=11; i<15; i++) {
|
|
data <<= 1;
|
|
data |= f->codec_bits[i];
|
|
}
|
|
codeword1 = golay23_encode(data);
|
|
|
|
/* now pack output frame with parity bits at end to make them
|
|
as far apart as possible from the data they protect. Parity
|
|
bits are LSB of the Golay codeword */
|
|
|
|
for(i=0; i<bits_per_codec_frame; i++)
|
|
f->tx_bits[i] = f->codec_bits[i];
|
|
for(j=0; i<bits_per_codec_frame+11; i++,j++) {
|
|
f->tx_bits[i] = (codeword1 >> (10-j)) & 0x1;
|
|
}
|
|
f->tx_bits[i] = 0; /* spare bit */
|
|
|
|
/* optionally overwrite with test frames */
|
|
|
|
if (f->test_frames) {
|
|
fdmdv_get_test_bits(f->fdmdv, f->tx_bits);
|
|
fdmdv_get_test_bits(f->fdmdv, &f->tx_bits[bits_per_modem_frame]);
|
|
//fprintf(stderr, "test frames on tx\n");
|
|
}
|
|
|
|
/* modulate even and odd frames */
|
|
|
|
fdmdv_mod(f->fdmdv, tx_fdm, f->tx_bits, &f->tx_sync_bit);
|
|
assert(f->tx_sync_bit == 1);
|
|
|
|
fdmdv_mod(f->fdmdv, &tx_fdm[FDMDV_NOM_SAMPLES_PER_FRAME], &f->tx_bits[bits_per_modem_frame], &f->tx_sync_bit);
|
|
assert(f->tx_sync_bit == 0);
|
|
|
|
assert(2*FDMDV_NOM_SAMPLES_PER_FRAME == f->n_nom_modem_samples);
|
|
|
|
for(i=0; i<f->n_nom_modem_samples; i++)
|
|
mod_out[i] = fcmult(FDMDV_SCALE, tx_fdm[i]);
|
|
|
|
delete[] tx_fdm;
|
|
}
|
|
|
|
static void freedv_comptx_700(struct freedv *f, COMP mod_out[]) {
|
|
int bit, byte, i, j, k;
|
|
int bits_per_codec_frame, bits_per_modem_frame;
|
|
int data_flag_index, nspare;
|
|
COMP *tx_fdm = new COMP[f->n_nat_modem_samples];
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bits_per_modem_frame = COHPSK_BITS_PER_FRAME;
|
|
|
|
byte = 0;
|
|
for (j=0; j<bits_per_modem_frame; j+=bits_per_codec_frame) {
|
|
|
|
/* unpack bits, MSB first */
|
|
|
|
bit = 7;
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
f->codec_bits[j+i] = (f->packed_codec_bits[byte] >> bit) & 0x1;
|
|
bit--;
|
|
if (bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
if (bit != 7)
|
|
byte++;
|
|
|
|
// spare bits in frame that codec defines. Use these spare
|
|
// bits/frame to send txt messages
|
|
|
|
switch(f->mode) {
|
|
case FREEDV_MODE_700:
|
|
nspare = 2;
|
|
break;
|
|
case FREEDV_MODE_700B:
|
|
nspare = 1; // Just one spare bit for FREEDV_MODE_700B
|
|
break;
|
|
case FREEDV_MODE_700C:
|
|
nspare = 0; // and no spare bits for 700C atm
|
|
break;
|
|
default:
|
|
nspare = 0;
|
|
fprintf(stderr, "FreeDV::freedv_comptx_700: unknown mode default to nspare = 0");
|
|
}
|
|
|
|
data_flag_index = codec2_get_spare_bit_index(f->codec2);
|
|
|
|
for(k=0; k<nspare; k++) {
|
|
if (f->nvaricode_bits) {
|
|
f->codec_bits[j+data_flag_index+k] = f->tx_varicode_bits[f->varicode_bit_index++];
|
|
//fprintf(stderr, "%d %d\n", j+data_flag_index+k, f->codec_bits[j+data_flag_index+k]);
|
|
f->nvaricode_bits--;
|
|
}
|
|
if (f->nvaricode_bits == 0) {
|
|
/* get new char and encode */
|
|
char s[2];
|
|
if (f->freedv_get_next_tx_char != NULL) {
|
|
s[0] = (*f->freedv_get_next_tx_char)(f->callback_state);
|
|
f->nvaricode_bits = varicode_encode(f->tx_varicode_bits, s, VARICODE_MAX_BITS, 1, 1);
|
|
f->varicode_bit_index = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* optionally ovwerwrite the codec bits with test frames */
|
|
|
|
if (f->test_frames) {
|
|
cohpsk_get_test_bits(f->cohpsk, f->codec_bits);
|
|
}
|
|
|
|
/* cohpsk modulator */
|
|
|
|
cohpsk_mod(f->cohpsk, tx_fdm, f->codec_bits, COHPSK_BITS_PER_FRAME);
|
|
if (f->clip)
|
|
cohpsk_clip(tx_fdm, COHPSK_CLIP, COHPSK_NOM_SAMPLES_PER_FRAME);
|
|
for(i=0; i<f->n_nat_modem_samples; i++)
|
|
mod_out[i] = fcmult(FDMDV_SCALE*NORM_PWR_COHPSK, tx_fdm[i]);
|
|
i = quisk_cfInterpDecim((std::complex<float> *)mod_out, f->n_nat_modem_samples, f->ptFilter7500to8000, 16, 15);
|
|
//assert(i == f->n_nom_modem_samples);
|
|
// Caution: assert fails if f->n_nat_modem_samples * 16.0 / 15.0 is not an integer
|
|
|
|
delete[] tx_fdm;
|
|
|
|
}
|
|
|
|
/*
|
|
Ok so when interleaved, we take the interleaver length of input samples,
|
|
and output that many modem samples, e.g. for interleaver of length 4:
|
|
|
|
record input speech 1234
|
|
freedv tx |
|
|
play modem sig 1234
|
|
record modem sig 1234
|
|
freedv_rx |
|
|
play output speech 1234
|
|
time axis --------->123456789012---->
|
|
|
|
So a sample of input speech at time 1 is ouput at time 9. We assume
|
|
the freedv_tx and freedv_rx and propogation time over channel (from
|
|
when a modem signal is played at the HF tx to when it is recorded at
|
|
the HF rx) is zero.
|
|
|
|
The freedv tx interface ouputs n_nom_modem_samples, which a single
|
|
OFDM modem frame, 112 payload bits or 4 speech codec frames. So
|
|
this function must always have 1280 speech samples as input, and
|
|
1280 modem samples as output, regradless of interleaver_frames. For
|
|
interleaver_frames > 1, we need to buffer samples.
|
|
*/
|
|
|
|
static void freedv_comptx_700d(struct freedv *f, COMP mod_out[]) {
|
|
int bit, byte, i, j, k;
|
|
int nspare;
|
|
|
|
int data_bits_per_frame = f->ldpc->data_bits_per_frame;
|
|
int bits_per_interleaved_frame = f->interleave_frames*data_bits_per_frame;
|
|
uint8_t *tx_bits = new uint8_t[bits_per_interleaved_frame];
|
|
int bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
|
|
byte = 0;
|
|
for (j=0; j<bits_per_interleaved_frame; j+=bits_per_codec_frame) {
|
|
|
|
/* unpack bits, MSB first */
|
|
|
|
bit = 7;
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
tx_bits[j+i] = (f->packed_codec_bits_tx[byte] >> bit) & 0x1;
|
|
bit--;
|
|
if (bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
if (bit != 7)
|
|
byte++;
|
|
}
|
|
|
|
assert(byte <= f->nbyte_packed_codec_bits);
|
|
|
|
// Generate Varicode txt bits. Txt bits in OFDM frame come just
|
|
// after Unique Word (UW). Txt bits aren't protected by FEC, and need to be
|
|
// added to each frame after interleaver as done it's thing
|
|
|
|
nspare = ofdm_ntxtbits*f->interleave_frames;
|
|
uint8_t *txt_bits = new uint8_t[nspare];
|
|
|
|
for(k=0; k<nspare; k++) {
|
|
if (f->nvaricode_bits == 0) {
|
|
/* get new char and encode */
|
|
char s[2];
|
|
if (f->freedv_get_next_tx_char != NULL) {
|
|
s[0] = (*f->freedv_get_next_tx_char)(f->callback_state);
|
|
f->nvaricode_bits = varicode_encode(f->tx_varicode_bits, s, VARICODE_MAX_BITS, 1, 1);
|
|
f->varicode_bit_index = 0;
|
|
}
|
|
}
|
|
if (f->nvaricode_bits) {
|
|
txt_bits[k] = f->tx_varicode_bits[f->varicode_bit_index++];
|
|
f->nvaricode_bits--;
|
|
}
|
|
}
|
|
|
|
/* optionally replace codec payload bits with test frames known to rx */
|
|
|
|
if (f->test_frames) {
|
|
uint8_t *payload_data_bits = new uint8_t[data_bits_per_frame];
|
|
ofdm_generate_payload_data_bits(payload_data_bits, data_bits_per_frame);
|
|
|
|
for (j=0; j<f->interleave_frames; j++) {
|
|
for(i=0; i<data_bits_per_frame; i++) {
|
|
tx_bits[j*data_bits_per_frame + i] = payload_data_bits[i];
|
|
}
|
|
}
|
|
|
|
delete[] payload_data_bits;
|
|
}
|
|
|
|
/* OK now ready to LDPC encode, interleave, and OFDM modulate */
|
|
|
|
std::complex<float> *tx_sams = new std::complex<float>[f->interleave_frames*f->n_nat_modem_samples];
|
|
COMP asam;
|
|
|
|
ofdm_ldpc_interleave_tx(f->ofdm, f->ldpc, tx_sams, tx_bits, txt_bits, f->interleave_frames, ofdm_config);
|
|
|
|
for(i=0; i<f->interleave_frames*f->n_nat_modem_samples; i++) {
|
|
asam.real = tx_sams[i].real();
|
|
asam.imag = tx_sams[i].imag();
|
|
mod_out[i] = fcmult(OFDM_AMP_SCALE*NORM_PWR_OFDM, asam);
|
|
}
|
|
|
|
if (f->clip) {
|
|
//fprintf(stderr, "clip ");
|
|
cohpsk_clip(mod_out, OFDM_CLIP, f->interleave_frames*f->n_nat_modem_samples);
|
|
}
|
|
|
|
delete[] tx_sams;
|
|
delete[] txt_bits;
|
|
delete[] tx_bits;
|
|
}
|
|
|
|
|
|
|
|
void freedv_comptx(struct freedv *f, COMP mod_out[], short speech_in[]) {
|
|
assert(f != NULL);
|
|
|
|
assert((f->mode == FREEDV_MODE_1600) || (f->mode == FREEDV_MODE_700) ||
|
|
(f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C) ||
|
|
(f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) ||
|
|
(f->mode == FREEDV_MODE_700D));
|
|
|
|
if (f->mode == FREEDV_MODE_1600) {
|
|
codec2_encode(f->codec2, f->packed_codec_bits, speech_in);
|
|
freedv_comptx_fdmdv_1600(f, mod_out);
|
|
}
|
|
|
|
|
|
int bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
int bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
int i,j;
|
|
|
|
/* all these modes need to pack a bunch of codec frames into one modem frame */
|
|
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
int codec_frames = f->n_codec_bits / bits_per_codec_frame;
|
|
|
|
for (j=0; j<codec_frames; j++) {
|
|
codec2_encode(f->codec2, f->packed_codec_bits + j * bytes_per_codec_frame, speech_in);
|
|
speech_in += codec2_samples_per_frame(f->codec2);
|
|
}
|
|
freedv_comptx_700(f, mod_out);
|
|
}
|
|
|
|
/* special treatment due to interleaver */
|
|
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
int data_bits_per_frame = f->ldpc->data_bits_per_frame;
|
|
int codec_frames = data_bits_per_frame / bits_per_codec_frame;
|
|
|
|
//fprintf(stderr, "modem_frame_count_tx: %d dec_frames: %d bytes offset: %d\n",
|
|
// f->modem_frame_count_tx, codec_frames, (f->modem_frame_count_tx*codec_frames)*bytes_per_codec_frame);
|
|
|
|
/* buffer up bits until we get enough encoded bits for interleaver */
|
|
|
|
for (j=0; j<codec_frames; j++) {
|
|
codec2_encode(f->codec2, f->packed_codec_bits_tx + (f->modem_frame_count_tx*codec_frames+j)*bytes_per_codec_frame, speech_in);
|
|
speech_in += codec2_samples_per_frame(f->codec2);
|
|
}
|
|
|
|
|
|
/* Only use extra local buffer if needed for interleave > 1 */
|
|
if (f->interleave_frames == 1) {
|
|
freedv_comptx_700d(f, mod_out);
|
|
} else {
|
|
/* call modulate function when we have enough frames to run interleaver */
|
|
assert((f->modem_frame_count_tx >= 0) &&
|
|
(f->modem_frame_count_tx < f->interleave_frames));
|
|
f->modem_frame_count_tx++;
|
|
if (f->modem_frame_count_tx == f->interleave_frames) {
|
|
freedv_comptx_700d(f, f->mod_out);
|
|
//fprintf(stderr, " calling freedv_comptx_700d()\n");
|
|
f->modem_frame_count_tx = 0;
|
|
}
|
|
/* output n_nom_modem_samples at a time from modulated buffer */
|
|
for(i=0; i<f->n_nat_modem_samples; i++) {
|
|
mod_out[i] =
|
|
f->mod_out[f->modem_frame_count_tx * f->n_nat_modem_samples+i];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* 2400 A and B are handled by the real-mode TX */
|
|
if((f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B)){
|
|
codec2_encode(f->codec2, f->packed_codec_bits, speech_in);
|
|
freedv_comptx_fsk_voice(f,mod_out);
|
|
}
|
|
}
|
|
|
|
void freedv_codectx(struct freedv *f, short mod_out[], unsigned char *packed_codec_bits) {
|
|
assert(f != NULL);
|
|
COMP *tx_fdm = new COMP[f->n_nom_modem_samples];
|
|
int bits_per_codec_frame;
|
|
int bytes_per_codec_frame;
|
|
int codec_frames;
|
|
int i;
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
codec_frames = f->n_codec_bits / bits_per_codec_frame;
|
|
|
|
memcpy(f->packed_codec_bits, packed_codec_bits, bytes_per_codec_frame * codec_frames);
|
|
|
|
switch(f->mode) {
|
|
case FREEDV_MODE_1600:
|
|
freedv_comptx_fdmdv_1600(f, tx_fdm);
|
|
break;
|
|
case FREEDV_MODE_700:
|
|
case FREEDV_MODE_700B:
|
|
case FREEDV_MODE_700C:
|
|
freedv_comptx_700(f, tx_fdm);
|
|
break;
|
|
case FREEDV_MODE_700D: {
|
|
/* special treatment due to interleaver */
|
|
int data_bits_per_frame = f->ldpc->data_bits_per_frame;
|
|
int codec_frames = data_bits_per_frame / bits_per_codec_frame;
|
|
int j;
|
|
|
|
/* buffer up bits until we get enough encoded bits for interleaver */
|
|
|
|
for (j=0; j<codec_frames; j++) {
|
|
memcpy(f->packed_codec_bits_tx + (f->modem_frame_count_tx*codec_frames+j)*bytes_per_codec_frame, packed_codec_bits, bytes_per_codec_frame);
|
|
packed_codec_bits += bytes_per_codec_frame;
|
|
}
|
|
|
|
/* call modulate function when we have enough frames to run interleaver */
|
|
|
|
assert((f->modem_frame_count_tx >= 0) && (f->modem_frame_count_tx < f->interleave_frames));
|
|
f->modem_frame_count_tx++;
|
|
if (f->modem_frame_count_tx == f->interleave_frames) {
|
|
freedv_comptx_700d(f, f->mod_out);
|
|
f->modem_frame_count_tx = 0;
|
|
}
|
|
|
|
/* output n_nom_modem_samples at a time from modulated buffer */
|
|
for(i=0; i<f->n_nat_modem_samples; i++) {
|
|
mod_out[i] = f->mod_out[f->modem_frame_count_tx*f->n_nat_modem_samples+i].real;
|
|
}
|
|
|
|
return; /* output is already real */
|
|
}
|
|
case FREEDV_MODE_2400A:
|
|
case FREEDV_MODE_2400B:
|
|
case FREEDV_MODE_800XA:
|
|
freedv_tx_fsk_voice(f, mod_out);
|
|
return; /* output is already real */
|
|
}
|
|
/* convert complex to real */
|
|
for(i=0; i<f->n_nom_modem_samples; i++)
|
|
mod_out[i] = tx_fdm[i].real;
|
|
|
|
delete[] tx_fdm;
|
|
}
|
|
|
|
void freedv_datatx (struct freedv *f, short mod_out[]){
|
|
assert(f != NULL);
|
|
if (f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_2400B || f->mode == FREEDV_MODE_800XA) {
|
|
freedv_tx_fsk_data(f, mod_out);
|
|
}
|
|
}
|
|
|
|
int freedv_data_ntxframes (struct freedv *f){
|
|
assert(f != NULL);
|
|
if (f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_2400B) {
|
|
if (f->deframer->fdc)
|
|
return freedv_data_get_n_tx_frames(f->deframer->fdc, 8);
|
|
} else if (f->mode == FREEDV_MODE_800XA) {
|
|
if (f->deframer->fdc)
|
|
return freedv_data_get_n_tx_frames(f->deframer->fdc, 6);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int freedv_nin(struct freedv *f) {
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C))
|
|
// For mode 700, the input rate is 8000 sps, but the modem rate is 7500 sps
|
|
// For mode 700, we request a larger number of Rx samples that will be decimated to f->nin samples
|
|
return (16 * f->nin + f->ptFilter8000to7500->decim_index) / 15;
|
|
else
|
|
return f->nin;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_rx
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Takes a frame of samples from the radio receiver, demodulates and
|
|
decodes them, producing a frame of decoded speech samples. See
|
|
freedv_rx.c for an example.
|
|
|
|
demod_in[] is a block of received samples sampled at 8000 Hz.
|
|
To account for difference in the transmit and receive sample clock
|
|
frequencies, the number of demod_in[] samples is time varying. You
|
|
MUST call freedv_nin() BEFORE each call to freedv_rx() and pass
|
|
exactly that many samples to this function.
|
|
|
|
To help set your buffer sizes, The maximum value of freedv_nin() is
|
|
freedv_get_n_max_modem_samples().
|
|
|
|
freedv_rx() returns the number of output speech samples available in
|
|
speech_out[], which is sampled at 8000 Hz. You should ALWAYS check
|
|
the return value of freedv_rx(), and read EXACTLY that number of
|
|
speech samples from speech_out[].
|
|
|
|
1600 and 700D mode: When out of sync, the number of output speech
|
|
samples returned will be freedv_nin(). When in sync to a valid
|
|
FreeDV 1600 signal, the number of output speech samples will
|
|
alternate between freedv_get_n_speech_samples() and 0.
|
|
|
|
700 .. 700C modes: The number of output speech samples returned will
|
|
always be freedv_get_n_speech_samples(), regardless of sync.
|
|
|
|
The peak level of demod_in[] is not critical, as the demod works
|
|
well over a wide range of amplitude scaling. However avoid clipping
|
|
(overload, or samples pinned to +/- 32767). speech_out[] will peak
|
|
at just less than +/-32767.
|
|
|
|
When out of sync, this function echoes the demod_in[] samples to
|
|
speech_out[]. This allows the user to listen to the channel, which
|
|
is useful for tuning FreeDV signals or reception of non-FreeDV
|
|
signals. Setting the squelch with freedv_set_squelch_en(1) will
|
|
return zero-valued samples instead.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
|
|
// short version
|
|
|
|
int freedv_rx(struct freedv *f, short speech_out[], short demod_in[]) {
|
|
assert(f != NULL);
|
|
int i;
|
|
int nin = freedv_nin(f);
|
|
|
|
assert(nin <= f->n_max_modem_samples);
|
|
|
|
/* FSK RX happens in real floats, so convert to those and call their demod here */
|
|
if( (f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA) ){
|
|
float *rx_float = new float[f->n_max_modem_samples];
|
|
for(i=0; i<nin; i++) {
|
|
rx_float[i] = ((float)demod_in[i]);
|
|
}
|
|
int rc = freedv_floatrx(f,speech_out,rx_float);
|
|
delete[] rx_float;
|
|
return rc;
|
|
}
|
|
|
|
if ( (f->mode == FREEDV_MODE_1600) || (f->mode == FREEDV_MODE_700) ||
|
|
(f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
|
|
float gain = 1.0;
|
|
|
|
/* FDM RX happens with complex samps, so do that */
|
|
COMP *rx_fdm = new COMP[f->n_max_modem_samples];
|
|
for(i=0; i<nin; i++) {
|
|
rx_fdm[i].real = gain*(float)demod_in[i];
|
|
rx_fdm[i].imag = 0.0;
|
|
}
|
|
int rc = freedv_comprx(f, speech_out, rx_fdm);
|
|
delete[] rx_fdm;
|
|
return rc;
|
|
}
|
|
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
float gain = 2.0; /* keep levels the same as Octave simulations and C unit tests for real signals */
|
|
return freedv_shortrx(f, speech_out, demod_in, gain);
|
|
}
|
|
|
|
return 0; /* should never get here */
|
|
}
|
|
|
|
|
|
// float input samples version
|
|
int freedv_comprx_fsk(struct freedv *f, COMP demod_in[], int *valid) {
|
|
/* Varicode and protocol bits */
|
|
uint8_t vc_bits[2];
|
|
uint8_t proto_bits[3];
|
|
short vc_bit;
|
|
int i;
|
|
int n_ascii;
|
|
char ascii_out;
|
|
|
|
if(f->mode == FREEDV_MODE_2400A || f->mode == FREEDV_MODE_800XA){
|
|
fsk_demod(f->fsk,(uint8_t*)f->tx_bits,demod_in);
|
|
f->nin = fsk_nin(f->fsk);
|
|
float EbNodB = f->fsk->stats->snr_est; /* fsk demod actually estimates Eb/No */
|
|
f->snr_est = EbNodB + 10.0*log10f(800.0/3000.0); /* so convert to SNR Rb=800, noise B=3000 */
|
|
//fprintf(stderr," %f %f\n", EbNodB, f->snr_est);
|
|
} else{
|
|
/* 2400B needs real input samples */
|
|
int n = fmfsk_nin(f->fmfsk);
|
|
float *demod_in_float = new float[n];
|
|
for(i=0; i<n; i++) {
|
|
demod_in_float[i] = demod_in[i].real;
|
|
}
|
|
fmfsk_demod(f->fmfsk,(uint8_t*)f->tx_bits,demod_in_float);
|
|
delete[] demod_in_float;
|
|
f->nin = fmfsk_nin(f->fmfsk);
|
|
}
|
|
|
|
if(fvhff_deframe_bits(f->deframer,f->packed_codec_bits,proto_bits,vc_bits,(uint8_t*)f->tx_bits)){
|
|
/* Decode varicode text */
|
|
for(i=0; i<2; i++){
|
|
/* Note: deframe_bits spits out bits in uint8_ts while varicode_decode expects shorts */
|
|
vc_bit = vc_bits[i];
|
|
n_ascii = varicode_decode(&f->varicode_dec_states, &ascii_out, &vc_bit, 1, 1);
|
|
if (n_ascii && (f->freedv_put_next_rx_char != NULL)) {
|
|
(*f->freedv_put_next_rx_char)(f->callback_state, ascii_out);
|
|
}
|
|
}
|
|
/* Pass proto bits on down if callback is present */
|
|
if( f->freedv_put_next_proto != NULL){
|
|
(*f->freedv_put_next_proto)(f->proto_callback_state,(char*)proto_bits);
|
|
}
|
|
*valid = 1;
|
|
|
|
/* squelch if if sync but SNR too low */
|
|
if (f->squelch_en && (f->snr_est < f->snr_squelch_thresh)) {
|
|
*valid = 0;
|
|
}
|
|
} else {
|
|
/* squelch if out of sync, or echo input of squelch off */
|
|
if (f->squelch_en)
|
|
*valid = 0;
|
|
else
|
|
*valid = -1;
|
|
}
|
|
f->sync = f->deframer->state;
|
|
f->stats.sync = f->deframer->state;
|
|
|
|
return f->n_speech_samples;
|
|
}
|
|
|
|
int freedv_floatrx(struct freedv *f, short speech_out[], float demod_in[]) {
|
|
assert(f != NULL);
|
|
int i;
|
|
int nin = freedv_nin(f);
|
|
|
|
assert(nin <= f->n_max_modem_samples);
|
|
|
|
COMP *rx_fdm = new COMP[f->n_max_modem_samples];
|
|
for(i=0; i<nin; i++) {
|
|
rx_fdm[i].real = demod_in[i];
|
|
rx_fdm[i].imag = 0;
|
|
}
|
|
|
|
int rc = freedv_comprx(f, speech_out, rx_fdm);
|
|
delete[] rx_fdm;
|
|
return rc;
|
|
}
|
|
|
|
// complex input samples version
|
|
|
|
static int freedv_comprx_fdmdv_1600(struct freedv *f, COMP demod_in[], int *valid) {
|
|
int bits_per_codec_frame, bytes_per_codec_frame, bits_per_fdmdv_frame;
|
|
int i, j, bit, byte, nin_prev, nout;
|
|
int recd_codeword, codeword1, data_flag_index, n_ascii;
|
|
short abit[1];
|
|
char ascii_out;
|
|
int reliable_sync_bit;
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
nout = f->n_speech_samples;
|
|
|
|
COMP *ademod_in = new COMP[f->nin];
|
|
for(i=0; i<f->nin; i++)
|
|
ademod_in[i] = fcmult(1.0/FDMDV_SCALE, demod_in[i]);
|
|
|
|
bits_per_fdmdv_frame = fdmdv_bits_per_frame(f->fdmdv);
|
|
|
|
nin_prev = f->nin;
|
|
fdmdv_demod(f->fdmdv, f->fdmdv_bits, &reliable_sync_bit, ademod_in, &f->nin);
|
|
fdmdv_get_demod_stats(f->fdmdv, &f->stats);
|
|
f->sync = f->fdmdv->sync;
|
|
f->snr_est = f->stats.snr_est;
|
|
|
|
if (reliable_sync_bit == 1) {
|
|
f->evenframe = 1;
|
|
}
|
|
|
|
if (f->stats.sync) {
|
|
if (f->evenframe == 0) {
|
|
memcpy(f->rx_bits, f->fdmdv_bits, bits_per_fdmdv_frame*sizeof(int));
|
|
nout = 0;
|
|
*valid = 0;
|
|
}
|
|
else {
|
|
memcpy(&f->rx_bits[bits_per_fdmdv_frame], f->fdmdv_bits, bits_per_fdmdv_frame*sizeof(int));
|
|
|
|
if (f->test_frames == 0) {
|
|
recd_codeword = 0;
|
|
for(i=0; i<8; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= (f->rx_bits[i] & 0x1);
|
|
}
|
|
for(i=11; i<15; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= (f->rx_bits[i] & 0x1);
|
|
}
|
|
for(i=bits_per_codec_frame; i<bits_per_codec_frame+11; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= (f->rx_bits[i] & 0x1);
|
|
}
|
|
codeword1 = golay23_decode(recd_codeword);
|
|
f->total_bit_errors += golay23_count_errors(recd_codeword, codeword1);
|
|
f->total_bits += 23;
|
|
|
|
//codeword1 = recd_codeword;
|
|
//fprintf(stderr, "received codeword1: 0x%x decoded codeword1: 0x%x\n", recd_codeword, codeword1);
|
|
|
|
for(i=0; i<bits_per_codec_frame; i++)
|
|
f->codec_bits[i] = f->rx_bits[i];
|
|
|
|
for(i=0; i<8; i++) {
|
|
f->codec_bits[i] = (codeword1 >> (22-i)) & 0x1;
|
|
}
|
|
for(i=8,j=11; i<12; i++,j++) {
|
|
f->codec_bits[j] = (codeword1 >> (22-i)) & 0x1;
|
|
}
|
|
|
|
// extract txt msg data bit ------------------------------------------------------------
|
|
|
|
data_flag_index = codec2_get_spare_bit_index(f->codec2);
|
|
abit[0] = f->codec_bits[data_flag_index];
|
|
|
|
n_ascii = varicode_decode(&f->varicode_dec_states, &ascii_out, abit, 1, 1);
|
|
if (n_ascii && (f->freedv_put_next_rx_char != NULL)) {
|
|
(*f->freedv_put_next_rx_char)(f->callback_state, ascii_out);
|
|
}
|
|
|
|
// reconstruct missing bit we steal for data bit and decode speech
|
|
|
|
codec2_rebuild_spare_bit(f->codec2, f->codec_bits);
|
|
|
|
// pack bits, MSB received first
|
|
|
|
bit = 7;
|
|
byte = 0;
|
|
memset(f->packed_codec_bits, 0, bytes_per_codec_frame);
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
f->packed_codec_bits[byte] |= (f->codec_bits[i] << bit);
|
|
bit--;
|
|
if(bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
*valid = 1;
|
|
}
|
|
else {
|
|
int test_frame_sync, bit_errors, ntest_bits, k;
|
|
short *error_pattern = new short[fdmdv_error_pattern_size(f->fdmdv)];
|
|
|
|
for(k=0; k<2; k++) {
|
|
/* test frames, so lets sync up to the test frames and count any errors */
|
|
|
|
fdmdv_put_test_bits(f->fdmdv, &test_frame_sync, error_pattern, &bit_errors, &ntest_bits, &f->rx_bits[k*bits_per_fdmdv_frame]);
|
|
|
|
if (test_frame_sync == 1) {
|
|
f->test_frame_sync_state = 1;
|
|
f->test_frame_count = 0;
|
|
}
|
|
|
|
if (f->test_frame_sync_state) {
|
|
if (f->test_frame_count == 0) {
|
|
f->total_bit_errors += bit_errors;
|
|
f->total_bits += ntest_bits;
|
|
if (f->freedv_put_error_pattern != NULL) {
|
|
(*f->freedv_put_error_pattern)(f->error_pattern_callback_state, error_pattern, fdmdv_error_pattern_size(f->fdmdv));
|
|
}
|
|
}
|
|
f->test_frame_count++;
|
|
if (f->test_frame_count == 4)
|
|
f->test_frame_count = 0;
|
|
}
|
|
|
|
//fprintf(stderr, "test_frame_sync: %d test_frame_sync_state: %d bit_errors: %d ntest_bits: %d\n",
|
|
// test_frame_sync, f->test_frame_sync_state, bit_errors, ntest_bits);
|
|
}
|
|
|
|
delete[] error_pattern;
|
|
}
|
|
|
|
|
|
/* squelch if beneath SNR threshold or test frames enabled */
|
|
|
|
if ((f->squelch_en && (f->stats.snr_est < f->snr_squelch_thresh)) || f->test_frames) {
|
|
//fprintf(stderr,"squelch %f %f !\n", f->stats.snr_est, f->snr_squelch_thresh);
|
|
*valid = 0;
|
|
}
|
|
|
|
nout = f->n_speech_samples;
|
|
|
|
}
|
|
|
|
/* note this freewheels if reliable sync dissapears on bad channels */
|
|
|
|
if (f->evenframe)
|
|
f->evenframe = 0;
|
|
else
|
|
f->evenframe = 1;
|
|
//fprintf(stderr,"%d\n", f->evenframe);
|
|
|
|
} /* if (sync) .... */
|
|
else {
|
|
/* if not in sync pass through analog samples */
|
|
/* this lets us "hear" whats going on, e.g. during tuning */
|
|
|
|
//fprintf(stderr, "out of sync\n");
|
|
|
|
if (f->squelch_en == 0) {
|
|
*valid = -1;
|
|
}
|
|
else {
|
|
*valid = 0;
|
|
}
|
|
//fprintf(stderr, "%d %d %d\n", nin_prev, speech_out[0], speech_out[nin_prev-1]);
|
|
nout = nin_prev;
|
|
}
|
|
delete[] ademod_in;
|
|
return nout;
|
|
}
|
|
|
|
static int freedv_comprx_700(struct freedv *f, COMP demod_in_8kHz[], int *valid) {
|
|
int bits_per_codec_frame, bytes_per_codec_frame;
|
|
int i, j, bit, byte, nout, k;
|
|
int data_flag_index, n_ascii, nspare;
|
|
short abit[1];
|
|
char ascii_out;
|
|
float rx_bits[COHPSK_BITS_PER_FRAME]; /* soft decn rx bits */
|
|
int sync;
|
|
int frames;
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
frames = f->n_codec_bits / bits_per_codec_frame;
|
|
nout = f->n_speech_samples;
|
|
|
|
// echo samples back out as default (say if sync not found)
|
|
*valid = -1;
|
|
|
|
// quisk_cfInterpDecim() modifies input data so lets make a copy just in case there
|
|
// is no sync and we need to echo inout to output
|
|
|
|
COMP *demod_in = new COMP[freedv_nin(f)];
|
|
for(i=0; i<freedv_nin(f); i++)
|
|
demod_in[i] = demod_in_8kHz[i];
|
|
|
|
i = quisk_cfInterpDecim((std::complex<float> *)demod_in, freedv_nin(f), f->ptFilter8000to7500, 15, 16);
|
|
//if (i != f->nin)
|
|
// printf("freedv_comprx decimation: input %d output %d\n", freedv_nin(f), i);
|
|
|
|
for(i=0; i<f->nin; i++)
|
|
demod_in[i] = fcmult(1.0/FDMDV_SCALE, demod_in[i]);
|
|
|
|
cohpsk_demod(f->cohpsk, rx_bits, &sync, demod_in, &f->nin);
|
|
|
|
f->sync = sync;
|
|
cohpsk_get_demod_stats(f->cohpsk, &f->stats);
|
|
f->snr_est = f->stats.snr_est;
|
|
|
|
memset(f->packed_codec_bits, 0, bytes_per_codec_frame * frames);
|
|
|
|
if (sync) {
|
|
|
|
if (f->test_frames == 0) {
|
|
data_flag_index = codec2_get_spare_bit_index(f->codec2);
|
|
|
|
/* optional smoothing of codec symbols */
|
|
|
|
if (f->smooth_symbols) {
|
|
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
rx_bits[i] += rx_bits[i+bits_per_codec_frame];
|
|
rx_bits[i+bits_per_codec_frame] = rx_bits[i];
|
|
}
|
|
}
|
|
|
|
byte = 0;
|
|
for (j=0; j<COHPSK_BITS_PER_FRAME; j+=bits_per_codec_frame) {
|
|
|
|
/* extract txt msg data bit(s) */
|
|
|
|
switch(f->mode) {
|
|
case FREEDV_MODE_700:
|
|
nspare = 2;
|
|
break;
|
|
case FREEDV_MODE_700B:
|
|
nspare = 1; // Just one spare bit for FREEDV_MODE_700B
|
|
break;
|
|
case FREEDV_MODE_700C:
|
|
nspare = 0; // and no spare bits for 700C atm
|
|
break;
|
|
default:
|
|
nspare = 0;
|
|
fprintf(stderr, "FreeDV::freedv_comprx_700: unknown mode default to nspare = 0");
|
|
}
|
|
|
|
for(k=0; k<nspare; k++) {
|
|
abit[0] = rx_bits[data_flag_index+j+k] < 0.0;
|
|
|
|
n_ascii = varicode_decode(&f->varicode_dec_states, &ascii_out, abit, 1, 1);
|
|
if (n_ascii && (f->freedv_put_next_rx_char != NULL)) {
|
|
(*f->freedv_put_next_rx_char)(f->callback_state, ascii_out);
|
|
}
|
|
}
|
|
|
|
/* pack bits, MSB received first */
|
|
|
|
bit = 7;
|
|
for(i=0; i<bits_per_codec_frame; i++) {
|
|
f->packed_codec_bits[byte] |= ((rx_bits[j+i] < 0.0) << bit);
|
|
bit--;
|
|
if (bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
if (bit != 7)
|
|
byte++;
|
|
|
|
if (f->squelch_en && (f->stats.snr_est < f->snr_squelch_thresh)) {
|
|
*valid = 0;
|
|
}
|
|
*valid = 1;
|
|
}
|
|
nout = f->n_speech_samples;
|
|
}
|
|
else {
|
|
//fprintf(stderr, " freedv_api: f->test_frames_diversity: %d\n", f->test_frames_diversity);
|
|
|
|
if (f->test_frames_diversity) {
|
|
/* normal operation - error pattern on frame after diveristy combination */
|
|
short error_pattern[COHPSK_BITS_PER_FRAME];
|
|
int bit_errors;
|
|
|
|
/* test data, lets see if we can sync to the test data sequence */
|
|
|
|
char rx_bits_char[COHPSK_BITS_PER_FRAME];
|
|
for(i=0; i<COHPSK_BITS_PER_FRAME; i++)
|
|
rx_bits_char[i] = rx_bits[i] < 0.0;
|
|
cohpsk_put_test_bits(f->cohpsk, &f->test_frame_sync_state, error_pattern, &bit_errors, rx_bits_char, 0);
|
|
if (f->test_frame_sync_state) {
|
|
f->total_bit_errors += bit_errors;
|
|
f->total_bits += COHPSK_BITS_PER_FRAME;
|
|
if (f->freedv_put_error_pattern != NULL) {
|
|
(*f->freedv_put_error_pattern)(f->error_pattern_callback_state, error_pattern, COHPSK_BITS_PER_FRAME);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* calculate error pattern on uncombined carriers - test mode to spot any carrier specific issues like
|
|
tx passband filtering */
|
|
|
|
short error_pattern[2*COHPSK_BITS_PER_FRAME];
|
|
char rx_bits_char[COHPSK_BITS_PER_FRAME];
|
|
int bit_errors_lower, bit_errors_upper;
|
|
|
|
/* lower group of carriers */
|
|
|
|
float *rx_bits_lower = cohpsk_get_rx_bits_lower(f->cohpsk);
|
|
for(i=0; i<COHPSK_BITS_PER_FRAME; i++) {
|
|
rx_bits_char[i] = rx_bits_lower[i] < 0.0;
|
|
}
|
|
cohpsk_put_test_bits(f->cohpsk, &f->test_frame_sync_state, error_pattern, &bit_errors_lower, rx_bits_char, 0);
|
|
|
|
/* upper group of carriers */
|
|
|
|
float *rx_bits_upper = cohpsk_get_rx_bits_upper(f->cohpsk);
|
|
for(i=0; i<COHPSK_BITS_PER_FRAME; i++) {
|
|
rx_bits_char[i] = rx_bits_upper[i] < 0.0;
|
|
}
|
|
cohpsk_put_test_bits(f->cohpsk, &f->test_frame_sync_state_upper, &error_pattern[COHPSK_BITS_PER_FRAME], &bit_errors_upper, rx_bits_char, 1);
|
|
// fprintf(stderr, " freedv_api: f->test_frame_sync_state: %d f->test_frame_sync_state_upper: %d\n",
|
|
// f->test_frame_sync_state, f->test_frame_sync_state_upper);
|
|
|
|
/* combine total errors and call callback */
|
|
|
|
if (f->test_frame_sync_state && f->test_frame_sync_state_upper) {
|
|
f->total_bit_errors += bit_errors_lower + bit_errors_upper;
|
|
f->total_bits += 2*COHPSK_BITS_PER_FRAME;
|
|
if (f->freedv_put_error_pattern != NULL) {
|
|
(*f->freedv_put_error_pattern)(f->error_pattern_callback_state, error_pattern, 2*COHPSK_BITS_PER_FRAME);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
*valid = 0;
|
|
nout = f->n_speech_samples;
|
|
}
|
|
|
|
}
|
|
|
|
/* no valid FreeDV signal - squelch output */
|
|
|
|
if (sync == 0) {
|
|
nout = freedv_nin(f);
|
|
if (f->squelch_en) {
|
|
*valid = 0;
|
|
}
|
|
}
|
|
delete[] demod_in;
|
|
return nout;
|
|
}
|
|
|
|
/*
|
|
TODO:
|
|
[X] in testframe mode count coded and uncoded errors
|
|
[X] freedv getter for modem and interleaver sync
|
|
[X] rms level the same as fdmdv
|
|
[X] way to stay in sync and not resync automatically
|
|
[X] SNR est, maybe from pilots, cohpsk have an example?
|
|
[X] work out how to handle return of multiple interleaved frames over time
|
|
[ ] error pattern support?
|
|
[ ] deal with out of sync returning nin samples, listening to analog audio when out of sync
|
|
*/
|
|
|
|
static int freedv_comprx_700d(struct freedv *f, short demod_in_8kHz[], float gain, int *valid) {
|
|
int bits_per_codec_frame, bytes_per_codec_frame;
|
|
int i, j, bit, byte, nout, k;
|
|
int n_ascii;
|
|
char ascii_out;
|
|
int frames;
|
|
struct OFDM *ofdm = f->ofdm;
|
|
struct LDPC *ldpc = f->ldpc;
|
|
|
|
int data_bits_per_frame = ldpc->data_bits_per_frame;
|
|
int coded_bits_per_frame = ldpc->coded_bits_per_frame;
|
|
int coded_syms_per_frame = ldpc->coded_syms_per_frame;
|
|
int interleave_frames = f->interleave_frames;
|
|
COMP *codeword_symbols = f->codeword_symbols;
|
|
float *codeword_amps = f->codeword_amps;
|
|
int *rx_bits = new int[ofdm_bitsperframe];
|
|
short *txt_bits = new short[ofdm_ntxtbits];
|
|
COMP *payload_syms = new COMP[coded_syms_per_frame];
|
|
float *payload_amps = new float[coded_syms_per_frame];
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
frames = f->n_codec_bits / bits_per_codec_frame;
|
|
|
|
// pass through is too noisey ....
|
|
//nout = f->n_speech_samples;
|
|
nout = 0;
|
|
|
|
int Nerrs_raw = 0;
|
|
int Nerrs_coded = 0;
|
|
int iter = 0;
|
|
int parityCheckCount = 0;
|
|
uint8_t *rx_uw = new uint8_t[ofdm_nuwbits];
|
|
|
|
float new_gain = gain / OFDM_AMP_SCALE;
|
|
|
|
/* echo samples back out as default (say if sync not found) */
|
|
|
|
*valid = 1;
|
|
f->sync = f->stats.sync = 0;
|
|
|
|
/* TODO estimate this properly from signal */
|
|
|
|
float EsNo = 3.0;
|
|
|
|
/* looking for modem sync */
|
|
|
|
if (ofdm->sync_state == search) {
|
|
ofdm_sync_search_shorts(f->ofdm, demod_in_8kHz, new_gain);
|
|
}
|
|
|
|
/* OK modem is in sync */
|
|
|
|
if ((ofdm->sync_state == synced) || (ofdm->sync_state == trial))
|
|
{
|
|
ofdm_demod_shorts(ofdm, rx_bits, demod_in_8kHz, new_gain);
|
|
ofdm_disassemble_modem_frame(ofdm, rx_uw, payload_syms, payload_amps, txt_bits);
|
|
|
|
f->sync = 1;
|
|
ofdm_get_demod_stats(f->ofdm, &f->stats);
|
|
f->snr_est = f->stats.snr_est;
|
|
|
|
assert((ofdm_nuwbits+ofdm_ntxtbits+coded_bits_per_frame) == ofdm_bitsperframe);
|
|
|
|
/* now we need to buffer for de-interleaving -------------------------------------*/
|
|
|
|
/* shift interleaved symbol buffers to make room for new symbols */
|
|
|
|
for(i=0, j=coded_syms_per_frame; j<interleave_frames*coded_syms_per_frame; i++,j++) {
|
|
codeword_symbols[i] = codeword_symbols[j];
|
|
codeword_amps[i] = codeword_amps[j];
|
|
}
|
|
|
|
/* newest symbols at end of buffer (uses final i from last loop), note we
|
|
change COMP formats from what modem uses internally */
|
|
|
|
for(i=(interleave_frames-1)*coded_syms_per_frame,j=0; i<interleave_frames*coded_syms_per_frame; i++,j++) {
|
|
codeword_symbols[i] = payload_syms[j];
|
|
codeword_amps[i] = payload_amps[j];
|
|
}
|
|
|
|
/* run de-interleaver */
|
|
|
|
COMP *codeword_symbols_de = new COMP[interleave_frames*coded_syms_per_frame];
|
|
float *codeword_amps_de = new float[interleave_frames*coded_syms_per_frame];
|
|
gp_deinterleave_comp (codeword_symbols_de, codeword_symbols, interleave_frames*coded_syms_per_frame);
|
|
gp_deinterleave_float(codeword_amps_de , codeword_amps , interleave_frames*coded_syms_per_frame);
|
|
|
|
float *llr = new float[coded_bits_per_frame];
|
|
uint8_t *out_char = new uint8_t[coded_bits_per_frame];
|
|
|
|
interleaver_sync_state_machine(ofdm, ldpc, ofdm_config, codeword_symbols_de, codeword_amps_de, EsNo,
|
|
interleave_frames, &iter, &parityCheckCount, &Nerrs_coded);
|
|
|
|
if ((ofdm->sync_state_interleaver == synced) && (ofdm->frame_count_interleaver == interleave_frames)) {
|
|
ofdm->frame_count_interleaver = 0;
|
|
|
|
if (f->test_frames) {
|
|
int *tmp = new int[interleave_frames];
|
|
Nerrs_raw = count_uncoded_errors(ldpc, ofdm_config, tmp, interleave_frames, codeword_symbols_de);
|
|
f->total_bit_errors += Nerrs_raw;
|
|
f->total_bits += ofdm_bitsperframe*interleave_frames;
|
|
delete[] tmp;
|
|
}
|
|
|
|
memset(f->packed_codec_bits, 0, bytes_per_codec_frame * frames);
|
|
byte = 0; f->modem_frame_count_rx = 0;
|
|
|
|
for (j=0; j<interleave_frames; j++) {
|
|
symbols_to_llrs(llr, &codeword_symbols_de[j*coded_syms_per_frame],
|
|
&codeword_amps_de[j*coded_syms_per_frame],
|
|
EsNo, ofdm->mean_amp, coded_syms_per_frame);
|
|
iter = run_ldpc_decoder(ldpc, out_char, llr, &parityCheckCount);
|
|
|
|
if (f->test_frames) {
|
|
uint8_t *payload_data_bits = new uint8_t[data_bits_per_frame];
|
|
ofdm_generate_payload_data_bits(payload_data_bits, data_bits_per_frame);
|
|
Nerrs_coded = count_errors(payload_data_bits, out_char, data_bits_per_frame);
|
|
f->total_bit_errors_coded += Nerrs_coded;
|
|
f->total_bits_coded += data_bits_per_frame;
|
|
delete[] payload_data_bits;
|
|
} else {
|
|
|
|
/* a frame of valid Codec 2 bits, pack into Codec 2 frame */
|
|
|
|
for (i=0; i<data_bits_per_frame; i+=bits_per_codec_frame) {
|
|
|
|
/* pack bits, MSB received first */
|
|
|
|
bit = 7;
|
|
for(k=0; k<bits_per_codec_frame; k++) {
|
|
f->packed_codec_bits[byte] |= (out_char[i+k] << bit);
|
|
bit--;
|
|
if (bit < 0) {
|
|
bit = 7;
|
|
byte++;
|
|
}
|
|
}
|
|
if (bit != 7)
|
|
byte++;
|
|
}
|
|
|
|
}
|
|
} /* for interleave frames ... */
|
|
|
|
/* make sure we don't overrun packed byte array */
|
|
|
|
assert(byte <= f->nbyte_packed_codec_bits);
|
|
|
|
nout = f->n_speech_samples;
|
|
|
|
if (f->squelch_en && (f->stats.snr_est < f->snr_squelch_thresh)) {
|
|
*valid = 0;
|
|
}
|
|
|
|
} /* if interleaver synced ..... */
|
|
|
|
/* If modem is synced we can decode txt bits */
|
|
|
|
for(k=0; k<ofdm_ntxtbits; k++) {
|
|
//fprintf(stderr, "txt_bits[%d] = %d\n", k, rx_bits[i]);
|
|
n_ascii = varicode_decode(&f->varicode_dec_states, &ascii_out, &txt_bits[k], 1, 1);
|
|
if (n_ascii && (f->freedv_put_next_rx_char != NULL)) {
|
|
(*f->freedv_put_next_rx_char)(f->callback_state, ascii_out);
|
|
}
|
|
}
|
|
|
|
/* estimate uncoded BER from UW. Coded bit errors could
|
|
probably be estimated as half of all failed LDPC parity
|
|
checks */
|
|
|
|
for(i=0; i<ofdm_nuwbits; i++) {
|
|
if (rx_uw[i] != ofdm->tx_uw[i]) {
|
|
f->total_bit_errors++;
|
|
}
|
|
}
|
|
f->total_bits += ofdm_nuwbits;
|
|
|
|
delete[] out_char;
|
|
delete[] llr;
|
|
delete[] codeword_amps_de;
|
|
delete[] codeword_symbols_de;
|
|
} /* if modem synced .... */
|
|
else
|
|
{
|
|
*valid = -1;
|
|
}
|
|
|
|
/* iterate state machine and update nin for next call */
|
|
|
|
f->nin = ofdm_get_nin(ofdm);
|
|
//fprintf(stderr, "nin: %d\n", ofdm_get_nin(ofdm));
|
|
ofdm_sync_state_machine(ofdm, rx_uw);
|
|
|
|
if (f->verbose && (ofdm->last_sync_state == search)) {
|
|
fprintf(stderr, "%3d st: %-6s euw: %2d %1d f: %5.1f ist: %-6s %2d eraw: %3d ecdd: %3d iter: %3d pcc: %3d vld: %d, nout: %4d\n",
|
|
f->frames++, statemode[ofdm->last_sync_state], ofdm->uw_errors, ofdm->sync_counter,
|
|
(double)ofdm->foff_est_hz,
|
|
statemode[ofdm->last_sync_state_interleaver], ofdm->frame_count_interleaver,
|
|
Nerrs_raw, Nerrs_coded, iter, parityCheckCount, *valid, nout);
|
|
}
|
|
|
|
/* no valid FreeDV signal - squelch output */
|
|
|
|
bool sync = ((ofdm->sync_state == synced) || (ofdm->sync_state == trial));
|
|
if (sync == false) {
|
|
if (f->squelch_en == true) {
|
|
*valid = 0;
|
|
}
|
|
//f->snr_est = 0.0;
|
|
}
|
|
|
|
//fprintf(stderr, "sync: %d valid: %d snr: %3.2f\n", f->sync, *valid, f->snr_est);
|
|
|
|
delete[] rx_uw;
|
|
delete[] payload_amps;
|
|
delete[] payload_syms;
|
|
delete[] txt_bits;
|
|
delete[] rx_bits;
|
|
return nout;
|
|
}
|
|
|
|
/* Original version for all but 700D */
|
|
int freedv_comprx(struct freedv *f, short speech_out[], COMP demod_in[]) {
|
|
assert(f != NULL);
|
|
int bits_per_codec_frame, bytes_per_codec_frame;
|
|
int i, nout = 0;
|
|
int valid = 0;
|
|
|
|
assert(f->nin <= f->n_max_modem_samples);
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
|
|
if (f->mode == FREEDV_MODE_1600) {
|
|
nout = freedv_comprx_fdmdv_1600(f, demod_in, &valid);
|
|
}
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
nout = freedv_comprx_700(f, demod_in, &valid);
|
|
}
|
|
|
|
if( (f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA)){
|
|
nout = freedv_comprx_fsk(f, demod_in, &valid);
|
|
}
|
|
|
|
if (valid == 0) {
|
|
//fprintf(stderr, "squelch nout: %d\n", nout);
|
|
|
|
/* squelch */
|
|
|
|
for (i = 0; i < nout; i++)
|
|
speech_out[i] = 0;
|
|
}
|
|
else if (valid < 0) {
|
|
/* we havent got sync so play audio from radio. This might
|
|
not work for all modes due to nin bouncing about */
|
|
for (i = 0; i < nout; i++)
|
|
speech_out[i] = demod_in[i].real;
|
|
}
|
|
else {
|
|
/* decoded audio to play */
|
|
|
|
int frames = f->n_codec_bits / bits_per_codec_frame;
|
|
//fprintf(stderr, "frames: %d\n", frames);
|
|
for (i = 0; i < frames; i++) {
|
|
codec2_decode(f->codec2, speech_out, f->packed_codec_bits + i * bytes_per_codec_frame);
|
|
speech_out += codec2_samples_per_frame(f->codec2);
|
|
}
|
|
}
|
|
|
|
//fprintf(stderr,"freedv_nin(f): %d nout: %d valid: %d\n", freedv_nin(f), nout, valid);
|
|
return nout;
|
|
}
|
|
|
|
/* 700D version */
|
|
int freedv_shortrx(struct freedv *f, short speech_out[], short demod_in[], float gain) {
|
|
assert(f != NULL);
|
|
int bits_per_codec_frame, bytes_per_codec_frame;
|
|
int i, nout = 0;
|
|
int valid = 0;
|
|
|
|
assert(f->nin <= f->n_max_modem_samples);
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
nout = freedv_comprx_700d(f, demod_in, gain, &valid);
|
|
}
|
|
|
|
if (valid == 0) {
|
|
//fprintf(stderr, "squelch nout: %d\n", nout);
|
|
|
|
/* squelch */
|
|
|
|
for (i = 0; i < nout; i++)
|
|
speech_out[i] = 0;
|
|
}
|
|
else if (valid < 0) {
|
|
/* we havent got sync so play audio from radio. This might
|
|
not work for all modes due to nin bouncing about */
|
|
for (i = 0; i < nout; i++)
|
|
speech_out[i] = demod_in[i];
|
|
}
|
|
else {
|
|
/* decoded audio to play */
|
|
|
|
int data_bits_per_frame = f->ldpc->data_bits_per_frame;
|
|
int frames = data_bits_per_frame/bits_per_codec_frame;
|
|
|
|
nout = 0;
|
|
if (f->modem_frame_count_rx < f->interleave_frames) {
|
|
nout = f->n_speech_samples;
|
|
//fprintf(stderr, "modem_frame_count_rx: %d nout: %d\n", f->modem_frame_count_rx, nout);
|
|
for (i = 0; i < frames; i++) {
|
|
codec2_decode(f->codec2, speech_out, f->packed_codec_bits + (i + frames*f->modem_frame_count_rx)* bytes_per_codec_frame);
|
|
speech_out += codec2_samples_per_frame(f->codec2);
|
|
}
|
|
f->modem_frame_count_rx++;
|
|
}
|
|
}
|
|
|
|
return nout;
|
|
}
|
|
|
|
|
|
int freedv_codecrx(struct freedv *f, unsigned char *packed_codec_bits, short demod_in[])
|
|
{
|
|
assert(f != NULL);
|
|
int i;
|
|
int nin = freedv_nin(f);
|
|
int valid;
|
|
int ret = 0;
|
|
int bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
|
|
assert(nin <= f->n_max_modem_samples);
|
|
|
|
if (f->mode != FREEDV_MODE_700D) {
|
|
COMP *rx_fdm = new COMP[f->n_max_modem_samples];
|
|
|
|
for(i=0; i<nin; i++) {
|
|
rx_fdm[i].real = (float)demod_in[i];
|
|
rx_fdm[i].imag = 0.0;
|
|
}
|
|
|
|
if (f->mode == FREEDV_MODE_1600) {
|
|
freedv_comprx_fdmdv_1600(f, rx_fdm, &valid);
|
|
}
|
|
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
freedv_comprx_700(f, rx_fdm, &valid);
|
|
}
|
|
|
|
if( (f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA)){
|
|
freedv_comprx_fsk(f, rx_fdm, &valid);
|
|
}
|
|
|
|
delete[] rx_fdm;
|
|
}
|
|
|
|
int bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
freedv_comprx_700d(f, demod_in, 1.0, &valid);
|
|
|
|
int data_bits_per_frame = f->ldpc->data_bits_per_frame;
|
|
int frames = data_bits_per_frame/bits_per_codec_frame;
|
|
|
|
if (valid == 1 && f->modem_frame_count_rx < f->interleave_frames) {
|
|
for (i = 0; i < frames; i++) {
|
|
memcpy(packed_codec_bits, f->packed_codec_bits + (i + frames*f->modem_frame_count_rx)* bytes_per_codec_frame, bytes_per_codec_frame);
|
|
packed_codec_bits += bytes_per_codec_frame;
|
|
ret += bytes_per_codec_frame;
|
|
}
|
|
f->modem_frame_count_rx++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
if (valid == 1) {
|
|
int codec_frames = f->n_codec_bits / bits_per_codec_frame;
|
|
|
|
memcpy(packed_codec_bits, f->packed_codec_bits, bytes_per_codec_frame * codec_frames);
|
|
ret = bytes_per_codec_frame * codec_frames;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_get_version
|
|
AUTHOR......: Jim Ahlstrom
|
|
DATE CREATED: 28 July 2015
|
|
|
|
Return the version of the FreeDV API. This is meant to help API users determine when
|
|
incompatible changes have occurred.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
int freedv_get_version(void)
|
|
{
|
|
return VERSION;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_set_callback_txt
|
|
AUTHOR......: Jim Ahlstrom
|
|
DATE CREATED: 28 July 2015
|
|
|
|
Set the callback functions and the callback state pointer that will be used
|
|
for the aux txt channel. The freedv_callback_rx is a function pointer that
|
|
will be called to return received characters. The freedv_callback_tx is a
|
|
function pointer that will be called to send transmitted characters. The callback
|
|
state is a user-defined void pointer that will be passed to the callback functions.
|
|
Any or all can be NULL, and the default is all NULL.
|
|
The function signatures are:
|
|
void receive_char(void *callback_state, char c);
|
|
char transmit_char(void *callback_state);
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_set_callback_txt(struct freedv *f, freedv_callback_rx rx, freedv_callback_tx tx, void *state)
|
|
{
|
|
if (f->mode != FREEDV_MODE_800XA) {
|
|
f->freedv_put_next_rx_char = rx;
|
|
f->freedv_get_next_tx_char = tx;
|
|
f->callback_state = state;
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_set_callback_protocol
|
|
AUTHOR......: Brady OBrien
|
|
DATE CREATED: 21 February 2016
|
|
|
|
Set the callback functions and callback pointer that will be used for the
|
|
protocol data channel. freedv_callback_protorx will be called when a frame
|
|
containing protocol data arrives. freedv_callback_prototx will be called
|
|
when a frame containing protocol information is being generated. Protocol
|
|
information is intended to be used to develop protocols and fancy features
|
|
atop VHF freedv, much like those present in DMR.
|
|
Protocol bits are to be passed in an msb-first char array
|
|
The number of protocol bits are findable with freedv_get_protocol_bits
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_set_callback_protocol(struct freedv *f, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state){
|
|
if (f->mode != FREEDV_MODE_800XA) {
|
|
f->freedv_put_next_proto = rx;
|
|
f->freedv_get_next_proto = tx;
|
|
f->proto_callback_state = callback_state;
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_set_callback_datarx / freedv_set_callback_datatx
|
|
AUTHOR......: Jeroen Vreeken
|
|
DATE CREATED: 04 March 2016
|
|
|
|
Set the callback functions and callback pointer that will be used for the
|
|
data channel. freedv_callback_datarx will be called when a packet has been
|
|
successfully received. freedv_callback_data_tx will be called when
|
|
transmission of a new packet can begin.
|
|
If the returned size of the datatx callback is zero the data frame is still
|
|
generated, but will contain only a header update.
|
|
\*---------------------------------------------------------------------------*/
|
|
void freedv_set_callback_data(struct freedv *f, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state) {
|
|
if ((f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA)){
|
|
if (!f->deframer->fdc)
|
|
f->deframer->fdc = freedv_data_channel_create();
|
|
if (!f->deframer->fdc)
|
|
return;
|
|
|
|
freedv_data_set_cb_rx(f->deframer->fdc, datarx, callback_state);
|
|
freedv_data_set_cb_tx(f->deframer->fdc, datatx, callback_state);
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_set_data_header
|
|
AUTHOR......: Jeroen Vreeken
|
|
DATE CREATED: 04 March 2016
|
|
|
|
Set the data header for the data channel.
|
|
Header compression will be used whenever packets from this header are sent.
|
|
The header will also be used for fill packets when a data frame is requested
|
|
without a packet available.
|
|
\*---------------------------------------------------------------------------*/
|
|
void freedv_set_data_header(struct freedv *f, unsigned char *header)
|
|
{
|
|
if ((f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_2400B) || (f->mode == FREEDV_MODE_800XA)){
|
|
if (!f->deframer->fdc)
|
|
f->deframer->fdc = freedv_data_channel_create();
|
|
if (!f->deframer->fdc)
|
|
return;
|
|
|
|
freedv_data_set_header(f->deframer->fdc, header);
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_get_modem_stats
|
|
AUTHOR......: Jim Ahlstrom
|
|
DATE CREATED: 28 July 2015
|
|
|
|
Return data from the modem. The arguments are pointers to the data items. The
|
|
pointers can be NULL if the data item is not wanted.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_get_modem_stats(struct freedv *f, int *sync, float *snr_est)
|
|
{
|
|
if (f->mode == FREEDV_MODE_1600)
|
|
fdmdv_get_demod_stats(f->fdmdv, &f->stats);
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C))
|
|
cohpsk_get_demod_stats(f->cohpsk, &f->stats);
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
ofdm_get_demod_stats(f->ofdm, &f->stats);
|
|
}
|
|
if (f->mode == FREEDV_MODE_2400B) {
|
|
fmfsk_get_demod_stats(f->fmfsk, &f->stats);
|
|
}
|
|
if (sync) *sync = f->stats.sync;
|
|
if (snr_est) *snr_est = f->stats.snr_est;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTIONS...: freedv_set_*
|
|
AUTHOR......: Jim Ahlstrom
|
|
DATE CREATED: 28 July 2015
|
|
|
|
Set some parameters used by FreeDV. It is possible to write a macro using ## for
|
|
this, but I wasn't sure it would be 100% portable.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
// Set integers
|
|
void freedv_set_test_frames (struct freedv *f, int val) {f->test_frames = val;}
|
|
void freedv_set_test_frames_diversity (struct freedv *f, int val) {f->test_frames_diversity = val;}
|
|
void freedv_set_squelch_en (struct freedv *f, int val) {f->squelch_en = val;}
|
|
void freedv_set_total_bit_errors (struct freedv *f, int val) {f->total_bit_errors = val;}
|
|
void freedv_set_total_bits (struct freedv *f, int val) {f->total_bits = val;}
|
|
void freedv_set_total_bit_errors_coded (struct freedv *f, int val) {f->total_bit_errors_coded = val;}
|
|
void freedv_set_total_bits_coded (struct freedv *f, int val) {f->total_bits_coded = val;}
|
|
void freedv_set_clip (struct freedv *f, int val) {f->clip = val;}
|
|
void freedv_set_varicode_code_num (struct freedv *f, int val) {varicode_set_code_num(&f->varicode_dec_states, val);}
|
|
void freedv_set_ext_vco (struct freedv *f, int val) {f->ext_vco = val;}
|
|
|
|
|
|
/* Band Pass Filter to cleanup OFDM tx waveform, only supported by FreeDV 700D */
|
|
|
|
void freedv_set_tx_bpf(struct freedv *f, int val) {
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
ofdm_set_tx_bpf(f->ofdm, val);
|
|
}
|
|
}
|
|
|
|
|
|
void freedv_set_verbose(struct freedv *f, int verbosity) {
|
|
f->verbose = verbosity;
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
ofdm_set_verbose(f->ofdm, f->verbose);
|
|
}
|
|
}
|
|
|
|
// Set floats
|
|
void freedv_set_snr_squelch_thresh (struct freedv *f, float val) {f->snr_squelch_thresh = val;}
|
|
|
|
void freedv_set_callback_error_pattern (struct freedv *f, freedv_calback_error_pattern cb, void *state)
|
|
{
|
|
f->freedv_put_error_pattern = cb;
|
|
f->error_pattern_callback_state = state;
|
|
}
|
|
|
|
void freedv_set_carrier_ampl(struct freedv *freedv, int c, float ampl) {
|
|
assert(freedv->mode == FREEDV_MODE_700C);
|
|
cohpsk_set_carrier_ampl(freedv->cohpsk, c, ampl);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTIONS...: freedv_set_alt_modem_samp_rate
|
|
AUTHOR......: Brady O'Brien
|
|
DATE CREATED: 25 June 2016
|
|
|
|
Attempt to set the alternative sample rate on the modem side of the api. Only
|
|
a few alternative sample rates are supported. Please see below.
|
|
|
|
2400A - 48000, 96000
|
|
2400B - 48000, 96000
|
|
|
|
TODO: Implement 2400B rate changing, allow other rate changing.
|
|
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
int freedv_set_alt_modem_samp_rate(struct freedv *f, int samp_rate){
|
|
if(f->mode == FREEDV_MODE_2400A){
|
|
if(samp_rate == 24000 || samp_rate == 48000 || samp_rate == 96000){
|
|
fsk_destroy(f->fsk);
|
|
f->fsk = fsk_create_hbr(samp_rate,1200,10,4,1200,1200);
|
|
|
|
free(f->tx_bits);
|
|
/* Note: fsk expects tx/rx bits as an array of uint8_ts, not ints */
|
|
f->tx_bits = (int*) malloc(f->fsk->Nbits*sizeof(uint8_t));
|
|
|
|
f->n_nom_modem_samples = f->fsk->N;
|
|
f->n_max_modem_samples = f->fsk->N + (f->fsk->Ts);
|
|
f->n_nat_modem_samples = f->fsk->N;
|
|
f->nin = fsk_nin(f->fsk);
|
|
f->modem_sample_rate = samp_rate;
|
|
return 0;
|
|
}else
|
|
return -1;
|
|
}else if(f->mode == FREEDV_MODE_2400B){
|
|
if(samp_rate == 48000 || samp_rate == 96000){
|
|
return -1;
|
|
}else
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------------* \
|
|
|
|
FUNCTIONS...: freedv_set_sync
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: May 2018
|
|
|
|
Extended control of sync state machines, especially for FreeDV 700D.
|
|
This mode is required to acquire sync up at very low SNRS. This is
|
|
difficult to implement, for example we may get a false sync, or the
|
|
state machine may fall out of sync by mistake during a long fade.
|
|
|
|
So with this API call we allow some operator assistance.
|
|
|
|
Ensure this is called inthe same thread as freedv_rx().
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_set_sync(struct freedv *freedv, Sync sync_cmd) {
|
|
assert (freedv != NULL);
|
|
|
|
if (freedv->mode == FREEDV_MODE_700D) {
|
|
ofdm_set_sync(freedv->ofdm, sync_cmd);
|
|
}
|
|
|
|
}
|
|
|
|
struct FSK * freedv_get_fsk(struct freedv *f){
|
|
return f->fsk;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTIONS...: freedv_get_*
|
|
AUTHOR......: Jim Ahlstrom
|
|
DATE CREATED: 28 July 2015
|
|
|
|
Get some parameters from FreeDV. It is possible to write a macro using ## for
|
|
this, but I wasn't sure it would be 100% portable.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
// Get integers
|
|
int freedv_get_protocol_bits (struct freedv *f) {return f->n_protocol_bits;}
|
|
int freedv_get_mode (struct freedv *f) {return f->mode;}
|
|
int freedv_get_test_frames (struct freedv *f) {return f->test_frames;}
|
|
int freedv_get_n_speech_samples (struct freedv *f) {return f->n_speech_samples;}
|
|
int freedv_get_modem_sample_rate (struct freedv *f) {return f->modem_sample_rate;}
|
|
int freedv_get_modem_symbol_rate (struct freedv *f) {return f->modem_symbol_rate;}
|
|
int freedv_get_n_max_modem_samples (struct freedv *f) {return f->n_max_modem_samples;}
|
|
int freedv_get_n_nom_modem_samples (struct freedv *f) {return f->n_nom_modem_samples;}
|
|
int freedv_get_total_bits (struct freedv *f) {return f->total_bits;}
|
|
int freedv_get_total_bit_errors (struct freedv *f) {return f->total_bit_errors;}
|
|
int freedv_get_total_bits_coded (struct freedv *f) {return f->total_bits_coded;}
|
|
int freedv_get_total_bit_errors_coded (struct freedv *f) {return f->total_bit_errors_coded;}
|
|
int freedv_get_sync (struct freedv *f) {return f->stats.sync;}
|
|
|
|
int freedv_get_sync_interleaver(struct freedv *f) {
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
return f->ofdm->sync_state_interleaver == synced;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int freedv_get_sz_error_pattern(struct freedv *f)
|
|
{
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
/* if diversity disabled callback sends error pattern for upper and lower carriers */
|
|
return f->sz_error_pattern * (2 - f->test_frames_diversity);
|
|
}
|
|
else {
|
|
return f->sz_error_pattern;
|
|
}
|
|
}
|
|
|
|
// Get floats
|
|
|
|
struct CODEC2 *freedv_get_codec2 (struct freedv *f){return f->codec2;}
|
|
int freedv_get_n_codec_bits (struct freedv *f){return f->n_codec_bits;}
|
|
|
|
// Get modem status
|
|
|
|
void freedv_get_modem_extended_stats(struct freedv *f, struct MODEM_STATS *stats)
|
|
{
|
|
if (f->mode == FREEDV_MODE_1600)
|
|
fdmdv_get_demod_stats(f->fdmdv, stats);
|
|
|
|
if ((f->mode == FREEDV_MODE_2400A) || (f->mode == FREEDV_MODE_800XA)) {
|
|
fsk_get_demod_stats(f->fsk, stats);
|
|
float EbNodB = stats->snr_est; /* fsk demod actually estimates Eb/No */
|
|
stats->snr_est = EbNodB + 10.0*log10f(800.0/3000.0); /* so convert to SNR Rb=800, noise B=3000 */
|
|
}
|
|
|
|
if (f->mode == FREEDV_MODE_2400B) {
|
|
fmfsk_get_demod_stats(f->fmfsk, stats);
|
|
}
|
|
|
|
if ((f->mode == FREEDV_MODE_700) || (f->mode == FREEDV_MODE_700B) || (f->mode == FREEDV_MODE_700C)) {
|
|
cohpsk_get_demod_stats(f->cohpsk, stats);
|
|
}
|
|
|
|
if (f->mode == FREEDV_MODE_700D) {
|
|
ofdm_get_demod_stats(f->ofdm, stats);
|
|
}
|
|
|
|
}
|
|
|
|
} // FreeDV
|
|
|