master
Hannes Matuschek 11 years ago
parent 2afdd5060c
commit fca04605f3

@ -1,22 +1,34 @@
# libsdr - A simple software defined radio (SDR) library # 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).
<a href="http://de.tinypic.com?ref=2jb2qfb" target="_blank"><img src="http://i61.tinypic.com/2jb2qfb.png" border="0" alt="SRD-RX"></a> <a href="http://de.tinypic.com?ref=2jb2qfb" target="_blank">
<img src="http://i61.tinypic.com/2jb2qfb.png" border="0" alt="SRD-RX">
</a>
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 ## 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. * `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user
* `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution filters. 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. * `PortAudio` (http://www.portaudio.com) - Allows for sound-card input and output.
* `librtlsdr` (http://rtlsdr.org) - Allows to interface RTL2382U based USB dongles. * `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: Compiling the library is the canonical cmake path:

@ -1,15 +1,19 @@
IF(SDR_WITH_PORTAUDIO) IF(SDR_WITH_PORTAUDIO)
add_executable(sdr_wavplay sdr_wavplay.cc) # add_executable(sdr_wavplay sdr_wavplay.cc)
target_link_libraries(sdr_wavplay ${LIBS} libsdr) # target_link_libraries(sdr_wavplay ${LIBS} libsdr)
add_executable(sdr_fm sdr_fm.cc) # add_executable(sdr_fm sdr_fm.cc)
target_link_libraries(sdr_fm ${LIBS} libsdr) # target_link_libraries(sdr_fm ${LIBS} libsdr)
add_executable(sdr_rec sdr_rec.cc) # add_executable(sdr_rec sdr_rec.cc)
target_link_libraries(sdr_rec ${LIBS} libsdr) # target_link_libraries(sdr_rec ${LIBS} libsdr)
add_executable(sdr_afsk1200 sdr_afsk1200.cc) add_executable(sdr_afsk1200 sdr_afsk1200.cc)
target_link_libraries(sdr_afsk1200 ${LIBS} libsdr) 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) ENDIF(SDR_WITH_PORTAUDIO)

@ -1,335 +1,13 @@
#include "wavfile.hh" #include "wavfile.hh"
#include "autocast.hh" #include "autocast.hh"
#include "interpolate.hh" #include "interpolate.hh"
#include "afsk.hh"
#include "ax25.hh"
#include "utils.hh"
using namespace sdr; 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<int16_t>, public Source {
public:
AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0)
: Sink<int16_t>(), 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<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AFSK1200: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
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<float>(2*8);
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
_dl_idx = 0;
// Assemble phase LUT:
_markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
// Allocate ring-buffers for mark and space symbols
_markHist = Buffer< std::complex<float> >(_corrLen);
_spaceHist = Buffer< std::complex<float> >(_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<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(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<uint8_t>(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<uint8_t>::scalarId, _baud, _buffer.size(), 1));
}
virtual void process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
size_t i=0, o=0;
while (i<buffer.size()) {
// Update sub-sampler
while ((_mu>1) && (i<buffer.size())) {
_markHist[_lutIdx] = float(buffer[i])*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(buffer[i])*_spaceLUT[_lutIdx];
// Modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
float symbol = _getSymbol();
// Put symbol into delay line
_dl[_dl_idx] = symbol; _dl[_dl_idx+8] = symbol;
_dl_idx = (_dl_idx+1)%8; _mu -= 1; i++;
}
if (i<buffer.size()) {
// Get interpolated symbol
float sample = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr;
// Get symbol
_symbols <<= 1; _symbols |= (sample>0);
// 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<double> 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<float> > _markLUT;
Buffer< std::complex<float> > _spaceLUT;
float _mu, _muIncr;
Buffer< float > _dl;
size_t _dl_idx;
Buffer< std::complex<float> > _markHist;
Buffer< std::complex<float> > _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<uint8_t> _buffer;
};
class AX25: public Sink<uint8_t>, public Source
{
public:
AX25()
: Sink<uint8_t>(), 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<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AX25: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
_bitstream = 0;
_bitbuffer = 0;
_state = 0;
_ptr = _rxbuffer;
// Allocate output buffer
_buffer = Buffer<uint8_t>(512);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
}
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
// Store bit in stream
_bitstream <<= 1; _bitstream |= !!buffer[i];
// Check for sync byte
if ((_bitstream & 0xff) == 0x7e) {
if (_state && ((_ptr - _rxbuffer) > 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<uint8_t> _buffer;
};
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -338,18 +16,23 @@ int main(int argc, char *argv[]) {
sdr::Logger::get().addHandler( sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
Queue &queue = Queue::get();
WavSource src(argv[1], 1024); WavSource src(argv[1], 1024);
if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; } if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; }
AutoCast< int16_t > cast; AutoCast< int16_t > cast;
AFSK demod; AFSK demod(1200, 1200, 2200, AFSK::TRANSITION);
AX25 decode; AX25 decode;
TextDump dump;
src.connect(&cast); src.connect(&cast);
cast.connect(&demod); cast.connect(&demod);
demod.connect(&decode); demod.connect(&decode);
decode.connect(&dump);
Queue::get().addIdle(&src, &WavSource::next); Queue::get().addIdle(&src, &WavSource::next);
src.addEOS(&queue, &Queue::stop);
Queue::get().start(); Queue::get().start();
Queue::get().wait(); Queue::get().wait();

@ -2,12 +2,13 @@
set(LIBSDR_SOURCES set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc buffer.cc node.cc queue.cc traits.cc
portaudio.cc utils.cc wavfile.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 set(LIBSDR_HEADERS sdr.hh math.hh
buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.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 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 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) if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.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<int16_t>(), 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<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AFSK1200: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
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<float>(2*8);
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
_dl_idx = 0;
// Assemble phase LUT:
_markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
// Allocate ring-buffers for mark and space symbols
_markHist = Buffer< std::complex<float> >(_corrLen);
_spaceHist = Buffer< std::complex<float> >(_corrLen);
_symbols = Buffer<int16_t>(_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<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(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<uint8_t>(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<uint8_t>::scalarId, _baud, _buffer.size(), 1));
}
void
AFSK::process(const Buffer<int16_t> &buffer, bool allow_overwrite)
{
size_t i=0, o=0;
while (i<buffer.size()) {
// Update sub-sampler
while ((_mu>=1) && (i<buffer.size())) {
_markHist[_lutIdx] = float(buffer[i])*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(buffer[i])*_spaceLUT[_lutIdx];
// inc _lutIdx, modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
// Get symbol from FIR filter results
float symbol = _getSymbol();
// Put symbol into delay line
_dl[_dl_idx] = symbol; _dl[_dl_idx+8] = symbol;
_dl_idx = (_dl_idx+1)%8; _mu -= 1; i++;
}
if (_mu >= 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)); }
}

@ -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<int16_t>, 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<int16_t> &buffer, bool allow_overwrite);
protected:
/** Performs the convolution filtering of the mark & space frequencies. */
inline double _getSymbol() {
std::complex<float> 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<float> > _markLUT;
/** Space frequency FIR filter LUT. */
Buffer< std::complex<float> > _spaceLUT;
/** FIR filter buffer. */
Buffer< std::complex<float> > _markHist;
/** FIR filter buffer. */
Buffer< std::complex<float> > _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<int16_t> _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<uint8_t> _buffer;
};
}
#endif // __SDR_AFSK_HH__

@ -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<uint8_t>(), 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<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AX25: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
_bitstream = 0;
_bitbuffer = 0;
_state = 0;
_ptr = _rxbuffer;
// Allocate output buffer
_buffer = Buffer<uint8_t>(512);
LogMessage msg(LOG_DEBUG);
msg << "Config AX.25 node.";
Logger::get().log(msg);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
}
void
AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
for (size_t i=0; i<buffer.size(); i++) {
// Store bit in stream
_bitstream = ((_bitstream << 1) | (buffer[i] & 0x01));
// Check for sync byte
if ((_bitstream & 0xff) == 0x7e) {
if ((1==_state) && ((_ptr - _rxbuffer) > 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);
}

@ -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<uint8_t>, 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<uint8_t> &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<uint8_t> _buffer;
};
}
#endif // __SDR_AX25_HH__

@ -14,9 +14,9 @@ namespace sdr {
/** This class performs several operations on the complex (integral) input stream, it first filters /** 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 * 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 * 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 * resulting stream. This node can be used to select a portion of the input spectrum and for the
* rate of the stream, allowing for some more expensive operations to be performed on the output * reduction of the stream rate, allowing for some more expensive operations to be performed on the
* stream. */ * output stream. */
template <class Scalar> template <class Scalar>
class IQBaseBand: public Sink< std::complex<Scalar> >, public Source, public FreqShiftBase<Scalar> class IQBaseBand: public Sink< std::complex<Scalar> >, public Source, public FreqShiftBase<Scalar>
{ {

@ -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<uint8_t>(), 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<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure Baudot: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
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<uint8_t>(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<uint8_t>::scalarId, 0, buffer_size, 1));
}
void
Baudot::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
size_t o=0;
for (size_t i=0; i<buffer.size(); i++) {
_bitstream = (_bitstream << 1) | (buffer[i] & 0x1); _bitcount++;
// Check if symbol as received:
if ((_bitsPerSymbol <= _bitcount) && (_pattern == (_bitstream & _mask))) {
_bitcount = 0;
// Unpack 5bit baudot code
uint8_t code = 0;
for (int j=0; j<5; j++) {
int shift = _stopHBits + 2*j;
code |= (((_bitstream>>shift)&0x01)<<j);
}
// Decode to ASCII
if (CHAR_STL == code) { _mode = LETTERS; }
else if (CHAR_STF == code) { _mode = FIGURES; }
else {
if (CHAR_SPA == code) { _mode = LETTERS; }
if (LETTERS == _mode) { _buffer[o++] = _letter[code]; }
else { _buffer[o++] = _figure[code]; }
}
}
}
if (0 < o) { this->send(_buffer.head(o)); }
}

@ -0,0 +1,72 @@
#ifndef __SDR_BAUDOT_HH__
#define __SDR_BAUDOT_HH__
#include "node.hh"
#include <map>
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<uint8_t>, 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<uint8_t> &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<uint8_t> _buffer;
};
}
#endif // BAUDOT_HH

@ -25,7 +25,9 @@ RawBuffer::RawBuffer(size_t N, BufferOwner *owner)
_refcount((int *)malloc(sizeof(int))), _owner(owner) _refcount((int *)malloc(sizeof(int))), _owner(owner)
{ {
// Check if data could be allocated // 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... // Set refcount, done...
if (_refcount) { (*_refcount) = 1; } if (_refcount) { (*_refcount) = 1; }
} }
@ -62,7 +64,7 @@ void RawBuffer::unref() {
// If empty -> skip... // If empty -> skip...
if ((0 == _ptr) || (0 == _refcount)) { return; } if ((0 == _ptr) || (0 == _refcount)) { return; }
// Decrement refcount // Decrement refcount
(*_refcount) -= 1; (*_refcount)--;
// If there is only one reference left and the buffer is owned -> notify owner, who holds the last // If there is only one reference left and the buffer is owned -> notify owner, who holds the last
// reference. // reference.
if ((1 == (*_refcount)) && (_owner)) { _owner->bufferUnused(*this); } if ((1 == (*_refcount)) && (_owner)) { _owner->bufferUnused(*this); }

@ -82,7 +82,7 @@ public:
void unref(); void unref();
/** Returns the reference counter. */ /** Returns the reference counter. */
inline int refCount() const { if (0 == _refcount) { return 0; } return (*_refcount); } 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. */ * owner holds the only reference to the buffer. */
inline bool isUnused() const { inline bool isUnused() const {
if (0 == _refcount) { return true; } if (0 == _refcount) { return true; }

@ -12,7 +12,7 @@ namespace sdr {
template <class Scalar> class Combine; template <class Scalar> 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 Scalar> template <class Scalar>
class CombineSink: public Sink<Scalar> class CombineSink: public Sink<Scalar>
{ {
@ -78,9 +78,11 @@ public:
virtual ~Combine() { virtual ~Combine() {
// Unref all buffers and free sinks // Unref all buffers and free sinks
for (size_t i=0; i<_sinks.size(); i++) { 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. */ /** Needs to be overridden. */

@ -26,7 +26,8 @@ public:
/** Destructor. */ /** Destructor. */
virtual ~AMDemod() { virtual ~AMDemod() {
// pass... // free buffers
_buffer.unref();
} }
/** Configures the AM demod. */ /** Configures the AM demod. */
@ -40,8 +41,11 @@ public:
<< ", expected " << Config::typeId< std::complex<Scalar> >(); << ", expected " << Config::typeId< std::complex<Scalar> >();
throw err; throw err;
} }
// Unreference previous buffer
_buffer.unref();
// Allocate buffer // Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize()); _buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG); LogMessage msg(LOG_DEBUG);
msg << "Configure AMDemod: " << this << std::endl msg << "Configure AMDemod: " << this << std::endl
@ -59,16 +63,6 @@ public:
/** Handles the I/Q input buffer. */ /** Handles the I/Q input buffer. */
virtual void process(const Buffer<std::complex<Scalar> > &buffer, bool allow_overwrite) virtual void process(const Buffer<std::complex<Scalar> > &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<Scalar> out_buffer; Buffer<Scalar> out_buffer;
// If source allow to overwrite the buffer, use it otherwise rely on own buffer // If source allow to overwrite the buffer, use it otherwise rely on own buffer
if (allow_overwrite) { out_buffer = Buffer<Scalar>(buffer); } if (allow_overwrite) { out_buffer = Buffer<Scalar>(buffer); }
@ -111,7 +105,7 @@ public:
/** Destructor. */ /** Destructor. */
virtual ~USBDemod() { virtual ~USBDemod() {
// pass... _buffer.unref();
} }
/** Configures the USB demodulator. */ /** Configures the USB demodulator. */
@ -125,6 +119,9 @@ public:
<< ", expected " << Config::typeId<CScalar>(); << ", expected " << Config::typeId<CScalar>();
throw err; throw err;
} }
// Unreference previous buffer
_buffer.unref();
// Allocate buffer // Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize()); _buffer = Buffer<Scalar>(src_cfg.bufferSize());
@ -146,16 +143,9 @@ public:
if (allow_overwrite) { if (allow_overwrite) {
// Process in-place // Process in-place
_process(buffer, Buffer<Scalar>(buffer)); _process(buffer, Buffer<Scalar>(buffer));
} else if (_buffer.isUnused()) { } else {
// Store result in buffer // Store result in buffer
_process(buffer, _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. */ /** Destructor. */
virtual ~FMDemod() { virtual ~FMDemod() {
// pass... _buffer.unref();
} }
/** Configures the FM demodulator. */ /** Configures the FM demodulator. */
@ -237,14 +227,8 @@ public:
if (allow_overwrite && _can_overwrite) { if (allow_overwrite && _can_overwrite) {
_process(buffer, Buffer<oScalar>(buffer)); _process(buffer, Buffer<oScalar>(buffer));
} else if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else { } else {
#ifdef SDR_DEBUG _process(buffer, _buffer);
LogMessage msg(LOG_WARNING);
msg << "FMDemod: Drop buffer: Output buffer still in use.";
Logger::get().log(msg);
#endif
} }
} }
@ -311,7 +295,7 @@ public:
/** Destructor. */ /** Destructor. */
virtual ~FMDeemph() { virtual ~FMDeemph() {
// pass... _buffer.unref();
} }
/** Returns true if the filter node is enabled. */ /** 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) )) ) ); 1.0/( (1.0-exp(-1.0/(src_cfg.sampleRate() * 75e-6) )) ) );
// Reset average: // Reset average:
_avg = 0; _avg = 0;
// Unreference previous buffer
_buffer.unref();
// Allocate buffer: // Allocate buffer:
_buffer = Buffer<Scalar>(src_cfg.bufferSize()); _buffer = Buffer<Scalar>(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: // Propergate config:
this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1)); this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
} }

@ -47,7 +47,10 @@ public:
} }
/** Destructor. */ /** Destructor. */
virtual ~FilterSink() { } virtual ~FilterSink() {
_in_buffer.unref();
_out_buffer.unref();
}
/** Configures the node. */ /** Configures the node. */
virtual void config(const Config &src_cfg) { virtual void config(const Config &src_cfg) {

@ -8,7 +8,7 @@
namespace sdr { 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 Scalar> template <class Scalar>
class FreqShiftBase class FreqShiftBase
{ {
@ -60,12 +60,15 @@ public:
if (0 == _lut_inc) { return value; } if (0 == _lut_inc) { return value; }
// Get index, idx = (_lut_count/256) // Get index, idx = (_lut_count/256)
size_t idx = (_lut_count>>8); size_t idx = (_lut_count>>8);
// Handle negative frequency shifts
if (0 > _freq_shift) { idx = _lut_size - idx - 1; } if (0 > _freq_shift) { idx = _lut_size - idx - 1; }
// Apply
value = ((_lut[idx] * value) >> Traits<Scalar>::shift); value = ((_lut[idx] * value) >> Traits<Scalar>::shift);
// Incement _lut_count // Incement _lut_count
_lut_count += _lut_inc; _lut_count += _lut_inc;
// _lut_count modulo (_lut_size*256) // _lut_count modulo (_lut_size*256)
while (_lut_count >= (_lut_size<<8)) { _lut_count -= (_lut_size<<8); } while (_lut_count >= (_lut_size<<8)) { _lut_count -= (_lut_size<<8); }
// Done.
return value; return value;
} }
@ -75,7 +78,7 @@ protected:
// Every sample increments the LUT index by lut_inc/256. // 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 // 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 // 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 // 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). // dividing _lut_count by 256 (right shift 8 bits).
_lut_inc = (_lut_size*(1<<8)*std::abs(_freq_shift))/_Fs; _lut_inc = (_lut_size*(1<<8)*std::abs(_freq_shift))/_Fs;

@ -8,16 +8,17 @@
namespace sdr { namespace sdr {
/** Specifies the possible log-level. */ /** Specifies the possible log levels. */
typedef enum { typedef enum {
LOG_DEBUG, LOG_DEBUG, ///< Every thing that may be of interest.
LOG_INFO, LOG_INFO, ///< Messages about state changes.
LOG_WARNING, LOG_WARNING, ///< Non critical errors (i.e. data loss).
LOG_ERROR LOG_ERROR ///< Critical errors.
} LogLevel; } LogLevel;
/** A log message. */ /** A log message.
* Bundles a message with a level. */
class LogMessage: public std::stringstream class LogMessage: public std::stringstream
{ {
public: public:
@ -41,7 +42,7 @@ protected:
}; };
/** Base class of all log message handlers. */ /** Base class of all log-message handlers. */
class LogHandler class LogHandler
{ {
protected: protected:
@ -68,6 +69,7 @@ public:
StreamLogHandler(std::ostream &stream, LogLevel level); StreamLogHandler(std::ostream &stream, LogLevel level);
/** Destructor. */ /** Destructor. */
virtual ~StreamLogHandler(); virtual ~StreamLogHandler();
/** Handles the message. */ /** Handles the message. */
virtual void handle(const LogMessage &msg); virtual void handle(const LogMessage &msg);
@ -96,6 +98,7 @@ public:
/** Logs a message. */ /** Logs a message. */
void log(const LogMessage &message); void log(const LogMessage &message);
/** Adds a message handler. The ownership of the hander is transferred to the logger /** Adds a message handler. The ownership of the hander is transferred to the logger
* instance. */ * instance. */
void addHandler(LogHandler *handler); void addHandler(LogHandler *handler);

@ -20,6 +20,7 @@ template <> inline int16_t fast_atan2<int8_t, int16_t>(int8_t a, int8_t b) {
return (a >= 0) ? angle : -angle; return (a >= 0) ? angle : -angle;
} }
/** Implementation of atan2 approximation using integers. */
template <> inline int16_t fast_atan2<uint8_t, int16_t>(uint8_t ua, uint8_t ub) { template <> inline int16_t fast_atan2<uint8_t, int16_t>(uint8_t ua, uint8_t ub) {
int8_t a = (int16_t(ua)-(1<<7)); int8_t a = (int16_t(ua)-(1<<7));
int8_t b = (int16_t(ub)-(1<<7)); int8_t b = (int16_t(ub)-(1<<7));

@ -70,7 +70,12 @@ public:
} }
/** Destructor. */ /** 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 /** Reads (blocking) the next buffer from the PortAudio stream. This function can be
* connected to the idle event of the @c Queue. */ * connected to the idle event of the @c Queue. */

@ -8,8 +8,8 @@
namespace sdr { namespace sdr {
/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample-rate of /** 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 * 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 * 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. */ * node uses a simple PLL to adjust for small detunings. */
template <class Scalar> template <class Scalar>
@ -62,7 +62,10 @@ public:
/** Destructor. */ /** Destructor. */
virtual ~BPSK31() { virtual ~BPSK31() {
// pass... // unreference buffers
_dl.unref();
_hist.unref();
_buffer.unref();
} }
virtual void config(const Config &src_cfg) virtual void config(const Config &src_cfg)

@ -105,7 +105,7 @@ RTLSource::setGain(double gain) {
void void
RTLSource::start() { RTLSource::start() {
pthread_create(&_thread, 0, RTLSource::__rtl_srd_parallel_main, this); pthread_create(&_thread, 0, RTLSource::__rtl_sdr_parallel_main, this);
} }
void void
@ -131,7 +131,7 @@ RTLSource::deviceName(size_t idx) {
void * void *
RTLSource::__rtl_srd_parallel_main(void *ctx) { RTLSource::__rtl_sdr_parallel_main(void *ctx) {
RTLSource *self = reinterpret_cast<RTLSource *>(ctx); RTLSource *self = reinterpret_cast<RTLSource *>(ctx);
rtlsdr_read_async(self->_device, &RTLSource::__rtl_sdr_callback, self, rtlsdr_read_async(self->_device, &RTLSource::__rtl_sdr_callback, self,
15, self->_buffer_size*2); 15, self->_buffer_size*2);

@ -8,7 +8,10 @@
namespace sdr { 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 class RTLSource: public Source
{ {
public: public:
@ -18,14 +21,15 @@ public:
* @c enableAGC and @c setGain methods. * @c enableAGC and @c setGain methods.
* *
* @param frequency Specifies the tuner frequency. * @param frequency Specifies the tuner frequency.
* @param sample_rate Specifies the sample rate. * @param sample_rate Specifies the sample rate in Hz.
* @param device_idx Specifies the device to be used. */ * @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); RTLSource(double frequency, double sample_rate=1e6, size_t device_idx=0);
/** Destructor. */ /** Destructor. */
virtual ~RTLSource(); virtual ~RTLSource();
/** Returns the freuency of the tuner. */ /** Returns the tuner frequency. */
inline double frequency() const { return _frequency; } inline double frequency() const { return _frequency; }
/** (Re-) Sets the tuner frequency. */ /** (Re-) Sets the tuner frequency. */
void setFrequency(double frequency); void setFrequency(double frequency);
@ -64,8 +68,8 @@ public:
static std::string deviceName(size_t idx); static std::string deviceName(size_t idx);
protected: protected:
/** Prallel routine to receive some data from the device. */ /** Parallel routine to receive some data from the device. */
static void *__rtl_srd_parallel_main(void *ctx); static void *__rtl_sdr_parallel_main(void *ctx);
/** Callback to process received data. */ /** Callback to process received data. */
static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx); static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx);

@ -290,6 +290,9 @@
#include "demod.hh" #include "demod.hh"
#include "psk31.hh" #include "psk31.hh"
#include "afsk.hh"
#include "baudot.hh"
#include "ax25.hh"
#include "fftplan.hh" #include "fftplan.hh"

@ -185,3 +185,34 @@ SignedToUnsigned::_process_int16(const RawBuffer &in, const RawBuffer &out) {
} }
this->send(RawBuffer(out, 0, num), true); this->send(RawBuffer(out, 0, num), true);
} }
/* ********************************************************************************************* *
* Implementation of TextDump
* ********************************************************************************************* */
TextDump::TextDump(std::ostream &stream)
: Sink<uint8_t>(), _stream(stream)
{
// pass...
}
void
TextDump::config(const Config &src_cfg) {
// Requires type
if (!src_cfg.hasType()) { return; }
if (src_cfg.type() != Traits<uint8_t>::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<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_stream << char(buffer[i]);
}
}

@ -869,6 +869,19 @@ protected:
}; };
class TextDump : public Sink<uint8_t>
{
public:
TextDump(std::ostream &stream=std::cerr);
virtual void config(const Config &src_cfg);
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
protected:
std::ostream &_stream;
};
/** A Gaussian White Noise source. */ /** A Gaussian White Noise source. */
template <class Scalar> template <class Scalar>
class GWNSource: public Source class GWNSource: public Source

Loading…
Cancel
Save