mirror of
https://github.com/gqrx-sdr/gqrx
synced 2026-08-13 17:37:52 -04:00
808 lines
20 KiB
C++
808 lines
20 KiB
C++
/* -*- c++ -*- */
|
|
/*
|
|
* Copyright 2011 Alexandru Csete OZ9AEC.
|
|
*
|
|
* Gqrx is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3, or (at your option)
|
|
* any later version.
|
|
*
|
|
* Gqrx is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Gqrx; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include <iostream>
|
|
#include <cmath>
|
|
|
|
#include <gr_top_block.h>
|
|
//#include <gr_audio_sink.h>
|
|
#include <gr_complex_to_xxx.h>
|
|
#include <gr_multiply_const_ff.h>
|
|
#include <gr_simple_squelch_cc.h>
|
|
|
|
#include "receiver.h"
|
|
#include "dsp/rx_source_fcd.h"
|
|
#include "dsp/correct_iq_cc.h"
|
|
#include "dsp/rx_filter.h"
|
|
#include "dsp/rx_meter.h"
|
|
#include "dsp/rx_demod_fm.h"
|
|
#include "dsp/rx_demod_am.h"
|
|
#include "dsp/rx_fft.h"
|
|
#include "dsp/rx_agc_xx.h"
|
|
|
|
//#include <gr_float_to_complex.h>
|
|
#include <pulseaudio/pa_sink.h>
|
|
//#include <pulseaudio/pa_source.h>
|
|
|
|
|
|
/*! \brief Public contructor.
|
|
* \param input_device Input device specifier, e.g. hw:1 for FCD source.
|
|
* \param audio_device Audio output device specifier,
|
|
* e.g. hw:0 when using ALSA or Portaudio.
|
|
*
|
|
* \todo Option to use UHD device instead of FCD.
|
|
*/
|
|
receiver::receiver(const std::string input_device, const std::string audio_device)
|
|
: d_bandwidth(96000.0), d_audio_rate(48000),
|
|
d_rf_freq(144800000.0), d_filter_offset(0.0),
|
|
d_demod(DEMOD_FM),
|
|
d_recording_iq(false),
|
|
d_recording_wav(false),
|
|
d_sniffer_active(false),
|
|
d_running(false)
|
|
{
|
|
tb = gr_make_top_block("gqrx");
|
|
|
|
src = make_rx_source_fcd(input_device);
|
|
//src->set_freq(d_rf_freq);
|
|
|
|
dc_corr = make_dc_corr_cc(0.01f);
|
|
iq_fft = make_rx_fft_c(4096, 0);
|
|
|
|
/* dummy I/Q recorder */
|
|
iq_sink = gr_make_file_sink(sizeof(gr_complex), "/tmp/gqrx.bin");
|
|
iq_sink->close();
|
|
|
|
nb = make_rx_nb_cc(d_bandwidth, 3.3, 2.5);
|
|
filter = make_rx_filter(d_bandwidth, d_filter_offset, -5000.0, 5000.0, 1000.0);
|
|
agc = make_rx_agc_cc(d_bandwidth, true, -100, 0, 2, 100, false);
|
|
sql = gr_make_simple_squelch_cc(-150.0, 0.001);
|
|
meter = make_rx_meter_c(DETECTOR_TYPE_RMS);
|
|
demod_ssb = gr_make_complex_to_real(1);
|
|
demod_fm = make_rx_demod_fm(d_bandwidth, d_bandwidth, 5000.0, 75.0e-6);
|
|
demod_am = make_rx_demod_am(d_bandwidth, d_bandwidth, true);
|
|
audio_rr = make_resampler_ff(d_bandwidth, d_audio_rate);
|
|
audio_fft = make_rx_fft_f(3072);
|
|
audio_gain = gr_make_multiply_const_ff(0.1);
|
|
|
|
audio_snk = make_pa_sink(audio_device, d_audio_rate, "GQRX", "Audio output");
|
|
|
|
/* wav sink and source is created when rec/play is started */
|
|
audio_null_sink = gr_make_null_sink(sizeof(float));
|
|
sniffer = make_sniffer_f();
|
|
/* sniffer_rr is created at each activation. */
|
|
|
|
tb->connect(src, 0, iq_sink, 0);
|
|
tb->connect(src, 0, nb, 0);
|
|
tb->connect(nb, 0, dc_corr, 0);
|
|
tb->connect(dc_corr, 0, iq_fft, 0);
|
|
tb->connect(dc_corr, 0, filter, 0);
|
|
|
|
tb->connect(filter, 0, meter, 0);
|
|
tb->connect(filter, 0, sql, 0);
|
|
tb->connect(sql, 0, agc, 0);
|
|
tb->connect(agc, 0, demod_fm, 0);
|
|
tb->connect(demod_fm, 0, audio_rr, 0);
|
|
tb->connect(audio_rr, 0, audio_fft, 0);
|
|
tb->connect(audio_rr, 0, audio_gain, 0);
|
|
tb->connect(audio_gain, 0, audio_snk, 0);
|
|
}
|
|
|
|
|
|
/*! \brief Public destructor. */
|
|
receiver::~receiver()
|
|
{
|
|
tb->stop();
|
|
|
|
/* FIXME: delete blocks? */
|
|
}
|
|
|
|
|
|
/*! \brief Start the receiver. */
|
|
void receiver::start()
|
|
{
|
|
/* FIXME: Check that flow graph is not running */
|
|
if (!d_running)
|
|
{
|
|
tb->start();
|
|
d_running = true;
|
|
}
|
|
}
|
|
|
|
|
|
/*! \brief Stop the receiver. */
|
|
void receiver::stop()
|
|
{
|
|
if (d_running)
|
|
{
|
|
tb->stop();
|
|
tb->wait(); // If the graph is needed to run again, wait() must be called after stop
|
|
d_running = false;
|
|
}
|
|
}
|
|
|
|
|
|
/*! \brief Select new input device.
|
|
*
|
|
* \bug When using ALSA, program will crash if the new device
|
|
* is the same as the previously used device:
|
|
* audio_alsa_source[hw:1]: Device or resource busy
|
|
*/
|
|
void receiver::set_input_device(const std::string device)
|
|
{
|
|
src->select_device(device);
|
|
}
|
|
|
|
|
|
/*! \brief Select new audio output device. */
|
|
void receiver::set_output_device(const std::string device)
|
|
{
|
|
tb->lock();
|
|
|
|
tb->disconnect(audio_gain, 0, audio_snk, 0);
|
|
audio_snk.reset();
|
|
audio_snk = make_pa_sink(device, d_audio_rate); // FIXME: does this keep app and stream name?
|
|
tb->connect(audio_gain, 0, audio_snk, 0);
|
|
|
|
tb->unlock();
|
|
}
|
|
|
|
|
|
|
|
/*! \brief Set RF frequency.
|
|
* \param freq_hz The desired frequency in Hz.
|
|
* \return RX_STATUS_ERROR if an error occurs, e.g. the frequency is out of range.
|
|
* \sa get_rf_freq()
|
|
*/
|
|
receiver::status receiver::set_rf_freq(double freq_hz)
|
|
{
|
|
d_rf_freq = freq_hz;
|
|
|
|
src->set_freq(d_rf_freq);
|
|
// FIXME: read back frequency?
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*! \brief Get RF frequency.
|
|
* \return The current RF frequency.
|
|
* \sa set_rf_freq()
|
|
*/
|
|
double receiver::get_rf_freq()
|
|
{
|
|
d_rf_freq = src->get_freq();
|
|
|
|
return d_rf_freq;
|
|
}
|
|
|
|
/*! \brief Set RF gain.
|
|
* \param gain_db The desired gain in dB.
|
|
* \return RX_STATUS_ERROR if an error occurs, e.g. the gain is out of valid range.
|
|
*/
|
|
receiver::status receiver::set_rf_gain(float gain_db)
|
|
{
|
|
src->set_gain(gain_db);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Set filter offset.
|
|
* \param offset_hz The desired filter offset in Hz.
|
|
* \return RX_STATUS_ERROR if the tuning offset is out of range.
|
|
*
|
|
* This method sets a new tuning offset for the receiver. The tuning offset is used
|
|
* to tune within the passband, i.e. select a specific channel within the received
|
|
* spectrum.
|
|
*
|
|
* The valid range for the tuning is +/- 0.5 * the bandwidth although this is just a
|
|
* logical limit.
|
|
*
|
|
* \sa get_filter_offset()
|
|
*/
|
|
receiver::status receiver::set_filter_offset(double offset_hz)
|
|
{
|
|
d_filter_offset = offset_hz;
|
|
filter->set_offset(d_filter_offset);
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*! \brief Get filterm offset.
|
|
* \return The current filter offset.
|
|
* \sa set_filter_offset()
|
|
*/
|
|
double receiver::get_filter_offset()
|
|
{
|
|
return d_filter_offset;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_filter(double low, double high, filter_shape shape)
|
|
{
|
|
double trans_width;
|
|
|
|
if ((low >= high) || (abs(high-low) < RX_FILTER_MIN_WIDTH))
|
|
return STATUS_ERROR;
|
|
|
|
switch (shape) {
|
|
|
|
case FILTER_SHAPE_SOFT:
|
|
trans_width = abs(high-low)*0.2;
|
|
break;
|
|
|
|
case FILTER_SHAPE_SHARP:
|
|
trans_width = abs(high-low)*0.01;
|
|
break;
|
|
|
|
case FILTER_SHAPE_NORMAL:
|
|
default:
|
|
trans_width = abs(high-low)*0.1;
|
|
break;
|
|
|
|
}
|
|
|
|
filter->set_param(low, high, trans_width);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_filter_low(double freq_hz)
|
|
{
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_filter_high(double freq_hz)
|
|
{
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_filter_shape(filter_shape shape)
|
|
{
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_freq_corr(int ppm)
|
|
{
|
|
src->set_freq_corr(ppm);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_dc_corr(double dci, double dcq)
|
|
{
|
|
src->set_dc_corr(dci, dcq);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
receiver::status receiver::set_iq_corr(double gain, double phase)
|
|
{
|
|
src->set_iq_corr(gain, phase);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Get current signal power.
|
|
* \param dbfs Whether to use dbfs or absolute power.
|
|
* \return The current signal power.
|
|
*
|
|
* This method returns the current signal power detected by the receiver. The detector
|
|
* is located after the band pass filter. The full scale is 1.0
|
|
*/
|
|
float receiver::get_signal_pwr(bool dbfs)
|
|
{
|
|
if (dbfs)
|
|
return meter->get_level_db();
|
|
else
|
|
return meter->get_level();
|
|
}
|
|
|
|
/*! \brief Get latest baseband FFT data. */
|
|
void receiver::get_iq_fft_data(std::complex<float>* fftPoints, int &fftsize)
|
|
{
|
|
iq_fft->get_fft_data(fftPoints, fftsize);
|
|
}
|
|
|
|
/*! \brief Get latest audio FFT data. */
|
|
void receiver::get_audio_fft_data(std::complex<float>* fftPoints, int &fftsize)
|
|
{
|
|
audio_fft->get_fft_data(fftPoints, fftsize);
|
|
}
|
|
|
|
receiver::status receiver::set_nb_on(int nbid, bool on)
|
|
{
|
|
if (nbid == 1)
|
|
nb->set_nb1_on(on);
|
|
else if (nbid == 2)
|
|
nb->set_nb2_on(on);
|
|
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
receiver::status receiver::set_nb_threshold(int nbid, float threshold)
|
|
{
|
|
if (nbid == 1)
|
|
nb->set_threshold1(threshold);
|
|
else if (nbid == 2)
|
|
nb->set_threshold2(threshold);
|
|
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
|
|
/*! \brief Set squelch level.
|
|
* \param level_db The new level in dBFS.
|
|
*/
|
|
receiver::status receiver::set_sql_level(double level_db)
|
|
{
|
|
sql->set_threshold(level_db);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
|
|
/*! \brief Set squelch alpha */
|
|
receiver::status receiver::set_sql_alpha(double alpha)
|
|
{
|
|
sql->set_alpha(alpha);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Enable/disable receiver AGC.
|
|
*
|
|
* When AGC is disabled a fixed manual gain is used, see set_agc_manual_gain().
|
|
*/
|
|
receiver::status receiver::set_agc_on(bool agc_on)
|
|
{
|
|
agc->set_agc_on(agc_on);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Enable/disable AGC hang. */
|
|
receiver::status receiver::set_agc_hang(bool use_hang)
|
|
{
|
|
agc->set_use_hang(use_hang);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Set AGC threshold. */
|
|
receiver::status receiver::set_agc_threshold(int threshold)
|
|
{
|
|
agc->set_threshold(threshold);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Set AGC slope. */
|
|
receiver::status receiver::set_agc_slope(int slope)
|
|
{
|
|
agc->set_slope(slope);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Set AGC decay time. */
|
|
receiver::status receiver::set_agc_decay(int decay_ms)
|
|
{
|
|
agc->set_decay(decay_ms);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
/*! \brief Set fixed gain used when AGC is OFF. */
|
|
receiver::status receiver::set_agc_manual_gain(int gain)
|
|
{
|
|
agc->set_manual_gain(gain);
|
|
return STATUS_OK; // FIXME
|
|
}
|
|
|
|
receiver::status receiver::set_demod(demod rx_demod)
|
|
{
|
|
status ret = STATUS_OK;
|
|
demod current_demod = d_demod;
|
|
|
|
/* check if new demodulator selection is valid */
|
|
if ((rx_demod < DEMOD_NONE) || (rx_demod >= DEMOD_NUM))
|
|
return STATUS_ERROR;
|
|
|
|
if (rx_demod == current_demod) {
|
|
/* nothing to do */
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/* lock graph while we reconfigure */
|
|
tb->lock();
|
|
|
|
/* disconnect current demodulator */
|
|
switch (current_demod) {
|
|
|
|
case DEMOD_NONE: /** FIXME! **/
|
|
case DEMOD_SSB:
|
|
tb->disconnect(agc, 0, demod_ssb, 0);
|
|
tb->disconnect(demod_ssb, 0, audio_rr, 0);
|
|
break;
|
|
|
|
case DEMOD_AM:
|
|
tb->disconnect(agc, 0, demod_am, 0);
|
|
tb->disconnect(demod_am, 0, audio_rr, 0);
|
|
break;
|
|
|
|
case DEMOD_FM:
|
|
tb->disconnect(agc, 0, demod_fm, 0);
|
|
tb->disconnect(demod_fm, 0, audio_rr, 0);
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
switch (rx_demod) {
|
|
|
|
case DEMOD_NONE: /** FIXME! **/
|
|
case DEMOD_SSB:
|
|
d_demod = rx_demod;
|
|
tb->connect(agc, 0, demod_ssb, 0);
|
|
tb->connect(demod_ssb, 0, audio_rr, 0);
|
|
break;
|
|
|
|
case DEMOD_AM:
|
|
d_demod = rx_demod;
|
|
tb->connect(agc, 0, demod_am, 0);
|
|
tb->connect(demod_am, 0, audio_rr, 0);
|
|
break;
|
|
|
|
case DEMOD_FM:
|
|
d_demod = DEMOD_FM;
|
|
tb->connect(agc, 0, demod_fm, 0);
|
|
tb->connect(demod_fm, 0, audio_rr, 0);
|
|
break;
|
|
|
|
default:
|
|
/* use FMN */
|
|
d_demod = DEMOD_FM;
|
|
tb->connect(agc, 0, demod_fm, 0);
|
|
tb->connect(demod_fm, 0, audio_rr, 0);
|
|
break;
|
|
}
|
|
|
|
/* continue processing */
|
|
tb->unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*! \brief Set maximum deviation of the FM demodulator.
|
|
* \param maxdev_hz The new maximum deviation in Hz.
|
|
*/
|
|
receiver::status receiver::set_fm_maxdev(float maxdev_hz)
|
|
{
|
|
demod_fm->set_max_dev(maxdev_hz);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_fm_deemph(double tau)
|
|
{
|
|
demod_fm->set_tau(tau);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Set AM DCR status.
|
|
* \param enabled Flag indicating whether DCR should be enabled or disabled.
|
|
*/
|
|
receiver::status receiver::set_am_dcr(bool enabled)
|
|
{
|
|
demod_am->set_dcr(enabled);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
receiver::status receiver::set_af_gain(float gain_db)
|
|
{
|
|
float k;
|
|
|
|
/* convert dB to factor */
|
|
k = pow(10.0, gain_db / 20.0);
|
|
//std::cout << "G:" << gain_db << "dB / K:" << k << std::endl;
|
|
audio_gain->set_k(k);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Start WAV file recorder.
|
|
* \param filename The filename where to record.
|
|
*
|
|
* A new recorder object is created every time we start recording and deleted every time
|
|
* we stop recording. The idea of creating one object and starting/stopping using different
|
|
* file names does not work with WAV files (the initial /tmp/gqrx.wav will not be stopped
|
|
* because the wav file can not be empty). See https://github.com/csete/gqrx/issues/36
|
|
*/
|
|
receiver::status receiver::start_audio_recording(const std::string filename)
|
|
{
|
|
if (d_recording_wav)
|
|
{
|
|
/* error - we are already recording */
|
|
std::cout << "ERROR: Can not start audio recorder (already recording)" << std::endl;
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
if (!d_running)
|
|
{
|
|
/* receiver is not running */
|
|
std::cout << "Can not start audio recorder (receiver not running)" << std::endl;
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
// not strictly necessary to lock but I think it is safer
|
|
tb->lock();
|
|
wav_sink = gr_make_wavfile_sink(filename.c_str(), 1, 48000, 16);
|
|
tb->connect(audio_gain, 0, wav_sink, 0);
|
|
tb->unlock();
|
|
d_recording_wav = true;
|
|
|
|
std::cout << "Recording audio to " << filename << std::endl;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Stop WAV file recorder. */
|
|
receiver::status receiver::stop_audio_recording()
|
|
{
|
|
if (!d_recording_wav) {
|
|
/* error: we are not recording */
|
|
std::cout << "ERROR: Can stop audio recorder (not recording)" << std::endl;
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
if (!d_running)
|
|
{
|
|
/* receiver is not running */
|
|
std::cout << "Can not start audio recorder (receiver not running)" << std::endl;
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
// not strictly necessary to lock but I think it is safer
|
|
tb->lock();
|
|
wav_sink->close();
|
|
tb->disconnect(audio_gain, 0, wav_sink, 0);
|
|
wav_sink.reset();
|
|
tb->unlock();
|
|
d_recording_wav = false;
|
|
|
|
std::cout << "Audio recorder stopped" << std::endl;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Start audio playback. */
|
|
receiver::status receiver::start_audio_playback(const std::string filename)
|
|
{
|
|
try {
|
|
wav_src = gr_make_wavfile_source(filename.c_str(), false);
|
|
}
|
|
catch (std::runtime_error &e) {
|
|
std::cout << "Error loading " << filename << ": " << e.what() << std::endl;
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/** FIXME: We can only handle 48k for now (should maybe use the audio_rr)? */
|
|
if (wav_src->sample_rate() != 48000) {
|
|
std::cout << "BUG: Can not handle sample rate " << wav_src->sample_rate() << std::cout;
|
|
wav_src.reset();
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
stop();
|
|
/* route demodulator output to null sink */
|
|
tb->disconnect(audio_rr, 0, audio_gain, 0);
|
|
tb->disconnect(audio_rr, 0, audio_fft, 0);
|
|
tb->connect(audio_rr, 0, audio_null_sink, 0);
|
|
tb->connect(wav_src, 0, audio_gain, 0);
|
|
tb->connect(wav_src, 0, audio_fft, 0);
|
|
start();
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Stop audio playback. */
|
|
receiver::status receiver::stop_audio_playback()
|
|
{
|
|
/* disconnect wav source and reconnect receiver */
|
|
stop();
|
|
tb->disconnect(wav_src, 0, audio_gain, 0);
|
|
tb->disconnect(wav_src, 0, audio_fft, 0);
|
|
tb->disconnect(audio_rr, 0, audio_null_sink, 0);
|
|
tb->connect(audio_rr, 0, audio_gain, 0);
|
|
tb->connect(audio_rr, 0, audio_fft, 0);
|
|
start();
|
|
|
|
/* delete wav_src since we can not change file name */
|
|
wav_src.reset();
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Start I/Q data recorder.
|
|
* \param filename The filename where to record.
|
|
*/
|
|
receiver::status receiver::start_iq_recording(const std::string filename)
|
|
{
|
|
if (d_recording_iq) {
|
|
/* error - we are already recording */
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/* iq_sink was created in the constructor */
|
|
if (iq_sink) {
|
|
/* not strictly necessary to lock but I think it is safer */
|
|
tb->lock();
|
|
iq_sink->open(filename.c_str());
|
|
tb->unlock();
|
|
d_recording_iq = true;
|
|
}
|
|
else {
|
|
std::cout << "BUG: I/Q file sink does not exist" << std::endl;
|
|
}
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Stop I/Q data recorder. */
|
|
receiver::status receiver::stop_iq_recording()
|
|
{
|
|
if (!d_recording_iq) {
|
|
/* error: we are not recording */
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
tb->lock();
|
|
iq_sink->close();
|
|
tb->unlock();
|
|
d_recording_iq = false;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Start playback of recorded I/Q data file.
|
|
* \param filename The file to play from. Must be raw file containing gr_complex samples.
|
|
* \param samprate The sample rate (currently fixed at 96ksps)
|
|
*/
|
|
receiver::status receiver::start_iq_playback(const std::string filename, float samprate)
|
|
{
|
|
if (samprate != d_bandwidth) {
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
try {
|
|
iq_src = gr_make_file_source(sizeof(gr_complex), filename.c_str(), false);
|
|
}
|
|
catch (std::runtime_error &e) {
|
|
std::cout << "Error loading " << filename << ": " << e.what() << std::endl;
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
tb->lock();
|
|
|
|
/* disconenct hardware source */
|
|
tb->disconnect(src, 0, nb, 0);
|
|
tb->disconnect(src, 0, iq_sink, 0);
|
|
|
|
/* connect I/Q source via throttle block */
|
|
tb->connect(iq_src, 0, nb, 0);
|
|
tb->connect(iq_src, 0, iq_sink, 0);
|
|
tb->unlock();
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*! \brief Stop I/Q data file playback.
|
|
* \return STATUS_OK
|
|
*
|
|
* This method will stop the I/Q data playback, disconnect the file source and throttle
|
|
* blocks, and reconnect the hardware source.
|
|
*
|
|
* FIXME: will probably crash if we try to stop playback that is not running.
|
|
*/
|
|
receiver::status receiver::stop_iq_playback()
|
|
{
|
|
tb->lock();
|
|
|
|
/* disconnect I/Q source and throttle block */
|
|
tb->disconnect(iq_src, 0, nb, 0);
|
|
tb->disconnect(iq_src, 0, iq_sink, 0);
|
|
|
|
/* reconenct hardware source */
|
|
tb->connect(src, 0, nb, 0);
|
|
tb->connect(src, 0, iq_sink, 0);
|
|
|
|
tb->unlock();
|
|
|
|
/* delete iq_src since we can not reuse for other files */
|
|
iq_src.reset();
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
|
|
/*! \brief Start data sniffer.
|
|
* \param buffsize The buffer that should be used in the sniffer.
|
|
* \return STATUS_OK if the sniffer was started, STATUS_ERROR if the sniffer is already in use.
|
|
*/
|
|
receiver::status receiver::start_sniffer(unsigned int samprate, int buffsize)
|
|
{
|
|
if (d_sniffer_active) {
|
|
/* sniffer already in use */
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
sniffer->set_buffer_size(buffsize);
|
|
sniffer_rr = make_resampler_ff(d_audio_rate, samprate);
|
|
tb->lock();
|
|
tb->connect(audio_rr, 0, sniffer_rr, 0);
|
|
tb->connect(sniffer_rr, 0, sniffer, 0);
|
|
tb->unlock();
|
|
d_sniffer_active = true;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*! \brief Stop data sniffer.
|
|
* \return STATUS_ERROR i the sniffer is not currently active.
|
|
*/
|
|
receiver::status receiver::stop_sniffer()
|
|
{
|
|
if (!d_sniffer_active) {
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
tb->lock();
|
|
tb->disconnect(audio_rr, 0, sniffer_rr, 0);
|
|
tb->disconnect(sniffer_rr, 0, sniffer, 0);
|
|
tb->unlock();
|
|
d_sniffer_active = false;
|
|
|
|
/* delete resampler */
|
|
sniffer_rr.reset();
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*! \brief Get sniffer data. */
|
|
void receiver::get_sniffer_data(float * outbuff, int &num)
|
|
{
|
|
sniffer->get_samples(outbuff, num);
|
|
}
|