Skip to content

Commit

Permalink
Implement new squelch-triggered audio recorder
Browse files Browse the repository at this point in the history
Move wav_sink into receiver_base_cf
Move file name generation to receiver_base_cf
Switch to pwr_squelch
Pull in wavfile_sink and fix gqrx-sdr#1075
Implement new squelch-triggered audio recorder while keeping in mind that
gqrx-sdr#946 would be next.
Add tag processing to wavfile_sink_gqrx.
Implement event-driven GUI updates.
Add GUI options
Make it possible to switch betweensimple_squelch and pwr_squelch implementations
to improve performace on weak systems
Update build dependencies (add libsndfile)
  • Loading branch information
vladisslav2011 committed Sep 24, 2024
1 parent abc6906 commit 08ca0da
Show file tree
Hide file tree
Showing 25 changed files with 1,484 additions and 138 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ jobs:
soapysdr-module-remote \
libuhd-dev \
liborc-0.4-dev \
libhidapi-dev
libhidapi-dev \
libsndfile-dev
cd /tmp
git clone https://gitea.osmocom.org/sdr/rtl-sdr.git
Expand Down Expand Up @@ -171,7 +172,7 @@ jobs:
run: |
brew update
brew install --HEAD librtlsdr
brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf libserialport portaudio pybind11 six uhd qt@6 || true
brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf libserialport portaudio pybind11 six uhd libsndfile qt@6 || true
brew tap pothosware/homebrew-pothos
brew install soapyremote
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ jobs:
- name: Install dependencies
run: |
brew update
brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 six uhd qt@6 || true
brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 six uhd libsndfile qt@6 || true
cd /tmp
git clone https://gitea.osmocom.org/sdr/gr-osmosdr.git
cd gr-osmosdr
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ endif()

include(FindPkgConfig)
find_package(Gnuradio-osmosdr REQUIRED)
find_package(SNDFILE REQUIRED)

set(GR_REQUIRED_COMPONENTS RUNTIME ANALOG AUDIO BLOCKS DIGITAL FILTER FFT PMT)
find_package(Gnuradio REQUIRED COMPONENTS analog audio blocks digital filter fft network)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ To compile gqrx from source you need the following dependencies:
- Network
- Widgets
- Svg (runtime-only)
- libsndfile
- cmake version >= 3.2.0

Gqrx can be compiled from within Qt Creator or in a terminal:
Expand Down
34 changes: 34 additions & 0 deletions cmake/Modules/FindSNDFILE.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
find_package(PkgConfig)
PKG_CHECK_MODULES(PC_SNDFILE "sndfile")

FIND_PATH(SNDFILE_INCLUDE_DIRS
NAMES sndfile.h
HINTS ${PC_SNDFILE_INCLUDE_DIR}
${CMAKE_INSTALL_PREFIX}/include
PATHS
/usr/local/include
/usr/include
)

FIND_LIBRARY(SNDFILE_LIBRARIES
NAMES sndfile ${SNDFILE_LIBRARY_NAME}
HINTS ${PC_SNDFILE_LIBDIR}
${CMAKE_INSTALL_PREFIX}/lib
${CMAKE_INSTALL_PREFIX}/lib64
PATHS
${SNDFILE_INCLUDE_DIRS}/../lib
/usr/local/lib
/usr/lib
)

INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SNDFILE DEFAULT_MSG SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS)
MARK_AS_ADVANCED(SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS)

if (SNDFILE_FOUND AND NOT TARGET sndfile::sndfile)
add_library(sndfile::sndfile INTERFACE IMPORTED)
set_target_properties(sndfile::sndfile PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SNDFILE_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "${SNDFILE_LIBRARIES}"
)
endif()
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ target_link_libraries(${PROJECT_NAME}
${PULSEAUDIO_LIBRARY}
${PULSE-SIMPLE}
${PORTAUDIO_LIBRARIES}
${SNDFILE_LIBRARIES}
)

if(NOT Gnuradio_VERSION VERSION_LESS "3.10")
Expand Down
95 changes: 76 additions & 19 deletions src/applications/gqrx/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
/* create receiver object */
rx = new receiver("", "", 1);
rx->set_rf_freq(144500000.0);
rx->set_audio_rec_event_handler(std::bind(audio_rec_event, this,
std::placeholders::_1,
std::placeholders::_2));

// remote controller
remote = new RemoteControl();
Expand Down Expand Up @@ -243,12 +246,16 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
connect(uiDockAudio, SIGNAL(audioMuteChanged(bool)), this, SLOT(setAudioMute(bool)));
connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool)));
connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming()));
connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString)));
connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), remote, SLOT(startAudioRecorder(QString)));
connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec()));
connect(uiDockAudio, SIGNAL(audioRecStopped()), remote, SLOT(stopAudioRecorder()));
connect(uiDockAudio, SIGNAL(audioRecStart()), this, SLOT(startAudioRec()));
connect(uiDockAudio, SIGNAL(audioRecStart()), remote, SLOT(startAudioRecorder()));
connect(uiDockAudio, SIGNAL(audioRecStop()), this, SLOT(stopAudioRec()));
connect(uiDockAudio, SIGNAL(audioRecStop()), remote, SLOT(stopAudioRecorder()));
connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString)));
connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback()));
connect(uiDockAudio, SIGNAL(recDirChanged(QString)), this, SLOT(recDirChanged(QString)));
connect(uiDockAudio, SIGNAL(recSquelchTriggeredChanged(bool)), this, SLOT(recSquelchTriggeredChanged(bool)));
connect(uiDockAudio, SIGNAL(recMinTimeChanged(int)), this, SLOT(recMinTimeChanged(int)));
connect(uiDockAudio, SIGNAL(recMaxGapChanged(int)), this, SLOT(recMaxGapChanged(int)));
connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int)));
connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int)));
connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int)));
Expand Down Expand Up @@ -310,8 +317,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double)));
connect(remote, SIGNAL(newAudioGain(float)), this, SLOT(setAudioGain(float)));
connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double)));
connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder()));
connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder()));
connect(remote, SIGNAL(startAudioRecorderEvent()), this, SLOT(startAudioRec()));
connect(remote, SIGNAL(stopAudioRecorderEvent()), this, SLOT(stopAudioRec()));
connect(ui->plotter, SIGNAL(newFilterFreq(int, int)), remote, SLOT(setPassband(int, int)));
connect(remote, SIGNAL(newPassband(int)), this, SLOT(setPassband(int)));
connect(remote, SIGNAL(gainChanged(QString, double)), uiDockInputCtl, SLOT(setGain(QString,double)));
Expand All @@ -320,6 +327,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)

rds_timer = new QTimer(this);
connect(rds_timer, SIGNAL(timeout()), this, SLOT(rdsTimeout()));
connect(this, SIGNAL(sigAudioRecEvent(QString, bool)), this, SLOT(audioRecEvent(QString, bool)), Qt::QueuedConnection);

// enable frequency tooltips on FFT plot
ui->plotter->setTooltipsEnabled(true);
Expand Down Expand Up @@ -1551,11 +1559,47 @@ void MainWindow::rdsTimeout()
}
}

/**
* @brief Set audio recording directory.
* @param dir The directory, where audio files should be created.
*/
void MainWindow::recDirChanged(const QString dir)
{
rx->set_audio_rec_dir(dir.toStdString());
}

/**
* @brief Set audio recording squelch triggered mode.
* @param enabled New state.
*/
void MainWindow::recSquelchTriggeredChanged(const bool enabled)
{
rx->set_audio_rec_sql_triggered(enabled);
}

/**
* @brief Set audio recording squelch triggered minimum time.
* @param time_ms New time in milliseconds.
*/
void MainWindow::recMinTimeChanged(const int time_ms)
{
rx->set_audio_rec_min_time(time_ms);
}

/**
* @brief Set audio recording squelch triggered maximum gap time.
* @param time_ms New time in milliseconds.
*/
void MainWindow::recMaxGapChanged(const int time_ms)
{
rx->set_audio_rec_max_gap(time_ms);
}

/**
* @brief Start audio recorder.
* @param filename The file name into which audio should be recorded.
*/
void MainWindow::startAudioRec(const QString& filename)
void MainWindow::startAudioRec()
{
if (!d_have_audio)
{
Expand All @@ -1567,17 +1611,11 @@ void MainWindow::startAudioRec(const QString& filename)
msg_box.exec();
uiDockAudio->setAudioRecButtonState(false);
}
else if (rx->start_audio_recording(filename.toStdString()))
else if (rx->start_audio_recording())
{
ui->statusBar->showMessage(tr("Error starting audio recorder"));

/* reset state of record button */
uiDockAudio->setAudioRecButtonState(false);
}
else
{
ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename));
}
}

/** Stop audio recorder. */
Expand All @@ -1587,15 +1625,23 @@ void MainWindow::stopAudioRec()
{
/* okay, this one would be weird if it really happened */
ui->statusBar->showMessage(tr("Error stopping audio recorder"));

uiDockAudio->setAudioRecButtonState(true);
}
else
}

/** Audio recording is started or stopped. */
void MainWindow::audioRecEvent(const QString filename, bool is_running)
{
if(is_running)
{
ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename));
uiDockAudio->audioRecStarted(QString(filename));
}else{
/* reset state of record button */
uiDockAudio->audioRecStopped();
ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000);
}
}
}

}

/** Start playback of audio file. */
void MainWindow::startAudioPlayback(const QString& filename)
Expand Down Expand Up @@ -2581,3 +2627,14 @@ void MainWindow::checkDXCSpotTimeout()
DXCSpots::Get().checkSpotTimeout();
}

/** Called from GNU Radio thread */
void MainWindow::audioRecEventEmitter(std::string filename, bool is_running)
{
emit sigAudioRecEvent(QString(filename.data()), is_running);
}

/** Called from GNU Radio thread */
void MainWindow::audio_rec_event(MainWindow *self, std::string filename, bool is_running)
{
self->audioRecEventEmitter(filename, is_running);
}
12 changes: 11 additions & 1 deletion src/applications/gqrx/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class MainWindow : public QMainWindow
{
Q_OBJECT

signals:
void sigAudioRecEvent(const QString filename, bool is_running);

public:
explicit MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent = nullptr);
~MainWindow() override;
Expand Down Expand Up @@ -134,6 +137,8 @@ public slots:
/* key shortcuts */
void frequencyFocusShortcut();
void rxOffsetZeroShortcut();
void audioRecEventEmitter(std::string filename, bool is_running);
static void audio_rec_event(MainWindow *self, std::string filename, bool is_running);

private slots:
/* RecentConfig */
Expand Down Expand Up @@ -176,8 +181,13 @@ private slots:
void setPassband(int bandwidth);

/* audio recording and playback */
void startAudioRec(const QString& filename);
void recDirChanged(const QString dir);
void recSquelchTriggeredChanged(const bool enabled);
void recMinTimeChanged(const int time_ms);
void recMaxGapChanged(const int time_ms);
void startAudioRec();
void stopAudioRec();
void audioRecEvent(const QString filename, bool is_running);
void startAudioPlayback(const QString& filename);
void stopAudioPlayback();

Expand Down
Loading

0 comments on commit 08ca0da

Please sign in to comment.