From 94ae32ce002e93eebbda1a7c05d4666868754cab Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 11 Dec 2015 06:07:50 +0000 Subject: [PATCH] Initial import of freebeacon daemon git-svn-id: https://svn.code.sf.net/p/freetel/code@2514 01035d8c-6547-0410-b346-abe4f91aad63 --- freebeacon/freebeacon.c | 502 ++++++++++++++++++++++++++++++++++++++ freebeacon/freebeacon.txt | 36 +++ 2 files changed, 538 insertions(+) create mode 100644 freebeacon/freebeacon.c create mode 100644 freebeacon/freebeacon.txt diff --git a/freebeacon/freebeacon.c b/freebeacon/freebeacon.c new file mode 100644 index 00000000..367e8f7e --- /dev/null +++ b/freebeacon/freebeacon.c @@ -0,0 +1,502 @@ +/* + freebeacon.c + David Rowe + Created Dec 2015 + + FreeDV beacon daemon. Listens for freedv signals, then transmits a + reply. Saves received signals as a rotating log of wave files so + they can be published on a web site. + + If stereo audio device we use left channel only. + + A whole lot of code was lifted from freedv-dev for this program. + + TODO: + + [X] 48 to 8 kHz sample rate conversion + [X] Port Audio list devices + [X] command line processing framework + [ ] alsa loop to test with wave files + [X] beacon state machine + [ ] rotating log + [ ] RS232 tx code + [ ] writing to wave files + [ ] basic SM1000 version + + has audio interfaces + + Building: + + gcc freebeacon.c -o freebeacon -lsamplerate -lportaudio -lsndfile -I/home/david/codec2-dev/src /home/david/codec2-dev/build_linux/src/libcodec2.so + + Running: + LD_LIBRARY_PATH=/home/david/codec2-dev/build_linux/src/ ./freebeacon + + (note I really should "make install" Codec 2 and have a proper cmake file) +*/ + +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" +#include "portaudio.h" + +#include "codec2_fifo.h" +#include "modem_stats.h" +#include "freedv_api.h" + +#define MAX_CHAR 80 +#define FS 8000 // 8 kHz sampling rate used for modem +#define SAMPLE_RATE 48000 // 48 kHz sampling rate rec. as we can trust accuracy of sound card +#define N8 160 // processing buffer size at 8 kHz +#define N48 (N8*SAMPLE_RATE/FS) // processing buffer size at 48 kHz + +/* globals used to communicate with async events */ + +volatile int keepRunning; +char txtMsg[MAX_CHAR], *ptxtMsg, triggerString[MAX_CHAR]; +int triggered; + +/* state machine defines */ + +#define SRX_IDLE 0 /* listening but no FreeDV signal */ +#define SRX_SYNC 1 /* We have sync on a valid FreeDV signal */ +#define SRX_TRIGGERED 2 /* the magic trigger text string has been received, but still in RX */ +#define STX 3 /* transmitting reply */ + +char *state_str[] = { + "Rx Idle", + "Rx Sync", + "Rx Triggered", + "Tx" +}; + + +/* Called on Ctrl-C */ + +void intHandler(int dummy) { + keepRunning = 0; +} + +/* returns number of output samples generated by resampling */ + +int resample(SRC_STATE *src, + short output_short[], + short input_short[], + int output_sample_rate, + int input_sample_rate, + int length_output_short, // maximum output array length in samples + int length_input_short + ) +{ + SRC_DATA src_data; + float input[N48*4]; + float output[N48*4]; + int ret; + + assert(src != NULL); + assert(length_input_short <= N48*4); + assert(length_output_short <= N48*4); + + src_short_to_float_array(input_short, input, length_input_short); + + src_data.data_in = input; + src_data.data_out = output; + src_data.input_frames = length_input_short; + src_data.output_frames = length_output_short; + src_data.end_of_input = 0; + src_data.src_ratio = (float)output_sample_rate/input_sample_rate; + + ret = src_process(src, &src_data); + assert(ret == 0); + + assert(src_data.output_frames_gen <= length_output_short); + src_float_to_short_array(output, output_short, src_data.output_frames_gen); + + return src_data.output_frames_gen; +} + + +void listAudioDevices(void) { + const PaDeviceInfo *deviceInfo = NULL; + PaError err; + int numDevices, devn; + + numDevices = Pa_GetDeviceCount(); + printf("Num Name API InCh OutCh DefFs\n"); + printf("==========================================================================\n"); + for (devn = 0; devnname, + Pa_GetHostApiInfo(deviceInfo->hostApi)->name, + deviceInfo->maxInputChannels, + deviceInfo->maxOutputChannels, + (int)deviceInfo->defaultSampleRate); + } +} + + +void printHelp(const struct option* long_options, int num_opts, char* argv[]) +{ + int i; + char *option_parameters; + + fprintf(stderr, "\nFreeBeacon - FreeDV Beacon\n" + "usage: %s [OPTIONS]\n\n" + "Options:\n" + "\t-l list audio devices\n", argv[0]); + for(i=0; i= MAX_CHAR)) + ptxtMsg == txtMsg; + + return *ptxtMsg++; +} + + +SNDFILE *openPlayFile(char fileName[], int *sfFs) +{ + SF_INFO sfInfo; + SNDFILE *sfPlayFile; + + sfInfo.format = 0; + + sfPlayFile = sf_open(fileName, SFM_READ, &sfInfo); + if(sfPlayFile == NULL) { + const char *strErr = sf_strerror(NULL); + fprintf(stderr, " %s couldn't open: %s", strErr, fileName); + } + *sfFs = sfInfo.samplerate; + + return sfPlayFile; +} + + +int main(int argc, char *argv[]) { + struct freedv *f; + PaStreamParameters inputParameters; + const PaDeviceInfo *deviceInfo = NULL; + PaStream *stream = NULL; + PaError err; + short stereo_short[2*N48]; + short in48k_short[N48], out48k_short[N48]; + short in8k_short[N48]; + int numDevices, nBufs, n8k, i, j, src_error, inputChannels, nin, devNum; + int outputChannels; + int state, next_state; + SRC_STATE *rxsrc, *txsrc; + SRC_STATE *playsrc; + struct FIFO *fifo; + char txFileName[MAX_CHAR]; + SNDFILE *sfPlayFile; + int sfFs; + int triggerf, txfilenamef, callsignf; + int sync; + float snr_est; + char callsign[MAX_CHAR]; + + /* Defaults -------------------------------------------------------------------------------*/ + + devNum = 0; + sprintf(triggerString, "FreeBeacon"); + sprintf(txFileName, "txaudio.wav"); + sprintf(callsign, "FreeBeacon"); + + /* Process command line options -----------------------------------------------------------*/ + + char* opt_string = "hl:"; + struct option long_options[] = { + { "dev", required_argument, &devNum, 1 }, + { "trigger", required_argument, &triggerf, 1 }, + { "txfilename", required_argument, &txfilenamef, 1 }, + { "callsign", required_argument, &callsignf, 1 }, + { NULL, no_argument, NULL, 0 } + }; + int num_opts=sizeof(long_options)/sizeof(struct option); + + if (argc < 2) { + printHelp(long_options, num_opts, argv); + } + + while(1) { + int option_index = 0; + int opt = getopt_long(argc, argv, opt_string, + long_options, &option_index); + if (opt == -1) + break; + + switch (opt) { + case 0: + if (strcmp(long_options[option_index].name, "dev") == 0) { + devNum = atoi(optarg); + } else if(strcmp(long_options[option_index].name, "trigger") == 0) { + strcpy(triggerString, optarg); + } else if(strcmp(long_options[option_index].name, "txfilename") == 0) { + strcpy(txFileName, optarg); + } else if(strcmp(long_options[option_index].name, "callsign") == 0) { + strcpy(callsign, optarg); + } + break; + + case 'h': + printHelp(long_options, num_opts, argv); + break; + + case 'l': + listAudioDevices(); + exit(0); + break; + + default: + /* This will never be reached */ + break; + } + } + + + /* Open Sound Device and start processing --------------------------------------------------------------*/ + + if (Pa_Initialize()) { + fprintf(stderr, "Port Audio failed to initialize"); + exit(1); + } + + f = freedv_open(FREEDV_MODE_1600); assert(f != NULL); + assert(freedv_get_modem_sample_rate(f) == FS); /* just in case modem FS every changes */ + freedv_set_callback_txt(f, callbackNextRxChar, callbackNextTxChar, NULL); + + fifo = fifo_create(N48); assert(fifo != NULL); + rxsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(rxsrc != NULL); + txsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(txsrc != NULL); + playsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(playsrc != NULL); + + /* work out how many input channels this device supports */ + + deviceInfo = Pa_GetDeviceInfo(devNum); + if (deviceInfo == NULL) { + fprintf(stderr, "Couldn't get device info from Port Audio for device: %d\n", devNum); + exit(1); + } + if (deviceInfo->maxInputChannels == 1) + inputChannels = 1; + else + inputChannels = 2; + + /* open device */ + + inputParameters.device = devNum; + inputParameters.channelCount = inputChannels; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + fprintf(stderr, "Ctrl-C to exit\n"); + fprintf(stderr, "trigger string: %s txFileName: %s\n", triggerString, txFileName); + fprintf(stderr, "Starting PortAudio on devNum: %d\n", devNum); + + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, + SAMPLE_RATE, + 0, /* let the driver decide */ + paClipOff, + NULL, /* no callback, use blocking API */ + NULL ); + + if (err != paNoError) { + fprintf(stderr, "Couldn't initialise sound device\n"); + exit(1); + } + + err = Pa_StartStream(stream); + if (err != paNoError) { + fprintf(stderr, "Couldn't start sound device\n"); + return; + } + + signal(SIGINT, intHandler); /* ctrl-C to exit gracefully */ + + /* init for main loop */ + + state = SRX_IDLE; + keepRunning = 1; + *txtMsg = 0; + ptxtMsg = txtMsg; + triggered = 0; + + while(keepRunning) { + + next_state = state; + + if ((state == SRX_IDLE) || (state == SRX_SYNC)) { + short demod_in[freedv_get_n_max_modem_samples(f)]; + short speech_out[freedv_get_n_speech_samples(f)]; + + /* Read samples, resample to modem sample rate */ + + Pa_ReadStream(stream, stereo_short, N48); + + if (inputChannels == 2) { + for(j=0; j