├── pager ├── pager.c ├── pager_priv.h ├── pager.h ├── CMakeLists.txt ├── test │ ├── CMakeLists.txt │ ├── test_pager_flex.c │ ├── test_mueller_muller.c │ └── test_pager_pocsag.c ├── bch_code.h ├── pager_pocsag.h ├── mueller_muller.h ├── mueller_muller.c ├── pager_flex.h └── pager_pocsag_priv.h ├── multifm ├── demod_base.h ├── fast_atan2f.h ├── multifm.h ├── file_if.h ├── uhd_if.h ├── uhd_if_priv.h ├── costas_demod.h ├── rtl_sdr_if.h ├── airspy_if.h ├── file_if_priv.h ├── fm_demod.h ├── CMakeLists.txt ├── demod.h ├── fm_demod.c ├── receiver.h ├── costas_demod.c ├── multifm.c ├── fast_atan2f.c ├── airspy_if.c ├── file_if.c └── receiver.c ├── .gitignore ├── filter ├── filter_priv.h ├── CMakeLists.txt ├── test │ ├── CMakeLists.txt │ ├── test_direct_fir.c │ └── test_polyphase_fir.c ├── utils.h ├── filter.h ├── polyphase_fir.h ├── sample_buf.c ├── complex.h ├── polyphase_fir_priv.h ├── sample_buf.h ├── dc_blocker.h ├── utils.c ├── direct_fir.h └── polyphase_fir.c ├── ais ├── CMakeLists.txt ├── test │ ├── CMakeLists.txt │ └── test_ais_demod.c ├── ais_msg_format.h ├── ais_demod.h ├── ais_decode.h ├── ais_demod_priv.h ├── ais_demod.c └── ais_decode.c ├── etc ├── multifm_airspy.json ├── multifm_1ch.json ├── multifm_file.json ├── pocsag_airspy.json ├── multifm_usrp.json ├── pocsag_rtlsdr.json ├── multifm.json ├── flex_25khz_lpf.json ├── pocsag_narrow.json ├── pocsag_1200khz_fs.json └── flex_25khz_lpf_3mhz.json ├── resampler ├── CMakeLists.txt └── resampler.c ├── decoder └── CMakeLists.txt ├── README.md ├── scripts └── design_interpolation_filter.py └── CMakeLists.txt /pager/pager.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | -------------------------------------------------------------------------------- /multifm/demod_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct demod_base { 4 | 5 | }; 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | *.o 3 | build 4 | tags 5 | .waf* 6 | .lock* 7 | *.pyc 8 | *.pyo 9 | -------------------------------------------------------------------------------- /multifm/fast_atan2f.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | float fast_atan2f(float y, float x); 4 | 5 | -------------------------------------------------------------------------------- /multifm/multifm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MFM_MSG(sev, sys, msg, ...) MESSAGE("MULTIFM", sev, sys, msg, ##__VA_ARGS__) 6 | -------------------------------------------------------------------------------- /filter/filter_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define FIL_MSG(sev, sys, msg, ...) MESSAGE("FILTER", sev, sys, msg, ##__VA_ARGS__) 6 | 7 | -------------------------------------------------------------------------------- /multifm/file_if.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct receiver; 6 | struct config; 7 | 8 | aresult_t file_worker_thread_new(struct receiver **pthr, struct config *cfg); 9 | -------------------------------------------------------------------------------- /pager/pager_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * A pagerlib debug message 7 | */ 8 | #define PAG_MSG(sev, sys, msg, ...) MESSAGE("PAGER", sev, sys, msg, ##__VA_ARGS__) 9 | 10 | -------------------------------------------------------------------------------- /ais/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ais 2 | ais_decode.c 3 | ais_demod.c) 4 | 5 | target_include_directories(ais PUBLIC 6 | "${TSL_SDR_BASE_DIR}" 7 | "${TSL_INCLUDE_DIRS}") 8 | 9 | add_subdirectory(test) 10 | 11 | -------------------------------------------------------------------------------- /pager/pager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** \file pager.h Pagerlib Primary Interface 4 | * This interface is used by any pagerlib clients. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | -------------------------------------------------------------------------------- /multifm/uhd_if.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct receiver; 6 | struct config; 7 | 8 | 9 | /** 10 | * Create a new UHD receiver thread 11 | */ 12 | aresult_t uhd_worker_thread_new(struct receiver **pthr, struct config *cfg); 13 | 14 | -------------------------------------------------------------------------------- /pager/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(pager STATIC 2 | bch_code.c 3 | pager.c 4 | pager_flex.c 5 | pager_pocsag.c) 6 | 7 | target_include_directories(pager PUBLIC 8 | "${TSL_SDR_BASE_DIR}" 9 | "${TSL_INCLUDE_DIRS}") 10 | 11 | add_subdirectory(test) 12 | 13 | -------------------------------------------------------------------------------- /filter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(filter STATIC 2 | direct_fir.c 3 | polyphase_fir.c 4 | sample_buf.c 5 | utils.c) 6 | 7 | target_include_directories(filter PUBLIC 8 | "${TSL_SDR_BASE_DIR}" 9 | "${TSL_INCLUDE_DIRS}") 10 | 11 | add_subdirectory(test) 12 | 13 | -------------------------------------------------------------------------------- /ais/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_ais 2 | test_ais_demod.c) 3 | 4 | target_link_libraries(test_ais 5 | ais 6 | tsltestframework 7 | tslconfig 8 | tslapp 9 | tsl 10 | jansson) 11 | 12 | target_include_directories(test_ais PRIVATE "${TSL_SDR_BASE_DIR}") 13 | 14 | -------------------------------------------------------------------------------- /ais/ais_msg_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define AIS_MESSAGE_POSITION_REPORT_SOTDMA 1 4 | #define AIS_MESSAGE_POSITION_REPORT_SOTDMA2 2 5 | #define AIS_MESSAGE_POSITION_REPORT_ITDMA 3 6 | #define AIS_MESSAGE_BASE_STATION_REPORT 4 7 | #define AIS_MESSAGE_SHIP_STATIC_INFO 5 8 | 9 | -------------------------------------------------------------------------------- /filter/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_filter 2 | test_direct_fir.c 3 | test_polyphase_fir.c) 4 | 5 | target_link_libraries(test_filter 6 | filter 7 | tsltestframework 8 | tslconfig 9 | tslapp 10 | tsl 11 | jansson) 12 | target_include_directories(test_filter PRIVATE "${TSL_SDR_BASE_DIR}") 13 | 14 | -------------------------------------------------------------------------------- /filter/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct sample_buf; 8 | 9 | aresult_t dot_product_sample_buffers_real(struct sample_buf *sb_active, 10 | struct sample_buf *sb_next, size_t buf_start_offset, 11 | int16_t *coeffs, size_t nr_coeffs, int16_t *psample); 12 | 13 | -------------------------------------------------------------------------------- /pager/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_pager 2 | test_pager_flex.c 3 | test_pager_pocsag.c) 4 | 5 | target_link_libraries(test_pager 6 | pager 7 | tsltestframework 8 | tslconfig 9 | tslapp 10 | tsl 11 | jansson) 12 | 13 | target_include_directories(test_pager PRIVATE "${TSL_SDR_BASE_DIR}") 14 | 15 | -------------------------------------------------------------------------------- /pager/bch_code.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct bch_code; 6 | 7 | aresult_t bch_code_new(struct bch_code **pcode, const int p[], int m, int n, int k, int t); 8 | void bch_code_delete(struct bch_code **bch_code_data); 9 | void bch_code_encode(struct bch_code *bch_code_data, int data[]); 10 | int bch_code_decode(struct bch_code *bch_code_data, uint32_t *precd); 11 | 12 | -------------------------------------------------------------------------------- /filter/filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * The filter library. Contains various types of filters, including: 5 | * 6 | * - Direct FIR (includes an optional phase derotator) 7 | * - Polyphase FIR (supports rational resampling) 8 | * 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | typedef int16_t sample_t; 15 | 16 | #define Q_15_SHIFT 14 17 | 18 | -------------------------------------------------------------------------------- /etc/multifm_airspy.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "airspy", 4 | "lnaGain" : 13, 5 | "vgaGain" : 8, 6 | "mixerGain" : 8 7 | }, 8 | "sampleRateHz" : 3000000, 9 | "centerFreqHz" : 930500000, 10 | "nrSampBufs" : 128, 11 | "decimationFactor" : 120, 12 | "channels" : [ 13 | { 14 | "outFifo" : "/home/pvachon/ch0.out", 15 | "chanCenterFreq" : 929612500 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /etc/multifm_1ch.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "rtlsdr", 4 | "deviceIndex" : 0, 5 | "dBGainLNA" : 16.6, 6 | "dbGainIF" : 14.0 7 | }, 8 | "sampleRateHz" : 1000000, 9 | "centerFreqHz" : 929500000, 10 | "nrSampBufs" : 128, 11 | "decimationFactor" : 40, 12 | "channels" : [ 13 | { 14 | "outFifo" : "/home/pvachon/ch0.out", 15 | "chanCenterFreq" : 929612500 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /multifm/uhd_if_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #define UHD_MSG(sev, sys, msg, ...) MESSAGE("UHD", sev, sys, msg, ##__VA_ARGS__) 10 | 11 | struct uhd_worker_thread { 12 | struct receiver rx; 13 | uhd_usrp_handle dev_hdl; 14 | uhd_rx_streamer_handle rx_stream; 15 | size_t *channels; 16 | size_t nr_channels; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /resampler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(resampler 2 | resampler.c) 3 | 4 | target_include_directories(resampler PUBLIC 5 | "${TSL_SDR_BASE_DIR}" 6 | "${TSL_INCLUDE_DIRS}") 7 | 8 | install(TARGETS resampler 9 | DESTINATION ${INSTALL_BIN_DIR}) 10 | 11 | target_link_libraries(resampler 12 | filter 13 | tsltestframework 14 | tslconfig 15 | tslapp 16 | tsl 17 | pthread 18 | m 19 | jansson) 20 | 21 | -------------------------------------------------------------------------------- /decoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(decoder 2 | decoder.c) 3 | 4 | target_include_directories(decoder PUBLIC 5 | "${TSL_SDR_BASE_DIR}" 6 | "${TSL_INCLUDE_DIRS}") 7 | 8 | install(TARGETS decoder 9 | DESTINATION ${INSTALL_BIN_DIR}) 10 | 11 | target_link_libraries(decoder 12 | pager 13 | ais 14 | filter 15 | tsltestframework 16 | tslconfig 17 | tslapp 18 | tsl 19 | pthread 20 | m 21 | jansson) 22 | 23 | 24 | -------------------------------------------------------------------------------- /etc/multifm_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "file", 4 | "filename" : "/home/pvachon/Downloads/goes13-2/2010-09-24-goes-13_2-8bit-01.dat", 5 | "fileFormat" : "cs8" 6 | }, 7 | "sampleRateHz" : 8738133, 8 | "centerFreqHz" : 1692000000, 9 | "nrSampBufs" : 128, 10 | "decimationFactor" : 1, 11 | "channels" : [ 12 | { 13 | "outFifo" : "/home/pvachon/ch0.out", 14 | "chanCenterFreq" : 1691000000 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /etc/pocsag_airspy.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "airspy", 4 | "lnaGain" : 8, 5 | "vgaGain" : 5, 6 | "mixerGain" : 5, 7 | "enableBiasTee" : true 8 | }, 9 | "sampleRateHz" : 2500000, 10 | "centerFreqHz" : 152500000, 11 | "nrSampBufs" : 128, 12 | "decimationFactor" : 100, 13 | "channels" : [ 14 | { 15 | "outFifo" : "/home/pvachon/ch0.out", 16 | "dBGain" : 2.0, 17 | "chanCenterFreq" : 152180000 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /multifm/costas_demod.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct demod_base; 6 | 7 | aresult_t multifm_costas_demod_init(struct demod_base **pdemod, float f_shift, float alpha, float beta, int16_t e_max); 8 | aresult_t multifm_costas_demod_process(struct demod_base *demod, int16_t *in_samples, size_t nr_in_samples, 9 | int16_t *out_samples, size_t *pnr_out_samples, size_t *pnr_out_bytes); 10 | aresult_t multifm_costas_demod_cleanup(struct demod_base **pdemod); 11 | 12 | -------------------------------------------------------------------------------- /filter/test/test_direct_fir.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | static 7 | aresult_t test_direct_fir_setup(void) 8 | { 9 | return A_OK; 10 | } 11 | 12 | static 13 | aresult_t test_direct_fir_cleanup(void) 14 | { 15 | return A_OK; 16 | } 17 | 18 | 19 | TEST_DECLARE_UNIT(test_smoke, flex) 20 | { 21 | return A_OK; 22 | } 23 | 24 | TEST_DECLARE_SUITE(flex, test_direct_fir_cleanup, test_direct_fir_setup, NULL, NULL); 25 | 26 | -------------------------------------------------------------------------------- /etc/multifm_usrp.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "usrp", 4 | "deviceId" : "serial=30E6230", 5 | "channel" : 0, 6 | "antenna" : "TX/RX", 7 | "gain" : [ 8 | { "name" : "PGA", "dBValue" : 32.2 } 9 | ] 10 | }, 11 | "sampleRateHz" : 3000000, 12 | "centerFreqHz" : 929500000, 13 | "nrSampBufs" : 128, 14 | "decimationFactor" : 120, 15 | "channels" : [ 16 | { 17 | "outFifo" : "/home/pvachon/ch0.out", 18 | "chanCenterFreq" : 929612500 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /etc/pocsag_rtlsdr.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "rtlsdr", 4 | "deviceIndex" : 0, 5 | "dBGainLNA" : 37.2 6 | }, 7 | "sampleRateHz" : 1200000, 8 | "centerFreqHz" : 152500000, 9 | "nrSampBufs" : 128, 10 | "decimationFactor" : 25, 11 | "channels" : [ 12 | { 13 | "outFifo" : "/home/pvachon/ch0.out", 14 | "dBGain" : 4.0, 15 | "chanCenterFreq" : 152180000 16 | }, 17 | { 18 | "outFifo" : "/home/pvachon/ch1.out", 19 | "dbGain" : 1.5, 20 | "chanCenterFreq" : 152008000 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /multifm/rtl_sdr_if.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct rtlsdr_dev; 6 | struct config; 7 | 8 | /** 9 | * State for the RTL-SDR reader thread 10 | */ 11 | struct rtl_sdr_thread { 12 | /** 13 | * The receiver context associated with this thread 14 | */ 15 | struct receiver rx; 16 | 17 | /** 18 | * The RTL SDR device we're capturing from 19 | */ 20 | struct rtlsdr_dev *dev; 21 | 22 | /** 23 | * File descriptor to dump raw samples to 24 | */ 25 | int dump_fd; 26 | }; 27 | 28 | /** 29 | * Create a new 30 | */ 31 | aresult_t rtl_sdr_worker_thread_new(struct receiver **pthr, struct config *cfg); 32 | 33 | -------------------------------------------------------------------------------- /multifm/airspy_if.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct airspy_device; 6 | struct config; 7 | 8 | /** 9 | * State for the RTL-SDR reader thread 10 | */ 11 | struct airspy_thread { 12 | /** 13 | * The receiver context associated with this thread 14 | */ 15 | struct receiver rx; 16 | 17 | /** 18 | * The Airspy device we're capturing from 19 | */ 20 | struct airspy_device *dev; 21 | 22 | /** 23 | * File descriptor to dump raw samples to 24 | */ 25 | int dump_fd; 26 | 27 | /** 28 | * The number of buffers we had to discard 29 | */ 30 | uint64_t dropped; 31 | }; 32 | 33 | /** 34 | * Create a new 35 | */ 36 | aresult_t airspy_worker_thread_new(struct receiver **pthr, struct config *cfg); 37 | 38 | -------------------------------------------------------------------------------- /filter/polyphase_fir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct sample_buf; 8 | struct polyphase_fir; 9 | 10 | aresult_t polyphase_fir_new(struct polyphase_fir **pfir, size_t nr_coeffs, const int16_t *fir_real_coeff, 11 | unsigned interpolate, unsigned decimate); 12 | aresult_t polyphase_fir_delete(struct polyphase_fir **pfir); 13 | aresult_t polyphase_fir_push_sample_buf(struct polyphase_fir *fir, struct sample_buf *buf); 14 | aresult_t polyphase_fir_process(struct polyphase_fir *fir, int16_t *out_buf, size_t nr_out_samples, 15 | size_t *nr_out_samples_generated); 16 | aresult_t polyphase_fir_can_process(struct polyphase_fir *fir, bool *pcan_process); 17 | aresult_t polyphase_fir_full(struct polyphase_fir *fir, bool *pfull); 18 | 19 | -------------------------------------------------------------------------------- /filter/test/test_polyphase_fir.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | static const 7 | int16_t test_polyphase_fir_coeffs[] = { 8 | 255, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 9 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 10 | }; 11 | 12 | static 13 | aresult_t test_polyphase_fir_setup(void) 14 | { 15 | return A_OK; 16 | } 17 | 18 | static 19 | aresult_t test_polyphase_fir_cleanup(void) 20 | { 21 | return A_OK; 22 | } 23 | 24 | 25 | TEST_DECLARE_UNIT(test_smoke, polyphase) 26 | { 27 | struct polyphase_fir *pfir = NULL; 28 | 29 | TEST_ASSERT_OK(polyphase_fir_new(&pfir, sizeof(test_polyphase_fir_coeffs)/sizeof(int16_t), 30 | test_polyphase_fir_coeffs, 3, 2)); 31 | TEST_ASSERT_OK(polyphase_fir_delete(&pfir)); 32 | return A_OK; 33 | } 34 | 35 | TEST_DECLARE_SUITE(polyphase, test_polyphase_fir_cleanup, test_polyphase_fir_setup, NULL, NULL); 36 | 37 | -------------------------------------------------------------------------------- /multifm/file_if_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct sample_buf; 8 | struct file_worker_thread; 9 | 10 | typedef aresult_t (*file_read_convert_call_func_t)(struct file_worker_thread *thr, struct sample_buf *sbuf); 11 | 12 | enum file_worker_sample_format { 13 | FILE_WORKER_SAMPLE_FORMAT_UNKNOWN = 0, 14 | FILE_WORKER_SAMPLE_FORMAT_S8, /* Signed 8-bit integer input */ 15 | FILE_WORKER_SAMPLE_FORMAT_U8, /* Unsigned 8-bit integer input */ 16 | FILE_WORKER_SAMPLE_FORMAT_S16, /* Signed 16-bit integer input */ 17 | }; 18 | 19 | struct file_worker_thread { 20 | struct receiver rcvr; 21 | 22 | int fd; 23 | 24 | long samples_per_sec; 25 | uint64_t time_per_buf_ns; 26 | enum file_worker_sample_format sample_format; 27 | 28 | file_read_convert_call_func_t read_call; 29 | void *bounce_buf; 30 | size_t bounce_buf_bytes; 31 | }; 32 | 33 | #define FL_MSG(sev, sys, msg, ...) MESSAGE("FILEIF", sev, sys, msg, ##__VA_ARGS__) 34 | 35 | -------------------------------------------------------------------------------- /etc/multifm.json: -------------------------------------------------------------------------------- 1 | { 2 | "device" : { 3 | "type" : "rtlsdr", 4 | "deviceIndex" : 0, 5 | "dBGainLNA" : 20.7 6 | }, 7 | "sampleRateHz" : 1000000, 8 | "centerFreqHz" : 929500000, 9 | "nrSampBufs" : 128, 10 | "decimationFactor" : 40, 11 | "channels" : [ 12 | { 13 | "outFifo" : "/home/pi/ch7.out", 14 | "chanCenterFreq" : 929838000 15 | }, 16 | { 17 | "outFifo" : "/home/pi/ch6.out", 18 | "chanCenterFreq" : 929538000 19 | }, 20 | { 21 | "outFifo" : "/home/pi/ch5.out", 22 | "chanCenterFreq" : 929388000 23 | }, 24 | { 25 | "outFifo" : "/home/pi/ch4.out", 26 | "chanCenterFreq" : 929938000 27 | }, 28 | { 29 | "outFifo" : "/home/pi/ch3.out", 30 | "chanCenterFreq" : 929362000 31 | }, 32 | { 33 | "outFifo" : "/home/pi/ch2.out", 34 | "chanCenterFreq" : 929662500 35 | }, 36 | { 37 | "outFifo" : "/home/pi/ch1.out", 38 | "chanCenterFreq" : 929638000 39 | }, 40 | { 41 | "outFifo" : "/home/pi/ch0.out", 42 | "chanCenterFreq" : 929612000 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /multifm/fm_demod.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct demod_base; 6 | 7 | /** 8 | * FM Demodulator 9 | * 10 | * This phase discriminator FM demodulator will convert an input complex FM signal to 11 | * a real-valued PCM stream of samples. The FM demodulator infrastructure takes complex 12 | * 16-bit integer pairs as inputs, and outputs a single PCM integer value. 13 | */ 14 | 15 | /** 16 | * Initialize a new FM demodulator 17 | * 18 | * \param pdemod The demodulator state, returned by reference. 19 | * 20 | * \return A_OK on success, an error code otherwise 21 | */ 22 | aresult_t multifm_fm_demod_init(struct demod_base **pdemod); 23 | 24 | /** 25 | * Given the demodulator state, process the specified sample buffers, and write the output samples 26 | * out to the real-valued PCM buffer. 27 | */ 28 | aresult_t multifm_fm_demod_process(struct demod_base *demod, int16_t *in_samples, size_t nr_in_samples, 29 | int16_t *out_samples, size_t *pnr_out_samples, size_t *pnr_out_bytes); 30 | 31 | /** 32 | * Cleanup the resources used by the FM demodulator 33 | */ 34 | aresult_t multifm_fm_demod_cleanup(struct demod_base **pdemod); 35 | 36 | -------------------------------------------------------------------------------- /pager/test/test_pager_flex.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | static 7 | aresult_t test_pager_flex_setup(void) 8 | { 9 | return A_OK; 10 | } 11 | 12 | static 13 | aresult_t test_pager_flex_cleanup(void) 14 | { 15 | return A_OK; 16 | } 17 | 18 | static 19 | aresult_t _test_flex_on_message_simple_cb( 20 | struct pager_flex *flex, 21 | uint16_t baud, 22 | uint8_t phase, 23 | uint8_t cycle_no, 24 | uint8_t frame_no, 25 | uint64_t cap_code, 26 | bool fragmented, 27 | bool maildrop, 28 | uint8_t seq_num, 29 | const char *message_bytes, 30 | size_t message_len) 31 | { 32 | return A_OK; 33 | } 34 | 35 | static 36 | aresult_t _test_flex_on_num_message_simple_cb( 37 | struct pager_flex *flex, 38 | uint16_t baud, 39 | uint8_t phase, 40 | uint8_t cycle_no, 41 | uint8_t frame_no, 42 | uint64_t cap_code, 43 | const char *message_bytes, 44 | size_t message_len) 45 | { 46 | return A_OK; 47 | } 48 | 49 | TEST_DECLARE_UNIT(test_smoke, flex) 50 | { 51 | struct pager_flex *flex = NULL; 52 | 53 | TEST_ASSERT_OK(pager_flex_new(&flex, 929612500ul, _test_flex_on_message_simple_cb, _test_flex_on_num_message_simple_cb, NULL)); 54 | TEST_ASSERT_OK(pager_flex_delete(&flex)); 55 | 56 | return A_OK; 57 | } 58 | 59 | TEST_DECLARE_SUITE(flex, test_pager_flex_cleanup, test_pager_flex_setup, NULL, NULL); 60 | 61 | -------------------------------------------------------------------------------- /filter/sample_buf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sample_buf.c - A sample buffer abstraction. 3 | * 4 | * Copyright (c)2017 Phil Vachon 5 | * 6 | * This file is a part of The Standard Library (TSL) 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | */ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | aresult_t sample_buf_decref(struct sample_buf *buf) 32 | { 33 | aresult_t ret = A_OK; 34 | 35 | TSL_ASSERT_ARG(NULL != buf); 36 | /* Decrement the reference count */ 37 | if (1 == atomic_fetch_sub(&buf->refcount, 1)) { 38 | TSL_BUG_ON(NULL == buf->release); 39 | TSL_BUG_IF_FAILED(buf->release(buf)); 40 | } 41 | 42 | return ret; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /multifm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(RF_INTERFACE_SOURCES ) 2 | set(RF_INTERFACE_DIRS ) 3 | set(RF_INTERFACE_LIBS ) 4 | 5 | if(RTLSDR_FOUND) 6 | list(APPEND RF_INTERFACE_SOURCES rtl_sdr_if.c) 7 | list(APPEND RF_INTERFACE_DIRS ${RTLSDR_INCLUDE_DIRS}) 8 | list(APPEND RF_INTERFACE_LIBS ${RTLSDR_LIBRARIES}) 9 | endif() 10 | 11 | if(UHD_FOUND) 12 | list(APPEND RF_INTERFACE_SOURCES uhd_if.c) 13 | list(APPEND RF_INTERFACE_DIRS ${UHD_INCLUDE_DIRS}) 14 | list(APPEND RF_INTERFACE_LIBS ${UHD_LIBRARIES}) 15 | endif() 16 | 17 | if (DESPAIRSPY_FOUND) 18 | list(APPEND RF_INTERFACE_SOURCES airspy_if.c) 19 | list(APPEND RF_INTERFACE_DIRS ${DESPAIRSPY_INCLUDE_DIRS}) 20 | list(APPEND RF_INTERFACE_LIBS ${DESPAIRSPY_LIBRARIES}) 21 | endif() 22 | 23 | add_executable(multifm 24 | costas_demod.c 25 | demod.c 26 | fast_atan2f.c 27 | file_if.c 28 | fm_demod.c 29 | multifm.c 30 | receiver.c 31 | ${RF_INTERFACE_SOURCES}) 32 | 33 | # Cumbersome, but add a DEFINE for the libraries found to ONLY the build command 34 | # line for multifm. 35 | if(RTLSDR_FOUND) 36 | target_compile_definitions(multifm PRIVATE -DHAVE_RTLSDR) 37 | endif() 38 | 39 | if(UHD_FOUND) 40 | target_compile_definitions(multifm PRIVATE -DHAVE_UHD) 41 | endif() 42 | 43 | if (DESPAIRSPY_FOUND) 44 | target_compile_definitions(multifm PRIVATE -DHAVE_DESPAIRSPY) 45 | endif() 46 | 47 | target_include_directories(multifm PUBLIC 48 | "${TSL_SDR_BASE_DIR}" 49 | "${TSL_INCLUDE_DIRS}" 50 | "${RF_INTERFACE_DIRS}") 51 | 52 | install(TARGETS multifm 53 | DESTINATION ${INSTALL_BIN_DIR}) 54 | 55 | target_link_libraries(multifm 56 | filter 57 | tsltestframework 58 | tslconfig 59 | tslapp 60 | tsl 61 | pthread 62 | m 63 | ${RF_INTERFACE_LIBS} 64 | jansson) 65 | 66 | -------------------------------------------------------------------------------- /ais/ais_demod.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct ais_demod; 8 | 9 | /** 10 | * Callback called whenever a packet has been received. 11 | * 12 | * \param demod The demodulator state 13 | * \param state The state passed to the demodulator on creation 14 | * \param packet The raw packet, packed as binary 15 | * \param packet_len The length of the raw packet, in bytes 16 | * \param fcs_valid Boolean value indicating if the FCS is valid or not 17 | */ 18 | typedef aresult_t (*ais_demod_on_message_callback_func_t)(struct ais_demod *demod, void *state, const uint8_t *packet, size_t packet_len, bool fcs_valid); 19 | 20 | /** 21 | * Create a new AIS demodulator. 22 | * 23 | * \param pdemod The new demodulator state, returned by reference 24 | * \param state State passed to the callback 25 | * \param cb The callback to execute after receiving and unpacking the AIS message (& checking FCS) 26 | * \param freq The frequency this is listening on. In Hz. 27 | * 28 | * \return A_OK on success, an error code otherwise. 29 | */ 30 | aresult_t ais_demod_new(struct ais_demod **pdemod, void *state, ais_demod_on_message_callback_func_t cb, uint32_t freq); 31 | 32 | /** 33 | * Delete a demodulator's state 34 | * 35 | * \param pdemod The demodulator state, passed by reference. Set to NULL on deletion. 36 | * 37 | * \return A_OK on success, an error code otherwise. 38 | */ 39 | aresult_t ais_demod_delete(struct ais_demod **pdemod); 40 | 41 | /** 42 | * Process a buffer of PCM samples, and decode any AIS packets found. 43 | * 44 | * \param demod The demodulator state 45 | * \param samples The buffer of PCM samples, 16-bit real-valued 46 | * \param nr_samples The number of samples 47 | */ 48 | aresult_t ais_demod_on_pcm(struct ais_demod *demod, const int16_t *samples, size_t nr_samples); 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Software Defined Radio Tools 2 | 3 | This is the repository for tools that use the TSL that are meant for software 4 | defined radio and signal processing. 5 | 6 | ## Getting the TSL 7 | 8 | Please have a look at [the TSL repository](https://github.com/pvachon/tsl). The easiest 9 | way to do this is to likely build the debian package. You do not need this package for 10 | running executables, only for building (everything is statically linked). 11 | 12 | # Building 13 | 14 | Please install CMake. Most repos have a package. As well, you'll likely want 15 | at least one of the following RF interface libraries: 16 | * `librtlsdr`for RTL-SDR (known as `librtlsdr0` in Debian variants) 17 | * `libuhd` for USRP (known as `libuhd003` in Debian variants) 18 | * `libdespairspy` for Airspy (find it [here](https://github.com/pvachon/despairspy)). 19 | 20 | Simply create a directory inside the project, e.g. `build`, change to that directory 21 | and instruct CMake to do its thing. Simply: 22 | ``` 23 | git clone https://github.com/pvachon/tsl-sdr 24 | mkdir tsl-sdr/build 25 | cd tsl-sdr/build 26 | cmake -DCMAKE_BUILD_TYPE=Release .. 27 | make 28 | sudo make install 29 | ``` 30 | 31 | You an optionally skip the `make` steps and invoke `cpack`. This will generate a Debian 32 | package for your convenience. 33 | 34 | # Getting Help 35 | 36 | Be sure to check the [project wiki](https://github.com/pvachon/tsl-sdr/wiki) for 37 | use cases, documentation and other details. 38 | 39 | If you think you've found a bug (hey, it happens), open a [Github issue](https://github.com/pvachon/tsl-sdr/issues) for the project. 40 | 41 | # License 42 | 43 | The TSL, MultiFM and Resampler (as well as libfilter, etc.) are provided under 44 | two licenses - the GPLv2 and the MIT/X license. You can pick whichever license 45 | works best for you. 46 | 47 | # Author 48 | 49 | Most of this code was written by Phil Vachon (phil@security-embedded.com). 50 | -------------------------------------------------------------------------------- /pager/pager_pocsag.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct pager_pocsag; 7 | 8 | typedef aresult_t (*pager_pocsag_on_numeric_msg_func_t)( 9 | struct pager_pocsag *pocsag, 10 | uint16_t baud_rate, 11 | uint32_t capcode, 12 | const char *data, 13 | size_t data_len, 14 | uint8_t function); 15 | 16 | typedef aresult_t (*pager_pocsag_on_alpha_msg_func_t)( 17 | struct pager_pocsag *pocsag, 18 | uint16_t baud_rate, 19 | uint32_t capcode, 20 | const char *data, 21 | size_t data_len, 22 | uint8_t function); 23 | 24 | /** 25 | * Create a new POCSAG decoder. 26 | * 27 | * \param ppocsag The new POCSAG pager decoder, returned by reference. 28 | * \param freq_hz The center frequency of this channel 29 | * \param on_numeric Function called when a numeric page has been successfully decoded. 30 | * \param on_alpha Function called when an alphanumeric page has been successfully decoded. 31 | * \param skip_bch_decode Skip BCH checks (not recommended) 32 | * 33 | * \return A_OK on success, an error code otherwise. 34 | */ 35 | aresult_t pager_pocsag_new(struct pager_pocsag **ppocsag, uint32_t freq_hz, pager_pocsag_on_numeric_msg_func_t on_numeric, 36 | pager_pocsag_on_alpha_msg_func_t on_alpha, bool skip_bch_decode); 37 | 38 | /** 39 | * Destroy a POCSAG decoder. 40 | * 41 | * \param ppocsag The decoder, passed by reference. Set to NULL on success. 42 | * 43 | * \return A_OK on success, an error code otherwise. 44 | */ 45 | aresult_t pager_pocsag_delete(struct pager_pocsag **ppocsag); 46 | 47 | /** 48 | * Process a block of PCM samples that have arrived, decoding any POCSAG messages contained within. 49 | * 50 | * \param pocsag The POCSAG decoder state. 51 | * \param pcm_samples the real-valued PCM samples, in Q.15 representation. 52 | * \param nr_samples The number of samples to process. 53 | * 54 | * \return A_OK on success, an error code otherwise. 55 | */ 56 | aresult_t pager_pocsag_on_pcm(struct pager_pocsag *pocsag, const int16_t *pcm_samples, size_t nr_samples); 57 | 58 | -------------------------------------------------------------------------------- /filter/complex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * complex.c - Numerical Helpers for Q.15 Fixed Point Arithmetic 3 | * 4 | * Copyright (c)2017 Phil Vachon 5 | * 6 | * This file is a part of The Standard Library (TSL) 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | /** 28 | * Round the given fixed point Q.30 value to a Q.15 value 29 | */ 30 | static inline 31 | int16_t round_q30_q15(int32_t a) 32 | { 33 | return ((a >> Q_15_SHIFT) + ((a >> (Q_15_SHIFT - 1)) & 1)); 34 | } 35 | 36 | /** 37 | * Calculate the product of 2 q15 values, and return as q30. This maintains maximum information 38 | * possible. 39 | */ 40 | static inline 41 | void cmul_q15_q30(int32_t a_re, int32_t a_im, int32_t b_re, int32_t b_im, 42 | int32_t *r_re, int32_t *r_im) 43 | { 44 | *r_re = a_re * b_re - a_im * b_im; 45 | *r_im = a_re * b_im + a_im * b_re; 46 | } 47 | 48 | /** 49 | * Calculate the product of 2 q15 values, and return as q15. 50 | */ 51 | static inline 52 | void cmul_q15_q15(int16_t a_re, int16_t a_im, int16_t b_re, int16_t b_im, 53 | int16_t *r_re, int16_t *r_im) 54 | { 55 | int32_t a_re_32 = a_re, 56 | a_im_32 = a_im, 57 | b_re_32 = b_re, 58 | b_im_32 = b_im; 59 | 60 | *r_re = round_q30_q15(a_re_32 * b_re_32 - a_im_32 * b_im_32); 61 | *r_im = round_q30_q15(a_re_32 * b_im_32 + a_im_32 * b_re_32); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /ais/ais_decode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct ais_decode; 6 | 7 | struct ais_position_report { 8 | uint32_t mmsi; 9 | uint32_t nav_stat; 10 | uint32_t position_acc; 11 | uint32_t course; 12 | uint32_t heading; 13 | uint32_t timestamp; 14 | 15 | float longitude; 16 | float latitude; 17 | 18 | int32_t rate_of_turn; 19 | float speed_over_ground; 20 | }; 21 | 22 | struct ais_base_station_report { 23 | uint32_t mmsi; 24 | uint32_t year; 25 | uint32_t month; 26 | uint32_t day; 27 | uint32_t hour; 28 | uint32_t minute; 29 | uint32_t second; 30 | 31 | float longitude; 32 | float latitude; 33 | 34 | uint32_t epfd_type; 35 | const char *epfd_name; 36 | }; 37 | 38 | struct ais_static_voyage_data { 39 | uint32_t mmsi; 40 | uint32_t version; 41 | uint32_t imo_number; 42 | uint32_t ship_type; 43 | uint32_t dim_to_bow; 44 | uint32_t dim_to_stern; 45 | uint32_t dim_to_port; 46 | uint32_t dim_to_starboard; 47 | uint32_t fix_type; 48 | const char *epfd_name; 49 | uint32_t eta_month; 50 | uint32_t eta_day; 51 | uint32_t eta_hour; 52 | uint32_t eta_minute; 53 | float draught; 54 | char callsign[8]; 55 | char ship_name[21]; 56 | char destination[21]; 57 | }; 58 | 59 | typedef aresult_t (*ais_decode_on_position_report_func_t)(struct ais_decode *decode, void *state, struct ais_position_report *rpt, const char *raw_msg); 60 | typedef aresult_t (*ais_decode_on_base_station_report_func_t)(struct ais_decode *decode, void *state, struct ais_base_station_report *bsr, const char *raw_msg); 61 | typedef aresult_t (*ais_decode_on_static_voyage_data_func_t)(struct ais_decode *decode, void *state, struct ais_static_voyage_data *svd, const char *raw_msg); 62 | 63 | aresult_t ais_decode_new(struct ais_decode **pdecode, uint32_t freq, ais_decode_on_position_report_func_t on_position_report, ais_decode_on_base_station_report_func_t on_base_station_report, ais_decode_on_static_voyage_data_func_t on_static_voyage_data); 64 | aresult_t ais_decode_delete(struct ais_decode **pdecode); 65 | aresult_t ais_decode_on_pcm(struct ais_decode *decode, const int16_t *samples, size_t nr_samples); 66 | 67 | -------------------------------------------------------------------------------- /scripts/design_interpolation_filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from gnuradio import filter 3 | 4 | import json 5 | import sys 6 | 7 | def design_filter(interpolation, decimation, fractional_bw): 8 | """ 9 | Given the interpolation rate, decimation rate and a fractional bandwidth, 10 | design a set of taps. 11 | Args: 12 | interpolation: interpolation factor (integer > 0) 13 | decimation: decimation factor (integer > 0) 14 | fractional_bw: fractional bandwidth in (0, 0.5) 0.4 works well. (float) 15 | Returns: 16 | : sequence of numbers 17 | """ 18 | 19 | if fractional_bw >= 0.5 or fractional_bw <= 0: 20 | raise ValueError('Invalid fractional bandwidth, must be in (0, 0.5)') 21 | 22 | if decimation < 1 or interpolation < 1: 23 | raise ValueError('Invalid interpolation or decimation rate. Must be a non-zero positive integer.') 24 | 25 | beta = 7.0 26 | halfband = 0.5 27 | rate = float(interpolation)/float(decimation) 28 | if(rate >= 1.0): 29 | trans_width = halfband - fractional_bw 30 | mid_transition_band = halfband - trans_width/2.0 31 | else: 32 | trans_width = rate*(halfband - fractional_bw) 33 | mid_transition_band = rate*halfband - trans_width/2.0 34 | 35 | taps = filter.firdes.low_pass(interpolation, # gain 36 | interpolation, # Fs 37 | mid_transition_band, # trans mid point 38 | trans_width, # transition width 39 | filter.firdes.WIN_KAISER, 40 | beta) # beta 41 | 42 | return taps 43 | 44 | def main(argv): 45 | if len(argv) < 3: 46 | print('Usage: {} [interpolation] [decimation] [fractional bandwidth]'.format(argv[0])) 47 | print(' Design a filter for use with a rational resampler') 48 | sys.exit(-1) 49 | 50 | interpolation = int(argv[1]) 51 | decimation = int(argv[2]) 52 | fractional_bw = float(argv[3]) 53 | 54 | print(json.dumps({'rationalResampler': {'interpolate': interpolation, 'decimate': decimation, 'fractionalBw': fractional_bw, 'lpfCoeffs': design_filter(interpolation, decimation, fractional_bw)}})) 55 | 56 | if __name__ == '__main__': 57 | main(sys.argv) 58 | -------------------------------------------------------------------------------- /filter/polyphase_fir_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct sample_buf; 6 | 7 | /** 8 | * The state for a polyphase FIR. A Polyphase FIR is stored as a set of smaller filters, depending on the interpolation/decimation 9 | * factors. 10 | * 11 | * All coefficients are stored as Q.15, and are real by definition. The FIR is restructured as coefficients grouped in the appropriate 12 | * phase. 13 | * 14 | * The FIR, by definition, will represent taking an f_in stream of real samples and changing the output sample rate to 15 | * f_out = I/D * f_in, 16 | * Where I, D are fairly small and relatively prime. 17 | * 18 | * The FIR that is to be applied to the FIR should be a low-pass filter for MIN(f_out, f_in), as to avoid imaging/aliasing. 19 | */ 20 | struct polyphase_fir { 21 | /** 22 | * The phase filters themselves. There are nr_phase_filters (L) with nr_filter_coeffs (M) coefficients. 23 | * 24 | * The filters are stored as a 2-d array, where each i'th filter's j'th coefficient can be found by: 25 | * (i*M + j) 26 | */ 27 | int16_t *phase_filters; 28 | 29 | /** 30 | * The number of phase filters in this polyphase FIR 31 | */ 32 | size_t nr_phase_filters; 33 | 34 | /** 35 | * The number of filter coefficients in each phase filter. Where the polyphase filter would have less 36 | * than nr_filter_coeffs by design, the remaining coefficients will be 0. Typically this will be sized 37 | * based on SIMD requirements or similar (i.e. for ARM NEON it will be divisible by 4). 38 | */ 39 | size_t nr_filter_coeffs; 40 | 41 | /** 42 | * The last phase we processed 43 | */ 44 | size_t last_phase; 45 | 46 | /** 47 | * The interpolation factor 48 | */ 49 | unsigned int interpolation; 50 | 51 | /** 52 | * The decimation factor 53 | */ 54 | unsigned int decimation; 55 | 56 | /** 57 | * The current sample buffer to process 58 | */ 59 | struct sample_buf *sb_active; 60 | 61 | /** 62 | * The next sample buffer to process 63 | */ 64 | struct sample_buf *sb_next; 65 | 66 | /** 67 | * The total number of samples contained in this polyphase FIR 68 | */ 69 | size_t nr_samples; 70 | 71 | /** 72 | * The next sample to be processed, relative to the start of sb_active. 73 | */ 74 | size_t sample_offset; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /filter/sample_buf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | struct sample_buf; 11 | 12 | /** 13 | * The sample representation contained in the given sample buffer 14 | */ 15 | enum sample_type { 16 | /** 17 | * Unknown sample representation 18 | */ 19 | UNKNOWN = 0, 20 | 21 | /** 22 | * Samples are real unsigned 16-bit integers 23 | */ 24 | REAL_UINT_16 = 1, 25 | 26 | /** 27 | * Samples are complex unsigned 16-bit integers 28 | */ 29 | COMPLEX_UINT_16 = 2, 30 | 31 | /** 32 | * Samples are complex signed 16-bit integers 33 | */ 34 | COMPLEX_INT_16 = 3, 35 | 36 | /** 37 | * Samples are real unsigned 32-bit integers 38 | */ 39 | REAL_UINT_32 = 4, 40 | 41 | /** 42 | * Samples are complex unsigned 32-bit integers 43 | */ 44 | COMPLEX_UINT_32 = 5, 45 | }; 46 | 47 | typedef aresult_t (*sample_buf_release_func_t)(struct sample_buf *buf); 48 | 49 | /** 50 | * A sample buffer. Represents a count of samples, with the specified 51 | * integer format. 52 | * 53 | * If samples are real, they are packed one after the other. 54 | * RRRRRRRRRRRRRR... etc. 55 | * 56 | * However, for complex samples, I and Q are interleaved directly: 57 | * IQIQIQIQIQIQIQ... etc. 58 | */ 59 | struct sample_buf { 60 | /** 61 | * Reference count for this sample buf. This is decremented atomically, and cannot 62 | * be incremented after being initially set. Sample buffers can be freed in any 63 | * context. 64 | */ 65 | uint32_t refcount CAL_ALIGN(16); 66 | 67 | /** 68 | * The type of a sample in this buffer. 69 | */ 70 | enum sample_type sample_type; 71 | 72 | /** 73 | * The number of samples in this sample buffer 74 | */ 75 | uint32_t nr_samples; 76 | 77 | /** 78 | * The size of the sample buffer, in bytes 79 | */ 80 | uint32_t sample_buf_bytes; 81 | 82 | /** 83 | * The start timestamp (in sample time) of this buffer 84 | */ 85 | uint64_t start_time_ns; 86 | 87 | /** 88 | * Pointer to the function that will be called to release the sample buffer once the reference 89 | * count reaches 0. 90 | */ 91 | sample_buf_release_func_t release; 92 | 93 | /** 94 | * Private state an application can attach to the sample buffer. 95 | */ 96 | void *priv; 97 | 98 | /** 99 | * The actual data. This will need to be cast appropriately. 100 | */ 101 | uint8_t data_buf[]; 102 | }; 103 | 104 | aresult_t sample_buf_decref(struct sample_buf *buf); 105 | 106 | -------------------------------------------------------------------------------- /filter/dc_blocker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * State for a simple DC blocker with a differentiator ahead of a leaky integrator. 14 | */ 15 | struct dc_blocker { 16 | /** 17 | * Filter pole coefficient for leaky integrator. In Q.15 representation. 18 | */ 19 | int32_t p; 20 | 21 | /** 22 | * Prior input sample, for the differentiator. x[n-1]. In Q.30 representation. 23 | */ 24 | int32_t x_n_1; 25 | 26 | /** 27 | * Prior output sample for feedback, in Q.15. y[n-1] 28 | */ 29 | int32_t y_n_1; 30 | 31 | /** 32 | * Accumulator, contains error. In Q.30. 33 | */ 34 | int32_t acc; 35 | }; 36 | 37 | /** 38 | * Initialize a DC blocker to its starting state, with a pole placed as specified. 39 | * 40 | * \param blk Enough memory to store a DC blocker's state 41 | * \param pole The location of the pole for the integrator. 42 | * 43 | * \return A_OK on success, an error code otherwise 44 | */ 45 | static inline 46 | aresult_t dc_blocker_init(struct dc_blocker *blk, double pole) 47 | { 48 | aresult_t ret = A_OK; 49 | 50 | TSL_ASSERT_ARG(NULL != blk); 51 | TSL_ASSERT_ARG(0.0 != fabs(pole)); 52 | 53 | memset(blk, 0, sizeof(*blk)); 54 | 55 | /* Convert the pole to a fixed point integer */ 56 | blk->p = (int16_t)((1.0 - pole) * (double)(1 << Q_15_SHIFT)); 57 | 58 | return ret; 59 | } 60 | 61 | /** 62 | * Apply a simple DC blocker, based on a combination of a differentiator and a 63 | * leaky integrator. 64 | * 65 | * \param blocker The DC blocker state 66 | * \param samples Samples to apply the DC blocker to 67 | * \param nr_samples The number of samples in the input buffer 68 | * 69 | * \return A_OK on success, an error code otherwise. 70 | */ 71 | static inline 72 | aresult_t dc_blocker_apply(struct dc_blocker *blocker, int16_t *samples, size_t nr_samples) 73 | { 74 | aresult_t ret = A_OK; 75 | 76 | TSL_ASSERT_ARG(NULL != blocker); 77 | TSL_ASSERT_ARG(NULL != samples); 78 | TSL_ASSERT_ARG(0 != nr_samples); 79 | 80 | for (size_t i = 0; i < nr_samples; i++) { 81 | /* Only the error from the previous sample remains */ 82 | blocker->acc -= blocker->x_n_1; 83 | /* Update the current sample to Q.30, store as previous */ 84 | blocker->x_n_1 = samples[i] << Q_15_SHIFT; 85 | /* Accumulate the leaky integrator term */ 86 | blocker->acc += blocker->x_n_1 - blocker->p * blocker->y_n_1; 87 | /* Convert the output to Q.15 from Q.30 */ 88 | blocker->y_n_1 = blocker->acc >> Q_15_SHIFT; 89 | samples[i] = blocker->y_n_1; 90 | } 91 | 92 | return ret; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /ais/ais_demod_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Expected input sample rate 7 | */ 8 | #define AIS_INPUT_SAMPLE_RATE 48000 9 | 10 | /** 11 | * Bit rate of AIS messages 12 | */ 13 | #define AIS_BIT_RATE 9600 14 | 15 | /** 16 | * The blind decimation rate for AIS reception. It should be 5, but let's parameterize 17 | */ 18 | #define AIS_DECIMATION_RATE (AIS_INPUT_SAMPLE_RATE/AIS_BIT_RATE) 19 | 20 | /** 21 | * Structure to track detecting the preamble for AIS 22 | */ 23 | struct ais_demod_detect { 24 | /** 25 | * Preamble detection shift registers 26 | */ 27 | uint32_t preambles[AIS_DECIMATION_RATE]; 28 | 29 | /** 30 | * The last sample we saw (for NRZI decoding) 31 | */ 32 | uint8_t prior_sample[AIS_DECIMATION_RATE]; 33 | 34 | /** 35 | * Next preamble decimation field to update 36 | */ 37 | size_t next_field; 38 | 39 | }; 40 | 41 | #define AIS_PACKET_BITS 256 42 | #define AIS_PACKET_BYTES ((5 * AIS_PACKET_BITS)/8) 43 | 44 | #define AIS_PACKET_PREAMBLE_BITS 24 45 | 46 | #define AIS_PACKET_START_FLAG_BITS 8 47 | #define AIS_PACKET_START_FLAG 0x7e 48 | 49 | #define AIS_PACKET_DATA_BITS 168 50 | #define AIS_PACKET_FCS_BITS 16 51 | 52 | #define AIS_PACKET_END_FLAG_BITS 8 53 | #define AIS_PACKET_END_FLAG 0x7e 54 | 55 | 56 | /** 57 | * Structure to track receiving the AIS message bits. This is before bit unstuffing 58 | * is performed, but it does detect the packet start/end flags 59 | */ 60 | struct ais_demod_rx { 61 | /** 62 | * Current amount of data. 63 | */ 64 | uint8_t packet[5 * AIS_PACKET_BYTES]; 65 | 66 | /** 67 | * Shift register for the raw bits received. Used to find the end-of-message flag. 68 | */ 69 | uint8_t raw_shr; 70 | 71 | /** 72 | * The previous sample we got 73 | */ 74 | uint8_t last_sample; 75 | 76 | /** 77 | * The current bit we're populating 78 | */ 79 | size_t current_bit; 80 | 81 | /** 82 | * Number of 1's in a row (to deal with bit stuffing) 83 | */ 84 | size_t nr_ones; 85 | }; 86 | 87 | /** 88 | * State of the demodulator 89 | */ 90 | enum ais_demod_state { 91 | AIS_DEMOD_STATE_SEARCH_SYNC = 0, 92 | AIS_DEMOD_STATE_RECEIVING = 1, 93 | }; 94 | 95 | /** 96 | * Structure for demodulating a stream of AIS data, single-channel, arriving at 48 kHz 97 | */ 98 | struct ais_demod { 99 | struct ais_demod_detect detector; 100 | struct ais_demod_rx packet_rx; 101 | enum ais_demod_state state; 102 | ais_demod_on_message_callback_func_t on_msg_cb; 103 | uint32_t freq; 104 | size_t sample_skip; 105 | size_t crc_rejects; 106 | void *caller_state; 107 | }; 108 | 109 | #define AIS_MSG(sev, sys, msg, ...) MESSAGE("AIS", sev, sys, msg, ##__VA_ARGS__) 110 | 111 | -------------------------------------------------------------------------------- /multifm/demod.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define LPF_OUTPUT_LEN 1024 13 | 14 | struct polyphase_fir; 15 | struct demod_base; 16 | 17 | /** 18 | * Demodulator thread context 19 | */ 20 | struct demod_thread { 21 | /** 22 | * SPSC queue used to deliver work to this worker thread 23 | */ 24 | struct work_queue wq CAL_CACHE_ALIGNED; 25 | 26 | /** 27 | * The FIR filter being applied by this thread (usually for baseband selection) 28 | */ 29 | struct direct_fir fir; 30 | 31 | /** 32 | * The file descriptor for the output FIFO 33 | */ 34 | int fifo_fd; 35 | 36 | /** 37 | * The file descriptor for dumping the filtered signal 38 | */ 39 | int debug_signal_fd; 40 | 41 | /** 42 | * Mutex for the work queue. Always must be held while manipulating it. 43 | */ 44 | pthread_mutex_t wq_mtx; 45 | 46 | /** 47 | * Condition variable to be signalled when there is work to be done. This 48 | * thread will wait on the condvar until signalled to wake up by the 49 | * sample producer. 50 | */ 51 | pthread_cond_t wq_cv; 52 | 53 | /** 54 | * Demodulator worker thread state 55 | */ 56 | struct worker_thread wthr; 57 | 58 | /** 59 | * Demodulator state 60 | */ 61 | struct demod_base *demod; 62 | 63 | /** 64 | * Linked list node demodulator thread 65 | */ 66 | struct list_entry dt_node; 67 | 68 | /** 69 | * Total number of samples demodulated 70 | */ 71 | size_t total_nr_demod_samples; 72 | 73 | /** 74 | * Total number of PCM samples generated 75 | */ 76 | size_t total_nr_pcm_samples; 77 | 78 | /** 79 | * Number of samples dropped on the floor 80 | */ 81 | size_t nr_dropped_samples; 82 | 83 | /** 84 | * Number of FM signal samples available 85 | */ 86 | size_t nr_fm_samples; 87 | 88 | /** 89 | * Filtered samples to be processed 90 | */ 91 | int16_t filt_samp_buf[2 * LPF_OUTPUT_LEN]; 92 | 93 | /** 94 | * Number of good PCM samples 95 | */ 96 | size_t nr_pcm_samples; 97 | 98 | /** 99 | * Output demodulated sample buffer 100 | */ 101 | int16_t out_buf[LPF_OUTPUT_LEN]; 102 | }; 103 | 104 | aresult_t demod_thread_delete(struct demod_thread **pthr); 105 | 106 | /** 107 | * Create a new demodulation thread. 108 | * 109 | * \param demod_gain The gain of the channelizing FIR, expressed in linear units. 110 | * 111 | */ 112 | aresult_t demod_thread_new(struct demod_thread **pthr, unsigned core_id, 113 | int32_t offset_hz, uint32_t samp_hz, const char *out_fifo, int decimation_factor, 114 | const double *lpf_taps, size_t lpf_nr_taps, 115 | const char *fir_debug_output, 116 | double channel_gain); 117 | 118 | -------------------------------------------------------------------------------- /pager/mueller_muller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | //#define _MM_DEBUG 8 | 9 | /** 10 | * State for a Mueller-Muller clock recovery. 11 | * 12 | * This is a soft-decision block, but it does not interpolate the samples. Thus, the 13 | * caller must slice the ouputs per their requirements. 14 | * 15 | * TODO: make this fixed point. Using an int32_t is probably enough overkill for error 16 | * accumulation to not murder us. 17 | */ 18 | struct mueller_muller { 19 | /* Number of samples, per bit, based on the sample rate */ 20 | float samples_per_bit; 21 | 22 | /* Omega gain for control loop */ 23 | float kw; 24 | 25 | /* Mu gain for control loop */ 26 | float km; 27 | 28 | float error_min; 29 | float error_max; 30 | 31 | /** 32 | * Omega 33 | */ 34 | float w; 35 | 36 | /** 37 | * Mu 38 | */ 39 | float m; 40 | 41 | /** 42 | * Offset of the next sample to process in the following block. 43 | */ 44 | float next_offset; 45 | 46 | /** 47 | * Prior sample processed 48 | */ 49 | float last_sample; 50 | 51 | /** 52 | * The step size, specified at initialization 53 | */ 54 | float ideal_step_size; 55 | 56 | #ifdef _MM_DEBUG 57 | /** 58 | * Total samples processed 59 | */ 60 | uint64_t nr_samples; 61 | #endif 62 | }; 63 | 64 | /** 65 | * Initialize a new Mueller-Muller CLock Recovery instance 66 | * 67 | * \param mm Memory to initialize state within 68 | * \param kw Omega gain 69 | * \param km Mu gain 70 | * \param samples_per_bit The number of samples of the input per decision output 71 | * \param error_min The minimum range of error in the control loop before clamping 72 | * \param error_max The maximum range of error in the control loop before clamping 73 | * 74 | * \return A_OK on success, an error code otherwise 75 | */ 76 | aresult_t mm_init(struct mueller_muller *mm, float kp, float km, float samples_per_bit, float error_min, float error_max); 77 | 78 | /** 79 | * Feed the next sample block to the Mueller-Muller Clock Recovery. 80 | * \param mm The Mueller-Muller Clock Recovery instance 81 | * \param samples Raw PCM samples to process. 82 | * \param nr_samples The number of samples 83 | * \param decisions Soft bit decisions, coming from Mueller-Muller 84 | * \param nr_decisions The maximum number of decisions the current buffer can hold 85 | * \param pnr_decisions_out The number of decisions made 86 | * 87 | * \return A_OK on success, an error code otherwise 88 | * 89 | * \note nr_decisions Needs to be at least (nr_samples/samples_per_bit) + 2 in length. Each 90 | * call to mm_process should be able to process the entirety of the input sample 91 | * buffer in a single shot. 92 | * 93 | * \note The state for the Mueller-Muller Clock Recovery block will track the offset to the 94 | * next sample in the subsequent sample buffer. 95 | */ 96 | aresult_t mm_process(struct mueller_muller *mm, const int16_t *samples, size_t nr_samples, int16_t *decisions, 97 | size_t nr_decisions, size_t *pnr_decisions_out); 98 | -------------------------------------------------------------------------------- /multifm/fm_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | struct multifm_fm_demod { 15 | struct demod_base demod; 16 | int32_t last_fm_re; 17 | int32_t last_fm_im; 18 | }; 19 | 20 | aresult_t multifm_fm_demod_init(struct demod_base **pdemod) 21 | { 22 | aresult_t ret = A_OK; 23 | 24 | struct multifm_fm_demod *demod = NULL; 25 | 26 | TSL_ASSERT_ARG(NULL != pdemod); 27 | *pdemod = NULL; 28 | 29 | TSL_BUG_IF_FAILED(TZAALLOC(demod, SYS_CACHE_LINE_LENGTH)); 30 | 31 | *pdemod = &demod->demod; 32 | 33 | return ret; 34 | } 35 | 36 | aresult_t multifm_fm_demod_process(struct demod_base *demod, int16_t *in_samples, size_t nr_in_samples, 37 | int16_t *out_samples, size_t *pnr_out_samples, size_t *pnr_out_bytes) 38 | { 39 | aresult_t ret = A_OK; 40 | 41 | struct multifm_fm_demod *dfm = NULL; 42 | static const float to_q15 = (float)(1 << Q_15_SHIFT); 43 | size_t nr_out_samples = 0; 44 | 45 | TSL_ASSERT_ARG(NULL != demod); 46 | TSL_ASSERT_ARG(NULL != in_samples); 47 | TSL_ASSERT_ARG(0 != nr_in_samples); 48 | TSL_ASSERT_ARG(NULL != out_samples); 49 | TSL_ASSERT_ARG(NULL != pnr_out_samples); 50 | 51 | dfm = BL_CONTAINER_OF(demod, struct multifm_fm_demod, demod); 52 | 53 | for (size_t i = 0; i < nr_in_samples; i++) { 54 | /* Get the complex conjugate of the prior sample, negating the phase term */ 55 | int32_t b_re = dfm->last_fm_re, 56 | b_im = -dfm->last_fm_im, 57 | a_re = in_samples[2 * i ], 58 | a_im = in_samples[2 * i + 1]; 59 | int32_t s_re = 0, 60 | s_im = 0; 61 | 62 | /* Calculate the phase difference */ 63 | s_re = a_re * b_re - a_im * b_im; 64 | s_im = a_re * b_im + a_im * b_re; 65 | 66 | /* Convert from cartesian coordinates to a phase angle */ 67 | /* TODO: This needs to be made full-integer */ 68 | float phi = fast_atan2f((float)s_im, (float)s_re); 69 | 70 | /* Scale by pi (since atan2 returns an angle in (-pi,pi]), convert back to Q.15 */ 71 | float phi_scaled = (phi/M_PI) * to_q15; 72 | out_samples[nr_out_samples] = (int16_t)phi_scaled; 73 | 74 | nr_out_samples++; 75 | 76 | /* Store the last sample processed */ 77 | dfm->last_fm_re = a_re; 78 | dfm->last_fm_im = a_im; 79 | } 80 | 81 | *pnr_out_samples = nr_out_samples; 82 | *pnr_out_bytes = nr_out_samples * sizeof(int16_t); 83 | 84 | return ret; 85 | } 86 | 87 | aresult_t multifm_fm_demod_cleanup(struct demod_base **pdemod) 88 | { 89 | aresult_t ret = A_OK; 90 | 91 | struct multifm_fm_demod *demod = NULL; 92 | 93 | TSL_ASSERT_ARG(NULL != pdemod); 94 | TSL_ASSERT_ARG(NULL != *pdemod); 95 | 96 | demod = BL_CONTAINER_OF(*pdemod, struct multifm_fm_demod, demod); 97 | 98 | TFREE(demod); 99 | 100 | *pdemod = NULL; 101 | 102 | return ret; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /etc/flex_25khz_lpf.json: -------------------------------------------------------------------------------- 1 | { 2 | "lpfTaps" : [ 3 | -0.00019319782815014954, -0.00017418171264726569, 4 | -0.00015577314809292, -0.00013700332750015603, 5 | -0.00011680866432179932, -9.4039137572954424E-5, 6 | -6.7467739520852429E-5, -3.580095349035462E-5, 7 | 2.3098216161804413E-6, 4.8255993753148596E-5, 8 | 0.00010345875026787696, 0.00016935539227939836, 9 | 0.00024738515025617895, 0.00033897460670573478, 10 | 0.00044552285731429519, 0.00056838654610670461, 11 | 0.0007088649131848767, 0.00086818499530824406, 12 | 0.0010474871199748126, 0.0012478108327321608, 13 | 0.0014700813951920631, 0.0017150969876513417, 14 | 0.0019835167453585998, 0.0022758497513478541, 15 | 0.00259244510143407, 0.0029334831484927591, 16 | 0.0032989680235973855, 0.0036887215210470894, 17 | 0.0041023784228751579, 0.00453938332618749, 18 | 0.0049989890237493867, 0.0054802564747353849, 19 | 0.005982056388603405, 0.0065030724307788574, 20 | 0.0070418060443683287, 0.0075965828675999972, 21 | 0.0081655607122440924, 0.0087467390540367611, 22 | 0.0093379699722484621, 0.0099369704621340756, 23 | 0.010541336031203967, 0.011148555478184663, 24 | 0.011756026742310424, 0.012361073700310778, 25 | 0.012960963779233385, 0.013552926245156723, 26 | 0.014134171020982028, 0.014701907880917764, 27 | 0.015253365865038136, 0.015785812754454905, 28 | 0.016296574446218896, 0.016783054067083168, 29 | 0.017242750666717047, 0.017673277333851146, 30 | 0.01807237858313453, 0.018437946866161483, 31 | 0.018768038067127291, 0.019060885851839366, 32 | 0.019314914748268783, 0.019528751847392764, 33 | 0.019701237024656974, 0.019831431594872054, 34 | 0.019918625326639711, 0.019962341756358033, 35 | 0.019962341756358033, 0.019918625326639711, 36 | 0.019831431594872054, 0.019701237024656974, 37 | 0.019528751847392764, 0.019314914748268783, 38 | 0.019060885851839366, 0.018768038067127291, 39 | 0.018437946866161483, 0.01807237858313453, 40 | 0.017673277333851146, 0.017242750666717047, 41 | 0.016783054067083168, 0.016296574446218896, 42 | 0.015785812754454905, 0.015253365865038136, 43 | 0.014701907880917764, 0.014134171020982028, 44 | 0.013552926245156723, 0.012960963779233385, 45 | 0.012361073700310778, 0.011756026742310424, 46 | 0.011148555478184663, 0.010541336031203967, 47 | 0.0099369704621340756, 0.0093379699722484621, 48 | 0.0087467390540367611, 0.0081655607122440924, 49 | 0.0075965828675999972, 0.0070418060443683287, 50 | 0.0065030724307788574, 0.005982056388603405, 51 | 0.0054802564747353849, 0.0049989890237493867, 52 | 0.00453938332618749, 0.0041023784228751579, 53 | 0.0036887215210470894, 0.0032989680235973855, 54 | 0.0029334831484927591, 0.00259244510143407, 55 | 0.0022758497513478541, 0.0019835167453585998, 56 | 0.0017150969876513417, 0.0014700813951920631, 57 | 0.0012478108327321608, 0.0010474871199748126, 58 | 0.00086818499530824406, 0.0007088649131848767, 59 | 0.00056838654610670461, 0.00044552285731429519, 60 | 0.00033897460670573478, 0.00024738515025617895, 61 | 0.00016935539227939836, 0.00010345875026787696, 62 | 4.8255993753148596E-5, 2.3098216161804413E-6, 63 | -3.580095349035462E-5, -6.7467739520852429E-5, 64 | -9.4039137572954424E-5, -0.00011680866432179932, 65 | -0.00013700332750015603, -0.00015577314809292, 66 | -0.00017418171264726569, -0.00019319782815014954 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /pager/mueller_muller.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | aresult_t mm_init(struct mueller_muller *mm, float kw, float km, float samples_per_bit, float error_min, float error_max) 11 | { 12 | aresult_t ret = A_OK; 13 | 14 | TSL_ASSERT_ARG(NULL != mm); 15 | 16 | /* Clear the memory */ 17 | memset(mm, 0, sizeof(struct mueller_muller)); 18 | 19 | mm->next_offset = 0.0; 20 | mm->m = mm->w = mm->ideal_step_size = samples_per_bit; 21 | mm->kw = kw; 22 | mm->km = km; 23 | mm->error_min = error_min; 24 | mm->error_max = error_max; 25 | mm->samples_per_bit = samples_per_bit; 26 | 27 | #ifdef _MM_DEBUG 28 | mm->nr_samples = 0; 29 | #endif 30 | 31 | return ret; 32 | } 33 | 34 | static 35 | float _mm_get_sign(float v) 36 | { 37 | return (float)(v > 0) - (float)(v < 0); 38 | } 39 | 40 | aresult_t mm_process(struct mueller_muller *mm, const int16_t *samples, size_t nr_samples, int16_t *decisions, 41 | size_t nr_decisions, size_t *pnr_decisions_out) 42 | { 43 | aresult_t ret = A_OK; 44 | 45 | float cur_sample = 0.0f, 46 | nr_samples_f = 0.0f, 47 | w = 0.0f, 48 | m = 0.0f; 49 | size_t cur_decision = 0; 50 | 51 | TSL_ASSERT_ARG(NULL != mm); 52 | TSL_ASSERT_ARG(NULL != samples); 53 | TSL_ASSERT_ARG(NULL != decisions); 54 | TSL_ASSERT_ARG(NULL != pnr_decisions_out); 55 | 56 | cur_sample = mm->next_offset; 57 | nr_samples_f = (float)nr_samples; 58 | w = mm->w; 59 | m = mm->m; 60 | 61 | #ifdef _MM_DEBUG 62 | DIAG("mm_process: nr_samples_f = %f, next_step_size = %f, cur_sample = %f", nr_samples_f, w, cur_sample); 63 | #endif 64 | 65 | while (cur_sample < nr_samples_f) { 66 | float sample = samples[(size_t)(cur_sample + 0.5f)]; 67 | float w_error = 0.0f; 68 | 69 | /* TODO: we might want to interpolate here */ 70 | decisions[cur_decision] = sample; 71 | cur_decision++; 72 | 73 | TSL_BUG_ON(cur_decision > nr_decisions); 74 | 75 | /* Calculate the error for our PI loop */ 76 | w_error = _mm_get_sign(mm->last_sample) * sample - _mm_get_sign(sample) * mm->last_sample; 77 | 78 | /* Determine the next sample to process */ 79 | w += w_error * mm->kw; 80 | 81 | #ifdef _MM_DEBUG 82 | fprintf(stdout, "%f, %f, %f;\n", (double)cur_sample + (double)mm->nr_samples, (double)sample, (double)w_error); 83 | #endif 84 | 85 | /* Clamp if our error is becoming too big */ 86 | if (mm->error_min > w) { 87 | w = mm->error_min; 88 | } else if (mm->error_max < w) { 89 | w = mm->error_max; 90 | } 91 | 92 | m += w + mm->km * sample; 93 | 94 | /* Calculate the offset of the next sample to be processed */ 95 | cur_sample += floorf(m); 96 | 97 | m -= floorf(m); 98 | 99 | /* Store the sample we just processed */ 100 | mm->last_sample = sample; 101 | } 102 | 103 | #ifdef _MM_DEBUG 104 | mm->nr_samples += nr_samples; 105 | #endif 106 | 107 | /* Store the offset of the next sample to be processed in the following buffer */ 108 | mm->next_offset = cur_sample - nr_samples_f; 109 | mm->w = w; 110 | mm->m = m; 111 | 112 | *pnr_decisions_out = cur_decision; 113 | 114 | return ret; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /multifm/receiver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct frame_alloc; 8 | struct receiver; 9 | struct config; 10 | struct sample_buf; 11 | 12 | typedef aresult_t (*receiver_cleanup_func_t)(struct receiver *rx); 13 | typedef aresult_t (*receiver_rx_thread_func_t)(struct receiver *rx); 14 | 15 | /** 16 | * Structure representing the generic state for a receiver. Usually embedded in a specialized 17 | * receiver structure. 18 | */ 19 | struct receiver { 20 | /** 21 | * Whether or not this thread is muted, to prevent output 22 | */ 23 | bool muted; 24 | 25 | /** 26 | * Linked list of all demodulator threads 27 | */ 28 | struct list_entry demod_threads; 29 | 30 | /** 31 | * The number of demodulator threads 32 | */ 33 | size_t nr_demod_threads; 34 | 35 | /** 36 | * Number of failed sample buffer allocations 37 | */ 38 | size_t nr_samp_buf_alloc_fails; 39 | 40 | /** 41 | * Frame allocator of sample buffers 42 | */ 43 | struct frame_alloc *samp_alloc; 44 | 45 | /** 46 | * The worker thread for this receiver. Mandatory, each receiver must live in 47 | * its own separate worker thread apartment. 48 | */ 49 | struct worker_thread wthr; 50 | 51 | /** 52 | * Function called to clean up the receiver state 53 | */ 54 | receiver_cleanup_func_t cleanup_func; 55 | 56 | /** 57 | * Function called to kick of the rx thread actions for this receiver 58 | */ 59 | receiver_rx_thread_func_t thread_func; 60 | }; 61 | 62 | /** 63 | * Initialize a receiver structure, hooking the receiver into the multifm 64 | * framework. Call `receiver_start` to kick off the receiver thread. This 65 | * assumes receiver is already initialized with memory allocated by the driver. 66 | * 67 | * \param rx The receiver structure. Preallocated, usually embedded in a specific 68 | * receiver type. 69 | * \param cfg The configuration for the overall receiver. Includes demodulation 70 | * and filter thread configuration. 71 | * \param rx_func The receive function, called on starting the receive thread 72 | * \param cleanup_func The cleanup function, called when `receiver_cleanup()` is called. 73 | * \param samples_per_buf The number of samples for each buffer handled. 74 | * 75 | * \return A_OK on success, an error code otherwise. 76 | */ 77 | aresult_t receiver_init(struct receiver *rx, struct config *cfg, 78 | receiver_rx_thread_func_t rx_func, receiver_cleanup_func_t cleanup_func, 79 | size_t samples_per_buf); 80 | 81 | /** 82 | * Start the receiver thread. 83 | */ 84 | aresult_t receiver_start(struct receiver *rx); 85 | 86 | /** 87 | * Cleanup any hidden allocations in the receiver structure, and tear-down the 88 | * demodulation threads. Also terminates the receiver thread, and forces the 89 | * driver to clean itself up. 90 | * 91 | * \param prx The receiver state. Passed by reference, set to NULL on success. 92 | * 93 | * \return A_OK on success, an error code otherwise. 94 | */ 95 | aresult_t receiver_cleanup(struct receiver **prx); 96 | 97 | /** 98 | * Allocate a receiver sample buffer 99 | */ 100 | aresult_t receiver_sample_buf_alloc(struct receiver *rx, struct sample_buf **pbuf); 101 | 102 | /** 103 | * Unmute/Mute the receiver 104 | */ 105 | aresult_t receiver_set_mute(struct receiver *rx, bool mute); 106 | 107 | /** 108 | * Deliver a filled sample buffer to the receiver's listeners. 109 | * 110 | * \param rx The receiver state 111 | * \param buf The buffer of samples to be delivered. 112 | */ 113 | aresult_t receiver_sample_buf_deliver(struct receiver *rx, struct sample_buf *buf); 114 | 115 | /** 116 | * Check if this receiver is still scheduled to be running 117 | * 118 | * \param rx The receiver state 119 | * 120 | * \return true if the thread is still scheduled to run, false otherwise 121 | * 122 | * \note asserts failure if rx is invalid 123 | */ 124 | bool receiver_thread_running(struct receiver *rx); 125 | 126 | -------------------------------------------------------------------------------- /multifm/costas_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct multifm_costas_demod { 15 | struct demod_base demod; 16 | float f_shift; 17 | float alpha; 18 | float beta; 19 | float last_phase; 20 | float f_dev; 21 | float f_dev_max; 22 | float f_dev_min; 23 | float e_max; 24 | }; 25 | 26 | aresult_t multifm_costas_demod_init(struct demod_base **pdemod, float f_shift, float alpha, float beta, 27 | int16_t e_max) 28 | { 29 | aresult_t ret = A_OK; 30 | 31 | struct multifm_costas_demod *demod = NULL; 32 | static const float to_q15 = (float)(1 << Q_15_SHIFT); 33 | 34 | TSL_ASSERT_ARG(NULL != pdemod); 35 | 36 | TSL_BUG_IF_FAILED(TZAALLOC(demod, SYS_CACHE_LINE_LENGTH)); 37 | 38 | demod->f_shift = f_shift; 39 | demod->alpha = alpha; 40 | demod->beta = beta; 41 | 42 | demod->last_phase = 0.0f; 43 | demod->e_max = (float)e_max/to_q15; 44 | 45 | demod->f_dev = 2.0f * M_PI * f_shift; 46 | 47 | /* TODO: un-fix this error coefficient */ 48 | demod->f_dev_max = demod->f_dev + 0.3f; 49 | demod->f_dev_min = demod->f_dev - 0.3f; 50 | 51 | *pdemod = &demod->demod; 52 | 53 | return ret; 54 | } 55 | 56 | aresult_t multifm_costas_demod_process(struct demod_base *demod, int16_t *in_samples, size_t nr_in_samples, 57 | int16_t *out_samples, size_t *pnr_out_samples, size_t *pnr_out_bytes) 58 | { 59 | aresult_t ret = A_OK; 60 | 61 | static const float to_q15 = (float)(1 << Q_15_SHIFT); 62 | struct multifm_costas_demod *dc = NULL; 63 | float e_max = 0.0; 64 | 65 | TSL_ASSERT_ARG(NULL != demod); 66 | TSL_ASSERT_ARG(NULL != in_samples); 67 | TSL_ASSERT_ARG(0 != nr_in_samples); 68 | TSL_ASSERT_ARG(NULL != out_samples); 69 | TSL_ASSERT_ARG(NULL != pnr_out_samples); 70 | TSL_ASSERT_ARG(NULL != pnr_out_bytes); 71 | 72 | dc = BL_CONTAINER_OF(demod, struct multifm_costas_demod, demod); 73 | 74 | e_max = dc->e_max; 75 | 76 | *pnr_out_samples = 0; 77 | *pnr_out_bytes = 0; 78 | 79 | for (size_t i = 0; i < nr_in_samples; i++) { 80 | /* Grab the sample */ 81 | float error = 0.0, 82 | phase = 0.0; 83 | complex float samp = CMPLX((float)in_samples[2 * i ]/to_q15, 84 | (float)in_samples[2 * i + 1]/to_q15); 85 | complex float nco = cexpf(CMPLX(0, -dc->last_phase)); 86 | complex float out_samp = samp * nco; 87 | 88 | error = cimagf(out_samp) * crealf(out_samp); 89 | 90 | if (error > e_max) error = e_max; 91 | else if (error < -e_max) error = -e_max; 92 | 93 | dc->f_dev = dc->f_dev + dc->beta * error; 94 | phase = dc->last_phase + dc->f_dev + dc->alpha * error; 95 | 96 | if (dc->f_dev > dc->f_dev_max) { 97 | dc->f_dev = dc->f_dev_max; 98 | } else if (dc->f_dev < dc->f_dev_min) { 99 | dc->f_dev = dc->f_dev_min; 100 | } 101 | 102 | dc->last_phase = fmodf(phase, 2 * M_PI); 103 | 104 | TSL_BUG_ON(fabsf(crealf(out_samp)) > 1.0); 105 | TSL_BUG_ON(fabsf(cimagf(out_samp)) > 1.0); 106 | 107 | out_samples[2 * i ] = crealf(out_samp) * to_q15; 108 | out_samples[2 * i + 1] = cimagf(out_samp) * to_q15; 109 | } 110 | 111 | *pnr_out_samples = nr_in_samples; 112 | *pnr_out_bytes = nr_in_samples * 2 * sizeof(int16_t); 113 | 114 | return ret; 115 | } 116 | 117 | aresult_t multifm_costas_demod_cleanup(struct demod_base **pdemod) 118 | { 119 | aresult_t ret = A_OK; 120 | 121 | struct multifm_costas_demod *demod = NULL; 122 | 123 | TSL_ASSERT_ARG(NULL != pdemod); 124 | TSL_ASSERT_ARG(NULL != *pdemod); 125 | 126 | demod = BL_CONTAINER_OF(*pdemod, struct multifm_costas_demod, demod); 127 | 128 | TFREE(demod); 129 | 130 | *pdemod = NULL; 131 | 132 | return ret; 133 | } 134 | 135 | -------------------------------------------------------------------------------- /pager/pager_flex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | struct pager_flex; 9 | 10 | /** 11 | * Callback type. This is registered with each pager_flex, and is called whenever there is an alphanumeric page to process. 12 | * 13 | * Provides the callee with the baud rate, cycle and frame number, the phase ID, message type and the cap code and the message 14 | * decoded as ASCII. 15 | */ 16 | typedef aresult_t (*pager_flex_on_alnum_msg_func_t)( 17 | struct pager_flex *flex, 18 | uint16_t baud, 19 | uint8_t phase, 20 | uint8_t cycle_no, 21 | uint8_t frame_no, 22 | uint64_t cap_code, 23 | bool fragmented, 24 | bool maildrop, 25 | uint8_t seq_num, 26 | const char *message_bytes, 27 | size_t message_len); 28 | 29 | /** 30 | * Callback type. This is registered with each pager_flex, and is called whenever there is a long numeric page to process. 31 | * 32 | * Provides the callee with the baud rate, cycle and frame number, the phase ID, message type and the cap code and the message 33 | * decoded as ASCII. 34 | */ 35 | typedef aresult_t (*pager_flex_on_num_msg_func_t)( 36 | struct pager_flex *flex, 37 | uint16_t baud, 38 | uint8_t phase, 39 | uint8_t cycle_no, 40 | uint8_t frame_no, 41 | uint64_t cap_code, 42 | const char *message_bytes, 43 | size_t message_len); 44 | 45 | /** 46 | * Special Instruction Vector (SIV) types 47 | * \{ 48 | */ 49 | 50 | /** 51 | * Temporary Address Activation, for the specified pagers (by CAPCODE) 52 | */ 53 | #define PAGER_FLEX_SIV_TEMP_ADDRESS_ACTIVATION 0x0 54 | 55 | /** 56 | * System Event, used to signal faults and system state changes. 57 | */ 58 | #define PAGER_FLEX_SIV_SYSTEM_EVENT 0x1 59 | 60 | /** 61 | * A reserved test vector type. 62 | */ 63 | #define PAGER_FLEX_SIV_RESERVED_TEST 0x3 64 | 65 | /** 66 | *\} 67 | */ 68 | 69 | /** 70 | * Callback type. This is registered with each pager_flex, and is called whenever there is a short instruction 71 | * vector to process. 72 | * 73 | * Provides the callee with information about the FLEX stream, as well as the SIV type. 74 | */ 75 | typedef aresult_t (*pager_flex_on_siv_msg_func_t)( 76 | struct pager_flex *flex, 77 | uint16_t baud, 78 | uint8_t phase, 79 | uint8_t cycle_no, 80 | uint8_t frame_no, 81 | uint64_t cap_code, 82 | uint8_t siv_msg_type, 83 | uint32_t data); 84 | 85 | /** 86 | * Create a new FLEX pager handler. 87 | * 88 | * \param pflex The new FLEX pager handler. Returned by reference. 89 | * \param freq_hz The frequency, in Hertz, of this channel. Used for record keeping mostly. 90 | * \param on_aln_msg Callback hit by the FLEX pager handler whenever a finished alphanumeric message is ready. 91 | * \param on_num_msg Callback hit by the FLEX pager handler whenever a finished numeric message is ready. 92 | * 93 | * \return A_OK on success, an error code otherwise. 94 | */ 95 | aresult_t pager_flex_new(struct pager_flex **pflex, uint32_t freq_hz, pager_flex_on_alnum_msg_func_t on_aln_msg, 96 | pager_flex_on_num_msg_func_t on_num_msg, pager_flex_on_siv_msg_func_t on_siv_msg); 97 | 98 | /** 99 | * Delete a FLEX pager handler. 100 | * 101 | * \param pflex The FLEX pager handler to delete. Passed by reference. Set to NULL when cleanup is finished. 102 | * 103 | * \return A_OK on success, an error code otherwise. 104 | */ 105 | aresult_t pager_flex_delete(struct pager_flex **pflex); 106 | 107 | /** 108 | * Push a block of PCM samples through the FLEX pager decoder. This will decode and demodulate/deliver data 109 | * as soon as enough data is available. 110 | * 111 | * \param flex The FLEX pager decoder state 112 | * \param pcm_samples PCM samples, in Q.15 113 | * \param nr_samples The number of samples to process in pcm_samples 114 | */ 115 | aresult_t pager_flex_on_pcm(struct pager_flex *flex, const int16_t *pcm_samples, size_t nr_samples); 116 | 117 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | include(CMakeDependentOption) 4 | 5 | project(TSL-SDR VERSION 1.0.0) 6 | 7 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -gdwarf-4 -rdynamic") 8 | set(CMAKE_C_STANDARD 11) 9 | set(CMAKE_C_STANDARD_REQUIRED True) 10 | 11 | # Installation path information 12 | set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") 13 | 14 | # Add our default set of gcc warnings 15 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wundef -Wstrict-prototypes -Wmissing-prototypes -Wno-trigraphs") 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration") 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-security") 18 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-delete-null-pointer-checks -Wuninitialized -Wmissing-include-dirs -Wshadow") 19 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wframe-larger-than=2047") 20 | 21 | set(TSL_SDR_BASE_DIR "${PROJECT_SOURCE_DIR}") 22 | 23 | add_definitions(-D_ATS_IN_TREE -D_GNU_SOURCE) 24 | add_definitions(-DSYS_CACHE_LINE_LENGTH=64) 25 | 26 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") 27 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") 28 | 29 | # Enable POCSAG debugging 30 | option(DEBUG_POCSAG "Enable POCSAG State Machine Debugging" OFF) 31 | 32 | # Enable DIAG statements 33 | cmake_dependent_option(DEBUG_TSL "Enable verbose TSL debugging" ON 34 | "DEBUG_POCSAG" OFF) 35 | 36 | # Grab the revision from git 37 | execute_process(COMMAND git describe --abbrev=16 --dirty --always --tags 38 | OUTPUT_VARIABLE GIT_REV 39 | ERROR_QUIET) 40 | 41 | if ("${GIT_REV}" STREQUAL "") 42 | set(GIT_REV "NotInGit") 43 | else() 44 | string(STRIP "${GIT_REV}" GIT_REV) 45 | endif() 46 | 47 | add_definitions(-D_VC_VERSION="${GIT_REV}") 48 | 49 | # Enable POCSAG debugging if requested 50 | if (DEBUG_POCSAG) 51 | add_definitions(-D_PAGER_POCSAG_DEBUG) 52 | 53 | endif (DEBUG_POCSAG) 54 | 55 | if (DEBUG_TSL) 56 | add_definitions(-D_TSL_DEBUG) 57 | endif (DEBUG_TSL) 58 | 59 | # Grab the CPU model we're building on (no cross-compiling support) 60 | execute_process(COMMAND uname -m 61 | OUTPUT_VARIABLE CPU_ARCH 62 | ERROR_QUIET) 63 | string(STRIP "${CPU_ARCH}" CPU_ARCH) 64 | 65 | message(STATUS "CPU Archiecture is ${CPU_ARCH}") 66 | 67 | if("${CPU_ARCH}" STREQUAL "armv7l") 68 | message(STATUS "Enabling NEON, setting CPU Architecture to armv7-a") 69 | add_definitions(-D_USE_ARM_NEON) 70 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfpu=crypto-neon-fp-armv8 -mfloat-abi=hard") 71 | elseif("${CPU_ARCH}" STREQUAL "aarch64") 72 | message(STATUS "Enabling NEON, setting CPU architecture to armv8-a") 73 | add_definitions(-D_USE_ARM_NEON) 74 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a -mtune=native") 75 | else() 76 | message(STATUS "Using conservative defaults for CPU architecture") 77 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -mtune=native") 78 | endif() 79 | 80 | # We use pkg-config to find our required libraries 81 | find_package(PkgConfig REQUIRED) 82 | 83 | # Find the TSL 84 | pkg_check_modules(TSL REQUIRED tsl) 85 | 86 | # Find RTL-SDR 87 | pkg_check_modules(RTLSDR librtlsdr) 88 | 89 | # Find DespAirspy 90 | pkg_check_modules(DESPAIRSPY libdespairspy) 91 | 92 | # Find UHD 93 | pkg_check_modules(UHD uhd) 94 | 95 | # Find ConcurrencyKit 96 | pkg_check_modules(CK REQUIRED ck) 97 | 98 | # Find Jansson 99 | pkg_check_modules(JANSSON REQUIRED jansson) 100 | 101 | # Build the Project 102 | add_subdirectory(filter) 103 | add_subdirectory(multifm) 104 | add_subdirectory(pager) 105 | add_subdirectory(ais) 106 | add_subdirectory(resampler) 107 | add_subdirectory(decoder) 108 | 109 | # Only include RF interface modules we built as dpkg deps 110 | set(RF_INTERFACE_DEPS ) 111 | if(RTLSDR_FOUND) 112 | set(RF_INTERFACE_DEPS "${RF_INTERFACE_DEPS}, librtlsdr0") 113 | endif() 114 | 115 | if(UHD_FOUND) 116 | set(RF_INTERFACE_DEPS "${RF_INTERFACE_DEPS}, libuhd003") 117 | endif() 118 | 119 | # Generate dpkg automatically 120 | set(CPACK_GENERATOR "DEB") 121 | set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) 122 | set(CPACK_PACKAGE_NAME "tsl-sdr") 123 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 124 | set(CPACK_PACKAGE_CONTACT "Phil Vachon ") 125 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The Standard Library of SDR Functions") 126 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libjansson4, libck0${RF_INTERFACE_DEPS}") 127 | include(CPack) 128 | 129 | -------------------------------------------------------------------------------- /filter/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * utils.c - Reusable utilities for writing filters 3 | * 4 | * Copyright (c)2017 Phil Vachon 5 | * 6 | * This file is a part of The Standard Library (TSL) 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | /** 33 | * Compute the dot product of samples spread across a zero-copy buffer with a coefficient vector. 34 | * 35 | * All inputs and outputs from this function are real-valued (i.e. baseband). 36 | * 37 | * \param sb_active The current sample buffer. Must never be NULL. 38 | * \param sb_next The "next" sample buffer. Must not be NULL if sb_active has fewer than nr_coeffs samples in it. 39 | * \param buf_start_offset The start offset, in samples, from the start of sb_active to the sample to be dotted starting from. 40 | * \param coeffs The coefficients to be dotted with samples in sb_active, sb_next. All real-valued. 41 | * \param nr_coeffs The number of coefficients in the coeffs vector. 42 | * \param psample The resultant sample. Returned by reference. 43 | * 44 | * \return A_OK on success, an error code otherwise. 45 | */ 46 | aresult_t dot_product_sample_buffers_real( 47 | struct sample_buf *sb_active, 48 | struct sample_buf *sb_next, 49 | size_t buf_start_offset, 50 | int16_t *coeffs, 51 | size_t nr_coeffs, 52 | int16_t *psample) 53 | { 54 | aresult_t ret = A_OK; 55 | 56 | int32_t acc_res = 0; 57 | size_t coeffs_remain = 0, 58 | buf_offset = 0; 59 | struct sample_buf *cur_buf = NULL; 60 | 61 | TSL_ASSERT_ARG_DEBUG(NULL != sb_active); 62 | TSL_ASSERT_ARG_DEBUG(NULL != coeffs); 63 | TSL_ASSERT_ARG_DEBUG(0 != nr_coeffs); 64 | TSL_ASSERT_ARG_DEBUG(NULL != psample); 65 | 66 | coeffs_remain = nr_coeffs; 67 | cur_buf = sb_active; 68 | buf_offset = buf_start_offset; 69 | 70 | /* Check if we have enough samples available 71 | * TODO: we should check if there are enough samples in the following buffer, too. 72 | */ 73 | if (buf_offset + nr_coeffs > sb_active->nr_samples && sb_next == NULL) { 74 | ret = A_E_DONE; 75 | goto done; 76 | } 77 | 78 | /* Walk the number of samples in the current buffer up to the filter size */ 79 | do { 80 | /* Figure out how many samples to pull out */ 81 | size_t nr_samples_in = cur_buf->nr_samples - buf_offset, 82 | start_coeff = nr_coeffs - coeffs_remain; 83 | 84 | /* Snap to either the number of coefficients in the FIR or the number of remaining 85 | * coefficients, whichever is smaller. 86 | */ 87 | nr_samples_in = BL_MIN2(nr_samples_in, coeffs_remain); 88 | 89 | for (size_t i = 0; i < nr_samples_in; i++) { 90 | #ifdef _TSL_DEBUG 91 | TSL_BUG_ON(i + start_coeff >= nr_coeffs); 92 | TSL_BUG_ON(i + buf_offset >= cur_buf->nr_samples); 93 | #endif /* defined(_TSL_DEBUG) */ 94 | 95 | int32_t sample = ((int16_t *)cur_buf->data_buf)[buf_offset + i], 96 | coeff = coeffs[start_coeff + i]; 97 | 98 | acc_res += sample * coeff; 99 | } 100 | 101 | /* If we iterate through, we'll start at the beginning of the next buffer */ 102 | buf_offset = 0; 103 | coeffs_remain -= nr_samples_in; 104 | #ifdef _TSL_DEBUG 105 | /* Check if we have coefficients remaining to process */ 106 | TSL_BUG_ON(cur_buf == sb_next && coeffs_remain != 0); 107 | #endif 108 | cur_buf = sb_next; 109 | } while (coeffs_remain != 0); 110 | 111 | /* Return the computed sample, in Q.15 (currently in Q.30 due to the prior multiplications) */ 112 | *psample = round_q30_q15(acc_res); 113 | 114 | done: 115 | return ret; 116 | } 117 | 118 | -------------------------------------------------------------------------------- /filter/direct_fir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct sample_buf; 8 | 9 | struct direct_fir { 10 | /** 11 | * Real coefficients. Must be aligned to int32_t's natural alignment. 12 | */ 13 | int16_t *fir_real_coeff; 14 | 15 | /** 16 | * Imaginary coefficients. Must be aligned to int32_t's natural alignment. 17 | */ 18 | int16_t *fir_imag_coeff; 19 | 20 | /** 21 | * The number of coefficients in this FIR 22 | */ 23 | size_t nr_coeffs; 24 | 25 | /** 26 | * Decimation factor. Determines how we walk through the sample buffer. 27 | */ 28 | unsigned decimate_factor; 29 | 30 | /** 31 | * The offset of the next sample to be processed, in sb_active 32 | */ 33 | unsigned sample_offset; 34 | 35 | /** 36 | * The total, pre-decimation number of samples available in the input buffer. 37 | */ 38 | size_t nr_samples; 39 | 40 | /** 41 | * Active sample buffer being processed 42 | */ 43 | struct sample_buf *sb_active; 44 | 45 | /** 46 | * Next sample buffer to be processed, if it's available 47 | */ 48 | struct sample_buf *sb_next; 49 | 50 | /** 51 | * The real part of the Q.15 rotation phase increment 52 | */ 53 | int16_t rot_phase_incr_re; 54 | 55 | /** 56 | * The imaginary part of the Q.15 rotaiton phase increment 57 | */ 58 | int16_t rot_phase_incr_im; 59 | 60 | /** 61 | * The real part of the Q.15 rotation factor to be applied to each sample 62 | */ 63 | int16_t rot_phase_re; 64 | 65 | /** 66 | * The imaginary part of the Q.15 rotation factor to be applied to each sample 67 | */ 68 | int16_t rot_phase_im; 69 | 70 | /** 71 | * The rotation counter. 72 | */ 73 | unsigned rot_counter; 74 | }; 75 | 76 | /** 77 | * Create a direct coefficient FIR, in Q.15. This function allocates memory. 78 | * 79 | * \param fir The FIR object. Pass a chunk of memory by reference. 80 | * \param nr_coeffs The number of coefficients in the FIR 81 | * \param fir_real_coeff The real coefficients for the FIR 82 | * \param fir_imag_coeff The imaginary coefficients for the FIR 83 | * \param decimation_factor The decimation factor to apply 84 | * \param derotate Set to `true` if you wish to apply a derotator. Useful if the filter will 85 | * shift a signal to baseband. 86 | * \param sampling_rate The sampling rate. Used to manage the phase derotator. Ignored if not 87 | * using the phase derotator. 88 | * \param freq_shift If the filter will be downshifting another signal to baseband, how much 89 | * is this shift by, in Hz. Ignored if not using the phase derotator. 90 | * 91 | * \return A_OK on success, an error code otherwise 92 | */ 93 | aresult_t direct_fir_init(struct direct_fir *fir, size_t nr_coeffs, const int16_t *fir_real_coeff, 94 | const int16_t *fir_imag_coeff, unsigned decimation_factor, 95 | bool derotate, uint32_t sampling_rate, int32_t freq_shift); 96 | 97 | /** 98 | * Cleanup memory and release sample buffers for the FIR 99 | * 100 | * \param fir The FIR to cleanup. 101 | * 102 | * \return A_OK on success, an error code otherwise 103 | */ 104 | aresult_t direct_fir_cleanup(struct direct_fir *fir); 105 | 106 | /** 107 | * Push an updated sample buffer. 108 | * 109 | * \param fir The direct FIR to process 110 | * \param buf The buffer to push onto the queue 111 | * 112 | * \return A_OK on success, an error code otherwise 113 | */ 114 | aresult_t direct_fir_push_sample_buf(struct direct_fir *fir, struct sample_buf *buf); 115 | 116 | /** 117 | * Apply the FIR to as many samples as possible, constrained by: 118 | * 1. The number of samples available (must be at least the FIR width of samples) 119 | * 2. The number of output buffer samples 120 | * 121 | * \param fir The FIR to apply 122 | * \param out_buf The buffer to write the output samples to 123 | * \param nr_out_samples The maximum number of output samples out_buf can hold 124 | * \param nr_output_samples_generated The number of valid samples in out_buf 125 | * 126 | * \return A_OK on success, an error code otherwise 127 | */ 128 | aresult_t direct_fir_process(struct direct_fir *fir, int16_t *out_buf, size_t nr_out_samples, 129 | size_t *nr_output_samples_generated); 130 | 131 | /** 132 | * Determine whether or not an additional sample buffer can be passed to the FIR, for further 133 | * processing. 134 | * 135 | * \param fir The FIR in question 136 | * \param pfull Whether or not the FIR has space for an extra sample buffer, returned by reference. 137 | * 138 | * \return A_OK on success, an error code otherwise. 139 | */ 140 | aresult_t direct_fir_full(struct direct_fir *fir, bool *pfull); 141 | 142 | /** 143 | * Determine whether or not there are enough samples available to produce at least one filtered, 144 | * decimated sample. 145 | * 146 | * \param fir The FIR in question 147 | * \param pfull Whether or not the FIR has enough samples available to produce one filtered sample. 148 | * \param pest_count The estimated count of samples that could be produced. 149 | */ 150 | aresult_t direct_fir_can_process(struct direct_fir *fir, bool *pcan_process, size_t *pest_count); 151 | -------------------------------------------------------------------------------- /pager/test/test_mueller_muller.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | static 14 | const int16_t *samples = NULL; 15 | 16 | static 17 | size_t nr_samples = 0; 18 | 19 | static 20 | aresult_t test_mueller_muller_setup(void) 21 | { 22 | aresult_t ret = A_OK; 23 | 24 | const char *test_data_dir = ".", 25 | *env_dir = NULL; 26 | char *file_path = NULL; 27 | size_t file_len = 0; 28 | 29 | FILE *fp = NULL; 30 | 31 | if (NULL != (env_dir = getenv("PAGER_TEST_DATA_DIR"))) { 32 | test_data_dir = env_dir; 33 | } 34 | 35 | TEST_INF("Retrieving MM test data from directory '%s'", test_data_dir); 36 | 37 | TSL_BUG_IF_FAILED(tasprintf(&file_path, "%s/%s", test_data_dir, "pocsag_hospital_25khz_long.raw")); 38 | 39 | if (NULL == (fp = fopen(file_path, "r"))) { 40 | TEST_ERR("Failed to open file %s, aborting.", file_path); 41 | ret = A_E_INVAL; 42 | goto done; 43 | } 44 | 45 | fseek(fp, 0, SEEK_END); 46 | file_len = ftell(fp); 47 | fseek(fp, 0, SEEK_SET); 48 | 49 | if (FAILED(ret = TCALLOC((void **)&samples, 1, file_len))) { 50 | goto done; 51 | } 52 | 53 | if (1 != fread((int16_t *)samples, file_len, 1, fp)) { 54 | TEST_ERR("Failed to read %zu bytes from file %s, aborting.", file_len, file_path); 55 | ret = A_E_INVAL; 56 | goto done; 57 | } 58 | 59 | nr_samples = file_len / sizeof(int16_t); 60 | 61 | done: 62 | if (FAILED(ret)) { 63 | TEST_ERR("Make sure PAGER_TEST_DATA_DIR is set to point to where we can find pocsag_single_burst_25khz.raw"); 64 | if (NULL != samples) { 65 | TFREE(samples); 66 | } 67 | } 68 | 69 | if (NULL != fp) { 70 | fclose(fp); 71 | fp = NULL; 72 | } 73 | 74 | if (NULL != file_path) { 75 | TFREE(file_path); 76 | } 77 | 78 | return ret; 79 | } 80 | 81 | static 82 | aresult_t test_mueller_muller_cleanup(void) 83 | { 84 | aresult_t ret = A_OK; 85 | 86 | if (NULL != samples) { 87 | TFREE(samples); 88 | } 89 | 90 | nr_samples = 0; 91 | 92 | return ret; 93 | } 94 | 95 | #define TEST_KW 0.0001 96 | #define TEST_KM 0.000004 97 | #define TEST_SAMPLES_PER_BIT (25000.0f/1200.0f) 98 | #define TEST_ERROR_MARGIN 0.05f 99 | #define TEST_MAX_NR_DECISIONS (1 << 8) 100 | 101 | #define POCSAG_SYNC_CODEWORD 0x7cd215d8ul 102 | 103 | TEST_DECLARE_UNIT(test_sample_process, mueller_muller) 104 | { 105 | int16_t decisions[2 * TEST_MAX_NR_DECISIONS]; 106 | size_t nr_decisions = 0, 107 | total_decisions = 0, 108 | samples_off = 0, 109 | nr_syncs = 0, 110 | last_sync = 0; 111 | struct mueller_muller mm; 112 | const size_t samples_per_iter = TEST_MAX_NR_DECISIONS * TEST_SAMPLES_PER_BIT; 113 | uint32_t shr = 0; 114 | 115 | /* 116 | * Use the samples that were read in to test the Mueller-Muller clock recovery 117 | */ 118 | 119 | TEST_ASSERT_OK(mm_init(&mm, TEST_KW, TEST_KM, TEST_SAMPLES_PER_BIT, 120 | TEST_SAMPLES_PER_BIT - TEST_ERROR_MARGIN, TEST_SAMPLES_PER_BIT + TEST_ERROR_MARGIN)); 121 | 122 | while (samples_off < nr_samples) { 123 | size_t iter_samples = samples_per_iter > nr_samples - samples_off ? nr_samples - samples_off : samples_per_iter; 124 | TEST_ASSERT_OK(mm_process(&mm, samples + samples_off, iter_samples, decisions, 2 * TEST_MAX_NR_DECISIONS, &nr_decisions)); 125 | 126 | samples_off += iter_samples; 127 | 128 | //TEST_INF("Iteration complete, got %zu samples, next = %f", nr_decisions, (double)mm.next_offset); 129 | 130 | for (size_t i = 0; i < nr_decisions; i++) { 131 | shr <<= 1; 132 | shr |= decisions[i] > 0 ? 0 : 1; 133 | if (__builtin_popcount(POCSAG_SYNC_CODEWORD ^ shr) < 4) { 134 | TEST_INF("Found sync word (decision %zu, delta %zu, block_offs %zu)!", total_decisions + i, total_decisions + i - last_sync, i); 135 | nr_syncs++; 136 | last_sync = total_decisions + i; 137 | } else if (0 != last_sync && total_decisions + i - last_sync == 544) { 138 | TEST_INF("Was expecting a sync word, got %08x", shr); 139 | } 140 | } 141 | 142 | total_decisions += nr_decisions; 143 | } 144 | 145 | TEST_INF("Total sync words: %zu", nr_syncs); 146 | TEST_INF("Total decisions: %zu", total_decisions); 147 | TEST_INF("Total samples: %zu", samples_off); 148 | 149 | TEST_ASSERT_EQUALS(nr_syncs, 9); 150 | 151 | return A_OK; 152 | } 153 | 154 | TEST_DECLARE_UNIT(test_smoke, mueller_muller) 155 | { 156 | struct mueller_muller mm; 157 | 158 | /* Simple smoke test to test the init function */ 159 | TEST_ASSERT_OK(mm_init(&mm, TEST_KW, TEST_KM, TEST_SAMPLES_PER_BIT, 160 | TEST_SAMPLES_PER_BIT - TEST_ERROR_MARGIN, TEST_SAMPLES_PER_BIT + TEST_ERROR_MARGIN)); 161 | 162 | 163 | return A_OK; 164 | } 165 | 166 | TEST_DECLARE_SUITE(mueller_muller, test_mueller_muller_cleanup, test_mueller_muller_setup, NULL, NULL); 167 | 168 | -------------------------------------------------------------------------------- /etc/pocsag_narrow.json: -------------------------------------------------------------------------------- 1 | {"lpfTaps":[0.00040164087274264967,0.00040533306666714003,0.00041044629537624155,0.00041700995576419785,0.00042505227422939547,0.000434600268791661,0.00044567971226783162,0.00045831509654948856,0.00047252959802569463,0.00048834504419246084,0.00050578188148952966,0.00052485914440384275,0.00054559442587781971,0.00056800384905929019,0.00059210204042855173,0.00061790210433668321,0.000645415598987791,0.000674652513896406,0.00070562124884975983,0.00073832859440312222,0.00077277971393480019,0.00080897812728580529,0.00084692569600755226,0.00088662261023927155,0.00092806737723514772,0.00097125681155943618,0.0010161860269661013,0.0010628484299777146,0.0011112357151765884,0.0011613378622192926,0.0012131431345838806,0.0012666380800573195,0.0013218075329687556,0.0013786346181723827,0.0014371007567818184,0.0014971856736560086,0.001558867406634791,0.001622122317520388,0.0016869251047991775,0.0017532488180962476,0.0018210648743533309,0.0018903430757188575,0.0019610516291370043,0.0020331571676207513,0.0021066247731921285,0.0021814180014710052,0.0022574989078919631,0.0023348280755270067,0.002413364644490084,0.0024930663428976768,0.0025738895193579375,0.0026557891769592255,0.002738719008727168,0.0028226314345177672,0.0029074776393124661,0.002993207612879517,0.0030797701907644341,0.0031671130965708868,0.0032551829854918306,0.003343925489049368,0.0034332852610003697,0.0035232060243636162,0.0036136306195229018,0.003704501053359338,0.0037957585493648845,0.0038873435986880112,0.0039791960120613224,0.0040712549725599057,0.0041634590891382519,0.0042557464508925953,0.0043480546819947511,0.0044403209972426038,0.004532482258171793,0.0046244750296722944,0.0047162356370531082,0.0048077002234975987,0.004898804807851577,0.0049894853426857427,0.005079677772573731,0.00516931809252668,0.0052583424065250238,0.005346686986087942,0.0054342883288208384,0.005521083216881135,0.0056070087753026755,0.0056920025301190761,0.0057760024662265163,0.0058589470849266549,0.0059407754610906168,0.0060214272998852869,0.0061008429930036138,0.0061789636743409624,0.0062557312750602025,0.0063310885779886755,0.0064049792712909,0.0064773480013615428,0.0065481404248839683,0.0066173032600004454,0.006684784336541052,0.0067505326452591665,0.0068144983860225251,0.0068766330149097775,0.0069368892901636335,0.0069952213169528835,0.007051584590896675,0.0071059360403058327,0.0071582340670972,0.0072084385863384025,0.0072565110643818551,0.0073024145555482031,0.0073461137373210077,0.0073875749440158889,0.007426766198889042,0.0074636572446515632,0.0074982195723577337,0.0075304264486370769,0.0075602529412417182,0.0075876759428823611,0.0076126741933279273,0.0076352282997457855,0.0076553207552612444,0.0076729359557169271,0.0076880602146144842,0.0077006817762229731,0.0077107908268402321,0.0077183795041954207,0.0077234419049828874,0.0077259740905195077,0.0077259740905195077,0.0077234419049828874,0.0077183795041954207,0.0077107908268402321,0.0077006817762229731,0.0076880602146144842,0.0076729359557169271,0.0076553207552612444,0.0076352282997457855,0.0076126741933279273,0.0075876759428823611,0.0075602529412417182,0.0075304264486370769,0.0074982195723577337,0.0074636572446515632,0.007426766198889042,0.0073875749440158889,0.0073461137373210077,0.0073024145555482031,0.0072565110643818551,0.0072084385863384025,0.0071582340670972,0.0071059360403058327,0.007051584590896675,0.0069952213169528835,0.0069368892901636335,0.0068766330149097775,0.0068144983860225251,0.0067505326452591665,0.006684784336541052,0.0066173032600004454,0.0065481404248839683,0.0064773480013615428,0.0064049792712909,0.0063310885779886755,0.0062557312750602025,0.0061789636743409624,0.0061008429930036138,0.0060214272998852869,0.0059407754610906168,0.0058589470849266549,0.0057760024662265163,0.0056920025301190761,0.0056070087753026755,0.005521083216881135,0.0054342883288208384,0.005346686986087942,0.0052583424065250238,0.00516931809252668,0.005079677772573731,0.0049894853426857427,0.004898804807851577,0.0048077002234975987,0.0047162356370531082,0.0046244750296722944,0.004532482258171793,0.0044403209972426038,0.0043480546819947511,0.0042557464508925953,0.0041634590891382519,0.0040712549725599057,0.0039791960120613224,0.0038873435986880112,0.0037957585493648845,0.003704501053359338,0.0036136306195229018,0.0035232060243636162,0.0034332852610003697,0.003343925489049368,0.0032551829854918306,0.0031671130965708868,0.0030797701907644341,0.002993207612879517,0.0029074776393124661,0.0028226314345177672,0.002738719008727168,0.0026557891769592255,0.0025738895193579375,0.0024930663428976768,0.002413364644490084,0.0023348280755270067,0.0022574989078919631,0.0021814180014710052,0.0021066247731921285,0.0020331571676207513,0.0019610516291370043,0.0018903430757188575,0.0018210648743533309,0.0017532488180962476,0.0016869251047991775,0.001622122317520388,0.001558867406634791,0.0014971856736560086,0.0014371007567818184,0.0013786346181723827,0.0013218075329687556,0.0012666380800573195,0.0012131431345838806,0.0011613378622192926,0.0011112357151765884,0.0010628484299777146,0.0010161860269661013,0.00097125681155943618,0.00092806737723514772,0.00088662261023927155,0.00084692569600755226,0.00080897812728580529,0.00077277971393480019,0.00073832859440312222,0.00070562124884975983,0.000674652513896406,0.000645415598987791,0.00061790210433668321,0.00059210204042855173,0.00056800384905929019,0.00054559442587781971,0.00052485914440384275,0.00050578188148952966,0.00048834504419246084,0.00047252959802569463,0.00045831509654948856,0.00044567971226783162,0.000434600268791661,0.00042505227422939547,0.00041700995576419785,0.00041044629537624155,0.00040533306666714003,0.00040164087274264967],"parameters":{"sampleRate":2.5E+6,"window":"hamming","cut_freq":4800}} -------------------------------------------------------------------------------- /etc/pocsag_1200khz_fs.json: -------------------------------------------------------------------------------- 1 | {"lpfTaps":[-0.000161491064335437,-0.00015859706981641362,-0.00015602397970066885,-0.00015373227463354573,-0.00015167794078221788,-0.00014981251749814732,-0.00014808315905925315,-0.00014643271046162171,-0.0001447997971916659,-0.00014311892887067229,-0.00014132061662475149,-0.00013933150399444684,-0.00013707451115975444,-0.00013446899221818728,-0.00013143090521585364,-0.00012787299459446461,-0.00012370498568078732,-0.00011883379080947123,-0.00011316372663546159,-0.00010659674215848901,-9.9032656949486371E-5,-9.03694090373123E-5,-8.0503311883970267E-5,-6.9329319847658707E-5,-5.6741301505593513E-5,-4.26323201826483E-5,-2.6894921007583736E-5,-9.4214237960049844E-6,9.8957789616730333E-6,3.1163919748063578E-5,5.4489550060460457E-5,7.9978231619611334E-5,0.000107734225532237,0.00013786018020524519,0.00017045681881881961,0.00020562262717276831,0.00024345354272553178,0.00028404264564820285,0.00032747985271663183,0.00037385161486331778,0.00042324061920716538,0.00047572549637346173,0.000531380533908474,0.000590275396582999,0.0006524748543669743,0.00071803851884290467,0.0007870205888094536,0.00085946960580804866,0.00093542822028486942,0.0010149329690781312,0.0010980140648961933,0.0011846951984257981,0.0012749933536817168,0.0013689186371793128,0.0014664741214801265,0.0015676557036275755,0.001672451978955372,0.0017808441307153283,0.0018928058359339881,0.0020083031878690366,0.0021272946353968077,0.002249730939621573,0.00237555514795569,0.0025047025858772375,0.002637100866528672,0.0027726699182762191,0.0029113220303054873,0.0030529619162841357,0.0031974867960775067,0.0033447864954580576,0.0034947435637043161,0.0036472334089400578,0.0038021244510195512,0.0039592782917202158,0.0041185499019599312,0.004279787825712685,0.0044428344002534065,0.0046075259923206513,0.004773693249744664,0.004941161368048029,0.005109750371487096,0.0052792754079643645,0.00544954705720542,0.0056203716515588822,0.00579155160874399,0.0059628857758384987,0.0061341697837689925,0.0063051964115371246,0.0064757559593884807,0.0066456366301057609,0.00681462491758518,0.0069825060018339153,0.0071490641495078836,0.0073140831190922637,0.0074773465698129236,0.0076386384733546859,0.0077977435274525046,0.0079544475704139174,0.0081085379956261326,0.0082598041650979691,0.0084080378210865236,0.0085530334948602,0.0086945889116539637,0.0088325053908793142,0.0089665882406602775,0.0090966471457782187,0.0092224965481215049,0.0093439560187522364,0.0094608506207201019,0.0095730112617738155,0.0096802750361429327,0.0097824855545874268,0.00987949326193889,0.009971155741385733,0.010057338004785215,0.01013791276831722,0.010212760712828822,0.010281770728254121,0.010344840141531107,0.010401874927475893,0.010452789902114696,0.010497508898015184,0.010535964921201303,0.010568100289279151,0.010593866750445937,0.01061322558309935,0.010626147675810684,0.010632613587471597,0.010632613587471597,0.010626147675810684,0.01061322558309935,0.010593866750445937,0.010568100289279151,0.010535964921201303,0.010497508898015184,0.010452789902114696,0.010401874927475893,0.010344840141531107,0.010281770728254121,0.010212760712828822,0.01013791276831722,0.010057338004785215,0.009971155741385733,0.00987949326193889,0.0097824855545874268,0.0096802750361429327,0.0095730112617738155,0.0094608506207201019,0.0093439560187522364,0.0092224965481215049,0.0090966471457782187,0.0089665882406602775,0.0088325053908793142,0.0086945889116539637,0.0085530334948602,0.0084080378210865236,0.0082598041650979691,0.0081085379956261326,0.0079544475704139174,0.0077977435274525046,0.0076386384733546859,0.0074773465698129236,0.0073140831190922637,0.0071490641495078836,0.0069825060018339153,0.00681462491758518,0.0066456366301057609,0.0064757559593884807,0.0063051964115371246,0.0061341697837689925,0.0059628857758384987,0.00579155160874399,0.0056203716515588822,0.00544954705720542,0.0052792754079643645,0.005109750371487096,0.004941161368048029,0.004773693249744664,0.0046075259923206513,0.0044428344002534065,0.004279787825712685,0.0041185499019599312,0.0039592782917202158,0.0038021244510195512,0.0036472334089400578,0.0034947435637043161,0.0033447864954580576,0.0031974867960775067,0.0030529619162841357,0.0029113220303054873,0.0027726699182762191,0.002637100866528672,0.0025047025858772375,0.00237555514795569,0.002249730939621573,0.0021272946353968077,0.0020083031878690366,0.0018928058359339881,0.0017808441307153283,0.001672451978955372,0.0015676557036275755,0.0014664741214801265,0.0013689186371793128,0.0012749933536817168,0.0011846951984257981,0.0010980140648961933,0.0010149329690781312,0.00093542822028486942,0.00085946960580804866,0.0007870205888094536,0.00071803851884290467,0.0006524748543669743,0.000590275396582999,0.000531380533908474,0.00047572549637346173,0.00042324061920716538,0.00037385161486331778,0.00032747985271663183,0.00028404264564820285,0.00024345354272553178,0.00020562262717276831,0.00017045681881881961,0.00013786018020524519,0.000107734225532237,7.9978231619611334E-5,5.4489550060460457E-5,3.1163919748063578E-5,9.8957789616730333E-6,-9.4214237960049844E-6,-2.6894921007583736E-5,-4.26323201826483E-5,-5.6741301505593513E-5,-6.9329319847658707E-5,-8.0503311883970267E-5,-9.03694090373123E-5,-9.9032656949486371E-5,-0.00010659674215848901,-0.00011316372663546159,-0.00011883379080947123,-0.00012370498568078732,-0.00012787299459446461,-0.00013143090521585364,-0.00013446899221818728,-0.00013707451115975444,-0.00013933150399444684,-0.00014132061662475149,-0.00014311892887067229,-0.0001447997971916659,-0.00014643271046162171,-0.00014808315905925315,-0.00014981251749814732,-0.00015167794078221788,-0.00015373227463354573,-0.00015602397970066885,-0.00015859706981641362,-0.000161491064335437],"parameters":{"sampleRate":1.2E+6,"window":"hamming","cut_freq":9000}} -------------------------------------------------------------------------------- /multifm/multifm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * multifm.c - A channelizer for extracting multiple narrowband channels 3 | * from a wideband captured chunk of the spectrum. 4 | * 5 | * Copyright (c)2017 Phil Vachon 6 | * 7 | * This file is a part of The Standard Library (TSL) 8 | * 9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software 21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22 | */ 23 | 24 | #include 25 | 26 | #ifdef HAVE_RTLSDR 27 | #include 28 | #include 29 | #endif /* HAVE_RTLSDR */ 30 | 31 | #ifdef HAVE_DESPAIRSPY 32 | #include 33 | #endif 34 | 35 | #ifdef HAVE_UHD 36 | #include 37 | #endif 38 | 39 | #include 40 | 41 | #include 42 | 43 | #include 44 | 45 | #include 46 | 47 | #include 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | 54 | #include 55 | #include 56 | 57 | #ifdef HAVE_RTLSDR 58 | static 59 | void _do_dump_rtl_sdr_devices(void) 60 | { 61 | uint32_t nr_devs = rtlsdr_get_device_count(); 62 | 63 | if (0 == nr_devs) { 64 | MFM_MSG(SEV_WARNING, "NO-DEVS-FOUND", "No RTL-SDR devices found."); 65 | goto done; 66 | } 67 | 68 | MFM_MSG(SEV_INFO, "DEVS-FOUND", "Found %u RTL-SDR devices", nr_devs + 1); 69 | 70 | for (uint32_t i = 0; i < nr_devs; i++) { 71 | const char *name = rtlsdr_get_device_name(i); 72 | fprintf(stderr, "%2u: %s\n", i, name); 73 | } 74 | 75 | done: 76 | return; 77 | } 78 | #endif 79 | 80 | static 81 | void _usage(const char *name) 82 | { 83 | fprintf(stderr, "usage: %s [Config File 1]{, Config File 2, ...} | %s -h\n", name, name); 84 | #ifdef HAVE_RTLSDR 85 | _do_dump_rtl_sdr_devices(); 86 | #endif 87 | } 88 | 89 | int main(int argc, const char *argv[]) 90 | { 91 | int ret = EXIT_FAILURE; 92 | struct config *cfg CAL_CLEANUP(config_delete) = NULL; 93 | struct config device = CONFIG_INIT_EMPTY; 94 | struct receiver *rx_thr = NULL; 95 | const char *dev_type = NULL; 96 | 97 | if (argc < 2) { 98 | _usage(argv[0]); 99 | goto done; 100 | } 101 | 102 | /* Parse and load the configurations from the command line */ 103 | TSL_BUG_IF_FAILED(config_new(&cfg)); 104 | 105 | for (int i = 1; i < argc; i++) { 106 | if (FAILED(config_add(cfg, argv[i]))) { 107 | MFM_MSG(SEV_FATAL, "MALFORMED-CONFIG", "Configuration file [%s] is malformed.", argv[i]); 108 | goto done; 109 | } 110 | DIAG("Added configuration file '%s'", argv[i]); 111 | } 112 | 113 | /* Initialize the app framework */ 114 | TSL_BUG_IF_FAILED(app_init("multifm", cfg)); 115 | TSL_BUG_IF_FAILED(app_sigint_catch(NULL)); 116 | 117 | /* Figure out what kind of device we should initialize */ 118 | if (FAILED(config_get(cfg, &device, "device"))) { 119 | MFM_MSG(SEV_FATAL, "MALFORMED-CONFIG", "Configuration is missing 'device' stanza. Aborting."); 120 | goto done; 121 | } 122 | 123 | if (FAILED(config_get_string(&device, &dev_type, "type"))) { 124 | MFM_MSG(SEV_FATAL, "MALFORMED-CONFIG", "The 'device' stanza is missing a 'type' specification. Aborting."); 125 | goto done; 126 | } 127 | 128 | /* Prepare the RTL-SDR thread and demod threads */ 129 | if (!strncmp(dev_type, "rtlsdr", 6)) { 130 | #ifdef HAVE_RTLSDR 131 | TSL_BUG_IF_FAILED(rtl_sdr_worker_thread_new(&rx_thr, cfg)); 132 | #else 133 | MFM_MSG(SEV_FATAL, "RTLSDR-NOT-SUPPORTED", "RTL-SDR devices are not supported by this build."); 134 | goto done; 135 | #endif 136 | } else if (!strncmp(dev_type, "airspy", 6)) { 137 | #ifdef HAVE_DESPAIRSPY 138 | TSL_BUG_IF_FAILED(airspy_worker_thread_new(&rx_thr, cfg)); 139 | #else 140 | MFM_MSG(SEV_FATAL, "AIRSPY-NOT-SUPPORTED", "Airspy devices are not supported by this build."); 141 | goto done; 142 | #endif 143 | } else if (!strncmp(dev_type, "usrp", 4)) { 144 | #ifdef HAVE_UHD 145 | TSL_BUG_IF_FAILED(uhd_worker_thread_new(&rx_thr, cfg)); 146 | #else 147 | MFM_MSG(SEV_FATAL, "USRP-NOT-SUPPORTED", "USRP devices are not supported by this build."); 148 | goto done; 149 | #endif 150 | } else if (!strncmp(dev_type, "file", 4)) { 151 | /* Source samples from a binary file o' samples */ 152 | TSL_BUG_IF_FAILED(file_worker_thread_new(&rx_thr, cfg)); 153 | } else { 154 | MFM_MSG(SEV_FATAL, "UNKNOWN-DEV-TYPE", "Unknown device type: '%s'", dev_type); 155 | goto done; 156 | } 157 | 158 | TSL_BUG_IF_FAILED(receiver_set_mute(rx_thr, false)); 159 | 160 | MFM_MSG(SEV_INFO, "CAPTURING", "Starting capture and demodulation process."); 161 | TSL_BUG_IF_FAILED(receiver_start(rx_thr)); 162 | 163 | while (app_running()) { 164 | sleep(1); 165 | } 166 | 167 | DIAG("Terminating."); 168 | 169 | ret = EXIT_SUCCESS; 170 | done: 171 | receiver_cleanup(&rx_thr); 172 | return ret; 173 | } 174 | 175 | -------------------------------------------------------------------------------- /pager/test/test_pager_pocsag.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | static 15 | const int16_t *samples = NULL; 16 | 17 | static 18 | size_t nr_samples = 0; 19 | 20 | //#define TEST_FILE_NAME "pocsag_hospital_38400_long.raw" 21 | #define TEST_FILE_NAME "pocsag_38400_test_512bps_hackrf.raw" 22 | 23 | static 24 | aresult_t test_pager_pocsag_setup(void) 25 | { 26 | aresult_t ret = A_OK; 27 | 28 | const char *test_data_dir = ".", 29 | *env_dir = NULL; 30 | char *file_path = NULL; 31 | size_t file_len = 0; 32 | 33 | FILE *fp = NULL; 34 | 35 | if (NULL != (env_dir = getenv("PAGER_TEST_DATA_DIR"))) { 36 | test_data_dir = env_dir; 37 | } 38 | 39 | TEST_INF("Retrieving POCSAG test data from directory '%s'", test_data_dir); 40 | 41 | TSL_BUG_IF_FAILED(tasprintf(&file_path, "%s/%s", test_data_dir, TEST_FILE_NAME)); 42 | 43 | if (NULL == (fp = fopen(file_path, "r"))) { 44 | TEST_ERR("Failed to open file %s, aborting.", file_path); 45 | ret = A_E_INVAL; 46 | goto done; 47 | } 48 | 49 | fseek(fp, 0, SEEK_END); 50 | file_len = ftell(fp); 51 | fseek(fp, 0, SEEK_SET); 52 | 53 | if (FAILED(ret = TCALLOC((void **)&samples, 1, file_len))) { 54 | goto done; 55 | } 56 | 57 | if (1 != fread((int16_t *)samples, file_len, 1, fp)) { 58 | TEST_ERR("Failed to read %zu bytes from file %s, aborting.", file_len, file_path); 59 | ret = A_E_INVAL; 60 | goto done; 61 | } 62 | 63 | nr_samples = file_len / sizeof(int16_t); 64 | 65 | done: 66 | if (FAILED(ret)) { 67 | TEST_ERR("Make sure PAGER_TEST_DATA_DIR is set to point to where we can find " TEST_FILE_NAME); 68 | if (NULL != samples) { 69 | TFREE(samples); 70 | } 71 | } 72 | 73 | if (NULL != fp) { 74 | fclose(fp); 75 | fp = NULL; 76 | } 77 | 78 | if (NULL != file_path) { 79 | TFREE(file_path); 80 | } 81 | 82 | return ret; 83 | } 84 | 85 | static 86 | aresult_t test_pager_pocsag_cleanup(void) 87 | { 88 | aresult_t ret = A_OK; 89 | 90 | if (NULL != samples) { 91 | TFREE(samples); 92 | } 93 | 94 | nr_samples = 0; 95 | 96 | return ret; 97 | } 98 | 99 | static const 100 | uint32_t _unpack_testi_7f[] = { 101 | 0x1FFFFE, 102 | 0x1FFFFE, 103 | 0x1FFFFE, 104 | 0x1FFFFE, 105 | 106 | 0x1FFFFE, 107 | 0x1FFFFE, 108 | 0x1FFFFE, 109 | 0x1FFFFE, 110 | 111 | 0x1FFFFE, 112 | 0x1FFFFE, 113 | 0x1FFFFE, 114 | 0x1FFFFE, 115 | 116 | 0x1FFFFE, 117 | 0x1FFFFE, 118 | }; 119 | 120 | static 121 | aresult_t _pager_pocsag_unpack_batch_ascii(const uint32_t *batch_words, size_t batch_nr_words, 122 | uint8_t *val, size_t max_val, size_t *pnr_val) 123 | { 124 | aresult_t ret = A_OK; 125 | 126 | size_t byte_off = 0; 127 | size_t nr_bits = 0; 128 | uint32_t acc = 0; 129 | 130 | for (size_t i = 0; i < batch_nr_words; i++) { 131 | acc |= ((batch_words[i] >> 1) & 0xfffffu) << nr_bits; 132 | nr_bits += 20; 133 | while (nr_bits >= 7) { 134 | val[byte_off++] = acc & 0x7f; 135 | if (max_val <= byte_off) { 136 | ret = A_E_INVAL; 137 | goto done; 138 | } 139 | nr_bits -= 7; 140 | } 141 | } 142 | 143 | TEST_INF("There are %zu bits left", nr_bits); 144 | 145 | *pnr_val = byte_off; 146 | 147 | done: 148 | return ret; 149 | } 150 | 151 | TEST_DECLARE_UNIT(test_7b_unpack, pocsag) 152 | { 153 | uint8_t unpacked[512]; 154 | size_t nr_unpacked; 155 | 156 | TEST_ASSERT_OK(_pager_pocsag_unpack_batch_ascii(_unpack_testi_7f, sizeof(_unpack_testi_7f)/sizeof(uint32_t), 157 | unpacked, sizeof(unpacked), &nr_unpacked)); 158 | TEST_ASSERT_EQUALS(nr_unpacked, 40); 159 | for (size_t i = 0; i < nr_unpacked; i++) { 160 | TEST_ASSERT_EQUALS(unpacked[i], 0x7f); 161 | } 162 | 163 | return A_OK; 164 | } 165 | 166 | static 167 | aresult_t _test_pocsag_on_message_simple_cb( 168 | struct pager_pocsag *pocsag, 169 | uint16_t baud_rate, 170 | uint32_t capcode, 171 | const char *data, 172 | size_t data_len, 173 | uint8_t function) 174 | { 175 | fprintf(stderr, "POCSAG%u: ALN(%u): [%8u]: %s\n", (unsigned)baud_rate, (unsigned)function, capcode, data); 176 | hexdump_dump_hex(data, data_len); 177 | return A_OK; 178 | } 179 | 180 | static 181 | aresult_t _test_pocsag_on_num_message_simple_cb( 182 | struct pager_pocsag *pocsag, 183 | uint16_t baud_rate, 184 | uint32_t capcode, 185 | const char *data, 186 | size_t data_len, 187 | uint8_t function) 188 | { 189 | fprintf(stderr, "POCSAG%u: NUM(%u): [%8u]: %s\n", (unsigned)baud_rate, function, capcode, data); 190 | return A_OK; 191 | } 192 | 193 | TEST_DECLARE_UNIT(test_one_shot, pocsag) 194 | { 195 | struct pager_pocsag *pocsag = NULL; 196 | 197 | TEST_ASSERT_OK(pager_pocsag_new(&pocsag, 929612500ul, _test_pocsag_on_num_message_simple_cb, _test_pocsag_on_message_simple_cb, false)); 198 | TEST_ASSERT_OK(pager_pocsag_on_pcm(pocsag, samples, nr_samples)); 199 | TEST_ASSERT_OK(pager_pocsag_delete(&pocsag)); 200 | 201 | return A_OK; 202 | } 203 | 204 | TEST_DECLARE_UNIT(test_smoke, pocsag) 205 | { 206 | struct pager_pocsag *pocsag = NULL; 207 | 208 | TEST_ASSERT_OK(pager_pocsag_new(&pocsag, 929612500ul, _test_pocsag_on_num_message_simple_cb, _test_pocsag_on_message_simple_cb, false)); 209 | TEST_ASSERT_NOT_NULL(pocsag); 210 | TEST_ASSERT_OK(pager_pocsag_delete(&pocsag)); 211 | TEST_ASSERT_EQUALS(pocsag, NULL); 212 | 213 | return A_OK; 214 | } 215 | 216 | TEST_DECLARE_SUITE(pocsag, test_pager_pocsag_cleanup, test_pager_pocsag_setup, NULL, NULL); 217 | 218 | -------------------------------------------------------------------------------- /ais/test/test_ais_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | static 16 | const int16_t *samples = NULL; 17 | 18 | static 19 | size_t nr_samples = 0; 20 | 21 | #define TEST_FILE_NAME "ais_48khz_16b_raw.bin" 22 | //#define TEST_FILE_NAME "ais_single_sample.bin" 23 | 24 | static 25 | aresult_t test_ais_demod_setup(void) 26 | { 27 | aresult_t ret = A_OK; 28 | 29 | const char *test_data_dir = ".", 30 | *env_dir = NULL; 31 | char *file_path = NULL; 32 | size_t file_len = 0; 33 | 34 | FILE *fp = NULL; 35 | 36 | if (NULL != (env_dir = getenv("AIS_TEST_DATA_DIR"))) { 37 | test_data_dir = env_dir; 38 | } 39 | 40 | TEST_INF("Retrieving AIS test data from directory '%s'", test_data_dir); 41 | 42 | TSL_BUG_IF_FAILED(tasprintf(&file_path, "%s/%s", test_data_dir, TEST_FILE_NAME)); 43 | 44 | TEST_INF("Attempting to open AIS test data from file '%s'", file_path); 45 | 46 | if (NULL == (fp = fopen(file_path, "r"))) { 47 | TEST_ERR("Failed to open file %s, aborting.", file_path); 48 | ret = A_E_INVAL; 49 | goto done; 50 | } 51 | 52 | fseek(fp, 0, SEEK_END); 53 | file_len = ftell(fp); 54 | fseek(fp, 0, SEEK_SET); 55 | 56 | if (FAILED(ret = TCALLOC((void **)&samples, 1, file_len))) { 57 | goto done; 58 | } 59 | 60 | if (1 != fread((int16_t *)samples, file_len, 1, fp)) { 61 | TEST_ERR("Failed to read %zu bytes from file %s, aborting.", file_len, file_path); 62 | ret = A_E_INVAL; 63 | goto done; 64 | } 65 | 66 | nr_samples = file_len / sizeof(int16_t); 67 | 68 | done: 69 | if (FAILED(ret)) { 70 | TEST_ERR("Make sure AIS_TEST_DATA_DIR is set to point to where we can find " TEST_FILE_NAME); 71 | if (NULL != samples) { 72 | TFREE(samples); 73 | } 74 | } 75 | 76 | if (NULL != fp) { 77 | fclose(fp); 78 | fp = NULL; 79 | } 80 | 81 | if (NULL != file_path) { 82 | TFREE(file_path); 83 | } 84 | 85 | return ret; 86 | } 87 | 88 | static 89 | aresult_t test_ais_demod_cleanup(void) 90 | { 91 | aresult_t ret = A_OK; 92 | 93 | if (NULL != samples) { 94 | TFREE(samples); 95 | } 96 | 97 | nr_samples = 0; 98 | 99 | return ret; 100 | } 101 | 102 | static 103 | uint32_t _ais_decode_get_bitfield(const uint8_t *packet, size_t packet_len, 104 | size_t offset, size_t len) 105 | { 106 | uint64_t acc = 0; 107 | size_t nr_bytes = (len + 7)/8, 108 | start_byte = offset/8; 109 | 110 | for (size_t i = 0; i < nr_bytes; i++) { 111 | acc <<= 8; 112 | acc |= packet[i + start_byte]; 113 | } 114 | 115 | size_t end_bit = (offset + len) % 8; 116 | 117 | acc >>= 8 - end_bit; 118 | acc &= ((1ul << len) - 1); 119 | 120 | return (uint32_t)acc; 121 | } 122 | 123 | static 124 | char _test_to_ascii_armor(uint8_t in) 125 | { 126 | if (in <= 39) { 127 | return in + 48; 128 | } else { 129 | return in - 40 + 96; 130 | } 131 | } 132 | 133 | static 134 | aresult_t _test_on_message_cb(struct ais_demod *demod, void *state, const uint8_t *packet, size_t packet_len, bool fcs_valid) 135 | { 136 | uint8_t msg_id = 0, 137 | repeat = 0; 138 | uint32_t mmsi = 0; 139 | uint8_t offs = 0; 140 | uint8_t msg_ascii_6[168/6]; 141 | 142 | memset(msg_ascii_6, 0, sizeof(msg_ascii_6)); 143 | 144 | for (size_t i = 0; i < sizeof(msg_ascii_6); i += 4) { 145 | uint32_t accum = 0; 146 | for (size_t j = offs; j < offs + 3; j++) { 147 | accum <<= 8; 148 | accum |= packet[j]; 149 | } 150 | offs += 3; 151 | for (size_t j = 0; j < 4; j++) { 152 | msg_ascii_6[i + j] = (accum >> ((3 - j) * 6)) & 0x3f; 153 | } 154 | } 155 | 156 | printf("Ascii Armored version: "); 157 | for (size_t i = 0; i < sizeof(msg_ascii_6); i++) { 158 | printf("%c", _test_to_ascii_armor(msg_ascii_6[i])); 159 | } 160 | printf("\n"); 161 | 162 | /* Extract the message type */ 163 | msg_id = (packet[0] >> 2) & 0x3f; 164 | 165 | /* Extract repeat indicator */ 166 | repeat = packet[0] & 0x3; 167 | 168 | /* Extract the MMSI from the packet */ 169 | mmsi = (uint32_t)packet[1] << 22; 170 | mmsi |= (uint32_t)packet[2] << 14; 171 | mmsi |= (uint32_t)packet[3] << 6; 172 | mmsi |= ((uint32_t)packet[4] >> 2) & 0x3f; 173 | 174 | uint32_t mmsi_test = _ais_decode_get_bitfield(packet, packet_len, 8, 30); 175 | 176 | TEST_INF("MsgId: %02u Rpt: %1u MMSI: %9u Test MMSI: %9u (Len: %zu bytes)", msg_id, repeat, mmsi, mmsi_test, packet_len); 177 | 178 | return A_OK; 179 | } 180 | 181 | TEST_DECLARE_UNIT(test_one_shot_decoder, ais_demod) 182 | { 183 | struct ais_decode *decoder = NULL; 184 | 185 | TEST_INF("Processing %zu samples in one shot.", nr_samples); 186 | 187 | TEST_ASSERT_OK(ais_decode_new(&decoder, 162025000ul, NULL, NULL, NULL)); 188 | TEST_ASSERT_NOT_NULL(decoder); 189 | TEST_ASSERT_OK(ais_decode_on_pcm(decoder, samples, nr_samples)); 190 | TEST_ASSERT_OK(ais_decode_delete(&decoder)); 191 | 192 | return A_OK; 193 | } 194 | 195 | TEST_DECLARE_UNIT(test_one_shot, ais_demod) 196 | { 197 | struct ais_demod *demod = NULL; 198 | 199 | TEST_INF("Processing %zu samples in one shot.", nr_samples); 200 | 201 | TEST_ASSERT_OK(ais_demod_new(&demod, NULL, _test_on_message_cb, 162025000ul)); 202 | TEST_ASSERT_NOT_NULL(demod); 203 | TEST_ASSERT_OK(ais_demod_on_pcm(demod, samples, nr_samples)); 204 | TEST_ASSERT_OK(ais_demod_delete(&demod)); 205 | 206 | return A_OK; 207 | } 208 | 209 | TEST_DECLARE_UNIT(test_smoke, ais_demod) 210 | { 211 | struct ais_demod *demod = NULL; 212 | 213 | TEST_ASSERT_OK(ais_demod_new(&demod, NULL, _test_on_message_cb, 162025000ul)); 214 | TEST_ASSERT_NOT_NULL(demod); 215 | TEST_ASSERT_OK(ais_demod_delete(&demod)); 216 | 217 | return A_OK; 218 | } 219 | 220 | TEST_DECLARE_SUITE(ais_demod, test_ais_demod_cleanup, test_ais_demod_setup, NULL, NULL); 221 | 222 | -------------------------------------------------------------------------------- /pager/pager_pocsag_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define PAGER_POCSAG_BATCH_BITS 512 7 | #define PAGER_POCSAG_SYNC_BITS 32 8 | 9 | /** 10 | * POCSAG decoder state 11 | */ 12 | enum pager_pocsag_state { 13 | /** 14 | * Searching for a synchronization codeword 15 | */ 16 | PAGER_POCSAG_STATE_SEARCH = 0, 17 | 18 | /** 19 | * Received sync word 20 | */ 21 | PAGER_POCSAG_STATE_SYNCHRONIZED = 1, 22 | 23 | /** 24 | * Receiving a batch (after sync word) 25 | */ 26 | PAGER_POCSAG_STATE_BATCH_RECEIVE = 2, 27 | 28 | /** 29 | * Search for a sync codeword at previously synchronized bit rate (batch finished). 30 | * Can transition to: 31 | * - Synchronized 32 | * - Search 33 | */ 34 | PAGER_POCSAG_STATE_SEARCH_SYNCWORD = 3, 35 | }; 36 | 37 | /** 38 | * Synchronization codeword, used to detect the baud rate 39 | */ 40 | #define POCSAG_SYNC_CODEWORD 0x7cd215d8ul 41 | 42 | /** 43 | * Idle codeword, used to detect when words in a batch are to be ignored. 44 | * Post-BCH correction. 45 | */ 46 | #define POCSAG_IDLE_CODEWORD 0x6983915eu 47 | 48 | #define POCSAG_PAGER_BASE_BAUD_RATE 38400 49 | #define POCSAG_PAGER_BAUD_512_SAMPLES (POCSAG_PAGER_BASE_BAUD_RATE/512) 50 | #define POCSAG_PAGER_BAUD_1200_SAMPLES (POCSAG_PAGER_BASE_BAUD_RATE/1200) 51 | #define POCSAG_PAGER_BAUD_2400_SAMPLES (POCSAG_PAGER_BASE_BAUD_RATE/2400) 52 | 53 | #define POCSAG_PAGER_MAX_ALNUM_LEN 42 54 | #define POCSAG_PAGER_MAX_NUM_LEN 75 55 | 56 | enum pager_pocsag_message_type { 57 | PAGER_POCSAG_MESSAGE_TYPE_NONE = 0, 58 | PAGER_POCSAG_MESSAGE_TYPE_UNKNOWN = 1, 59 | PAGER_POCSAG_MESSAGE_TYPE_ALPHA = 2, 60 | PAGER_POCSAG_MESSAGE_TYPE_NUMERIC = 3, 61 | }; 62 | 63 | /** 64 | * Current POCSAG message decoding 65 | */ 66 | struct pager_pocsag_message_decode { 67 | /** 68 | * The currently decoded message, as alphanumeric 69 | */ 70 | char message_alpha[512]; 71 | 72 | /** 73 | * The next message alphanumeric byte to be written 74 | */ 75 | size_t next_byte_alpha; 76 | 77 | /** 78 | * Score for the alpha message 79 | */ 80 | int score_alpha; 81 | 82 | /** 83 | * We've seen non-printables, so we need to penalize 84 | */ 85 | bool seen_nonprint; 86 | 87 | /** 88 | * The current decoded message, as numeric 89 | */ 90 | char message_numeric[512]; 91 | 92 | /** 93 | * The next message numeric byte to be written 94 | */ 95 | size_t next_byte_numeric; 96 | 97 | /** 98 | * The CAPcode this message is destined for 99 | */ 100 | uint32_t cap_code; 101 | 102 | /** 103 | * Active data word, alphanumeric 104 | */ 105 | uint32_t data_word_alpha; 106 | 107 | /** 108 | * Number of bits in the active data word 109 | */ 110 | size_t data_word_alpha_valid_bits; 111 | 112 | /** 113 | * Active data word, numeric 114 | */ 115 | uint32_t data_word_numeric; 116 | 117 | /** 118 | * Number of bits in the active data word, numeric 119 | */ 120 | size_t data_word_numeric_valid_bits; 121 | 122 | /** 123 | * Function value recorded in address word 124 | */ 125 | uint8_t function; 126 | 127 | /** 128 | * Set to true if this is an early termination 129 | */ 130 | bool early_termination; 131 | 132 | /** 133 | * The message type 134 | */ 135 | enum pager_pocsag_message_type msg_type; 136 | }; 137 | 138 | /** 139 | * Processing state for detecting the baud rate of a burst of POCSAG 140 | */ 141 | struct pager_pocsag_baud_detect { 142 | /** 143 | * The number of samples per bit, also the number of words in the 144 | */ 145 | uint32_t samples_per_bit; 146 | 147 | /** 148 | * The baud rate 149 | */ 150 | uint16_t baud_rate; 151 | 152 | /** 153 | * The current word being processed 154 | */ 155 | uint32_t cur_word; 156 | 157 | /** 158 | * Number of samples in the eye that match 159 | */ 160 | uint32_t nr_eye_matches; 161 | 162 | /** 163 | * Sync word eye detection 164 | */ 165 | uint32_t eye_detect[]; 166 | }; 167 | 168 | /** 169 | * Batch word collection state 170 | */ 171 | struct pager_pocsag_batch { 172 | /** 173 | * The slice bit count 174 | */ 175 | uint16_t cur_sample_skip; 176 | 177 | /** 178 | * The current batch. Filled in by the baud block when it has 179 | * found a synchronization word. 180 | */ 181 | uint32_t current_batch[PAGER_POCSAG_BATCH_BITS/32]; 182 | 183 | /** 184 | * The current word in the batch 185 | */ 186 | uint16_t current_batch_word; 187 | 188 | /** 189 | * The next bit to be populated in the batch 190 | */ 191 | uint16_t current_batch_word_bit; 192 | 193 | /** 194 | * Total bit count in this batch 195 | */ 196 | uint16_t bit_count; 197 | }; 198 | 199 | /** 200 | * Sync search state, for after receiving a batch 201 | */ 202 | struct pager_pocsag_sync_search { 203 | /** 204 | * The current skipped sample ID 205 | */ 206 | uint16_t cur_sample_skip; 207 | 208 | /** 209 | * Number of sync bits captured 210 | */ 211 | size_t nr_sync_bits; 212 | 213 | /** 214 | * Current estimated sync word 215 | */ 216 | uint32_t sync_word; 217 | }; 218 | 219 | /** 220 | * POCSAG processing state. 221 | */ 222 | struct pager_pocsag { 223 | /** 224 | * The rate to skip samples at 225 | */ 226 | uint16_t sample_skip; 227 | 228 | /** 229 | * The current baud rate, if any 230 | */ 231 | uint16_t baud_rate; 232 | 233 | /** 234 | * Whether or not we should skip the BCH code validation 235 | */ 236 | bool skip_bch; 237 | 238 | /** 239 | * Callback for handling the arrival of numeric messages 240 | */ 241 | pager_pocsag_on_numeric_msg_func_t on_numeric; 242 | 243 | /** 244 | * Callback for handling the arrival of alphanumeric messages 245 | */ 246 | pager_pocsag_on_alpha_msg_func_t on_alpha; 247 | 248 | /** 249 | * Batch parsing/handling state. 250 | */ 251 | struct pager_pocsag_batch batch; 252 | 253 | /** 254 | * Sync word search state. 255 | */ 256 | struct pager_pocsag_sync_search sync; 257 | 258 | /** 259 | * State for decoding 512bps POCSAG 260 | */ 261 | struct pager_pocsag_baud_detect *baud_512; 262 | 263 | /** 264 | * State for decoding 1200bps SuperPOCSAG 265 | */ 266 | struct pager_pocsag_baud_detect *baud_1200; 267 | 268 | /** 269 | * State for decoding 2400bps SuperPOCSAG 270 | */ 271 | struct pager_pocsag_baud_detect *baud_2400; 272 | 273 | /** 274 | * Message decoder state 275 | */ 276 | struct pager_pocsag_message_decode decoder; 277 | 278 | /** 279 | * State for BCH(31, 21) code 280 | */ 281 | struct bch_code *bch; 282 | 283 | /** 284 | * Current state of the wire protocol handling 285 | */ 286 | enum pager_pocsag_state cur_state; 287 | }; 288 | 289 | -------------------------------------------------------------------------------- /multifm/fast_atan2f.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /***************************************************************************/ 6 | /* Constant definitions */ 7 | /***************************************************************************/ 8 | 9 | #define TAN_MAP_RES 0.003921569 /* (smallest non-zero value in table) */ 10 | #define RAD_PER_DEG 0.017453293 11 | #define TAN_MAP_SIZE 255 12 | 13 | /* arctangents from 0 to pi/4 radians */ 14 | static float 15 | fast_atan_table[257] = { 16 | 0.000000e+00, 3.921549e-03, 7.842976e-03, 1.176416e-02, 17 | 1.568499e-02, 1.960533e-02, 2.352507e-02, 2.744409e-02, 18 | 3.136226e-02, 3.527947e-02, 3.919560e-02, 4.311053e-02, 19 | 4.702413e-02, 5.093629e-02, 5.484690e-02, 5.875582e-02, 20 | 6.266295e-02, 6.656816e-02, 7.047134e-02, 7.437238e-02, 21 | 7.827114e-02, 8.216752e-02, 8.606141e-02, 8.995267e-02, 22 | 9.384121e-02, 9.772691e-02, 1.016096e-01, 1.054893e-01, 23 | 1.093658e-01, 1.132390e-01, 1.171087e-01, 1.209750e-01, 24 | 1.248376e-01, 1.286965e-01, 1.325515e-01, 1.364026e-01, 25 | 1.402496e-01, 1.440924e-01, 1.479310e-01, 1.517652e-01, 26 | 1.555948e-01, 1.594199e-01, 1.632403e-01, 1.670559e-01, 27 | 1.708665e-01, 1.746722e-01, 1.784728e-01, 1.822681e-01, 28 | 1.860582e-01, 1.898428e-01, 1.936220e-01, 1.973956e-01, 29 | 2.011634e-01, 2.049255e-01, 2.086818e-01, 2.124320e-01, 30 | 2.161762e-01, 2.199143e-01, 2.236461e-01, 2.273716e-01, 31 | 2.310907e-01, 2.348033e-01, 2.385093e-01, 2.422086e-01, 32 | 2.459012e-01, 2.495869e-01, 2.532658e-01, 2.569376e-01, 33 | 2.606024e-01, 2.642600e-01, 2.679104e-01, 2.715535e-01, 34 | 2.751892e-01, 2.788175e-01, 2.824383e-01, 2.860514e-01, 35 | 2.896569e-01, 2.932547e-01, 2.968447e-01, 3.004268e-01, 36 | 3.040009e-01, 3.075671e-01, 3.111252e-01, 3.146752e-01, 37 | 3.182170e-01, 3.217506e-01, 3.252758e-01, 3.287927e-01, 38 | 3.323012e-01, 3.358012e-01, 3.392926e-01, 3.427755e-01, 39 | 3.462497e-01, 3.497153e-01, 3.531721e-01, 3.566201e-01, 40 | 3.600593e-01, 3.634896e-01, 3.669110e-01, 3.703234e-01, 41 | 3.737268e-01, 3.771211e-01, 3.805064e-01, 3.838825e-01, 42 | 3.872494e-01, 3.906070e-01, 3.939555e-01, 3.972946e-01, 43 | 4.006244e-01, 4.039448e-01, 4.072558e-01, 4.105574e-01, 44 | 4.138496e-01, 4.171322e-01, 4.204054e-01, 4.236689e-01, 45 | 4.269229e-01, 4.301673e-01, 4.334021e-01, 4.366272e-01, 46 | 4.398426e-01, 4.430483e-01, 4.462443e-01, 4.494306e-01, 47 | 4.526070e-01, 4.557738e-01, 4.589307e-01, 4.620778e-01, 48 | 4.652150e-01, 4.683424e-01, 4.714600e-01, 4.745676e-01, 49 | 4.776654e-01, 4.807532e-01, 4.838312e-01, 4.868992e-01, 50 | 4.899573e-01, 4.930055e-01, 4.960437e-01, 4.990719e-01, 51 | 5.020902e-01, 5.050985e-01, 5.080968e-01, 5.110852e-01, 52 | 5.140636e-01, 5.170320e-01, 5.199904e-01, 5.229388e-01, 53 | 5.258772e-01, 5.288056e-01, 5.317241e-01, 5.346325e-01, 54 | 5.375310e-01, 5.404195e-01, 5.432980e-01, 5.461666e-01, 55 | 5.490251e-01, 5.518738e-01, 5.547124e-01, 5.575411e-01, 56 | 5.603599e-01, 5.631687e-01, 5.659676e-01, 5.687566e-01, 57 | 5.715357e-01, 5.743048e-01, 5.770641e-01, 5.798135e-01, 58 | 5.825531e-01, 5.852828e-01, 5.880026e-01, 5.907126e-01, 59 | 5.934128e-01, 5.961032e-01, 5.987839e-01, 6.014547e-01, 60 | 6.041158e-01, 6.067672e-01, 6.094088e-01, 6.120407e-01, 61 | 6.146630e-01, 6.172755e-01, 6.198784e-01, 6.224717e-01, 62 | 6.250554e-01, 6.276294e-01, 6.301939e-01, 6.327488e-01, 63 | 6.352942e-01, 6.378301e-01, 6.403565e-01, 6.428734e-01, 64 | 6.453808e-01, 6.478788e-01, 6.503674e-01, 6.528466e-01, 65 | 6.553165e-01, 6.577770e-01, 6.602282e-01, 6.626701e-01, 66 | 6.651027e-01, 6.675261e-01, 6.699402e-01, 6.723452e-01, 67 | 6.747409e-01, 6.771276e-01, 6.795051e-01, 6.818735e-01, 68 | 6.842328e-01, 6.865831e-01, 6.889244e-01, 6.912567e-01, 69 | 6.935800e-01, 6.958943e-01, 6.981998e-01, 7.004964e-01, 70 | 7.027841e-01, 7.050630e-01, 7.073330e-01, 7.095943e-01, 71 | 7.118469e-01, 7.140907e-01, 7.163258e-01, 7.185523e-01, 72 | 7.207701e-01, 7.229794e-01, 7.251800e-01, 7.273721e-01, 73 | 7.295557e-01, 7.317307e-01, 7.338974e-01, 7.360555e-01, 74 | 7.382053e-01, 7.403467e-01, 7.424797e-01, 7.446045e-01, 75 | 7.467209e-01, 7.488291e-01, 7.509291e-01, 7.530208e-01, 76 | 7.551044e-01, 7.571798e-01, 7.592472e-01, 7.613064e-01, 77 | 7.633576e-01, 7.654008e-01, 7.674360e-01, 7.694633e-01, 78 | 7.714826e-01, 7.734940e-01, 7.754975e-01, 7.774932e-01, 79 | 7.794811e-01, 7.814612e-01, 7.834335e-01, 7.853982e-01, 80 | 7.853982e-01 81 | }; 82 | 83 | 84 | /***************************************************************************** 85 | Function: Arc tangent 86 | 87 | Syntax: angle = fast_atan2(y, x); 88 | float y y component of input vector 89 | float x x component of input vector 90 | float angle angle of vector (x, y) in radians 91 | 92 | Description: This function calculates the angle of the vector (x,y) 93 | based on a table lookup and linear interpolation. The table uses a 94 | 256 point table covering -45 to +45 degrees and uses symetry to 95 | determine the final angle value in the range of -180 to 180 96 | degrees. Note that this function uses the small angle approximation 97 | for values close to zero. This routine calculates the arc tangent 98 | with an average error of +/- 3.56e-5 degrees (6.21e-7 radians). 99 | *****************************************************************************/ 100 | 101 | float fast_atan2f(float y, float x) 102 | { 103 | float x_abs, y_abs, z; 104 | float alpha, angle, base_angle; 105 | int index; 106 | 107 | /* normalize to +/- 45 degree range */ 108 | y_abs = fabsf(y); 109 | x_abs = fabsf(x); 110 | /* don't divide by zero! */ 111 | if(!((y_abs > 0.0f) || (x_abs > 0.0f))) 112 | return 0.0; 113 | 114 | if(y_abs < x_abs) 115 | z = y_abs / x_abs; 116 | else 117 | z = x_abs / y_abs; 118 | 119 | /* when ratio approaches the table resolution, the angle is */ 120 | /* best approximated with the argument itself... */ 121 | if(z < TAN_MAP_RES) 122 | base_angle = z; 123 | else { 124 | /* find index and interpolation value */ 125 | alpha = z * (float)TAN_MAP_SIZE; 126 | index = ((int)alpha) & 0xff; 127 | alpha -= (float)index; 128 | /* determine base angle based on quadrant and */ 129 | /* add or subtract table value from base angle based on quadrant */ 130 | base_angle = fast_atan_table[index]; 131 | base_angle += (fast_atan_table[index + 1] - fast_atan_table[index]) * alpha; 132 | } 133 | 134 | if(x_abs > y_abs) { /* -45 -> 45 or 135 -> 225 */ 135 | if(x >= 0.0) { /* -45 -> 45 */ 136 | if(y >= 0.0) 137 | angle = base_angle; /* 0 -> 45, angle OK */ 138 | else 139 | angle = -base_angle; /* -45 -> 0, angle = -angle */ 140 | } 141 | else { /* 135 -> 180 or 180 -> -135 */ 142 | angle = 3.14159265358979323846; 143 | if(y >= 0.0) 144 | angle -= base_angle; /* 135 -> 180, angle = 180 - angle */ 145 | else 146 | angle = base_angle - angle; /* 180 -> -135, angle = angle - 180 */ 147 | } 148 | } 149 | else { /* 45 -> 135 or -135 -> -45 */ 150 | if(y >= 0.0) { /* 45 -> 135 */ 151 | angle = 1.57079632679489661923; 152 | if(x >= 0.0) 153 | angle -= base_angle; /* 45 -> 90, angle = 90 - angle */ 154 | else 155 | angle += base_angle; /* 90 -> 135, angle = 90 + angle */ 156 | } 157 | else { /* -135 -> -45 */ 158 | angle = -1.57079632679489661923; 159 | if(x >= 0.0) 160 | angle += base_angle; /* -90 -> -45, angle = -90 + angle */ 161 | else 162 | angle -= base_angle; /* -135 -> -90, angle = -90 - angle */ 163 | } 164 | } 165 | 166 | #ifdef ZERO_TO_TWOPI 167 | if (angle < 0) 168 | return (angle + TWOPI); 169 | else 170 | return (angle); 171 | #else 172 | return (angle); 173 | #endif 174 | } 175 | -------------------------------------------------------------------------------- /ais/ais_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #ifdef AIS_DEBUG_STATE 13 | #define STATE_TRANSITION(x, ...) DIAG(x, ##__VA_ARGS__) 14 | #else /* ndef(AIS_DEBUG_STATE) */ 15 | #define STATE_TRANSITION(...) 16 | #endif /* defined(AIS_DEBUG_STATE) */ 17 | 18 | static 19 | uint16_t _ais_crc16(const uint8_t *data, size_t len) 20 | { 21 | uint16_t crc = 0xffffu; 22 | const uint16_t poly = 0x8408u; 23 | 24 | for (size_t i = 0; i < len; i++) { 25 | crc ^= (uint16_t)data[i]; 26 | for (size_t j = 0; j < 8; j++) { 27 | if (crc & 1) { 28 | crc = (crc >> 1) ^ poly; 29 | } else { 30 | crc >>= 1; 31 | } 32 | } 33 | } 34 | 35 | return ~crc; 36 | } 37 | 38 | static 39 | bool _ais_demod_compare(uint32_t x, uint32_t y, unsigned diff) 40 | { 41 | return __builtin_popcountll(x ^ y) <= diff; 42 | } 43 | 44 | static 45 | void _ais_demod_detect_reset(struct ais_demod_detect *detect) 46 | { 47 | memset(detect->preambles, 0, sizeof(detect->preambles)); 48 | memset(detect->prior_sample, 0, sizeof(detect->prior_sample)); 49 | detect->next_field = 0; 50 | } 51 | 52 | static 53 | void _ais_demod_rx_reset(struct ais_demod_rx *rx) 54 | { 55 | memset(rx->packet, 0, sizeof(rx->packet)); 56 | rx->raw_shr = 0; 57 | rx->current_bit = 0; 58 | rx->nr_ones = 0; 59 | } 60 | 61 | aresult_t ais_demod_new(struct ais_demod **pdemod, void *state, ais_demod_on_message_callback_func_t cb, uint32_t freq) 62 | { 63 | aresult_t ret = A_OK; 64 | 65 | struct ais_demod *demod = NULL; 66 | 67 | TSL_ASSERT_ARG(NULL != pdemod); 68 | TSL_ASSERT_ARG(NULL != cb); 69 | 70 | *pdemod = NULL; 71 | 72 | if (FAILED(ret = TZAALLOC(demod, SYS_CACHE_LINE_LENGTH))) { 73 | goto done; 74 | } 75 | 76 | _ais_demod_rx_reset(&demod->packet_rx); 77 | _ais_demod_detect_reset(&demod->detector); 78 | 79 | demod->on_msg_cb = cb; 80 | demod->freq = freq; 81 | demod->state = AIS_DEMOD_STATE_SEARCH_SYNC; 82 | demod->caller_state = state; 83 | 84 | *pdemod = demod; 85 | 86 | done: 87 | if (FAILED(ret)) { 88 | if (NULL != demod) { 89 | TFREE(demod); 90 | } 91 | } 92 | return ret; 93 | } 94 | 95 | aresult_t ais_demod_delete(struct ais_demod **pdemod) 96 | { 97 | aresult_t ret = A_OK; 98 | 99 | struct ais_demod *demod = NULL; 100 | 101 | TSL_ASSERT_ARG(NULL != pdemod); 102 | TSL_ASSERT_ARG(NULL != *pdemod); 103 | 104 | demod = *pdemod; 105 | 106 | TFREE(demod); 107 | 108 | *pdemod = NULL; 109 | 110 | return ret; 111 | } 112 | 113 | static inline 114 | void _ais_demod_detect_handle_sample(struct ais_demod *demod, int16_t sample) 115 | { 116 | struct ais_demod_detect *detector = NULL; 117 | 118 | uint8_t sample_slice = 0, 119 | nr_match = 0, 120 | last_bit = 0; 121 | 122 | TSL_BUG_ON(NULL == demod); 123 | 124 | detector = &demod->detector; 125 | 126 | /* Slice our new sample */ 127 | sample_slice = sample > 0; 128 | 129 | /* Grab our prior sample */ 130 | last_bit = detector->prior_sample[detector->next_field]; 131 | 132 | /* Record this sample */ 133 | detector->prior_sample[detector->next_field] = sample_slice; 134 | 135 | detector->preambles[detector->next_field] <<= 1; 136 | detector->preambles[detector->next_field] |= !(last_bit ^ sample_slice); 137 | 138 | for (size_t i = 0; i < AIS_DECIMATION_RATE; i++) { 139 | if (_ais_demod_compare(detector->preambles[i], 0x5555557eul, 2)) { 140 | #ifdef AIS_PACKET_DEBUG 141 | DIAG(" Preamble [%zu]: 0x%08x", i, detector->preambles[i]); 142 | #endif /* define(AIS_PACKET_DEBUG) */ 143 | nr_match++; 144 | } 145 | } 146 | 147 | if (nr_match >= 3) { 148 | STATE_TRANSITION("SEARCH_SYNC -> RECEIVING (%d matches)", (int)nr_match); 149 | 150 | demod->state = AIS_DEMOD_STATE_RECEIVING; 151 | demod->sample_skip = 2; 152 | _ais_demod_rx_reset(&demod->packet_rx); 153 | demod->packet_rx.last_sample = detector->prior_sample[detector->next_field]; 154 | } 155 | 156 | detector->next_field = (detector->next_field + 1) % AIS_DECIMATION_RATE; 157 | } 158 | 159 | static inline 160 | void _ais_demod_packet_rx_sample(struct ais_demod *demod, int16_t sample) 161 | { 162 | struct ais_demod_rx *rx = NULL; 163 | 164 | uint8_t bit = 0, 165 | raw = 0, 166 | last = 0; 167 | 168 | TSL_BUG_ON(NULL == demod); 169 | 170 | rx = &demod->packet_rx; 171 | 172 | raw = sample > 0; 173 | last = rx->last_sample; 174 | 175 | bit = !(last ^ raw); 176 | rx->raw_shr <<= 1; 177 | rx->raw_shr |= bit; 178 | rx->last_sample = raw; 179 | 180 | if (rx->nr_ones < 5) { 181 | rx->packet[rx->current_bit / 8] |= bit << (rx->current_bit % 8); 182 | rx->current_bit++; 183 | } 184 | 185 | if (0 == bit) { 186 | rx->nr_ones = 0; 187 | } else { 188 | rx->nr_ones++; 189 | } 190 | 191 | if (rx->raw_shr == AIS_PACKET_END_FLAG || rx->current_bit == (5 * 256)) { 192 | /* We have a packet or some horrible corruption */ 193 | size_t packet_bytes = rx->current_bit / 8; 194 | if (4 <= packet_bytes) { 195 | uint16_t crc = _ais_crc16(rx->packet, packet_bytes - 2), 196 | rx_crc = (uint16_t)rx->packet[packet_bytes - 2] | (uint16_t)rx->packet[packet_bytes - 1] << 8; 197 | 198 | if (rx_crc == crc) { 199 | TSL_BUG_IF_FAILED(demod->on_msg_cb(demod, demod->caller_state, rx->packet, packet_bytes - 2, true)); 200 | } else { 201 | demod->crc_rejects++; 202 | #ifdef AIS_PACKET_DEBUG 203 | DIAG("Failed CRC match, raw packet (calculated %04x, received %04x):", crc, rx_crc); 204 | hexdump_dump_hex(rx->packet, packet_bytes); 205 | #endif /* defined(_TSL_DEBUG) */ 206 | } 207 | } 208 | STATE_TRANSITION("RECEIVING -> SEARCH_SYNC"); 209 | demod->state = AIS_DEMOD_STATE_SEARCH_SYNC; 210 | demod->sample_skip = 0; 211 | _ais_demod_detect_reset(&demod->detector); 212 | } 213 | } 214 | 215 | aresult_t ais_demod_on_pcm(struct ais_demod *demod, const int16_t *samples, size_t nr_samples) 216 | { 217 | aresult_t ret = A_OK; 218 | 219 | size_t cur_sample = 0; 220 | 221 | TSL_ASSERT_ARG(NULL != demod); 222 | TSL_ASSERT_ARG(NULL != samples); 223 | 224 | while (nr_samples > cur_sample) { 225 | if (demod->state == AIS_DEMOD_STATE_SEARCH_SYNC) { 226 | for (size_t i = cur_sample; i < nr_samples; i++, cur_sample++) { 227 | /* Process this sample */ 228 | _ais_demod_detect_handle_sample(demod, samples[i]); 229 | if (demod->state == AIS_DEMOD_STATE_RECEIVING) { 230 | /* Preamble was found, break. */ 231 | #ifdef AIS_PACKET_DEBUG 232 | fprintf(stderr, " %zu, %d %% last preamble bit\n", i, samples[i]); 233 | #endif 234 | cur_sample = i + 1; 235 | break; 236 | } 237 | } 238 | } else if (demod->state == AIS_DEMOD_STATE_RECEIVING) { 239 | for (size_t i = cur_sample; i < nr_samples; i++, cur_sample++) { 240 | if ((demod->sample_skip++ % AIS_DECIMATION_RATE) == 0) { 241 | #ifdef AIS_PACKET_DEBUG 242 | fprintf(stderr, " %zu, %d %% skip = %zu\n", i, samples[i], demod->sample_skip - 1); 243 | #endif /* defined(AIS_PACKET_DEBUG) */ 244 | _ais_demod_packet_rx_sample(demod, samples[i]); 245 | if (demod->state == AIS_DEMOD_STATE_SEARCH_SYNC) { 246 | cur_sample = i + 1; 247 | break; 248 | } 249 | } 250 | } 251 | } else { 252 | PANIC("Unknown state for AIS demodulator: %d", demod->state); 253 | } 254 | } 255 | 256 | return ret; 257 | } 258 | 259 | -------------------------------------------------------------------------------- /multifm/airspy_if.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | static 18 | aresult_t _airspy_worker_thread_delete(struct receiver *rx) 19 | { 20 | aresult_t ret = A_OK; 21 | 22 | struct airspy_thread *athr = NULL; 23 | 24 | TSL_ASSERT_ARG(NULL != rx); 25 | 26 | athr = BL_CONTAINER_OF(rx, struct airspy_thread, rx); 27 | 28 | if (NULL == athr->dev) { 29 | goto done; 30 | } 31 | 32 | if (0 != airspy_term_rx(athr->dev)) { 33 | ret = A_E_INVAL; 34 | goto done; 35 | } 36 | 37 | airspy_close(athr->dev); 38 | athr->dev = NULL; 39 | 40 | done: 41 | return ret; 42 | } 43 | 44 | static 45 | int _airspy_on_sample_block(struct airspy_device *dev, void *ctx, airspy_transfer *transfer) 46 | { 47 | int ret = 0; 48 | 49 | struct airspy_thread *thr = ctx; 50 | struct sample_buf *sbuf = NULL; 51 | 52 | if (true == thr->rx.muted) { 53 | /* Don't do anything, we're muted */ 54 | goto done; 55 | } 56 | 57 | /* TODO: Dump FD stuff */ 58 | 59 | if (FAILED(receiver_sample_buf_alloc(&thr->rx, &sbuf))) { 60 | DIAG("Dropping buffer due to sample buffer memory pressure."); 61 | thr->dropped++; 62 | goto done; 63 | } 64 | 65 | DIAG("Received %u samples", transfer->sample_count); 66 | 67 | /* TODO: for now, just memcpy to the output */ 68 | memcpy(sbuf->data_buf, transfer->samples, transfer->sample_count * sizeof(int16_t) * 2); 69 | sbuf->nr_samples = transfer->sample_count; 70 | 71 | /* Something has gone very wrong... */ 72 | if (FAILED(receiver_sample_buf_deliver(&thr->rx, sbuf))) { 73 | TSL_BUG_IF_FAILED(sample_buf_decref(sbuf)); 74 | ret = -1; 75 | goto done; 76 | } 77 | 78 | done: 79 | return ret; 80 | } 81 | 82 | static 83 | aresult_t _airspy_worker_thread(struct receiver *rx) 84 | { 85 | aresult_t ret = A_OK; 86 | 87 | struct airspy_thread *athr = NULL; 88 | 89 | TSL_ASSERT_ARG(NULL != rx); 90 | 91 | athr = BL_CONTAINER_OF(rx, struct airspy_thread, rx); 92 | 93 | DIAG("Starting Airspy worker thread..."); 94 | 95 | if (0 != airspy_init_rx(athr->dev)) { 96 | MFM_MSG(SEV_ERROR, "AIRSPY-ERROR", "Failed to initialize the receive path, aborting."); 97 | goto done; 98 | } 99 | 100 | if (0 != airspy_do_rx(athr->dev, _airspy_on_sample_block, athr)) { 101 | MFM_MSG(SEV_ERROR, "AIRSPY-ERROR", "Error while running Airspy capture process, aborting."); 102 | } 103 | 104 | if (0 != airspy_term_rx(athr->dev)) { 105 | MFM_MSG(SEV_ERROR, "AIRSPY-ERROR", "An error occurred while cleaning up Airspy, aborting."); 106 | } 107 | 108 | MFM_MSG(SEV_INFO, "AIRSPY-TERMINATED", "Terminating Airspy receiver thread"); 109 | 110 | done: 111 | return ret; 112 | } 113 | 114 | aresult_t airspy_worker_thread_new(struct receiver **pthr, struct config *cfg) 115 | { 116 | aresult_t ret = A_OK; 117 | 118 | struct airspy_thread *athr = NULL; 119 | struct airspy_device *dev = NULL; 120 | int sample_rate = 0, 121 | center_freq = 0, 122 | ser_no = -1, 123 | lna_gain = 1, 124 | vga_gain = 5, 125 | mixer_gain = 5, 126 | airspy_ret = 0; 127 | bool bias_t = false; 128 | struct config device = CONFIG_INIT_EMPTY; 129 | 130 | TSL_ASSERT_ARG(NULL != pthr); 131 | TSL_ASSERT_ARG(NULL != cfg); 132 | 133 | TSL_BUG_IF_FAILED(config_get(cfg, &device, "device")); 134 | 135 | if (FAILED(ret = config_get_integer(cfg, &sample_rate, "sampleRateHz"))) { 136 | MFM_MSG(SEV_INFO, "NO-SAMPLE-RATE", "Need to specify a sample rate, in Hertz."); 137 | goto done; 138 | } 139 | 140 | if (FAILED(ret = config_get_integer(cfg, ¢er_freq, "centerFreqHz"))) { 141 | MFM_MSG(SEV_INFO, "NO-CENTER-FREQ", "You forgot to specify a center frequency, in Hz."); 142 | goto done; 143 | } 144 | 145 | /* Grab our device serial number, if present */ 146 | if (FAILED(config_get_integer(&device, &ser_no, "serialNo"))) { 147 | /* Not present, so we'll open the first device we find */ 148 | ser_no = -1; 149 | } 150 | 151 | /* Get the LNA gain from the device config */ 152 | if (FAILED(config_get_integer(&device, &lna_gain, "lnaGain"))) { 153 | lna_gain = 1; 154 | } 155 | 156 | /* Get the VGA gain from the device config */ 157 | if (FAILED(config_get_integer(&device, &vga_gain, "vgaGain"))) { 158 | vga_gain = 5; 159 | } 160 | 161 | /* Get the Mixer gain from the device config */ 162 | if (FAILED(config_get_integer(&device, &mixer_gain, "mixerGain"))) { 163 | mixer_gain = 5; 164 | } 165 | 166 | MFM_MSG(SEV_INFO, "GAINS", "Gains: LNA = %d dB, VGA = %d dB, Mixer = %d dB", 167 | lna_gain, vga_gain, mixer_gain); 168 | 169 | /* Check if we should enable the Bias Tee */ 170 | if (FAILED(config_get_boolean(&device, &bias_t, "enableBiasTee"))) { 171 | bias_t = false; 172 | } else { 173 | if (true == bias_t) { 174 | MFM_MSG(SEV_INFO, "BIAS-TEE", "Bias Tee is enabled, so hope you have something attached."); 175 | } 176 | } 177 | 178 | /* Open the device */ 179 | if (-1 != ser_no) { 180 | if (0 != (airspy_ret = airspy_open_sn(&dev, ser_no))) { 181 | MFM_MSG(SEV_FATAL, "BAD-DEVICE", "Unable to find Airspy device with ID %d", ser_no); 182 | ret = A_E_INVAL; 183 | goto done; 184 | } 185 | } else { 186 | if (0 != (airspy_ret = airspy_open(&dev))) { 187 | MFM_MSG(SEV_FATAL, "NO-DEVICE", "Unable to find any Airspy devices."); 188 | ret = A_E_INVAL; 189 | goto done; 190 | } 191 | } 192 | 193 | /* Set the sample rate, as requested */ 194 | if (0 != airspy_set_samplerate(dev, sample_rate)) { 195 | MFM_MSG(SEV_FATAL, "BAD-SAMPLE-RATE", "Unable to set sampling rate to %d Hz, aborting.", 196 | sample_rate); 197 | ret = A_E_INVAL; 198 | goto done; 199 | } 200 | 201 | /* Set the center frequency to the requested value */ 202 | if (0 != airspy_set_freq(dev, center_freq)) { 203 | MFM_MSG(SEV_FATAL, "BAD-CENTER-FREQ", "Unable to set center frequency to %d Hz, aborting.", 204 | center_freq); 205 | ret = A_E_INVAL; 206 | goto done; 207 | } 208 | 209 | /* Set the LNA gain */ 210 | if (0 != airspy_set_lna_gain(dev, lna_gain)) { 211 | MFM_MSG(SEV_FATAL, "BAD-LNA-GAIN", "LNA gain value of %d dB is invalid, aborting", lna_gain); 212 | ret = A_E_INVAL; 213 | goto done; 214 | } 215 | 216 | /* Set the VGA gain */ 217 | if (0 != airspy_set_vga_gain(dev, vga_gain)) { 218 | MFM_MSG(SEV_FATAL, "BAD-VGA-GAIN", "VGA gain value of %d dB is invalid, aborting", vga_gain); 219 | ret = A_E_INVAL; 220 | goto done; 221 | } 222 | 223 | /* Set the Mixer gain */ 224 | if (0 != airspy_set_mixer_gain(dev, mixer_gain)) { 225 | MFM_MSG(SEV_FATAL, "BAD-MIXER-GAIN", "Mixer gain value of %d dB is invalid, aborting", mixer_gain); 226 | ret = A_E_INVAL; 227 | goto done; 228 | } 229 | 230 | /* Enable the Bias Tee if we were asked to do so */ 231 | if (0 != airspy_set_rf_bias(dev, (true == bias_t) ? 1 : 0)) { 232 | MFM_MSG(SEV_WARNING, "FAILED-ENABLE-BIAS", "Failed to enable Bias Tee for powering an outside device."); 233 | } 234 | 235 | /* Create the device object */ 236 | if (FAILED(ret = TZAALLOC(athr, SYS_CACHE_LINE_LENGTH))) { 237 | goto done; 238 | } 239 | 240 | athr->dev = dev; 241 | athr->dump_fd = -1; 242 | 243 | /* Initialize the worker thread */ 244 | TSL_BUG_IF_FAILED(receiver_init(&athr->rx, cfg, _airspy_worker_thread, _airspy_worker_thread_delete, 245 | 128 * 1024 * 2)); 246 | 247 | *pthr = &athr->rx; 248 | 249 | done: 250 | if (FAILED(ret)) { 251 | /* Clean up the device handle */ 252 | if (NULL != dev) { 253 | airspy_close(dev); 254 | } 255 | } 256 | 257 | return ret; 258 | } 259 | 260 | -------------------------------------------------------------------------------- /filter/polyphase_fir.c: -------------------------------------------------------------------------------- 1 | /* 2 | * polyphase_fir.c - A polyphase FIR implementation for rational 3 | * resampling of an input signal. 4 | * 5 | * Copyright (c)2017 Phil Vachon 6 | * 7 | * This file is a part of The Standard Library (TSL) 8 | * 9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software 21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | /** 37 | * Construct a new polyphase FIR. 38 | * 39 | * \param pfir The new polyphase FIR state, returned by reference. 40 | * \param nr_coeffs The number of coefficients in the FIR 41 | * \param fir_coeff The real coefficients for the FIR. In Q.15 representation. 42 | * \param interpolate The factor to interpolate (upsample) by 43 | * \param decimate The factor to decimate (downsample) by 44 | * 45 | * \return A_OK on success, an error code otherwise. 46 | */ 47 | aresult_t polyphase_fir_new(struct polyphase_fir **pfir, size_t nr_coeffs, const int16_t *fir_coeff, 48 | unsigned interpolate, unsigned decimate) 49 | { 50 | aresult_t ret = A_OK; 51 | 52 | struct polyphase_fir *fir = NULL; 53 | unsigned phase_coeffs = 0; 54 | 55 | TSL_ASSERT_ARG(NULL != pfir); 56 | TSL_ASSERT_ARG(0 != nr_coeffs); 57 | TSL_ASSERT_ARG(NULL != fir_coeff); 58 | TSL_ASSERT_ARG(0 < interpolate); 59 | TSL_ASSERT_ARG(0 < decimate); 60 | 61 | if (FAILED(ret = TZAALLOC(fir, SYS_CACHE_LINE_LENGTH))) { 62 | goto done; 63 | } 64 | 65 | fir->nr_phase_filters = interpolate; 66 | fir->interpolation = interpolate; 67 | fir->decimation = decimate; 68 | 69 | /* Determine the number of coefficients in each phase */ 70 | phase_coeffs = (nr_coeffs + interpolate - 1)/interpolate; 71 | 72 | /* Round up to nearest 4 */ 73 | phase_coeffs = (phase_coeffs + 3) & ~(4-1); 74 | fir->nr_filter_coeffs = phase_coeffs; 75 | 76 | if (FAILED(ret = TACALLOC((void **)&fir->phase_filters, interpolate, phase_coeffs * sizeof(sample_t), SYS_CACHE_LINE_LENGTH))) { 77 | goto done; 78 | } 79 | 80 | /* Walk the input filter and set the coefficients in the appropriate filter phase */ 81 | for (size_t i = 0; i < nr_coeffs; i++) { 82 | fir->phase_filters[(i % interpolate) * phase_coeffs + (i / interpolate)] = fir_coeff[i]; 83 | } 84 | 85 | #ifdef _DUMP_FILTER_COEFFICIENTS 86 | for (size_t i = 0; i < fir->nr_phase_filters; i++) { 87 | printf("\nPhase %4zu: ", i); 88 | for (size_t j = 0; j < fir->nr_filter_coeffs; j++) { 89 | printf("%6d ", fir->phase_filters[i * fir->nr_filter_coeffs + j]); 90 | } 91 | } 92 | printf("\n"); 93 | #endif /* defined(_DUMP_FILTER_COEFFICIENTS) */ 94 | 95 | /* Bing, and we're done */ 96 | *pfir = fir; 97 | 98 | done: 99 | if (FAILED(ret)) { 100 | if (NULL != fir) { 101 | TFREE(fir); 102 | } 103 | } 104 | return ret; 105 | } 106 | 107 | /** 108 | * Delete/clean up resources consumed by a polyphase FIR. 109 | * 110 | * \param pfir The polyphase FIR to clean up, passed by reference. Set to NULL on success. 111 | * 112 | * \return A_OK on success, an error code otherwise. 113 | */ 114 | aresult_t polyphase_fir_delete(struct polyphase_fir **pfir) 115 | { 116 | aresult_t ret = A_OK; 117 | 118 | struct polyphase_fir *fir = NULL; 119 | 120 | TSL_ASSERT_PTR_BY_REF(pfir); 121 | 122 | fir = *pfir; 123 | 124 | if (NULL != fir->phase_filters) { 125 | TFREE(fir->phase_filters); 126 | } 127 | 128 | TFREE(fir); 129 | *pfir = NULL; 130 | 131 | return ret; 132 | } 133 | 134 | aresult_t polyphase_fir_push_sample_buf(struct polyphase_fir *fir, struct sample_buf *buf) 135 | { 136 | aresult_t ret = A_OK; 137 | 138 | TSL_ASSERT_ARG(NULL != fir); 139 | TSL_ASSERT_ARG(NULL != buf); 140 | 141 | TSL_BUG_ON(fir->sb_active == buf); 142 | TSL_BUG_ON(fir->sb_next == buf); 143 | 144 | if (NULL == fir->sb_active) { 145 | fir->sb_active = buf; 146 | TSL_BUG_ON(NULL != fir->sb_next); 147 | } else { 148 | if (NULL == fir->sb_next) { 149 | fir->sb_next = buf; 150 | } else { 151 | ret = A_E_BUSY; 152 | goto done; 153 | } 154 | } 155 | 156 | fir->nr_samples += buf->nr_samples; 157 | 158 | done: 159 | return ret; 160 | } 161 | 162 | aresult_t polyphase_fir_process(struct polyphase_fir *fir, int16_t *out_buf, size_t nr_out_samples, 163 | size_t *nr_out_samples_generated) 164 | { 165 | aresult_t ret = A_OK; 166 | 167 | size_t phase_id = 0, 168 | nr_consumed = 0, 169 | nr_computed_samples = 0; 170 | 171 | TSL_ASSERT_ARG(NULL != fir); 172 | TSL_ASSERT_ARG(NULL != out_buf); 173 | TSL_ASSERT_ARG(0 != nr_out_samples); 174 | TSL_ASSERT_ARG(NULL != nr_out_samples_generated); 175 | 176 | *nr_out_samples_generated = 0; 177 | 178 | if (NULL == fir->sb_active && NULL == fir->sb_next) { 179 | goto done; 180 | } 181 | 182 | phase_id = fir->last_phase; 183 | 184 | for (size_t i = 0; i < nr_out_samples && fir->nr_samples > fir->nr_filter_coeffs; i++) { 185 | size_t interp_phase = 0; 186 | TSL_BUG_ON(phase_id >= fir->nr_phase_filters); 187 | 188 | aresult_t filt_ret = dot_product_sample_buffers_real( 189 | fir->sb_active, 190 | fir->sb_next, 191 | fir->sample_offset, 192 | &fir->phase_filters[fir->nr_filter_coeffs * phase_id], 193 | fir->nr_filter_coeffs, 194 | &out_buf[i]); 195 | 196 | if (filt_ret == A_E_DONE) { 197 | *nr_out_samples_generated = i; 198 | goto done; 199 | } else if (FAILED(filt_ret)) { 200 | goto done; 201 | } 202 | 203 | nr_computed_samples++; 204 | 205 | /* Calculate the next phase to process */ 206 | phase_id += fir->decimation; 207 | 208 | interp_phase = phase_id / fir->interpolation; 209 | phase_id = phase_id % fir->interpolation; 210 | nr_consumed += interp_phase; 211 | fir->nr_samples -= interp_phase; 212 | 213 | /* Check if we're going to need to update the active buffer */ 214 | if (fir->sample_offset + interp_phase > fir->sb_active->nr_samples) { 215 | /* Retire the active buffer, shift next to active */ 216 | size_t old_nr_samples = fir->sb_active->nr_samples; 217 | TSL_BUG_IF_FAILED(sample_buf_decref(fir->sb_active)); 218 | fir->sb_active = fir->sb_next; 219 | fir->sb_next = NULL; 220 | fir->sample_offset = fir->sample_offset + interp_phase - old_nr_samples; 221 | } else { 222 | /* Continue walking the current buffer */ 223 | fir->sample_offset += interp_phase; 224 | } 225 | 226 | fir->last_phase = phase_id; 227 | } 228 | 229 | *nr_out_samples_generated = nr_computed_samples; 230 | 231 | done: 232 | return ret; 233 | } 234 | 235 | aresult_t polyphase_fir_can_process(struct polyphase_fir *fir, bool *pcan_process) 236 | { 237 | aresult_t ret = A_OK; 238 | 239 | TSL_ASSERT_ARG(NULL != fir); 240 | TSL_ASSERT_ARG(NULL != pcan_process); 241 | 242 | /* The trick for this is to see if there are at least enough samples to run a single pass of the 243 | * FIR. 244 | */ 245 | *pcan_process = fir->nr_samples >= fir->nr_filter_coeffs; 246 | 247 | return ret; 248 | } 249 | 250 | aresult_t polyphase_fir_full(struct polyphase_fir *fir, bool *pfull) 251 | { 252 | aresult_t ret = A_OK; 253 | 254 | TSL_ASSERT_ARG_DEBUG(NULL != fir); 255 | TSL_ASSERT_ARG_DEBUG(NULL != pfull); 256 | 257 | *pfull = (NULL != fir->sb_next); 258 | 259 | return ret; 260 | } 261 | 262 | -------------------------------------------------------------------------------- /resampler/resampler.c: -------------------------------------------------------------------------------- 1 | /* 2 | * resampler.c - A really brainless app for doing rational resampling of 3 | * samples being passed between applications. 4 | * 5 | * Copyright (c)2017 Phil Vachon 6 | * 7 | * This file is a part of The Standard Library (TSL) 8 | * 9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software 21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #define RES_MSG(sev, sys, msg, ...) MESSAGE("RESAMPLER", sev, sys, msg, ##__VA_ARGS__) 44 | 45 | static 46 | unsigned interpolate = 1; 47 | 48 | static 49 | unsigned decimate = 1; 50 | 51 | static 52 | unsigned input_sample_rate = 0; 53 | 54 | static 55 | int in_fifo = -1; 56 | 57 | static 58 | int out_fifo = -1; 59 | 60 | static 61 | int16_t *filter_coeffs = NULL; 62 | 63 | static 64 | size_t nr_filter_coeffs = 0; 65 | 66 | static 67 | struct polyphase_fir *pfir = NULL; 68 | 69 | static 70 | bool dc_blocker = false; 71 | 72 | static 73 | void _usage(const char *appname) 74 | { 75 | RES_MSG(SEV_INFO, "USAGE", "%s -I [interpolate] -D [decimate] -F [filter file] -S [sample rate] [-b] [in_fifo] [out_fifo]", 76 | appname); 77 | RES_MSG(SEV_INFO, "USAGE", " -b Enable DC blocking filter"); 78 | exit(EXIT_SUCCESS); 79 | } 80 | 81 | static 82 | void _set_options(int argc, char * const argv[]) 83 | { 84 | int arg = -1; 85 | const char *filter_file = NULL; 86 | struct config *cfg CAL_CLEANUP(config_delete) = NULL; 87 | double *filter_coeffs_f = NULL; 88 | 89 | while ((arg = getopt(argc, argv, "I:D:S:F:bh")) != -1) { 90 | switch (arg) { 91 | case 'I': 92 | interpolate = strtoll(optarg, NULL, 0); 93 | break; 94 | case 'D': 95 | decimate = strtoll(optarg, NULL, 0); 96 | break; 97 | case 'S': 98 | input_sample_rate = strtoll(optarg, NULL, 0); 99 | break; 100 | case 'F': 101 | filter_file = optarg; 102 | break; 103 | case 'b': 104 | dc_blocker = true; 105 | RES_MSG(SEV_INFO, "DC-BLOCKER-ENABLED", "Enabling DC Blocking Filter."); 106 | break; 107 | case 'h': 108 | _usage(argv[0]); 109 | break; 110 | } 111 | } 112 | 113 | if (optind > argc) { 114 | RES_MSG(SEV_FATAL, "MISSING-SRC-DEST", "Missing source/destination file"); 115 | exit(EXIT_FAILURE); 116 | } 117 | 118 | if (0 == decimate) { 119 | RES_MSG(SEV_FATAL, "BAD-DECIMATION", "Decimation factor must be a non-zero integer."); 120 | exit(EXIT_FAILURE); 121 | } 122 | 123 | if (0 == decimate) { 124 | RES_MSG(SEV_FATAL, "BAD-INTERPOLATION", "Interpolation factor must be a non-zero integer."); 125 | exit(EXIT_FAILURE); 126 | } 127 | 128 | if (NULL == filter_file) { 129 | RES_MSG(SEV_FATAL, "BAD-FILTER-FILE", "Need to specify a filter JSON file."); 130 | exit(EXIT_FAILURE); 131 | } 132 | 133 | RES_MSG(SEV_INFO, "CONFIG", "Resampling: %u/%u from %u to %f", interpolate, decimate, input_sample_rate, 134 | ((double)interpolate/(double)decimate)*(double)input_sample_rate); 135 | RES_MSG(SEV_INFO, "CONFIG", "Loading filter coefficients from '%s'", filter_file); 136 | 137 | TSL_BUG_IF_FAILED(config_new(&cfg)); 138 | 139 | if (FAILED(config_add(cfg, filter_file))) { 140 | RES_MSG(SEV_INFO, "BAD-CONFIG", "Configuration file '%s' cannot be processed, aborting.", 141 | filter_file); 142 | exit(EXIT_FAILURE); 143 | } 144 | 145 | TSL_BUG_IF_FAILED(config_get_float_array(cfg, &filter_coeffs_f, &nr_filter_coeffs, "lpfCoeffs")); 146 | TSL_BUG_IF_FAILED(TCALLOC((void **)&filter_coeffs, sizeof(int16_t) * nr_filter_coeffs, (size_t)1)); 147 | 148 | for (size_t i = 0; i < nr_filter_coeffs; i++) { 149 | double q15 = 1 << Q_15_SHIFT; 150 | filter_coeffs[i] = (int16_t)(filter_coeffs_f[i] * q15); 151 | } 152 | 153 | if (0 > (out_fifo = open(argv[optind + 1], O_WRONLY))) { 154 | RES_MSG(SEV_INFO, "BAD-OUTPUT", "Bad output - cannot open %s", argv[optind + 1]); 155 | exit(EXIT_FAILURE); 156 | } 157 | 158 | if (0 > (in_fifo = open(argv[optind], O_RDONLY))) { 159 | RES_MSG(SEV_INFO, "BAD-INPUT", "Bad input - cannot open %s", argv[optind]); 160 | exit(EXIT_FAILURE); 161 | } 162 | } 163 | 164 | static 165 | aresult_t _free_sample_buf(struct sample_buf *buf) 166 | { 167 | TSL_BUG_ON(NULL == buf); 168 | TFREE(buf); 169 | return A_OK; 170 | } 171 | 172 | #define NR_SAMPLES 1024 173 | 174 | static 175 | aresult_t _alloc_sample_buf(struct sample_buf **pbuf) 176 | { 177 | aresult_t ret = A_OK; 178 | 179 | struct sample_buf *buf = NULL; 180 | 181 | TSL_ASSERT_ARG(NULL != pbuf); 182 | 183 | if (FAILED(ret = TCALLOC((void **)&buf, NR_SAMPLES * sizeof(int16_t) + sizeof(struct sample_buf), 1ul))) { 184 | goto done; 185 | } 186 | 187 | buf->refcount = 0; 188 | buf->sample_type = COMPLEX_INT_16; 189 | buf->sample_buf_bytes = NR_SAMPLES * sizeof(int16_t); 190 | buf->nr_samples = 0; 191 | buf->release = _free_sample_buf; 192 | buf->priv = NULL; 193 | 194 | *pbuf = buf; 195 | 196 | done: 197 | return ret; 198 | } 199 | 200 | static 201 | int16_t output_buf[NR_SAMPLES]; 202 | 203 | static 204 | aresult_t process_fir(void) 205 | { 206 | int ret = A_OK; 207 | 208 | struct dc_blocker blck; 209 | 210 | TSL_BUG_IF_FAILED(dc_blocker_init(&blck, 0.9999)); 211 | 212 | do { 213 | int op_ret = 0; 214 | struct sample_buf *read_buf = NULL; 215 | size_t new_samples = 0; 216 | bool full = false; 217 | 218 | TSL_BUG_IF_FAILED(polyphase_fir_full(pfir, &full)); 219 | 220 | if (false == full) { 221 | TSL_BUG_IF_FAILED(_alloc_sample_buf(&read_buf)); 222 | 223 | if (0 >= (op_ret = read(in_fifo, read_buf->data_buf, read_buf->sample_buf_bytes))) { 224 | int errnum = errno; 225 | ret = A_E_INVAL; 226 | RES_MSG(SEV_FATAL, "READ-FIFO-FAIL", "Failed to read from input fifo: %s (%d)", 227 | strerror(errnum), errnum); 228 | goto done; 229 | } 230 | 231 | DIAG("Read %d bytes from input FIFO", op_ret); 232 | 233 | TSL_BUG_ON((1 & op_ret) != 0); 234 | 235 | read_buf->nr_samples = op_ret/sizeof(int16_t); 236 | 237 | TSL_BUG_IF_FAILED(polyphase_fir_push_sample_buf(pfir, read_buf)); 238 | } 239 | 240 | /* Filter the samples */ 241 | TSL_BUG_IF_FAILED(polyphase_fir_process(pfir, output_buf, NR_SAMPLES, &new_samples)); 242 | TSL_BUG_ON(0 == new_samples); 243 | 244 | /* Apply DC blocker, if asked */ 245 | if (true == dc_blocker) { 246 | TSL_BUG_IF_FAILED(dc_blocker_apply(&blck, output_buf, new_samples)); 247 | } 248 | 249 | /* Write them out */ 250 | if (0 > (op_ret = write(out_fifo, output_buf, new_samples * sizeof(int16_t)))) { 251 | int errnum = errno; 252 | ret = A_E_INVAL; 253 | RES_MSG(SEV_FATAL, "WRITE-FIFO-FAIL", "Failed to write to output fifo: %s (%d)", 254 | strerror(errnum), errnum); 255 | goto done; 256 | } 257 | 258 | DIAG("Wrote %d bytes to output FIFO", op_ret); 259 | } while (app_running()); 260 | 261 | done: 262 | return ret; 263 | } 264 | 265 | int main(int argc, char * const argv[]) 266 | { 267 | int ret = EXIT_FAILURE; 268 | 269 | TSL_BUG_IF_FAILED(app_init("resampler", NULL)); 270 | TSL_BUG_IF_FAILED(app_sigint_catch(NULL)); 271 | 272 | _set_options(argc, argv); 273 | TSL_BUG_IF_FAILED(polyphase_fir_new(&pfir, nr_filter_coeffs, filter_coeffs, interpolate, decimate)); 274 | 275 | RES_MSG(SEV_INFO, "STARTING", "Starting polyphase resampler"); 276 | 277 | if (FAILED(process_fir())) { 278 | RES_MSG(SEV_FATAL, "FIR-FAILED", "Failed during filtering."); 279 | goto done; 280 | } 281 | 282 | ret = EXIT_SUCCESS; 283 | 284 | done: 285 | polyphase_fir_delete(&pfir); 286 | return ret; 287 | } 288 | 289 | -------------------------------------------------------------------------------- /multifm/file_if.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define SAMPLES_PER_BUF (4 * 1024) 19 | 20 | static 21 | aresult_t __file_read_bytes(struct file_worker_thread *rx, void *tgt_buf, size_t max_bytes, size_t *pbytes_read) 22 | { 23 | aresult_t ret = A_OK; 24 | 25 | long nr_bytes = 0; 26 | 27 | TSL_ASSERT_ARG(NULL != rx); 28 | TSL_ASSERT_ARG(NULL != tgt_buf); 29 | TSL_ASSERT_ARG(0 != max_bytes); 30 | TSL_ASSERT_ARG(NULL != pbytes_read); 31 | 32 | if (0 > (nr_bytes = read(rx->fd, tgt_buf, max_bytes))) { 33 | int errnum = errno; 34 | FL_MSG(SEV_FATAL, "FILE-READ-ERROR", "Failed to read data from file, reason: %s (%d)", 35 | strerror(errnum), errnum); 36 | ret = A_E_INVAL; 37 | goto done; 38 | } 39 | 40 | *pbytes_read = nr_bytes; 41 | 42 | done: 43 | return ret; 44 | } 45 | 46 | static 47 | aresult_t _file_read_cs16(struct file_worker_thread *rx, struct sample_buf *sbuf) 48 | { 49 | aresult_t ret = A_OK; 50 | 51 | size_t nr_read = 0; 52 | 53 | TSL_ASSERT_ARG(NULL != rx); 54 | TSL_ASSERT_ARG(NULL != sbuf); 55 | 56 | if (FAILED(ret = __file_read_bytes(rx, sbuf->data_buf, SAMPLES_PER_BUF * 2 * sizeof(int16_t), &nr_read))) { 57 | goto done; 58 | } 59 | 60 | sbuf->nr_samples = nr_read/(2 * sizeof(int16_t)); 61 | 62 | done: 63 | return ret; 64 | } 65 | 66 | static 67 | aresult_t _file_read_cs8(struct file_worker_thread *rx, struct sample_buf *sbuf) 68 | { 69 | aresult_t ret = A_OK; 70 | 71 | size_t nr_read = 0, 72 | rem = 0; 73 | 74 | int8_t *in_buf = NULL; 75 | int16_t *out_buf = NULL; 76 | 77 | TSL_ASSERT_ARG(NULL != rx); 78 | TSL_ASSERT_ARG(NULL != sbuf); 79 | 80 | /* Read into bounce buffer */ 81 | if (FAILED(ret = __file_read_bytes(rx, rx->bounce_buf, rx->bounce_buf_bytes, &nr_read))) { 82 | DIAG("Failed to read from file, aborting."); 83 | goto done; 84 | } 85 | 86 | TSL_BUG_ON(nr_read > rx->bounce_buf_bytes); 87 | 88 | in_buf = rx->bounce_buf; 89 | out_buf = (int16_t *)sbuf->data_buf; 90 | 91 | /* Convert to s16 just through a cast */ 92 | for (size_t i = 0; i < nr_read; i += 4) { 93 | out_buf[i] = in_buf[i]; 94 | out_buf[i + 1] = in_buf[i + 1]; 95 | out_buf[i + 2] = in_buf[i + 2]; 96 | out_buf[i + 3] = in_buf[i + 3]; 97 | } 98 | 99 | rem = nr_read % 4; 100 | 101 | for (size_t i = 0; i < rem; i++) { 102 | out_buf[nr_read - rem + i] = in_buf[nr_read - rem + i]; 103 | } 104 | 105 | DIAG("Read %zu bytes from input file", nr_read); 106 | 107 | /* Ensure we mark the buffer only for the number of samples actually available */ 108 | sbuf->nr_samples = nr_read/2; 109 | 110 | done: 111 | return ret; 112 | } 113 | 114 | static 115 | aresult_t _file_read_cu8(struct file_worker_thread *rx, struct sample_buf *sbuf) 116 | { 117 | aresult_t ret = A_OK; 118 | 119 | size_t nr_read = 0, 120 | rem = 0; 121 | 122 | int8_t *in_buf = NULL; 123 | int16_t *out_buf = NULL; 124 | 125 | TSL_ASSERT_ARG(NULL != rx); 126 | TSL_ASSERT_ARG(NULL != sbuf); 127 | 128 | /* Read into bounce buffer */ 129 | if (FAILED(ret = __file_read_bytes(rx, rx->bounce_buf, rx->bounce_buf_bytes, &nr_read))) { 130 | goto done; 131 | } 132 | 133 | TSL_BUG_ON(nr_read > rx->bounce_buf_bytes); 134 | 135 | in_buf = rx->bounce_buf; 136 | out_buf = (int16_t *)sbuf->data_buf; 137 | 138 | /* Convert to s16 just through a cast, then subtracting 127 (assumes input is [0, 255]) */ 139 | for (size_t i = 0; i < nr_read; i += 4) { 140 | out_buf[i + 0] = (int16_t)in_buf[i + 0] - 127; 141 | out_buf[i + 1] = (int16_t)in_buf[i + 1] - 127; 142 | out_buf[i + 2] = (int16_t)in_buf[i + 2] - 127; 143 | out_buf[i + 3] = (int16_t)in_buf[i + 3] - 127; 144 | } 145 | 146 | rem = nr_read % 4; 147 | 148 | for (size_t i = 0; i < rem; i++) { 149 | out_buf[nr_read - rem + i] = in_buf[nr_read - rem + i]; 150 | } 151 | 152 | /* Ensure we mark the buffer only for the number of samples actually available */ 153 | sbuf->nr_samples = nr_read/2; 154 | 155 | done: 156 | return ret; 157 | } 158 | 159 | static 160 | aresult_t _file_worker_thread_work(struct receiver *rx) 161 | { 162 | aresult_t ret = A_OK; 163 | 164 | struct file_worker_thread *thr = NULL; 165 | 166 | TSL_ASSERT_ARG(NULL != rx); 167 | 168 | thr = BL_CONTAINER_OF(rx, struct file_worker_thread, rcvr); 169 | 170 | while (receiver_thread_running(rx)) { 171 | /* Start timer */ 172 | uint64_t start_time = tsl_get_clock_monotonic(); 173 | 174 | /* Read in a sample buffer */ 175 | struct sample_buf *sbuf = NULL; 176 | if (FAILED(receiver_sample_buf_alloc(rx, &sbuf))) { 177 | /* TODO: we need to make this saner */ 178 | usleep(500000); 179 | continue; 180 | } 181 | 182 | TSL_BUG_ON(NULL == thr->read_call); 183 | if (FAILED(ret = thr->read_call(thr, sbuf))) { 184 | /* Chances are we ran out of samples to process */ 185 | goto done; 186 | } 187 | 188 | DIAG("There are %u samples in the input read sample buffer", sbuf->nr_samples); 189 | 190 | /* Deliver the sample buffer */ 191 | TSL_BUG_IF_FAILED(receiver_sample_buf_deliver(rx, sbuf)); 192 | 193 | /* Stop timer, check how long this mess took */ 194 | uint64_t total_time = tsl_get_clock_monotonic() - start_time; 195 | 196 | /* Sleep until we should deliver the next one */ 197 | if (total_time < thr->time_per_buf_ns) { 198 | usleep((thr->time_per_buf_ns - total_time)/1000); 199 | } 200 | } 201 | 202 | done: 203 | return ret; 204 | } 205 | 206 | static 207 | aresult_t _file_worker_thread_cleanup(struct receiver *rx) 208 | { 209 | aresult_t ret = A_OK; 210 | 211 | struct file_worker_thread *fwt = NULL; 212 | 213 | TSL_ASSERT_ARG(NULL != rx); 214 | 215 | fwt = BL_CONTAINER_OF(rx, struct file_worker_thread, rcvr); 216 | 217 | if (0 >= fwt->fd) { 218 | close(fwt->fd); 219 | fwt->fd = -1; 220 | } 221 | 222 | if (NULL != fwt->bounce_buf) { 223 | TFREE(fwt->bounce_buf); 224 | } 225 | 226 | return ret; 227 | } 228 | 229 | aresult_t file_worker_thread_new(struct receiver **pthr, struct config *cfg) 230 | { 231 | aresult_t ret = A_OK; 232 | 233 | struct file_worker_thread *thr = NULL; 234 | int fd = -1; 235 | const char *filename = NULL, 236 | *format = NULL; 237 | struct config devcfg = CONFIG_INIT_EMPTY; 238 | enum file_worker_sample_format sample_format = FILE_WORKER_SAMPLE_FORMAT_UNKNOWN; 239 | 240 | TSL_ASSERT_ARG(NULL != pthr); 241 | TSL_ASSERT_ARG(NULL != cfg); 242 | 243 | if (FAILED(ret = config_get(cfg, &devcfg, "device"))) { 244 | FL_MSG(SEV_FATAL, "MISSING-DEVICE-STANZA", "Missing 'device' stanza of configuration, aborting."); 245 | goto done; 246 | } 247 | 248 | if (FAILED(ret = config_get_string(&devcfg, &filename, "filename"))) { 249 | FL_MSG(SEV_FATAL, "CONFIG-NO-FILE", "Need to specify a filename in the device config, aborting."); 250 | goto done; 251 | } 252 | 253 | if (FAILED(config_get_string(&devcfg, &format, "fileFormat"))) { 254 | goto done; 255 | } 256 | 257 | /* Validate that the format is supported */ 258 | if (!strncmp(format, "cs16", 4)) { 259 | sample_format = FILE_WORKER_SAMPLE_FORMAT_S16; 260 | } else if (!strncmp(format, "cs8", 3)) { 261 | sample_format = FILE_WORKER_SAMPLE_FORMAT_S8; 262 | } else if (!strncmp(format, "cu8", 3)) { 263 | sample_format = FILE_WORKER_SAMPLE_FORMAT_U8; 264 | } else { 265 | FL_MSG(SEV_FATAL, "UNSUPPORTED-FILE-FORMAT", "File format [%s] is not supported, aborting.", 266 | format); 267 | ret = A_E_INVAL; 268 | goto done; 269 | } 270 | 271 | FL_MSG(SEV_INFO, "CREATING-FILE-SOURCE", "Sourcing samples in format %s from file [%s]", 272 | format, filename); 273 | 274 | /* Try to open the file */ 275 | if (0 > (fd = open(filename, O_RDONLY))) { 276 | int errnum = errno; 277 | FL_MSG(SEV_FATAL, "BAD-FILE", "Unable to open file [%s], aborting. Reason: %s (%d)", 278 | filename, strerror(errnum), errnum); 279 | ret = A_E_INVAL; 280 | goto done; 281 | } 282 | 283 | if (FAILED(ret = TZAALLOC(thr, SYS_CACHE_LINE_LENGTH))) { 284 | goto done; 285 | } 286 | 287 | thr->fd = fd; 288 | thr->sample_format = sample_format; 289 | 290 | if (sample_format == FILE_WORKER_SAMPLE_FORMAT_S8 || sample_format == FILE_WORKER_SAMPLE_FORMAT_U8) { 291 | DIAG("Creating bounce buffer, input format requires conversion."); 292 | switch (sample_format) { 293 | case FILE_WORKER_SAMPLE_FORMAT_S8: 294 | thr->read_call = _file_read_cs8; 295 | break; 296 | case FILE_WORKER_SAMPLE_FORMAT_U8: 297 | thr->read_call = _file_read_cu8; 298 | break; 299 | default: 300 | PANIC("Sample format is corrupted, aborting."); 301 | } 302 | 303 | if (FAILED(ret = TACALLOC(&thr->bounce_buf, SAMPLES_PER_BUF, 2 * sizeof(int8_t), SYS_CACHE_LINE_LENGTH))) { 304 | goto done; 305 | } 306 | 307 | thr->bounce_buf_bytes = SAMPLES_PER_BUF * 2 * sizeof(int8_t); 308 | } else if (sample_format == FILE_WORKER_SAMPLE_FORMAT_S16) { 309 | thr->read_call = _file_read_cs16; 310 | } else { 311 | FL_MSG(SEV_FATAL, "UNSUPPORTED-SAMPLE-FORMAT", "Sample format [%s] is not supported, aborting.", format); 312 | } 313 | 314 | /* Initialize the receiver subsystem */ 315 | TSL_BUG_IF_FAILED(receiver_init(&thr->rcvr, cfg, _file_worker_thread_work, 316 | _file_worker_thread_cleanup, SAMPLES_PER_BUF)); 317 | 318 | *pthr = &thr->rcvr; 319 | 320 | done: 321 | if (FAILED(ret)) { 322 | if (NULL != thr) { 323 | TFREE(thr); 324 | thr = NULL; 325 | } 326 | 327 | if (fd != -1) { 328 | close(fd); 329 | fd = -1; 330 | } 331 | } 332 | return ret; 333 | } 334 | 335 | -------------------------------------------------------------------------------- /multifm/receiver.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | /** 19 | * Free a live sample buffer. 20 | * 21 | * \param buf The buffer to free 22 | * 23 | * \return A_OK on success, an error code otherwise. 24 | */ 25 | static 26 | aresult_t _sample_buf_release(struct sample_buf *buf) 27 | { 28 | aresult_t ret = A_OK; 29 | 30 | struct frame_alloc *fa = NULL; 31 | 32 | TSL_ASSERT_ARG(NULL != buf); 33 | TSL_BUG_ON(atomic_load(&buf->refcount) != 0); 34 | 35 | fa = buf->priv; 36 | 37 | TSL_BUG_IF_FAILED(frame_free(fa, (void **)&buf)); 38 | 39 | return ret; 40 | } 41 | 42 | /** 43 | * Allocate a sample buffer 44 | */ 45 | aresult_t receiver_sample_buf_alloc(struct receiver *rx, struct sample_buf **pbuf) 46 | { 47 | aresult_t ret = A_OK; 48 | 49 | struct sample_buf *sbuf = NULL; 50 | 51 | TSL_ASSERT_ARG(NULL != rx); 52 | TSL_ASSERT_ARG(NULL != pbuf); 53 | 54 | *pbuf = NULL; 55 | 56 | /* Allocate an output buffer */ 57 | if (FAILED(ret = frame_alloc(rx->samp_alloc, (void **)&sbuf))) { 58 | if (0 == rx->nr_samp_buf_alloc_fails) { 59 | MFM_MSG(SEV_INFO, "NO-SAMPLE-BUFFER", "There are no available sample buffers, dropping received samples."); 60 | } 61 | rx->nr_samp_buf_alloc_fails++; 62 | goto done; 63 | } 64 | 65 | /* Initialize the state for the sample buffer */ 66 | sbuf->release = _sample_buf_release; 67 | sbuf->priv = rx->samp_alloc; 68 | 69 | *pbuf = sbuf; 70 | 71 | done: 72 | return ret; 73 | } 74 | 75 | /** 76 | * Deliver a sample buffer to any waiting consumers 77 | */ 78 | aresult_t receiver_sample_buf_deliver(struct receiver *rx, struct sample_buf *buf) 79 | { 80 | aresult_t ret = A_OK; 81 | 82 | struct demod_thread *dthr = NULL; 83 | 84 | TSL_BUG_ON(0 == buf->nr_samples); 85 | 86 | atomic_store(&buf->refcount, rx->nr_demod_threads); 87 | 88 | /* Make it available to each demodulator/processing thread */ 89 | list_for_each_type(dthr, &rx->demod_threads, dt_node) { 90 | pthread_mutex_lock(&dthr->wq_mtx); 91 | TSL_BUG_IF_FAILED(work_queue_push(&dthr->wq, buf)); 92 | pthread_mutex_unlock(&dthr->wq_mtx); 93 | /* Signal there is data ready, if the thread is waiting on the condvar */ 94 | pthread_cond_signal(&dthr->wq_cv); 95 | } 96 | 97 | return ret; 98 | } 99 | 100 | aresult_t receiver_init(struct receiver *rx, struct config *cfg, 101 | receiver_rx_thread_func_t rx_func, receiver_cleanup_func_t cleanup_func, 102 | size_t samples_per_buf) 103 | { 104 | aresult_t ret = A_OK; 105 | 106 | double *lpf_taps = NULL, 107 | *resample_filter_taps CAL_CLEANUP(free_double_array) = NULL; 108 | 109 | size_t lpf_nr_taps = 0, 110 | arr_ctr = 0; 111 | int decimation_factor = 0, 112 | nr_samp_bufs = 0, 113 | sample_rate = 0, 114 | center_freq = 0; 115 | int16_t *resample_int_filter_taps CAL_CLEANUP(free_i16_array) = NULL; 116 | 117 | struct config channels, 118 | channel; 119 | 120 | struct frame_alloc *sample_buf_alloc = NULL; 121 | 122 | TSL_ASSERT_ARG(NULL != rx); 123 | TSL_ASSERT_ARG(NULL != cfg); 124 | TSL_ASSERT_ARG(NULL != rx_func); 125 | TSL_ASSERT_ARG(NULL != cleanup_func); 126 | TSL_ASSERT_ARG(0 != samples_per_buf); 127 | 128 | rx->muted = true; 129 | rx->samp_alloc = sample_buf_alloc; 130 | rx->cleanup_func = cleanup_func; 131 | rx->thread_func = rx_func; 132 | 133 | if (FAILED(ret = config_get_integer(cfg, &nr_samp_bufs, "nrSampBufs"))) { 134 | MFM_MSG(SEV_INFO, "DEFAULT-SAMP-BUFS", "Setting sample buffer count to 64"); 135 | nr_samp_bufs = 64; 136 | } 137 | 138 | if (FAILED(ret = config_get_integer(cfg, &sample_rate, "sampleRateHz"))) { 139 | MFM_MSG(SEV_INFO, "NO-SAMPLE-RATE", "Need to specify a sample rate, in Hertz."); 140 | goto done; 141 | } 142 | 143 | if (FAILED(ret = config_get_integer(cfg, ¢er_freq, "centerFreqHz"))) { 144 | MFM_MSG(SEV_INFO, "NO-CENTER-FREQ", "You forgot to specify a center frequency, in Hz."); 145 | goto done; 146 | } 147 | 148 | MFM_MSG(SEV_INFO, "SAMPLE-RATE", "Sample rate is set to %u Hz", sample_rate); 149 | MFM_MSG(SEV_INFO, "CENTER-FREQ", "Center Frequency is %u Hz", center_freq); 150 | 151 | /* 152 | * Create the memory frame allocator for sample buffers 153 | */ 154 | TSL_BUG_IF_FAILED(frame_alloc_new(&rx->samp_alloc, 155 | sizeof(struct sample_buf) + 156 | samples_per_buf * sizeof(int16_t) * 2, 157 | nr_samp_bufs)); 158 | 159 | /* Grab the decimation factor and other parameters first, just to validate them. */ 160 | if (FAILED(ret = config_get_integer(cfg, &decimation_factor, "decimationFactor"))) { 161 | decimation_factor = 1; 162 | MFM_MSG(SEV_INFO, "NO-DECIMATION", "Not decimating the output signal: using full bandwidth."); 163 | ret = A_E_INVAL; 164 | goto done; 165 | } 166 | 167 | if (0 >= decimation_factor) { 168 | MFM_MSG(SEV_ERROR, "BAD-DECIMATION-FACTOR", "Decimation factor of '%d' is not valid.", 169 | decimation_factor); 170 | ret = A_E_INVAL; 171 | goto done; 172 | } 173 | 174 | /* Check that there's a filter specified */ 175 | if (FAILED(ret = config_get_float_array(cfg, &lpf_taps, &lpf_nr_taps, "lpfTaps"))) { 176 | MFM_MSG(SEV_ERROR, "BAD-FILTER-TAPS", "Need to provide a baseband filter with at least two filter taps as 'lpfTaps'."); 177 | goto done; 178 | } 179 | 180 | if (1 >= lpf_nr_taps) { 181 | MFM_MSG(SEV_ERROR, "INSUFF-FILTER-TAPS", "Not enough filter taps for the low-pass filter."); 182 | ret = A_E_INVAL; 183 | goto done; 184 | } 185 | 186 | list_init(&rx->demod_threads); 187 | 188 | /* Create the demodulator threads, walking the list of channels to be processed. */ 189 | if (FAILED(ret = config_get(cfg, &channels, "channels"))) { 190 | MFM_MSG(SEV_ERROR, "MISSING-CHANNELS", "Need to specify at least one channel to demodulate."); 191 | ret = A_E_INVAL; 192 | goto done; 193 | } 194 | 195 | CONFIG_ARRAY_FOR_EACH(channel, &channels, ret, arr_ctr) { 196 | const char *fifo_name = NULL, 197 | *signal_debug = NULL; 198 | int nb_center_freq = -1; 199 | struct demod_thread *dmt = NULL; 200 | double channel_gain = 1.0, 201 | channel_gain_db = 0.0; 202 | 203 | if (FAILED(ret = config_get_string(&channel, &fifo_name, "outFifo"))) { 204 | MFM_MSG(SEV_ERROR, "MISSING-FIFO-ID", "Missing output FIFO filename, aborting."); 205 | goto done; 206 | } 207 | 208 | if (FAILED(ret = config_get_integer(&channel, &nb_center_freq, "chanCenterFreq"))) { 209 | MFM_MSG(SEV_ERROR, "MISSING-CENTER-FREQ", "Missing output channel center frequency."); 210 | goto done; 211 | } 212 | 213 | if (!FAILED(ret = config_get_string(&channel, &signal_debug, "signalDebugFile"))) { 214 | MFM_MSG(SEV_INFO, "WRITING-SIGNAL-DEBUG", "The channel at frequency %d will have raw I/Q written to '%s'", 215 | nb_center_freq, signal_debug); 216 | } 217 | 218 | if (!FAILED(ret = config_get_float(&channel, &channel_gain_db, "dBGain"))) { 219 | /* Convert the gain to linear units */ 220 | channel_gain = pow(10.0, channel_gain_db/10.0); 221 | DIAG("Setting input channel gain to: %f (%f dB)", channel_gain, channel_gain_db); 222 | } 223 | 224 | DIAG("Center Frequency: %d Hz FIFO: %s", nb_center_freq, fifo_name); 225 | 226 | /* Create demodulator thread object */ 227 | if (FAILED(ret = demod_thread_new(&dmt, -1, (int32_t)nb_center_freq - center_freq, 228 | sample_rate, fifo_name, decimation_factor, lpf_taps, lpf_nr_taps, 229 | signal_debug, 230 | channel_gain))) 231 | { 232 | MFM_MSG(SEV_ERROR, "FAILED-DEMOD-THREAD", "Failed to create demodulator thread, aborting."); 233 | goto done; 234 | } 235 | 236 | list_init(&dmt->dt_node); 237 | list_append(&rx->demod_threads, &dmt->dt_node); 238 | rx->nr_demod_threads++; 239 | 240 | MFM_MSG(SEV_INFO, "CHANNEL", "[%zu]: %4.5f MHz Gain: %f dB -> [%s]%s%s", 241 | rx->nr_demod_threads, (double)nb_center_freq/1e6, channel_gain_db, fifo_name, 242 | (NULL != signal_debug ? " DEBUG: " : ""), 243 | (NULL != signal_debug ? signal_debug : "")); 244 | } 245 | if (FAILED(ret)) { 246 | MFM_MSG(SEV_ERROR, "CHANNEL-SETUP-FAILURE", "Error reading array of channels, aborting."); 247 | goto done; 248 | } 249 | 250 | done: 251 | if (NULL != lpf_taps) { 252 | TFREE(lpf_taps); 253 | } 254 | 255 | return ret; 256 | } 257 | 258 | static 259 | aresult_t _receiver_worker_thread(struct worker_thread *wthr) 260 | { 261 | struct receiver *rx = BL_CONTAINER_OF(wthr, struct receiver, wthr); 262 | 263 | TSL_BUG_ON(NULL == rx->thread_func); 264 | 265 | return rx->thread_func(rx); 266 | } 267 | 268 | aresult_t receiver_start(struct receiver *rx) 269 | { 270 | aresult_t ret = A_OK; 271 | 272 | TSL_ASSERT_ARG(NULL != rx); 273 | 274 | if (FAILED(ret = worker_thread_new(&rx->wthr, _receiver_worker_thread, WORKER_THREAD_CPU_MASK_ANY))) { 275 | MFM_MSG(SEV_ERROR, "THREAD-START-FAIL", "Failed to start worker thread, aborting."); 276 | goto done; 277 | } 278 | 279 | done: 280 | return ret; 281 | } 282 | 283 | aresult_t receiver_cleanup(struct receiver **prx) 284 | { 285 | aresult_t ret = A_OK; 286 | 287 | struct receiver *rx = NULL; 288 | struct demod_thread *cur = NULL, 289 | *tmp = NULL; 290 | 291 | TSL_ASSERT_ARG(NULL != prx); 292 | TSL_ASSERT_ARG(NULL != *prx); 293 | 294 | rx = *prx; 295 | 296 | /* Clean up the receiver state */ 297 | TSL_BUG_IF_FAILED(rx->cleanup_func(rx)); 298 | 299 | /* Shut down the worker thread */ 300 | TSL_BUG_IF_FAILED(worker_thread_request_shutdown(&rx->wthr)); 301 | TSL_BUG_IF_FAILED(worker_thread_delete(&rx->wthr)); 302 | 303 | list_for_each_type_safe(cur, tmp, &rx->demod_threads, dt_node) { 304 | list_del(&cur->dt_node); 305 | TSL_BUG_IF_FAILED(demod_thread_delete(&cur)); 306 | } 307 | 308 | TSL_BUG_IF_FAILED(frame_alloc_delete(&rx->samp_alloc)); 309 | 310 | return ret; 311 | } 312 | 313 | aresult_t receiver_set_mute(struct receiver *rx, bool mute) 314 | { 315 | aresult_t ret = A_OK; 316 | 317 | TSL_ASSERT_ARG(NULL != rx); 318 | 319 | rx->muted = mute; 320 | 321 | return ret; 322 | } 323 | 324 | bool receiver_thread_running(struct receiver *rx) 325 | { 326 | TSL_BUG_ON(NULL == rx); 327 | 328 | return worker_thread_is_running(&rx->wthr); 329 | } 330 | 331 | -------------------------------------------------------------------------------- /etc/flex_25khz_lpf_3mhz.json: -------------------------------------------------------------------------------- 1 | {"lpfTaps" : [-0.00010062782319930877, -0.00010124737631463218, 2 | -0.00010192428884809439, -0.00010265940757567257, 3 | -0.00010345340181739212, -0.0001043067597643223, 4 | -0.00010521978495773791, -0.00010619259292466449, 5 | -0.00010722510797390859, -0.00010831706015655065, 6 | -0.00010946798239475351, -0.00011067720778260815, 7 | -0.00011194386706260679, -0.00011326688628119332, 8 | -0.00011464498462670334, -0.00011607667245286, -0.00011756024949084552, 9 | -0.00011909380325281626, -0.00012067520762957794, 10 | -0.00012230212168497855, -0.00012397198864941855, 11 | -0.00012568203511471629, -0.00012742927043240016, 12 | -0.0001292104863173341, -0.00013102225665841306, 13 | -0.00013286093753789169, -0.0001347226674607408, 14 | -0.00013660336779524416, -0.00013849874342587853, 15 | -0.00014040428361933552, -0.00014231526310436532, 16 | -0.00014422674336594485, -0.00014613357415408309, 17 | -0.00014803039520740115, -0.00014991163819143337, 18 | -0.0001517715288514165, -0.00015360408937914341, 19 | -0.00015540314099327768, -0.00015716230673233384, 20 | -0.00015887501445934664, -0.00016053450007706306, 21 | -0.00016213381095230874, -0.00016366580954799278, 22 | -0.00016512317726102989, -0.00016649841846427853, 23 | -0.0001677838647504059, -0.0001689716793754128, 24 | -0.00017005386189936815, -0.00017102225302172453, 25 | -0.00017186853960840873, -0.00017258425990770467, 26 | -0.00017316080895177102, -0.00017358944414046487, 27 | -0.00017386129100397304, -0.00017396734914058168, 28 | -0.0001738984983257533, -0.00017364550478851486, 29 | -0.00017319902765100111, -0.00017254962552684081, 30 | -0.0001716877632739178, -0.00017060381889688928, 31 | -0.00016928809059469404, -0.00016773080394814105, 32 | -0.00016592211924252632, -0.0001638521389200893, 33 | -0.00016151091515698575, -0.00015888845755932792, 34 | -0.00015597474097271151, -0.000152759713399533, 35 | -0.00014923330401828263, -0.00014538543129888234, 36 | -0.00014120601120803369, -0.00013668496549843793, 37 | -0.00013181223007564555, -0.00012657776343620472, 38 | -0.00012097155517068811, -0.00011498363452508798, 39 | -0.00010860407901399693, -0.00010182302307891475, 40 | -9.4630666784951235E-5, -8.701728454913348E-5, -7.8973233893469768E-5, 41 | -7.0488964215863423E-5, -6.1555025571927662E-5, 42 | -5.2162077460711524E-5, -4.2300897607304434E-5, 43 | -3.1962390735260706E-5, -2.1137597321766377E-5, 44 | -9.8177023284331862E-6, 2.0059560993854314E-6, 1.4341877971889662E-5, 45 | 2.7198392839862226E-5, 4.058365123255531E-5, 5.4505616155550573E-5, 46 | 6.8972054655685674E-5, 8.3990529460130687E-5, 9.9568390696658341E-5, 47 | 0.00011571276770213215, 0.00013243056092617934, 48 | 0.00014972843393699648, 0.0001676128055361632, 0.00018608984198929137, 49 | 0.00020516544937928166, 0.00022484526608888307, 50 | 0.0002451346554191792, 0.00026603869835055535, 0.00028756218645260395, 51 | 0.00030970961494934765, 0.00033248517594605318, 52 | 0.00035589275182382034, 0.00037993590880801986, 53 | 0.00040461789071653484, 0.00042994161289366245, 54 | 0.000455909656335397, 0.00048252426201169046, 0.000509787325391166, 55 | 0.00053770039117361651, 0.00056626464823547162, 56 | 0.00059548092479329946, 0.00062534968379021963, 57 | 0.00065587101850999457, 0.00068704464842336561, 58 | 0.0007188699152710739, 0.00075134577938781844, 0.00078447081627123594, 59 | 0.00081824321339982371, 0.0008526607673035436, 0.00088772088089065007, 60 | 0.00092342056103413375, 0.00095975641642095065, 61 | 0.00099672465566703287, 0.0010343210857008858, 0.0010725411104183756, 62 | 0.0011113797296111016, 0.00115083153817057, 0.0011908907255701682, 63 | 0.0012315510756267154, 0.0012728059665432043, 0.0013146483712340854, 64 | 0.0013570708579342713, 0.001400065591092813, 0.0014436243325519796, 65 | 0.0014877384430122731, 0.0015323988837836742, 0.0015775962188232157, 66 | 0.001623320617058758, 0.0016695618549986051, 0.0017163093196264083, 67 | 0.0017635520115805779, 0.0018112785486171755, 0.0018594771693550966, 68 | 0.0019081357373020823, 0.0019572417451599079, 0.0020067823194068819, 69 | 0.0020567442251555558, 0.0021071138712833496, 0.0021578773158335658, 70 | 0.0022090202716840758, 0.0022605281124807165, 0.0023123858788322766, 71 | 0.002364578284763694, 0.0024170897244239174, 0.002469904279044663, 72 | 0.0025230057241461107, 0.0025763775369853749, 0.0026300029042434056, 73 | 0.0026838647299457542, 0.0027379456436125108, 0.00279222800863244, 74 | 0.0028466939308562709, 0.0029013252674038223, 0.0029561036356795336, 75 | 0.0030110104225907685, 0.0030660267939630987, 0.0031211337041466445, 76 | 0.0031763119058073031, 0.0032315419598966757, 0.0032868042457942087, 77 | 0.0033420789716150511, 0.0033973461846768861, 0.0034525857821189267, 78 | 0.0035077775216661154, 0.0035629010325314205, 0.0036179358264490482, 79 | 0.0036728613088312261, 0.0037276567900411408, 0.0037823014967744988, 80 | 0.003836774583542063, 0.0038910551442454646, 0.0039451222238384535, 81 | 0.00399895483006573, 0.0040525319452713532, 0.00410583253826874, 82 | 0.00415883557626411, 0.0042115200368252562, 0.0042638649198874466, 83 | 0.0043158492597881529, 0.0043674521373224121, 0.0044186526918104166, 84 | 0.004469430133169046, 0.0045197637539789734, 0.0045696329415389359, 85 | 0.0046190171898988575, 0.0046678961118633705, 0.0047162494509574281, 86 | 0.0047640570933455972, 0.0048112990796967325, 0.0048579556169856966, 87 | 0.0049040070902238381, 0.0049494340741100054, 0.0049942173445938765, 88 | 0.0050383378903434833, 0.0050817769241087961, 0.005124515893973401, 89 | 0.0051665364944862586, 0.005207820677665698, 0.0052483506638678328, 90 | 0.0052881089525116873, 0.0053270783326534316, 0.0053652418934021827, 91 | 0.0054025830341699835, 0.005439085474748649, 0.0054747332652062943, 92 | 0.0055095107955965015, 0.0055434028054731537, 0.005576394393204195, 93 | 0.0056084710250776026, 0.0056396185441930794, 0.005669823179133119, 94 | 0.0056990715524071651, 0.0057273506886629027, 0.005754648022658705, 95 | 0.0057809514069915588, 0.0058062491195749154, 0.0058305298708610726, 96 | 0.0058537828108029136, 0.0058759975355499759, 0.0058971640938740638, 97 | 0.005917272993319735, 0.0059363152060752638, 0.0059542821745598284, 98 | 0.0059711658167229233, 0.0059869585310521308, 0.0060016532012856939, 99 | 0.0060152432008264258, 0.006027722396853839, 0.0060390851541314676, 100 | 0.006049326338506673, 0.0060584413201003684, 0.0060664259761844027, 101 | 0.0060732766937444714, 0.006078990371726759, 0.006083564422966631, 102 | 0.0060869967757980394, 0.0060892858753424208, 0.0060904306844762083, 103 | 0.0060904306844762083, 0.0060892858753424208, 0.0060869967757980394, 104 | 0.006083564422966631, 0.006078990371726759, 0.0060732766937444714, 105 | 0.0060664259761844027, 0.0060584413201003684, 0.006049326338506673, 106 | 0.0060390851541314676, 0.006027722396853839, 0.0060152432008264258, 107 | 0.0060016532012856939, 0.0059869585310521308, 0.0059711658167229233, 108 | 0.0059542821745598284, 0.0059363152060752638, 0.005917272993319735, 109 | 0.0058971640938740638, 0.0058759975355499759, 0.0058537828108029136, 110 | 0.0058305298708610726, 0.0058062491195749154, 0.0057809514069915588, 111 | 0.005754648022658705, 0.0057273506886629027, 0.0056990715524071651, 112 | 0.005669823179133119, 0.0056396185441930794, 0.0056084710250776026, 113 | 0.005576394393204195, 0.0055434028054731537, 0.0055095107955965015, 114 | 0.0054747332652062943, 0.005439085474748649, 0.0054025830341699835, 115 | 0.0053652418934021827, 0.0053270783326534316, 0.0052881089525116873, 116 | 0.0052483506638678328, 0.005207820677665698, 0.0051665364944862586, 117 | 0.005124515893973401, 0.0050817769241087961, 0.0050383378903434833, 118 | 0.0049942173445938765, 0.0049494340741100054, 0.0049040070902238381, 119 | 0.0048579556169856966, 0.0048112990796967325, 0.0047640570933455972, 120 | 0.0047162494509574281, 0.0046678961118633705, 0.0046190171898988575, 121 | 0.0045696329415389359, 0.0045197637539789734, 0.004469430133169046, 122 | 0.0044186526918104166, 0.0043674521373224121, 0.0043158492597881529, 123 | 0.0042638649198874466, 0.0042115200368252562, 0.00415883557626411, 124 | 0.00410583253826874, 0.0040525319452713532, 0.00399895483006573, 125 | 0.0039451222238384535, 0.0038910551442454646, 0.003836774583542063, 126 | 0.0037823014967744988, 0.0037276567900411408, 0.0036728613088312261, 127 | 0.0036179358264490482, 0.0035629010325314205, 0.0035077775216661154, 128 | 0.0034525857821189267, 0.0033973461846768861, 0.0033420789716150511, 129 | 0.0032868042457942087, 0.0032315419598966757, 0.0031763119058073031, 130 | 0.0031211337041466445, 0.0030660267939630987, 0.0030110104225907685, 131 | 0.0029561036356795336, 0.0029013252674038223, 0.0028466939308562709, 132 | 0.00279222800863244, 0.0027379456436125108, 0.0026838647299457542, 133 | 0.0026300029042434056, 0.0025763775369853749, 0.0025230057241461107, 134 | 0.002469904279044663, 0.0024170897244239174, 0.002364578284763694, 135 | 0.0023123858788322766, 0.0022605281124807165, 0.0022090202716840758, 136 | 0.0021578773158335658, 0.0021071138712833496, 0.0020567442251555558, 137 | 0.0020067823194068819, 0.0019572417451599079, 0.0019081357373020823, 138 | 0.0018594771693550966, 0.0018112785486171755, 0.0017635520115805779, 139 | 0.0017163093196264083, 0.0016695618549986051, 0.001623320617058758, 140 | 0.0015775962188232157, 0.0015323988837836742, 0.0014877384430122731, 141 | 0.0014436243325519796, 0.001400065591092813, 0.0013570708579342713, 142 | 0.0013146483712340854, 0.0012728059665432043, 0.0012315510756267154, 143 | 0.0011908907255701682, 0.00115083153817057, 0.0011113797296111016, 144 | 0.0010725411104183756, 0.0010343210857008858, 0.00099672465566703287, 145 | 0.00095975641642095065, 0.00092342056103413375, 146 | 0.00088772088089065007, 0.0008526607673035436, 0.00081824321339982371, 147 | 0.00078447081627123594, 0.00075134577938781844, 148 | 0.0007188699152710739, 0.00068704464842336561, 0.00065587101850999457, 149 | 0.00062534968379021963, 0.00059548092479329946, 150 | 0.00056626464823547162, 0.00053770039117361651, 151 | 0.000509787325391166, 0.00048252426201169046, 0.000455909656335397, 152 | 0.00042994161289366245, 0.00040461789071653484, 153 | 0.00037993590880801986, 0.00035589275182382034, 154 | 0.00033248517594605318, 0.00030970961494934765, 155 | 0.00028756218645260395, 0.00026603869835055535, 156 | 0.0002451346554191792, 0.00022484526608888307, 0.00020516544937928166, 157 | 0.00018608984198929137, 0.0001676128055361632, 0.00014972843393699648, 158 | 0.00013243056092617934, 0.00011571276770213215, 159 | 9.9568390696658341E-5, 8.3990529460130687E-5, 6.8972054655685674E-5, 160 | 5.4505616155550573E-5, 4.058365123255531E-5, 2.7198392839862226E-5, 161 | 1.4341877971889662E-5, 2.0059560993854314E-6, -9.8177023284331862E-6, 162 | -2.1137597321766377E-5, -3.1962390735260706E-5, 163 | -4.2300897607304434E-5, -5.2162077460711524E-5, 164 | -6.1555025571927662E-5, -7.0488964215863423E-5, 165 | -7.8973233893469768E-5, -8.701728454913348E-5, -9.4630666784951235E-5, 166 | -0.00010182302307891475, -0.00010860407901399693, 167 | -0.00011498363452508798, -0.00012097155517068811, 168 | -0.00012657776343620472, -0.00013181223007564555, 169 | -0.00013668496549843793, -0.00014120601120803369, 170 | -0.00014538543129888234, -0.00014923330401828263, 171 | -0.000152759713399533, -0.00015597474097271151, 172 | -0.00015888845755932792, -0.00016151091515698575, 173 | -0.0001638521389200893, -0.00016592211924252632, 174 | -0.00016773080394814105, -0.00016928809059469404, 175 | -0.00017060381889688928, -0.0001716877632739178, 176 | -0.00017254962552684081, -0.00017319902765100111, 177 | -0.00017364550478851486, -0.0001738984983257533, 178 | -0.00017396734914058168, -0.00017386129100397304, 179 | -0.00017358944414046487, -0.00017316080895177102, 180 | -0.00017258425990770467, -0.00017186853960840873, 181 | -0.00017102225302172453, -0.00017005386189936815, 182 | -0.0001689716793754128, -0.0001677838647504059, 183 | -0.00016649841846427853, -0.00016512317726102989, 184 | -0.00016366580954799278, -0.00016213381095230874, 185 | -0.00016053450007706306, -0.00015887501445934664, 186 | -0.00015716230673233384, -0.00015540314099327768, 187 | -0.00015360408937914341, -0.0001517715288514165, 188 | -0.00014991163819143337, -0.00014803039520740115, 189 | -0.00014613357415408309, -0.00014422674336594485, 190 | -0.00014231526310436532, -0.00014040428361933552, 191 | -0.00013849874342587853, -0.00013660336779524416, 192 | -0.0001347226674607408, -0.00013286093753789169, 193 | -0.00013102225665841306, -0.0001292104863173341, 194 | -0.00012742927043240016, -0.00012568203511471629, 195 | -0.00012397198864941855, -0.00012230212168497855, 196 | -0.00012067520762957794, -0.00011909380325281626, 197 | -0.00011756024949084552, -0.00011607667245286, -0.00011464498462670334, 198 | -0.00011326688628119332, -0.00011194386706260679, 199 | -0.00011067720778260815, -0.00010946798239475351, 200 | -0.00010831706015655065, -0.00010722510797390859, 201 | -0.00010619259292466449, -0.00010521978495773791, 202 | -0.0001043067597643223, -0.00010345340181739212, 203 | -0.00010265940757567257, -0.00010192428884809439, 204 | -0.00010124737631463218, -0.00010062782319930877 205 | ]} 206 | 207 | -------------------------------------------------------------------------------- /ais/ais_decode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | struct ais_decode { 13 | struct ais_demod *demod; 14 | uint32_t freq; 15 | ais_decode_on_position_report_func_t on_position_report; 16 | ais_decode_on_base_station_report_func_t on_base_station_report; 17 | ais_decode_on_static_voyage_data_func_t on_static_voyage_data; 18 | }; 19 | 20 | #define DUMP(...) 21 | 22 | static 23 | uint32_t _ais_decode_get_bitfield(const uint8_t *packet, size_t packet_len, 24 | size_t offset, size_t len) 25 | { 26 | uint64_t acc = 0; 27 | size_t nr_bytes = 0, 28 | start_byte = offset/8, 29 | end_byte = (offset + len + 7)/8, 30 | end_rem_bits = 0; 31 | 32 | nr_bytes = end_byte - start_byte; 33 | 34 | TSL_BUG_ON(nr_bytes + start_byte > packet_len); 35 | 36 | for (size_t i = 0; i < nr_bytes; i++) { 37 | acc <<= 8; 38 | acc |= packet[i + start_byte]; 39 | } 40 | 41 | end_rem_bits = (end_byte * 8) - (offset + len); 42 | 43 | acc >>= end_rem_bits; 44 | acc &= ((1ull << len) - 1); 45 | 46 | return (uint32_t)acc; 47 | } 48 | 49 | static 50 | int32_t _ais_decode_get_bitfield_signed(const uint8_t *packet, size_t packet_len, 51 | size_t offset, size_t len) 52 | { 53 | uint32_t t = _ais_decode_get_bitfield(packet, packet_len, offset, len); 54 | int32_t v = t << (32 - len); 55 | return v >> (32 - len); 56 | } 57 | 58 | static 59 | void _ais_decode_get_string(const uint8_t *packet, size_t packet_len, 60 | size_t offset, size_t nr_chars, char *dest) 61 | { 62 | size_t base = offset; 63 | memset(dest, 0, nr_chars); 64 | 65 | for (size_t i = 0; i < nr_chars; i++) { 66 | char v = _ais_decode_get_bitfield(packet, packet_len, base, 6); 67 | /* Convert out of the 6-bit ASCII format */ 68 | dest[i] = v > 0x1f ? v : v + 0x40; 69 | base += 6; 70 | } 71 | } 72 | 73 | static 74 | aresult_t _ais_decode_position_report(struct ais_decode *decode, const uint8_t *packet, size_t packet_len, 75 | unsigned msg_id, unsigned repeat, uint32_t mmsi, const char *raw_msg) 76 | { 77 | aresult_t ret = A_OK; 78 | 79 | struct ais_position_report rpt; 80 | 81 | TSL_ASSERT_ARG(NULL != decode); 82 | TSL_ASSERT_ARG(NULL != packet); 83 | TSL_ASSERT_ARG(0 != packet_len); 84 | 85 | memset(&rpt, 0, sizeof(rpt)); 86 | 87 | rpt.mmsi = mmsi; 88 | rpt.nav_stat = _ais_decode_get_bitfield(packet, packet_len, 38, 4); 89 | rpt.rate_of_turn = _ais_decode_get_bitfield_signed(packet, packet_len, 42, 8); 90 | rpt.speed_over_ground = (float)_ais_decode_get_bitfield(packet, packet_len, 50, 10)/10.0; 91 | rpt.position_acc = _ais_decode_get_bitfield(packet, packet_len, 60, 1); 92 | rpt.longitude = (float)_ais_decode_get_bitfield_signed(packet, packet_len, 61, 28)/600000.0; 93 | rpt.latitude = (float)_ais_decode_get_bitfield_signed(packet, packet_len, 89, 27)/600000.0; 94 | rpt.course = _ais_decode_get_bitfield(packet, packet_len, 116, 12); 95 | rpt.heading = _ais_decode_get_bitfield(packet, packet_len, 128, 9); 96 | rpt.timestamp = _ais_decode_get_bitfield(packet, packet_len, 137, 6); 97 | 98 | DUMP(" Nav Stat = %1u RoT = %3f SoG = %4.2f (%9.6f, %9.6f), CoG = %u Heading = %u Timestamp = %u\n", 99 | rpt.nav_stat, (double)rpt.rate_of_turn, (double)rpt.speed_over_ground, (double)rpt.latitude, 100 | (double)rpt.longitude, rpt.course, rpt.heading, rpt.timestamp); 101 | 102 | if (NULL != decode->on_position_report) { 103 | decode->on_position_report(decode, NULL, &rpt, raw_msg); 104 | } 105 | 106 | return ret; 107 | } 108 | 109 | static 110 | const char *_ais_decode_epfd_type[] = { 111 | [ 0] = "Undefined", 112 | [ 1] = "GPS", 113 | [ 2] = "GLONASS", 114 | [ 3] = "Combined GPS/GLONASS", 115 | [ 4] = "Loran-C", 116 | [ 5] = "Chayka", 117 | [ 6] = "Integrated Navigation System", 118 | [ 7] = "Surveyed", 119 | [ 8] = "Galileo", 120 | [ 9] = "Unknown 9", 121 | [10] = "Unknown 10", 122 | [11] = "Unknown 11", 123 | [12] = "Unknown 12", 124 | [13] = "Unknown 13", 125 | [14] = "Unknown 14", 126 | [15] = "Unknown 15", 127 | }; 128 | 129 | static 130 | aresult_t _ais_decode_base_station_report(struct ais_decode *decode, const uint8_t *packet, size_t packet_len, 131 | unsigned msg_id, unsigned repeat, uint32_t mmsi, const char *raw_msg) 132 | { 133 | aresult_t ret = A_OK; 134 | 135 | struct ais_base_station_report bsr; 136 | 137 | memset(&bsr, 0, sizeof(bsr)); 138 | 139 | bsr.mmsi = mmsi; 140 | 141 | bsr.year = _ais_decode_get_bitfield(packet, packet_len, 38, 14); 142 | bsr.month = _ais_decode_get_bitfield(packet, packet_len, 52, 4); 143 | bsr.day = _ais_decode_get_bitfield(packet, packet_len, 56, 5); 144 | bsr.hour = _ais_decode_get_bitfield(packet, packet_len, 61, 5); 145 | bsr.minute = _ais_decode_get_bitfield(packet, packet_len, 66, 6); 146 | bsr.second = _ais_decode_get_bitfield(packet, packet_len, 72, 6); 147 | 148 | bsr.longitude = (float)_ais_decode_get_bitfield_signed(packet, packet_len, 79, 28)/600000.0; 149 | bsr.latitude = (float)_ais_decode_get_bitfield_signed(packet, packet_len, 107, 27)/600000.0; 150 | 151 | bsr.epfd_type = _ais_decode_get_bitfield(packet, packet_len, 134, 4); 152 | bsr.epfd_name = _ais_decode_epfd_type[bsr.epfd_type & 0xf]; 153 | 154 | DUMP(" %04u-%02u-%02u-%02u:%02u:%02u - (%9.6f, %9.6f) - EPFD: %s (%u)\n", 155 | bsr.year, bsr.month, bsr.day, bsr.hour, bsr.minute, bsr.second, bsr.latitude, 156 | bsr.longitude, bsr.epfd_name, bsr.epfd_type); 157 | 158 | if (NULL != decode->on_base_station_report) { 159 | decode->on_base_station_report(decode, NULL, &bsr, raw_msg); 160 | } 161 | 162 | return ret; 163 | } 164 | 165 | static 166 | aresult_t _ais_decode_static_voyage_data(struct ais_decode *decode, const uint8_t *packet, size_t packet_len, 167 | unsigned msg_id, unsigned repeat, uint32_t mmsi, const char *raw_msg) 168 | { 169 | aresult_t ret = A_OK; 170 | 171 | struct ais_static_voyage_data asd; 172 | 173 | TSL_ASSERT_ARG(NULL != decode); 174 | TSL_ASSERT_ARG(NULL != packet); 175 | TSL_ASSERT_ARG(0 != packet_len); 176 | 177 | asd.mmsi = mmsi; 178 | 179 | asd.version = _ais_decode_get_bitfield(packet, packet_len, 38, 2); 180 | asd.imo_number = _ais_decode_get_bitfield(packet, packet_len, 40, 30); 181 | 182 | _ais_decode_get_string(packet, packet_len, 70, 7, asd.callsign); 183 | asd.callsign[7] = '\0'; 184 | _ais_decode_get_string(packet, packet_len, 112, 20, asd.ship_name); 185 | asd.ship_name[20] = '\0'; 186 | 187 | asd.ship_type = _ais_decode_get_bitfield(packet, packet_len, 232, 8); 188 | asd.dim_to_bow = _ais_decode_get_bitfield(packet, packet_len, 240, 9); 189 | asd.dim_to_stern = _ais_decode_get_bitfield(packet, packet_len, 249, 9); 190 | asd.dim_to_port = _ais_decode_get_bitfield(packet, packet_len, 258, 6); 191 | asd.dim_to_starboard = _ais_decode_get_bitfield(packet, packet_len, 264, 6); 192 | asd.fix_type = _ais_decode_get_bitfield(packet, packet_len, 270, 4); 193 | asd.epfd_name = _ais_decode_epfd_type[asd.fix_type & 0xf]; 194 | 195 | asd.eta_month = _ais_decode_get_bitfield(packet, packet_len, 274, 4); 196 | asd.eta_day = _ais_decode_get_bitfield(packet, packet_len, 278, 5); 197 | asd.eta_hour = _ais_decode_get_bitfield(packet, packet_len, 283, 5); 198 | asd.eta_minute = _ais_decode_get_bitfield(packet, packet_len, 288, 6); 199 | 200 | asd.draught = (float)_ais_decode_get_bitfield(packet, packet_len, 294, 8)/10.0; 201 | 202 | _ais_decode_get_string(packet, packet_len, 302, 20, asd.destination); 203 | asd.destination[20] = '\0'; 204 | 205 | DUMP(" V=%u Imo=%9u Callsign=[%s] Vessel=[%s] ShipType=%3u (%u, %u, %u, %u) Fix=%s ETA=%u-%u %u:%u Draught=%4.1f Destination=[%s]\n", 206 | asd.version, asd.imo_number, asd.callsign, asd.ship_name, asd.ship_type, asd.dim_to_bow, 207 | asd.dim_to_stern, asd.dim_to_port, asd.dim_to_starboard, asd.epfd_name, 208 | asd.eta_month, asd.eta_day, asd.eta_hour, asd.eta_minute, asd.draught, asd.destination); 209 | 210 | if (NULL != decode->on_static_voyage_data) { 211 | decode->on_static_voyage_data(decode, NULL, &asd, raw_msg); 212 | } 213 | 214 | return ret; 215 | } 216 | 217 | static 218 | char _ais_decode_to_ascii_armor(uint8_t in) 219 | { 220 | if (in <= 39) { 221 | return in + 48; 222 | } else { 223 | return in - 40 + 96; 224 | } 225 | } 226 | 227 | 228 | static 229 | aresult_t _ais_decode_demod_on_msg(struct ais_demod *demod, void *state, const uint8_t *packet, 230 | size_t packet_len, bool fcs_valid) 231 | { 232 | aresult_t ret = A_OK; 233 | 234 | uint8_t msg_id = 0, 235 | repeat = 0; 236 | uint32_t mmsi = 0; 237 | size_t offs = 0; 238 | struct ais_decode *decode = state; 239 | char msg_ascii_6[(168+(4*256)+5)/6]; 240 | 241 | TSL_ASSERT_ARG(NULL != demod); 242 | TSL_ASSERT_ARG(NULL != state); 243 | TSL_ASSERT_ARG(NULL != packet); 244 | TSL_ASSERT_ARG(0 != packet_len); 245 | 246 | memset(msg_ascii_6, 0, sizeof(msg_ascii_6)); 247 | 248 | /* Convert the raw message to ASCII for storage */ 249 | for (size_t i = 0; i < sizeof(msg_ascii_6) && offs < packet_len; i += 4) { 250 | uint32_t accum = 0; 251 | for (size_t j = offs; j < offs + 3 && j < packet_len; j++) { 252 | accum <<= 8; 253 | accum |= packet[j]; 254 | } 255 | offs += 3; 256 | for (size_t j = 0; j < 4; j++) { 257 | msg_ascii_6[i + j] = _ais_decode_to_ascii_armor((accum >> ((3 - j) * 6)) & 0x3f); 258 | } 259 | } 260 | 261 | /* Extract the message type */ 262 | msg_id = (packet[0] >> 2) & 0x3f; 263 | 264 | /* Extract repeat indicator */ 265 | repeat = packet[0] & 0x3; 266 | 267 | /* Extract the MMSI from the packet */ 268 | mmsi = (uint32_t)packet[1] << 22; 269 | mmsi |= (uint32_t)packet[2] << 14; 270 | mmsi |= (uint32_t)packet[3] << 6; 271 | mmsi |= ((uint32_t)packet[4] >> 2) & 0x3f; 272 | 273 | DUMP("MsgId: %02u Rpt: %1u MMSI: %9u (Len: %zu bytes)\n", msg_id, repeat, mmsi, packet_len); 274 | 275 | switch (msg_id) { 276 | case AIS_MESSAGE_POSITION_REPORT_SOTDMA: 277 | case AIS_MESSAGE_POSITION_REPORT_SOTDMA2: 278 | case AIS_MESSAGE_POSITION_REPORT_ITDMA: 279 | _ais_decode_position_report(decode, packet, packet_len, msg_id, repeat, mmsi, msg_ascii_6); 280 | break; 281 | case AIS_MESSAGE_BASE_STATION_REPORT: 282 | _ais_decode_base_station_report(decode, packet, packet_len, msg_id, repeat, mmsi, msg_ascii_6); 283 | break; 284 | case AIS_MESSAGE_SHIP_STATIC_INFO: 285 | _ais_decode_static_voyage_data(decode, packet, packet_len, msg_id, repeat, mmsi, msg_ascii_6); 286 | break; 287 | } 288 | 289 | return ret; 290 | } 291 | 292 | aresult_t ais_decode_new(struct ais_decode **pdecode, uint32_t freq, ais_decode_on_position_report_func_t on_position_report, ais_decode_on_base_station_report_func_t on_base_station_report, ais_decode_on_static_voyage_data_func_t on_static_voyage_data) 293 | { 294 | aresult_t ret = A_OK; 295 | 296 | struct ais_decode *decode = NULL; 297 | 298 | TSL_ASSERT_ARG(NULL != pdecode); 299 | 300 | if (FAILED(ret = TZAALLOC(decode, SYS_CACHE_LINE_LENGTH))) { 301 | goto done; 302 | } 303 | 304 | if (FAILED(ret = ais_demod_new(&decode->demod, decode, _ais_decode_demod_on_msg, freq))) { 305 | goto done; 306 | } 307 | 308 | decode->freq = freq; 309 | 310 | decode->on_position_report = on_position_report; 311 | decode->on_base_station_report = on_base_station_report; 312 | decode->on_static_voyage_data = on_static_voyage_data; 313 | 314 | *pdecode = decode; 315 | 316 | done: 317 | if (FAILED(ret)) { 318 | if (NULL != decode) { 319 | if (NULL != decode->demod) { 320 | TSL_BUG_IF_FAILED(ais_demod_delete(&decode->demod)); 321 | } 322 | TFREE(decode); 323 | } 324 | } 325 | return ret; 326 | } 327 | 328 | aresult_t ais_decode_delete(struct ais_decode **pdecode) 329 | { 330 | aresult_t ret = A_OK; 331 | 332 | struct ais_decode *decode = NULL; 333 | 334 | TSL_ASSERT_ARG(NULL != pdecode); 335 | TSL_ASSERT_ARG(NULL != *pdecode); 336 | 337 | decode = *pdecode; 338 | 339 | if (NULL != decode->demod) { 340 | TSL_BUG_IF_FAILED(ais_demod_delete(&decode->demod)); 341 | } 342 | 343 | TFREE(decode); 344 | 345 | *pdecode = NULL; 346 | 347 | return ret; 348 | } 349 | 350 | aresult_t ais_decode_on_pcm(struct ais_decode *decode, const int16_t *samples, size_t nr_samples) 351 | { 352 | TSL_ASSERT_ARG(NULL != decode); 353 | TSL_ASSERT_ARG(NULL != samples); 354 | TSL_ASSERT_ARG(0 != nr_samples); 355 | return ais_demod_on_pcm(decode->demod, samples, nr_samples); 356 | } 357 | 358 | --------------------------------------------------------------------------------