mirror of
https://github.com/n5ac/smartsdr-dsp.git
synced 2025-12-06 03:01:59 +01:00
400 lines
14 KiB
C
400 lines
14 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.
|
|
|
|
TODO:
|
|
[X] speex tx/rx works
|
|
[X] txt messages
|
|
[ ] optional test tx framemode
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
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 <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "codec2.h"
|
|
#include "codec2_fdmdv.h"
|
|
#include "golay23.h"
|
|
#include "varicode.h"
|
|
#include "freedv_api.h"
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
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) {
|
|
struct freedv *f;
|
|
int Nc, codec2_mode, nbit, nbyte;
|
|
|
|
if (mode != FREEDV_MODE_1600)
|
|
return NULL;
|
|
|
|
f = (struct freedv*)malloc(sizeof(struct freedv));
|
|
if (f == NULL)
|
|
return NULL;
|
|
|
|
f->mode = mode;
|
|
f->tx_sync_bit = 0;
|
|
f->snr_thresh = 2.0;
|
|
|
|
if (mode == FREEDV_MODE_1600) {
|
|
Nc = 16;
|
|
codec2_mode = CODEC2_MODE_1300;
|
|
}
|
|
|
|
f->codec2 = codec2_create(codec2_mode);
|
|
if (f->codec2 == NULL)
|
|
return NULL;
|
|
|
|
f->fdmdv = fdmdv_create(Nc);
|
|
if (f->fdmdv == NULL)
|
|
return NULL;
|
|
|
|
nbit = codec2_bits_per_frame(f->codec2);
|
|
nbyte = (nbit + 7) / 8;
|
|
f->packed_codec_bits = (unsigned char*)malloc(nbyte*sizeof(char));
|
|
f->codec_bits = (int*)malloc(nbit*sizeof(int));
|
|
|
|
nbit = 2*fdmdv_bits_per_frame(f->fdmdv);
|
|
f->tx_bits = (int*)malloc(nbit*sizeof(int));
|
|
f->rx_bits = (int*)malloc(nbit*sizeof(int));
|
|
|
|
nbit = fdmdv_bits_per_frame(f->fdmdv);
|
|
f->fdmdv_bits = (int*)malloc(nbit*sizeof(int));
|
|
|
|
if ((f->packed_codec_bits == NULL) || (f->codec_bits == NULL)
|
|
|| (f->tx_bits == NULL) || (f->rx_bits == NULL) || (f->fdmdv_bits == NULL))
|
|
return NULL;
|
|
|
|
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;
|
|
|
|
golay23_init();
|
|
f->total_bit_errors = 0;
|
|
|
|
f->nin = FDMDV_NOM_SAMPLES_PER_FRAME;
|
|
|
|
return f;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_close
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Frees up memory.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_close(struct freedv *freedv) {
|
|
free(freedv->packed_codec_bits);
|
|
free(freedv->codec_bits);
|
|
free(freedv->tx_bits);
|
|
fdmdv_destroy(freedv->fdmdv);
|
|
codec2_destroy(freedv->codec2);
|
|
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.
|
|
|
|
speech_in[] and mod_out[] are sampled at 8 kHz, 16 bit shorts, and
|
|
FREEDV_NSAMPLES long. The speech_in[] level should be such that the
|
|
peak speech level is between +/16384 and +/- 32767. mod_out[] will
|
|
be scaled such that the peak level is just less than +/-32767.
|
|
|
|
The FDM modem signal mod_out[] has a high crest factor (around
|
|
12dB), however the energy and duration of the peaks is small.
|
|
FreeDV is generally operated at a "backoff" of 6-8dB. Adjust the
|
|
power amplifier drive so that the average power is 6-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 20-25W.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void freedv_tx(struct freedv *f, short mod_out[], short speech_in[]) {
|
|
int bit, byte, i, j;
|
|
int bits_per_codec_frame, bits_per_fdmdv_frame;
|
|
int data, codeword1, data_flag_index;
|
|
COMP tx_fdm[2*FDMDV_NOM_SAMPLES_PER_FRAME];
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bits_per_fdmdv_frame = fdmdv_bits_per_frame(f->fdmdv);
|
|
|
|
codec2_encode(f->codec2, f->packed_codec_bits, speech_in);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
if (f->mode == FREEDV_MODE_1600) {
|
|
|
|
/* 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 */
|
|
|
|
//for(i=0; i<bits_per_codec_frame+12; i++)
|
|
// printf("%d\n", f->tx_bits[i]);
|
|
}
|
|
|
|
/* 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_fdmdv_frame], &f->tx_sync_bit);
|
|
assert(f->tx_sync_bit == 0);
|
|
|
|
for(i=0; i<2*FDMDV_NOM_SAMPLES_PER_FRAME; i++)
|
|
mod_out[i] = FDMDV_SCALE * tx_fdm[i].real;
|
|
|
|
assert(2*FDMDV_NOM_SAMPLES_PER_FRAME == FREEDV_NSAMPLES);
|
|
}
|
|
|
|
int freedv_nin(struct freedv *f) {
|
|
return f->nin;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: freedv_rx
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 3 August 2014
|
|
|
|
Takes a frame of samples from the radio receiver, demodulates them,
|
|
then decodes them, producing a frame of decoded speech samples.
|
|
|
|
Both demod_in[] and speech_out[] are 16 bit shorts sampled at 8 kHz.
|
|
The peak level of demod_in[] is not critical, as the demod works
|
|
well over a wide range of amplitude scaling. However it is best to
|
|
avoid clipping (overload, or samples pinned to +/- 32767). Suggest
|
|
scaling so the peak (modem signal plus noise) is between +/-16384
|
|
and +/-32767. speech_out[] will peak at just less than +/-32767.
|
|
|
|
To account for difference in the transmit and receive sample clock
|
|
frequencies, the number of demod_in[] samples is time varying. It
|
|
is the responsibility of the caller to supply the correct number of
|
|
samples. Call freedv_nin() before each call to freedv_rx() to
|
|
determine how many samples to pass to this function (see example).
|
|
|
|
Returns the number of output speech samples available in
|
|
speech_out[]. When in sync this will typically alternate between 0
|
|
and FREEDV_NSAMPLES. When out of sync, this will be f->nin.
|
|
|
|
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.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
int freedv_rx(struct freedv *f, short speech_out[], short demod_in[]) {
|
|
COMP rx_fdm[FDMDV_MAX_SAMPLES_PER_FRAME];
|
|
int bits_per_codec_frame, bytes_per_codec_frame, bits_per_fdmdv_frame;
|
|
int reliable_sync_bit, i, j, bit, byte, nin_prev, nout;
|
|
int recd_codeword, codeword1, data_flag_index, n_ascii;
|
|
short abit[1];
|
|
char ascii_out;
|
|
|
|
bits_per_codec_frame = codec2_bits_per_frame(f->codec2);
|
|
bytes_per_codec_frame = (bits_per_codec_frame + 7) / 8;
|
|
bits_per_fdmdv_frame = fdmdv_bits_per_frame(f->fdmdv);
|
|
|
|
for(i=0; i<f->nin; i++) {
|
|
rx_fdm[i].real = (float)demod_in[i]/FDMDV_SCALE;
|
|
rx_fdm[i].imag = 0;
|
|
}
|
|
|
|
nin_prev = f->nin;
|
|
fdmdv_demod(f->fdmdv, f->fdmdv_bits, &reliable_sync_bit, rx_fdm, &f->nin);
|
|
fdmdv_get_demod_stats(f->fdmdv, &f->fdmdv_stats);
|
|
f->reliable_sync_bit = reliable_sync_bit;
|
|
if (f->fdmdv_stats.sync) {
|
|
// printf("\033[97mIn sync. Pass demod_in to Codec, Codec to speech_out\n");
|
|
if (reliable_sync_bit == 0) {
|
|
memcpy(f->rx_bits, f->fdmdv_bits, bits_per_fdmdv_frame*sizeof(int));
|
|
nout = 0;
|
|
}
|
|
else {
|
|
memcpy(&f->rx_bits[bits_per_fdmdv_frame], f->fdmdv_bits, bits_per_fdmdv_frame*sizeof(int));
|
|
|
|
if (f->mode == FREEDV_MODE_1600) {
|
|
recd_codeword = 0;
|
|
for(i=0; i<8; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= f->rx_bits[i];
|
|
}
|
|
for(i=11; i<15; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= f->rx_bits[i];
|
|
}
|
|
for(i=bits_per_codec_frame; i<bits_per_codec_frame+11; i++) {
|
|
recd_codeword <<= 1;
|
|
recd_codeword |= f->rx_bits[i];
|
|
}
|
|
codeword1 = golay23_decode(recd_codeword);
|
|
f->total_bit_errors += golay23_count_errors(recd_codeword, codeword1);
|
|
|
|
//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++;
|
|
}
|
|
}
|
|
|
|
codec2_decode(f->codec2, speech_out, f->packed_codec_bits);
|
|
|
|
/* squelch if beneath SNR threshold */
|
|
|
|
if (f->fdmdv_stats.snr_est < f->snr_thresh) {
|
|
for(i=0; i<FREEDV_NSAMPLES; i++)
|
|
speech_out[i] = 0;
|
|
}
|
|
|
|
nout = FREEDV_NSAMPLES;
|
|
}
|
|
} /* if (sync) .... */
|
|
else {
|
|
// printf("\033[97mNot in sync. Pass demod_in to speech_out\n");
|
|
/* if not in sync pass through analog samples */
|
|
/* this lets us "hear" whats going on, e.g. during tuning */
|
|
for(i=0; i<nin_prev; i++)
|
|
speech_out[i] = demod_in[i];
|
|
nout = nin_prev;
|
|
}
|
|
|
|
return nout;
|
|
}
|
|
|
|
|