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