Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package acarsdec for openSUSE:Factory checked in at 2026-11-04 19:46:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/acarsdec (Old) and /work/SRC/openSUSE:Factory/.acarsdec.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "acarsdec" Wed Nov 4 19:46:07 2026 rev:9 rq:1394841 version:4.7 Changes: -------- --- /work/SRC/openSUSE:Factory/acarsdec/acarsdec.changes 2026-08-26 19:57:50.271460210 +0200 +++ /work/SRC/openSUSE:Factory/.acarsdec.new.3759290/acarsdec.changes 2026-11-04 19:49:52.004472368 +0100 @@ -1,0 +2,15 @@ +Tue Nov 3 18:28:26 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 4.7: + * IQ file input support (U8 and S16 sample formats) + * sdrplay support improvements: antenna naming aligned with + dumpvdl2, automatic bandwidth adjustment and multiplier + selection, cleanup and better error handling + * fix unsafe processing of input in label decoding + * enable resampling routines to fall back to faster + non-resampling ones when possible + * allow timestamping of audio and IQ input via -T + * speed up output processing by reusing formatted data + * add -V for version output + +------------------------------------------------------------------- Old: ---- acarsdec-4.6.tar.gz New: ---- acarsdec-4.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ acarsdec.spec ++++++ --- /var/tmp/diff_new_pack.jZhYWv/_old 2026-11-04 19:49:53.253524692 +0100 +++ /var/tmp/diff_new_pack.jZhYWv/_new 2026-11-04 19:49:53.258524901 +0100 @@ -18,7 +18,7 @@ Name: acarsdec -Version: 4.6 +Version: 4.7 Release: 0 Summary: ACARS SDR decoder # Legal-Review-Notice: every source file carries a boilerplate naming the "GNU ++++++ acarsdec-4.6.tar.gz -> acarsdec-4.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/CMakeLists.txt new/acarsdec-4.7/CMakeLists.txt --- old/acarsdec-4.6/CMakeLists.txt 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/CMakeLists.txt 2026-11-03 12:42:53.000000000 +0100 @@ -7,7 +7,7 @@ include(CMakeDependentOption) -add_executable(acarsdec acars.c acarsdec.c label.c msk.c output.c netout.c fileout.c lib.c statsd.c) +add_executable(acarsdec acars.c acarsdec.c label.c msk.c iqfile.c output.c netout.c fileout.c lib.c statsd.c) execute_process(COMMAND git describe --tags --always --dirty OUTPUT_VARIABLE VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) if(VERSION) add_compile_definitions(VERSION="${VERSION}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/README.md new/acarsdec-4.7/README.md --- old/acarsdec-4.6/README.md 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/README.md 2026-11-03 12:42:53.000000000 +0100 @@ -1,15 +1,15 @@ # ACARSDEC -acarsdec is a multi-channels ACARS decoder with built-in rtl_sdr, soapysdr, airspy or sdrplay device support. +acarsdec is a multi-channels ACARS decoder with built-in SDR, audio and IQ input support. Since 3.0, it can work with a database backend: [acarsserv](https://github.com/TLeconte/acarsserv) to store received ACARS messages. ## Features * arbitrary number of channels decoded simultaneously (limited by bandwidth and CPU power) * error detection AND correction - * input via either audio file (including raw audio), ALSA soundcard, [rtl_sdr](https://sdr.osmocom.org/trac/wiki/rtl-sdr), + * input via either multichannel audio file/stream (including raw audio), ALSA soundcard, SDR devices ([rtl_sdr](https://sdr.osmocom.org/trac/wiki/rtl-sdr), [SoapySDR](https://github.com/pothosware/SoapySDR/wiki), [airspy](https://airspy.com/) - or [sdrplay](https://www.sdrplay.com) software defined radios (SDR) + [sdrplay](https://www.sdrplay.com)) or raw IQ file/stream * multiple simultaneous outputs via file, UDP or MQTT * multiple output formats: one line, full text, planeplotter, acarsserv, JSON and live monitoring * decoding of ARINC-622 ATS applications (ADS-C, CPDLC) via [libacars](https://github.com/szpajder/libacars) library @@ -72,7 +72,8 @@ - `-DMQTT=OFF` The `-DCMAKE_C_FLAGS="-march=native"` argument to cmake will produce an `acarsdec` executable -that is optimized for the machine it was built on (and may not run correctly on other devices). +that is optimized for the machine it was built on (and may not run correctly on other devices), +improving its performance (i.e. reduced CPU load). It can be ommitted for a platform-generic build (with possibly reduced performance). #### Raspberry Pi builds @@ -102,8 +103,11 @@ -A don't output uplink messages (ie : only aircraft messages) -b <filter> filter output by label (ex: -b "H1:Q0" : only output messages with label H1 or Q0) -e don't output empty messages (ie : _d,Q0, etc ...) + -h show the full list of available options and exit -i <stationid> station id used in statsd reports and network destinations (default: hostname) -t <seconds> set forget time (TTL) in seconds for monitor mode (default: 600) + -T enable timestamping for non-live inputs (enabled by default for SDR/ALSA, disabled for others) + -V show version number and exit --statsd host=HOST,port=1234 enable statsd reporting to host "HOST" (name or IP) on port "1234" --output FORMAT:DESTINATION:DESTPARAMS (see below for supported output formats and destinations. DESTPARAMS are coma-separated: ',') @@ -118,6 +122,9 @@ Multiple instances of `--output` can be used. At least one is required. See `--output help` for a list of supported formats and destinations. Not all combinations of formats and destinations are valid, acarsdec will complain if an invalid combination is chosen. +When using several outputs with the same formats, grouping them by same output format on the commandline will reduce acarsdec CPU usage. +(e.g. prefer `--output json:DEST1:PARAMS1 --output json:DEST2:PARAM2 --output full:file --output full:DEST3:PARAM3` instead of +`--output json:DEST1:PARAMS1 --output full:file --output json:DEST2:PARAM2 --output full:DEST3:PARAM3`) One (and **only one**) input source must also be selected, see below. Options (including frequencies) can be provided in any order. @@ -196,32 +203,47 @@ -g <linearity_gain> set linearity gain [0-21] (default: 18) ``` -#### SDRplay (API version 3.15 and above - untested/unmaintained, help wanted) +#### SDRplay (API version 3.15 and above - barely tested, help wanted) ``` --sdrplay <device> decode from sdrplay with sequence number <device> or hex serial <device> - -a <antenna> set antenna port to use (default: first antenna) + -a <antenna> set antenna port to use, A B or C (default: first antenna) -B <bias> enable (1) or disable (0) the bias tee (default is 0) -c <freq> set center frequency to tune to in MHz, e.g. 131.800 (default: automatic) -G <gRdB> gain reduction in dB's, range 20 .. 59 (default: -100 is autogain) - -L <lnaState> set the lnaState (depends on the device) - -m <rateMult> set sample rate multiplier: sample rate is <rateMult> * 12000 S/s (default: 252) + -L <lnaState> set the lnaState, works together with gRdB (depends on the device) + -m <rateMult> set sample rate multiplier: sample rate is <rateMult> * 12000 S/s (default: automatic) -p <ppm> set ppm frequency correction (default: 0) ``` -All SDR sources described above expect a list of frequencies `<f1> [<f2> [...]]` to decode from, expressed in decimal MHz -e.g. `131.525 131.725 131.825`. +All SDR sources described above as well as IQ file input below expect a list of frequencies `<f1> [<f2> [...]]` +to decode from, expressed in decimal MHz e.g. `131.525 131.725 131.825`. **NOTE: all the SDR sources set the input gain to "automatic", which generally means "maximum" and is a bad idea for good ACARS message reception. It is best to tune the gain to maximize Signal-to-Noise Ratio and reduce the number of saturated messages (where the received level is over 0dB).** +#### IQ file + +``` + --iqfile file=<file>,sr=<N>,format=<F> + decode raw IQ input from <file>, with samplerate <N> S/s and format <F>: 'U8' (default) or 'S16' + Best performance is achieved if <N> is an integer multiple of 12000 + -c <freq> set the center frequency of the IQ data in MHz, e.g. 131.800 (required) + -T enable timestamping +``` + +See `--iqfile help` for more details. + +NB: `<file>` can be `/dev/stdin` to process piped-in data. + #### audio file All formats supported by libsndfile can be processed. ``` --sndfile <file> decode from <file>, which must be a libsndfile supported format and sampled at a multiple of 12kHz + -T enable timestamping ``` To decode raw audio, extra parameters must be provided. Example: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/acars.c new/acarsdec-4.7/acars.c --- old/acarsdec-4.6/acars.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/acars.c 2026-11-03 12:42:53.000000000 +0100 @@ -345,7 +345,7 @@ case SOH1: if (likely(r == SOH)) { if (ch->blk == NULL) { - ch->blk = malloc(sizeof(*ch->blk)); + ch->blk = calloc(1, sizeof(*ch->blk)); if (unlikely(ch->blk == NULL)) { perror(NULL); break; // fail diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/acarsdec.c new/acarsdec-4.7/acarsdec.c --- old/acarsdec-4.6/acarsdec.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/acarsdec.c 2026-11-03 12:42:53.000000000 +0100 @@ -1,6 +1,6 @@ /* * Copyright (c) 2007-2022,2025 Thierry Leconte - * Copyright (c) 2024 Thibaut VARENE + * Copyright (c) 2024-2026 Thibaut VARENE * * * This code is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include <unistd.h> #include <limits.h> #include <err.h> +#include <math.h> #ifdef HAVE_LIBACARS #include <libacars/version.h> #endif @@ -56,6 +57,7 @@ #ifdef WITH_SNDFILE #include "soundfile.h" #endif +#include "iqfile.h" runtime_t R = { .mdly = 600, @@ -72,6 +74,7 @@ #ifdef WITH_SNDFILE "[--sndfile <file.wav>]", #endif + "[--iqfile <params>]", }; const char *const sdropts[] = { @@ -114,28 +117,40 @@ fprintf(stderr, " | "); } fprintf(stderr, " <f1> [<f2> [...]] ]\n\n"); - fprintf(stderr, " <f1> [<f2> [...]] are given in decimal MHz, e.g. 131.525"); + fprintf(stderr, " <f1> [<f2> [...]] are given in decimal MHz, e.g. 131.525\n"); } } -static void usage(void) +static void versstr(void) { fprintf(stderr, "Acarsdec %s Copyright (c) 2007-2022,2025 Thierry Leconte, (c) 2024-2026 Thibaut VARENE\n", ACARSDEC_VERSION); #ifdef HAVE_LIBACARS fprintf(stderr, "(libacars %s)\n", LA_VERSION); #endif - fprintf(stderr, "\nUsage: acarsdec [-A] [-b 'labels,..'] [-e] [-i station_id] [-t secs] [--statsd host=ip,port=1234] --output FORMAT:DESTINATION:PARAMS [--output ...]"); +} + +static void usage(void) +{ + fprintf(stderr, "\nUsage: acarsdec [-AehTV] [-b 'labels,..'] [-i station_id] [-t secs] [--statsd host=ip,port=1234] --output FORMAT:DESTINATION:PARAMS [--output ...]"); #ifdef HAVE_LIBACARS fprintf(stderr, " [--skip-reassembly]"); #endif print_available_ins(); +} + +static void long_usage(void) { + versstr(); + usage(); fprintf(stderr, - "\n\n" + "\n" " -A\t\t\t: don't output uplink messages (ie : only aircraft messages)\n" " -b <filter>\t\t: filter output by label (ex: -b \"H1:Q0\" : only output messages with label H1 or Q0)\n" " -e\t\t\t: don't output empty messages (ie : _d,Q0, etc ...)\n" + " -h \t\t\t: show the full list of available options and exit\n" " -i <stationid>\t\t: station id used in acarsdec network format (default: hostname)\n" " -t <seconds>\t\t: set forget time (TTL) to <seconds> for flight routes (affects monitor and routejson, default: 600)\n" + " -T \t\t\t: enable timestamping for non-live inputs (enabled by default for SDR/ALSA, disabled for others)\n" + " -V \t\t\t: show version number and exit\n" #ifdef HAVE_LIBACARS " --skip-reassembly\t: disable reassembling fragmented ACARS messages\n" #endif @@ -148,7 +163,7 @@ #endif #ifdef WITH_SNDFILE fprintf(stderr, "\n --sndfile <file>\t: decode from <file> sampled at a multiple of %u Hz\n", INTRATE); - fprintf(stderr, " see \"--sndfile help\" for details\n"); + fprintf(stderr, "\t\t\t see \"--sndfile help\" for details\n"); #endif #ifdef WITH_RTL fprintf(stderr, @@ -172,12 +187,12 @@ fprintf(stderr, "\n sdrplayopts:\n" " --sdrplay <device>\t: decode from sdrplay with sequence number <device> or hex serial <device>\n" - " -a <antenna>\t\t: set antenna port to use (default: first antenna)\n" + " -a <antenna>\t\t: set antenna port to use, A B or C (default: first antenna)\n" " -B <bias>\t\t: enable (1) or disable (0) the bias tee (default is 0)\n" " -c <freq>\t\t: set center frequency to tune to in MHz, e.g. 131.800 (default: automatic)\n" " -G <gRdB>\t\t: gain reduction in dB's, range 20 .. 59 (default: -100 is autogain)\n" - " -L <lnaState>\t\t: set the lnaState (depends on the device)\n" - " -m <rateMult>\t\t: set sample rate multiplier: sample rate is <rateMult> * 12000 S/s (default: 252)\n" + " -L <lnaState>\t\t: set the lnaState, works together with gRdB (depends on the device)\n" + " -m <rateMult>\t\t: set sample rate multiplier: sample rate is <rateMult> * 12000 S/s (default: automatic)\n" " -p <ppm>\t\t: set ppm frequency correction (default: 0)\n"); #endif #ifdef WITH_SOAPY @@ -190,6 +205,7 @@ " -m <rateMult>\t\t: set sample rate multiplier: sample rate is <rateMult> * 12000 S/s (default: automatic)\n" " -p <ppm>\t\t: set ppm frequency correction (default: 0)\n"); #endif + fprintf(stderr, "\n --iqfile <params>\t: decode from raw IQ input, see \"--iqfile help\" for details\n"); exit(1); } @@ -286,6 +302,7 @@ #ifdef WITH_SOAPY { "soapysdr", required_argument, NULL, IN_SOAPY }, #endif + { "iqfile", required_argument, NULL, IN_IQFILE }, { "verbose", no_argument, NULL, 'v' }, { "output", required_argument, NULL, -2 }, // -1 is EOF { "statsd", required_argument, NULL, -3 }, @@ -295,7 +312,6 @@ { NULL, 0, NULL, 0 } }; char sys_hostname[HOST_NAME_MAX + 1]; - char *lblf = NULL; char *inarg = NULL, *statsdarg = NULL; gethostname(sys_hostname, HOST_NAME_MAX); @@ -303,7 +319,7 @@ R.idstation = strdup(sys_hostname); res = 0; - while ((c = getopt_long(argc, argv, "hvt:g:m:a:Aep:c:i:L:G:b:B:", long_opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "hvt:g:m:a:Aep:c:i:L:G:b:B:TV", long_opts, NULL)) != EOF) { switch (c) { case -2: res = setup_output(optarg); @@ -325,7 +341,13 @@ R.mdly = atoi(optarg); break; case 'b': - lblf = optarg; + build_label_filter(optarg); + break; + case IN_IQFILE: + if (R.inmode) + errx(-1, "Only 1 input allowed"); + R.inmode = IN_IQFILE; + inarg = optarg; break; #ifdef WITH_ALSA case IN_ALSA: @@ -355,6 +377,9 @@ case 'B': R.bias = atoi(optarg); break; + case 'T': + R.ts = true; + break; #ifdef WITH_RTL case IN_RTL: if (R.inmode) @@ -399,7 +424,7 @@ break; #endif case 'c': - R.Fc = (unsigned int)(1000000 * atof(optarg)); + R.Fc = (unsigned int)lround(1000000 * atof(optarg)); break; case 'A': R.airflt = 1; @@ -411,14 +436,22 @@ free(R.idstation); R.idstation = strdup(optarg); break; + case 'V': + versstr(); + exit(0); + break; + case '?': + usage(); + exit(1); + break; case 'h': default: - usage(); + long_usage(); } } if (R.inmode == IN_NONE) { - fprintf(stderr, "Missing input\n"); + fprintf(stderr, "Missing input\n\n"); usage(); } @@ -460,6 +493,9 @@ res = initSoapy(inarg); break; #endif + case IN_IQFILE: + res = initIqfile(inarg); + break; default: res = -1; } @@ -467,8 +503,6 @@ if (res) errx(res, "Unable to init input"); - build_label_filter(lblf); - res = initOutputs(); if (res) errx(res, "Unable to init outputs"); @@ -548,6 +582,9 @@ res = runSoapySample(); break; #endif + case IN_IQFILE: + res = runIqfileSample(); + break; default: res = -1; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/acarsdec.h new/acarsdec-4.7/acarsdec.h --- old/acarsdec-4.6/acarsdec.h 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/acarsdec.h 2026-11-03 12:42:53.000000000 +0100 @@ -31,7 +31,7 @@ #endif #ifndef VERSION -#define ACARSDEC_VERSION "4.6" +#define ACARSDEC_VERSION "4.7" #else #define ACARSDEC_VERSION VERSION #endif @@ -107,6 +107,7 @@ float complex *restrict inb; // oversampled bit circular buffer float complex mix_phase; // arbitrary-rate SDR NCO state float complex mix_step; // arbitrary-rate SDR NCO increment + float mix_scale; // arbitrary-rate SDR NCO scale factor (for renormalization) float MskPhi; // MSK oscillator phase float MskDphi; // MSK oscillator phase offset float MskDf; // MSK oscillator phase offset integral @@ -116,7 +117,6 @@ float MskClk; // MSK bit clock unsigned int MskS; // MSK sample index unsigned int idx; // `inb` buffer index - unsigned int mix_count; // arbitrary-rate SDR NCO renormalization counter unsigned int Fr; // channel frequency (in Hz) @@ -140,6 +140,7 @@ unsigned int nbch; volatile bool running; bool verbose; + bool ts; // timestamp output // used only in output bool airflt; @@ -152,7 +153,7 @@ output_t *outputs; char *idstation; - enum { IN_NONE = 0, IN_ALSA, IN_SNDFILE, IN_RTL, IN_AIR, IN_SDRPLAY, IN_SOAPY } inmode; + enum { IN_NONE = 0, IN_ALSA, IN_SNDFILE, IN_RTL, IN_AIR, IN_SDRPLAY, IN_SOAPY, IN_IQFILE } inmode; // used only during setup float gain; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/air.c new/acarsdec-4.7/air.c --- old/acarsdec-4.6/air.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/air.c 2026-11-03 12:42:53.000000000 +0100 @@ -88,6 +88,8 @@ uint64_t *airspy_device_list = NULL; uint32_t required_rate; + R.ts = true; + if (!R.gain) R.gain = 18; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/alsa.c new/acarsdec-4.7/alsa.c --- old/acarsdec-4.6/alsa.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/alsa.c 2026-11-03 12:42:53.000000000 +0100 @@ -111,6 +111,8 @@ return 1; } + R.ts = true; + return (0); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/iqfile.c new/acarsdec-4.7/iqfile.c --- old/acarsdec-4.6/iqfile.c 1970-01-01 01:00:00.000000000 +0100 +++ new/acarsdec-4.7/iqfile.c 2026-11-03 12:42:53.000000000 +0100 @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2026 Thibaut VARENE + * + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "acarsdec.h" +#include "lib.h" + +#define ERRPFX "ERROR: IQFILE: " +#define MAXNSAMPLES 4096 + + +static FILE *iqfp; +static enum iqfile_format { IQ_U8, IQ_S16 } iqfmt; +static unsigned int iqrate; + +static void usage(void) +{ + fprintf(stderr, + "iqfile input accepts as parameters the following coma-separated elements (in any order):\n" + " - the 'file=' followed by a filename (mandatory),\n" + " - the 'sr=' argument specifying the source samplerate which must be at least %d Sps (mandatory),\n" + " - and the optional 'format=' argument, which is one of 'U8' (default), 'S16'.\n" + "The center and target frequencies must also be provided on the command line.\n" + "Best performance is obtained when using a sample rate which is a multiple of %d Sps.\n" + "examples:\n" + " 'file=data.raw,sr=1200000' to process unsigned 8-bit raw IQ data sampled at 1.2MSps\n" + " 'format=S16,sr=2000000,file=data.raw' to process signed 16-bit raw IQ data sample at 2MSps\n" + "\n", + 4*INTRATE, INTRATE); + + exit(1); +} + +int initIqfile(char *optarg) +{ + char *format = NULL, *fname = NULL, *sr = NULL; + struct params_s iqp[] = { + { .name = "sr", .valp = &sr, }, + { .name = "format", .valp = &format, }, + { .name = "file", .valp = &fname, }, + }; + char *retp; + float scale = 127.5F; // U8 default + + if (!strcmp("help", optarg)) + usage(); + + retp = parse_params(&optarg, iqp, ARRAY_SIZE(iqp)); + if (retp) { + fprintf(stderr, ERRPFX "invalid parameter '%s'\n", retp); + return -1; + } + + if (!fname) { + fprintf(stderr, ERRPFX "missing filename\n"); + return -1; + } + + if (!sr) { + fprintf(stderr, ERRPFX "missing samplerate\n"); + return -1; + } + else { + iqrate = strtoul(sr, &sr, 0); + if ('\0' != *sr) { + fprintf(stderr, ERRPFX "invalid sr value '%s'\n", sr); + return -1; + } + if (iqrate < 4*INTRATE) { + fprintf(stderr, ERRPFX "invalid sr value '%d'\n", iqrate); + return -1; + } + } + + if (format) { + if (!strcmp("U8", format)) { + iqfmt = IQ_U8; + scale = 127.5F; + } + else if (!strcmp("S16", format)) { + iqfmt = IQ_S16; + scale = 32768.0F; + } + else { + fprintf(stderr, ERRPFX "invalid format value '%s'\n", format); + return -1; + } + } + + if (!R.Fc) { + fprintf(stderr, ERRPFX "missing center frequency\n"); + return -1; + } + + if (!R.minFc) { + fprintf(stderr, ERRPFX "missing input frequencies\n"); + return -1; + } + + iqfp = fopen(fname, "r"); + if (!iqfp) { + fprintf(stderr, ERRPFX "could not open %s: %s\n", fname, strerror(errno)); + return 1; + } + + return channels_init_sdr_resample(R.Fc, iqrate, scale); +} + + +static void process_samples(const void *buf, size_t nread) +{ + const unsigned int mult = iqrate / INTRATE; + float complex phasors[mult]; + const uint8_t *pu8 = buf; + const int16_t *ps16 = buf; + + if (unlikely(nread % 2)) { + fprintf(stderr, ERRPFX "incomplete read\n"); + return; + } + + while (nread) { + unsigned int ind, lim = unlikely(nread / 2 < mult) ? nread / 2 : mult; + float i, q; + + switch (iqfmt) { + case IQ_U8: + for (ind = 0; ind < lim; ind++) { + i = (float)(*pu8++) - 127.5F; + q = (float)(*pu8++) - 127.5F; + phasors[ind] = i + q * I; + } + break; + case IQ_S16: + for (ind = 0; ind < lim; ind++) { + i = (float)(*ps16++); + q = (float)(*ps16++); + phasors[ind] = i + q * I; + } + break; + default: + return; + } + + channels_mix_phasors_resample(phasors, lim, iqrate); + nread -= lim * 2; + } +} + +int runIqfileSample(void) +{ + size_t nread, ssize, bufsize = MAXNSAMPLES * 2; + void *buf; + + switch (iqfmt) { + case IQ_U8: + ssize = sizeof(uint8_t); + break; + case IQ_S16: + ssize = sizeof(int16_t); + break; + default: + return -1; + } + + buf = malloc(bufsize * ssize); + if (!buf) { + perror(ERRPFX "runIqfileSample()"); + return -1; + } + + do { + nread = fread(buf, ssize, bufsize, iqfp); + if (!nread) + goto out; + + process_samples(buf, nread); + } while (R.running); + +out: + free(buf); + fclose(iqfp); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/iqfile.h new/acarsdec-4.7/iqfile.h --- old/acarsdec-4.6/iqfile.h 1970-01-01 01:00:00.000000000 +0100 +++ new/acarsdec-4.7/iqfile.h 2026-11-03 12:42:53.000000000 +0100 @@ -0,0 +1,7 @@ +#ifndef iqfile_h +#define iqfile_h + +int initIqfile(char *optarg); +int runIqfileSample(void); + +#endif /* iqfile_h */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/label.c new/acarsdec-4.7/label.c --- old/acarsdec-4.6/label.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/label.c 2026-11-03 12:42:53.000000000 +0100 @@ -4,24 +4,13 @@ #include <string.h> #include "label.h" -static char *lblfilter[1024]; +#define MAX_FILTERS 1024 +static char * lblfilter[MAX_FILTERS]; void build_label_filter(char *arg) { - int i = 0; - char *aptr; - - lblfilter[0] = NULL; - if (arg == NULL) - return; - - aptr = strtok(strdup(arg), ":"); - while (aptr) { - lblfilter[i] = aptr; - i++; - aptr = strtok(NULL, ":"); - } - lblfilter[i] = NULL; + char **ap; + for (ap = lblfilter; ap < &lblfilter[MAX_FILTERS-2] && (*ap = strsep(&arg, ":")); ap++); } int label_filter(char *lbl) @@ -31,12 +20,9 @@ if (lblfilter[0] == NULL) return 1; - i = 0; - while (lblfilter[i]) { + for (i=0; lblfilter[i]; i++) if (strcmp(lbl, lblfilter[i]) == 0) return 1; - i++; - } return 0; } @@ -387,6 +373,9 @@ int DecodeLabel(acarsmsg_t *msg, oooi_t *oooi) { + if (!msg->txt) + return 0; + switch (msg->label[0]) { case '1': switch (msg->label[1]) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/lib.c new/acarsdec-4.7/lib.c --- old/acarsdec-4.6/lib.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/lib.c 2026-11-03 12:42:53.000000000 +0100 @@ -65,14 +65,19 @@ unsigned int find_centerfreq_rate(unsigned int minFc, unsigned int maxFc, unsigned int input_rate) { - if (R.Fc) - return R.Fc; + if (!minFc) { + fprintf(stderr, "ERROR: need a least one input frequency\n"); + return 0; + } if ((maxFc - minFc) > input_rate - 4 * INTRATE) { - fprintf(stderr, "Frequencies too far apart\n"); + fprintf(stderr, "ERROR: input frequencies too far apart\n"); return 0; } + if (R.Fc) + return R.Fc; + return (maxFc + minFc) / 2; } @@ -120,20 +125,23 @@ * @param Fc the chosen center frequency * @param input_rate the chosen sampling rate * @param scale the phasor scale (e.g. 1.0F for complex float32 phasors; 32768.0F for int16 phasors, ...) + * @note falls back to channels_init_sdr() if input_rate is an integer multiple of INTRATE */ int channels_init_sdr_resample(unsigned int Fc, unsigned int input_rate, float scale) { unsigned int n; - float scale_factor; + float correctionPhase, scale_factor; if (!input_rate) return 1; + if (!(input_rate % INTRATE)) + return channels_init_sdr(Fc, input_rate / INTRATE, scale); + scale_factor = ((float)INTRATE / (float)input_rate) / scale; for (n = 0; n < R.nbch; n++) { channel_t *ch = &R.channels[n]; - float correctionPhase; ch->dm_buffer = malloc(DMBUFSZ * sizeof(*ch->dm_buffer)); if (ch->dm_buffer == NULL) { @@ -145,9 +153,8 @@ vprerr("#%d: Fc = %uHz, Fr = %uHz, phase = % f (%+dHz)\n", n + 1, Fc, ch->Fr, correctionPhase, (signed)(ch->Fr - Fc)); - ch->mix_phase = scale_factor; + ch->mix_scale = ch->mix_phase = scale_factor; ch->mix_step = cexpf(correctionPhase * -I); - ch->mix_count = 0; } return 0; @@ -271,17 +278,22 @@ * @note this implementation is an order of magnitude more CPU-intensive than * the regular `channels_mix_phasors()` implementation and should only be used * for inputs that do not support sample rates which are multiples of INTRATE. + * @note this function, like its init() counterpart, falls back to channels_mix_phasors() + * if the provided samplerate is an integer multiple of INTRATE. */ void channels_mix_phasors_resample(const float complex *restrict phasors, unsigned int len, unsigned int input_rate) { static float complex *restrict D = NULL; - static unsigned int phase_acc = 0; + static unsigned int mix_count = 0, phase_acc = 0; const unsigned int nbch = R.nbch; unsigned int i, n; - if (unlikely(!len || !input_rate)) + if (unlikely(!len)) return; + if (!(input_rate % INTRATE)) + return channels_mix_phasors(phasors, len, input_rate / INTRATE); + if (unlikely(!D)) { // Allocate one accumlator per channel D = calloc(nbch, sizeof(*D)); @@ -295,24 +307,11 @@ for (n = 0; n < nbch; n++) { channel_t *ch = &R.channels[n]; - float complex mixed; float phase_mag; // Shift this channel from its RF offset down to baseband // and add the result into the current output accumulator. - mixed = sample * ch->mix_phase; - -#if defined(DEBUG) - // The below can be used to debug if the renormalization isn't working - // correctly or if the data is bad (the sample is NaN or inf). - if (!isfinite(crealf(mixed)) || !isfinite(cimagf(mixed))) { - vprerr("WARNING: MIX: resetting non-finite mixed sample on channel #%u\n", n + 1); - ch->mix_phase = cabsf(ch->mix_phase) > 0.0F ? ch->mix_phase / cabsf(ch->mix_phase) * ((float)INTRATE / (float)input_rate) : ((float)INTRATE / (float)input_rate); - ch->mix_count = 0; - mixed = 0.0F; - } -#endif - D[n] += mixed; + D[n] += sample * ch->mix_phase; // Advance the oscillator so the next sample uses the correct // phase for this channels frequency offset. @@ -320,15 +319,14 @@ // Every MIX_RENORM_INTERVAL, renormalize the phase magnitude, preventing // drift towards zero/infinity. Floating point roundoff can slowly change - // the magnitude of mix_phase. This keeps the oscillator stable. Prior to - // implementing this check, after several minutes of runtime, the mix_phase - // could/would trend towards zero/inf. - if ((++ch->mix_count & (MIX_RENORM_INTERVAL - 1U)) == 0) { + // the magnitude of mix_phase. This keeps the oscillator stable. + if ((mix_count & (MIX_RENORM_INTERVAL - 1U)) == 0) { phase_mag = cabsf(ch->mix_phase); ch->mix_phase /= phase_mag; - ch->mix_phase *= ((float)INTRATE / (float)input_rate); + ch->mix_phase *= ch->mix_scale; } } + mix_count++; // Convert the arbitrary rate into a steady 12kHz output, // and when enough has accumulated, emit a decoder-rate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/output.c new/acarsdec-4.7/output.c --- old/acarsdec-4.6/output.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/output.c 2026-11-03 12:42:53.000000000 +0100 @@ -397,7 +397,7 @@ else len += snprintf(buf + len, bufsz - len, "[#%1d (L:%+5.1f/%.1f E:%1d) ", chn + 1, msg->lvl, msg->nf, msg->err); - if (R.inmode != IN_SNDFILE) + if (R.ts) len += fmt_date(tv, buf + len, bufsz - len); len += snprintf(buf + len, bufsz - len, " --------------------------------\n"); @@ -473,7 +473,7 @@ len = snprintf(buf, bufsz, "#%1d (L:%+5.1f/%.1f E:%1d) ", chn + 1, msg->lvl, msg->nf, msg->err); - if (R.inmode != IN_SNDFILE) + if (R.ts) len += fmt_date(tv, buf + len, bufsz - len); len += snprintf(buf + len, bufsz - len, " %7s %6s %1c %2s %4s %s", msg->addr, msg->fid, msg->mode, msg->label, msg->no, txt); @@ -565,8 +565,10 @@ if (json_obj == NULL) return ok; - double t = (double)tv.tv_sec + ((double)tv.tv_usec) / 1e6; - cJSON_AddNumberToObject(json_obj, "timestamp", t); + if (R.ts) { + double t = (double)tv.tv_sec + ((double)tv.tv_usec) / 1e6; + cJSON_AddNumberToObject(json_obj, "timestamp", t); + } if (R.idstation[0]) cJSON_AddStringToObject(json_obj, "station_id", R.idstation); cJSON_AddNumberToObject(json_obj, "channel", chn); @@ -660,8 +662,10 @@ if (json_obj == NULL) return 0; - double t = (double)tv.tv_sec + ((double)tv.tv_usec) / 1e6; - cJSON_AddNumberToObject(json_obj, "timestamp", t); + if (R.ts) { + double t = (double)tv.tv_sec + ((double)tv.tv_usec) / 1e6; + cJSON_AddNumberToObject(json_obj, "timestamp", t); + } if (R.idstation[0]) cJSON_AddStringToObject(json_obj, "station_id", R.idstation); cJSON_AddStringToObject(json_obj, "flight", fl->fid); @@ -871,42 +875,44 @@ if (R.emptymsg && (msg.txt == NULL || msg.txt[0] == '\0')) return; + int len = 0, prevfmt = 0; // for now and until we see contention, we don't bother using a separate thread per output. KISS. for (out = R.outputs; out; out = out->next) { - int len = 0; - switch (out->fmt) { - case FMT_MONITOR: - len = fmt_monitor(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); - break; - case FMT_ONELINE: - len = fmt_oneline(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); - break; - case FMT_FULL: - len = fmt_msg(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); - break; - case FMT_NATIVE: - len = fmt_sv(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); - break; - case FMT_PP: - len = fmt_pp(&msg, fmtbuf, FMTBUFLEN); - break; + if (prevfmt != out->fmt) { // avoid reformatting if same format is used consecutively + switch (out->fmt) { + case FMT_MONITOR: + len = fmt_monitor(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_ONELINE: + len = fmt_oneline(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_FULL: + len = fmt_msg(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_NATIVE: + len = fmt_sv(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_PP: + len = fmt_pp(&msg, fmtbuf, FMTBUFLEN); + break; #ifdef HAVE_CJSON - case FMT_ROUTEJSON: - len = fl ? fmt_routejson(fl, blk->tv, fmtbuf, FMTBUFLEN) : -1; - break;; - case FMT_JSON: - len = fmt_json(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); - break; + case FMT_ROUTEJSON: + len = fl ? fmt_routejson(fl, blk->tv, fmtbuf, FMTBUFLEN) : -1; + break;; + case FMT_JSON: + len = fmt_json(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; #endif /* HAVE_CJSON */ - default: - continue; + default: + continue; + } } - // NB if the same format is used for multiple outputs, the buffer will be recomputed each time. Deemed acceptable - if (len <= 0) continue; + prevfmt = out->fmt; + switch (out->dst) { case DST_FILE: Filewrite(fmtbuf, len, out->priv); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/rtl.c new/acarsdec-4.7/rtl.c --- old/acarsdec-4.6/rtl.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/rtl.c 2026-11-03 12:42:53.000000000 +0100 @@ -139,6 +139,8 @@ if (!optarg) return 1; // cannot happen with getopt() + R.ts = true; + if (!R.rateMult) { m = min_multiplier(R.minFc, R.maxFc); R.rateMult = (m > 80U) ? m : 80U; // rtl has a hole in available sr between 300kHz and 900kHZ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/sdrplay.c new/acarsdec-4.7/sdrplay.c --- old/acarsdec-4.6/sdrplay.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/sdrplay.c 2026-11-03 12:42:53.000000000 +0100 @@ -1,6 +1,7 @@ /* * Copyright (c) 2015 Thierry Leconte * Copyright (c) 2025 Franco Venturi + * Copyright (c) 2026 Thibaut VARENE * * * This code is free software; you can redistribute it and/or modify @@ -28,13 +29,6 @@ #include "lib.h" #include <sdrplay_api.h> -// choose 252 as the 12k (INTRATE) multiplier because 12k * 252 = 3024Msps -// (which is reasonable) -// the specific reason for 252 is that the SDRplay API RX callback size -// (numSamples) is 1008, which is an integer multiple of 252, and that is -// supposed to improve performance with acarsdec -#define SDRPLAY_MULT 252U - #define ERRPFX "ERROR: SDRplay: " #define WARNPFX "WARNING: SDRplay: " @@ -50,6 +44,8 @@ if (!optarg) return 1; // cannot happen with getopt() + R.ts = true; + char *serialNumber = NULL; int rspSequenceNumber = -1; if (strlen(optarg) == 10 && strspn(optarg, "0123456789ABCDEF") == 10) { @@ -62,7 +58,9 @@ } if (!R.rateMult) - R.rateMult = SDRPLAY_MULT; + R.rateMult = min_multiplier(R.minFc, R.maxFc); + + // SDRplay hardware sampling rate starts at 2MSps, lower rates are achieved via decimation unsigned int sr = INTRATE * R.rateMult; int decimation = 1; while (sr < 2000000 && decimation <= 32) { @@ -73,9 +71,6 @@ if (!R.gRdB) R.gRdB = -100; // AGC - if (!R.lnaState) - R.lnaState = 2; - Fc = find_centerfreq(R.minFc, R.maxFc, R.rateMult); if (Fc == 0) return 5; @@ -94,29 +89,24 @@ err = sdrplay_api_ApiVersion(&ver); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "ApiVersion failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_Close(); - return -2; + goto fail; } if (ver != SDRPLAY_API_VERSION) { fprintf(stderr, ERRPFX "API version mismatch - expected=%.2f found=%.2f\n", SDRPLAY_API_VERSION, ver); - sdrplay_api_Close(); - return -3; + goto fail; } /* select device */ err = sdrplay_api_LockDeviceApi(); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "LockDeviceApi failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_Close(); - return -4; + goto fail; } unsigned int ndevices = sizeof(devices) / sizeof(devices[0]); err = sdrplay_api_GetDevices(devices, &ndevices, ndevices); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "GetDevices failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_UnlockDeviceApi(); - sdrplay_api_Close(); - return -5; + goto unlockfail; } int deviceIndex = -1; if (serialNumber != NULL) { @@ -133,9 +123,7 @@ } if (deviceIndex == -1) { fprintf(stderr, ERRPFX "RSP not found or not available\n"); - sdrplay_api_UnlockDeviceApi(); - sdrplay_api_Close(); - return -6; + goto unlockfail; } device = devices[deviceIndex]; @@ -143,37 +131,22 @@ if (device.hwVer == SDRPLAY_RSPduo_ID) { if ((device.rspDuoMode & sdrplay_api_RspDuoMode_Single_Tuner) != sdrplay_api_RspDuoMode_Single_Tuner) { fprintf(stderr, ERRPFX "RSPduo single tuner mode not available\n"); - sdrplay_api_UnlockDeviceApi(); - sdrplay_api_Close(); - return -7; + goto unlockfail; } device.rspDuoMode = sdrplay_api_RspDuoMode_Single_Tuner; - if (R.antenna != NULL) { - if (strcmp(R.antenna, "Tuner 1 50 ohm") == 0 || strcmp(R.antenna, "High Z") == 0) { - device.tuner = sdrplay_api_Tuner_A; - } else if (strcmp(R.antenna, "Tuner 2 50 ohm") == 0) { - device.tuner = sdrplay_api_Tuner_B; - } else { - device.tuner = sdrplay_api_Tuner_A; - } - } - device.rspDuoSampleFreq = 0; + device.tuner = sdrplay_api_Tuner_A; } err = sdrplay_api_SelectDevice(&device); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "SelectDevice failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_UnlockDeviceApi(); - sdrplay_api_Close(); - return -8; + goto unlockfail; } err = sdrplay_api_UnlockDeviceApi(); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "UnlockDeviceApi failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_ReleaseDevice(&device); - sdrplay_api_Close(); - return -9; + goto unlockfail; } /* select device settings */ @@ -181,9 +154,7 @@ err = sdrplay_api_GetDeviceParams(device.dev, &device_params); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "GetDeviceParams failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_ReleaseDevice(&device); - sdrplay_api_Close(); - return -10; + goto fail; } device_params->devParams->mode = sdrplay_api_BULK; @@ -193,78 +164,98 @@ rx_channel_params->ctrlParams.decimation.enable = decimation > 1; rx_channel_params->ctrlParams.decimation.decimationFactor = decimation; rx_channel_params->tunerParams.ifType = sdrplay_api_IF_Zero; + rx_channel_params->tunerParams.rfFreq.rfHz = Fc; + + uint32_t bw = ((R.maxFc - R.minFc) + 2 * INTRATE) / 1000U; + sdrplay_api_Bw_MHzT bwt = sdrplay_api_BW_Undefined; + if (bw <= 200U) bwt = sdrplay_api_BW_0_200; + else if (bw <= 300U) bwt = sdrplay_api_BW_0_300; + else if (bw <= 600U) bwt = sdrplay_api_BW_0_600; + else if (bw <= 1536U) bwt = sdrplay_api_BW_1_536; + else if (bw <= 5000U) bwt = sdrplay_api_BW_5_000; + else if (bw <= 6000U) bwt = sdrplay_api_BW_6_000; + else if (bw <= 7000U) bwt = sdrplay_api_BW_7_000; + else if (bw <= 8000U) bwt = sdrplay_api_BW_8_000; + rx_channel_params->tunerParams.bwType = bwt; + + vprerr("Setting center freq: %.4f MHz, sample rate: %.4f MS/s, decimation factor: %d, bandwidth: %d kHZ\n", + Fc / 1e6, sr / 1e6, decimation, bwt); + if (R.gRdB == -100) { - rx_channel_params->ctrlParams.agc.enable = sdrplay_api_AGC_100HZ; + rx_channel_params->ctrlParams.agc.enable = sdrplay_api_AGC_5HZ; + rx_channel_params->ctrlParams.agc.setPoint_dBfs = -30; // from dumpvdl2 + vprerr("AGC enabled, setpoint at -30 dBFS\n"); } else { rx_channel_params->ctrlParams.agc.enable = sdrplay_api_AGC_DISABLE; rx_channel_params->tunerParams.gain.gRdB = R.gRdB; + rx_channel_params->tunerParams.gain.LNAstate = R.lnaState; + vprerr("IF gain reduction: %d, LNA state: %d\n", R.gRdB, R.lnaState); } - rx_channel_params->tunerParams.gain.LNAstate = R.lnaState; device_params->devParams->ppm = (double)(R.ppm); - rx_channel_params->tunerParams.rfFreq.rfHz = Fc; + if (R.antenna != NULL) { int antennaOK = 0; - if (device.hwVer == SDRPLAY_RSP2_ID) { - if (strcmp(R.antenna, "Antenna A") == 0) { - antennaOK = 1; - rx_channel_params->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_A; - rx_channel_params->rsp2TunerParams.amPortSel = sdrplay_api_Rsp2_AMPORT_2; - } else if (strcmp(R.antenna, "Antenna B") == 0) { - antennaOK = 1; - rx_channel_params->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_B; - rx_channel_params->rsp2TunerParams.amPortSel = sdrplay_api_Rsp2_AMPORT_2; - } else if (strcmp(R.antenna, "Hi-Z") == 0) { - antennaOK = 1; - rx_channel_params->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_A; - rx_channel_params->rsp2TunerParams.amPortSel = sdrplay_api_Rsp2_AMPORT_1; - } - } else if (device.hwVer == SDRPLAY_RSPduo_ID) { - if (strcmp(R.antenna, "High Z") == 0) { - antennaOK = 1; - rx_channel_params->rspDuoTunerParams.tuner1AmPortSel = sdrplay_api_RspDuo_AMPORT_1; - } else { - antennaOK = 1; - rx_channel_params->rspDuoTunerParams.tuner1AmPortSel = sdrplay_api_RspDuo_AMPORT_2; - } - } else if (device.hwVer == SDRPLAY_RSPdx_ID || device.hwVer == SDRPLAY_RSPdxR2_ID) { - if (strcmp(R.antenna, "Antenna A") == 0) { - antennaOK = 1; - device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_A; - } else if (strcmp(R.antenna, "Antenna B") == 0) { - antennaOK = 1; - device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_B; - } else if (strcmp(R.antenna, "Antenna C") == 0) { - antennaOK = 1; - device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_C; - } + switch (device.hwVer) { + case SDRPLAY_RSP2_ID: + if (strcmp(R.antenna, "A") == 0) { + antennaOK = 1; + rx_channel_params->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_A; + } else if (strcmp(R.antenna, "B") == 0) { + antennaOK = 1; + rx_channel_params->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_B; + } + break; + case SDRPLAY_RSPdx_ID: + case SDRPLAY_RSPdxR2_ID: + if (strcmp(R.antenna, "A") == 0) { + antennaOK = 1; + device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_A; + } else if (strcmp(R.antenna, "B") == 0) { + antennaOK = 1; + device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_B; + } else if (strcmp(R.antenna, "C") == 0) { + antennaOK = 1; + device_params->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_C; + } + break; + default: + fprintf(stderr, ERRPFX "cannot select antenna port: not supported\n"); + goto fail; } if (!antennaOK) { fprintf(stderr, ERRPFX "invalid antenna: %s\n", R.antenna); - sdrplay_api_ReleaseDevice(&device); - sdrplay_api_Close(); - return -11; + goto fail; } } if (R.bias) { - if (device.hwVer == SDRPLAY_RSP1A_ID || SDRPLAY_RSP1B_ID) { - rx_channel_params->rsp1aTunerParams.biasTEnable = R.bias; - } else if (device.hwVer == SDRPLAY_RSP2_ID) { - rx_channel_params->rsp2TunerParams.biasTEnable = R.bias; - } else if (device.hwVer == SDRPLAY_RSPduo_ID) { - rx_channel_params->rspDuoTunerParams.biasTEnable = R.bias; - } else if (device.hwVer == SDRPLAY_RSPdx_ID || device.hwVer == SDRPLAY_RSPdxR2_ID) { - device_params->devParams->rspDxParams.biasTEnable = R.bias; + switch (device.hwVer) { + case SDRPLAY_RSP1A_ID: + case SDRPLAY_RSP1B_ID: + rx_channel_params->rsp1aTunerParams.biasTEnable = R.bias; + break; + case SDRPLAY_RSP2_ID: + rx_channel_params->rsp2TunerParams.biasTEnable = R.bias; + break; + case SDRPLAY_RSPduo_ID: + rx_channel_params->rspDuoTunerParams.biasTEnable = R.bias; + break; + case SDRPLAY_RSPdx_ID: + case SDRPLAY_RSPdxR2_ID: + device_params->devParams->rspDxParams.biasTEnable = R.bias; + break; + default: + fprintf(stderr, WARNPFX "not enabling Bias-T: not supported\n"); } } - if (R.gRdB == -100) - fprintf(stderr, "SDRplay device selects freq %d and sets autogain and LNA state %d\n", Fc, R.lnaState); - else - fprintf(stderr, "SDRplay device selects freq %d and sets IF gain reduction %d and LNA state %d\n", - Fc, R.gRdB, R.lnaState); - return 0; + +unlockfail: + sdrplay_api_UnlockDeviceApi(); +fail: + sdrplay_api_Close(); + return -1; } static void sdrplayRXCallback(short *xi, @@ -291,9 +282,7 @@ err = sdrplay_api_Init(device.dev, &callbackFns, NULL); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "Init failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_ReleaseDevice(&device); - sdrplay_api_Close(); - return -1; + goto fail; } while (R.running) @@ -302,12 +291,14 @@ err = sdrplay_api_Uninit(device.dev); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "Uninit failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_ReleaseDevice(&device); - sdrplay_api_Close(); - return -2; + goto fail; } return 0; + +fail: + sdrplay_api_Close(); + return -1; } static unsigned int nextSampleNum = 0xffffffff; @@ -359,14 +350,7 @@ (void)cbContext; if (eventId == sdrplay_api_PowerOverloadChange) { - switch (params->powerOverloadParams.powerOverloadChangeType) { - case sdrplay_api_Overload_Detected: - fprintf(stderr, WARNPFX "power overload detected event\n"); - break; - case sdrplay_api_Overload_Corrected: - fprintf(stderr, WARNPFX "power overload correct event\n"); - break; - } + vprerr("power overload %s event\n", (params->powerOverloadParams.powerOverloadChangeType == sdrplay_api_Overload_Detected) ? "detected" : "corrected"); sdrplay_api_Update(device.dev, tuner, sdrplay_api_Update_Ctrl_OverloadMsgAck, sdrplay_api_Update_Ext1_None); } return; @@ -377,11 +361,9 @@ sdrplay_api_ErrT err; err = sdrplay_api_ReleaseDevice(&device); - if (err != sdrplay_api_Success) { - fprintf(stderr, ERRPFX "ReleaseDevice failed: %s\n", sdrplay_api_GetErrorString(err)); - sdrplay_api_Close(); - return -1; - } + if (err != sdrplay_api_Success) + fprintf(stderr, WARNPFX "ReleaseDevice failed: %s\n", sdrplay_api_GetErrorString(err)); + err = sdrplay_api_Close(); if (err != sdrplay_api_Success) { fprintf(stderr, ERRPFX "Close failed: %s\n", sdrplay_api_GetErrorString(err)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/soapy.c new/acarsdec-4.7/soapy.c --- old/acarsdec-4.6/soapy.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/soapy.c 2026-11-03 12:42:53.000000000 +0100 @@ -83,6 +83,8 @@ if (!optarg) return 1; // cannot happen after getopt() + R.ts = true; + dev = SoapySDRDevice_makeStrArgs(optarg); if (dev == NULL) { fprintf(stderr, ERRPFX "opening SoapySDR device using string \"%s\": %s\n", optarg, SoapySDRDevice_lastError()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acarsdec-4.6/soundfile.c new/acarsdec-4.7/soundfile.c --- old/acarsdec-4.6/soundfile.c 2026-06-18 08:44:42.000000000 +0200 +++ new/acarsdec-4.7/soundfile.c 2026-11-03 12:42:53.000000000 +0100 @@ -32,7 +32,7 @@ static SNDFILE *insnd; -static int usage(void) +static void usage(void) { fprintf(stderr, "sndfile input accepts as parameter:\n" @@ -48,10 +48,11 @@ " 'data.raw,subtype=2,channels=2,endian=little' to process a 2-channel, 16-bit little-endian PCM data\n" "\n" "NOTE: audio sample rate must be a multiple of %d Hz.\n" - "For raw audio, the rate multiplier can be specified via '-m' (default: 1)\n", + "For raw audio, the rate multiplier can be specified via '-m' (default: 1)\n" + "\n", INTRATE); - return 1; + exit(1); } int initSoundfile(char *optarg) @@ -91,7 +92,7 @@ } if (!strcmp("help", fname)) - return usage(); + usage(); // if we have a subtype, assume we're dealing with raw input if (stype) {
