git-svn-id: https://svn.code.sf.net/p/freetel/code@639 01035d8c-6547-0410-b346-abe4f9...
authorwittend99 <wittend99@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 24 Aug 2012 19:01:17 +0000 (19:01 +0000)
committerwittend99 <wittend99@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 24 Aug 2012 19:01:17 +0000 (19:01 +0000)
fdmdv2/build/fdmdv2_copyright_block.txt [new file with mode: 0644]
fdmdv2/src/comp.h [new file with mode: 0644]
fdmdv2/src/fdmdv2_process_audio.cpp [new file with mode: 0644]

diff --git a/fdmdv2/build/fdmdv2_copyright_block.txt b/fdmdv2/build/fdmdv2_copyright_block.txt
new file mode 100644 (file)
index 0000000..785ec41
--- /dev/null
@@ -0,0 +1,24 @@
+//==========================================================================\r
+// Name:\r
+// Purpose:\r
+// Created:         June 22, 2012
+// Initial author:  David Witten\r
+// Derived from:\r
+// License:\r
+//\r
+//  Copyright (C) 2012 David Witten
+//
+//  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/>.
+//
+//==========================================================================\r
diff --git a/fdmdv2/src/comp.h b/fdmdv2/src/comp.h
new file mode 100644 (file)
index 0000000..a3a1bd9
--- /dev/null
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------*\
+
+  FILE........: comp.h
+  AUTHOR......: David Rowe
+  DATE CREATED: 24/08/09
+
+  Complex number definition.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+  Copyright (C) 2009 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/>.
+*/
+
+#ifndef __COMP__
+#define __COMP__
+
+/* Complex number */
+
+typedef struct
+{
+    float real;
+    float imag;
+} COMP;
+
+#endif
diff --git a/fdmdv2/src/fdmdv2_process_audio.cpp b/fdmdv2/src/fdmdv2_process_audio.cpp
new file mode 100644 (file)
index 0000000..99dfcbc
--- /dev/null
@@ -0,0 +1,477 @@
+//==========================================================================\r
+// Name:            fdmdv2_process_audio.cpp\r
+// Purpose:         Implements processing of data received from audio interfaces.\r
+// Created:         August 12, 2012
+// Initial author:  David Rowe\r
+// Derived from:    code trivially converted for integration with C++ code
+//                  by Dave Witten\r
+// License:\r
+//\r
+//  Copyright (C) 2012 David Witten
+//
+//  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/>.
+//
+//==========================================================================\r
+/*
+
+  FUNCTION: per_frame_rx_processing()
+  AUTHOR..: David Rowe
+  DATE....: July 2012
+
+  Called every rx frame to take a buffer of input modem samples and
+  convert them to a buffer of output speech samples.
+
+  The sample source could be a sound card or file.  The sample source
+  supplies a fixed number of samples with each call.  However
+  fdmdv_demod requires a variable number of samples for each call.
+  This function will buffer as appropriate and call fdmdv_demod with
+  the correct number of samples.
+
+  The processing sequence is:
+
+  collect demod input samples from sound card 1 A/D
+  while we have enough samples:
+        demod samples into bits
+        decode bits into speech samples
+        output a buffer of speech samples to sound card 2 D/A
+
+  Note that sound card 1 and sound card 2 will have slightly different
+  sample rates, as their sample clocks are not syncronised.  We
+  effectively lock the system to the demod A/D (sound card 1) sample
+  rate. This ensures the demod gets a continuous sequence of samples,
+  maintaining sync. Sample underflow or overflow will instead occur on
+  the sound card 2 D/A.  This is acceptable as a buffer of lost or
+  extra speech samples is unlikely to be noticed.
+
+  The situation is actually a little more complex than that.  Through
+  the demod timing estimation the buffers supplied to sound card D/A 2
+  are effectively clocked at the remote modulator sound card D/A clock
+  rate.  We slip/gain buffers supplied to sound card 2 to compensate.
+
+  The current demod handles varying clock rates by having a variable
+  number of input samples, e.g. 120 160 (nominal) or 200.  However the
+  A/D always delivers a fixed number of samples.
+
+  So we currently need some logic between the A/D and the demod:
+        + A/D delivers fixed number of samples
+        + demod processes a variable number of samples
+        + this means we run demod 0,1 or 2 times, depending
+            on number of buffered A/D samples
+        + demod always outputs 1 frame of bits
+        + so run demod and speech decoder 0, 1 or 2 times
+
+  The ouput of the demod is codec voice data so it's OK if we miss or
+  repeat a frame every now and again.
+
+*/
+#include "fdmdv2_main.h"\r
+#include "portaudio.h"
+
+//#include "fdmdv.h"
+#include "codec2.h"
+
+//-- Globals: Eliminate!! --------------------------------
+char         *fin_name = NULL;
+char         *fout_name = NULL;
+char         *sound_dev_name = NULL;
+FILE         *fin = NULL;
+FILE         *fout = NULL;
+struct FDMDV *fdmdv;
+struct CODEC2 *codec2;
+//float         av_mag[FDMDV_NSPEC];                  // shared between a few classes
+extern float *av_mag;\r
+//--------------------------------------------------------
+
+void new_data(float *);
+float  Ts = 0.0;
+short  input_buf[2*FDMDV_NOM_SAMPLES_PER_FRAME];
+int    n_input_buf = 0;
+int    nin = FDMDV_NOM_SAMPLES_PER_FRAME;
+short *output_buf;
+int    n_output_buf = 0;
+int    codec_bits[2*FDMDV_BITS_PER_FRAME];
+int    state = 0;
+
+// Portaudio states -----------------------------
+
+PaStream *stream = NULL;
+PaError err;
+
+typedef struct
+{
+    float               in48k[FDMDV_OS_TAPS + N48];
+    float               in8k[MEM8 + N8];
+} paCallBackData;
+
+//----------------------------------------------------------------
+//
+//----------------------------------------------------------------
+void per_frame_rx_processing(
+                                short  output_buf[], /* output buf of decoded speech samples          */
+                                int   *n_output_buf, /* how many samples currently in output_buf[]    */
+                                int    codec_bits[], /* current frame of bits for decoder             */
+                                short  input_buf[],  /* input buf of modem samples input to demod     */
+                                int   *n_input_buf,  /* how many samples currently in input_buf[]     */
+                                int   *nin,          /* amount of samples demod needs for next call   */
+                                int   *state,        /* used to collect codec_bits[] halves           */
+                                struct CODEC2 *c2    /* Codec 2 states                                */
+                            )
+{
+    struct FDMDV_STATS stats;
+    int    sync_bit;
+    float  rx_fdm[FDMDV_MAX_SAMPLES_PER_FRAME];
+    int    rx_bits[FDMDV_BITS_PER_FRAME];
+    unsigned char  packed_bits[BYTES_PER_CODEC_FRAME];
+    float  rx_spec[FDMDV_NSPEC];
+    int    i, nin_prev, bit, byte;
+    int    next_state;
+
+    assert(*n_input_buf <= (2 * FDMDV_NOM_SAMPLES_PER_FRAME));
+
+    /*
+      This while loop will run the demod 0, 1 (nominal) or 2 times:
+
+      0: when tx sample clock runs faster than rx, occasionally we
+         will run out of samples
+
+      1: normal, run decoder once, every 2nd frame output a frame of
+         speech samples to D/A
+
+      2: when tx sample clock runs slower than rx, occasionally we will
+         have enough samples to run demod twice.
+
+      With a +/- 10 Hz sample clock difference at FS=8000Hz (+/- 1250
+      ppm), case 0 or 1 occured about once every 30 seconds.  This is
+      no problem for the decoded audio.
+    */
+    while(*n_input_buf >= *nin)
+    {
+        // demod per frame processing
+        for(i=0; i<*nin; i++)
+        {
+            rx_fdm[i] = (float)input_buf[i]/FDMDV_SCALE;
+        }
+        nin_prev = *nin;
+        fdmdv_demod(fdmdv, rx_bits, &sync_bit, rx_fdm, nin);
+        *n_input_buf -= nin_prev;
+        assert(*n_input_buf >= 0);
+
+        // shift input buffer
+        for(i=0; i<*n_input_buf; i++)
+        {
+            input_buf[i] = input_buf[i+nin_prev];
+        }
+
+        // compute rx spectrum & get demod stats, and update GUI plot data
+        fdmdv_get_rx_spectrum(fdmdv, rx_spec, rx_fdm, nin_prev);
+        fdmdv_get_demod_stats(fdmdv, &stats);
+        new_data(rx_spec);
+//        aScatter->add_new_samples(stats.rx_symbols);
+//        aTimingEst->add_new_sample(stats.rx_timing);
+//        aFreqEst->add_new_sample(stats.foff);
+//        aSNR->add_new_sample(stats.snr_est);
+        /*
+           State machine to:
+
+           + Mute decoded audio when out of sync.  The demod is synced
+             when we are using the fine freq estimate and SNR is above
+             a thresh.
+
+           + Decode codec bits only if we have a 0,1 sync bit
+             sequence.  Collects two frames of demod bits to decode
+             one frame of codec bits.
+        */
+        next_state = *state;
+        switch(*state)
+        {
+            case 0:
+                // mute output audio when out of sync
+                if(*n_output_buf < 2*codec2_samples_per_frame(c2) - N8)
+                {
+                    for(i=0; i<N8; i++)
+                        output_buf[*n_output_buf + i] = 0;
+
+                    *n_output_buf += N8;
+                }
+                assert(*n_output_buf <= (2*codec2_samples_per_frame(c2)));
+                if((stats.fest_coarse_fine == 1) && (stats.snr_est > 3.0))
+                {
+                    next_state = 1;
+                }
+                break;
+
+            case 1:
+                if(sync_bit == 0)
+                {
+                    next_state = 2;
+                    // first half of frame of codec bits
+                    memcpy(codec_bits, rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
+                }
+                else
+                {
+                    next_state = 1;
+                }
+                if(stats.fest_coarse_fine == 0)
+                {
+                    next_state = 0;
+                }
+                break;
+
+            case 2:
+                next_state = 1;
+                if(stats.fest_coarse_fine == 0)
+                {
+                    next_state = 0;
+                }
+                if(sync_bit == 1)
+                {
+                    // second half of frame of codec bits
+                    memcpy(&codec_bits[FDMDV_BITS_PER_FRAME], rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
+                    // pack bits, MSB received first
+                    bit = 7;
+                    byte = 0;
+                    memset(packed_bits, 0, BYTES_PER_CODEC_FRAME);
+                    for(i=0; i<BITS_PER_CODEC_FRAME; i++)
+                    {
+                        packed_bits[byte] |= (codec_bits[i] << bit);
+                        bit--;
+                        if(bit < 0)
+                        {
+                            bit = 7;
+                            byte++;
+                        }
+                    }
+                    assert(byte == BYTES_PER_CODEC_FRAME);
+                    // add decoded speech to end of output buffer
+                    if(*n_output_buf <= codec2_samples_per_frame(c2))
+                    {
+                        codec2_decode(c2, &output_buf[*n_output_buf], packed_bits);
+                        *n_output_buf += codec2_samples_per_frame(c2);
+                    }
+                    assert(*n_output_buf <= (2*codec2_samples_per_frame(c2)));
+                }
+                break;
+        }
+        *state = next_state;
+    }
+}
+
+//----------------------------------------------------------------
+// update average of each spectrum point
+//----------------------------------------------------------------
+void new_data(float mag_dB[])
+{
+    int i;
+
+    for(i=0; i<FDMDV_NSPEC; i++)
+    {
+        av_mag[i] = (1.0 - BETA)*av_mag[i] + BETA*mag_dB[i];
+    }
+}
+
+//----------------------------------------------------------------
+// Redraw windows every DT seconds.
+//----------------------------------------------------------------
+void update_gui(int nin, float *Ts)
+{
+    *Ts += (float)nin/FS;
+    *Ts += (float)nin/FS;
+    if(*Ts >= DT)
+    {
+/*
+        *Ts -= DT;
+        if(!zoomSpectrumWindow->shown() && !zoomWaterfallWindow->shown())
+        {
+            aSpectrum->redraw();
+            aWaterfall->redraw();
+            aScatter->redraw();
+            aTimingEst->redraw();
+            aFreqEst->redraw();
+            aSNR->redraw();
+        }
+        if(zoomSpectrumWindow->shown())
+        {
+            aZoomedSpectrum->redraw();
+        }
+        if(zoomWaterfallWindow->shown())
+        {
+            aZoomedWaterfall->redraw();
+        }
+*/
+    }
+}
+
+//----------------------------------------------------------------
+//  idle() is the FLTK function that gets continusouly called when FLTK
+//  is not doing GUI work.  We use this function for providing file
+//  input to update the GUI when simulating real time operation.
+//----------------------------------------------------------------
+void idle(void*)
+{
+/*
+    int ret, i;
+    if(fin_name != NULL)
+    {
+        ret = fread(&input_buf[n_input_buf], sizeof(short), FDMDV_NOM_SAMPLES_PER_FRAME, fin);
+        n_input_buf += FDMDV_NOM_SAMPLES_PER_FRAME;
+        per_frame_rx_processing(output_buf, &n_output_buf, codec_bits, input_buf, &n_input_buf, &nin, &state, codec2);
+        if(fout_name != NULL)
+        {
+            if(n_output_buf >= N8)
+            {
+                ret = fwrite(output_buf, sizeof(short), N8, fout);
+                n_output_buf -= N8;
+                assert(n_output_buf >= 0);
+                // shift speech sample output buffer
+                for(i=0; i<n_output_buf; i++)
+                {
+                    output_buf[i] = output_buf[i+N8];
+                }
+            }
+        }
+    }
+    update_gui(nin, &Ts);
+    // simulate time delay from real world A/D input, and pause betwen
+    // screen updates
+    usleep(20000);
+*/
+}
+
+//----------------------------------------------------------------
+// This routine will be called by the PortAudio engine when audio
+//  is available.
+//----------------------------------------------------------------
+static int callback(const void *inputBuffer, void *outputBuffer,
+                    unsigned long framesPerBuffer,
+                    const PaStreamCallbackTimeInfo* timeInfo,
+                    PaStreamCallbackFlags statusFlags,
+                    void *userData)
+{
+    paCallBackData *cbData = (paCallBackData*)userData;
+    unsigned int    i;
+    short           *rptr = (short*)inputBuffer;
+    short           *wptr = (short*)outputBuffer;
+    float           *in8k = cbData->in8k;
+    float           *in48k = cbData->in48k;
+    float           out8k[N8];
+    float           out48k[N48];
+    short           out48k_short[N48];
+
+    (void) timeInfo;
+    (void) statusFlags;
+
+    assert(inputBuffer != NULL);
+    // Convert input model samples from 48 to 8 kHz
+    // just use left channel
+    for(i = 0; i < framesPerBuffer; i++, rptr += 2)
+    {
+        in48k[i + FDMDV_OS_TAPS] = *rptr;
+    }
+    // downsample and update filter memory
+    fdmdv_48_to_8(out8k, &in48k[FDMDV_OS_TAPS], N8);
+    for(i = 0; i < FDMDV_OS_TAPS; i++)
+    {
+        in48k[i] = in48k[i+framesPerBuffer];
+    }
+    // run demod, decoder and update GUI info
+    for(i=0; i<N8; i++)
+    {
+        input_buf[n_input_buf + i] = (short)out8k[i];
+    }
+    n_input_buf += FDMDV_NOM_SAMPLES_PER_FRAME;
+    per_frame_rx_processing(output_buf, &n_output_buf, codec_bits, input_buf, &n_input_buf, &nin, &state, codec2);
+
+    // if demod out of sync copy input audio from A/D to aid in tuning
+    if(n_output_buf >= N8)
+    {
+        if(state == 0)
+        {
+            for(i=0; i<N8; i++)
+            {
+                in8k[MEM8 + i] = out8k[i];       // A/D signal
+            }
+        }
+        else
+        {
+            for(i=0; i < N8; i++)
+            {
+                in8k[MEM8+i] = output_buf[i];   // decoded speech
+            }
+        }
+        n_output_buf -= N8;
+    }
+    assert(n_output_buf >= 0);
+    // shift speech samples in output buffer
+    for(i = 0; i < (unsigned int)n_output_buf; i++)
+    {
+        output_buf[i] = output_buf[i+N8];
+    }
+    // Convert output speech to 48 kHz sample rate
+    // upsample and update filter memory
+    fdmdv_8_to_48(out48k, &in8k[MEM8], N8);
+    for(i = 0; i < MEM8; i++)
+    {
+        in8k[i] = in8k[i+N8];
+    }
+    assert(outputBuffer != NULL);
+    /* write signal to both channels */
+    for(i = 0; i < N48; i++)
+    {
+        out48k_short[i] = (short)out48k[i];
+    }
+    for(i=0; i<framesPerBuffer; i++, wptr+=2)
+    {
+        wptr[0] = out48k_short[i];
+        wptr[1] = out48k_short[i];
+    }
+    return paContinue;
+}
+
+//----------------------------------------------------------------
+// arg_callback()
+//----------------------------------------------------------------
+int arg_callback(int argc, char **argv, int &i)
+{
+    if(argv[i][1] == 'i')
+    {
+        if((i+1) >= argc)
+        {
+            return 0;
+        }
+        fin_name = argv[i+1];
+        i += 2;
+        return 2;
+    }
+    if(argv[i][1] == 'o')
+    {
+        if((i+1) >= argc)
+        {
+            return 0;
+        }
+        fout_name = argv[i+1];
+        i += 2;
+        return 2;
+    }
+    if(argv[i][1] == 's')
+    {
+        if((i+1) >= argc)
+        {
+            return 0;
+        }
+        sound_dev_name = argv[i+1];
+        i += 2;
+        return 2;
+    }
+    return 0;
+}