diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..f0a6660 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,47 @@ +name: Docker Builds + +on: [workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.3.0 + with: + image: tonistiigi/binfmt:qemu-v8.1.5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + id: cache-docker + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.RTLAIS_GITHUB_TOKEN }} + + - name: Build and push to ghcr + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v6,linux/arm/v7 + push: ${{ github.event_name != 'pull_request' }} + outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=AIS recevier using RTL-SDR dongle + tags: | + ghcr.io/bklofas/rtl-ais:latest + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39281c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +rtl_ais +debianPackage/usr +debianPackage/*.deb diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eafd98d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# ------------------- +# The build container +# ------------------- +FROM debian:bookworm-slim AS build + +WORKDIR /usr/src/app + +COPY . /usr/src/app + +# Upgrade bookworm and install dependencies +RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \ + rtl-sdr \ + librtlsdr-dev \ + libusb-1.0-0-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Build rtl_ais +RUN make && \ + make install + + +# ------------------------- +# The application container +# ------------------------- +FROM debian:bookworm-slim + +LABEL org.opencontainers.image.title="rtl-ais" +LABEL org.opencontainers.image.description="AIS decoding using RTL-SDR dongle" +LABEL org.opencontainers.image.authors="Bryan Klofas KF6ZEO bklofas@gmail" +LABEL org.opencontainers.image.source="https://github.com/bklofas/rtl-ais" + +# Upgrade bookworm and install dependencies +RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \ + tini \ + rtl-sdr \ + librtlsdr-dev \ + libusb-1.0-0-dev &&\ + rm -rf /var/lib/apt/lists/* + +COPY --from=build /usr/src/app / + +# Use tini as init. +ENTRYPOINT ["/usr/bin/tini", "--"] + +CMD ["/rtl_ais", "-n"] + diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..0ad6d5b --- /dev/null +++ b/LICENCE @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * rtl-ais uses code from AISDecoder Copyright (C) 2013 Astra Paging Ltd / AISHub (info@aishub.net) + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * + * 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 2 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 . + */ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d7d5fb --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +CFLAGS?=-O2 -g -Wall +CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener +LD_EXTRA_PATHS= -L /usr/lib/arm-linux-gnueabihf -L /usr/lib/i386-linux-gnu -L /usr/lib/x86_64-linux-gnu +LDFLAGS+=-lpthread -lm $(LD_EXTRA_PATHS) +ifeq ($(PREFIX),) + PREFIX := /usr/local +endif +UNAME := $(shell uname) +ifeq ($(UNAME),Linux) +#Conditional for Linux +CFLAGS+= $(shell pkg-config --cflags librtlsdr) +LD_LIBRTLSDR=$(shell pkg-config --libs librtlsdr) +#Ugly hack. Check if the output of pkg-config is long enough to be valid +LD_LIBRTLSDR_LENGTH := $(shell echo "$(LD_LIBRTLSDR)" | wc -c) +ifeq ($(shell test $(LD_LIBRTLSDR_LENGTH) -gt 13; echo $$?),0) +#The pkg-config output seem to be ok, let's use it + LDFLAGS+=$(shell pkg-config --libs librtlsdr) +else +#The pkg-config output seem to be too short, use the default lib name and default paths + LDFLAGS+=-lrtlsdr +endif + +else +# +#ADD THE CORRECT PATH FOR LIBUSB AND RTLSDR +#TODO: +# CMAKE will be much better or create a conditional pkg-config + + +# RTLSDR +RTLSDR_INCLUDE=/tmp/rtl-sdr/include +RTLSDR_LIB=/tmp/rtl-sdr/build/src + +# LIBUSB +LIBUSB_INCLUDE=/tmp/libusb/include/libusb-1.0 +LIBUSB_LIB=/tmp/libusb/lib + +ifeq ($(UNAME),Darwin) +#Conditional for OSX +CFLAGS+= -I/usr/local/include/ -I/opt/homebrew/include -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE) +LDFLAGS+= -L/usr/local/lib -L/opt/homebrew/lib -L$(LIBUSB_LIB) -L$(RTLSDR_LIB) -lrtlsdr -lusb-1.0 +else +#Conditional for Windows +CFLAGS+=-I $(LIBUSB_INCLUDE) -I $(RTLSDR_INCLUDE) +LDFLAGS+=-L$(LIBUSB_INCLUDE) -L$(RTLSDR_LIB) -L/usr/lib -lusb-1.0 -lrtlsdr -lWs2_32 +endif + + +endif + +CC?=gcc +SOURCES= \ + main.c rtl_ais.c convenience.c \ + ./aisdecoder/aisdecoder.c \ + ./aisdecoder/sounddecoder.c \ + ./aisdecoder/lib/receiver.c \ + ./aisdecoder/lib/protodec.c \ + ./aisdecoder/lib/hmalloc.c \ + ./aisdecoder/lib/filter.c \ + ./tcp_listener/tcp_listener.c + +OBJECTS=$(SOURCES:.c=.o) +EXECUTABLE=rtl_ais + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(LDFLAGS) + +.c.o: + $(CC) -c $< -o $@ $(CFLAGS) + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE).exe + +install: + install -d -m 755 $(PREFIX)/bin + install -m 755 $(EXECUTABLE) "$(PREFIX)/bin/" + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..75d1502 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +`rtl-ais`, a simple AIS tuner and generic dual-frequency FM demodulator +----------------------------------------------------------------------- + +rtl-ais provides the `rtl_ais` command, which decodes AIS data from Software Defined Radio (SDR) and outputs `AIVDM` / `AIVDO` sentences. + +| OS support | | +|------------|---| +| Linux | ✅ | +| Windows | ✅ | +| OSX | ✅ | + + +Command Line +------------ +``` +Use: rtl_ais [options] [outputfile] + [-l left_frequency (default: 161.975M)] + [-r right_frequency (default: 162.025M)] + left freq < right freq + frequencies must be within 1.2MHz + [-s sample_rate (default: 24k)] + maximum value, might be down to 12k + [-o output_rate (default: 48k)] + must be equal or greater than twice -s value + [-E toggle edge tuning (default: off)] + [-D toggle DC filter (default: on)] + [-d device_index (default: 0)] + [-g tuner_gain (default: automatic)] + [-p ppm_error (default: 0)] + [-R enable RTL chip AGC (default: off)] + [-A turn off built-in AIS decoder (default: on)] + use this option to output samples to file or stdout. + Built-in AIS decoder options: + [-h host (default: 127.0.0.1)] + [-P port (default: 10110)] + [-T use TCP communication as tcp listener ( -h is ignored)] + [-t time to keep ais messages in sec, using tcp listener (default: 15)] + [-n log NMEA sentences to console (stderr) (default off)] + [-L log sound levels to console (stderr) (default off)] + [-I add sample index to NMEA mesages (default off)] + [-S seconds_for_decoder_stats (default 0=off)] + When the built-in AIS decoder is disabled the samples are sent to + to [outputfile] (a '-' dumps samples to stdout) + omitting the filename also uses stdout + Output is stereo 2x16 bit signed ints + Examples: + Receive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110 + and log the senteces to console: + rtl_ais -n + Tune two fm stations and play one on each channel: + rtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - +``` + + +Compiling +--------- +Make sure you have the following dependencies: + - librtlsdr + - libusb + - libpthread + +```console +$ # Get the source code: +$ git clone https://github.com/dgiardini/rtl-ais +$ # Change to the source dir +$ cd rtl-ais +$ make +$ # Test running the command +$ ./rtl_ais +``` + +For compiling a MS Windows executable you will need a working MSYS/MinGW environment. +Edit the `Makefile`, and modify these lines: + +```Makefile +#### point this to your correct path ### +RTLSDR_PATH="/c/tmp/rtl-sdr/" +RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ +######################################## +``` + + +Installing +---------- +* On Linux, `sudo make install` +* On Windows, put the `librtlsdr.dll` and `libusb-1.0.dll` files in the same directory +with `rtl_ais.exe`. You'll need the `zadig` driver installed too. + + +Running +------- + +rtl-ais uses software defined radio (SDR). The specific +hardware we use for this is a DVB-T dongle. A good starting point is: +https://www.rtl-sdr.com/about-rtl-sdr + +You'll need also an antenna, and be located near (some miles) the +passing vessels. + +You'll also need to do some procedure to get the tunning error for the +specfic dongle you have (aka ppm error), and pass that number as parameter +of rtl-ais. + + +Docker Container +---------------- +Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is 55 to 75 MB, depending on the host architecture. Get/install docker [here](https://docs.docker.com/get-docker/). + +Two options for obtaining the container: Either download and run a pre-built container, or build the container locally. + + 1. Just to test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` + * This downloads a pre-built container from the Github container registry. + * Architectures supported are i386, amd64, arm32v6, arm32v7, and arm64. Tested on amd64, arm32v7, and arm64. arm packages run on all RaspberryPi flavors. Your client will automatically download the appropriate architecture. + * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. Uses all other default values. + * You can add other ./rtl-sdr options, see below. + * Make sure at least one RTL-SDR dongle is connected. + * Startup messages and decoded packets will display in the terminal. + * Ctrl-C to kill. + * Using the `--rm` flag will delete the container when you kill it. Otherwise, it will stay around until you prune. + + 1. For a more permanent setup, run the container in the background and add any options you want: `docker run -d --name rtl-ais --restart=unless-stopped --log-driver=local --network=host --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest ./rtl_ais -n -d 00000002 -h 127.0.0.1 -P 10110` + * -d: Start this container in daemon/background mode. + * --name: Name this anything you want. + * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container. + * --log-driver=local: By default, docker uses the json log driver which may fill up your harddrive, depending on how busy your station is. `local` log driver defaults to 100MB of saved logs, and automatically rotates them. + * --network=host: Allows the container to talk to the internet, if you are sending the packets to an online service. + * --device=: Allows the container to talk to the USB bus to access the RTL-SDR dongle. + * ./rtl_ais: Same command-line options as non-containerized. + * View the startup messages and decoded packets with `docker logs --follow rtl-ais` + * Stop the container with `docker stop rtl-ais` + +Building the container: + + * Clone the repository with `git clone https://github.com/dgiardini/rtl-ais.git` , then from the folder `docker build -t rtl-ais .` + * Or, build the container without cloning the repository: `docker build https://github.com/dgiardini/rtl-ais.git` + +Other tips and tricks: + + * If you have the `-n` flag, view the decoded AIS packets in real-time with `docker logs --follow rtl-ais` + * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they provide. + * You are encouraged to send your decoded packets to multiple online services. Check out AIS packet multiplexer [kplex](http://www.stripydog.com/kplex/), pre-built containers are available [here](https://ghcr.io/bklofas/kplex). + + +Testing +------- + +TODO: something like +https://github.com/freerange/ais-on-sdr/wiki/Testing-AISDecoder#with-an-audio-file + + +Known Issues +------------ +* The `[-p ppm error]` parameter is essential for rtl_ais to work. + * The ppm error is the frequency deviation in parts per million from the desired tuning +frequency, and the real tuned frequency due to the crystal oscillator deviation. This +figure is different for each device, it's very important to know this value and pass this parameter to rtl_ais. + * Some instructions for get the ppm error are here: + http://www.rtl-sdr.com/how-to-calibrate-rtl-sdr-using-kalibrate-rtl-on-linux + * and here (using SDR#): + http://www.atouk.com/SDRSharpQuickStart.html#adjusting + * and here (using HDSDR ad AIS traffic) + http://www.cruisersforum.com/forums/f134/new-rtlsdr-plugin-102929-11.html#post1844966 diff --git a/TODO b/TODO new file mode 100644 index 0000000..b99e716 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +---- +TODO +---- +- Auto calibration mode +- Compile as library +- Add audio gain control diff --git a/ais/build.sh b/ais/build.sh deleted file mode 100755 index 4497f52..0000000 --- a/ais/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# todo, a real makefile - -files="rtl_ais.c convenience.c" -flags="-Wall -O2" -includes="-I/usr/include/libusb-1.0" -libs="-lusb-1.0 -lrtlsdr -lpthread -lm" - -rm -f rtl_ais -gcc -o rtl_ais $files $flags $includes $libs - diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c deleted file mode 100644 index 5389500..0000000 --- a/ais/rtl_ais.c +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright (C) 2012 by Kyle Keen - * - * 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 2 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 . - */ - - -/* todo - * support left > right - * thread left/right channels - * more array sharing - * something to correct for clock drift (look at demod's dc bias?) - * 4x oversampling (with cic up/down) - * droop correction - * alsa integration - * better upsampler (libsamplerate?) - * windows support - * ais decoder - */ - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include "convenience.h" - -#define DEFAULT_ASYNC_BUF_NUMBER 12 -#define DEFAULT_BUF_LENGTH (16 * 16384) -#define AUTO_GAIN -100 - -static pthread_t demod_thread; -static pthread_cond_t ready; -static pthread_mutex_t ready_m; -static volatile int do_exit = 0; -static rtlsdr_dev_t *dev = NULL; - -/* todo, less globals */ -int16_t *merged; -int merged_len; -FILE *file; -int oversample = 0; -int dc_filter = 1; - -/* signals are not threadsafe by default */ -#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) -#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) - -struct downsample_state -{ - int16_t *buf; - int len_in; - int len_out; - int rate_in; - int rate_out; - int downsample; - int downsample_passes; - int16_t lp_i_hist[10][6]; - int16_t lp_q_hist[10][6]; - pthread_rwlock_t rw; -}; - -struct demod_state -{ - int16_t *buf; - int buf_len; - int16_t *result; - int result_len; - int now_r, now_j; - int pre_r, pre_j; - int dc_avg; // really should get its own struct -}; - -struct upsample_stereo -{ - int16_t *buf_left; - int16_t *buf_right; - int16_t *result; - int bl_len; - int br_len; - int result_len; - int rate; -}; - -/* complex iq pairs */ -struct downsample_state both; -struct downsample_state left; -struct downsample_state right; -/* iq pairs and real mono */ -struct demod_state left_demod; -struct demod_state right_demod; -/* real stereo pairs (upsampled) */ -struct upsample_stereo stereo; - -void usage(void) -{ - fprintf(stderr, - "rtl_ais, a simple AIS tuner\n" - "\t and generic dual-frequency FM demodulator\n\n" - "(probably not a good idea to use with e4000 tuners)\n" - "Use: rtl_ais [options] [outputfile]\n" - "\t[-l left_frequency (default: 161.975M)]\n" - "\t[-r right_frequency (default: 162.025M)]\n" - "\t left freq < right freq\n" - "\t frequencies must be within 1.2MHz\n" - "\t[-s sample_rate (default: 12k)]\n" - "\t minimum value, might be up to 2x specified\n" - "\t[-o output_rate (default: 48k)]\n" - "\t must be equal or greater than twice -s value\n" - "\t[-E toggle edge tuning (default: off)]\n" - "\t[-D toggle DC filter (default: on)]\n" - //"\t[-O toggle oversampling (default: off)\n" - "\t[-d device_index (default: 0)]\n" - "\t[-g tuner_gain (default: automatic)]\n" - "\t[-p ppm_error (default: 0)]\n" - "\tfilename (a '-' dumps samples to stdout)\n" - "\t omitting the filename also uses stdout\n\n" - "\tOutput is stereo 2x16 bit signed ints\n\n" - "rtl_ais | play -t raw -r 48k -es -b 16 -c 2 -V1 -\n" - "\n"); - exit(1); -} - -static void sighandler(int signum) -{ - fprintf(stderr, "Signal caught, exiting!\n"); - do_exit = 1; - rtlsdr_cancel_async(dev); -} - -void rotate_90(int16_t *buf, int len) -/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j - or [0, 1, -3, 2, -4, -5, 7, -6] */ -{ - int i; - int16_t tmp; - for (i=0; i> 4; - for (i=4; i> 4; - } - /* archive */ - hist[0] = a; - hist[1] = b; - hist[2] = c; - hist[3] = d; - hist[4] = e; - hist[5] = f; -} - -void downsample(struct downsample_state *d) -{ - int i, ds_p; - ds_p = d->downsample_passes; - for (i=0; ibuf, (d->len_in >> i), d->lp_i_hist[i]); - fifth_order(d->buf+1, (d->len_in >> i)-1, d->lp_q_hist[i]); - } -} - -void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) -{ - *cr = ar*br - aj*bj; - *cj = aj*br + ar*bj; -} - -int polar_discriminant(int ar, int aj, int br, int bj) -{ - int cr, cj; - double angle; - multiply(ar, aj, br, -bj, &cr, &cj); - angle = atan2((double)cj, (double)cr); - return (int)(angle / 3.14159 * (1<<14)); -} - -int fast_atan2(int y, int x) -/* pre scaled for int16 */ -{ - int yabs, angle; - int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14 - if (x==0 && y==0) { - return 0; - } - yabs = y; - if (yabs < 0) { - yabs = -yabs; - } - if (x >= 0) { - angle = pi4 - pi4 * (x-yabs) / (x+yabs); - } else { - angle = pi34 - pi4 * (x+yabs) / (yabs-x); - } - if (y < 0) { - return -angle; - } - return angle; -} - -int polar_disc_fast(int ar, int aj, int br, int bj) -{ - int cr, cj; - multiply(ar, aj, br, -bj, &cr, &cj); - return fast_atan2(cj, cr); -} - -void demodulate(struct demod_state *d) -{ - int i, pcm; - int16_t *buf = d->buf; - int16_t *result = d->result; - pcm = polar_disc_fast(buf[0], buf[1], - d->pre_r, d->pre_j); - result[0] = (int16_t)pcm; - for (i = 2; i < (d->buf_len-1); i += 2) { - // add the other atan types? - pcm = polar_disc_fast(buf[i], buf[i+1], - buf[i-2], buf[i-1]); - result[i/2] = (int16_t)pcm; - } - d->pre_r = buf[d->buf_len - 2]; - d->pre_j = buf[d->buf_len - 1]; -} - -void dc_block_filter(struct demod_state *d) -{ - int i, avg; - int64_t sum = 0; - int16_t *result = d->result; - for (i=0; i < d->result_len; i++) { - sum += result[i]; - } - avg = sum / d->result_len; - avg = (avg + d->dc_avg * 9) / 10; - for (i=0; i < d->result_len; i++) { - result[i] -= avg; - } - d->dc_avg = avg; -} - -void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) -/* linear interpolation, len1 < len2 */ -{ - int i = 1; - int j = 0; - int tick = 0; - double frac; // use integers... - while (j < len2) { - frac = (double)tick / (double)len2; - buf2[j] = (int16_t)((double)buf1[i-1]*(1-frac) + (double)buf1[i]*frac); - j++; - tick += len1; - if (tick > len2) { - tick -= len2; - i++; - } - if (i >= len1) { - i = len1 - 1; - tick = len2; - } - } -} - -static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) -{ - int i; - if (do_exit) { - return;} - pthread_rwlock_wrlock(&both.rw); - for (i=0; ibuf = malloc(dss->len_in * sizeof(int16_t)); - dss->rate_out = dss->rate_in / dss->downsample; - //dss->downsample_passes = (int)log2(dss->downsample); - dss->len_out = dss->len_in / dss->downsample; - for (i=0; i<10; i++) { for (j=0; j<6; j++) { - dss->lp_i_hist[i][j] = 0; - dss->lp_q_hist[i][j] = 0; - }} - pthread_rwlock_init(&dss->rw, NULL); -} - -void demod_init(struct demod_state *ds) -{ - ds->buf = malloc(ds->buf_len * sizeof(int16_t)); - ds->result = malloc(ds->result_len * sizeof(int16_t)); -} - -void stereo_init(struct upsample_stereo *us) -{ - us->buf_left = malloc(us->bl_len * sizeof(int16_t)); - us->buf_right = malloc(us->br_len * sizeof(int16_t)); - us->result = malloc(us->result_len * sizeof(int16_t)); -} - -int main(int argc, char **argv) -{ - struct sigaction sigact; - char *filename = NULL; - int r, opt; - int i, gain = AUTO_GAIN; /* tenths of a dB */ - int dev_index = 0; - int dev_given = 0; - int ppm_error = 0; - int custom_ppm = 0; - int left_freq = 161975000; - int right_freq = 162025000; - int sample_rate = 12000; - int output_rate = 48000; - int dongle_freq, dongle_rate, delta; - int edge = 0; - pthread_cond_init(&ready, NULL); - pthread_mutex_init(&ready_m, NULL); - - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:h")) != -1) - { - switch (opt) { - case 'l': - left_freq = (int)atofs(optarg); - break; - case 'r': - right_freq = (int)atofs(optarg); - break; - case 's': - sample_rate = (int)atofs(optarg); - break; - case 'o': - output_rate = (int)atofs(optarg); - break; - case 'E': - edge = !edge; - break; - case 'D': - dc_filter = !dc_filter; - break; - case 'O': - oversample = !oversample; - break; - case 'd': - dev_index = verbose_device_search(optarg); - dev_given = 1; - break; - case 'g': - gain = (int)(atof(optarg) * 10); - break; - case 'p': - ppm_error = atoi(optarg); - custom_ppm = 1; - break; - case 'h': - default: - usage(); - return 2; - } - } - - if (argc <= optind) { - filename = "-"; - } else { - filename = argv[optind]; - } - - if (left_freq > right_freq) { - usage(); - return 2; - } - - /* precompute rates */ - dongle_freq = left_freq/2 + right_freq/2; - if (edge) { - dongle_freq -= sample_rate/2;} - delta = right_freq - left_freq; - if (delta > 1.2e6) { - fprintf(stderr, "Frequencies may be at most 1.2MHz apart."); - exit(1); - } - if (delta < 0) { - fprintf(stderr, "Left channel must be lower than right channel."); - exit(1); - } - i = (int)log2(2.4e6 / delta); - dongle_rate = delta * (1< output_rate) { - fprintf(stderr, "Channel bandwidth too high or output bandwidth too low."); - exit(1); - } - - stereo.rate = output_rate; - - if (edge) { - fprintf(stderr, "Edge tuning enabled.\n"); - } else { - fprintf(stderr, "Edge tuning disabled.\n"); - } - if (dc_filter) { - fprintf(stderr, "DC filter enabled.\n"); - } else { - fprintf(stderr, "DC filter disabled.\n"); - } - fprintf(stderr, "Buffer size: %0.2f mS\n", 1000 * (double)DEFAULT_BUF_LENGTH / (double)dongle_rate); - fprintf(stderr, "Downsample factor: %i\n", both.downsample * left.downsample); - fprintf(stderr, "Low pass: %i Hz\n", left.rate_out); - fprintf(stderr, "Output: %i Hz\n", output_rate); - - /* precompute lengths */ - both.len_in = DEFAULT_BUF_LENGTH; - both.len_out = both.len_in / both.downsample; - left.len_in = both.len_out; - right.len_in = both.len_out; - left.len_out = left.len_in / left.downsample; - right.len_out = right.len_in / right.downsample; - left_demod.buf_len = left.len_out; - left_demod.result_len = left_demod.buf_len / 2; - right_demod.buf_len = left_demod.buf_len; - right_demod.result_len = left_demod.result_len; - stereo.bl_len = (int)((long)(DEFAULT_BUF_LENGTH/2) * (long)output_rate / (long)dongle_rate); - stereo.br_len = stereo.bl_len; - stereo.result_len = stereo.br_len * 2; - stereo.rate = output_rate; - - if (!dev_given) { - dev_index = verbose_device_search("0"); - } - - if (dev_index < 0) { - exit(1); - } - - downsample_init(&both); - downsample_init(&left); - downsample_init(&right); - demod_init(&left_demod); - demod_init(&right_demod); - stereo_init(&stereo); - - r = rtlsdr_open(&dev, (uint32_t)dev_index); - if (r < 0) { - fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dev_index); - exit(1); - } - sigact.sa_handler = sighandler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGPIPE, &sigact, NULL); - - if (strcmp(filename, "-") == 0) { /* Write samples to stdout */ - file = stdout; - setvbuf(stdout, NULL, _IONBF, 0); - } else { - file = fopen(filename, "wb"); - if (!file) { - fprintf(stderr, "Failed to open %s\n", filename); - exit(1); - } - } - - /* Set the tuner gain */ - if (gain == AUTO_GAIN) { - verbose_auto_gain(dev); - } else { - gain = nearest_gain(dev, gain); - verbose_gain_set(dev, gain); - } - - if (!custom_ppm) { - verbose_ppm_eeprom(dev, &ppm_error); - } - verbose_ppm_set(dev, ppm_error); - //r = rtlsdr_set_agc_mode(dev, 1); - - /* Set the tuner frequency */ - verbose_set_frequency(dev, dongle_freq); - - /* Set the sample rate */ - verbose_set_sample_rate(dev, dongle_rate); - - /* Reset endpoint before we start reading from it (mandatory) */ - verbose_reset_buffer(dev); - - pthread_create(&demod_thread, NULL, demod_thread_fn, (void *)(NULL)); - rtlsdr_read_async(dev, rtlsdr_callback, (void *)(NULL), - DEFAULT_ASYNC_BUF_NUMBER, - DEFAULT_BUF_LENGTH); - - if (do_exit) { - fprintf(stderr, "\nUser cancel, exiting...\n");} - else { - fprintf(stderr, "\nLibrary error %d, exiting...\n", r);} - rtlsdr_cancel_async(dev); - safe_cond_signal(&ready, &ready_m); - pthread_cond_destroy(&ready); - pthread_mutex_destroy(&ready_m); - - if (file != stdout) { - fclose(file);} - - rtlsdr_close(dev); - return r >= 0 ? r : -r; -} - -// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c new file mode 100644 index 0000000..5c78bc9 --- /dev/null +++ b/aisdecoder/aisdecoder.c @@ -0,0 +1,274 @@ +/* + * main.cpp -- AIS Decoder + * + * Copyright (C) 2013 + * Astra Paging Ltd / AISHub (info@aishub.net) + * + * AISDecoder 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 2 of the License, or + * (at your option) any later version. + * + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * + */ +/* This is a stripped down version for use with rtl_ais*/ + +#ifndef WIN32 +#include +#include +#include +#else +// Horrible hack for compiling freeaddrinfo() and getaddrinfo() with MSys, fix this please +#define WIN32_VER_TMP _WIN32_WINNT +#define _WIN32_WINNT 0x0502 +#include +#include +#undef _WIN32_WINNT +#define _WIN32_WINNT WIN32_VER_TMP + +#endif +#include +#include +#include +#include +#include +#include +#include +//#include "config.h" +#include "sounddecoder.h" +#include "lib/callbacks.h" +#include "../tcp_listener/tcp_listener.h" + +#define MAX_BUFFER_LENGTH 2048 +//#define MAX_BUFFER_LENGTH 8190 + +static char buffer[MAX_BUFFER_LENGTH]; +static unsigned int buffer_count=0; +#ifdef WIN32 + WSADATA wsaData; +#endif +static int debug_nmea; +static int sock; +static int use_tcp = 0; + +static struct addrinfo* addr=NULL; +// messages can be retrived from a different thread +static pthread_mutex_t message_mutex; + +// queue of decoded ais messages +struct ais_message { + char *buffer; + struct ais_message *next; +} *ais_messages_head, *ais_messages_tail, *last_message; + +static void append_message(const char *buffer) +{ + struct ais_message *m = malloc(sizeof *m); + + m->buffer = strdup(buffer); + m->next = NULL; + pthread_mutex_lock(&message_mutex); + + // enqueue + if(!ais_messages_head) + ais_messages_head = m; + else + ais_messages_tail->next = m; + ais_messages_tail = m; + pthread_mutex_unlock(&message_mutex); +} + +static void free_message(struct ais_message *m) +{ + if(m) { + free(m->buffer); + free(m); + } +} + +const char *aisdecoder_next_message() +{ + free_message(last_message); + last_message = NULL; + + pthread_mutex_lock(&message_mutex); + if(!ais_messages_head) { + pthread_mutex_unlock(&message_mutex); + return NULL; + } + + // dequeue + last_message = ais_messages_head; + ais_messages_head = ais_messages_head->next; + + pthread_mutex_unlock(&message_mutex); + return last_message->buffer; +} + +static int initSocket(const char *host, const char *portname); +int send_nmea( const char *sentence, unsigned int length); + +void sound_level_changed(float level, int channel, unsigned char high) { + if (high != 0) + fprintf(stderr, "Level on ch %d too high: %.0f %%\n", channel, level); + else + fprintf(stderr, "Level on ch %d: %.0f %%\n", channel, level); +} + +void nmea_sentence_received(const char *sentence, + unsigned int length, + unsigned char sentences, + unsigned char sentencenum) { + append_message(sentence); + + if (sentences == 1) { + if (send_nmea( sentence, length) == -1){ + fprintf(stderr,"Error sending UDP packet with NMEA message: %s\n", strerror(errno)); + abort(); + } + if (debug_nmea) fprintf(stderr, "%s", sentence); + } else { + if (buffer_count + length < MAX_BUFFER_LENGTH) { + memcpy(&buffer[buffer_count], sentence, length); + buffer_count += length; + } else { + buffer_count=0; + } + + if (sentences == sentencenum && buffer_count > 0) { + if (send_nmea( buffer, buffer_count) == -1){ + fprintf(stderr,"Error sending UDP packet with NMEA message (buffer_count=%d):%s\n",buffer_count, strerror(errno)); + abort(); + } + if (debug_nmea) fprintf(stderr, "%s", buffer); + buffer_count=0; + }; + } +} + +int send_nmea( const char *sentence, unsigned int length) { + if( use_tcp) { + return add_nmea_ais_message(sentence, length); + } + else if(sock) { + return sendto(sock, sentence, length, 0, addr->ai_addr, addr->ai_addrlen); + } + return 0; +} + +int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time, int add_sample_num){ + debug_nmea=_debug_nmea; + use_tcp = use_tcp_listener; + pthread_mutex_init(&message_mutex, NULL); + if(debug_nmea) + fprintf(stderr,"Log NMEA sentences to console ON\n"); + else + fprintf(stderr,"Log NMEA sentences to console OFF\n"); + if( !use_tcp_listener) { + if (host && port && !initSocket(host, port)) { + return EXIT_FAILURE; + } + } + else { + if (!initTcpSocket(port, debug_nmea, tcp_keep_ais_time)) { + return EXIT_FAILURE; + } + } + if (show_levels) on_sound_level_changed=sound_level_changed; + on_nmea_sentence_received=nmea_sentence_received; + initSoundDecoder(buf_len,time_print_stats,add_sample_num); + return 0; +} + +void run_rtlais_decoder(short * buff, int len) +{ + run_mem_decoder(buff,len,MAX_BUFFER_LENGTH); +} +int free_ais_decoder(void) +{ + pthread_mutex_destroy(&message_mutex); + + // free all stored messa ages + free_message(last_message); + last_message = NULL; + + while(ais_messages_head) { + struct ais_message *m = ais_messages_head; + ais_messages_head = ais_messages_head->next; + + free_message(m); + } + + freeSoundDecoder(); + freeaddrinfo(addr); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; +} + + + +/* Check if the host is broacast address. I suppose there are better options than this :-| */ +int isBroadcastAddress (const char *ipAddress) { + // Find the last dot in the IP address + const char *lastDot = strrchr(ipAddress, '.'); + if (lastDot != NULL) { + // Extract the last octet after the dot + const char *lastOctet = lastDot + 1; + // Check if the last octet is "255" + if (strcmp(lastOctet, "255") == 0) { + return 1; // Last digits are 255 + } + } + return 0; //Last digits are not 255 +} + + + +int initSocket(const char *host, const char *portname) { + struct addrinfo hints; + int enable_broadcast=1; + memset(&hints, 0, sizeof(hints)); + hints.ai_family=AF_UNSPEC; + hints.ai_socktype=SOCK_DGRAM; + hints.ai_protocol=IPPROTO_UDP; +#ifndef WIN32 + hints.ai_flags=AI_ADDRCONFIG; +#else + int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + printf("WSAStartup failed: %d\n", iResult); + return 0; + } +#endif + int err=getaddrinfo(host, portname, &hints, &addr); + if (err!=0) { + fprintf(stderr, "Failed to resolve remote socket address!\n"); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; + } + + sock=socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock==-1) { + fprintf(stderr, "%s",strerror(errno)); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; + } + if(isBroadcastAddress(host)){ + fprintf(stderr, "Broadcast address detected. Setting SO_BROADCAST option to socket.\n"); + // Enable sending broadcast packets + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable_broadcast, sizeof(enable_broadcast)) < 0) { + perror("Failed to set socket option SO_BROADCAST:"); + exit(1); + } + } + fprintf(stderr,"AIS data will be sent to %s port %s\n",host,portname); + return 1; +} + diff --git a/aisdecoder/aisdecoder.h b/aisdecoder/aisdecoder.h new file mode 100644 index 0000000..33f8d9e --- /dev/null +++ b/aisdecoder/aisdecoder.h @@ -0,0 +1,8 @@ +#ifndef __AIS_RL_AIS_INC_ +#define __AIS_RL_AIS_INC_ +int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time, int add_sample_num); +void run_rtlais_decoder(short * buff, int len); +const char *aisdecoder_next_message(); +int free_ais_decoder(void); +#endif + diff --git a/aisdecoder/lib/callbacks.h b/aisdecoder/lib/callbacks.h new file mode 100644 index 0000000..c818762 --- /dev/null +++ b/aisdecoder/lib/callbacks.h @@ -0,0 +1,20 @@ +#ifndef CALLBACKS_H +#define CALLBACKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*receiver_on_level_changed)(float level, int channel, unsigned char high); +typedef void (*decoder_on_nmea_sentence_received)(const char *sentence, + unsigned int length, + unsigned char sentences, + unsigned char sentencenum); + +extern receiver_on_level_changed on_sound_level_changed; +extern decoder_on_nmea_sentence_received on_nmea_sentence_received; + +#ifdef __cplusplus +} +#endif +#endif // CALLBACKS_H diff --git a/aisdecoder/lib/filter-i386.h b/aisdecoder/lib/filter-i386.h new file mode 100644 index 0000000..afdc645 --- /dev/null +++ b/aisdecoder/lib/filter-i386.h @@ -0,0 +1,275 @@ +/* + * filter-i386.h -- optimized filter routines + * + * Copyright (C) 1996 + * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* ---------------------------------------------------------------------- */ + +#ifndef _FILTER_I386_H +#define _FILTER_I386_H + +/* ---------------------------------------------------------------------- */ + +#define __HAVE_ARCH_MAC +#define mac(a,b,size) \ +(__builtin_constant_p(size) ? __mac_c((a),(b),(size)) : __mac_g((a),(b),(size))) + +#include + +__attribute__ ((gnu_inline)) extern inline float __mac_g(const float *a, const float *b, + unsigned int size) +{ + float sum = 0; + unsigned int i; + + for (i = 0; i < size; i++) + sum += (*a++) * (*b++); + return sum; +} + +__attribute__ ((gnu_inline)) extern inline float __mac_c(const float *a, const float *b, + unsigned int size) +{ + float f; + + /* + * inspired from Phil Karn, KA9Q's home page + */ + switch (size) { + case 53: + asm volatile ("flds (%1);\n\t" + "fmuls (%2);\n\t" + "flds 4(%1);\n\t" + "fmuls 4(%2);\n\t" + "flds 8(%1);\n\t" + "fmuls 8(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 12(%1);\n\t" + "fmuls 12(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 16(%1);\n\t" + "fmuls 16(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 20(%1);\n\t" + "fmuls 20(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 24(%1);\n\t" + "fmuls 24(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 28(%1);\n\t" + "fmuls 28(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 32(%1);\n\t" + "fmuls 32(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 36(%1);\n\t" + "fmuls 36(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 40(%1);\n\t" + "fmuls 40(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 44(%1);\n\t" + "fmuls 44(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 48(%1);\n\t" + "fmuls 48(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 52(%1);\n\t" + "fmuls 52(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 56(%1);\n\t" + "fmuls 56(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 60(%1);\n\t" + "fmuls 60(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 64(%1);\n\t" + "fmuls 64(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 68(%1);\n\t" + "fmuls 68(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 72(%1);\n\t" + "fmuls 72(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 76(%1);\n\t" + "fmuls 76(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 80(%1);\n\t" + "fmuls 80(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 84(%1);\n\t" + "fmuls 84(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 88(%1);\n\t" + "fmuls 88(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 92(%1);\n\t" + "fmuls 92(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 96(%1);\n\t" + "fmuls 96(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 100(%1);\n\t" + "fmuls 100(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 104(%1);\n\t" + "fmuls 104(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 108(%1);\n\t" + "fmuls 108(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 112(%1);\n\t" + "fmuls 112(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 116(%1);\n\t" + "fmuls 116(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 120(%1);\n\t" + "fmuls 120(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 124(%1);\n\t" + "fmuls 124(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 128(%1);\n\t" + "fmuls 128(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 132(%1);\n\t" + "fmuls 132(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 136(%1);\n\t" + "fmuls 136(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 140(%1);\n\t" + "fmuls 140(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 144(%1);\n\t" + "fmuls 144(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 148(%1);\n\t" + "fmuls 148(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 152(%1);\n\t" + "fmuls 152(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 156(%1);\n\t" + "fmuls 156(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 160(%1);\n\t" + "fmuls 160(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 164(%1);\n\t" + "fmuls 164(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 168(%1);\n\t" + "fmuls 168(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 172(%1);\n\t" + "fmuls 172(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 176(%1);\n\t" + "fmuls 176(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 180(%1);\n\t" + "fmuls 180(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 184(%1);\n\t" + "fmuls 184(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 188(%1);\n\t" + "fmuls 188(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 192(%1);\n\t" + "fmuls 192(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 196(%1);\n\t" + "fmuls 196(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 200(%1);\n\t" + "fmuls 200(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 204(%1);\n\t" + "fmuls 204(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 208(%1);\n\t" + "fmuls 208(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "faddp;\n\t":"=t" (f):"r"(a), "r"(b):"memory"); + return f; + + default: + fprintf(stderr, + "Warning: optimize __mac_c(..., ..., %d)\n", size); + return __mac_g(a, b, size); + } +} + +/* ---------------------------------------------------------------------- */ +#endif /* _FILTER_I386_H */ diff --git a/aisdecoder/lib/filter.c b/aisdecoder/lib/filter.c new file mode 100644 index 0000000..3909dd3 --- /dev/null +++ b/aisdecoder/lib/filter.c @@ -0,0 +1,145 @@ +/* + * filter.c -- FIR filter + * + * Copyright (C) 2001, 2002, 2003 + * Tomi Manninen (oh2bns@sral.fi) + * + * This file is part of gMFSK. + * + * gMFSK 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 2 of the License, or + * (at your option) any later version. + * + * gMFSK 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 gMFSK; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +#include "hmalloc.h" +#include "filter.h" + +#undef DEBUG + +#ifdef DEBUG +#include +#endif + +/* ---------------------------------------------------------------------- */ + +/* + * This gets used when not optimising + */ +#ifndef __OPTIMIZE__ +float filter_mac(const float *a, const float *b, int size) +{ + float sum = 0; + int i; + + for (i = 0; i < size; i++) + sum += a[i] * b[i]; + + return sum; +} +#endif + +/* ---------------------------------------------------------------------- */ + +struct filter *filter_init(int len, float *taps) +{ + struct filter *f; + + f = (struct filter *) hmalloc(sizeof(struct filter)); + memset(f, 0, sizeof(struct filter)); + + f->taps = (float *) hmalloc(len * sizeof(float)); + memcpy(f->taps, taps, len * sizeof(float)); + + f->length = len; + f->pointer = f->length; + + return f; +} + +void filter_free(struct filter *f) +{ + if (f) { + hfree(f->taps); + hfree(f); + } +} + +/* ---------------------------------------------------------------------- */ + +void filter_run(struct filter *f, float in, float *out) +{ + float *ptr = f->buffer + f->pointer++; + + *ptr = in; + + // TODO: optimize: pass filter length as constant to enable + // using optimized __mac_c and fix the number of rounds there! + #ifndef __HAVE_ARCH_MAC + *out = filter_mac(ptr - f->length, f->taps, f->length); + #else + *out = mac(ptr - f->length, f->taps, f->length); + #endif + //*out = filter_mac(ptr - f->length, f->taps, 53); + + if (f->pointer == BufferLen) { + memcpy(f->buffer, + f->buffer + BufferLen - f->length, + f->length * sizeof(float)); + f->pointer = f->length; + } +} + +short filter_run_buf(struct filter *f, short *in, float *out, int step, int len) +{ + int id = 0; + int od = 0; + short maxval = 0; + int pointer = f->pointer; + float *buffer = f->buffer; + + while (od < len) { + buffer[pointer] = in[id]; + + // look for peak volume + if (in[id] > maxval) + maxval = in[id]; + + #ifndef __HAVE_ARCH_MAC + out[od] = filter_mac(&buffer[pointer - f->length], f->taps, f->length); + #else + out[od] = mac(&buffer[pointer - f->length], f->taps, f->length); + #endif + pointer++; + + /* the buffer is much smaller than the incoming chunks */ + if (pointer == BufferLen) { + memcpy(buffer, + buffer + BufferLen - f->length, + f->length * sizeof(float)); + pointer = f->length; + } + + id += step; + od++; + } + + f->pointer = pointer; + + return maxval; +} + +/* ---------------------------------------------------------------------- */ diff --git a/aisdecoder/lib/filter.h b/aisdecoder/lib/filter.h new file mode 100644 index 0000000..beac695 --- /dev/null +++ b/aisdecoder/lib/filter.h @@ -0,0 +1,72 @@ +/* + * filter.h -- FIR filter + * + * Copyright (C) 2001, 2002, 2003, 2004 + * Tomi Manninen (oh2bns@sral.fi) + * + * This file is part of gMFSK. + * + * gMFSK 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 2 of the License, or + * (at your option) any later version. + * + * gMFSK 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 gMFSK; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _FILTER_H +#define _FILTER_H + +#define BufferLen 1024 + +/* ---------------------------------------------------------------------- */ + +#ifdef __OPTIMIZE__ + +#ifdef __i386__ +#include "filter-i386.h" +#endif /* __i386__ */ + + +#ifndef __HAVE_ARCH_MAC +static __inline__ float filter_mac(const float *a, const float *b, int size) +{ + float sum = 0; + int i; + + for (i = 0; i < size; i++) + sum += a[i] * b[i]; + + return sum; +} +#endif /* __HAVE_ARCH_MAC */ + +#endif /* __OPTIMIZE__ */ + + +/* ---------------------------------------------------------------------- */ + +struct filter { + int length; + float *taps; + float buffer[BufferLen]; + int pointer; +}; + +extern struct filter *filter_init(int len, float *taps); +extern void filter_free(struct filter *f); + +extern void filter_run(struct filter *f, float in, float *out); +extern short filter_run_buf(struct filter *f, short *in, float *out, int step, int len); + +/* ---------------------------------------------------------------------- */ + +#endif /* _FILTER_H */ diff --git a/aisdecoder/lib/hmalloc.c b/aisdecoder/lib/hmalloc.c new file mode 100644 index 0000000..910d238 --- /dev/null +++ b/aisdecoder/lib/hmalloc.c @@ -0,0 +1,74 @@ +/* + * (c) Heikki Hannikainen, OH7LZB + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Replacements for malloc, realloc and free, which never fail, + * and might keep statistics on memory allocation... + * + * GPL'ed, by Heikki Hannikainen + */ + +#include +#include +#include + +#include "hmalloc.h" + +#ifdef DMALLOC +#include +#endif + +int mem_panic = 0; + +void *hmalloc(size_t size) { + void *p; + if (!(p = malloc(size))) { + if (mem_panic) exit(1); + mem_panic = 1; + exit(1); + } + + return p; +} + +void *hrealloc(void *ptr, size_t size) { + void *p; + + if (!(p = realloc(ptr, size))) { + if (mem_panic) exit(1); + mem_panic = 1; + exit(1); + } + + return p; +} + +void hfree(void *ptr) { + if (ptr) free(ptr); +} + +char *hstrdup(const char *s) { + char *p; + + p = (char*)hmalloc(strlen(s)+1); + strcpy(p, s); + + return p; +} + diff --git a/aisdecoder/lib/hmalloc.h b/aisdecoder/lib/hmalloc.h new file mode 100644 index 0000000..30007b0 --- /dev/null +++ b/aisdecoder/lib/hmalloc.h @@ -0,0 +1,42 @@ +/* + * (c) Heikki Hannikainen, OH7LZB + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HMALLOC_H +#define HMALLOC_H + +#include +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Replacements for malloc, realloc and free, which never fail, + * and might keep statistics on memory allocation... + */ + +extern void *hmalloc(size_t size); +extern void *hrealloc(void *ptr, size_t size); +extern void hfree(void *ptr); + +extern char *hstrdup(const char *s); +#ifdef __cplusplus +} +#endif +#endif + diff --git a/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c new file mode 100644 index 0000000..90d44c7 --- /dev/null +++ b/aisdecoder/lib/protodec.c @@ -0,0 +1,377 @@ + +/* + * protodec.c + * + * (c) Ruben Undheim 2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include /* String function definitions */ +#include "callbacks.h" +//#include "config.h" + +#include "protodec.h" +#include "hmalloc.h" + +decoder_on_nmea_sentence_received on_nmea_sentence_received=NULL; + +#ifdef DMALLOC +#include +#endif + +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid, int add_sample_num) +{ + memset(d, 0, sizeof(struct demod_state_t)); + + d->chanid = chanid; + d->serial = serial; + + d->receivedframes = 0; + d->lostframes = 0; + d->lostframes2 = 0; + + protodec_reset(d); + + d->seqnr = 0; + d->add_sample_num = add_sample_num; + + d->buffer = hmalloc(DEMOD_BUFFER_LEN); + d->rbuffer = hmalloc(DEMOD_BUFFER_LEN); + d->nmea = hmalloc(NMEABUFFER_LEN); +} + +void protodec_deinit(struct demod_state_t *d) +{ + hfree(d->buffer); + hfree(d->rbuffer); + hfree(d->nmea); +} + +void protodec_reset(struct demod_state_t *d) +{ + d->state = ST_SKURR; + d->nskurr = 0; + d->ndata = 0; + d->npreamble = 0; + d->nstartsign = 0; + d->nstopsign = 0; + d->antallpreamble = 0; + d->antallenner = 0; + d->last = 0; + d->bitstuff = 0; + d->bufferpos = 0; +} + +/* + * Calculates CRC-checksum + */ + +unsigned short protodec_sdlc_crc(const unsigned char *data, unsigned len) +{ + unsigned short c, crc = 0xffff; + + while (len--) + for (c = 0x100 + *data++; c > 1; c >>= 1) + if ((crc ^ c) & 1) + crc = (crc >> 1) ^ 0x8408; + else + crc >>= 1; + return ~crc; + +} + +int protodec_calculate_crc(int length_bits, struct demod_state_t *d) +{ + int length_bytes; + unsigned char *buf; + int buflen; + int i, j, x; + unsigned char tmp; + + if (length_bits <= 0) { + return 0; + } + + length_bytes = length_bits / 8; + buflen = length_bytes + 2; + + /* what is this? */ + buf = (unsigned char *) hmalloc(sizeof(*buf) * buflen); + for (j = 0; j < buflen; j++) { + tmp = 0; + for (i = 0; i < 8; i++) + tmp |= (((d->buffer[i + 8 * j]) << (i))); + buf[j] = tmp; + } + + /* ok, here's the actual CRC calculation */ + unsigned short crc = protodec_sdlc_crc(buf, buflen); + //DBG(printf("CRC: %04x\n",crc)); + + /* what is this? */ + memset(d->rbuffer, 0, DEMOD_BUFFER_LEN); + for (j = 0; j < length_bytes; j++) { + for (i = 0; i < 8; i++) { + x = j * 8 + i; + if (x >= DEMOD_BUFFER_LEN) { + hfree(buf); + return 0; + } else { + d->rbuffer[x] = (buf[j] >> (7 - i)) & 1; + } + } + } + + hfree(buf); + + return (crc == 0x0f47); +} + +unsigned long protodec_henten(int from, int size, unsigned char *frame) +{ + int i = 0; + unsigned long tmp = 0; + + for (i = 0; i < size; i++) + tmp |= (frame[from + i]) << (size - 1 - i); + + return tmp; +} + + +void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits, time_t received_t) +{ + int senlen; + int pos; + int k, offset; + int m; + int inc; + + unsigned char sentences, sentencenum, nmeachk, letter; + received_t=received_t; // not used here, avoid compiling warnings + //6bits to nmea-ascii. One sentence len max 82char + //inc. head + tail.This makes inside datamax 62char multipart, 62 single + senlen = 56; //this is normally not needed.For testing only. May be fixed number + if (bufferlen <= (senlen * 6)) { + sentences = 1; + } else { + sentences = bufferlen / (senlen * 6); + //sentences , if overflow put one more + if (bufferlen % (senlen * 6) != 0) + sentences++; + }; + + sentencenum = 0; + pos = 0; + offset = (sentences>1) ? 15 : 14; + do { + k = offset; //leave room for nmea header + while (k < senlen + offset && bufferlen > pos) { + letter = (unsigned char)protodec_henten(pos, 6, d->rbuffer); + // 6bit-to-ascii conversion by IEC + letter += (letter < 40) ? 48 : 56; + d->nmea[k] = letter; + pos += 6; + k++; + } + sentencenum++; + + memcpy(&d->nmea[0], "!AIVDM,0,0,", 11); + d->nmea[7] += sentences; + d->nmea[9] += sentencenum; + + memcpy(&d->nmea[k], ",0*00\0", 6); + + if (sentences > 1) { + d->nmea[11] = '0' + d->seqnr; + d->nmea[12] = ','; + d->nmea[13] = d->chanid; + d->nmea[14] = ','; + if (sentencenum == sentences) d->nmea[k + 1] = '0' + fillbits; + } else { + d->nmea[11] = ','; + d->nmea[12] = d->chanid; + d->nmea[13] = ','; + } + + m = 1; + nmeachk = d->nmea[m++]; + while (d->nmea[m] != '*') nmeachk ^= d->nmea[m++]; + + if (d->add_sample_num){ + inc = sprintf(&d->nmea[k + 3], "%02X,%lu\r\n", nmeachk, d->startsample); + }else{ + inc = sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); + } + if (on_nmea_sentence_received != NULL) + on_nmea_sentence_received(d->nmea, k+3+inc, sentences, sentencenum); + } while (sentencenum < sentences); +} + +void protodec_getdata(int bufferlen, struct demod_state_t *d) +{ + unsigned char type = protodec_henten(0, 6, d->rbuffer); + if (type < 1 || type > MAX_AIS_PACKET_TYPE /* 9 */) + return; +// unsigned long mmsi = protodec_henten(8, 30, d->rbuffer); + int fillbits = 0; + int k; + time_t received_t; + time(&received_t); + + if (bufferlen % 6 > 0) { + fillbits = 6 - (bufferlen % 6); + for (k = bufferlen; k < bufferlen + fillbits; k++) + d->rbuffer[k] = 0; + + bufferlen = bufferlen + fillbits; + } + + protodec_generate_nmea(d, bufferlen, fillbits, received_t); + + d->seqnr++; + if (d->seqnr > 9) + d->seqnr = 0; + + if (type < 1 || type > MAX_AIS_PACKET_TYPE) + return; // unsupported packet type +} + +void protodec_decode(char *in, int count, struct demod_state_t *d, unsigned long samplenum) +{ + int i = 0; + int bufferlength, correct; + + while (i < count) { + switch (d->state) { + case ST_DATA: + if (d->bitstuff) { + if (in[i] == 1) { + d->state = ST_STOPSIGN; + d->ndata = 0; + d->bitstuff = 0; + } else { + d->ndata++; + d->last = in[i]; + d->bitstuff = 0; + } + } else { + if (in[i] == d->last && in[i] == 1) { + d->antallenner++; + if (d->antallenner == 4) { + d->bitstuff = 1; + d->antallenner = 0; + } + + } else + d->antallenner = 0; + + d->buffer[d->bufferpos] = in[i]; + d->bufferpos++; + d->ndata++; + + if (d->bufferpos >= 449) { + protodec_reset(d); + } + } + break; + + case ST_SKURR: + if (in[i] != d->last) + d->antallpreamble++; + else + d->antallpreamble = 0; + d->last = in[i]; + if (d->antallpreamble > 14 && in[i] == 0) { + d->state = ST_PREAMBLE; + d->nskurr = 0; + d->antallpreamble = 0; + } + d->nskurr++; + break; + + case ST_PREAMBLE: + if (in[i] != d->last && d->nstartsign == 0) { + d->antallpreamble++; + } else { + if (in[i] == 1) { + if (d->nstartsign == 0) { + d->nstartsign = 3; + d->last = in[i]; + } else if (d->nstartsign == 5) { + d->nstartsign++; + d->npreamble = 0; + d->antallpreamble = 0; + d->state = ST_STARTSIGN; + } else { + d->nstartsign++; + } + + } else { + if (d->nstartsign == 0) { + d->nstartsign = 1; + } else { + protodec_reset(d); + } + } + } + d->npreamble++; + break; + + case ST_STARTSIGN: + if (d->nstartsign >= 7) { + if (in[i] == 0) { + d->state = ST_DATA; + d->startsample = samplenum; + d->nstartsign = 0; + d->antallenner = 0; + memset(d->buffer, 0, DEMOD_BUFFER_LEN); + d->bufferpos = 0; + } else { + protodec_reset(d); + } + + } else if (in[i] == 0) { + protodec_reset(d); + } + d->nstartsign++; + break; + + case ST_STOPSIGN: + bufferlength = d->bufferpos - 6 - 16; + if (in[i] == 0 && bufferlength > 0) { + correct = protodec_calculate_crc(bufferlength, d); + if (correct) { + d->receivedframes++; + protodec_getdata(bufferlength, d); + } else { + d->lostframes++; + } + } else { + d->lostframes2++; + } + protodec_reset(d); + break; + + + } + d->last = in[i]; + i++; + } +} + diff --git a/aisdecoder/lib/protodec.h b/aisdecoder/lib/protodec.h new file mode 100644 index 0000000..61f82dc --- /dev/null +++ b/aisdecoder/lib/protodec.h @@ -0,0 +1,68 @@ + +/* + * protodec.h + * + * (c) Ruben Undheim 2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef INC_PROTODEC_H +#define INC_PROTODEC_H + +#define ST_SKURR 1 +#define ST_PREAMBLE 2 +#define ST_STARTSIGN 3 +#define ST_DATA 4 +#define ST_STOPSIGN 5 + +#define DEMOD_BUFFER_LEN 450 +#define MAX_AIS_PACKET_TYPE 27 +#define NMEABUFFER_LEN 128 + +struct demod_state_t { + char chanid; + int state; + unsigned int offset; + int nskurr, npreamble, nstartsign, ndata, nstopsign; + + int antallenner; + unsigned char *buffer; + unsigned char *rbuffer; + char *tbuffer; + int bufferpos; + char last; + int antallpreamble; + int bitstuff; + int receivedframes; + int lostframes; + int lostframes2; + unsigned char seqnr; + + unsigned long startsample; + int add_sample_num; + + struct serial_state_t *serial; + + char *nmea; +}; + +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid, int add_sample_num); +void protodec_reset(struct demod_state_t *d); +void protodec_getdata(int bufferlengde, struct demod_state_t *d); +void protodec_decode(char *in, int count, struct demod_state_t *d, unsigned long samplenum); + +#endif diff --git a/aisdecoder/lib/receiver.c b/aisdecoder/lib/receiver.c new file mode 100644 index 0000000..cba1e7e --- /dev/null +++ b/aisdecoder/lib/receiver.c @@ -0,0 +1,149 @@ + +/* + * receiver.c + * + * (c) Ruben Undheim 2008 + * (c) Heikki Hannikainen 2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +//#include "config.h" + +#include +#include + +#include "receiver.h" +#include "hmalloc.h" +#include "filter.h" + +static int sound_levellog=1; + +receiver_on_level_changed on_sound_level_changed=NULL; + +static float coeffs[]={ + 2.5959e-55, 2.9479e-49, 1.4741e-43, 3.2462e-38, 3.1480e-33, + 1.3443e-28, 2.5280e-24, 2.0934e-20, 7.6339e-17, 1.2259e-13, + 8.6690e-11, 2.6996e-08, 3.7020e-06, 2.2355e-04, 5.9448e-03, + 6.9616e-02, 3.5899e-01, 8.1522e-01, 8.1522e-01, 3.5899e-01, + 6.9616e-02, 5.9448e-03, 2.2355e-04, 3.7020e-06, 2.6996e-08, + 8.6690e-11, 1.2259e-13, 7.6339e-17, 2.0934e-20, 2.5280e-24, + 1.3443e-28, 3.1480e-33, 3.2462e-38, 1.4741e-43, 2.9479e-49, + 2.5959e-55 +}; +#define COEFFS_L 36 + + +struct receiver *init_receiver(char name, int num_ch, int ch_ofs, int add_sample_num) +{ + struct receiver *rx; + + rx = (struct receiver *) hmalloc(sizeof(struct receiver)); + memset(rx, 0, sizeof(struct receiver)); + + rx->filter = filter_init(COEFFS_L, coeffs); + + rx->decoder = hmalloc(sizeof(struct demod_state_t)); + protodec_initialize(rx->decoder, NULL, name, add_sample_num); + + rx->name = name; + rx->lastbit = 0; + rx->num_ch = num_ch; + rx->ch_ofs = ch_ofs; + rx->pll = 0; + rx->pllinc = 0x10000 / 5; + rx->prev = 0; + rx->last_levellog = 0; + + return rx; +} + +void free_receiver(struct receiver *rx) +{ + if (rx) { + filter_free(rx->filter); + hfree(rx); + } +} + +#define INC 16 +#define FILTERED_LEN 8192 + +void receiver_run(struct receiver *rx, short *buf, int len) +{ + float out; + int curr, bit; + char b; + short maxval = 0; + int level_distance; + float level; + int rx_num_ch = rx->num_ch; + float filtered[FILTERED_LEN]; + int i; + + /* len is number of samples available in buffer for each + * channels - something like 1024, regardless of number of channels */ + + buf += rx->ch_ofs; + + if (len > FILTERED_LEN) + abort(); + + maxval = filter_run_buf(rx->filter, buf, filtered, rx_num_ch, len); + + for (i = 0; i < len; i++) { + rx->samplenum++; + + out = filtered[i]; + curr = (out > 0); + + if ((curr ^ rx->prev) == 1) { + if (rx->pll < (0x10000 / 2)) { + rx->pll += rx->pllinc / INC; + } else { + rx->pll -= rx->pllinc / INC; + } + } + rx->prev = curr; + + rx->pll += rx->pllinc; + + if (rx->pll > 0xffff) { + /* slice */ + bit = (out > 0); + /* nrzi decode */ + b = !(bit ^ rx->lastbit); + /* feed to the decoder */ + protodec_decode(&b, 1, rx->decoder, rx->samplenum); + + rx->lastbit = bit; + rx->pll &= 0xffff; + } + } + + /* calculate level, and log it */ + level = (float)maxval / (float)32768 * (float)100; + level_distance = time(NULL) - rx->last_levellog; + + if (level > 95.0 && (level_distance >= 30 || level_distance >= sound_levellog)) { + if (on_sound_level_changed != NULL) on_sound_level_changed(level, rx->ch_ofs, 1); + time(&rx->last_levellog); + } else if (sound_levellog != 0 && level_distance >= sound_levellog) { + if (on_sound_level_changed != NULL) on_sound_level_changed(level, rx->ch_ofs, 0); + time(&rx->last_levellog); + } +} + diff --git a/aisdecoder/lib/receiver.h b/aisdecoder/lib/receiver.h new file mode 100644 index 0000000..63eb4c5 --- /dev/null +++ b/aisdecoder/lib/receiver.h @@ -0,0 +1,60 @@ + +/* + * receiver.h + * + * (c) Ruben Undheim 2008 + * (c) Heikki Hannikainen 2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef INC_RECEIVER_H +#define INC_RECEIVER_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#include "protodec.h" +#include "callbacks.h" + +struct receiver { + struct filter *filter; + char name; + int lastbit; + int num_ch; + int ch_ofs; + unsigned int pll; + unsigned int pllinc; + struct demod_state_t *decoder; + int prev; + time_t last_levellog; + unsigned long samplenum; +}; + +extern struct receiver *init_receiver(char name, int num_ch, int ch_ofs, int add_sample_num); +extern void free_receiver(struct receiver *rx); + +extern void receiver_run(struct receiver *rx, short *buf, int len); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/aisdecoder/sounddecoder.c b/aisdecoder/sounddecoder.c new file mode 100644 index 0000000..79b97ce --- /dev/null +++ b/aisdecoder/sounddecoder.c @@ -0,0 +1,128 @@ +/* + * sounddecoder.cpp + * + * This file is part of AISDecoder. + * + * Copyright (C) 2013 + * Astra Paging Ltd / AISHub (info@aishub.net) + * + * AISDecoder 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 2 of the License, or + * (at your option) any later version. + * + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * + */ + +#include +#include +//#include "config.h" +#ifdef WIN32 + #include +#endif + +#include "lib/receiver.h" +#include "lib/hmalloc.h" + +#define MAX_FILENAME_SIZE 512 +#define ERROR_MESSAGE_LENGTH 1024 +#include "sounddecoder.h" + + +char errorSoundDecoder[ERROR_MESSAGE_LENGTH]; + +static struct receiver *rx_a=NULL; +static struct receiver *rx_b=NULL; + +static short *buffer=NULL; +static int buffer_l=0; +static int buffer_read=0; +static int channels=0; +static Sound_Channels sound_channels; +static FILE *fp=NULL; +static void readBuffers(); +static time_t tprev=0; +static int time_print_stats=0; + +int initSoundDecoder(int buf_len,int _time_print_stats, int add_sample_num) +{ + sound_channels=SOUND_CHANNELS_STEREO; + channels = sound_channels == SOUND_CHANNELS_MONO ? 1 : 2; + time_print_stats=_time_print_stats; + tprev=time(NULL); // for decoder statistics + buffer = (short *) hmalloc(channels*sizeof(short)*buf_len); + rx_a = init_receiver('A', 2, 0, add_sample_num); + rx_b = init_receiver('B', 2, 1, add_sample_num); + return 1; +} + +void run_mem_decoder(short * buf, int len,int max_buf_len) +{ + int offset=0; + int bytes_in_len=len*channels; + char * p=(char *) buf; + while(bytes_in_len > max_buf_len ) + { + memcpy(buffer,p+offset,max_buf_len); + buffer_read=max_buf_len/(channels*sizeof(short)); + bytes_in_len-=max_buf_len; + offset+=max_buf_len; + readBuffers(); + } + memcpy(buffer,p+offset,bytes_in_len); + buffer_read=bytes_in_len/(channels*sizeof(short)); + readBuffers(); + + if(time_print_stats && (time(NULL)-tprev >= time_print_stats)) + { + struct demod_state_t *d = rx_a->decoder; + tprev=time(NULL); + fprintf(stderr, + "A: Received correctly: %d packets, wrong CRC: %d packets, wrong size: %d packets\n", + d->receivedframes, d->lostframes, + d->lostframes2); + d = rx_b->decoder; + fprintf(stderr, + "B: Received correctly: %d packets, wrong CRC: %d packets, wrong size: %d packets\n", + d->receivedframes, d->lostframes, + d->lostframes2); + } +} +void runSoundDecoder(int *stop) { + while (!*stop) { + buffer_read = fread(buffer, channels * sizeof(short), buffer_l, fp); + readBuffers(); + } +} + +static void readBuffers() { + if (buffer_read <= 0) return; + if (rx_a != NULL && sound_channels != SOUND_CHANNELS_RIGHT) + receiver_run(rx_a, buffer, buffer_read); + + if (rx_b != NULL && + (sound_channels == SOUND_CHANNELS_STEREO || sound_channels == SOUND_CHANNELS_RIGHT) + ) receiver_run(rx_b, buffer, buffer_read); +} + +void freeSoundDecoder(void) { + if (fp != NULL) { + fclose(fp); + fp=NULL; + } + + if (rx_a != NULL) { + free_receiver(rx_a); + rx_a=NULL; + } + + if (rx_b != NULL) { + free_receiver(rx_b); + rx_b=NULL; + } + if (buffer != NULL) { + hfree(buffer); + buffer = NULL; + } +} diff --git a/aisdecoder/sounddecoder.h b/aisdecoder/sounddecoder.h new file mode 100644 index 0000000..93a469a --- /dev/null +++ b/aisdecoder/sounddecoder.h @@ -0,0 +1,37 @@ +#ifndef SOUNDDECODER_H +#define SOUNDDECODER_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SOUND_CHANNELS_MONO, + SOUND_CHANNELS_STEREO, + SOUND_CHANNELS_LEFT, + SOUND_CHANNELS_RIGHT +} Sound_Channels; + +typedef enum { +#ifdef HAVE_ALSA + DRIVER_ALSA, +#endif +#ifdef HAVE_PULSEAUDIO + DRIVER_PULSE, +#endif +#ifdef WIN32 + DRIVER_WINMM, +#endif + DRIVER_FILE +} Sound_Driver; + +extern char errorSoundDecoder[]; +int initSoundDecoder(int buf_len,int _time_print_stats, int add_sample_num); +void runSoundDecoder(int *stop); +void freeSoundDecoder(void); +void run_mem_decoder(short * buf, int len,int max_buf_len); + +#ifdef __cplusplus +} +#endif +#endif // SOUNDDECODER_H diff --git a/ais/convenience.c b/convenience.c similarity index 100% rename from ais/convenience.c rename to convenience.c diff --git a/ais/convenience.h b/convenience.h similarity index 97% rename from ais/convenience.h rename to convenience.h index 088e755..d194b87 100644 --- a/ais/convenience.h +++ b/convenience.h @@ -17,6 +17,10 @@ /* a collection of user friendly tools */ +#include +//struct rtlsdr_dev_t; +//typedef struct rtlsdr_dev_t rtlsdr_dev_t; + /*! * Convert standard suffixes (k, M, G) to double * diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..2440006 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +rtl-ais (0.3.ppa1) bionic; urgency=low + + [ Frederic Guilbault ] + * Initial release. + + -- frederic guilbault <2@0464.ca> Sat, 29 Aug 2020 15:09:41 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..345c0e2 --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: rtl-ais +Maintainer: dgiardini +Uploaders: Frederic Guilbault <2@0464.ca> +Section: comm +Priority: optional +Standards-Version: 4.5.0 +Build-Depends: debhelper (>=9),librtlsdr-dev, pkg-config + +Package: rtl-ais +Section: comm +Priority: optional +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, librtlsdr0 +Homepage: https://github.com/dgiardini/rtl-ais +Description: AIS tuner and generic dual-frequency FM demodulator diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..885261e --- /dev/null +++ b/debian/copyright @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * 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 2 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 . + */ + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. \ No newline at end of file diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..b5d57d9 --- /dev/null +++ b/debian/files @@ -0,0 +1 @@ +rtl-ais_0.3.ppa13_source.buildinfo comm optional diff --git a/debian/readme.md b/debian/readme.md new file mode 100644 index 0000000..50a2c91 --- /dev/null +++ b/debian/readme.md @@ -0,0 +1,12 @@ +# Build rtl_ais into a debian package + +First, make sure you have the required dependencies : + ``` +sudo apt-get install build-essential devscripts lintian +``` + +Step 2, copy the compiled binary into the package location (implying rtl-ais have been compiled already): +``` +debuild -i -us -uc -b # for local package +debuild -S # for source package (required by launchpad) +``` diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..48f8ee5 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 +%: + dh $@ + +override_dh_auto_install: + $(MAKE) DESTDIR=$$(pwd)/debian/rtl-ais PREFIX=/usr install diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file diff --git a/heatmap/Vera.ttf b/heatmap/Vera.ttf deleted file mode 100644 index 58cd6b5..0000000 Binary files a/heatmap/Vera.ttf and /dev/null differ diff --git a/heatmap/flatten.py b/heatmap/flatten.py deleted file mode 100644 index 64b6949..0000000 --- a/heatmap/flatten.py +++ /dev/null @@ -1,52 +0,0 @@ -#! /usr/bin/env python - -import sys -from collections import defaultdict - -# todo -# interval based summary -# tall vs wide vs super wide output - -def help(): - print("flatten.py input.csv") - print("turns any rtl_power csv into a more compact summary") - sys.exit() - -if len(sys.argv) <= 1: - help() - -if len(sys.argv) > 2: - help() - -path = sys.argv[1] - -sums = defaultdict(float) -counts = defaultdict(int) - -def frange(start, stop, step): - i = 0 - f = start - while f <= stop: - f = start + step*i - yield f - i += 1 - -for line in open(path): - line = line.strip().split(', ') - low = int(line[2]) - high = int(line[3]) - step = float(line[4]) - weight = int(line[5]) - dbm = [float(d) for d in line[6:]] - for f,d in zip(frange(low, high, step), dbm): - sums[f] += d*weight - counts[f] += weight - -ave = defaultdict(float) -for f in sums: - ave[f] = sums[f] / counts[f] - -for f in sorted(ave): - print(','.join([str(f), str(ave[f])])) - - diff --git a/heatmap/raw_iq.py b/heatmap/raw_iq.py deleted file mode 100644 index b762ae2..0000000 --- a/heatmap/raw_iq.py +++ /dev/null @@ -1,103 +0,0 @@ -#! /usr/bin/env python - -""" -takes raw iq, turns into heatmap -extremely crude, lacks features like windowing -""" - -import sys, math, struct -import numpy -from PIL import Image - -def help(): - print("raw_iq.py bins averages sample-type input.raw") - print(" sample_types: u1 (uint8), s1 (int8), s2 (int16)") - sys.exit() - -def byte_reader(path, sample): - dtype = None - offset = 0 - scale = 2**7 - if sample == 'u1': - dtype = numpy.uint8 - offset = -127 - if sample == 's1': - dtype = numpy.int8 - if sample == 's2': - dtype = numpy.int16 - scale = 2**15 - raw = numpy.fromfile(path, dtype).astype(numpy.float64) - raw += offset - raw /= scale - return raw[0::2] + 1j * raw[1::2] - -def psd(data, bin_count, averages): - "really basic, lacks windowing" - length = len(data) - table = [numpy.zeros(bin_count)] - ave = 0 - for i in range(0, length, bin_count): - sub_data = numpy.array(data[i:i+bin_count]) - dc_bias = sum(sub_data) / len(sub_data) - #sub_data -= dc_bias - fft = numpy.fft.fft(sub_data) - if len(fft) != bin_count: - continue - table[-1] = table[-1] + numpy.real(numpy.conjugate(fft)*fft) - ave += 1 - if ave >= averages: - ave = max(1, ave) - row = table[-1] - row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2])) - # spurious warnings - table[-1] = 10 * numpy.log10(row / ave) - table.append(numpy.zeros(bin_count)) - ave = 0 - if ave != 0: - row = table[-1] - row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2])) - table[-1] = 10 * numpy.log10(row / ave) - if ave == 0: - table.pop(-1) - return table - -def rgb2(z, lowest, highest): - g = (z - lowest) / (highest - lowest) - return (int(g*255), int(g*255), 50) - -def heatmap(table): - lowest = -1 - highest = -100 - for row in table: - lowest = min(lowest, min(z for z in row if not math.isinf(z))) - highest = max(highest, max(row)) - img = Image.new("RGB", (len(table[0]), len(table))) - pix = img.load() - for y,row in enumerate(table): - for x,val in enumerate(row): - if not val >= lowest: # fast nan/-inf test - val = lowest - pix[x,y] = rgb2(val, lowest, highest) - return img - -if __name__ == '__main__': - try: - _, bin_count, averages, sample, path = sys.argv - bin_count = int(bin_count) - bin_count = int(2**(math.ceil(math.log(bin_count, 2)))) - averages = int(averages) - except: - help() - print("loading data") - data = byte_reader(path, sample) - print("estimated size: %i x %i" % (bin_count, - int(len(data) / (bin_count*averages)))) - print("crunching fft") - fft_table = psd(data, bin_count, averages) - print("drawing image") - img = heatmap(fft_table) - print("saving image") - img.save(path + '.png') - - - diff --git a/main.c b/main.c new file mode 100644 index 0000000..4c43d51 --- /dev/null +++ b/main.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * 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 2 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 + +typedef void* rtlsdr_dev_t; +#include "convenience.h" +#include "rtl_ais.h" + +void usage(void) +{ + fprintf(stderr, + "rtl_ais, a simple AIS tuner\n" + "\t and generic dual-frequency FM demodulator\n\n" + "(probably not a good idea to use with e4000 tuners)\n" + "Use: rtl_ais [options] [outputfile]\n" + "\t[-l left_frequency (default: 161.975M)]\n" + "\t[-r right_frequency (default: 162.025M)]\n" + "\t left freq < right freq\n" + "\t frequencies must be within 1.2MHz\n" + "\t[-s sample_rate (default: 24k)]\n" + "\t maximum value, might be down to 12k\n" + "\t[-o output_rate (default: 48k)]\n" + "\t must be equal or greater than twice -s value\n" + "\t[-E toggle edge tuning (default: off)]\n" + "\t[-D toggle DC filter (default: on)]\n" + //"\t[-O toggle oversampling (default: off)\n" + "\t[-d device_index (default: 0)]\n" + "\t[-g tuner_gain (default: automatic)]\n" + "\t[-p ppm_error (default: 0)]\n" + "\t[-R enable RTL chip AGC (default: off)]\n" + "\t[-A turn off built-in AIS decoder (default: on)]\n" + "\t use this option to output samples to file or stdout.\n" + "\tBuilt-in AIS decoder options:\n" + "\t[-h host (default: 127.0.0.1)]\n" + "\t[-P port (default: 10110)]\n" + "\t[-T use TCP communication, rtl-ais is tcp server ( -h is ignored)\n" + "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" + "\t[-n log NMEA sentences to console (stderr) (default off)]\n" + "\t[-I add sample index to NMEA messages (default off)]\n" + "\t[-L log sound levels to console (stderr) (default off)]\n\n" + "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" + "\tWhen the built-in AIS decoder is disabled the samples are sent to\n" + "\tto [outputfile] (a '-' dumps samples to stdout)\n" + "\t omitting the filename also uses stdout\n\n" + "\tOutput is stereo 2x16 bit signed ints\n\n" + "\tExamples:\n" + "\tReceive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110\n" + "\t and log the senteces to console:\n\n" + "\trtl_ais -n\n\n" + "\tTune two fm stations and play one on each channel:\n\n" + "\trtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - " + "\n"); + exit(1); +} + +static volatile int do_exit = 0; +static void sighandler(int signum) +{ + signum = signum; + fprintf(stderr, "Signal caught, exiting!\n"); + do_exit = 1; +} + +int main(int argc, char **argv) +{ +#ifndef WIN32 + struct sigaction sigact; + + sigact.sa_handler = sighandler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGPIPE, &sigact, NULL); +#else + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); +#endif + int opt; + + struct rtl_ais_config config; + rtl_ais_default_config(&config); + + config.host = strdup("127.0.0.1"); + config.port = strdup("10110"); + + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATIt:P:h:nLS:?")) != -1) + { + switch (opt) { + case 'l': + config.left_freq = (int)atofs(optarg); + break; + case 'r': + config.right_freq = (int)atofs(optarg); + break; + case 's': + config.sample_rate = (int)atofs(optarg); + break; + case 'o': + config.output_rate = (int)atofs(optarg); + break; + case 'E': + config.edge = !config.edge; + break; + case 'D': + config.dc_filter = !config.dc_filter; + break; + case 'O': + config.oversample = !config.oversample; + break; + case 'd': + config.dev_index = verbose_device_search(optarg); + config.dev_given = 1; + break; + case 'g': + config.gain = (int)(atof(optarg) * 10); + break; + case 'p': + config.ppm_error = atoi(optarg); + config.custom_ppm = 1; + break; + case 'R': + config.rtl_agc=1; + break; + case 'A': + config.use_internal_aisdecoder=0; + break; + case 'I': + config.add_sample_num = 1; + break; + case 'P': + config.port=strdup(optarg); + break; + case 'T': + config.use_tcp_listener=1; + break; + case 't': + config.tcp_keep_ais_time = atoi(optarg); + break; + case 'h': + config.host=strdup(optarg); + break; + case 'L': + config.show_levels=1; + break; + case 'S': + config.seconds_for_decoder_stats=atoi(optarg); + break; + case 'n': + config.debug_nmea = 1; + break; + case '?': + default: + usage(); + return 2; + } + } + + if (argc <= optind) { + config.filename = "-"; + } else { + config.filename = argv[optind]; + } + + if (config.edge) { + fprintf(stderr, "Edge tuning enabled.\n"); + } else { + fprintf(stderr, "Edge tuning disabled.\n"); + } + if (config.dc_filter) { + fprintf(stderr, "DC filter enabled.\n"); + } else { + fprintf(stderr, "DC filter disabled.\n"); + } + if (config.rtl_agc) { + fprintf(stderr, "RTL AGC enabled.\n"); + } else { + fprintf(stderr, "RTL AGC disabled.\n"); + } + if (config.use_internal_aisdecoder) { + fprintf(stderr, "Internal AIS decoder enabled.\n"); + } else { + fprintf(stderr, "Internal AIS decoder disabled.\n"); + } + + struct rtl_ais_context *ctx = rtl_ais_start(&config); + if(!ctx) { + fprintf(stderr, "\nrtl_ais_start failed, exiting...\n"); + exit(1); + } + /* + aidecoder.c appends the messages to a queue that can be used for a + routine if rtl_ais is compiled as lib. Here we only loop and dequeue + the messages, and the puts() sentence that print the message is + commented out. If the -n parameter is used the messages are printed from + nmea_sentence_received() in aidecoder.c + */ + while(!do_exit && rtl_ais_isactive(ctx)) { + #if _POSIX_C_SOURCE >= 199309L // nanosleep available() + struct timespec five = { 0, 50 * 1000 * 1000}; + #endif + const char *str; + if(config.use_internal_aisdecoder) + { + // dequeue + while((str = rtl_ais_next_message(ctx))) + { + //puts(str); or code something that fits your needs + } + } + #if _POSIX_C_SOURCE >= 199309L // nanosleep available() + nanosleep(&five, NULL); + #else + usleep(50000); + #endif + } + rtl_ais_cleanup(ctx); + return 0; +} diff --git a/rtl-sdl/8-bit-arch.pcx b/rtl-sdl/8-bit-arch.pcx deleted file mode 100644 index cd54e31..0000000 Binary files a/rtl-sdl/8-bit-arch.pcx and /dev/null differ diff --git a/rtl-sdl/build.sh b/rtl-sdl/build.sh deleted file mode 100755 index 7760597..0000000 --- a/rtl-sdl/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -# todo, a real makefile - -files="waterfall.c" -binary="waterfall" -flags="-Wall -O2" -includes="-I/usr/include/libusb-1.0" -libs="-lSDL -lSDL_image -lSDL_ttf -lusb-1.0 -lrtlsdr -lpthread -lm" - -rm -f $binary -gcc -o $binary $files $flags $includes $libs - diff --git a/rtl-sdl/din1451alt.ttf b/rtl-sdl/din1451alt.ttf deleted file mode 100644 index aef931a..0000000 Binary files a/rtl-sdl/din1451alt.ttf and /dev/null differ diff --git a/rtl-sdl/rtl_power_lite.c b/rtl-sdl/rtl_power_lite.c deleted file mode 100644 index 3984cf5..0000000 --- a/rtl-sdl/rtl_power_lite.c +++ /dev/null @@ -1,395 +0,0 @@ -/* - * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver - * Copyright (C) 2012 by Steve Markgraf - * Copyright (C) 2012 by Hoernchen - * Copyright (C) 2012 by Kyle Keen - * - * 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 2 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 . - */ - -// a quick and horrible hack job of rtl_power.c -// 1024 element FFT -// no downsampling -// dedicated thread -// external flags for retune, gain change, data ready, quit -// todo, preface with fft_ - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "rtl-sdr.h" - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -#define FFT_LEVEL 10 -#define FFT_STACK 4 -#define FFT_SIZE (1 << FFT_LEVEL) -#define DEFAULT_BUF_LENGTH (2 * FFT_SIZE * FFT_STACK) -#define BUFFER_DUMP (1<<12) -#define DEFAULT_ASYNC_BUF_NUMBER 32 -#define SAMPLE_RATE 3200000 -#define PRESCALE 8 -#define POSTSCALE 2 -#define FREQ_MIN 27000000 -#define FREQ_MAX 1700000000 - -struct buffer -{ - // each buffer should have one writer and one reader thread - // the reader waits for the cond - int16_t buf[DEFAULT_BUF_LENGTH]; - int len; - pthread_rwlock_t rw; - pthread_cond_t ready; - pthread_mutex_t ready_m; - int ready_fast; -}; - -// shared items - -static volatile int do_exit = 0; -static rtlsdr_dev_t *dev = NULL; -static struct buffer fft_out; -static int frequency = 97000000; - -// local items - -struct buffer rtl_out; -struct buffer fft_tmp; - -int16_t* Sinewave; -double* power_table; -int N_WAVE, LOG2_N_WAVE; -int next_power; -int16_t *fft_buf; -int *window_coefs; - -pthread_t dongle_thread; -pthread_t fft_thread; - -#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) -#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) - -// some functions from convenience.c - -void gain_default(void) -{ - int count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - rtlsdr_set_tuner_gain(dev, gains[count-1]); - free(gains); -} - -void gain_increase(void) -{ - int i, g, count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - g = rtlsdr_get_tuner_gain(dev); - for (i=0; i<(count-1); i++) - { - if (gains[i] == g) - { - rtlsdr_set_tuner_gain(dev, gains[i+1]); - break; - } - } - free(gains); -} - -void gain_decrease(void) -{ - int i, g, count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - g = rtlsdr_get_tuner_gain(dev); - for (i=1; i FREQ_MAX) - {frequency = FREQ_MAX;} - rtlsdr_set_center_freq(dev, frequency); -} - -// fft stuff - -void sine_table(int size) -{ - int i; - double d; - LOG2_N_WAVE = size; - N_WAVE = 1 << LOG2_N_WAVE; - Sinewave = malloc(sizeof(int16_t) * N_WAVE*3/4); - power_table = malloc(sizeof(double) * N_WAVE); - for (i=0; i> 14; - b = c & 0x01; - return (c >> 1) + b; -} - -int fix_fft(int16_t iq[], int m) -/* interleaved iq[], 0 <= n < 2**m, changes in place */ -{ - int mr, nn, i, j, l, k, istep, n, shift; - int16_t qr, qi, tr, ti, wr, wi; - n = 1 << m; - if (n > N_WAVE) - {return -1;} - mr = 0; - nn = n - 1; - /* decimation in time - re-order data */ - for (m=1; m<=nn; ++m) { - l = n; - do - {l >>= 1;} - while (mr+l > nn); - mr = (mr & (l-1)) + l; - if (mr <= m) - {continue;} - // real = 2*m, imag = 2*m+1 - tr = iq[2*m]; - iq[2*m] = iq[2*mr]; - iq[2*mr] = tr; - ti = iq[2*m+1]; - iq[2*m+1] = iq[2*mr+1]; - iq[2*mr+1] = ti; - } - l = 1; - k = LOG2_N_WAVE-1; - while (l < n) { - shift = 1; - istep = l << 1; - for (m=0; m>= 1; wi >>= 1;} - for (i=m; i>= 1; qi >>= 1;} - iq[2*j] = qr - tr; - iq[2*j+1] = qi - ti; - iq[2*i] = qr + tr; - iq[2*i+1] = qi + ti; - } - } - --k; - l = istep; - } - return 0; -} - -void remove_dc(int16_t *data, int length) -/* works on interleaved data */ -{ - int i; - int16_t ave; - long sum = 0L; - for (i=0; i < length; i+=2) { - sum += data[i]; - } - ave = (int16_t)(sum / (long)(length)); - if (ave == 0) { - return;} - for (i=0; i < length; i+=2) { - data[i] -= ave; - } -} - -int32_t real_conj(int16_t real, int16_t imag) -/* real(n * conj(n)) */ -{ - return ((int32_t)real*(int32_t)real + (int32_t)imag*(int32_t)imag); -} - -// threading stuff - -void rtl_callback_fn(unsigned char *buf, uint32_t len, void *ctx) -{ - int i; - if (do_exit) - {return;} - pthread_rwlock_wrlock(&rtl_out.rw); - for (i=0; irw, NULL); - pthread_cond_init(&buf->ready, NULL); - pthread_mutex_init(&buf->ready_m, NULL); - return 0; -} - -int buffer_cleanup(struct buffer* buf) -{ - pthread_rwlock_destroy(&buf->rw); - pthread_cond_destroy(&buf->ready); - pthread_mutex_destroy(&buf->ready_m); - return 0; -} - -static int fft_launch(void) -{ - sine_table(FFT_LEVEL); - - buffer_init(&rtl_out); - buffer_init(&fft_tmp); - buffer_init(&fft_out); - - rtlsdr_open(&dev, 0); // todo, verbose_device_search() - - // settings - rtlsdr_reset_buffer(dev); - rtlsdr_set_center_freq(dev, frequency); - rtlsdr_set_sample_rate(dev, SAMPLE_RATE); - rtlsdr_set_tuner_gain_mode(dev, 1); - gain_default(); - - pthread_create(&dongle_thread, NULL, &dongle_thread_fn, NULL); - pthread_create(&fft_thread, NULL, &fft_thread_fn, NULL); - return 0; -} - -static int fft_cleanup(void) -{ - do_exit = 1; - usleep(10000); - rtlsdr_cancel_async(dev); - pthread_join(dongle_thread, NULL); - safe_cond_signal(&rtl_out.ready, &rtl_out.ready_m); - pthread_join(fft_thread, NULL); - safe_cond_signal(&fft_out.ready, &fft_out.ready_m); - - rtlsdr_close(dev); - - buffer_cleanup(&rtl_out); - buffer_cleanup(&fft_tmp); - buffer_cleanup(&fft_out); - - return 0; -} - -// vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab diff --git a/rtl-sdl/sdl1.png b/rtl-sdl/sdl1.png deleted file mode 100644 index ac83a23..0000000 Binary files a/rtl-sdl/sdl1.png and /dev/null differ diff --git a/rtl-sdl/sdl2.png b/rtl-sdl/sdl2.png deleted file mode 100644 index 861a731..0000000 Binary files a/rtl-sdl/sdl2.png and /dev/null differ diff --git a/rtl-sdl/waterfall.c b/rtl-sdl/waterfall.c deleted file mode 100644 index e58fb4c..0000000 --- a/rtl-sdl/waterfall.c +++ /dev/null @@ -1,531 +0,0 @@ - -/* - -SDL powered waterfall - -at the moment this everything is hard-coded for a single platform -the BeagleboneBlack with an LCD7 touchscreen (framebuffer mode) - -it can run on other platforms, but will not autodetect anything -the keybinds are laid out for the touchscreen face buttons - -on the BBB: -full screen double buffered blits seem to perform at 140 fps (cpu limited) - -to run automatically: -@reboot sleep 1 && cd /the/install/path && ./waterfall - -todo: -benchmark against fftw3 -replace defines with options -autodetect things like screen resolution -change the displayed bandwidth -audio demodulation -fix screen blanking -a real make file - -*/ - -#include -#include - -#include "SDL/SDL.h" -#include "SDL/SDL_image.h" -#include "SDL/SDL_ttf.h" - -#include "rtl_power_lite.c" - -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 480 -char* font_path = "./din1451alt.ttf"; -#define FONT_SIZE 24 -#define FRAME_MS 30 -#define FRAME_LINES 10 -#define MAX_STRING 100 -#define BIG_JUMP 50000000 - -#if SDL_BYTEORDER == SDL_BIG_ENDIAN -static const Uint32 r_mask = 0xFF000000; -static const Uint32 g_mask = 0x00FF0000; -static const Uint32 b_mask = 0x0000FF00; -static const Uint32 a_mask = 0x000000FF; -#else -static const Uint32 r_mask = 0x000000FF; -static const Uint32 g_mask = 0x0000FF00; -static const Uint32 b_mask = 0x00FF0000; -static const Uint32 a_mask = 0xFF000000; -#endif - -static SDL_Surface* img_surface; -static SDL_Surface* scroll_surface; -static SDL_Surface* future_surface; -static const SDL_VideoInfo* info = 0; -SDL_Surface* screen; -TTF_Font *font; -int do_flip; // todo, cond -int credits_toggle; - -struct text_bin -{ - char string[MAX_STRING]; - int x, y; - int i; - int dirty; - SDL_Surface* surf_fg; - SDL_Surface* surf_bg; -}; - -struct text_bin credits[6]; -struct text_bin freq_labels[5]; - -int init_video() -{ - if (SDL_Init(SDL_INIT_VIDEO) < 0) - { - fprintf(stderr, "Video initialization failed: %s\n", - SDL_GetError()); - return 0; - } - - info = SDL_GetVideoInfo(); - - if( !info ) { - fprintf( stderr, "Video query failed: %s\n", - SDL_GetError( ) ); - return 0; - } - - return 1; -} - -int set_video( Uint16 width, Uint16 height, int bpp, int flags) -{ - if (init_video()) - { - if((screen = SDL_SetVideoMode(width,height,bpp,flags))==0) - { - fprintf( stderr, "Video mode set failed: %s\n", - SDL_GetError( ) ); - return 0; - } - } - return 1; -} - -int init_ttf() -{ - if (TTF_Init() != 0) - { - fprintf( stderr, "TTF init failed: %s\n", - SDL_GetError( ) ); - return 1; - } - font = TTF_OpenFont(font_path, FONT_SIZE); - if (font == NULL) - { - fprintf( stderr, "TTF load failed: %s\n", - TTF_GetError( ) ); - return 1; - } - return 0; -} - -void quit( int code ) -{ - SDL_FreeSurface(scroll_surface); - SDL_FreeSurface(future_surface); - SDL_FreeSurface(img_surface); - - TTF_Quit( ); - SDL_Quit( ); - - exit( code ); -} - -void handle_key_down(SDL_keysym* keysym) -{ - switch(keysym->sym) - { - case SDLK_ESCAPE: - quit(0); - break; - case SDLK_RETURN: - credits_toggle = !credits_toggle; - break; - case SDLK_DOWN: - case SDLK_UP: - case SDLK_LEFT: - case SDLK_RIGHT: - default: - break; - } -} - -void process_events( void ) -{ - SDL_Event event; - - while( SDL_PollEvent( &event ) ) { - - switch( event.type ) { - case SDL_KEYDOWN: - handle_key_down( &event.key.keysym ); - break; - case SDL_QUIT: - quit( 0 ); - break; - } - } -} - -void init() -{ - SDL_Surface* tmp; - int i; - SDL_Color colors[256]; - - tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH, - SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask); - scroll_surface = SDL_DisplayFormat(tmp); - SDL_FreeSurface(tmp); - - tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH, - SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask); - future_surface = SDL_DisplayFormat(tmp); - SDL_FreeSurface(tmp); - - img_surface = IMG_Load("8-bit-arch.pcx"); - for (i = 0; i < SDL_NUMEVENTS; ++i) - { - if (i != SDL_KEYDOWN && i != SDL_QUIT) - { - SDL_EventState(i, SDL_IGNORE); - } - } - - for(i=0; i<256; i++) - { - colors[i].r = i; - colors[i].g = i; - colors[i].b = 50; - } - colors[0].r = 0; colors[0].g = 0; colors[0].b = 0; - colors[255].r = 255; colors[255].g = 255; colors[255].b = 255; - - SDL_SetPalette(future_surface, SDL_LOGPAL|SDL_PHYSPAL, colors, 0, 256); - - SDL_ShowCursor(SDL_DISABLE); -} - -void putpixel(SDL_Surface *surface, int x, int y, uint32_t pixel) -/* taken from some stackoverflow post */ -{ - int bpp = surface->format->BytesPerPixel; - /* Here p is the address to the pixel we want to set */ - Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - - switch (bpp) { - case 1: - *p = pixel; - break; - - case 2: - *(uint16_t *)p = pixel; - break; - - case 3: - if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { - p[0] = (pixel >> 16) & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = pixel & 0xff; - } - else { - p[0] = pixel & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = (pixel >> 16) & 0xff; - } - break; - - case 4: - *(uint32_t *)p = pixel; - break; - - default: - break; /* shouldn't happen, but avoids warnings */ - } -} - -int pretty_text(SDL_Surface* surface, struct text_bin* text) -{ - SDL_Color fg_color = {255, 255, 255}; - SDL_Color bg_color = {0, 0, 0}; - SDL_Rect fg_rect = {text->x + 0, text->y + 0, SCREEN_WIDTH, SCREEN_HEIGHT}; - SDL_Rect bg_rect = {text->x + 2, text->y + 2, SCREEN_WIDTH, SCREEN_HEIGHT}; - - if (text->dirty) - { - // this leaks, but freeing segfaults? - // in practice, it leaks an MB an hour under very heavy use - //if (text->surf_fg != NULL) - // {SDL_FreeSurface(text->surf_fg);} - //if (text->surf_bg != NULL) - // {SDL_FreeSurface(text->surf_bg);} - text->surf_fg = TTF_RenderText_Solid(font, text->string, fg_color); - text->surf_bg = TTF_RenderText_Solid(font, text->string, bg_color); - text->dirty = 0; - } - - SDL_BlitSurface(text->surf_bg, NULL, surface, &bg_rect); - SDL_BlitSurface(text->surf_fg, NULL, surface, &fg_rect); - - return 0; -} - -void build_credits(void) -{ - int i; - int xs[] = {300, 300, 300, 300, 300, 300}; - int ys[] = {100, 150, 200, 250, 300, 350}; - strncpy(credits[0].string, "board: BeagleBone Black", MAX_STRING); - strncpy(credits[1].string, "display: CircuitCo LCD7", MAX_STRING); - strncpy(credits[2].string, "radio: rtl-sdr", MAX_STRING); - strncpy(credits[3].string, "graphics: SDL", MAX_STRING); - strncpy(credits[4].string, "os: Arch Linux ARM", MAX_STRING); - strncpy(credits[5].string, "glue: Kyle Keen", MAX_STRING); - for (i=0; i<6; i++) - { - credits[i].x = xs[i]; - credits[i].y = ys[i]; - credits[i].dirty = 1; - } -} - -void show_credits(SDL_Surface* surface) -{ - int i; - for (i=0; i<6; i++) - {pretty_text(surface, &(credits[i]));} -} - -void build_labels(void) -{ - // very similar to the lines code - int f, i, x, drift, center; - drift = (frequency % 1000000) / (SAMPLE_RATE / FFT_SIZE); - center = frequency - (frequency % 1000000); - for (i=-2; i<=2; i++) - { - x = SCREEN_WIDTH / 2 + -drift + i * 1000000 / (SAMPLE_RATE / FFT_SIZE); - f = center + i * 1000000; - freq_labels[i+2].x = x - FONT_SIZE/2; - freq_labels[i+2].y = 10; - if (freq_labels[i+2].i == f) - {continue;} - freq_labels[i+2].dirty = 1; - freq_labels[i+2].i = f; - snprintf(freq_labels[i+2].string, MAX_STRING, "%i", f/1000000); - } -} - -void static_events(void) -{ - SDL_Rect blank = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; - uint8_t* keystate = SDL_GetKeyState(NULL); - if (keystate[SDLK_LEFT]) - { - frequency -= BIG_JUMP; - frequency_set(); - build_labels(); - SDL_FillRect(scroll_surface, &blank, 0); - } - if (keystate[SDLK_RIGHT]) - { - frequency += BIG_JUMP; - frequency_set(); - build_labels(); - SDL_FillRect(scroll_surface, &blank, 0); - } - if (keystate[SDLK_UP]) - {gain_decrease();} - if (keystate[SDLK_DOWN]) - {gain_increase();} -} - -uint32_t frame_callback(uint32_t interval, void* param) -{ - do_flip = 1; - return interval; -} - -uint32_t rgb(uint32_t i) -{ - return ((b_mask/255)*20 | (r_mask/255)*i | (g_mask/255)*i); -} - -int mouse_stuff(void) -// returns X scroll offset -// kind of crap with variable framerate -{ - static double prev_x = -100; - static double velo = 0; - double deaccel = 10; - int x, y, buttons; - buttons = SDL_GetMouseState(&x, &y); - if (buttons & SDL_BUTTON_LMASK) - { - if (prev_x < 0) - { - prev_x = x; - } - velo = x - prev_x; - prev_x = x; - //fprintf(stdout, "%i %f\n", x, velo); - } else { - prev_x = -100; - if (velo > deaccel) - {velo -= deaccel;} - if (velo < -deaccel) - {velo += deaccel;} - if (velo >= -deaccel && velo <= deaccel) - {velo *= 0.5;} - } - return (int)round(velo); -} - -int main( int argc, char* argv[] ) -{ - int i, c, x, y, v, line; - int blits = 0; - uint32_t pixel = 0; - struct text_bin text; - SDL_Rect ScrollFrom = {0, 1, SCREEN_WIDTH, SCREEN_HEIGHT}; - if (!set_video(SCREEN_WIDTH, SCREEN_HEIGHT, 8, - SDL_HWSURFACE | SDL_HWACCEL | SDL_HWPALETTE /*| SDL_FULLSCREEN*/)) - quit(1); - init_ttf(); - //SDL_Init(SDL_INIT_TIMER); - - SDL_WM_SetCaption("Demo", ""); - - init(); - - build_credits(); - build_labels(); - - strncpy(text.string, "<< >> - + ?", MAX_STRING); - text.x = 30; - text.y = 450; - text.dirty = 1; - - SDL_BlitSurface(img_surface, NULL, scroll_surface, NULL); - //SDL_AddTimer(FRAME_MS, frame_callback, NULL); - - fft_launch(); - y = 0; - SDL_LockSurface(future_surface); - while(1) - { - process_events(); - //safe_cond_wait(&fft_out.ready, &fft_out.ready_m); - if (!fft_out.ready_fast) - { - usleep(1000); - continue; - } - fft_out.ready_fast = 0; - pthread_rwlock_rdlock(&fft_out.rw); - for (x=0; x 254) - {c = 254;} - if (c < 1) - {c = 1;} - //fprintf(stdout, "%i ", fft_out.buf[i]); - putpixel(future_surface, x, y, 40*fft_out.buf[i] + 1); - pixel++; - } - // lines every 100KHz - line = (frequency % 100000) / (SAMPLE_RATE / FFT_SIZE); - for (i=-15; i<15; i++) - { - if (y%4) - {break;} - x = SCREEN_WIDTH / 2 + -line + i * 100000 / (SAMPLE_RATE / FFT_SIZE); - if (x < 0) - {continue;} - if (x > SCREEN_WIDTH) - {continue;} - putpixel(future_surface, x, y, 0xFF); - } - //fprintf(stdout, "\n"); - pthread_rwlock_unlock(&fft_out.rw); - y++; - if (!do_flip && y <= FRAME_LINES) - {continue;} - static_events(); - v = mouse_stuff(); - if (v != 0) - { - frequency += (-v * SAMPLE_RATE / FFT_SIZE); - frequency_set(); - build_labels(); - } - SDL_UnlockSurface(future_surface); - // scroll - ScrollFrom.x = -v; - ScrollFrom.y = y; - ScrollFrom.w = SCREEN_WIDTH; - ScrollFrom.h = SCREEN_HEIGHT; - SDL_BlitSurface(scroll_surface, &ScrollFrom, scroll_surface, NULL); - // nuke edges - if (v > 0) - { - ScrollFrom.x = 0; - ScrollFrom.y = 0; - } - if (v < 0) - { - ScrollFrom.x = SCREEN_WIDTH+v; - ScrollFrom.y = 0; - } - if (v != 0) - { - ScrollFrom.w = abs(v); - ScrollFrom.h = SCREEN_HEIGHT-y; - SDL_FillRect(scroll_surface, &ScrollFrom, 0); - } - // new stuff - ScrollFrom.x = v; - ScrollFrom.y = SCREEN_HEIGHT - y; - ScrollFrom.w = SCREEN_WIDTH; - ScrollFrom.h = SCREEN_HEIGHT; - SDL_BlitSurface(future_surface, NULL, scroll_surface, &ScrollFrom); - SDL_BlitSurface(scroll_surface, NULL, screen, NULL); - // overlay - pretty_text(screen, &text); - if (credits_toggle) - {show_credits(screen);} - for (i=0; i<5; i++) - {pretty_text(screen, &freq_labels[i]);} - pretty_text(screen, &text); - SDL_Flip(screen); - // only way to keep the BBB from blanking the screen - // (the 10 minute timeout can not be changed by any known means) - if (blits % 2000 == 0) - {system("setterm -blank poke");} - blits++; - do_flip = 0; - y = 0; - SDL_LockSurface(future_surface); - } - quit(0); - fft_cleanup(); - - return 0; -} - - -// vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab smarttab: diff --git a/rtl_ais.c b/rtl_ais.c new file mode 100644 index 0000000..d553537 --- /dev/null +++ b/rtl_ais.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * This program is free software: you can redistribsetute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 . + */ + + +/* todo + * support left > right + * thread left/right channels + * more array sharing + * something to correct for clock drift (look at demod's dc bias?) + * 4x oversampling (with cic up/down) + * droop correction + * alsa integration + * better upsampler (libsamplerate?) + * windows support + */ + +#include +#include +#include +#include +#include +#ifdef WIN32 + #include +#endif + +#include + +#include + +#include "rtl_ais.h" +#include "convenience.h" +#include "aisdecoder/aisdecoder.h" + +#define DEFAULT_ASYNC_BUF_NUMBER 12 +#define DEFAULT_BUF_LENGTH (16 * 16384) +#define AUTO_GAIN -100 + +/* signals are not threadsafe by default */ +#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) +#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) + +struct downsample_state +{ + int16_t *buf; + int len_in; + int len_out; + int rate_in; + int rate_out; + int downsample; + int downsample_passes; + int16_t lp_i_hist[10][6]; + int16_t lp_q_hist[10][6]; + pthread_rwlock_t rw; + //droop compensation + int16_t droop_i_hist[9]; + int16_t droop_q_hist[9]; + +}; + +struct demod_state +{ + int16_t *buf; + int buf_len; + int16_t *result; + int result_len; + int now_r, now_j; + int pre_r, pre_j; + int dc_avg; // really should get its own struct + +}; + +struct upsample_stereo +{ + int16_t *buf_left; + int16_t *buf_right; + int16_t *result; + int bl_len; + int br_len; + int result_len; + int rate; +}; + +int cic_9_tables[][10] = { + {0,}, + {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, + {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, + {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, + {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, + {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, + {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, + {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, + {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, + {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, + {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, +}; + + +static void rotate_90(int16_t *buf, int len) +/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j + or [0, 1, -3, 2, -4, -5, 7, -6] */ +{ + int i; + int16_t tmp; + for (i=0; i> 4; + for (i=4; i> 4; + } + /* archive */ + hist[0] = a; + hist[1] = b; + hist[2] = c; + hist[3] = d; + hist[4] = e; + hist[5] = f; +} + +static void generic_fir(int16_t *data, int length, const int *fir, int16_t *hist) +/* Okay, not at all generic. Assumes length 9, fix that eventually. */ +{ + int d, temp, sum; + for (d=0; d> 15 ; + hist[0] = hist[1]; + hist[1] = hist[2]; + hist[2] = hist[3]; + hist[3] = hist[4]; + hist[4] = hist[5]; + hist[5] = hist[6]; + hist[6] = hist[7]; + hist[7] = hist[8]; + hist[8] = temp; + } +} + +static void downsample(struct downsample_state *d) +{ + int i, ds_p; + ds_p = d->downsample_passes; + for (i=0; ibuf, (d->len_in >> i), d->lp_i_hist[i]); + fifth_order(d->buf+1, (d->len_in >> i)-1, d->lp_q_hist[i]); + } + // droop compensation + generic_fir(d->buf, d->len_in >> ds_p,cic_9_tables[ds_p], d->droop_i_hist); + generic_fir(d->buf+1, (d->len_in>> ds_p)-1,cic_9_tables[ds_p], d->droop_q_hist); +} + +static void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) +{ + *cr = ar*br - aj*bj; + *cj = aj*br + ar*bj; +} + +#if 0 // not used +static int polar_discriminant(int ar, int aj, int br, int bj) +{ + int cr, cj; + double angle; + multiply(ar, aj, br, -bj, &cr, &cj); + angle = atan2((double)cj, (double)cr); + return (int)(angle / 3.14159 * (1<<14)); +} +#endif + +static int fast_atan2(int y, int x) +/* pre scaled for int16 */ +{ + int yabs, angle; + int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14 + if (x==0 && y==0) { + return 0; + } + yabs = y; + if (yabs < 0) { + yabs = -yabs; + } + if (x >= 0) { + angle = pi4 - pi4 * (x-yabs) / (x+yabs); + } else { + angle = pi34 - pi4 * (x+yabs) / (yabs-x); + } + if (y < 0) { + return -angle; + } + return angle; +} + +static int polar_disc_fast(int ar, int aj, int br, int bj) +{ + int cr, cj; + multiply(ar, aj, br, -bj, &cr, &cj); + return fast_atan2(cj, cr); +} + +static void demodulate(struct demod_state *d) +{ + int i, pcm; + int16_t *buf = d->buf; + int16_t *result = d->result; + pcm = polar_disc_fast(buf[0], buf[1], + d->pre_r, d->pre_j); + + result[0] = (int16_t)pcm; + for (i = 2; i < (d->buf_len-1); i += 2) { + // add the other atan types? + pcm = polar_disc_fast(buf[i], buf[i+1], + buf[i-2], buf[i-1]); + result[i/2] = (int16_t)pcm; + } + d->pre_r = buf[d->buf_len - 2]; + d->pre_j = buf[d->buf_len - 1]; +} + +static void dc_block_filter(struct demod_state *d) +{ + int i, avg; + int64_t sum = 0; + int16_t *result = d->result; + for (i=0; i < d->result_len; i++) { + sum += result[i]; + } + avg = sum / d->result_len; + avg = (avg + d->dc_avg * 9) / 10; + for (i=0; i < d->result_len; i++) { + result[i] -= avg; + } + d->dc_avg = avg; +} + +static void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) +/* linear interpolation, len1 < len2 */ +{ + int i = 1; + int j = 0; + int tick = 0; + double frac; // use integers... + while (j < len2) { + frac = (double)tick / (double)len2; + buf2[j] = (int16_t)((double)buf1[i-1]*(1-frac) + (double)buf1[i]*frac); + j++; + tick += len1; + if (tick > len2) { + tick -= len2; + i++; + } + if (i >= len1) { + i = len1 - 1; + tick = len2; + } + } +} + +struct rtl_ais_context +{ + int active, dc_filter, use_internal_aisdecoder; + + pthread_t demod_thread; + pthread_t rtlsdr_thread; + + pthread_cond_t ready; + pthread_mutex_t ready_m; + + rtlsdr_dev_t *dev; + FILE *file; + + /* complex iq pairs */ + struct downsample_state both; + struct downsample_state left; + struct downsample_state right; + /* iq pairs and real mono */ + struct demod_state left_demod; + struct demod_state right_demod; + /* real stereo pairs (upsampled) */ + struct upsample_stereo stereo; +}; + +static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *arg) +{ + struct rtl_ais_context *ctx = arg; + unsigned i; + if (!ctx->active) { + return;} + pthread_rwlock_wrlock(&ctx->both.rw); + for (i=0; iboth.buf[i] = ((int16_t)buf[i]) - 127; + + pthread_rwlock_unlock(&ctx->both.rw); + safe_cond_signal(&ctx->ready, &ctx->ready_m); +} + +static void *rtlsdr_thread_fn(void *arg) +{ + struct rtl_ais_context *ctx = arg; + rtlsdr_read_async(ctx->dev, rtlsdr_callback, arg, + DEFAULT_ASYNC_BUF_NUMBER, + DEFAULT_BUF_LENGTH); + + ctx->active = 0; + return 0; +} + +static void pre_output(struct rtl_ais_context *ctx) +{ + int i; + for (i=0; istereo.bl_len; i++) { + ctx->stereo.result[i*2] = ctx->stereo.buf_left[i]; + ctx->stereo.result[i*2+1] = ctx->stereo.buf_right[i]; + } +} + +static void *demod_thread_fn(void *arg) +{ + struct rtl_ais_context *ctx = arg; + while (ctx->active) { + safe_cond_wait(&ctx->ready, &ctx->ready_m); + pthread_rwlock_wrlock(&ctx->both.rw); + downsample(&ctx->both); + memcpy(ctx->left.buf, ctx->both.buf, 2*ctx->both.len_out); + memcpy(ctx->right.buf, ctx->both.buf, 2*ctx->both.len_out); + pthread_rwlock_unlock(&ctx->both.rw); + rotate_90(ctx->left.buf, ctx->left.len_in); + downsample(&ctx->left); + memcpy(ctx->left_demod.buf, ctx->left.buf, 2*ctx->left.len_out); + demodulate(&ctx->left_demod); + if (ctx->dc_filter) { + dc_block_filter(&ctx->left_demod);} + //if (oversample) { + // downsample(&left);} + //fprintf(stderr,"\nUpsample result_len:%d stereo.bl_len:%d :%f\n",left_demod.result_len,stereo.bl_len,(float)stereo.bl_len/(float)left_demod.result_len); + arbitrary_upsample(ctx->left_demod.result, ctx->stereo.buf_left, ctx->left_demod.result_len, ctx->stereo.bl_len); + rotate_m90(ctx->right.buf, ctx->right.len_in); + downsample(&ctx->right); + memcpy(ctx->right_demod.buf, ctx->right.buf, 2*ctx->right.len_out); + demodulate(&ctx->right_demod); + if (ctx->dc_filter) { + dc_block_filter(&ctx->right_demod);} + //if (oversample) { + // downsample(&right);} + arbitrary_upsample(ctx->right_demod.result, ctx->stereo.buf_right, ctx->right_demod.result_len, ctx->stereo.br_len); + pre_output(ctx); + if(ctx->use_internal_aisdecoder){ + // stereo.result -> int_16 + // stereo.result_len -> number of samples for each channel + run_rtlais_decoder(ctx->stereo.result,ctx->stereo.result_len); + } + else{ + fwrite(ctx->stereo.result, 2, ctx->stereo.result_len, ctx->file); + } + } + + free_ais_decoder(); + return 0; +} + +static void downsample_init(struct downsample_state *dss) +/* simple ints should be already set */ +{ + int i, j; + dss->buf = malloc(dss->len_in * sizeof(int16_t)); + dss->rate_out = dss->rate_in / dss->downsample; + + //dss->downsample_passes = (int)log2(dss->downsample); + dss->len_out = dss->len_in / dss->downsample; + for (i=0; i<10; i++) { for (j=0; j<6; j++) { + dss->lp_i_hist[i][j] = 0; + dss->lp_q_hist[i][j] = 0; + }} + pthread_rwlock_init(&dss->rw, NULL); +} + +static void demod_init(struct demod_state *ds) +{ + ds->buf = malloc(ds->buf_len * sizeof(int16_t)); + ds->result = malloc(ds->result_len * sizeof(int16_t)); + ds->dc_avg=0; +} + +static void stereo_init(struct upsample_stereo *us) +{ + us->buf_left = malloc(us->bl_len * sizeof(int16_t)); + us->buf_right = malloc(us->br_len * sizeof(int16_t)); + us->result = malloc(us->result_len * sizeof(int16_t)); +} + +void rtl_ais_default_config(struct rtl_ais_config *config) +{ + config->gain = AUTO_GAIN; /* tenths of a dB */ + config->dev_index = 0; + config->dev_given = 0; + config->ppm_error = 0; + config->rtl_agc=0; + config->custom_ppm = 0; + config->left_freq = 161975000; + config->right_freq = 162025000; + config->sample_rate = 24000; + config->output_rate = 48000; + config->dc_filter=1; + config->edge = 0; + config->use_tcp_listener = 0, config->tcp_keep_ais_time = 15; + config->use_internal_aisdecoder=1; + config->seconds_for_decoder_stats=0; + /* Aisdecoder */ + config->show_levels=0; + config->debug_nmea = 0; + + config->host=NULL; + config->port=NULL; + + config->filename = "-"; + + config->add_sample_num = 0; +} + +struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) +{ + if (config->left_freq > config->right_freq) + return NULL; + + struct rtl_ais_context *ctx = malloc(sizeof(struct rtl_ais_context)); + ctx->active = 1; + + /* precompute rates */ + int dongle_freq, dongle_rate, delta, i; + dongle_freq = config->left_freq/2 + config->right_freq/2; + if (config->edge) { + dongle_freq -= config->sample_rate/2;} + delta = config->right_freq - config->left_freq; + if (delta > 1.2e6) { + fprintf(stderr, "Frequencies may be at most 1.2MHz apart."); + exit(1); + } + if (delta < 0) { + fprintf(stderr, "Left channel must be lower than right channel."); + exit(1); + } + i = (int)log2(2.4e6 / delta); + dongle_rate = delta * (1<both.rate_in = dongle_rate; + ctx->both.rate_out = delta * 2; + i = (int)log2(ctx->both.rate_in/ctx->both.rate_out); + ctx->both.downsample_passes = i; + ctx->both.downsample = 1 << i; + ctx->left.rate_in = ctx->both.rate_out; + i = (int)log2(ctx->left.rate_in / config->sample_rate); + ctx->left.downsample_passes = i; + ctx->left.downsample = 1 << i; + ctx->left.rate_out = ctx->left.rate_in / ctx->left.downsample; + + ctx->right.rate_in = ctx->left.rate_in; + ctx->right.rate_out = ctx->left.rate_out; + ctx->right.downsample = ctx->left.downsample; + ctx->right.downsample_passes = ctx->left.downsample_passes; + ctx->dc_filter=config->dc_filter; + if (ctx->left.rate_out > config->output_rate) { + fprintf(stderr, "Channel bandwidth too high or output bandwidth too low."); + exit(1); + } + + fprintf(stderr, "Buffer size: %0.2f mS\n", 1000 * (double)DEFAULT_BUF_LENGTH / (double)dongle_rate); + fprintf(stderr, "Downsample factor: %i\n", ctx->both.downsample * ctx->left.downsample); + fprintf(stderr, "Low pass: %i Hz\n", ctx->left.rate_out); + fprintf(stderr, "Output: %i Hz\n", config->output_rate); + + /* precompute lengths */ + ctx->both.len_in = DEFAULT_BUF_LENGTH; + ctx->both.len_out = ctx->both.len_in / ctx->both.downsample; + ctx->left.len_in = ctx->both.len_out; + ctx->right.len_in = ctx->both.len_out; + ctx->left.len_out = ctx->left.len_in / ctx->left.downsample; + ctx->right.len_out = ctx->right.len_in / ctx->right.downsample; + ctx->left_demod.buf_len = ctx->left.len_out; + ctx->left_demod.result_len = ctx->left_demod.buf_len / 2; + ctx->right_demod.buf_len = ctx->left_demod.buf_len; + ctx->right_demod.result_len = ctx->left_demod.result_len; +// stereo.bl_len = (int)((long)(DEFAULT_BUF_LENGTH/2) * (long)output_rate / (long)dongle_rate); -> Doesn't work on Linux + ctx->stereo.bl_len = (int)((double)(DEFAULT_BUF_LENGTH/2) * (double)config->output_rate / (double)dongle_rate); + ctx->stereo.br_len = ctx->stereo.bl_len; + ctx->stereo.result_len = ctx->stereo.br_len * 2; + ctx->stereo.rate = config->output_rate; + + if (!config->dev_given) { + config->dev_index = verbose_device_search("0"); + } + + if (config->dev_index < 0) { + exit(1); + } + + downsample_init(&ctx->both); + downsample_init(&ctx->left); + downsample_init(&ctx->right); + demod_init(&ctx->left_demod); + demod_init(&ctx->right_demod); + stereo_init(&ctx->stereo); + + int r = rtlsdr_open(&ctx->dev, (uint32_t)config->dev_index); + if (r < 0) { + fprintf(stderr, "Failed to open rtlsdr device #%d.\n", config->dev_index); + exit(1); + } + + if(!config->use_internal_aisdecoder){ + if (strcmp(config->filename, "-") == 0) { /* Write samples to stdout */ + ctx->file = stdout; + #ifdef WIN32 + setmode(fileno(stdout), O_BINARY); // Binary mode, avoid text mode + #endif + setvbuf(stdout, NULL, _IONBF, 0); + } else { + ctx->file = fopen(config->filename, "wb"); + if (!ctx->file) { + fprintf(stderr, "Failed to open %s\n", config->filename); + exit(1); + } + } + } + else{ // Internal AIS decoder + int ret=init_ais_decoder(config->host,config->port,config->show_levels,config->debug_nmea,ctx->stereo.bl_len,config->seconds_for_decoder_stats, config->use_tcp_listener, config->tcp_keep_ais_time, config->add_sample_num); + if(ret != 0){ + fprintf(stderr,"Error initializing built-in AIS decoder\n"); + rtlsdr_cancel_async(ctx->dev); + rtlsdr_close(ctx->dev); + exit(1); + } + } + ctx->use_internal_aisdecoder = config->use_internal_aisdecoder; + + /* Set the tuner gain */ + if (config->gain == AUTO_GAIN) { + verbose_auto_gain(ctx->dev); + } else { + config->gain = nearest_gain(ctx->dev, config->gain); + verbose_gain_set(ctx->dev, config->gain); + } + if(config->rtl_agc){ + int r = rtlsdr_set_agc_mode(ctx->dev, 1); + if(r<0) { + fprintf(stderr,"Error seting RTL AGC mode ON"); + exit(1); + } + else { + fprintf(stderr,"RTL AGC mode ON\n"); + } + } + if (!config->custom_ppm) { + verbose_ppm_eeprom(ctx->dev, &config->ppm_error); + } + + verbose_ppm_set(ctx->dev, config->ppm_error); + + /* Set the tuner frequency */ + verbose_set_frequency(ctx->dev, dongle_freq); + + /* Set the sample rate */ + verbose_set_sample_rate(ctx->dev, dongle_rate); + + /* Reset endpoint before we start reading from it (mandatory) */ + verbose_reset_buffer(ctx->dev); + + pthread_cond_init(&ctx->ready, NULL); + pthread_mutex_init(&ctx->ready_m, NULL); + + /* create two threads */ + pthread_create(&ctx->demod_thread, NULL, demod_thread_fn, ctx); + pthread_create(&ctx->rtlsdr_thread, NULL, rtlsdr_thread_fn, ctx); + + return ctx; +} + +int rtl_ais_isactive(struct rtl_ais_context *ctx) +{ + return ctx->active; +} + +const char *rtl_ais_next_message(struct rtl_ais_context *ctx) +{ + ctx = ctx; //unused for now + return aisdecoder_next_message(); +} + +void rtl_ais_cleanup(struct rtl_ais_context *ctx) +{ + rtlsdr_cancel_async(ctx->dev); + ctx->active = 0; + + pthread_detach(ctx->demod_thread); + pthread_detach(ctx->rtlsdr_thread); + + if (ctx->file != stdout) { + if(ctx->file) + fclose(ctx->file); + } + + rtlsdr_cancel_async(ctx->dev); + safe_cond_signal(&ctx->ready, &ctx->ready_m); + pthread_cond_destroy(&ctx->ready); + pthread_mutex_destroy(&ctx->ready_m); + + rtlsdr_close(ctx->dev); + + free(ctx); +} + + +// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/rtl_ais.h b/rtl_ais.h new file mode 100644 index 0000000..bec41ce --- /dev/null +++ b/rtl_ais.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * 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 2 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 . + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtl_ais_config +{ + int gain, dev_index, dev_given, ppm_error, rtl_agc, custom_ppm; + int left_freq, right_freq, sample_rate, output_rate, dongle_freq; + int dongle_rate, delta, edge; + + int oversample, dc_filter, use_internal_aisdecoder; + int seconds_for_decoder_stats; + int use_tcp_listener, tcp_keep_ais_time; + /* Aisdecoder */ + int show_levels, debug_nmea; + char *port, *host, *filename; + + int add_sample_num; +}; + +struct rtl_ais_context; + +void rtl_ais_default_config(struct rtl_ais_config *config); +struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config); +int rtl_ais_isactive(struct rtl_ais_context *ctx); +const char *rtl_ais_next_message(struct rtl_ais_context *ctx); +void rtl_ais_cleanup(struct rtl_ais_context *ctx); + +#ifdef __cplusplus +} +#endif diff --git a/tcp_listener/Makefile b/tcp_listener/Makefile new file mode 100644 index 0000000..61ea5c9 --- /dev/null +++ b/tcp_listener/Makefile @@ -0,0 +1,8 @@ +libtcp_listener.a: libtcp_listener.o + ar rcs $@ $^ + +libtcp_listener.o: tcp_listener.c + gcc -c -o $@ $< + +clean: + rm -f *.o *.a diff --git a/tcp_listener/README b/tcp_listener/README new file mode 100644 index 0000000..d992d18 --- /dev/null +++ b/tcp_listener/README @@ -0,0 +1,20 @@ +Use tcp listener service insted of udp. +These files are compiles into a library and a tcp server has the advantage that more clients can access AIS data from the same server. +The tcp_listener is tested under raspbian jessie on a raspberry pi with 5 concurrent connections. +Tested on OpenCPN. +Rtl-ais runs excactly as before using udp if the -T option is not supplyed. + +New options: +Use: rtl_ais [options] [outputfile] +... + [-T use TCP communication ( -h is ignored) + [-t time to keep ais messages in sec, using tcp listener (default: 15) +... + + +TODO: Fix to run under OSX and Windows. (I'll do the windows part if others find it usefull) + Makefile must be included in rtl-ais Makefile + Merge of rtl-ais.c, aisdecoder.c and aisdecoder.h if it is found usefull. + Daemonize rtl-ais. (I'll do it if others find it usefull) + + diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c new file mode 100644 index 0000000..e1fe652 --- /dev/null +++ b/tcp_listener/tcp_listener.c @@ -0,0 +1,466 @@ +// ------------------------------------------------------------ +// tcp_listener.c +// Written by Peter Schultz, hp@hpes.dk +// ------------------------------------------------------------ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (__WIN32__) + #include + #include +#else + #include + #include + #include + #include +#endif + +typedef struct t_sockIo { + int sock; + pthread_t thread_t; + int sesion_active; + struct sockaddr_in cli_addr; + char from_ip[20]; + struct t_sockIo *next; + +} TCP_SOCK, *P_TCP_SOCK; + +static int sockfd; +static int _debug_nmea = 0; +static int _debug = 0; +static int _tcp_keep_ais_time = 15; +static int portno; +pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;; + +// Linked list vars. +P_TCP_SOCK head = (P_TCP_SOCK) NULL; +P_TCP_SOCK end = (P_TCP_SOCK) NULL; + +pthread_t tcp_listener_thread; + +typedef struct t_ais_mess { + char message[100]; // max on nmea message is 83 char's + char *plmess; + int length; + struct timeval timestamp; + struct t_ais_mess *next; + +} AIS_MESS, *P_AIS_MESS; + +// Linked list ais messages. +P_AIS_MESS ais_head = (P_AIS_MESS) NULL; +P_AIS_MESS ais_end = (P_AIS_MESS) NULL; + +pthread_mutex_t ais_lock=PTHREAD_MUTEX_INITIALIZER;; + +// Local Prototypes +P_TCP_SOCK init_node(); +void add_node(P_TCP_SOCK new_node); +void delete_node(P_TCP_SOCK p); +int accept_c(P_TCP_SOCK p_tcp_sock); +int error_category(int rc); +static void *tcp_listener_fn(void *arg); +void *handle_remote_close(void *arg); +void delete_ais_node(P_AIS_MESS p); +void remove_old_ais_messages( ); + +#include "tcp_listener.h" + +int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) { + + _debug_nmea = debug_nmea; + _tcp_keep_ais_time = tcp_keep_ais_time; + struct sockaddr_in serv_addr; +#if defined (__WIN32__) + WSADATA wsaData; + WORD wVersionRequested; + wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); +#endif + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "Failed to create socket! error %d\n", errno); + return 0; + } + memset((char *) &serv_addr, 0, sizeof(serv_addr)); + portno = atoi(portnumber); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(portno); + if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + fprintf(stderr, "Failed to bind socket! error %d\n", errno); + return 0; + } + + if (listen(sockfd, MAX_TCP_CONNECTIONS) < 0) { + fprintf(stderr, "listen failed with error %d\n", errno); + return 0; + } + + pthread_create(&tcp_listener_thread, NULL, tcp_listener_fn, (void *) NULL); + + return 1; +} + +void closeTcpSocket() { + + // wait for socket shutdown complete + shutdown( sockfd,2); +#if defined (__WIN32__) + Sleep(3000); + closesocket(sockfd); +#else + sleep(3); + close(sockfd); +#endif +} + +// ------------------------------------------------------------ +// The main listener loop thread +// ------------------------------------------------------------ +static void *tcp_listener_fn(void *arg) { + int rc; + arg=arg; // not used, avoid compiling warnings + P_TCP_SOCK t; + + fprintf(stderr, "Tcp listen port %d\nAis message timeout with %d\n", portno, _tcp_keep_ais_time); + + while (1) { + + t = init_node(); + + rc = accept_c(t); + + if ( rc == -1) + break; + + if (rc == -2) { +#if defined (__WIN32__) + closesocket(t->sock); +#else + close(t->sock); +#endif + free(t); + continue; + } + add_node(t); + pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); + + } + shutdown( sockfd,2); +#if defined (__WIN32__) + closesocket(t->sock); +#else + close(t->sock); +#endif + return 0; + + +} + +// ------------------------------------------------------------ +// thread func for hanling client close +// ------------------------------------------------------------ +void *handle_remote_close(void *arg) { + unsigned char buff[100]; + int rc; + P_TCP_SOCK t = (P_TCP_SOCK) arg; + P_AIS_MESS ais_temp; + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + // get rid of old messages before send + remove_old_ais_messages(); + + // send saved ais_messages to new socket + ais_temp = ais_head; + while (ais_temp != NULL) { + if (ais_temp->plmess != NULL) + rc = send(t->sock, ais_temp->plmess, ais_temp->length, 0); + else + rc = send(t->sock, ais_temp->message, ais_temp->length, 0); + if( _debug) + fprintf( stdout, "%ld: Send to %s, <%.*s>, rc=%d\n",ais_temp->timestamp.tv_sec, t->from_ip, ais_temp->length, ais_temp->message, rc); + ais_temp = ais_temp->next; + } + + while(1){ + + rc = recv(t->sock, (char *)buff, 99, 0); + if( rc < 0) { + + // check timeout + if (errno == EAGAIN) + continue; + if( _debug) + fprintf( stdout, "Some socket error happend %d\n", errno); + break; + } + else if( rc == 0) { + if( _debug) + fprintf( stdout, "client gracefully closed the socket\n"); + break; + } + else { + if( _debug) + fprintf( stdout, "Something receiced from client <%.*s>\n", rc, buff ); + break; + } + } + shutdown(t->sock, 2); +#if defined (__WIN32__) + closesocket(t->sock); +#else + close(t->sock); +#endif + delete_node(t); + return 0; +} + + + +// ------------------------------------------------------------ +// Accept call +// ------------------------------------------------------------ +int accept_c(P_TCP_SOCK p_tcp_sock) { + + int optval = 1; // Keep alive + socklen_t optlen = sizeof(optval); + socklen_t clilen = sizeof(p_tcp_sock->cli_addr); + + /* wait for connection on local port.*/ + if ((p_tcp_sock->sock = accept(sockfd, (struct sockaddr*) &p_tcp_sock->cli_addr, &clilen)) < 0) { + fprintf(stderr, "Failed to accept socket!, error = %d\n", errno); + if( errno == 22) return -1; + return error_category(errno); + } + + if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE,(char *) &optval, optlen) < 0) { + fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); + return error_category(errno); + } + + sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); + if (_debug) { + fprintf(stdout, "connect from %s\n", p_tcp_sock->from_ip); + } + + p_tcp_sock->sesion_active = 1; + + return 0; +} + +// ------------------------------------------------------------ +// Remove messages older than timeout +// ------------------------------------------------------------ +void remove_old_ais_messages( ) { + struct timeval now; + P_AIS_MESS temp_1; + P_AIS_MESS temp; + gettimeofday(&now, NULL); + + temp = ais_head; + + while (temp != NULL) { + if ((int) (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { + if( _debug) + fprintf(stdout, "remove mess <%.*s>, timeout %ld\n", temp->length, temp->message, (long) (now.tv_sec - temp->timestamp.tv_sec)); + temp_1 = temp->next; + pthread_mutex_lock(&ais_lock); + delete_ais_node(temp); + pthread_mutex_unlock(&ais_lock); + temp = temp_1; + } else { + temp = temp->next; + } + } +} + +// ------------------------------------------------------------ +// send ais message to all clients +// ------------------------------------------------------------ +int add_nmea_ais_message(const char * mess, unsigned int length) { + + P_AIS_MESS new_node; + + // remove eventually old messages + remove_old_ais_messages(); + + pthread_mutex_lock(&ais_lock); + + // allocate an add the new message + new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); + if (length>=sizeof(new_node->message)) { + new_node->plmess = malloc(length); + if(new_node->plmess == NULL) { + free(new_node); + return -1; + } + strncpy(new_node->plmess, mess, length); + new_node->message[0] = 0; // Just in case + } else { + new_node->plmess = NULL; + strncpy(new_node->message, mess, length); + } + new_node->length = length; + gettimeofday(&new_node->timestamp, NULL); + + + if (ais_head == NULL) { + ais_head = new_node; + ais_end = new_node; + } + ais_end->next = new_node; + new_node->next = NULL; + ais_end = new_node; + + pthread_mutex_unlock(&ais_lock); + + return 0; +} + +// ------------------------------------------------------------ +// deletes the specified node pointed to by 'p' from the list +// ------------------------------------------------------------ +void delete_ais_node(P_AIS_MESS p) { + P_AIS_MESS temp; + P_AIS_MESS prev; + + temp = p; + prev = ais_head; + + if (temp == prev) { + ais_head = ais_head->next; + if (ais_end == temp) + ais_end = ais_end->next; + } else { + while (prev->next != temp) { + prev = prev->next; + } + prev->next = temp->next; + if (ais_end == temp) + ais_end = prev; + } + if (p->plmess != NULL) + free(p->plmess); + free(p); +} + +// ------------------------------------------------------------ +// initnode : Allocates a theads data structure +// ------------------------------------------------------------ +P_TCP_SOCK init_node() { + P_TCP_SOCK ptr; + + ptr = (P_TCP_SOCK) malloc(sizeof(TCP_SOCK)); + + memset(ptr, 0, sizeof(TCP_SOCK)); + + if (ptr == NULL) + return (P_TCP_SOCK) NULL; + else { + return ptr; + } +} + +// ------------------------------------------------------------ +// adding to end of list. +// ------------------------------------------------------------ +void add_node(P_TCP_SOCK new_node) { + pthread_mutex_lock(&lock); + if (head == NULL) { + head = new_node; + end = new_node; + } + end->next = new_node; + new_node->next = NULL; + end = new_node; + pthread_mutex_unlock(&lock); +} + +// ------------------------------------------------------------ +// deletes the specified node pointed to by 'p' from the list +// ------------------------------------------------------------ +void delete_node(P_TCP_SOCK p) { + P_TCP_SOCK temp; + P_TCP_SOCK prev; + + pthread_mutex_lock(&lock); + temp = p; + prev = head; + + if (temp == prev) { + head = head->next; + if (end == temp) + end = end->next; + } else { + while (prev->next != temp) { + prev = prev->next; + } + prev->next = temp->next; + if (end == temp) + end = prev; + } + free(p); + pthread_mutex_unlock(&lock); + +} + +// ------------------------------------------------------------------ +// Return error category. Some errors we can live with, some we can't +// ------------------------------------------------------------------ +int error_category(int rc) { +#if defined (__WIN32__) + rc=rc; // Not used, avoid compiling warnings + return -1; // Just work as sis +#else + switch (rc) { + // Fatal errors + case EINVAL: // The listen function was not invoked prior to accept. + case ENOTSOCK: // The descriptor is not a socket. + case EOPNOTSUPP: // The referenced socket is not a type that supports connection-oriented service. + case EPROTONOSUPPORT: // The specified protocol is not supported. + case EPROTOTYPE: + case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. + case EADDRINUSE: // The specified address is already in use. + if( _debug) + fprintf( stderr, "Socket fatal error: %d\n", rc); + return -1; + + // Retry errors + case ENETDOWN: // The network subsystem has failed. + case EINTR: // The (blocking) call was canceled through. + case EINPROGRESS: // A blocking call is in progress, or the service provider is still processing a callback function. + case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. + case ENOBUFS: // No buffer space is available. + case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. + case EALREADY: // A nonblocking connect call is in progress on the specified socket. + case EADDRNOTAVAIL: // The specified address is not available from the local machine. + case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. + case ECONNREFUSED: // The attempt to connect was forcefully rejected. + case EISCONN: // The socket is already connected (connection-oriented sockets only). + case ENETUNREACH: // The network cannot be reached from this host at this time. + case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. + case EACCES: // Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. + case ECONNRESET: + if( _debug) + fprintf( stderr, "Socket retry error: %d\n", rc); + return -2; + + default: + // Fatal error + if( _debug) + fprintf( stderr, "Socket unknown error: %d\n", rc); + return -1; + + } +#endif +} + diff --git a/tcp_listener/tcp_listener.h b/tcp_listener/tcp_listener.h new file mode 100644 index 0000000..1695d9b --- /dev/null +++ b/tcp_listener/tcp_listener.h @@ -0,0 +1,16 @@ +// ------------------------------------------------------- +// tcp_listener.h +// Written by Peter Schultz, hp@hpes.dk +// ------------------------------------------------------- +#ifndef __TCP_LISTENER_H_ +#define __TCP_LISTENER_H_ + +#define MAX_TCP_CONNECTIONS 100 + +// Prototypes +int initTcpSocket( const char *portnumber, int debug_nmea, int tcp_keep_ais_time); +int add_nmea_ais_message(const char * mess, unsigned int length); +void closeTcpSocket(); + +#endif +