1 /*---------------------------------------------------------------------------*\
2 
3   FILE........: freedv_rx.c
4   AUTHOR......: David Rowe
5   DATE CREATED: August 2014
6 
7   Demo/development receive program for FreeDV API functions:
8 
9   Example usage (all one line):
10 
11     $ cd codec2/build_linux/src
12     $ ./freedv_tx 1600 ../../raw/ve9qrp_10s.raw - | ./freedv_rx 1600 - - | aplay -f S16
13 
14 \*---------------------------------------------------------------------------*/
15 
16 /*
17   Copyright (C) 2014 David Rowe
18 
19   All rights reserved.
20 
21   This program is free software; you can redistribute it and/or modify
22   it under the terms of the GNU Lesser General Public License version 2.1, as
23   published by the Free Software Foundation.  This program is
24   distributed in the hope that it will be useful, but WITHOUT ANY
25   WARRANTY; without even the implied warranty of MERCHANTABILITY or
26   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
27   License for more details.
28 
29   You should have received a copy of the GNU Lesser General Public License
30   along with this program; if not, see <http://www.gnu.org/licenses/>.
31 */
32 
33 #include <assert.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <errno.h>
38 
39 #include "reliable_text.h"
40 #include "freedv_api.h"
41 #include "modem_stats.h"
42 
43 #define NDISCARD 5                /* BER measure optionally discards first few frames after sync */
44 
45 /* optioal call-back function for received txt characters */
my_put_next_rx_char(void * states,char c)46 void my_put_next_rx_char(void *states, char c) { fprintf((FILE*)states, "%c", c); }
47 
48 static FILE* reliable_tx_fp;
49 reliable_text_t reliable_text_obj;
50 
on_reliable_text_rx(reliable_text_t rt,const char * txt_ptr,int length,void * state)51 void on_reliable_text_rx(reliable_text_t rt, const char* txt_ptr, int length, void* state)
52 {
53     fprintf(reliable_tx_fp, "%s\n", txt_ptr);
54     reliable_text_reset(reliable_text_obj);
55 }
56 
main(int argc,char * argv[])57 int main(int argc, char *argv[]) {
58     FILE                      *fin, *fout, *ftxt_rx = NULL;
59     int                        nin, nout, nout_total = 0, frame = 0;
60     struct MODEM_STATS         stats = {0};
61     int                        mode;
62     int                        sync;
63     float                      snr_est;
64     float                      clock_offset;
65     int                        use_testframes, verbose, discard, use_complex, use_dpsk, use_reliabletext;
66     int                        use_squelch, highpassthroughgain;
67     float                      squelch = 0;
68     int                        i;
69     struct freedv             *freedv;
70 
71     if (argc < 4) {
72         char f2020[80] = {0};
73         #ifdef __LPCNET__
74         sprintf(f2020,"|2020");
75         #endif
76 	printf("usage: %s 1600|700C|700D|700E|2400A|2400B|800XA%s InputModemSpeechFile OutputSpeechRawFile\n"
77                " [--testframes] [-v] [--discard] [--usecomplex] [--dpsk] [--squelch leveldB] [--txtrx filename] [--reliabletext]\n"
78 	       " [--highpassthroughgain]\n", argv[0],f2020);
79 	printf("e.g    %s 1600 hts1a_fdmdv.raw hts1a_out.raw\n", argv[0]);
80 	exit(1);
81     }
82 
83     mode = -1;
84     if (!strcmp(argv[1],"1600")) mode = FREEDV_MODE_1600;
85     if (!strcmp(argv[1],"700C")) mode = FREEDV_MODE_700C;
86     if (!strcmp(argv[1],"700D")) mode = FREEDV_MODE_700D;
87     if (!strcmp(argv[1],"700E")) mode = FREEDV_MODE_700E;
88     if (!strcmp(argv[1],"2400A")) mode = FREEDV_MODE_2400A;
89     if (!strcmp(argv[1],"2400B")) mode = FREEDV_MODE_2400B;
90     if (!strcmp(argv[1],"800XA")) mode = FREEDV_MODE_800XA;
91     #ifdef __LPCNET__
92     if (!strcmp(argv[1],"2020"))  mode = FREEDV_MODE_2020;
93     #endif
94     if (mode == -1) {
95         fprintf(stderr, "Error in mode: %s\n", argv[1]);
96         exit(1);
97     }
98 
99     if (strcmp(argv[2], "-")  == 0) fin = stdin;
100     else if ( (fin = fopen(argv[2],"rb")) == NULL ) {
101 	fprintf(stderr, "Error opening input raw modem sample file: %s: %s.\n",
102          argv[2], strerror(errno));
103 	exit(1);
104     }
105 
106     if (strcmp(argv[3], "-") == 0) fout = stdout;
107     else if ( (fout = fopen(argv[3],"wb")) == NULL ) {
108 	fprintf(stderr, "Error opening output speech sample file: %s: %s.\n",
109          argv[3], strerror(errno));
110 	exit(1);
111     }
112 
113     use_testframes = verbose = discard = use_complex = use_dpsk = use_squelch = 0; use_reliabletext = 0;
114     highpassthroughgain = 0;
115 
116     if (argc > 4) {
117         for (i = 4; i < argc; i++) {
118             if (strcmp(argv[i], "--testframes") == 0) use_testframes = 1;
119             else if (strcmp(argv[i], "-v") == 0) verbose = 1;
120             else if (strcmp(argv[i], "-vv") == 0) verbose = 2;
121             else if (strcmp(argv[i], "--discard") == 0) discard = 1;
122             else if (strcmp(argv[i], "--usecomplex") == 0) use_complex = 1;
123             else if (strcmp(argv[i], "--highpassthroughgain") == 0) highpassthroughgain = 1;
124             else if (strcmp(argv[i], "--squelch") == 0) {
125                 squelch = atof(argv[i + 1]);
126                 i++;
127                 use_squelch = 1;
128             } else if (strcmp(argv[i], "--dpsk") == 0) use_dpsk = 1;
129             else if (strcmp(argv[i], "--txtrx") == 0) {
130                 ftxt_rx = fopen(argv[i+1], "wt"); i++;
131                 assert(ftxt_rx != NULL);
132             } else if (strcmp(argv[i], "--reliabletext") == 0) {
133                 use_reliabletext = 1;
134                 // received text is saved to file specified by --txtrx.
135             } else {
136                 fprintf(stderr, "unkown option: %s\n", argv[i]);
137                 exit(1);
138             }
139         }
140     }
141 
142     freedv = freedv_open(mode);
143     assert(freedv != NULL);
144 
145     /* set up a few options, calling these is optional -------------------------*/
146 
147     freedv_set_test_frames(freedv, use_testframes);
148     if (verbose == 2) freedv_set_verbose(freedv, verbose);
149     if (highpassthroughgain) { freedv_passthrough_gain(freedv, 1.0); }
150 
151     if (use_squelch) {
152         freedv_set_snr_squelch_thresh(freedv, squelch);
153         freedv_set_squelch_en(freedv, 1);
154     }
155     freedv_set_dpsk(freedv, use_dpsk);
156 
157     /* install optional handler for recevied txt characters */
158     if (ftxt_rx != NULL)
159     {
160         if (use_reliabletext)
161         {
162             reliable_tx_fp = ftxt_rx;
163 
164             reliable_text_obj = reliable_text_create();
165             assert(reliable_text_obj != NULL);
166             reliable_text_set_string(reliable_text_obj, "AB1CDEF", 7); // not used
167             reliable_text_use_with_freedv(reliable_text_obj, freedv, on_reliable_text_rx, NULL);
168         }
169         else
170         {
171             freedv_set_callback_txt(freedv, my_put_next_rx_char, NULL, ftxt_rx);
172         }
173     }
174 
175     /* note use of API functions to tell us how big our buffers need to be -----*/
176 
177     short speech_out[freedv_get_n_max_speech_samples(freedv)];
178     short demod_in[freedv_get_n_max_modem_samples(freedv)];
179 
180     /* We need to work out how many samples the demod needs on each
181        call (nin).  This is used to adjust for differences in the tx
182        and rx sample clock frequencies.  Note also the number of
183        output speech samples "nout" is time varying. */
184 
185     nin = freedv_nin(freedv);
186     while(fread(demod_in, sizeof(short), nin, fin) == nin) {
187         frame++;
188 
189         if (use_complex) {
190             /* exercise the complex version of the API (useful
191                for testing 700D which has a different code path for
192                short samples) */
193             COMP demod_in_complex[nin];
194 
195             for(int i=0; i<nin; i++) {
196                 demod_in_complex[i].real = (float)demod_in[i];
197                 demod_in_complex[i].imag = 0.0f;
198             }
199             nout = freedv_comprx(freedv, speech_out, demod_in_complex);
200         } else {
201             // most common interface - real shorts in, real shorts out
202             nout = freedv_rx(freedv, speech_out, demod_in);
203         }
204 
205        /* IMPORTANT: don't forget to do this in the while loop to
206            ensure we fread the correct number of samples: ie update
207            "nin" before every call to freedv_rx()/freedv_comprx() */
208         nin = freedv_nin(freedv);
209 
210         /* optionally read some stats */
211         freedv_get_modem_stats(freedv, &sync, &snr_est);
212         freedv_get_modem_extended_stats(freedv, &stats);
213         int total_bit_errors = freedv_get_total_bit_errors(freedv);
214         clock_offset = stats.clock_offset;
215 
216         if (discard && (sync == 0)) {
217             // discard BER results if we get out of sync, helps us get sensible BER results
218             freedv_set_total_bits(freedv, 0); freedv_set_total_bit_errors(freedv, 0);
219             freedv_set_total_bits_coded(freedv, 0); freedv_set_total_bit_errors_coded(freedv, 0);
220         }
221 
222         fwrite(speech_out, sizeof(short), nout, fout);
223         nout_total += nout;
224 
225         if (verbose == 1) {
226             fprintf(stderr, "frame: %d  demod sync: %d  nin: %d demod snr: %3.2f dB  bit errors: %d clock_offset: %f\n",
227                     frame, sync, nin, snr_est, total_bit_errors, clock_offset);
228         }
229 
230 	/* if using pipes we probably don't want the usual buffering
231            to occur */
232         if (fout == stdout) fflush(stdout);
233     }
234 
235     if (ftxt_rx != NULL) fclose(ftxt_rx);
236     fclose(fin);
237     fclose(fout);
238     fprintf(stderr, "frames decoded: %d  output speech samples: %d\n", frame, nout_total);
239 
240     /* finish up with some stats */
241 
242     if (freedv_get_test_frames(freedv)) {
243         int Tbits = freedv_get_total_bits(freedv);
244         int Terrs = freedv_get_total_bit_errors(freedv);
245         float uncoded_ber = (float)Terrs/Tbits;
246         fprintf(stderr, "BER......: %5.4f  Tbits: %8d  Terrs: %8d\n",
247 		                    (double)uncoded_ber, Tbits, Terrs);
248         if ((mode == FREEDV_MODE_700D) || (mode == FREEDV_MODE_700E) || (mode == FREEDV_MODE_2020)) {
249             int Tbits_coded = freedv_get_total_bits_coded(freedv);
250             int Terrs_coded = freedv_get_total_bit_errors_coded(freedv);
251             float coded_ber = (float)Terrs_coded/Tbits_coded;
252             fprintf(stderr, "Coded BER: %5.4f  Tbits: %8d  Terrs: %8d\n",
253                     (double)coded_ber, Tbits_coded, Terrs_coded);
254             int Tpackets = freedv_get_total_packets(freedv);
255             int Tpacket_errors = freedv_get_total_packet_errors(freedv);
256             float per = (float)Tpacket_errors/Tpackets;
257             fprintf(stderr, "Coded PER: %5.4f  Tpkts: %8d  Tpers: %8d\n",
258                     per, Tpackets, Tpacket_errors);
259 
260             /* set return code for Ctest */
261             if ((uncoded_ber < 0.1f) && (coded_ber < 0.01f))
262                 return 0;
263             else
264                 return 1;
265         }
266     }
267 
268     if (use_reliabletext)
269     {
270         reliable_text_destroy(reliable_text_obj);
271     }
272 
273     freedv_close(freedv);
274 
275     return 0;
276 }
277