Initial import of freebeacon daemon
authordrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 11 Dec 2015 06:07:50 +0000 (06:07 +0000)
committerdrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 11 Dec 2015 06:07:50 +0000 (06:07 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@2514 01035d8c-6547-0410-b346-abe4f91aad63

freebeacon/freebeacon.c [new file with mode: 0644]
freebeacon/freebeacon.txt [new file with mode: 0644]

diff --git a/freebeacon/freebeacon.c b/freebeacon/freebeacon.c
new file mode 100644 (file)
index 0000000..367e8f7
--- /dev/null
@@ -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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <samplerate.h>
+#include <getopt.h>
+
+#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; devn<numDevices; devn++) {
+        deviceInfo = Pa_GetDeviceInfo(devn);
+        if (deviceInfo == NULL) {
+            fprintf(stderr, "Couldn't open devNum: %d\n", devn);
+            return;
+        }
+        printf(" %2d %40s %8s %6d %6d %6d\n", 
+               devn, 
+               deviceInfo->name,
+               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<num_opts-1; i++) {
+               if(long_options[i].has_arg == no_argument) {
+                       option_parameters="";
+               } else if (strcmp("dev", long_options[i].name) == 0) {
+                       option_parameters = " Audio Device Number";
+                } else if (strcmp("trigger", long_options[i].name) == 0) {
+                       option_parameters = " text string used to trigger beacon";
+                } else if (strcmp("callsign", long_options[i].name) == 0) {
+                       option_parameters = " callsign to return on tx";
+                } else if (strcmp("txfilename", long_options[i].name) == 0) {
+                       option_parameters = " wavefile to use for source audio on tramsmit";
+                }
+               fprintf(stderr, "\t--%s%s\n", long_options[i].name, option_parameters);
+       }
+
+       exit(1);
+}
+
+
+/* text message callbacks */
+
+void callbackNextRxChar(void *callback_state, char c) {
+
+    /* if we hit end of buffer wrap around to start */
+
+    if ((ptxtMsg - txtMsg) < (MAX_CHAR-1))
+        *ptxtMsg++ = c;
+    else
+        ptxtMsg = txtMsg;
+
+    /* if end of string let see if we have a match for the trigger
+       string.  Note tx may send trigger string many times.  We only
+       need to receive it once to trigger a beacon tx cycle. */
+
+    if (c == 13) {
+        *ptxtMsg++ = c;
+        *ptxtMsg = 0;
+         ptxtMsg = txtMsg;
+         if (strstr(txtMsg, triggerString) != NULL)
+             triggered = 1;
+    } 
+}
+
+char callbackNextTxChar(void *callback_state) {
+    if ((*ptxtMsg == 0) || ((ptxtMsg - txtMsg) >= 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<N48; j++)
+                    in48k_short[j] = stereo_short[2*j]; /* left channel only */
+            }
+            else {
+                for(j=0; j<N48; j++)
+                    in48k_short[j] = stereo_short[j]; 
+            }
+            int n8k = resample(rxsrc, in8k_short, in48k_short, FS, SAMPLE_RATE, N48, N48);
+            fifo_write(fifo, in8k_short, n8k);
+
+            /* demodulate to decoded speech samples */
+
+            nin = freedv_nin(f);
+            if (fifo_read(fifo, demod_in, nin) == nin) {
+                freedv_rx(f, speech_out, demod_in);
+                freedv_get_modem_stats(f, &sync, &snr_est);
+             }
+        }
+
+        if (state == STX) {
+            short mod_out[freedv_get_n_max_modem_samples(f)];
+            short speech_in[freedv_get_n_speech_samples(f)];
+
+            /* TODO: assert PTT, e.g. via RS232 */
+            
+            /* resample sound file as can't guarantee 8KHz sample rate */
+
+            unsigned int nsf = freedv_get_n_speech_samples(f)*sfFs/FS;
+            short        insf_short[nsf];
+            unsigned int n = sf_read_short(sfPlayFile, insf_short, nsf);
+            n8k = resample(playsrc, speech_in, insf_short, SAMPLE_RATE, sfFs, freedv_get_n_speech_samples(f), nsf);
+            
+            if (n != nsf) {
+                /* end of file - this signals state machine we've finished */
+                sf_close(sfPlayFile);
+                sfPlayFile = NULL;
+            }
+
+            freedv_tx(f, mod_out, speech_in);
+
+            int n48k = resample(txsrc, out48k_short, mod_out, SAMPLE_RATE, FS, N48, freedv_get_n_nom_modem_samples(f));
+            for(j=0; j<n48k; j++) {
+                if (outputChannels == 2) {
+                    stereo_short[2*j] = out48k_short[j];   // left channel
+                    stereo_short[2*j+1] = out48k_short[j]; // right channel
+                }
+                else {
+                    stereo_short[j] = out48k_short[j];     // mono
+                }
+            }
+
+            Pa_WriteStream(stream, stereo_short, n48k);
+        }
+
+        /* state machine processing */
+
+        switch(state) {
+        case SRX_IDLE:
+            if (sync) {
+                next_state = SRX_SYNC;
+                *txtMsg = 0;
+                ptxtMsg = txtMsg;
+                triggered = 0;
+                freedv_set_total_bit_errors(f, 0);
+                freedv_set_total_bits(f, 0);
+            }
+            break;
+        case SRX_SYNC:
+            if (!sync) {
+                if (triggered) {
+                    float ber = (float)freedv_get_total_bit_errors(f)/freedv_get_total_bits(f);
+                    char tmpStr[MAX_CHAR];
+
+                    sprintf(tmpStr, "SNR: %3.1f BER: %3.2f de %s\n",
+                            snr_est, ber, callsign);
+                    strcpy(txtMsg, tmpStr);
+                    ptxtMsg = txtMsg;
+                    sfPlayFile = openPlayFile(txFileName, &sfFs);
+
+                    next_state = STX;
+                }
+            }
+            else
+                next_state = SRX_IDLE;
+            break;
+        case STX:
+            if (sfPlayFile == NULL)
+                next_state = SRX_IDLE;
+            break;
+        }
+
+        if (next_state != state) 
+            fprintf(stderr, "state: %s next_state %s\n", state_str[state], state_str[next_state]);
+        state = next_state;
+    }
+
+    /* Attempt to shut down gracefully */
+
+    err = Pa_StopStream(stream);
+    if (err != paNoError) {
+        fprintf(stderr, "Couldn't stop sound device\n");       
+        exit(1);
+    }
+    Pa_CloseStream(stream);
+    Pa_Terminate();
+    fifo_destroy(fifo);
+    src_delete(rxsrc);
+    src_delete(txsrc);
+    src_delete(playsrc);
+    freedv_close(f);
+}
diff --git a/freebeacon/freebeacon.txt b/freebeacon/freebeacon.txt
new file mode 100644 (file)
index 0000000..a042d46
--- /dev/null
@@ -0,0 +1,36 @@
+- freebeacon runs as a linux daemon
+- configured via command line at start up
+
+Beacon:
+
+- text string "XXX" entered on command line at start up
+- when we get valid sync, start parsing rx text
+- on rx of "XXX", trigger tx event.
+- tx starts after sync is lost for 10 seconds
+- send canned, encoded speech, echoing received txt string
+- then back to listen
+
+Logging files:
+
+- log dir entered on command line.  This could be a web server directory,
+  then logged files will automatically appear on the web
+- maximum nuber of audio log files entered on command line
+- when we get a valid sync and txt string "XXX"
+  - log rx audio to wave file in log directory
+  - log decoded audio to wave file in log directory
+- stop writing wave files when sync lost for 10s or after 60 seconds
+- use rotating file names 0 ... 9 so we re-use storage
+- optional: fire off event to another process, perhaps via a UDP port,
+  this could be used to add text to a web page, (e.g. txt string)
+
+Task List
+---------
+
+[ ] daemonise
+[ ] command line parsing for 
+    [ ] log dir and number of log files
+    [ ] sound devices
+[ ] state machine
+[ ] work out how to play to sound devices, maybe portaudio?
+[ ] code to key tx via rs232
+