Added #583: add periodic file generation, plus other options:

- Added a Recording menu,
git commit -m Added
This commit is contained in:
vsonnier 2018-01-13 11:50:08 +01:00
parent 4d0f3a794d
commit 26deefd606
14 changed files with 441 additions and 91 deletions

View File

@ -535,6 +535,24 @@ bool AppConfig::verifyRecordingPath() {
return true; return true;
} }
void AppConfig::setRecordingSquelchOption(int enumChoice) {
recordingSquelchOption = enumChoice;
}
int AppConfig::getRecordingSquelchOption() {
return recordingSquelchOption;
}
void AppConfig::setRecordingFileTimeLimit(int nbSeconds) {
recordingFileTimeLimitSeconds = nbSeconds;
}
int AppConfig::getRecordingFileTimeLimit() {
return recordingFileTimeLimitSeconds;
}
void AppConfig::setConfigName(std::string configName) { void AppConfig::setConfigName(std::string configName) {
this->configName = configName; this->configName = configName;
} }
@ -588,8 +606,11 @@ bool AppConfig::save() {
*window_node->newChild("bookmark_visible") = bookmarksVisible.load(); *window_node->newChild("bookmark_visible") = bookmarksVisible.load();
} }
//Recording settings:
DataNode *rec_node = cfg.rootNode()->newChild("recording"); DataNode *rec_node = cfg.rootNode()->newChild("recording");
*rec_node->newChild("path") = recordingPath; *rec_node->newChild("path") = recordingPath;
*rec_node->newChild("squelch") = recordingSquelchOption;
*rec_node->newChild("file_time_limit") = recordingFileTimeLimitSeconds;
DataNode *devices_node = cfg.rootNode()->newChild("devices"); DataNode *devices_node = cfg.rootNode()->newChild("devices");
@ -773,6 +794,7 @@ bool AppConfig::load() {
} }
} }
//Recording settings:
if (cfg.rootNode()->hasAnother("recording")) { if (cfg.rootNode()->hasAnother("recording")) {
DataNode *rec_node = cfg.rootNode()->getNext("recording"); DataNode *rec_node = cfg.rootNode()->getNext("recording");
@ -780,6 +802,16 @@ bool AppConfig::load() {
DataNode *rec_path = rec_node->getNext("path"); DataNode *rec_path = rec_node->getNext("path");
recordingPath = rec_path->element()->toString(); recordingPath = rec_path->element()->toString();
} }
if (rec_node->hasAnother("squelch")) {
DataNode *rec_squelch = rec_node->getNext("squelch");
rec_squelch->element()->get(recordingSquelchOption);
}
if (rec_node->hasAnother("file_time_limit")) {
DataNode *rec_file_time_limit = rec_node->getNext("file_time_limit");
rec_file_time_limit->element()->get(recordingFileTimeLimitSeconds);
}
} }
if (cfg.rootNode()->hasAnother("devices")) { if (cfg.rootNode()->hasAnother("devices")) {

View File

@ -138,11 +138,17 @@ public:
void setBookmarksVisible(bool state); void setBookmarksVisible(bool state);
bool getBookmarksVisible(); bool getBookmarksVisible();
//Recording settings:
void setRecordingPath(std::string recPath); void setRecordingPath(std::string recPath);
std::string getRecordingPath(); std::string getRecordingPath();
bool verifyRecordingPath(); bool verifyRecordingPath();
void setRecordingSquelchOption(int enumChoice);
int getRecordingSquelchOption();
void setRecordingFileTimeLimit(int nbSeconds);
int getRecordingFileTimeLimit();
#if USE_HAMLIB #if USE_HAMLIB
int getRigModel(); int getRigModel();
void setRigModel(int rigModel); void setRigModel(int rigModel);
@ -189,7 +195,10 @@ private:
std::atomic_int dbOffset; std::atomic_int dbOffset;
std::vector<SDRManualDef> manualDevices; std::vector<SDRManualDef> manualDevices;
std::atomic_bool bookmarksVisible; std::atomic_bool bookmarksVisible;
std::string recordingPath;
std::string recordingPath = "";
int recordingSquelchOption = 0;
int recordingFileTimeLimitSeconds = 0;
#if USE_HAMLIB #if USE_HAMLIB
std::atomic_int rigModel, rigRate; std::atomic_int rigModel, rigRate;
std::string rigPort; std::string rigPort;

View File

@ -18,7 +18,7 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include "AudioThread.h" #include "AudioSinkFileThread.h"
#include "CubicSDR.h" #include "CubicSDR.h"
#include "DataTree.h" #include "DataTree.h"
#include "ColorTheme.h" #include "ColorTheme.h"
@ -403,9 +403,7 @@ AppFrame::AppFrame() :
// Make a menubar // Make a menubar
menuBar = new wxMenuBar; menuBar = new wxMenuBar;
fileMenu = makeFileMenu(); menuBar->Append(makeFileMenu(), wxT("&File"));
menuBar->Append(fileMenu, wxT("&File"));
settingsMenu = new wxMenu; settingsMenu = new wxMenu;
@ -494,6 +492,11 @@ AppFrame::AppFrame() :
menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate")); menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate"));
//Add a Recording menu
menuBar->Append(makeRecordingMenu(), wxT("Recordin&g"));
//
updateRecordingMenu();
//Add Display menu //Add Display menu
displayMenu = new wxMenu; displayMenu = new wxMenu;
@ -708,14 +711,6 @@ wxMenu *AppFrame::makeFileMenu() {
menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); menu->Append(wxID_SDR_START_STOP, "Stop / Start Device");
menu->AppendSeparator(); menu->AppendSeparator();
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
if (recPath.length() > 32) {
recPath = "..." + recPath.substr(recPath.length() - 32, 32);
}
menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", recPath.empty() ? "<Not Set>" : recPath));
menu->AppendSeparator();
menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_OPEN, "&Open Session");
menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVE, "&Save Session");
menu->Append(wxID_SAVEAS, "Save Session &As.."); menu->Append(wxID_SAVEAS, "Save Session &As..");
@ -741,15 +736,88 @@ wxMenu *AppFrame::makeFileMenu() {
#endif #endif
#endif #endif
fileMenu = menu;
return menu; return menu;
} }
void AppFrame::updateFileMenu() { wxMenu *AppFrame::makeRecordingMenu() {
wxMenu *newFileMenu = makeFileMenu();
menuBar->Replace(0, newFileMenu, wxT("&File")); recordingMenuItems.clear();
fileMenu = newFileMenu;
wxMenu *menu = new wxMenu;
recordingMenuItems[wxID_RECORDING_PATH] = menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", "<Not Set>"));
menu->AppendSeparator();
//Squelch options as sub-menu:
wxMenu *subMenu = new wxMenu;
recordingMenuItems[wxID_RECORDING_SQUELCH_BASE] = menu->AppendSubMenu(subMenu, "Squelch");
recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SILENCE, "Record Silence",
"Record below squelch-break audio as silence, i.e records as the user may hear.");
recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SKIP, "Skip Silence",
"Do not record below squelch-break audio, i.e squelch-break audio parts are packed together.");
recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_ALWAYS, "Record Always",
"Record everything irrespective of the squelch level.");
recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT] = menu->Append(wxID_RECORDING_FILE_TIME_LIMIT, getSettingsLabel("File time limit", "<Not Set>"),
"Creates a new file automatically, each time the recording lasts longer than the limit, named according to the current time.");
recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true);
recordingMenu = menu;
return menu;
} }
void AppFrame::updateRecordingMenu() {
// Recording path:
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
if (recPath.length() > 32) {
recPath = "..." + recPath.substr(recPath.length() - 32, 32);
}
recordingMenuItems[wxID_RECORDING_PATH]->SetItemLabel(getSettingsLabel("Set Recording Path", recPath.empty() ? "<Not Set>" : recPath));
//Squelch options:
int squelchEnumValue = wxGetApp().getConfig()->getRecordingSquelchOption();
if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) {
recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true);
recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence"));
} else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) {
recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP]->Check(true);
recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Skip Silence"));
} else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) {
recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS]->Check(true);
recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Always"));
}
else {
recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true);
recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence"));
}
//File time limit:
int fileTimeLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit();
if (fileTimeLimitSeconds <= 0) {
recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit","<Not Set>"));
}
else {
recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit",
std::to_string(fileTimeLimitSeconds), "s"));
}
}
void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) {
this->devInfo = devInfo; this->devInfo = devInfo;
@ -1512,6 +1580,73 @@ bool AppFrame::actionOnMenuLoadSave(wxCommandEvent& event) {
return false; return false;
} }
bool AppFrame::actionOnMenuRecording(wxCommandEvent& event) {
if (event.GetId() == wxID_RECORDING_PATH) {
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
wxDirDialog recPathDialog(this, _("File Path for Recordings"), recPath, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (recPathDialog.ShowModal() == wxID_CANCEL) {
return true;
}
wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString());
updateRecordingMenu();
return true;
}
else if (event.GetId() == wxID_RECORDING_SQUELCH_SILENCE) {
wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_SILENCE);
updateRecordingMenu();
return true;
}
else if (event.GetId() == wxID_RECORDING_SQUELCH_SKIP) {
wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_SKIP_SILENCE);
updateRecordingMenu();
return true;
}
else if (event.GetId() == wxID_RECORDING_SQUELCH_ALWAYS) {
wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_ALWAYS);
updateRecordingMenu();
return true;
}
else if (event.GetId() == wxID_RECORDING_FILE_TIME_LIMIT) {
int currentFileLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit();
long newFileLimit = wxGetNumberFromUser(wxString("\nFile time limit:\n") +
"\nCreates a new file automatically, each time the recording lasts longer than the limit, named according to the current time.\n\n " +
+ "min: 0 s (no limit)"
+ ", max: 36000 s (10 hours)\n",
"Time in seconds",
"File Time Limit",
//If a manual sample rate has already been input, recall this one.
currentFileLimitSeconds > 0 ? currentFileLimitSeconds : 0,
0,
36000,
this);
if (newFileLimit != -1) {
wxGetApp().getConfig()->setRecordingFileTimeLimit((int)newFileLimit);
updateRecordingMenu();
}
return true;
}
return false;
}
bool AppFrame::actionOnMenuRig(wxCommandEvent& event) { bool AppFrame::actionOnMenuRig(wxCommandEvent& event) {
bool bManaged = false; bool bManaged = false;
@ -1667,17 +1802,6 @@ void AppFrame::OnMenu(wxCommandEvent& event) {
} }
} }
} }
else if (event.GetId() == wxID_RECORDING_PATH) {
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
wxDirDialog recPathDialog(this, _("File Path for Recordings"), recPath, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (recPathDialog.ShowModal() == wxID_CANCEL) {
return;
}
wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString());
updateFileMenu();
}
else if (event.GetId() == wxID_LOW_PERF) { else if (event.GetId() == wxID_LOW_PERF) {
lowPerfMode = lowPerfMenuItem->IsChecked(); lowPerfMode = lowPerfMenuItem->IsChecked();
wxGetApp().getConfig()->setLowPerfMode(lowPerfMode); wxGetApp().getConfig()->setLowPerfMode(lowPerfMode);
@ -1741,6 +1865,9 @@ void AppFrame::OnMenu(wxCommandEvent& event) {
} }
else if (actionOnMenuAudioSampleRate(event)) { else if (actionOnMenuAudioSampleRate(event)) {
return; return;
}
else if (actionOnMenuRecording(event)) {
return;
} }
else if (actionOnMenuDisplay(event)) { else if (actionOnMenuDisplay(event)) {
return; return;

View File

@ -42,7 +42,6 @@
#define wxID_LOW_PERF 2011 #define wxID_LOW_PERF 2011
#define wxID_SET_DB_OFFSET 2012 #define wxID_SET_DB_OFFSET 2012
#define wxID_ABOUT_CUBICSDR 2013 #define wxID_ABOUT_CUBICSDR 2013
#define wxID_RECORDING_PATH 2014
#define wxID_OPEN_BOOKMARKS 2020 #define wxID_OPEN_BOOKMARKS 2020
#define wxID_SAVE_BOOKMARKS 2021 #define wxID_SAVE_BOOKMARKS 2021
@ -77,6 +76,13 @@
#define wxID_DEVICE_ID 3500 #define wxID_DEVICE_ID 3500
#define wxID_RECORDING_PATH 8500
#define wxID_RECORDING_SQUELCH_BASE 8501
#define wxID_RECORDING_SQUELCH_SILENCE 8502
#define wxID_RECORDING_SQUELCH_SKIP 8503
#define wxID_RECORDING_SQUELCH_ALWAYS 8504
#define wxID_RECORDING_FILE_TIME_LIMIT 8505
#define wxID_AUDIO_BANDWIDTH_BASE 9000 #define wxID_AUDIO_BANDWIDTH_BASE 9000
#define wxID_AUDIO_DEVICE_MULTIPLIER 50 #define wxID_AUDIO_DEVICE_MULTIPLIER 50
@ -103,7 +109,9 @@ public:
~AppFrame(); ~AppFrame();
wxMenu *makeFileMenu(); wxMenu *makeFileMenu();
void updateFileMenu();
wxMenu *makeRecordingMenu();
void updateRecordingMenu();
void initDeviceParams(SDRDeviceInfo *devInfo); void initDeviceParams(SDRDeviceInfo *devInfo);
void updateDeviceParams(); void updateDeviceParams();
@ -178,6 +186,7 @@ private:
bool actionOnMenuAudioSampleRate(wxCommandEvent& event); bool actionOnMenuAudioSampleRate(wxCommandEvent& event);
bool actionOnMenuDisplay(wxCommandEvent& event); bool actionOnMenuDisplay(wxCommandEvent& event);
bool actionOnMenuLoadSave(wxCommandEvent& event); bool actionOnMenuLoadSave(wxCommandEvent& event);
bool actionOnMenuRecording(wxCommandEvent& event);
bool actionOnMenuRig(wxCommandEvent& event); bool actionOnMenuRig(wxCommandEvent& event);
wxString getSettingsLabel(const std::string& settingsName, wxString getSettingsLabel(const std::string& settingsName,
@ -221,6 +230,10 @@ private:
std::map<int, wxMenuItem *> settingsMenuItems; std::map<int, wxMenuItem *> settingsMenuItems;
std::map<int, wxMenuItem *> audioSampleRateMenuItems; std::map<int, wxMenuItem *> audioSampleRateMenuItems;
//
std::map<int, wxMenuItem *> recordingMenuItems;
std::map<int, wxMenuItem *> directSamplingMenuItems; std::map<int, wxMenuItem *> directSamplingMenuItems;
wxMenuBar *menuBar; wxMenuBar *menuBar;
@ -231,6 +244,7 @@ private:
wxMenuItem *lowPerfMenuItem = nullptr; wxMenuItem *lowPerfMenuItem = nullptr;
wxMenu *fileMenu = nullptr; wxMenu *fileMenu = nullptr;
wxMenu *settingsMenu = nullptr; wxMenu *settingsMenu = nullptr;
wxMenu *recordingMenu = nullptr;
SoapySDR::ArgInfoList settingArgs; SoapySDR::ArgInfoList settingArgs;
int settingsIdMax; int settingsIdMax;

View File

@ -3,7 +3,6 @@
#include "AudioFile.h" #include "AudioFile.h"
#include "CubicSDR.h" #include "CubicSDR.h"
#include <iomanip>
#include <sstream> #include <sstream>
AudioFile::AudioFile() { AudioFile::AudioFile() {
@ -18,7 +17,7 @@ void AudioFile::setOutputFileName(std::string filename) {
filenameBase = filename; filenameBase = filename;
} }
std::string AudioFile::getOutputFileName(int sequenceNumber) { std::string AudioFile::getOutputFileName() {
std::string recPath = wxGetApp().getConfig()->getRecordingPath(); std::string recPath = wxGetApp().getConfig()->getRecordingPath();
@ -36,10 +35,6 @@ std::string AudioFile::getOutputFileName(int sequenceNumber) {
std::stringstream outputFileName; std::stringstream outputFileName;
outputFileName << recPath << filePathSeparator << filenameBaseSafe; outputFileName << recPath << filePathSeparator << filenameBaseSafe;
if (sequenceNumber > 0) {
outputFileName << "_" << std::setfill('0') << std::setw(3) << sequenceNumber;
}
int idx = 0; int idx = 0;
// If the file exists; then find the next non-existing file in sequence. // If the file exists; then find the next non-existing file in sequence.

View File

@ -14,7 +14,7 @@ public:
virtual void setOutputFileName(std::string filename); virtual void setOutputFileName(std::string filename);
virtual std::string getExtension() = 0; virtual std::string getExtension() = 0;
virtual std::string getOutputFileName(int sequenceNumber = 0); virtual std::string getOutputFileName();
virtual bool writeToFile(AudioThreadInputPtr input) = 0; virtual bool writeToFile(AudioThreadInputPtr input) = 0;
virtual bool closeFile() = 0; virtual bool closeFile() = 0;

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0+ // SPDX-License-Identifier: GPL-2.0+
#include "AudioFileWAV.h" #include "AudioFileWAV.h"
#include "CubicSDR.h"
#include <iomanip>
//limit file size to 2GB (- margin) for maximum compatibility. //limit file size to 2GB (- margin) for maximum compatibility.
#define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024) #define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024)
@ -63,7 +65,7 @@ std::string AudioFileWAV::getExtension()
bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) bool AudioFileWAV::writeToFile(AudioThreadInputPtr input)
{ {
if (!outputFileStream.is_open()) { if (!outputFileStream.is_open()) {
std::string ofName = getOutputFileName(currentSequenceNumber); std::string ofName = getOutputFileName();
outputFileStream.open(ofName.c_str(), std::ios::binary); outputFileStream.open(ofName.c_str(), std::ios::binary);
@ -85,7 +87,7 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input)
currentSequenceNumber++; currentSequenceNumber++;
currentFileSize = 0; currentFileSize = 0;
std::string ofName = getOutputFileName(currentSequenceNumber); std::string ofName = getOutputFileName();
outputFileStream.open(ofName.c_str(), std::ios::binary); outputFileStream.open(ofName.c_str(), std::ios::binary);
writeHeaderToFileStream(input); writeHeaderToFileStream(input);
@ -166,3 +168,39 @@ size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) {
return (size_t)(remainingBytesInFile / (input->channels * 2)); return (size_t)(remainingBytesInFile / (input->channels * 2));
} }
std::string AudioFileWAV::getOutputFileName() {
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
// Strip any invalid characters from the name
std::string stripChars("<>:\"/\\|?*");
std::string filenameBaseSafe = filenameBase;
for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) {
if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) {
filenameBaseSafe.replace(i, 1, "_");
}
}
// Create output file name
std::stringstream outputFileName;
outputFileName << recPath << filePathSeparator << filenameBaseSafe;
//customized part: append a sequence number.
if (currentSequenceNumber > 0) {
outputFileName << "_" << std::setfill('0') << std::setw(3) << currentSequenceNumber;
}
int idx = 0;
// If the file exists; then find the next non-existing file in sequence.
std::string fileNameCandidate = outputFileName.str();
while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) {
fclose(file);
fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx);
}
return fileNameCandidate + "." + getExtension();
}

View File

@ -13,10 +13,14 @@ public:
AudioFileWAV(); AudioFileWAV();
~AudioFileWAV(); ~AudioFileWAV();
std::string getExtension(); //override of the base method to generate multi-part
//WAV to overcome the WAV format size limit.
virtual std::string getOutputFileName();
bool writeToFile(AudioThreadInputPtr input); virtual std::string getExtension();
bool closeFile();
virtual bool writeToFile(AudioThreadInputPtr input);
virtual bool closeFile();
protected: protected:
std::ofstream outputFileStream; std::ofstream outputFileStream;

View File

@ -2,6 +2,9 @@
// SPDX-License-Identifier: GPL-2.0+ // SPDX-License-Identifier: GPL-2.0+
#include "AudioSinkFileThread.h" #include "AudioSinkFileThread.h"
#include <ctime>
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() {
@ -17,6 +20,57 @@ void AudioSinkFileThread::sink(AudioThreadInputPtr input) {
if (!audioFileHandler) { if (!audioFileHandler) {
return; return;
} }
//by default, always write something
bool isSomethingToWrite = true;
if (input->is_squelch_active) {
if (squelchOption == SQUELCH_RECORD_SILENCE) {
//patch with "silence"
input->data.assign(input->data.size(), 0.0f);
input->peak = 0.0f;
}
else if (squelchOption == SQUELCH_SKIP_SILENCE) {
isSomethingToWrite = false;
}
}
//else, nothing to do record as if squelch was not enabled.
if (!isSomethingToWrite) {
return;
}
if (fileTimeLimit > 0) {
durationMeasurement.update();
//duration exeeded, close this file and create another
//with "now" as timestamp.
if (durationMeasurement.getSeconds() > fileTimeLimit) {
audioFileHandler->closeFile();
//initialize the filename of the AudioFile with the current time
time_t t = std::time(nullptr);
tm ltm = *std::localtime(&t);
// GCC 5+
// fileName << "_" << std::put_time(&ltm, "%d-%m-%Y_%H-%M-%S");
char timeStr[512];
//International format: Year.Month.Day, also lexicographically sortable
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", &ltm);
audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr);
//reset duration counter
durationMeasurement.start();
//the following writeToFile will take care of creating another file.
}
}
// forward to output file handler // forward to output file handler
audioFileHandler->writeToFile(input); audioFileHandler->writeToFile(input);
} }
@ -28,8 +82,64 @@ void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInp
} }
audioFileHandler->closeFile(); audioFileHandler->closeFile();
//reset duration counter
durationMeasurement.start();
}
void AudioSinkFileThread::setAudioFileNameBase(const std::string& baseName) {
fileNameBase = baseName;
} }
void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) { void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) {
audioFileHandler = output; audioFileHandler = output;
//initialize the filename of the AudioFile with the current time
time_t t = std::time(nullptr);
tm ltm = *std::localtime(&t);
// GCC 5+
// fileName << "_" << std::put_time(&ltm, "%d-%m-%Y_%H-%M-%S");
char timeStr[512];
//International format: Year.Month.Day, also lexicographically sortable
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", &ltm);
audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr);
// reset Timer
durationMeasurement.start();
}
void AudioSinkFileThread::setSquelchOption(int squelchOptEnumValue) {
if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) {
squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE;
}
else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) {
squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE;
}
else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) {
squelchOption = AudioSinkFileThread::SQUELCH_RECORD_ALWAYS;
}
else {
squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE;
}
}
// Time limit
void AudioSinkFileThread::setFileTimeLimit(int nbSeconds) {
if (nbSeconds > 0) {
fileTimeLimit = nbSeconds;
}
else {
fileTimeLimit = 0;
}
} }

View File

@ -5,6 +5,7 @@
#include "AudioSinkThread.h" #include "AudioSinkThread.h"
#include "AudioFile.h" #include "AudioFile.h"
#include "Timer.h"
class AudioSinkFileThread : public AudioSinkThread { class AudioSinkFileThread : public AudioSinkThread {
@ -12,13 +13,38 @@ public:
AudioSinkFileThread(); AudioSinkFileThread();
~AudioSinkFileThread(); ~AudioSinkFileThread();
void sink(AudioThreadInputPtr input); enum SquelchOption {
void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); SQUELCH_RECORD_SILENCE = 0, // default value, record as a user would hear it.
SQUELCH_SKIP_SILENCE = 1, // skip below-squelch level.
SQUELCH_RECORD_ALWAYS = 2, // record irrespective of the squelch level.
SQUELCH_RECORD_MAX
};
virtual void sink(AudioThreadInputPtr input);
virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps);
void setAudioFileHandler(AudioFile *output); void setAudioFileHandler(AudioFile *output);
void setAudioFileNameBase(const std::string& baseName);
//Squelch
void setSquelchOption(int squelchOptEnumValue);
// Time limit
void setFileTimeLimit(int nbSeconds);
protected: protected:
std::string fileNameBase;
AudioFile *audioFileHandler = nullptr; AudioFile *audioFileHandler = nullptr;
SquelchOption squelchOption = SQUELCH_RECORD_SILENCE;
int fileTimeLimit = 0;
int fileTimeDurationSeconds = -1;
Timer durationMeasurement;
}; };

View File

@ -21,6 +21,8 @@ public:
int channels; int channels;
float peak; float peak;
int type; int type;
boolean is_squelch_active = false;
std::vector<float> data; std::vector<float> data;
AudioThreadInput() : AudioThreadInput() :

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0+ // SPDX-License-Identifier: GPL-2.0+
#include <memory> #include <memory>
#include <ctime>
#include <iomanip> #include <iomanip>
#include "DemodulatorInstance.h" #include "DemodulatorInstance.h"
@ -628,9 +627,6 @@ void DemodulatorInstance::startRecording() {
AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); AudioSinkFileThread *newSinkThread = new AudioSinkFileThread();
AudioFileWAV *afHandler = new AudioFileWAV(); AudioFileWAV *afHandler = new AudioFileWAV();
time_t t = std::time(nullptr);
tm ltm = *std::localtime(&t);
std::stringstream fileName; std::stringstream fileName;
std::wstring userLabel = getDemodulatorUserLabel(); std::wstring userLabel = getDemodulatorUserLabel();
@ -644,16 +640,12 @@ void DemodulatorInstance::startRecording() {
fileName << getLabel(); fileName << getLabel();
} }
// GCC 5+ newSinkThread->setAudioFileNameBase(fileName.str());
// fileName << "_" << std::put_time(&ltm, "%d-%m-%Y_%H-%M-%S");
char timeStr[512]; //attach options:
//International format: Year.Month.Day, also lexicographically sortable newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption());
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", &ltm); newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit());
fileName << "_" << timeStr;
afHandler->setOutputFileName(fileName.str());
newSinkThread->setAudioFileHandler(afHandler); newSinkThread->setAudioFileHandler(afHandler);
audioSinkThread = newSinkThread; audioSinkThread = newSinkThread;

View File

@ -232,7 +232,8 @@ void DemodulatorThread::run() {
localAudioSinkOutputQueue = audioSinkOutputQueue; localAudioSinkOutputQueue = audioSinkOutputQueue;
} }
if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) { //compute audio peak:
if (audioOutputQueue != nullptr && ati) {
ati->peak = 0; ati->peak = 0;
@ -242,19 +243,25 @@ void DemodulatorThread::run() {
ati->peak = p; ati->peak = p;
} }
} }
} else if (ati) { }
//squelch situation, but recording is on-going, so record "silence" to AudioSink:
if (localAudioSinkOutputQueue != nullptr) {
//Zero the ati samples //attach squelch flag to samples, to be used by audio sink.
ati->peak = 0; if (ati) {
ati->data.assign(ati->data.size(), 0.0f); ati->is_squelch_active = squelched;
}
//Push to audio sink, if any:
if (ati && localAudioSinkOutputQueue != nullptr) {
if (!localAudioSinkOutputQueue->try_push(ati)) { if (!localAudioSinkOutputQueue->try_push(ati)) {
std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl;
std::this_thread::yield();
} }
} }
//now we can nullify ati if squelched, to skip the next processing entirely.
if (ati && squelched) {
ati = nullptr; ati = nullptr;
} }
@ -345,12 +352,6 @@ void DemodulatorThread::run() {
std::this_thread::yield(); std::this_thread::yield();
} }
} }
if (localAudioSinkOutputQueue != nullptr) {
if (!localAudioSinkOutputQueue->try_push(ati)) {
std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl;
}
}
} }
DemodulatorThreadControlCommand command; DemodulatorThreadControlCommand command;