diff --git a/.gitignore b/.gitignore index 8102018..574eb1f 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ src/jtacs/jtacs src/r2000/radiocom2000 src/jolly/jollycom src/tv/osmotv +src/radio/osmoradio sim/cnetz_sim src/test/test_filter src/test/test_sendevolumenregler diff --git a/configure.ac b/configure.ac index 62e67a3..aa34386 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,7 @@ AC_OUTPUT( src/r2000/Makefile src/jolly/Makefile src/tv/Makefile + src/radio/Makefile src/test/Makefile src/Makefile sim/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 594dcf9..aa068e1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,5 +47,6 @@ SUBDIRS += \ r2000 \ jolly \ tv \ + radio \ test diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index e4dcfcc..fd891ec 100644 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -62,6 +62,7 @@ struct debug_cat { { "uhd", "\033[1;35m" }, { "soapy", "\033[1;35m" }, { "wave", "\033[1;33m" }, + { "radio", "\033[1;34m" }, { NULL, NULL } }; diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h index 782b56c..8a76d45 100644 --- a/src/libdebug/debug.h +++ b/src/libdebug/debug.h @@ -25,6 +25,7 @@ #define DUHD 18 #define DSOAPY 19 #define DWAVE 20 +#define DRADIO 21 void get_win_size(int *w, int *h); diff --git a/src/radio/Makefile.am b/src/radio/Makefile.am new file mode 100644 index 0000000..24c65da --- /dev/null +++ b/src/radio/Makefile.am @@ -0,0 +1,44 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +if HAVE_SDR + +bin_PROGRAMS = \ + osmoradio + +osmoradio_SOURCES = \ + radio.c \ + main.c +osmoradio_LDADD = \ + $(COMMON_LA) \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(top_builddir)/src/libsdr/libsdr.a \ + $(top_builddir)/src/libclipper/libclipper.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libam/libam.a \ + $(top_builddir)/src/libemphasis/libemphasis.a \ + $(top_builddir)/src/libsamplerate/libsamplerate.a \ + $(top_builddir)/src/libjitter/libjitter.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libdisplay/libdisplay.a \ + $(top_builddir)/src/libfft/libfft.a \ + $(top_builddir)/src/libtimer/libtimer.a \ + $(UHD_LIBS) \ + $(SOAPY_LIBS) \ + -lm + +if HAVE_ALSA +osmoradio_LDADD += \ + $(top_builddir)/src/libsound/libsound.a \ + $(ALSA_LIBS) +endif + +if HAVE_ALSA +AM_CPPFLAGS += -DHAVE_ALSA +endif + +AM_CPPFLAGS += -DHAVE_SDR + +endif + diff --git a/src/radio/main.c b/src/radio/main.c new file mode 100644 index 0000000..d576ffe --- /dev/null +++ b/src/radio/main.c @@ -0,0 +1,488 @@ +/* main function + * + * (C) 2018 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License + * along with this program. If not, see . + */ + +enum paging_signal; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libsdr/sdr_config.h" +#include "../libsdr/sdr.h" +#include "../libdisplay/display.h" +#include "radio.h" + +#define DEFAULT_LO_OFFSET -1000000.0 + +void *sender_head = NULL; +int use_sdr = 0; +int num_kanal = 1; /* only one channel used for debugging */ + +void *get_sender_by_empfangsfrequenz() { return NULL; } + +static double frequency = 0.0; +static int samplerate = 100000; +static int latency = 30; +static const char *tx_wave_file = NULL; +static const char *rx_wave_file = NULL; +static const char *tx_audiodev = NULL; +static const char *rx_audiodev = NULL; +static enum modulation modulation = MODULATION_NONE; +static int rx = 0, tx = 0; +static double bandwidth_am = 4500.0; +static double bandwidth_fm = 15000.0; +static double bandwidth = 0.0; +static double deviation = 75000.0; +static double modulation_index = 1.0; +static double time_constant_us = 50.0; +static int stereo = 0; +static int rds = 0; +static int rds2 = 0; + +/* global variable to quit main loop */ +int quit = 0; + +void sighandler(int sigset) +{ + if (sigset == SIGHUP) + return; + if (sigset == SIGPIPE) + return; + +// clear_console_text(); + printf("Signal received: %d\n", sigset); + + quit = 1; +} + +static int get_char() +{ + struct timeval tv = {0, 0}; + fd_set fds; + char c = 0; + int __attribute__((__unused__)) rc; + + FD_ZERO(&fds); + FD_SET(0, &fds); + select(0+1, &fds, NULL, NULL, &tv); + if (FD_ISSET(0, &fds)) { + rc = read(0, &c, 1); + return c; + } else + return -1; +} + +void print_help(const char *arg0) + +{ + printf("Usage: %s --sdr-soapy|--sdr-uhd -f -M -R|-T [options]\n", arg0); + /* - - */ + printf("\noptions:\n"); + printf(" -f --frequency \n"); + printf(" Give frequency in Hertz.\n"); + printf(" -s --samplerate \n"); + printf(" Give signal processing sample rate in Hz. (default = %d)\n", samplerate); + printf(" This sample rate must be high enough for the signal's spectrum to fit.\n"); + printf(" I will inform you, if this bandwidth is too low.\n"); + printf(" -r --tx-wave-file \n"); + printf(" Input transmitted audio from wave file\n"); + printf(" -w --rx-wave-file \n"); + printf(" Output received audio to wave file\n"); + printf(" -a --audio-device hw:,\n"); + printf(" Input audio from sound card's device number\n"); + printf(" -M --modulation fm | am | usb | lsb\n"); + printf(" fm = Frequency modulation to be used for VHF.\n"); + printf(" am = Amplitude modulation to be used for long/medium/short wave.\n"); + printf(" usb = Amplitude modulation with upper side band only.\n"); + printf(" lsb = Amplitude modulation with lower side band only.\n"); + printf(" -R --rx\n"); + printf(" Receive radio signal.\n"); + printf(" -T --tx\n"); + printf(" Transmit radio signal.\n"); + printf(" -B --bandwidth\n"); + printf(" Give bandwidth of audio frequency. (default AM=%.0f FM=%.0f)\n", bandwidth_am, bandwidth_fm); + printf(" -D --deviation\n"); + printf(" Give deviation of frequency modulated signal. (default %.0f)\n", deviation); + printf(" -I --modulation-index 0..1\n"); + printf(" Give modulation index of amplitude modulated signal. (default %.0f)\n", deviation); + printf(" -E --emphasis | 0\n"); + printf(" Use given time constant of pre- and de-emphasis for frequency\n"); + printf(" modulation. Give 0 to disbale. (default = %.0f uS)\n", time_constant_us); + printf(" VHF broadcast 50 uS in Europe and 75 uS in the United States.\n"); + printf(" Other radio FM should use 530 uS, to cover complete speech spectrum.\n"); + printf(" -S --stereo\n"); + printf(" Enables stereo carrier for frequency modulated UHF broadcast.\n"); + printf(" It uses the 'Pilot-tone' system.\n"); + sdr_config_print_help(); +} + +static struct option long_options_common[] = { + {"help", 0, 0, 'h'}, + {"frequency", 1, 0, 'f'}, + {"samplerate", 1, 0, 's'}, + {"tx-wave-file", 1, 0, 'r'}, + {"rx-wave-file", 1, 0, 'w'}, + {"audio-device", 1, 0, 'a'}, + {"modulation", 1, 0, 'M'}, + {"rx", 0, 0, 'R'}, + {"tx", 0, 0, 'T'}, + {"bandwidth", 1, 0, 'B'}, + {"deviation", 1, 0, 'D'}, + {"modulation-index", 1, 0, 'I'}, + {"emphasis", 1, 0, 'E'}, + {"stereo", 0, 0, 'S'}, + {0, 0, 0, 0} +}; + +static const char *optstring_common = "hf:s:r:w:a:M:RTB:D:I:E:S"; + +struct option *long_options; +char *optstring; + +static void check_duplicate_option(int num, struct option *option) +{ + int i; + + for (i = 0; i < num; i++) { + if (long_options[i].val == option->val) { + fprintf(stderr, "Duplicate option %d. Please fix!\n", option->val); + abort(); + } + } +} + +void set_options_common(void) +{ + int i = 0, j; + + long_options = calloc(sizeof(*long_options), 256); + for (j = 0; long_options_common[i].name; i++, j++) { + check_duplicate_option(i, &long_options_common[j]); + memcpy(&long_options[i], &long_options_common[j], sizeof(*long_options)); + } + for (j = 0; sdr_config_long_options[j].name; i++, j++) { + check_duplicate_option(i, &sdr_config_long_options[j]); + memcpy(&long_options[i], &sdr_config_long_options[j], sizeof(*long_options)); + } + + optstring = calloc(256, 2); + strcpy(optstring, optstring_common); + strcat(optstring, sdr_config_optstring); +} + +static int handle_options(int argc, char **argv) +{ + int skip_args = 0; + int rc; + + set_options_common(); + + while (1) { + int option_index = 0, c; + + c = getopt_long(argc, argv, optstring, long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + case 'f': + frequency = atof(optarg); + skip_args += 2; + break; + case 's': + samplerate = atof(optarg); + skip_args += 2; + break; + case 'r': + tx_wave_file = strdup(optarg); + skip_args += 2; + break; + case 'w': + rx_wave_file = strdup(optarg); + skip_args += 2; + break; + case 'a': + tx_audiodev = strdup(optarg); + rx_audiodev = strdup(optarg); + skip_args += 2; + break; + case 'M': + if (!strcasecmp(optarg, "fm")) + modulation = MODULATION_FM; + else + if (!strcasecmp(optarg, "am")) + modulation = MODULATION_AM_DSB; + else + if (!strcasecmp(optarg, "usb")) + modulation = MODULATION_AM_USB; + else + if (!strcasecmp(optarg, "lsb")) + modulation = MODULATION_AM_LSB; + else + { + fprintf(stderr, "Invalid modulation option, see help!\n"); + exit(0); + } + skip_args += 2; + break; + case 'R': + rx = 1; + skip_args += 1; + break; + case 'T': + tx = 1; + skip_args += 1; + break; + case 'B': + bandwidth = atof(optarg); + skip_args += 2; + break; + case 'D': + deviation = atof(optarg); + skip_args += 2; + break; + case 'I': + modulation_index = atof(optarg); + if (modulation_index < 0.0 || modulation_index > 1.0) { + fprintf(stderr, "Invalid modulation index, see help!\n"); + exit(0); + } + skip_args += 2; + break; + case 'E': + time_constant_us = atof(optarg); + skip_args += 2; + break; + case 'S': + stereo = 1; + skip_args += 1; + break; + default: + rc = sdr_config_opt_switch(c, &skip_args); + if (rc < 0) + exit(0); + break; + } + } + + return skip_args; +} + +int main(int argc, char *argv[]) +{ + int skip_args; + int rc; + const char *arg0 = argv[0]; + radio_t radio; + struct termios term, term_orig; + int c; + int latspl; + + debuglevel = 0; + + sdr_config_init(DEFAULT_LO_OFFSET); + + skip_args = handle_options(argc, argv); + argc -= skip_args + 1; + argv += skip_args + 1; + + if (frequency == 0.0) { + printf("No frequency given, I suggest to use 100000000 (100 MHz) and FM\n\n"); + print_help(arg0); + exit(0); + } + + rc = sdr_configure(samplerate); + if (rc < 0) + return rc; + if (rc == 0) { + fprintf(stderr, "Please select SDR, see help!\n"); + exit(0); + } + + if (modulation == MODULATION_NONE) { + fprintf(stderr, "Please select modulation, see help!\n"); + exit(0); + } + + if (bandwidth == 0) { + if (modulation == MODULATION_FM) + bandwidth = bandwidth_fm; + else + bandwidth = bandwidth_am; + } + + if (stereo && modulation != MODULATION_FM) { + fprintf(stderr, "Stereo works with FM only, see help!\n"); + exit(0); + } + if (!rx && !tx) { + fprintf(stderr, "You need to specify --rx (receiver) and/or --tx (transmitter), see help!\n"); + exit(0); + } + if (stereo && bandwidth != 15000.0) { + fprintf(stderr, "Warning: Stereo works with bandwidth of 15 KHz only, using this bandwidth!\n"); + } + if (stereo && time_constant_us != 75.0 && time_constant_us != 50.0) { + fprintf(stderr, "Stereo works with time constant of 50 uS or 75 uS only, see help!\n"); + exit(0); + } + + /* now we have latency and sample rate */ + latspl = samplerate * latency / 1000; + + rc = radio_init(&radio, latspl, samplerate, tx_wave_file, rx_wave_file, (tx) ? tx_audiodev : NULL, (rx) ? rx_audiodev : NULL, modulation, bandwidth, deviation, modulation_index, time_constant_us, stereo, rds, rds2); + if (rc < 0) { + fprintf(stderr, "Failed to initialize radio with given options, exitting!\n"); + exit(0); + } + + void *sdr = NULL; + float *sendbuff = NULL; + + sendbuff = calloc(latspl * 2, sizeof(*sendbuff)); + if (!sendbuff) { + fprintf(stderr, "No mem!\n"); + goto error; + } + + double tx_frequencies[1], rx_frequencies[1]; + tx_frequencies[0] = frequency; + rx_frequencies[0] = frequency; + sdr = sdr_open(NULL, tx_frequencies, rx_frequencies, 1, 0.0, samplerate, latspl, 0.0, 0.0); + if (!sdr) + goto error; + sdr_start(sdr); + + /* prepare terminal */ + tcgetattr(0, &term_orig); + term = term_orig; + term.c_lflag &= ~(ISIG|ICANON|ECHO); + term.c_cc[VMIN]=1; + term.c_cc[VTIME]=2; + tcsetattr(0, TCSANOW, &term); + + /* catch signals */ + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + printf("Starting radio...\n"); + rc = radio_start(&radio); + if (rc < 0) { + fprintf(stderr, "Failed to start radio's streaming, exitting!\n"); + goto error; + } + + int tosend, got; + while (!quit) { + usleep(1000); + got = sdr_read(sdr, (void *)sendbuff, latspl, 0, NULL); + if (rx) { + got = radio_rx(&radio, sendbuff, got); + if (got < 0) + break; + } + if (tx) { + tosend = sdr_get_tosend(sdr, latspl); + if (tosend > latspl / 10) + tosend = latspl / 10; + if (tosend == 0) { + continue; + } + /* perform radio modulation */ + tosend = radio_tx(&radio, sendbuff, tosend); + if (tosend < 0) + break; + /* write to SDR */ + sdr_write(sdr, (void *)sendbuff, NULL, tosend, NULL, NULL, 0); + } + + /* process keyboard input */ +next_char: + c = get_char(); + switch (c) { + case 3: + /* quit */ +// if (clear_console_text) +// clear_console_text(); + printf("CTRL+c received, quitting!\n"); + quit = 1; + goto next_char; +#if 0 +- carrier frequency +- deviation +- modulation index +- stereo pilot + case 'm': + /* toggle measurements display */ + display_iq_on(0); + display_spectrum_on(0); + display_measurements_on(-1); + goto next_char; +#endif + case 'q': + /* toggle IQ display */ + display_measurements_on(0); + display_spectrum_on(0); + display_iq_on(-1); + goto next_char; + case 's': + /* toggle spectrum display */ + display_measurements_on(0); + display_iq_on(0); + display_spectrum_on(-1); + goto next_char; + case 'B': + calibrate_bias(); + goto next_char; + } + } + + /* reset signals */ + signal(SIGINT, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + /* reset terminal */ + tcsetattr(0, TCSANOW, &term_orig); + +error: + free(sendbuff); + if (sdr) + sdr_close(sdr); + radio_exit(&radio); + + return 0; +} + diff --git a/src/radio/radio.c b/src/radio/radio.c new file mode 100644 index 0000000..48c8daf --- /dev/null +++ b/src/radio/radio.c @@ -0,0 +1,705 @@ +/* main function + * + * (C) 2018 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libsound/sound.h" +#include "../libclipper/clipper.h" +#include "radio.h" + +#define CLIP_POINT 0.85 +#define DC_CUTOFF 30.0 // Wikipedia: UKW-Rundfunk +#define STEREO_BW 15000.0 +#define PILOT_FREQ 19000.0 +#define PILOT_BW 5.0 + +int radio_init(radio_t *radio, int latspl, int samplerate, const char *tx_wave_file, const char *rx_wave_file, const char *tx_audiodev, const char *rx_audiodev, enum modulation modulation, double bandwidth, double deviation, double modulation_index, double time_constant_us, int stereo, int rds, int rds2) +{ + int rc = -EINVAL; + + clipper_init(CLIP_POINT); + + memset(radio, 0, sizeof(*radio)); + radio->latspl = latspl; + radio->stereo = stereo; + radio->rds = rds; + radio->rds2 = rds2; + radio->tx_wave_file = tx_wave_file; + radio->modulation = modulation; + radio->signal_samplerate = samplerate; + radio->audio_bandwidth = bandwidth; + + switch (radio->modulation) { + case MODULATION_FM: + radio->fm_deviation = deviation; + radio->signal_bandwidth = deviation + bandwidth; + if (radio->stereo) { + radio->signal_bandwidth = deviation + 53000.0; + radio->audio_bandwidth = STEREO_BW; + } + if (radio->rds) + radio->signal_bandwidth = deviation + 60000.0; + if (radio->rds2) + radio->signal_bandwidth = deviation + 80000.0; + break; + case MODULATION_AM_DSB: + case MODULATION_AM_USB: + case MODULATION_AM_LSB: + /* level is 1.0, which is full amplitude */ + radio->signal_bandwidth = bandwidth; + break; + case MODULATION_NONE: + PDEBUG(DRADIO, DEBUG_ERROR, "Wrong modulation, plese fix!\n"); + goto error; + } + + if (tx_wave_file) { + /* open wave file */ + int _samplerate = 0; + radio->tx_audio_channels = 0; + rc = wave_create_playback(&radio->wave_tx_play, tx_wave_file, &_samplerate, &radio->tx_audio_channels, 1.0); + if (rc < 0) { + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to create WAVE playback instance!\n"); + goto error; + } + if (radio->tx_audio_channels != 1 && radio->tx_audio_channels != 2) + { + PDEBUG(DRADIO, DEBUG_ERROR, "WAVE file must have one or two channels!\n"); + goto error; + } + radio->tx_audio_samplerate = _samplerate; + radio->tx_audio_mode = AUDIO_MODE_WAVEFILE; + } else if (tx_audiodev) { +#ifdef HAVE_ALSA + /* open audio device */ + radio->tx_audio_samplerate = 48000; + radio->tx_audio_channels = (stereo) ? 2 : 1; + radio->tx_sound = sound_open(tx_audiodev, NULL, NULL, radio->tx_audio_channels, 0.0, radio->tx_audio_samplerate, radio->latspl, 1.0, 0.0); + if (!radio->tx_sound) { + rc = -EIO; + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to open sound device!\n"); + goto error; + } + jitter_create(&radio->tx_dejitter[0], radio->tx_audio_samplerate / 5); + jitter_create(&radio->tx_dejitter[1], radio->tx_audio_samplerate / 5); + radio->tx_audio_mode = AUDIO_MODE_AUDIODEV; +#else + rc = -ENOTSUP; + PDEBUG(DRADIO, DEBUG_ERROR, "No sound card support compiled in!\n"); + goto error; +#endif + } else { + int i; + double phase; + /* use built-in sample sound */ + radio->tx_audio_samplerate = samplerate; + radio->tx_audio_channels = (radio->stereo) ? 2 : 1; + radio->testtone_length = radio->tx_audio_samplerate; + radio->testtone[0] = calloc(radio->testtone_length * 2, sizeof(sample_t)); + if (!radio->testtone[0]) { + rc = -ENOMEM; + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to allocate test sound buffer!\n"); + goto error; + } + radio->testtone[1] = radio->testtone[0] + radio->testtone_length; + /* generate tone */ + phase = 2.0 * M_PI * 1000.0 / radio->tx_audio_samplerate; + if (radio->stereo) { + for (i = 0; i < radio->testtone_length / 2; i++) { + radio->testtone[0][i] = sin(i * phase); + radio->testtone[1][i] = 0.0; + } + for (; i < radio->testtone_length; i++) { + radio->testtone[0][i] = 0.0; + radio->testtone[1][i] = sin(i * phase); + } + } else { + for (i = 0; i < radio->testtone_length; i++) { + radio->testtone[0][i] = sin(i * phase); + } + } + radio->tx_audio_mode = AUDIO_MODE_TESTTONE; + } + + if (rx_wave_file) { + /* open wave file */ + radio->rx_audio_samplerate = 4800; + radio->rx_audio_channels = (radio->stereo) ? 2 : 1; + rc = wave_create_record(&radio->wave_rx_rec, rx_wave_file, radio->rx_audio_samplerate, radio->rx_audio_channels, 1.0); + if (rc < 0) { + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to create WAVE record instance!\n"); + goto error; + } + radio->rx_audio_mode = AUDIO_MODE_WAVEFILE; + } else if (rx_audiodev) { +#ifdef HAVE_ALSA + /* open audio device */ + radio->rx_audio_samplerate = 48000; + radio->rx_audio_channels = (stereo) ? 2 : 1; + /* check if we use same device */ + if (radio->tx_sound && !strcmp(tx_audiodev, rx_audiodev)) + radio->rx_sound = radio->tx_sound; + else + radio->rx_sound = sound_open(rx_audiodev, NULL, NULL, radio->rx_audio_channels, 0.0, radio->rx_audio_samplerate, radio->latspl, 1.0, 0.0); + if (!radio->rx_sound) { + rc = -EIO; + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to open sound device!\n"); + goto error; + } + jitter_create(&radio->rx_dejitter[0], radio->rx_audio_samplerate / 5); + jitter_create(&radio->rx_dejitter[1], radio->rx_audio_samplerate / 5); + radio->rx_audio_mode = AUDIO_MODE_AUDIODEV; +#else + rc = -ENOTSUP; + PDEBUG(DRADIO, DEBUG_ERROR, "No sound card support compiled in!\n"); + goto error; +#endif + } + + /* check if sample rate is too low */ + if (radio->tx_audio_samplerate > radio->signal_samplerate) { + rc = -EINVAL; + PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your audio sample rate is %.0f.\n", radio->signal_samplerate, radio->tx_audio_samplerate); + PDEBUG(DRADIO, DEBUG_ERROR, "Please select a sample rate that is higher or equal the audio sample rate!\n"); + goto error; + } + if (radio->rx_audio_samplerate > radio->signal_samplerate) { + rc = -EINVAL; + PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your audio sample rate is %.0f.\n", radio->signal_samplerate, radio->rx_audio_samplerate); + PDEBUG(DRADIO, DEBUG_ERROR, "Please select a sample rate that is higher or equal the audio sample rate!\n"); + goto error; + } + if (radio->signal_samplerate < radio->signal_bandwidth * 2 / 0.75) { + rc = -EINVAL; + PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your signal's bandwidth %.0f.\n", radio->signal_samplerate, radio->signal_bandwidth); + PDEBUG(DRADIO, DEBUG_ERROR, "Your signal processing sample rate must be at least one third greater than the signal's double bandwidth. Use at least %.0f.\n", radio->signal_bandwidth * 2.0 / 0.75); + goto error; + } + + iir_highpass_init(&radio->tx_dc_removal[0], DC_CUTOFF, radio->tx_audio_samplerate, 1); + iir_highpass_init(&radio->tx_dc_removal[1], DC_CUTOFF, radio->tx_audio_samplerate, 1); + + /* stereo pilot tone phase */ + radio->pilot_phasestep = 2.0 * M_PI * PILOT_FREQ / radio->signal_samplerate; + + /* stere decoding filters */ + iir_lowpass_init(&radio->rx_lp_pilot_I, PILOT_BW, radio->signal_samplerate, 2); + iir_lowpass_init(&radio->rx_lp_pilot_Q, PILOT_BW, radio->signal_samplerate, 2); + iir_lowpass_init(&radio->rx_lp_sum, STEREO_BW, radio->signal_samplerate, 2); + iir_lowpass_init(&radio->rx_lp_diff, STEREO_BW, radio->signal_samplerate, 2); + + /* init sample rate conversion, use complete bandwidth for resample filter */ + rc = init_samplerate(&radio->tx_resampler[0], radio->tx_audio_samplerate, radio->signal_samplerate, radio->tx_audio_samplerate / 2.0); + if (rc < 0) + goto error; + rc = init_samplerate(&radio->tx_resampler[1], radio->tx_audio_samplerate, radio->signal_samplerate, radio->tx_audio_samplerate / 2.0); + if (rc < 0) + goto error; + rc = init_samplerate(&radio->rx_resampler[0], radio->rx_audio_samplerate, radio->signal_samplerate, radio->rx_audio_samplerate / 2.0); + if (rc < 0) + goto error; + rc = init_samplerate(&radio->rx_resampler[1], radio->rx_audio_samplerate, radio->signal_samplerate, radio->rx_audio_samplerate / 2.0); + if (rc < 0) + goto error; + + /* init filters (using signal sample rate) */ + switch (radio->modulation) { + case MODULATION_FM: + /* time constant */ + PDEBUG(DRADIO, DEBUG_INFO, "Using emphasis cut-off at %.0f Hz.\n", timeconstant2cutoff(time_constant_us)); + rc = init_emphasis(&radio->fm_emphasis[0], radio->signal_samplerate, timeconstant2cutoff(time_constant_us), DC_CUTOFF, radio->audio_bandwidth); + if (rc < 0) + goto error; + rc = init_emphasis(&radio->fm_emphasis[1], radio->signal_samplerate, timeconstant2cutoff(time_constant_us), DC_CUTOFF, radio->audio_bandwidth); + if (rc < 0) + goto error; + rc = fm_mod_init(&radio->fm_mod, radio->signal_samplerate, 0.0, 1.0); + if (rc < 0) + goto error; + rc = fm_demod_init(&radio->fm_demod, radio->signal_samplerate, 0.0, 2 * radio->signal_bandwidth); + if (rc < 0) + goto error; + break; + case MODULATION_AM_DSB: + iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1); + /* modulation index 0.0 = no envelope, bias 1.0 + * modulation index 1.0 = envelope +-0.5, bias 0.5 + * modulation index 0.5 = envelope +-0.25, bias 0.75 + */ + double gain = modulation_index / 2.0; + double bias = 1.0 - gain; + rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, gain, bias); + if (rc < 0) + goto error; + rc = am_demod_init(&radio->am_demod, radio->signal_samplerate, 0.0, radio->signal_bandwidth, 1.0 / modulation_index); + if (rc < 0) + goto error; + break; + case MODULATION_AM_USB: + iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1); + rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, 1.0, 0.0); + if (rc < 0) + goto error; + break; + case MODULATION_AM_LSB: + iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1); + rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, 1.0, 0.0); + if (rc < 0) + goto error; + break; + default: + break; + } + + if (radio->tx_audio_mode) + PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio source is %.0f Hz.\n", radio->tx_audio_samplerate / 2.0); + if (radio->rx_audio_mode) + PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio sink is %.0f Hz.\n", radio->rx_audio_samplerate / 2.0); + PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio signal is %.0f Hz.\n", radio->audio_bandwidth); + PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of modulated signal is %.0f Hz.\n", radio->signal_bandwidth); + if (radio->tx_audio_mode) + PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of audio source is %.0f Hz.\n", radio->tx_audio_samplerate); + if (radio->rx_audio_mode) + PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of audio sink is %.0f Hz.\n", radio->rx_audio_samplerate); + PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of signal is %.0f Hz.\n", radio->signal_samplerate); + + /* one or two audio channels */ + if (radio->tx_audio_channels != 1 && radio->tx_audio_channels != 2) + { + PDEBUG(DRADIO, DEBUG_ERROR, "Wrong number of audio channels, please fix!\n"); + goto error; + } + + /* audio buffers: how many sample for audio (rounded down) */ + int tx_size = (int)((double)latspl / radio->tx_resampler[0].factor); + int rx_size = (int)((double)latspl / radio->rx_resampler[0].factor); + if (tx_size > rx_size) + radio->audio_buffer_size = tx_size; + else + radio->audio_buffer_size = rx_size; + radio->audio_buffer = calloc(radio->audio_buffer_size * 2, sizeof(*radio->audio_buffer)); + if (!radio->audio_buffer) { + PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n"); + rc = -ENOMEM; + goto error; + } + + /* signal buffers */ + radio->signal_buffer_size = latspl; + radio->signal_buffer = calloc(radio->signal_buffer_size * 3, sizeof(*radio->signal_buffer)); + radio->signal_power_buffer = calloc(radio->signal_buffer_size, sizeof(*radio->signal_power_buffer)); + if (!radio->signal_buffer || !radio->signal_power_buffer) { + PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n"); + rc = -ENOMEM; + goto error; + } + + /* termporary I/Q/carrier buffers, used while demodulating */ + radio->I_buffer = calloc(latspl, sizeof(*radio->I_buffer)); + radio->Q_buffer = calloc(latspl, sizeof(*radio->Q_buffer)); + radio->carrier_buffer = calloc(latspl, sizeof(*radio->carrier_buffer)); + if (!radio->I_buffer || !radio->Q_buffer || !radio->carrier_buffer) { + PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n"); + rc = -ENOMEM; + goto error; + } + + return 0; + +error: + radio_exit(radio); + return rc; +} + +void radio_exit(radio_t *radio) +{ + if (radio->audio_buffer) { + free(radio->audio_buffer); + radio->audio_buffer = NULL; + } + if (radio->signal_buffer) { + free(radio->signal_buffer); + radio->signal_buffer = NULL; + } + if (radio->signal_power_buffer) { + free(radio->signal_power_buffer); + radio->signal_power_buffer = NULL; + } + if (radio->I_buffer) { + free(radio->I_buffer); + radio->I_buffer = NULL; + } + if (radio->Q_buffer) { + free(radio->Q_buffer); + radio->Q_buffer = NULL; + } + if (radio->carrier_buffer) { + free(radio->carrier_buffer); + radio->carrier_buffer = NULL; + } + if (radio->tx_audio_mode == AUDIO_MODE_WAVEFILE) { + wave_destroy_playback(&radio->wave_tx_play); + radio->tx_audio_mode = AUDIO_MODE_NONE; + } + if (radio->rx_audio_mode == AUDIO_MODE_WAVEFILE) { + wave_destroy_record(&radio->wave_rx_rec); + radio->rx_audio_mode = AUDIO_MODE_NONE; + } +#ifdef HAVE_ALSA + if (radio->tx_sound) { + sound_close(radio->tx_sound); + /* if same device was used */ + if (radio->tx_sound == radio->rx_sound) + radio->rx_sound = NULL; + radio->tx_sound = NULL; + radio->tx_audio_mode = AUDIO_MODE_NONE; + } + if (radio->rx_sound) { + sound_close(radio->rx_sound); + radio->rx_sound = NULL; + radio->rx_audio_mode = AUDIO_MODE_NONE; + } +#endif + jitter_destroy(&radio->tx_dejitter[0]); + jitter_destroy(&radio->tx_dejitter[1]); + jitter_destroy(&radio->rx_dejitter[0]); + jitter_destroy(&radio->rx_dejitter[1]); + if (radio->tx_audio_mode == AUDIO_MODE_TESTTONE) { + free(radio->testtone[0]); + radio->tx_audio_mode = AUDIO_MODE_NONE; + } + if (radio->modulation == MODULATION_FM) + fm_mod_exit(&radio->fm_mod); + else + am_mod_exit(&radio->am_mod); +} + +int radio_start(radio_t __attribute__((unused)) *radio) +{ + int rc = 0; + +#ifdef HAVE_ALSA + /* start rx sound */ + if (radio->rx_sound) + rc = sound_start(radio->rx_sound); + /* start tx sound, if different device */ + if (radio->tx_sound && radio->tx_sound != radio->rx_sound) + rc = sound_start(radio->tx_sound); +#endif + + return rc; +} + +int radio_tx(radio_t *radio, float *baseband, int signal_num) +{ + int i; + int __attribute__((unused)) rc; + int audio_num; + sample_t *audio_samples[2]; + sample_t *signal_samples[3]; + uint8_t *signal_power; + + if (signal_num > radio->latspl) { + PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > latspl, please fix!.\n"); + abort(); + } + + /* audio buffers: how many sample for audio (rounded down) */ + audio_num = (int)((double)signal_num / radio->tx_resampler[0].factor); + if (audio_num > radio->audio_buffer_size) { + PDEBUG(DRADIO, DEBUG_ERROR, "audio_num > audio_buffer_size, please fix!.\n"); + abort(); + } + audio_samples[0] = radio->audio_buffer; + audio_samples[1] = radio->audio_buffer + radio->audio_buffer_size; + + /* signal buffers: a bit more samples to be safe */ + signal_num = (int)((double)audio_num * radio->tx_resampler[0].factor + 0.5) + 10; + if (signal_num > radio->signal_buffer_size) { + PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > signal_buffer_size, please fix!.\n"); + abort(); + } + signal_samples[0] = radio->signal_buffer; + signal_samples[1] = radio->signal_buffer + radio->signal_buffer_size; + signal_samples[2] = radio->signal_buffer + radio->signal_buffer_size * 2; + signal_power = radio->signal_power_buffer; + + /* get audio to be sent */ + switch (radio->tx_audio_mode) { + case AUDIO_MODE_WAVEFILE: + wave_read(&radio->wave_tx_play, audio_samples, audio_num); + if (!radio->wave_tx_play.left) { + int rc; + int _samplerate = 0; + wave_destroy_playback(&radio->wave_tx_play); + rc = wave_create_playback(&radio->wave_tx_play, radio->tx_wave_file, &_samplerate, &radio->tx_audio_channels, 1.0); + if (rc < 0) { + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to re-open wave file.\n"); + return rc; + } + } + break; +#ifdef HAVE_ALSA + case AUDIO_MODE_AUDIODEV: + rc = sound_read(radio->tx_sound, audio_samples, radio->audio_buffer_size, radio->tx_audio_channels, NULL); + if (rc < 0) { + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", audio_num); + if (rc == -EPIPE) + PDEBUG(DRADIO, DEBUG_ERROR, "Trying to recover.\n"); + else + return 0; + } + jitter_save(&radio->tx_dejitter[0], audio_samples[0], rc); + jitter_load(&radio->tx_dejitter[0], audio_samples[0], audio_num); + if (radio->tx_audio_channels == 2) { + jitter_save(&radio->tx_dejitter[1], audio_samples[1], rc); + jitter_load(&radio->tx_dejitter[1], audio_samples[1], audio_num); + } + break; +#endif + case AUDIO_MODE_TESTTONE: + for (i = 0; i < audio_num; i++) { + audio_samples[0][i] = radio->testtone[0][radio->testtone_pos]; + audio_samples[1][i] = radio->testtone[1][radio->testtone_pos]; + radio->testtone_pos = (radio->testtone_pos + 1) % radio->testtone_length; + } + break; + default: + PDEBUG(DRADIO, DEBUG_ERROR, "Wrong audio mode, plese fix!\n"); + return -EINVAL; + } + + /* convert mono/stereo, generate differential signal */ + if (radio->stereo && radio->tx_audio_channels == 1) { + /* mono to stereo: sum is 90%, differential signal is 0 */ + for (i = 0; i < audio_num; i++) { + audio_samples[0][i] = 0.9; + audio_samples[1][i] = 0.0; + } + } + if (radio->stereo && radio->tx_audio_channels == 2) { + /* stereo: sum is 90%, diffential is 90% */ + double left, right; + for (i = 0; i < audio_num; i++) { + left = audio_samples[0][i]; + right = audio_samples[1][i]; + audio_samples[0][i] = (left + right) * 0.45; + audio_samples[1][i] = (left - right) * 0.45; + } + } + if (!radio->stereo && radio->tx_audio_channels == 2) { + /* stereo to mono: sum both channel */ + for (i = 0; i < audio_num; i++) + audio_samples[0][i] = (audio_samples[0][i] + audio_samples[1][i]) / 2.0; + } + + /* remove DC */ + iir_process(&radio->tx_dc_removal[0], audio_samples[0], audio_num); + if (radio->stereo) + iir_process(&radio->tx_dc_removal[1], audio_samples[1], audio_num); + + /* upsample */ + signal_num = samplerate_upsample(&radio->tx_resampler[0], audio_samples[0], audio_num, signal_samples[0]); + if (radio->stereo) + samplerate_upsample(&radio->tx_resampler[1], audio_samples[1], audio_num, signal_samples[1]); + + /* prepare baseband */ + memset(baseband, 0, sizeof(float) * 2 * signal_num); + + /* filter audio (remove DC, remove high frequencies, pre-emphasis) + * and modulate */ + switch (radio->modulation) { + case MODULATION_FM: + memset(signal_power, 1, signal_num); + pre_emphasis(&radio->fm_emphasis[0], signal_samples[0], signal_num); + clipper_process(signal_samples[0], signal_num); + if (radio->stereo) { + pre_emphasis(&radio->fm_emphasis[1], signal_samples[1], signal_num); + clipper_process(signal_samples[1], signal_num); + /* add pilot tone */ + double phasestep = radio->pilot_phasestep; + double phase = radio->tx_pilot_phase; + for (i = 0; i < signal_num; i++) { + signal_samples[0][i] += sin(phase) * 0.1; + signal_samples[0][i] += signal_samples[1][i] * sin(phase * 2); + phase += phasestep; + if (phase >= 2.0 * M_PI) + phase -= 2.0 * M_PI; + } + radio->tx_pilot_phase = phase; + } + for (i = 0; i < signal_num; i++) + signal_samples[0][i] *= radio->fm_deviation; + fm_modulate_complex(&radio->fm_mod, signal_samples[0], signal_power, signal_num, baseband); + break; + case MODULATION_AM_DSB: + /* also clip to prevent overshooting after audio filtering */ + clipper_process(signal_samples[0], signal_num); + iir_process(&radio->tx_am_bw_limit, signal_samples[0], signal_num); + am_modulate_complex(&radio->am_mod, signal_samples[0], signal_num, baseband); + break; + case MODULATION_AM_USB: + case MODULATION_AM_LSB: + /* also clip to prevent overshooting after audio filtering */ + clipper_process(signal_samples[0], signal_num); + iir_process(&radio->tx_am_bw_limit, signal_samples[0], signal_num); + am_modulate_complex(&radio->am_mod, signal_samples[0], signal_num, baseband); + break; + default: + break; + } + + return signal_num; +} + +int radio_rx(radio_t *radio, float *baseband, int signal_num) +{ + int i; + int audio_num; + sample_t *samples[3]; + double p; + + if (signal_num > radio->latspl) { + PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > latspl, please fix!.\n"); + abort(); + } + + if (signal_num > radio->signal_buffer_size) { + PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > signal_buffer_size, please fix!.\n"); + abort(); + } + samples[0] = radio->signal_buffer; + samples[1] = radio->signal_buffer + radio->signal_buffer_size; + samples[2] = radio->signal_buffer + radio->signal_buffer_size * 2; + + switch (radio->modulation) { + case MODULATION_FM: + fm_demodulate_complex(&radio->fm_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer); + for (i = 0; i < signal_num; i++) + samples[0][i] /= radio->fm_deviation; + if (radio->stereo) { + /* filter pilot tone */ + p = radio->rx_pilot_phase; /* don't increment in radio structure, will be done later */ + for (i = 0; i < signal_num; i++) { + samples[1][i] = samples[0][i] * cos(p); /* I */ + samples[2][i] = samples[0][i] * sin(p); /* Q */ + p += radio->pilot_phasestep; + if (p >= 2.0 * M_PI) + p -= 2.0 * M_PI; + } + iir_process(&radio->rx_lp_pilot_I, samples[1], signal_num); + iir_process(&radio->rx_lp_pilot_Q, samples[2], signal_num); + /* mix pilot tone (double phase) with differential signal */ + for (i = 0; i < signal_num; i++) { + p = atan2(samples[2][i], samples[1][i]); + /* substract measured phase difference (use double amplitude, because we filter later) */ + samples[1][i] = samples[0][i] * sin((radio->rx_pilot_phase - p) * 2.0) * 2.0; + radio->rx_pilot_phase += radio->pilot_phasestep; + if (radio->rx_pilot_phase >= 2.0 * M_PI) + radio->rx_pilot_phase -= 2.0 * M_PI; + } + /* filter to match bandwidth */ + iir_process(&radio->rx_lp_sum, samples[0], signal_num); + iir_process(&radio->rx_lp_diff, samples[1], signal_num); + } + dc_filter(&radio->fm_emphasis[0], samples[0], signal_num); + de_emphasis(&radio->fm_emphasis[0], samples[0], signal_num); + if (radio->stereo) { + dc_filter(&radio->fm_emphasis[1], samples[1], signal_num); + de_emphasis(&radio->fm_emphasis[1], samples[1], signal_num); + } + break; + case MODULATION_AM_DSB: + am_demodulate_complex(&radio->am_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer, radio->carrier_buffer); + break; + case MODULATION_AM_USB: + case MODULATION_AM_LSB: + am_demodulate_complex(&radio->am_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer, radio->carrier_buffer); + break; + default: + break; + } + + /* downsample */ + audio_num = samplerate_downsample(&radio->rx_resampler[0], samples[0], signal_num); + if (radio->stereo) + samplerate_downsample(&radio->rx_resampler[1], samples[1], signal_num); + + /* convert mono/stereo, (from differential signal) */ + if (radio->stereo && radio->rx_audio_channels == 1) { + /* stereo to mono */ + for (i = 0; i < audio_num; i++) { + samples[0][i] = (samples[0][i] + samples[1][i]) / 2.0; + } + } + if (radio->stereo && radio->rx_audio_channels == 2) { + /* stereo from differential */ + double sum, diff; + for (i = 0; i < audio_num; i++) { + sum = samples[0][i]; + diff = samples[1][i]; + samples[0][i] = sum + diff / 2.0; + samples[1][i] = sum - diff / 2.0; + } + } + if (!radio->stereo && radio->rx_audio_channels == 2) { + /* mono to stereo: clone channel */ + for (i = 0; i < audio_num; i++) + samples[1][i] = samples[0][i]; + } + + /* store received audio */ + switch (radio->rx_audio_mode) { + case AUDIO_MODE_WAVEFILE: + wave_write(&radio->wave_rx_rec, samples, audio_num); + break; +#ifdef HAVE_ALSA + case AUDIO_MODE_AUDIODEV: + jitter_save(&radio->rx_dejitter[0], samples[0], audio_num); + if (radio->rx_audio_channels == 2) + jitter_save(&radio->rx_dejitter[1], samples[1], audio_num); + audio_num = sound_get_tosend(radio->rx_sound, radio->signal_buffer_size); + jitter_load(&radio->rx_dejitter[0], samples[0], audio_num); + if (radio->rx_audio_channels == 2) + jitter_load(&radio->rx_dejitter[1], samples[1], audio_num); + audio_num = sound_write(radio->rx_sound, samples, NULL, audio_num, NULL, NULL, radio->rx_audio_channels); + if (audio_num < 0) { + PDEBUG(DRADIO, DEBUG_ERROR, "Failed to write to sound device (rc = %d)!\n", audio_num); + if (audio_num == -EPIPE) + PDEBUG(DRADIO, DEBUG_ERROR, "Trying to recover.\n"); + else + return 0; + } + break; +#endif + default: + PDEBUG(DRADIO, DEBUG_ERROR, "Wrong audio mode, plese fix!\n"); + return -EINVAL; + } + + return signal_num; +} + diff --git a/src/radio/radio.h b/src/radio/radio.h new file mode 100644 index 0000000..5bb5239 --- /dev/null +++ b/src/radio/radio.h @@ -0,0 +1,85 @@ + +#include "../libwave/wave.h" +#include "../libsamplerate/samplerate.h" +#include "../libemphasis/emphasis.h" +#include "../libjitter/jitter.h" +#include "../libfm/fm.h" +#include "../libam/am.h" + +enum modulation { + MODULATION_NONE = 0, + MODULATION_FM, + MODULATION_AM_DSB, + MODULATION_AM_USB, + MODULATION_AM_LSB, +}; + +enum audio_mode { + AUDIO_MODE_NONE = 0, + AUDIO_MODE_WAVEFILE, + AUDIO_MODE_AUDIODEV, + AUDIO_MODE_TESTTONE, +}; + +typedef struct radio { + /* modes */ + int latspl; /* maximum number of samples */ + enum modulation modulation; /* modulation type */ + enum audio_mode tx_audio_mode; /* mode for audio source */ + enum audio_mode rx_audio_mode; /* mode for audio sink */ + int stereo; /* use stere FM */ + int rds, rds2; /* use RDS */ + /* audio stage */ + double tx_audio_samplerate; /* sample rate of audio source */ + double rx_audio_samplerate; /* sample rate of audio sink */ + int tx_audio_channels; /* number of channels of audio source */ + int rx_audio_channels; /* number of channels of audio sink */ + double audio_bandwidth; /* audio bandwidth */ + const char *tx_wave_file; /* wave file name of source */ + const char *rx_wave_file; /* wave file name of sink */ + wave_play_t wave_tx_play; /* wave playback process */ + wave_rec_t wave_rx_rec; /* wave record process */ + void *tx_sound; /* sound card process */ + void *rx_sound; /* sound card process */ + jitter_t tx_dejitter[2]; /* jitter buffer when reading from sound card */ + jitter_t rx_dejitter[2]; /* jitter buffer when writing to sound card */ + sample_t *testtone[2]; /* test tone sample */ + int testtone_length; + int testtone_pos; + /* signal stage */ + double signal_samplerate; + double signal_bandwidth; + samplerate_t tx_resampler[2]; /* resampling from audio rate to signal rate (two channels) */ + samplerate_t rx_resampler[2]; /* resampling from signal rate to audi rate (two channels) */ + emphasis_t fm_emphasis[2]; /* FM pre emphasis */ + double fm_deviation; /* deviation of fm signal */ + fm_mod_t fm_mod; /* FM modulation */ + fm_demod_t fm_demod; /* FM modulation */ + double pilot_phasestep; /* phase change of pilot tone for each sample */ + double tx_pilot_phase; /* current phase of tx sine */ + double rx_pilot_phase; /* current phase of rx mixer */ + iir_filter_t tx_dc_removal[2]; /* AM/FM DC level removal */ + iir_filter_t tx_am_bw_limit; /* AM bandwidth limiter */ + iir_filter_t rx_lp_pilot_I; /* low pass filter for pilot tone extraction */ + iir_filter_t rx_lp_pilot_Q; /* low pass filter for pilot tone extraction */ + iir_filter_t rx_lp_sum; /* filter sum signal of stereo */ + iir_filter_t rx_lp_diff; /* filter differential signal of stereo */ + am_mod_t am_mod; /* AM modulation */ + am_demod_t am_demod; /* AM modulation */ + /* buffers */ + sample_t *audio_buffer; + int audio_buffer_size; + sample_t *signal_buffer; + uint8_t *signal_power_buffer; + int signal_buffer_size; + sample_t *I_buffer; + sample_t *Q_buffer; + sample_t *carrier_buffer; +} radio_t; + +int radio_init(radio_t *radio, int latspl, int samplerate, const char *tx_wave_file, const char *rx_wave_file, const char *tx_audiodev, const char *rx_audiodev, enum modulation modulation, double bandwidth, double deviation, double modulation_index, double time_constant, int stereo, int rds, int rds2); +void radio_exit(radio_t *radio); +int radio_start(radio_t *radio); +int radio_tx(radio_t *radio, float *baseband, int num); +int radio_rx(radio_t *radio, float *baseband, int num); +