/* -*- 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 #include #include //#include #include #include #include #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 #include //#include /*! \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* fftPoints, int &fftsize) { iq_fft->get_fft_data(fftPoints, fftsize); } /*! \brief Get latest audio FFT data. */ void receiver::get_audio_fft_data(std::complex* 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); }