diff --git a/README.md b/README.md index 4c24d41..a7c92ce 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,34 @@ # libsdr - A simple software defined radio (SDR) library -**First of all:** I assembled this library for my one entertainment and to learn something about software defined radio. If you are interested into a full-featured, performant SDR framework, consider using GNU radio (http://gnuradio.org). +**First of all:** I assembled this library for my one entertainment and to learn something about +software defined radio. If you are interested into a full-featured, performant SDR framework, +consider using GNU radio (http://gnuradio.org). -SRD-RX + + SRD-RX + -Although being simple, libsdr is sufficient to write a simple SDR receiver application (http://github.com/hmatuschek/sdr-rx, above). This RX application supports several input sources (i.e. sound card, files, RTL2382 dongles etc.) and modes (i.e. AM, FM, SSB, CW, etc.). +Although being simple, libsdr is sufficient to write a simple SDR receiver application +(http://github.com/hmatuschek/sdr-rx, above). This RX application supports several input sources +(i.e. sound card, files, RTL2382 dongles etc.) and modes (i.e. AM, FM, SSB, CW, etc.). ## Build -The only required run-time dependency of `libsdr` is `libpthread`, which is available on all Unix-like OSs like Linux and MacOS X. It is also available for windows if `mingw` is used (http://www.mingw.org) of compilation. There are also some optional dependencies, which allow for the usage of some additional features of the library. +The only required run-time dependency of `libsdr` is `libpthread`, which is available on all +Unix-like OSs like Linux and MacOS X. It is also available for windows if `mingw` is used +(http://www.mingw.org) of compilation. There are also some optional dependencies, which allow for +the usage of some additional features of the library. -* `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user interface elements like a spectrum view. -* `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution filters. +* `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user + interface elements like a spectrum view. +* `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution + filters. * `PortAudio` (http://www.portaudio.com) - Allows for sound-card input and output. * `librtlsdr` (http://rtlsdr.org) - Allows to interface RTL2382U based USB dongles. -For the compilation of the library, `cmake` (http://www.cmake.org) is also required (as well as a compiler like gcc or clang of cause). +For the compilation of the library, `cmake` (http://www.cmake.org) is also required (as well as a +compiler like gcc or clang of cause). Compiling the library is the canonical cmake path: diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 09f9589..fabce14 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,15 +1,19 @@ IF(SDR_WITH_PORTAUDIO) - add_executable(sdr_wavplay sdr_wavplay.cc) - target_link_libraries(sdr_wavplay ${LIBS} libsdr) +# add_executable(sdr_wavplay sdr_wavplay.cc) +# target_link_libraries(sdr_wavplay ${LIBS} libsdr) - add_executable(sdr_fm sdr_fm.cc) - target_link_libraries(sdr_fm ${LIBS} libsdr) +# add_executable(sdr_fm sdr_fm.cc) +# target_link_libraries(sdr_fm ${LIBS} libsdr) - add_executable(sdr_rec sdr_rec.cc) - target_link_libraries(sdr_rec ${LIBS} libsdr) +# add_executable(sdr_rec sdr_rec.cc) +# target_link_libraries(sdr_rec ${LIBS} libsdr) add_executable(sdr_afsk1200 sdr_afsk1200.cc) target_link_libraries(sdr_afsk1200 ${LIBS} libsdr) + + add_executable(sdr_rtty sdr_rtty.cc) + target_link_libraries(sdr_rtty ${LIBS} libsdr) + ENDIF(SDR_WITH_PORTAUDIO) diff --git a/examples/sdr_afsk1200.cc b/examples/sdr_afsk1200.cc index 95ecc31..fdc530a 100644 --- a/examples/sdr_afsk1200.cc +++ b/examples/sdr_afsk1200.cc @@ -1,335 +1,13 @@ #include "wavfile.hh" #include "autocast.hh" #include "interpolate.hh" +#include "afsk.hh" +#include "ax25.hh" +#include "utils.hh" + using namespace sdr; -static const uint16_t crc_ccitt_table[] = { - 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, - 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, - 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, - 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, - 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, - 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, - 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, - 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, - 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, - 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, - 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, - 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, - 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, - 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, - 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, - 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, - 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, - 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, - 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, - 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, - 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, - 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, - 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, - 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, - 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, - 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, - 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, - 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, - 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, - 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, - 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, - 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 -}; - -static inline bool check_crc_ccitt(const uint8_t *buf, int cnt) -{ - uint32_t crc = 0xffff; - for (; cnt > 0; cnt--, buf++) { - crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff]; - } - return (crc & 0xffff) == 0xf0b8; -} - - -class AFSK: public Sink, public Source { -public: - AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0) - : Sink(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace) - { - // pass... - } - - virtual ~AFSK() { - // pass... - } - - virtual void config(const Config &src_cfg) { - // Check if config is complete - if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } - - // Check if buffer type matches - if (Config::typeId() != src_cfg.type()) { - ConfigError err; - err << "Can not configure AFSK1200: Invalid type " << src_cfg.type() - << ", expected " << Config::typeId(); - throw err; - } - - // The input sample rate - _sampleRate = src_cfg.sampleRate(); - - // Samples per bit - _corrLen = int(_sampleRate/_baud); - // Compute symbol rate: - _symbolRate = std::min(10*_baud, _baud*_corrLen); - - // Samples per symbol (fractional): - _mu = 0.0; _muIncr = _sampleRate/_symbolRate; - // Delayline for interpolating sub-sampler - _dl = Buffer(2*8); - for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; } - _dl_idx = 0; - - // Assemble phase LUT: - _markLUT = Buffer< std::complex >(_corrLen); - _spaceLUT = Buffer< std::complex >(_corrLen); - - // Allocate ring-buffers for mark and space symbols - _markHist = Buffer< std::complex >(_corrLen); - _spaceHist = Buffer< std::complex >(_corrLen); - - // Initialize LUTs and ring-buffers - double phiMark=0, phiSpace=0; - for (size_t i=0; i<_corrLen; i++) { - _markLUT[i] = std::exp(std::complex(0.0, phiMark)); - _spaceLUT[i] = std::exp(std::complex(0.0, phiSpace)); - phiMark += (2.*M_PI*_Fmark)/_sampleRate; - phiSpace += (2.*M_PI*_Fspace)/_sampleRate; - _markHist[i] = 0; _spaceHist[i] = 0; - } - _lutIdx = 0; - - // Get phase increment per symbol - _phase = 0; _omega = _baud/_symbolRate; - _omegaMin = _omega - 0.01*_omega; - _omegaMax = _omega + 0.01*_omega; - _gainOmega = 0.01; - - // Allocate output buffer: - _buffer = Buffer(src_cfg.bufferSize()/_corrLen + 1); - - LogMessage msg(LOG_DEBUG); - msg << "Config AFSK1200 node: " << std::endl - << " input sample rate: " << _sampleRate << "Hz" << std::endl - << " samples per symbol: " << _muIncr << std::endl - << " symbols per bit: " << _corrLen << std::endl - << " symbol rate: " << _symbolRate << "Hz" << std::endl - << " Phase incr/symbol: " << float(_omega); - - Logger::get().log(msg); - - this->setConfig(Config(Traits::scalarId, _baud, _buffer.size(), 1)); - } - - - virtual void process(const Buffer &buffer, bool allow_overwrite) { - size_t i=0, o=0; - while (i1) && (i0); - // Advance phase - _phase += _omega; - - // Sample bit - if (_phase >= 1) { - // Modulo "2 pi" - _phase = fmodf(_phase, 1.0); - // Store bit - _lastBits <<= 1; _lastBits |= (_symbols & 1); - // Put decoded bit in output buffer - // transition -> 0; no transition -> 1 - _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); - } - - // If transition - if ((_symbols ^ (_symbols >> 1)) & 1) { - // Phase correction - /*std::cerr << "Transition at phi=" << _phase << std::endl - << " update omega from " << _omega << " to "; */ - if (_phase < 0.5) { _omega -= _gainOmega*(_phase); } - else { _omega += _gainOmega*(1-_phase); } - // Limit omega - _omega = std::min(_omegaMax, std::max(_omegaMin, _omega)); - //std::cerr << _omega << std::endl; - } - } - } - this->send(_buffer.head(o)); - } - - -protected: - inline double _getSymbol() { - std::complex markSum(0), spaceSum(0); - for (size_t i=0; i<_corrLen; i++) { - markSum += _markHist[i]; - spaceSum += _spaceHist[i]; - } - double f = markSum.real()*markSum.real() + - markSum.imag()*markSum.imag() - - spaceSum.real()*spaceSum.real() - - spaceSum.imag()*spaceSum.imag(); - return f; - } - - -protected: - float _sampleRate, _symbolRate; - float _baud; - float _Fmark, _Fspace; - - uint32_t _corrLen; - uint32_t _lutIdx; - Buffer< std::complex > _markLUT; - Buffer< std::complex > _spaceLUT; - - float _mu, _muIncr; - Buffer< float > _dl; - size_t _dl_idx; - - Buffer< std::complex > _markHist; - Buffer< std::complex > _spaceHist; - - uint32_t _symbols; - uint32_t _lastBits; - float _phase; - float _omega, _omegaMin, _omegaMax; - float _gainOmega; - - static const uint32_t _phasePeriod = 0x10000u; - static const uint32_t _phaseMask = 0x0ffffu; - - /** Output buffer. */ - Buffer _buffer; -}; - - -class AX25: public Sink, public Source -{ -public: - AX25() - : Sink(), Source() - { - // pass... - } - - virtual ~AX25() { - // pass... - } - - virtual void config(const Config &src_cfg) { - if (! src_cfg.hasType()) { return; } - // Check if buffer type matches - if (Config::typeId() != src_cfg.type()) { - ConfigError err; - err << "Can not configure AX25: Invalid type " << src_cfg.type() - << ", expected " << Config::typeId(); - throw err; - } - - _bitstream = 0; - _bitbuffer = 0; - _state = 0; - _ptr = _rxbuffer; - - // Allocate output buffer - _buffer = Buffer(512); - - // propergate config - this->setConfig(Config(Traits::scalarId, 0, 512, 1)); - } - - virtual void process(const Buffer &buffer, bool allow_overwrite) { - for (size_t i=0; i 2)) { - *_ptr = 0; - if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) { - std::cerr << "Got invalid buffer: " << _rxbuffer << std::endl; - } else { - std::cerr << "GOT: " << _rxbuffer << std::endl; - memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer); - this->send(_buffer.head(_ptr-_rxbuffer)); - } - - } - _state = 1; - _ptr = _rxbuffer; - _bitbuffer = 0x80; - continue; - } - - // If 7 ones are received in a row -> error, wait or sync byte - if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; } - - // If state == wait for sync byte -> receive next bit - if (!_state) { continue; } - - /* stuffed bit */ - if ((_bitstream & 0x3f) == 0x3e) { continue; } - - // prepend bit to bitbuffer - _bitbuffer |= ((_bitstream & 1) << 8); - - // If 8 bits have been received (stored in b8-b1 of _bitbuffer) - if (_bitbuffer & 1) { - // Check for buffer overrun - if ((_ptr-_rxbuffer) >= 512) { - Logger::get().log(LogMessage(LOG_ERROR, "AX.25 packet too long.")); - // Wait for next sync byte - _state = 0; - continue; - } - - // Store received byte and ... - *_ptr++ = (_bitbuffer >> 1); - // reset bit buffer - _bitbuffer = 0x80; - continue; - } - // Shift bitbuffer one to the left - _bitbuffer >>= 1; - } - } - -protected: - uint32_t _bitstream; - uint32_t _bitbuffer; - uint32_t _state; - - uint8_t _rxbuffer[512]; - uint8_t *_ptr; - - Buffer _buffer; -}; int main(int argc, char *argv[]) { @@ -338,18 +16,23 @@ int main(int argc, char *argv[]) { sdr::Logger::get().addHandler( new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); + Queue &queue = Queue::get(); + WavSource src(argv[1], 1024); if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; } AutoCast< int16_t > cast; - AFSK demod; + AFSK demod(1200, 1200, 2200, AFSK::TRANSITION); AX25 decode; + TextDump dump; src.connect(&cast); cast.connect(&demod); demod.connect(&decode); + decode.connect(&dump); Queue::get().addIdle(&src, &WavSource::next); + src.addEOS(&queue, &Queue::stop); Queue::get().start(); Queue::get().wait(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 503f01f..41310aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,12 +2,13 @@ set(LIBSDR_SOURCES buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc - exception.cc logger.cc psk31.cc options.cc) + exception.cc logger.cc psk31.cc options.cc afsk.cc ax25.cc baudot.cc) set(LIBSDR_HEADERS sdr.hh math.hh buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh - combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh) + combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh afsk.hh ax25.hh + baudot.hh) if(SDR_WITH_PORTAUDIO) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) diff --git a/src/afsk.cc b/src/afsk.cc new file mode 100644 index 0000000..dc8e6ab --- /dev/null +++ b/src/afsk.cc @@ -0,0 +1,168 @@ +#include "afsk.hh" +#include "logger.hh" +#include "traits.hh" +#include "interpolate.hh" + +using namespace sdr; + + +AFSK::AFSK(double baud, double Fmark, double Fspace, Mode mode) + : Sink(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace), _mode(mode) +{ + // pass... +} + +AFSK::~AFSK() { + // pass... +} + +void +AFSK::config(const Config &src_cfg) +{ + // Check if config is complete + if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } + + // Check if buffer type matches + if (Config::typeId() != src_cfg.type()) { + ConfigError err; + err << "Can not configure AFSK1200: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } + + // The input sample rate + _sampleRate = src_cfg.sampleRate(); + + // Samples per bit + _corrLen = int(_sampleRate/_baud); + // Compute symbol rate: + _symbolRate = std::max(_baud, _baud*_corrLen); + + // Samples per symbol (fractional): + _muIncr = _sampleRate/_symbolRate; + _mu = _muIncr; + // Delayline for interpolating sub-sampler + _dl = Buffer(2*8); + for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; } + _dl_idx = 0; + + // Assemble phase LUT: + _markLUT = Buffer< std::complex >(_corrLen); + _spaceLUT = Buffer< std::complex >(_corrLen); + + // Allocate ring-buffers for mark and space symbols + _markHist = Buffer< std::complex >(_corrLen); + _spaceHist = Buffer< std::complex >(_corrLen); + _symbols = Buffer(_corrLen); + + // Initialize LUTs and ring-buffers + double phiMark=0, phiSpace=0; + for (size_t i=0; i<_corrLen; i++) { + _markLUT[i] = std::exp(std::complex(0.0, phiMark)); + _spaceLUT[i] = std::exp(std::complex(0.0, phiSpace)); + phiMark += (2.*M_PI*_Fmark)/_sampleRate; + phiSpace += (2.*M_PI*_Fspace)/_sampleRate; + // Apply Window functions + //_markLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen)); + //_spaceLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen)); + _markHist[i] = 0; _spaceHist[i] = 0; _symbols[i] = 0; + } + // Ring buffer indices + _lutIdx = 0; _symbolIdx = 0; + + // Get phase increment per symbol + _phase = 0; _omega = _baud/_symbolRate; + // PLL limits +/- 10% around _omega + _omegaMin = _omega - 0.005*_omega; + _omegaMax = _omega + 0.005*_omega; + // PLL gain + _gainOmega = 0.0005; + + _symSum = 0; + _lastSymSum = 0; + + // Allocate output buffer: + _buffer = Buffer(src_cfg.bufferSize()/_corrLen + 1); + + LogMessage msg(LOG_DEBUG); + msg << "Config AFSK node: " << std::endl + << " input sample rate: " << _sampleRate << " Hz" << std::endl + << " samples per symbol: " << _muIncr << std::endl + << " symbols per bit: " << _corrLen << std::endl + << " symbol rate: " << _symbolRate << " Hz" << std::endl + << " bit rate: " << _symbolRate/_corrLen << " baud" << std::endl + << " phase incr/symbol: " << float(_omega) << std::endl + << " bit mode: " << ((TRANSITION == _mode) ? "transition" : "normal"); + Logger::get().log(msg); + + // Forward config. + this->setConfig(Config(Traits::scalarId, _baud, _buffer.size(), 1)); +} + + +void +AFSK::process(const Buffer &buffer, bool allow_overwrite) +{ + size_t i=0, o=0; + while (i=1) && (i= 1) { continue; } + + // Get interpolated symbol + float symbol = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr; + // store symbol + _lastSymSum = _symSum; + _symSum -= _symbols[_symbolIdx]; + _symbols[_symbolIdx] = ( (symbol>=0) ? 1 : -1 ); + _symSum += _symbols[_symbolIdx]; + _symbolIdx = ((_symbolIdx+1) % _corrLen); + // Advance phase + _phase += _omega; + + // Sample bit + if (_phase >= 1) { + // Modulo "2 pi", phase is defined on the interval [0,1) + while (_phase>=1) { _phase -= 1; } + // Estimate bit by majority vote on all symbols + _lastBits = ((_lastBits<<1) | (_symSum>0)); + // Put decoded bit in output buffer + if (TRANSITION == _mode) { + // transition -> 0; no transition -> 1 + _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); + } else { + // mark -> 1, space -> 0 + _buffer[o++] = _lastBits & 1; + } + } + + // If there was a symbol transition + if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) { + // Phase correction + /**std::cerr << "Transition at phi=" << _phase << std::endl + << " update omega from " << _omega << " to "; */ + // transition at [-pi,0] -> increase omega + if (_phase < 0.5) { _omega += _gainOmega*(0.5-_phase); } + // transition at [0,pi] -> decrease omega + else { _omega -= _gainOmega*(_phase-0.5); } + // Limit omega + _omega = std::min(_omegaMax, std::max(_omegaMin, _omega)); + //_phase += _gainOmega*(_phase-0.5); + /* std::cerr << _omega << std::endl; */ + } + } + + if (0 < o) { this->send(_buffer.head(o)); } +} + diff --git a/src/afsk.hh b/src/afsk.hh new file mode 100644 index 0000000..186bcd2 --- /dev/null +++ b/src/afsk.hh @@ -0,0 +1,111 @@ +#ifndef __SDR_AFSK_HH__ +#define __SDR_AFSK_HH__ + +#include "node.hh" + +namespace sdr { + +/** A simple (Audio) Frequency Shift Keying (AFSK) demodulator. + * This node consists of two convolution peak-filters at the mark and space frequencies, a + * interpolating sub-sampler to match the baud-rate exactly and a PLL to lock to the symbol + * transitions. The node will decode the (A)FSK signal and will send a bit-stream (uint8_t). + * @ingroup demodulator */ +class AFSK: public Sink, public Source +{ +public: + /** Possible bit decoding modes. */ + typedef enum { + NORMAL, ///< Normal mode (i.e. mark -> 1, space -> 0). + TRANSITION ///< Transition mode (i.e. transition -> 0, no transition -> 1). + } Mode; + +public: + /** Constructs a AFSK node with the specified @c baud rate and @c Fmark, @c Fspace frequencies. + * The default valuse corresponds to those used for 1200 baud packet radio. */ + AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0, + Mode mode=TRANSITION); + /** Destructor. */ + virtual ~AFSK(); + + /** Configures the node. */ + virtual void config(const Config &src_cfg); + /** Processes the given buffer. */ + virtual void process(const Buffer &buffer, bool allow_overwrite); + +protected: + /** Performs the convolution filtering of the mark & space frequencies. */ + inline double _getSymbol() { + std::complex markSum(0), spaceSum(0); + for (size_t i=0; i<_corrLen; i++) { + markSum += _markHist[i]; + spaceSum += _spaceHist[i]; + } + double f = markSum.real()*markSum.real() + + markSum.imag()*markSum.imag() - + spaceSum.real()*spaceSum.real() - + spaceSum.imag()*spaceSum.imag(); + return f; + } + + +protected: + /** The sample rate of the input signal. */ + float _sampleRate; + /** A multiple of the baud rate. */ + float _symbolRate; + /** The baud rate. */ + float _baud; + /** Mark "tone" frequency. */ + float _Fmark; + /** Space "tone" frequency. */ + float _Fspace; + /** Bit encoding mode. */ + Mode _mode; + /** Correlation length, the number of "symbols" per bit. */ + uint32_t _corrLen; + /** The current FIR filter LUT index. */ + uint32_t _lutIdx; + /** Mark frequency FIR filter LUT. */ + Buffer< std::complex > _markLUT; + /** Space frequency FIR filter LUT. */ + Buffer< std::complex > _spaceLUT; + + /** FIR filter buffer. */ + Buffer< std::complex > _markHist; + /** FIR filter buffer. */ + Buffer< std::complex > _spaceHist; + + /** Symbol subsampling counter. */ + float _mu; + /** Symbol subsampling. */ + float _muIncr; + /** Delay line for the 8-pole interpolation filter. */ + Buffer< float > _dl; + /** Delay line index. */ + size_t _dl_idx; + + Buffer _symbols; + size_t _symbolIdx; + int32_t _symSum; + int32_t _lastSymSum; + + /** Last received bits. */ + uint32_t _lastBits; + /** Current PLL phase. */ + float _phase; + /** PLL phase speed. */ + float _omega; + /** Maximum PLL phase speed. */ + float _omegaMin; + /** Minimum PLL phase speed. */ + float _omegaMax; + /** PLL gain. */ + float _gainOmega; + + /** Output buffer. */ + Buffer _buffer; +}; + + +} +#endif // __SDR_AFSK_HH__ diff --git a/src/ax25.cc b/src/ax25.cc new file mode 100644 index 0000000..ea42d4a --- /dev/null +++ b/src/ax25.cc @@ -0,0 +1,173 @@ +#include "ax25.hh" +#include "logger.hh" +#include "traits.hh" + +using namespace sdr; + + +static const uint16_t crc_ccitt_table[] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +static inline bool check_crc_ccitt(const uint8_t *buf, int cnt) +{ + uint32_t crc = 0xffff; + for (; cnt > 0; cnt--, buf++) { + crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff]; + } + return (crc & 0xffff) == 0xf0b8; +} + + + +AX25::AX25() + : Sink(), Source() +{ + // pass... +} + +AX25::~AX25() { + // pass... +} + +void +AX25::config(const Config &src_cfg) { + if (! src_cfg.hasType()) { return; } + // Check if buffer type matches + if (Config::typeId() != src_cfg.type()) { + ConfigError err; + err << "Can not configure AX25: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } + + _bitstream = 0; + _bitbuffer = 0; + _state = 0; + _ptr = _rxbuffer; + + // Allocate output buffer + _buffer = Buffer(512); + + LogMessage msg(LOG_DEBUG); + msg << "Config AX.25 node."; + Logger::get().log(msg); + + // propergate config + this->setConfig(Config(Traits::scalarId, 0, 512, 1)); +} + +void +AX25::process(const Buffer &buffer, bool allow_overwrite) +{ + for (size_t i=0; i 2)) { + *_ptr = 0; + if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) { + LogMessage msg(LOG_DEBUG); + msg << "AX.25: Received invalid buffer: " << _rxbuffer; + Logger::get().log(msg); + } else { + bool addrExt; + std::string src, dst; + int srcSSID, dstSSID; + unpackCall(_rxbuffer, dst, dstSSID, addrExt); + unpackCall(_rxbuffer+7, src, srcSSID, addrExt); + std::cerr << "RX: " << src << "-" << srcSSID + << " > " << dst << "-" << dstSSID << std::endl + << " " << _rxbuffer+14 << std::endl; + memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer); + this->send(_buffer.head(_ptr-_rxbuffer)); + } + } + // Receive data + _state = 1; + _ptr = _rxbuffer; + _bitbuffer = 0x80; + continue; + } + + // If 7 ones are received in a row -> error, wait or sync byte + if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; } + + // If state == wait for sync byte -> receive next bit + if (!_state) { continue; } + + /* stuffed bit */ + if ((_bitstream & 0x3f) == 0x3e) { continue; } + + // prepend bit to bitbuffer + /// @todo Double check this!!! + _bitbuffer |= ((_bitstream & 0x01) << 8); + + // If 8 bits have been received (stored in b8-b1 of _bitbuffer) + if (_bitbuffer & 0x01) { + // Check for buffer overrun + if ((_ptr-_rxbuffer) >= 512) { + Logger::get().log(LogMessage(LOG_DEBUG, "AX.25 packet too long.")); + // Wait for next sync byte + _state = 0; + continue; + } + + // Store received byte and ... + *_ptr++ = (_bitbuffer >> 1); + // reset bit buffer + _bitbuffer = 0x80; + continue; + } + + // Shift bitbuffer one to the left + _bitbuffer >>= 1; + } +} + + +void +AX25::unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt) { + size_t length = 0; call.resize(6); + for (size_t i=0; i<6; i++) { + call.at(i) = char(buffer[i]>>1); + if (' ' != call.at(i)) { length++; } + } + call.resize(length); + ssid = int( (buffer[6] & 0x1f) >> 1); + addrExt = !bool(buffer[6] & 0x01); +} + diff --git a/src/ax25.hh b/src/ax25.hh new file mode 100644 index 0000000..59ce58d --- /dev/null +++ b/src/ax25.hh @@ -0,0 +1,55 @@ +#ifndef __SDR_AX25_HH__ +#define __SDR_AX25_HH__ + +#include "node.hh" + + +namespace sdr { + +/** Decodes AX25 (PacketRadio) messages from a bit stream. + * + * In conjecture with the (A)FSK demodulator, the AX25 can be used to receive packet radio or APRS + * messages. AX25 is usually transmitted as FSK in transition mode, means the bits aren't + * encoded by mark & space tones but rather as a transition from mark to space or in reverse. Hence + * the FSK node needs to be configured in transition mode. + * + * The node does not process the actual AX.25 packages, it only checks the frame check sequence and + * forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible + * for unpacking and handling the received datagram. + * + * @ingroup datanode */ +class AX25: public Sink, public Source +{ +public: + /** Constructor. */ + AX25(); + /** Destructor. */ + virtual ~AX25(); + /** Configures the node. */ + virtual void config(const Config &src_cfg); + /** Processes the bit stream. */ + virtual void process(const Buffer &buffer, bool allow_overwrite); + + static void unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt); + +protected: + /** The last bits. */ + uint32_t _bitstream; + /** A buffer of received bits. */ + uint32_t _bitbuffer; + /** The current state. */ + uint32_t _state; + + /** Message buffer. */ + uint8_t _rxbuffer[512]; + /** Insert-pointer to the buffer. */ + uint8_t *_ptr; + + /** Output buffer. */ + Buffer _buffer; +}; + +} + + +#endif // __SDR_AX25_HH__ diff --git a/src/baseband.hh b/src/baseband.hh index 11b6842..71696c2 100644 --- a/src/baseband.hh +++ b/src/baseband.hh @@ -14,9 +14,9 @@ namespace sdr { /** This class performs several operations on the complex (integral) input stream, it first filters * out some part of the input stream using a FIR band pass (band pass is centerred around @c Ff * with width @c width) then shifts the center frequency @c Fc to 0 and finally sub-samples the - * resulting stream. This node can be used to select a portion of the input stream and reduce the - * rate of the stream, allowing for some more expensive operations to be performed on the output - * stream. */ + * resulting stream. This node can be used to select a portion of the input spectrum and for the + * reduction of the stream rate, allowing for some more expensive operations to be performed on the + * output stream. */ template class IQBaseBand: public Sink< std::complex >, public Source, public FreqShiftBase { diff --git a/src/baudot.cc b/src/baudot.cc new file mode 100644 index 0000000..eda65d9 --- /dev/null +++ b/src/baudot.cc @@ -0,0 +1,111 @@ +#include "baudot.hh" +#include "traits.hh" +#include "logger.hh" + + +using namespace sdr; + +// Baudot code tables +char Baudot::_letter[32] = { 0, 'E','\n', 'A', ' ', 'S', 'I', 'U','\n', 'D', 'R', 'J', 'N', 'F', + 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', 0, + 'M', 'X', 'V', 0}; +char Baudot::_figure[32] = { 0, '3','\n', '-', ' ','\a', '8', '7','\n', '?', '4','\'', ',', '!', + ':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', 0, + '.', '/', ';', 0}; + +// Some special codes +#define CHAR_NUL 0 +#define CHAR_STF 27 +#define CHAR_STL 31 +#define CHAR_SPA 4 + + +Baudot::Baudot(StopBits stopBits) + : Sink(), Source(), _mode(LETTERS) +{ + switch (stopBits) { + case STOP1: + // Pattern xx11 xxxx xxxx xx00 + // Mask 0011 0000 0000 0011 + _stopHBits = 2; + _bitsPerSymbol = 14; + _pattern = 0x3000; + _mask = 0x3003; + break; + case STOP15: + // Pattern x11x xxxx xxxx x000 + // Mask 0110 0000 0000 0111 + _stopHBits = 3; + _bitsPerSymbol = 15; + _pattern = 0x6000; + _mask = 0x6007; + break; + case STOP2: + // Pattern 11xx xxxx xxxx 0000 + // Mask 1100 0000 0000 1111 + _stopHBits = 4; + _bitsPerSymbol = 16; + _pattern = 0xC000; + _mask = 0xC00F; + break; + } +} + +void +Baudot::config(const Config &src_cfg) { + if (! src_cfg.hasType()) { return; } + // Check if buffer type matches + if (Config::typeId() != src_cfg.type()) { + ConfigError err; + err << "Can not configure Baudot: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } + + // Init (half) bit stream and counter + _bitstream = 0; + _bitcount = 0; + + // Compute buffer size. + size_t buffer_size = (src_cfg.bufferSize()/(2*_bitsPerSymbol))+1; + _buffer = Buffer(buffer_size); + + LogMessage msg(LOG_DEBUG); + msg << "Config Baudot node: " << std::endl + << " input sample rate: " << src_cfg.sampleRate() << " half-bits/s" << std::endl + << " start bits: " << 1 << std::endl + << " stop bits: " << float(_stopHBits)/2 << std::endl; + Logger::get().log(msg); + + // propergate config + this->setConfig(Config(Traits::scalarId, 0, buffer_size, 1)); +} + + +void +Baudot::process(const Buffer &buffer, bool allow_overwrite) +{ + size_t o=0; + for (size_t i=0; i>shift)&0x01)<send(_buffer.head(o)); } +} diff --git a/src/baudot.hh b/src/baudot.hh new file mode 100644 index 0000000..e8c7c81 --- /dev/null +++ b/src/baudot.hh @@ -0,0 +1,72 @@ +#ifndef __SDR_BAUDOT_HH__ +#define __SDR_BAUDOT_HH__ + +#include "node.hh" +#include + +namespace sdr { + +/** Implements a Baudot decoder. Inconjecture with the (A)FSK demodulator, it enables the + * reception of radio teletype (RTTY) messages. + * + * Please note that a baudot encoded char is usually transmitted in a frame with one start bit and + * 1, 1.5 or 2 stop bits. Hence this node expects to receive two bits for one decoded bit in order + * to detect the 1.5 stop bits reliably. + * + * I.e. to receive a 45.45 baud RTTY signal, the (A)FSK demodulator need to be configured for + * 90.90 baud (= 2*45.45 baud). + * @ingroup datanodes */ +class Baudot: public Sink, public Source +{ +public: + /** Specifies the current code-tables. */ + typedef enum { + LETTERS, ///< Letters. + FIGURES ///< Numbers, symbols etc. + } Mode; + + /** Specifies the number of stop bits. */ + typedef enum { + STOP1, ///< 1 stop bit. + STOP15, ///< 1.5 stop bits. + STOP2 ///< 2 stop bits. + } StopBits; + +public: + /** Constructor. */ + Baudot(StopBits stopBits = STOP15); + + /** Configures the node. */ + virtual void config(const Config &src_cfg); + /** Processes the bit-stream. */ + virtual void process(const Buffer &buffer, bool allow_overwrite); + +protected: + /** Code table for letters. */ + static char _letter[32]; + /** Code table for symbols or figure (i.e. numbers). */ + static char _figure[32]; + /** The last bits received. */ + uint16_t _bitstream; + /** The number of bits received. */ + size_t _bitcount; + + /** The currently selected table. */ + Mode _mode; + + /** Specifies the number of half bits per symbol. */ + size_t _bitsPerSymbol; + /** Specifies the frame pattern. */ + uint16_t _pattern; + /** Specifies the frame mask. */ + uint16_t _mask; + /** Number of half bits forming the stop bit. */ + uint16_t _stopHBits; + + /** The output buffer. */ + Buffer _buffer; +}; + +} + +#endif // BAUDOT_HH diff --git a/src/buffer.cc b/src/buffer.cc index 376f4dc..958c802 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -25,7 +25,9 @@ RawBuffer::RawBuffer(size_t N, BufferOwner *owner) _refcount((int *)malloc(sizeof(int))), _owner(owner) { // Check if data could be allocated - if ((0 == _ptr) && (0 != _refcount)) { free(_refcount); _refcount = 0; _storage_size = 0; return; } + if ((0 == _ptr) && (0 != _refcount)) { + free(_refcount); _refcount = 0; _storage_size = 0; return; + } // Set refcount, done... if (_refcount) { (*_refcount) = 1; } } @@ -62,7 +64,7 @@ void RawBuffer::unref() { // If empty -> skip... if ((0 == _ptr) || (0 == _refcount)) { return; } // Decrement refcount - (*_refcount) -= 1; + (*_refcount)--; // If there is only one reference left and the buffer is owned -> notify owner, who holds the last // reference. if ((1 == (*_refcount)) && (_owner)) { _owner->bufferUnused(*this); } diff --git a/src/buffer.hh b/src/buffer.hh index 2780bf9..d6664f9 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -82,7 +82,7 @@ public: void unref(); /** Returns the reference counter. */ inline int refCount() const { if (0 == _refcount) { return 0; } return (*_refcount); } - /** We assume here that buffers are owned by some object: A buffer is therefore "unused" if the + /** We assume here that buffers are owned by one object: A buffer is therefore "unused" if the * owner holds the only reference to the buffer. */ inline bool isUnused() const { if (0 == _refcount) { return true; } diff --git a/src/combine.hh b/src/combine.hh index 7c66c90..70d8127 100644 --- a/src/combine.hh +++ b/src/combine.hh @@ -12,7 +12,7 @@ namespace sdr { template class Combine; -/** A single sink of a Combine node. */ +/** A single sink of a Combine node. Do not use this node explicitly, consider using @c Combine. */ template class CombineSink: public Sink { @@ -78,9 +78,11 @@ public: virtual ~Combine() { // Unref all buffers and free sinks for (size_t i=0; i<_sinks.size(); i++) { - _buffers[i].unref(); delete _sinks[i]; + delete _sinks[i]; + _buffers[i].unref(); } - _buffers.clear(); _sinks.clear(); + _buffers.clear(); + _sinks.clear(); } /** Needs to be overridden. */ diff --git a/src/demod.hh b/src/demod.hh index 729fa43..d357047 100644 --- a/src/demod.hh +++ b/src/demod.hh @@ -26,7 +26,8 @@ public: /** Destructor. */ virtual ~AMDemod() { - // pass... + // free buffers + _buffer.unref(); } /** Configures the AM demod. */ @@ -40,8 +41,11 @@ public: << ", expected " << Config::typeId< std::complex >(); throw err; } + + // Unreference previous buffer + _buffer.unref(); // Allocate buffer - _buffer = Buffer(src_cfg.bufferSize()); + _buffer = Buffer(src_cfg.bufferSize()); LogMessage msg(LOG_DEBUG); msg << "Configure AMDemod: " << this << std::endl @@ -59,16 +63,6 @@ public: /** Handles the I/Q input buffer. */ virtual void process(const Buffer > &buffer, bool allow_overwrite) { - // Drop buffer if output buffer is still in use: - if (! _buffer.isUnused()) { -#ifdef SDR_DEBUG - LogMessage msg(LOG_WARNING); - msg << __FILE__ << ": Output buffer still in use: Drop received buffer..."; - Logger::get().log(msg); - return; -#endif - } - Buffer out_buffer; // If source allow to overwrite the buffer, use it otherwise rely on own buffer if (allow_overwrite) { out_buffer = Buffer(buffer); } @@ -111,7 +105,7 @@ public: /** Destructor. */ virtual ~USBDemod() { - // pass... + _buffer.unref(); } /** Configures the USB demodulator. */ @@ -125,6 +119,9 @@ public: << ", expected " << Config::typeId(); throw err; } + + // Unreference previous buffer + _buffer.unref(); // Allocate buffer _buffer = Buffer(src_cfg.bufferSize()); @@ -146,16 +143,9 @@ public: if (allow_overwrite) { // Process in-place _process(buffer, Buffer(buffer)); - } else if (_buffer.isUnused()) { + } else { // Store result in buffer _process(buffer, _buffer); - } else { - // Drop buffer -#ifdef SDR_DEBUG - LogMessage msg(LOG_WARNING); - msg << "SSBDemod: Drop buffer."; - Logger::get().log(msg); -#endif } } @@ -193,7 +183,7 @@ public: /** Destructor. */ virtual ~FMDemod() { - // pass... + _buffer.unref(); } /** Configures the FM demodulator. */ @@ -237,14 +227,8 @@ public: if (allow_overwrite && _can_overwrite) { _process(buffer, Buffer(buffer)); - } else if (_buffer.isUnused()) { - _process(buffer, _buffer); } else { -#ifdef SDR_DEBUG - LogMessage msg(LOG_WARNING); - msg << "FMDemod: Drop buffer: Output buffer still in use."; - Logger::get().log(msg); -#endif + _process(buffer, _buffer); } } @@ -311,7 +295,7 @@ public: /** Destructor. */ virtual ~FMDeemph() { - // pass... + _buffer.unref(); } /** Returns true if the filter node is enabled. */ @@ -336,8 +320,17 @@ public: 1.0/( (1.0-exp(-1.0/(src_cfg.sampleRate() * 75e-6) )) ) ); // Reset average: _avg = 0; + // Unreference previous buffer + _buffer.unref(); // Allocate buffer: _buffer = Buffer(src_cfg.bufferSize()); + + LogMessage msg(LOG_DEBUG); + msg << "Configured FMDDeemph node: " << this << std::endl + << " sample-rate: " << src_cfg.sampleRate() << std::endl + << " type: " << src_cfg.type(); + Logger::get().log(msg); + // Propergate config: this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1)); } diff --git a/src/filternode.hh b/src/filternode.hh index a1d0562..fc5bce8 100644 --- a/src/filternode.hh +++ b/src/filternode.hh @@ -47,7 +47,10 @@ public: } /** Destructor. */ - virtual ~FilterSink() { } + virtual ~FilterSink() { + _in_buffer.unref(); + _out_buffer.unref(); + } /** Configures the node. */ virtual void config(const Config &src_cfg) { diff --git a/src/freqshift.hh b/src/freqshift.hh index c18fe4b..0349827 100644 --- a/src/freqshift.hh +++ b/src/freqshift.hh @@ -8,7 +8,7 @@ namespace sdr { -/** A performant implementation of a frequency shift operation on integer signals. */ +/** A performant implementation of a frequency-shift operation on integer signals. */ template class FreqShiftBase { @@ -60,12 +60,15 @@ public: if (0 == _lut_inc) { return value; } // Get index, idx = (_lut_count/256) size_t idx = (_lut_count>>8); + // Handle negative frequency shifts if (0 > _freq_shift) { idx = _lut_size - idx - 1; } + // Apply value = ((_lut[idx] * value) >> Traits::shift); // Incement _lut_count _lut_count += _lut_inc; // _lut_count modulo (_lut_size*256) while (_lut_count >= (_lut_size<<8)) { _lut_count -= (_lut_size<<8); } + // Done. return value; } @@ -75,7 +78,7 @@ protected: // Every sample increments the LUT index by lut_inc/256. // The multiple is needed as ratio between the frequency shift _Fc and the sample rate _Fs // may not result in an integer increment. By simply flooring _lut_size*_Fc/_Fs, the actual - // down conversion may be much smaller than actual reuqired. Hence, the counter in therefore + // down conversion may be much smaller than actual reuqired. Hence, the counter is therefore // incremented by the integer (256*_lut_size*_Fc/_Fs) and the index is then obtained by // dividing _lut_count by 256 (right shift 8 bits). _lut_inc = (_lut_size*(1<<8)*std::abs(_freq_shift))/_Fs; diff --git a/src/logger.hh b/src/logger.hh index bfd484d..b54f0da 100644 --- a/src/logger.hh +++ b/src/logger.hh @@ -8,16 +8,17 @@ namespace sdr { -/** Specifies the possible log-level. */ +/** Specifies the possible log levels. */ typedef enum { - LOG_DEBUG, - LOG_INFO, - LOG_WARNING, - LOG_ERROR + LOG_DEBUG, ///< Every thing that may be of interest. + LOG_INFO, ///< Messages about state changes. + LOG_WARNING, ///< Non critical errors (i.e. data loss). + LOG_ERROR ///< Critical errors. } LogLevel; -/** A log message. */ +/** A log message. + * Bundles a message with a level. */ class LogMessage: public std::stringstream { public: @@ -41,7 +42,7 @@ protected: }; -/** Base class of all log message handlers. */ +/** Base class of all log-message handlers. */ class LogHandler { protected: @@ -68,6 +69,7 @@ public: StreamLogHandler(std::ostream &stream, LogLevel level); /** Destructor. */ virtual ~StreamLogHandler(); + /** Handles the message. */ virtual void handle(const LogMessage &msg); @@ -96,6 +98,7 @@ public: /** Logs a message. */ void log(const LogMessage &message); + /** Adds a message handler. The ownership of the hander is transferred to the logger * instance. */ void addHandler(LogHandler *handler); diff --git a/src/math.hh b/src/math.hh index 90e7387..51d2476 100644 --- a/src/math.hh +++ b/src/math.hh @@ -20,6 +20,7 @@ template <> inline int16_t fast_atan2(int8_t a, int8_t b) { return (a >= 0) ? angle : -angle; } +/** Implementation of atan2 approximation using integers. */ template <> inline int16_t fast_atan2(uint8_t ua, uint8_t ub) { int8_t a = (int16_t(ua)-(1<<7)); int8_t b = (int16_t(ub)-(1<<7)); diff --git a/src/portaudio.hh b/src/portaudio.hh index a38d085..18a02b9 100644 --- a/src/portaudio.hh +++ b/src/portaudio.hh @@ -70,7 +70,12 @@ public: } /** Destructor. */ - virtual ~PortSource() { if (0 != _stream) { Pa_CloseStream(_stream); } } + virtual ~PortSource() { + // close stream + if (0 != _stream) { Pa_CloseStream(_stream); } + // unref buffer + _buffer.unref(); + } /** Reads (blocking) the next buffer from the PortAudio stream. This function can be * connected to the idle event of the @c Queue. */ diff --git a/src/psk31.hh b/src/psk31.hh index b9f0fe2..3e9eb11 100644 --- a/src/psk31.hh +++ b/src/psk31.hh @@ -8,8 +8,8 @@ namespace sdr { -/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample-rate of - * at least 2000Hz and produces a bitstream with 31.25 Hz "sample-rate". Use the @c Varicode node +/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample rate of + * at least 2000Hz and produces a bitstream with 31.25 Hz "sample rate". Use the @c Varicode node * to decode this bitstream to ASCII chars. The BPSK31 signal should be centered around 0Hz. This * node uses a simple PLL to adjust for small detunings. */ template @@ -62,7 +62,10 @@ public: /** Destructor. */ virtual ~BPSK31() { - // pass... + // unreference buffers + _dl.unref(); + _hist.unref(); + _buffer.unref(); } virtual void config(const Config &src_cfg) diff --git a/src/rtlsource.cc b/src/rtlsource.cc index b1bdbcb..94cdb86 100644 --- a/src/rtlsource.cc +++ b/src/rtlsource.cc @@ -105,7 +105,7 @@ RTLSource::setGain(double gain) { void RTLSource::start() { - pthread_create(&_thread, 0, RTLSource::__rtl_srd_parallel_main, this); + pthread_create(&_thread, 0, RTLSource::__rtl_sdr_parallel_main, this); } void @@ -131,7 +131,7 @@ RTLSource::deviceName(size_t idx) { void * -RTLSource::__rtl_srd_parallel_main(void *ctx) { +RTLSource::__rtl_sdr_parallel_main(void *ctx) { RTLSource *self = reinterpret_cast(ctx); rtlsdr_read_async(self->_device, &RTLSource::__rtl_sdr_callback, self, 15, self->_buffer_size*2); diff --git a/src/rtlsource.hh b/src/rtlsource.hh index 0c200a2..13054b3 100644 --- a/src/rtlsource.hh +++ b/src/rtlsource.hh @@ -8,7 +8,10 @@ namespace sdr { -/** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles. */ +/** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles. + * This source runs in its own thread, hence the user does not need to trigger the reception of + * the next data chunk explicitly. The reception is started by calling the @c start method and + * stopped by calling the @c stop method. */ class RTLSource: public Source { public: @@ -18,14 +21,15 @@ public: * @c enableAGC and @c setGain methods. * * @param frequency Specifies the tuner frequency. - * @param sample_rate Specifies the sample rate. - * @param device_idx Specifies the device to be used. */ + * @param sample_rate Specifies the sample rate in Hz. + * @param device_idx Specifies the device to be used. The @c numDevices + * and @c deviceName static method can be used to select the desired device index. */ RTLSource(double frequency, double sample_rate=1e6, size_t device_idx=0); /** Destructor. */ virtual ~RTLSource(); - /** Returns the freuency of the tuner. */ + /** Returns the tuner frequency. */ inline double frequency() const { return _frequency; } /** (Re-) Sets the tuner frequency. */ void setFrequency(double frequency); @@ -64,8 +68,8 @@ public: static std::string deviceName(size_t idx); protected: - /** Prallel routine to receive some data from the device. */ - static void *__rtl_srd_parallel_main(void *ctx); + /** Parallel routine to receive some data from the device. */ + static void *__rtl_sdr_parallel_main(void *ctx); /** Callback to process received data. */ static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx); diff --git a/src/sdr.hh b/src/sdr.hh index d3b9710..3eaa227 100644 --- a/src/sdr.hh +++ b/src/sdr.hh @@ -290,6 +290,9 @@ #include "demod.hh" #include "psk31.hh" +#include "afsk.hh" +#include "baudot.hh" +#include "ax25.hh" #include "fftplan.hh" diff --git a/src/utils.cc b/src/utils.cc index f321ebc..b409fb0 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -185,3 +185,34 @@ SignedToUnsigned::_process_int16(const RawBuffer &in, const RawBuffer &out) { } this->send(RawBuffer(out, 0, num), true); } + + +/* ********************************************************************************************* * + * Implementation of TextDump + * ********************************************************************************************* */ +TextDump::TextDump(std::ostream &stream) + : Sink(), _stream(stream) +{ + // pass... +} + +void +TextDump::config(const Config &src_cfg) { + // Requires type + if (!src_cfg.hasType()) { return; } + if (src_cfg.type() != Traits::scalarId) { + ConfigError err; + err << "Can not configure TextDump node: Invalid input type " << src_cfg.type() + << ", expected " << Config::Type_u8 << "."; + throw err; + } + // done +} + +void +TextDump::process(const Buffer &buffer, bool allow_overwrite) { + for (size_t i=0; i +{ +public: + TextDump(std::ostream &stream=std::cerr); + + virtual void config(const Config &src_cfg); + virtual void process(const Buffer &buffer, bool allow_overwrite); + +protected: + std::ostream &_stream; +}; + + /** A Gaussian White Noise source. */ template class GWNSource: public Source