smartsdr-dsp/DSP_API/CODEC2_FREEDV/freedv_api.c
Graham Haddock 4efa220580 Merge branch 'BBB-dsp' of https://github.com/n5ac/smartsdr-dsp into BBB-dsp
Merging Graham's debug changes with Ed's improvements.
2015-01-30 11:26:13 -06:00

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;
}