Skip to content

Commit c51624d

Browse files
Implement new squelch-triggered audio recorder
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 #1075 Implement new squelch-triggered audio recorder while keeping in mind that #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)
1 parent 5c9a9ff commit c51624d

25 files changed

+1485
-137
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ jobs:
4444
soapysdr-module-remote \
4545
libuhd-dev \
4646
liborc-0.4-dev \
47-
libhidapi-dev
47+
libhidapi-dev \
48+
libsndfile1-dev
4849
4950
cd /tmp
5051
git clone https://github.com/Nuand/bladeRF.git
@@ -123,7 +124,7 @@ jobs:
123124
- name: Install dependencies
124125
run: |
125126
brew update
126-
brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf librtlsdr libserialport portaudio pybind11 uhd qt@6
127+
brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf librtlsdr libserialport portaudio pybind11 uhd libsndfile qt@6
127128
brew tap pothosware/homebrew-pothos
128129
brew install soapysdr soapyremote
129130

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
- name: Install dependencies
7878
run: |
7979
brew update
80-
brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 uhd qt@6
80+
brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 uhd libsndfile qt@6
8181
8282
cd /tmp
8383
git clone https://gitea.osmocom.org/sdr/gr-osmosdr.git

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ if(NOT Qt6_FOUND)
127127
endif()
128128
include(FindPkgConfig)
129129
find_package(Gnuradio-osmosdr REQUIRED)
130+
find_package(SNDFILE REQUIRED)
130131

131132
set(GR_REQUIRED_COMPONENTS RUNTIME ANALOG AUDIO BLOCKS DIGITAL FILTER FFT PMT)
132133
find_package(Gnuradio REQUIRED COMPONENTS analog audio blocks digital filter fft network)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ To compile gqrx from source you need the following dependencies:
109109
- Network
110110
- Widgets
111111
- Svg (runtime-only)
112+
- libsndfile
112113
- cmake version >= 3.2.0
113114

114115
Gqrx can be compiled from within Qt Creator or in a terminal:

cmake/Modules/FindSNDFILE.cmake

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
find_package(PkgConfig)
2+
PKG_CHECK_MODULES(PC_SNDFILE "sndfile")
3+
4+
FIND_PATH(SNDFILE_INCLUDE_DIRS
5+
NAMES sndfile.h
6+
HINTS ${PC_SNDFILE_INCLUDE_DIR}
7+
${CMAKE_INSTALL_PREFIX}/include
8+
PATHS
9+
/usr/local/include
10+
/usr/include
11+
)
12+
13+
FIND_LIBRARY(SNDFILE_LIBRARIES
14+
NAMES sndfile ${SNDFILE_LIBRARY_NAME}
15+
HINTS ${PC_SNDFILE_LIBDIR}
16+
${CMAKE_INSTALL_PREFIX}/lib
17+
${CMAKE_INSTALL_PREFIX}/lib64
18+
PATHS
19+
${SNDFILE_INCLUDE_DIRS}/../lib
20+
/usr/local/lib
21+
/usr/lib
22+
)
23+
24+
INCLUDE(FindPackageHandleStandardArgs)
25+
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SNDFILE DEFAULT_MSG SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS)
26+
MARK_AS_ADVANCED(SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS)
27+
28+
if (SNDFILE_FOUND AND NOT TARGET sndfile::sndfile)
29+
add_library(sndfile::sndfile INTERFACE IMPORTED)
30+
set_target_properties(sndfile::sndfile PROPERTIES
31+
INTERFACE_INCLUDE_DIRECTORIES "${SNDFILE_INCLUDE_DIRS}"
32+
INTERFACE_LINK_LIBRARIES "${SNDFILE_LIBRARIES}"
33+
)
34+
endif()

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ target_link_libraries(${PROJECT_NAME}
9999
${PULSEAUDIO_LIBRARY}
100100
${PULSE-SIMPLE}
101101
${PORTAUDIO_LIBRARIES}
102+
${SNDFILE_LIBRARIES}
102103
)
103104

104105
if(NOT Gnuradio_VERSION VERSION_LESS "3.10")

src/applications/gqrx/mainwindow.cpp

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
9999
/* create receiver object */
100100
rx = new receiver("", "", 1);
101101
rx->set_rf_freq(144500000.0f);
102+
rx->set_audio_rec_event_handler(std::bind(audio_rec_event, this,
103+
std::placeholders::_1,
104+
std::placeholders::_2));
102105

103106
// remote controller
104107
remote = new RemoteControl();
@@ -238,12 +241,16 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
238241
connect(uiDockAudio, SIGNAL(audioMuteChanged(bool)), this, SLOT(setAudioMute(bool)));
239242
connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool)));
240243
connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming()));
241-
connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString)));
242-
connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), remote, SLOT(startAudioRecorder(QString)));
243-
connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec()));
244-
connect(uiDockAudio, SIGNAL(audioRecStopped()), remote, SLOT(stopAudioRecorder()));
244+
connect(uiDockAudio, SIGNAL(audioRecStart()), this, SLOT(startAudioRec()));
245+
connect(uiDockAudio, SIGNAL(audioRecStart()), remote, SLOT(startAudioRecorder()));
246+
connect(uiDockAudio, SIGNAL(audioRecStop()), this, SLOT(stopAudioRec()));
247+
connect(uiDockAudio, SIGNAL(audioRecStop()), remote, SLOT(stopAudioRecorder()));
245248
connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString)));
246249
connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback()));
250+
connect(uiDockAudio, SIGNAL(recDirChanged(QString)), this, SLOT(recDirChanged(QString)));
251+
connect(uiDockAudio, SIGNAL(recSquelchTriggeredChanged(bool)), this, SLOT(recSquelchTriggeredChanged(bool)));
252+
connect(uiDockAudio, SIGNAL(recMinTimeChanged(int)), this, SLOT(recMinTimeChanged(int)));
253+
connect(uiDockAudio, SIGNAL(recMaxGapChanged(int)), this, SLOT(recMaxGapChanged(int)));
247254
connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int)));
248255
connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int)));
249256
connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int)));
@@ -301,8 +308,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
301308
connect(remote, SIGNAL(newSquelchLevel(double)), this, SLOT(setSqlLevel(double)));
302309
connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double)));
303310
connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double)));
304-
connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder()));
305-
connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder()));
311+
connect(remote, SIGNAL(startAudioRecorderEvent()), this, SLOT(startAudioRec()));
312+
connect(remote, SIGNAL(stopAudioRecorderEvent()), this, SLOT(stopAudioRec()));
306313
connect(ui->plotter, SIGNAL(newFilterFreq(int, int)), remote, SLOT(setPassband(int, int)));
307314
connect(remote, SIGNAL(newPassband(int)), this, SLOT(setPassband(int)));
308315
connect(remote, SIGNAL(gainChanged(QString, double)), uiDockInputCtl, SLOT(setGain(QString,double)));
@@ -311,6 +318,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
311318

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

315323
// enable frequency tooltips on FFT plot
316324
ui->plotter->setTooltipsEnabled(true);
@@ -1478,11 +1486,47 @@ void MainWindow::rdsTimeout()
14781486
}
14791487
}
14801488

1489+
/**
1490+
* @brief Set audio recording directory.
1491+
* @param dir The directory, where audio files should be created.
1492+
*/
1493+
void MainWindow::recDirChanged(const QString dir)
1494+
{
1495+
rx->set_audio_rec_dir(dir.toStdString());
1496+
}
1497+
1498+
/**
1499+
* @brief Set audio recording squelch triggered mode.
1500+
* @param enabled New state.
1501+
*/
1502+
void MainWindow::recSquelchTriggeredChanged(const bool enabled)
1503+
{
1504+
rx->set_audio_rec_sql_triggered(enabled);
1505+
}
1506+
1507+
/**
1508+
* @brief Set audio recording squelch triggered minimum time.
1509+
* @param time_ms New time in milliseconds.
1510+
*/
1511+
void MainWindow::recMinTimeChanged(const int time_ms)
1512+
{
1513+
rx->set_audio_rec_min_time(time_ms);
1514+
}
1515+
1516+
/**
1517+
* @brief Set audio recording squelch triggered maximum gap time.
1518+
* @param time_ms New time in milliseconds.
1519+
*/
1520+
void MainWindow::recMaxGapChanged(const int time_ms)
1521+
{
1522+
rx->set_audio_rec_max_gap(time_ms);
1523+
}
1524+
14811525
/**
14821526
* @brief Start audio recorder.
14831527
* @param filename The file name into which audio should be recorded.
14841528
*/
1485-
void MainWindow::startAudioRec(const QString& filename)
1529+
void MainWindow::startAudioRec()
14861530
{
14871531
if (!d_have_audio)
14881532
{
@@ -1494,17 +1538,11 @@ void MainWindow::startAudioRec(const QString& filename)
14941538
msg_box.exec();
14951539
uiDockAudio->setAudioRecButtonState(false);
14961540
}
1497-
else if (rx->start_audio_recording(filename.toStdString()))
1541+
else if (rx->start_audio_recording())
14981542
{
14991543
ui->statusBar->showMessage(tr("Error starting audio recorder"));
1500-
1501-
/* reset state of record button */
15021544
uiDockAudio->setAudioRecButtonState(false);
15031545
}
1504-
else
1505-
{
1506-
ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename));
1507-
}
15081546
}
15091547

15101548
/** Stop audio recorder. */
@@ -1514,15 +1552,23 @@ void MainWindow::stopAudioRec()
15141552
{
15151553
/* okay, this one would be weird if it really happened */
15161554
ui->statusBar->showMessage(tr("Error stopping audio recorder"));
1517-
1518-
uiDockAudio->setAudioRecButtonState(true);
15191555
}
1520-
else
1556+
}
1557+
1558+
/** Audio recording is started or stopped. */
1559+
void MainWindow::audioRecEvent(const QString filename, bool is_running)
1560+
{
1561+
if(is_running)
15211562
{
1563+
ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename));
1564+
uiDockAudio->audioRecStarted(QString(filename));
1565+
}else{
1566+
/* reset state of record button */
1567+
uiDockAudio->audioRecStopped();
15221568
ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000);
1523-
}
1524-
}
1569+
}
15251570

1571+
}
15261572

15271573
/** Start playback of audio file. */
15281574
void MainWindow::startAudioPlayback(const QString& filename)
@@ -2464,3 +2510,15 @@ void MainWindow::frequencyFocusShortcut()
24642510
{
24652511
ui->freqCtrl->setFrequencyFocus();
24662512
}
2513+
2514+
/** Called from GNU Radio thread */
2515+
void MainWindow::audioRecEventEmitter(std::string filename, bool is_running)
2516+
{
2517+
emit sigAudioRecEvent(QString(filename.data()), is_running);
2518+
}
2519+
2520+
/** Called from GNU Radio thread */
2521+
void MainWindow::audio_rec_event(MainWindow *self, std::string filename, bool is_running)
2522+
{
2523+
self->audioRecEventEmitter(filename, is_running);
2524+
}

src/applications/gqrx/mainwindow.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class MainWindow : public QMainWindow
5555
{
5656
Q_OBJECT
5757

58+
signals:
59+
void sigAudioRecEvent(const QString filename, bool is_running);
60+
5861
public:
5962
explicit MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent = nullptr);
6063
~MainWindow() override;
@@ -128,6 +131,8 @@ public slots:
128131
const QString &window_title);
129132
/* key shortcut */
130133
void frequencyFocusShortcut();
134+
void audioRecEventEmitter(std::string filename, bool is_running);
135+
static void audio_rec_event(MainWindow *self, std::string filename, bool is_running);
131136

132137
private slots:
133138
/* RecentConfig */
@@ -171,8 +176,13 @@ private slots:
171176
void setPassband(int bandwidth);
172177

173178
/* audio recording and playback */
174-
void startAudioRec(const QString& filename);
179+
void recDirChanged(const QString dir);
180+
void recSquelchTriggeredChanged(const bool enabled);
181+
void recMinTimeChanged(const int time_ms);
182+
void recMaxGapChanged(const int time_ms);
183+
void startAudioRec();
175184
void stopAudioRec();
185+
void audioRecEvent(const QString filename, bool is_running);
176186
void startAudioPlayback(const QString& filename);
177187
void stopAudioPlayback();
178188

0 commit comments

Comments
 (0)