From 26deefd60674a9ef2f0bbfe3b79dff0b849dfea9 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 13 Jan 2018 11:50:08 +0100 Subject: [PATCH] Added #583: add periodic file generation, plus other options: - Added a Recording menu, git commit -m Added --- src/AppConfig.cpp | 32 ++++++ src/AppConfig.h | 13 ++- src/AppFrame.cpp | 185 +++++++++++++++++++++++++----- src/AppFrame.h | 18 ++- src/audio/AudioFile.cpp | 7 +- src/audio/AudioFile.h | 2 +- src/audio/AudioFileWAV.cpp | 42 ++++++- src/audio/AudioFileWAV.h | 10 +- src/audio/AudioSinkFileThread.cpp | 110 ++++++++++++++++++ src/audio/AudioSinkFileThread.h | 30 ++++- src/audio/AudioSinkThread.h | 4 +- src/audio/AudioThread.h | 2 + src/demod/DemodulatorInstance.cpp | 22 ++-- src/demod/DemodulatorThread.cpp | 55 ++++----- 14 files changed, 441 insertions(+), 91 deletions(-) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index dc68da5..f85ab7c 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -535,6 +535,24 @@ bool AppConfig::verifyRecordingPath() { 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) { this->configName = configName; } @@ -588,8 +606,11 @@ bool AppConfig::save() { *window_node->newChild("bookmark_visible") = bookmarksVisible.load(); } + //Recording settings: DataNode *rec_node = cfg.rootNode()->newChild("recording"); *rec_node->newChild("path") = recordingPath; + *rec_node->newChild("squelch") = recordingSquelchOption; + *rec_node->newChild("file_time_limit") = recordingFileTimeLimitSeconds; DataNode *devices_node = cfg.rootNode()->newChild("devices"); @@ -773,6 +794,7 @@ bool AppConfig::load() { } } + //Recording settings: if (cfg.rootNode()->hasAnother("recording")) { DataNode *rec_node = cfg.rootNode()->getNext("recording"); @@ -780,6 +802,16 @@ bool AppConfig::load() { DataNode *rec_path = rec_node->getNext("path"); 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")) { diff --git a/src/AppConfig.h b/src/AppConfig.h index 5121272..c1fe392 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -138,10 +138,16 @@ public: void setBookmarksVisible(bool state); bool getBookmarksVisible(); + //Recording settings: void setRecordingPath(std::string recPath); std::string getRecordingPath(); + bool verifyRecordingPath(); + + void setRecordingSquelchOption(int enumChoice); + int getRecordingSquelchOption(); - bool verifyRecordingPath(); + void setRecordingFileTimeLimit(int nbSeconds); + int getRecordingFileTimeLimit(); #if USE_HAMLIB int getRigModel(); @@ -189,7 +195,10 @@ private: std::atomic_int dbOffset; std::vector manualDevices; std::atomic_bool bookmarksVisible; - std::string recordingPath; + + std::string recordingPath = ""; + int recordingSquelchOption = 0; + int recordingFileTimeLimitSeconds = 0; #if USE_HAMLIB std::atomic_int rigModel, rigRate; std::string rigPort; diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 5656604..77d8fce 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -18,7 +18,7 @@ #include #include -#include "AudioThread.h" +#include "AudioSinkFileThread.h" #include "CubicSDR.h" #include "DataTree.h" #include "ColorTheme.h" @@ -402,10 +402,8 @@ AppFrame::AppFrame() : // Make a menubar menuBar = new wxMenuBar; - - fileMenu = makeFileMenu(); - - menuBar->Append(fileMenu, wxT("&File")); + + menuBar->Append(makeFileMenu(), wxT("&File")); settingsMenu = new wxMenu; @@ -494,6 +492,11 @@ AppFrame::AppFrame() : menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate")); + //Add a Recording menu + menuBar->Append(makeRecordingMenu(), wxT("Recordin&g")); + // + updateRecordingMenu(); + //Add Display menu displayMenu = new wxMenu; @@ -708,14 +711,6 @@ wxMenu *AppFrame::makeFileMenu() { menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); 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() ? "" : recPath)); - - menu->AppendSeparator(); menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVEAS, "Save Session &As.."); @@ -741,15 +736,88 @@ wxMenu *AppFrame::makeFileMenu() { #endif #endif + fileMenu = menu; + return menu; } -void AppFrame::updateFileMenu() { - wxMenu *newFileMenu = makeFileMenu(); - menuBar->Replace(0, newFileMenu, wxT("&File")); - fileMenu = newFileMenu; +wxMenu *AppFrame::makeRecordingMenu() { + + recordingMenuItems.clear(); + + wxMenu *menu = new wxMenu; + + recordingMenuItems[wxID_RECORDING_PATH] = menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", "")); + + 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", ""), + "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() ? "" : 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","")); + } + else { + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit", + std::to_string(fileTimeLimitSeconds), "s")); + } +} void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { this->devInfo = devInfo; @@ -1512,6 +1580,73 @@ bool AppFrame::actionOnMenuLoadSave(wxCommandEvent& event) { 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 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) { lowPerfMode = lowPerfMenuItem->IsChecked(); wxGetApp().getConfig()->setLowPerfMode(lowPerfMode); @@ -1742,9 +1866,12 @@ void AppFrame::OnMenu(wxCommandEvent& event) { else if (actionOnMenuAudioSampleRate(event)) { return; } - else if (actionOnMenuDisplay(event)) { + else if (actionOnMenuRecording(event)) { return; } + else if (actionOnMenuDisplay(event)) { + return; + } //Optional : Rig else if (actionOnMenuRig(event)) { return; diff --git a/src/AppFrame.h b/src/AppFrame.h index 6f31a04..081db1c 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -42,7 +42,6 @@ #define wxID_LOW_PERF 2011 #define wxID_SET_DB_OFFSET 2012 #define wxID_ABOUT_CUBICSDR 2013 -#define wxID_RECORDING_PATH 2014 #define wxID_OPEN_BOOKMARKS 2020 #define wxID_SAVE_BOOKMARKS 2021 @@ -77,6 +76,13 @@ #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_DEVICE_MULTIPLIER 50 @@ -103,7 +109,9 @@ public: ~AppFrame(); wxMenu *makeFileMenu(); - void updateFileMenu(); + + wxMenu *makeRecordingMenu(); + void updateRecordingMenu(); void initDeviceParams(SDRDeviceInfo *devInfo); void updateDeviceParams(); @@ -178,6 +186,7 @@ private: bool actionOnMenuAudioSampleRate(wxCommandEvent& event); bool actionOnMenuDisplay(wxCommandEvent& event); bool actionOnMenuLoadSave(wxCommandEvent& event); + bool actionOnMenuRecording(wxCommandEvent& event); bool actionOnMenuRig(wxCommandEvent& event); wxString getSettingsLabel(const std::string& settingsName, @@ -221,6 +230,10 @@ private: std::map settingsMenuItems; std::map audioSampleRateMenuItems; + + // + std::map recordingMenuItems; + std::map directSamplingMenuItems; wxMenuBar *menuBar; @@ -231,6 +244,7 @@ private: wxMenuItem *lowPerfMenuItem = nullptr; wxMenu *fileMenu = nullptr; wxMenu *settingsMenu = nullptr; + wxMenu *recordingMenu = nullptr; SoapySDR::ArgInfoList settingArgs; int settingsIdMax; diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp index 1fefed3..6b15db7 100644 --- a/src/audio/AudioFile.cpp +++ b/src/audio/AudioFile.cpp @@ -3,7 +3,6 @@ #include "AudioFile.h" #include "CubicSDR.h" -#include #include AudioFile::AudioFile() { @@ -18,7 +17,7 @@ void AudioFile::setOutputFileName(std::string filename) { filenameBase = filename; } -std::string AudioFile::getOutputFileName(int sequenceNumber) { +std::string AudioFile::getOutputFileName() { std::string recPath = wxGetApp().getConfig()->getRecordingPath(); @@ -36,10 +35,6 @@ std::string AudioFile::getOutputFileName(int sequenceNumber) { std::stringstream outputFileName; outputFileName << recPath << filePathSeparator << filenameBaseSafe; - if (sequenceNumber > 0) { - outputFileName << "_" << std::setfill('0') << std::setw(3) << sequenceNumber; - } - int idx = 0; // If the file exists; then find the next non-existing file in sequence. diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h index 46fc1bd..c8636b8 100644 --- a/src/audio/AudioFile.h +++ b/src/audio/AudioFile.h @@ -14,7 +14,7 @@ public: virtual void setOutputFileName(std::string filename); virtual std::string getExtension() = 0; - virtual std::string getOutputFileName(int sequenceNumber = 0); + virtual std::string getOutputFileName(); virtual bool writeToFile(AudioThreadInputPtr input) = 0; virtual bool closeFile() = 0; diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index d60cfee..ad4078f 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0+ #include "AudioFileWAV.h" +#include "CubicSDR.h" +#include //limit file size to 2GB (- margin) for maximum compatibility. #define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024) @@ -63,7 +65,7 @@ std::string AudioFileWAV::getExtension() bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) { if (!outputFileStream.is_open()) { - std::string ofName = getOutputFileName(currentSequenceNumber); + std::string ofName = getOutputFileName(); outputFileStream.open(ofName.c_str(), std::ios::binary); @@ -85,7 +87,7 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) currentSequenceNumber++; currentFileSize = 0; - std::string ofName = getOutputFileName(currentSequenceNumber); + std::string ofName = getOutputFileName(); outputFileStream.open(ofName.c_str(), std::ios::binary); writeHeaderToFileStream(input); @@ -166,3 +168,39 @@ size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) { 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(); +} \ No newline at end of file diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index 883b893..dcd102d 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -13,10 +13,14 @@ public: 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); - bool closeFile(); + virtual std::string getExtension(); + + virtual bool writeToFile(AudioThreadInputPtr input); + virtual bool closeFile(); protected: std::ofstream outputFileStream; diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp index 2b00cdb..f17a5b6 100644 --- a/src/audio/AudioSinkFileThread.cpp +++ b/src/audio/AudioSinkFileThread.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: GPL-2.0+ #include "AudioSinkFileThread.h" +#include + +#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { @@ -17,6 +20,57 @@ void AudioSinkFileThread::sink(AudioThreadInputPtr input) { if (!audioFileHandler) { 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(<m, "%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", <m); + + 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 audioFileHandler->writeToFile(input); } @@ -28,8 +82,64 @@ void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInp } audioFileHandler->closeFile(); + + //reset duration counter + durationMeasurement.start(); +} + +void AudioSinkFileThread::setAudioFileNameBase(const std::string& baseName) { + + fileNameBase = baseName; } void AudioSinkFileThread::setAudioFileHandler(AudioFile * 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(<m, "%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", <m); + + 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; + } } diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h index 1ba4c4c..f0bdb73 100644 --- a/src/audio/AudioSinkFileThread.h +++ b/src/audio/AudioSinkFileThread.h @@ -5,6 +5,7 @@ #include "AudioSinkThread.h" #include "AudioFile.h" +#include "Timer.h" class AudioSinkFileThread : public AudioSinkThread { @@ -12,13 +13,38 @@ public: AudioSinkFileThread(); ~AudioSinkFileThread(); - void sink(AudioThreadInputPtr input); - void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + enum SquelchOption { + 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 setAudioFileNameBase(const std::string& baseName); + + //Squelch + void setSquelchOption(int squelchOptEnumValue); + + // Time limit + void setFileTimeLimit(int nbSeconds); + protected: + + std::string fileNameBase; + AudioFile *audioFileHandler = nullptr; + SquelchOption squelchOption = SQUELCH_RECORD_SILENCE; + int fileTimeLimit = 0; + + int fileTimeDurationSeconds = -1; + + Timer durationMeasurement; + }; diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h index b862b0d..754c75d 100644 --- a/src/audio/AudioSinkThread.h +++ b/src/audio/AudioSinkThread.h @@ -12,8 +12,8 @@ public: AudioSinkThread(); virtual ~AudioSinkThread(); - virtual void run(); - virtual void terminate(); + virtual void run(); + virtual void terminate(); virtual void sink(AudioThreadInputPtr input) = 0; virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index d37593b..540a8b2 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -21,6 +21,8 @@ public: int channels; float peak; int type; + boolean is_squelch_active = false; + std::vector data; AudioThreadInput() : diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 743c12a..d7014d6 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0+ #include -#include #include #include "DemodulatorInstance.h" @@ -628,9 +627,6 @@ void DemodulatorInstance::startRecording() { AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); AudioFileWAV *afHandler = new AudioFileWAV(); - time_t t = std::time(nullptr); - tm ltm = *std::localtime(&t); - std::stringstream fileName; std::wstring userLabel = getDemodulatorUserLabel(); @@ -643,17 +639,13 @@ void DemodulatorInstance::startRecording() { } else { fileName << getLabel(); } - - // GCC 5+ - // fileName << "_" << std::put_time(<m, "%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", <m); - fileName << "_" << timeStr; - - - afHandler->setOutputFileName(fileName.str()); + + newSinkThread->setAudioFileNameBase(fileName.str()); + + //attach options: + newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption()); + newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit()); + newSinkThread->setAudioFileHandler(afHandler); audioSinkThread = newSinkThread; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index b71ea98..82cb198 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -232,32 +232,39 @@ void DemodulatorThread::run() { localAudioSinkOutputQueue = audioSinkOutputQueue; } - if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) { - - ati->peak = 0; + //compute audio peak: + if (audioOutputQueue != nullptr && ati) { - for (auto data_i : ati->data) { - float p = fabs(data_i); - if (p > ati->peak) { - ati->peak = p; - } - } - } else if (ati) { - //squelch situation, but recording is on-going, so record "silence" to AudioSink: - if (localAudioSinkOutputQueue != nullptr) { + ati->peak = 0; - //Zero the ati samples - ati->peak = 0; - ati->data.assign(ati->data.size(), 0.0f); - - if (!localAudioSinkOutputQueue->try_push(ati)) { - std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + for (auto data_i : ati->data) { + float p = fabs(data_i); + if (p > ati->peak) { + ati->peak = p; } } + } - ati = nullptr; - } - + //attach squelch flag to samples, to be used by audio sink. + if (ati) { + ati->is_squelch_active = squelched; + } + + //Push to audio sink, if any: + if (ati && localAudioSinkOutputQueue != nullptr) { + + if (!localAudioSinkOutputQueue->try_push(ati)) { + 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; + } + //At that point, capture the current state of audioVisOutputQueue in a local //variable, and works with it with now on until the next while-turn. DemodulatorThreadOutputQueuePtr localAudioVisOutputQueue = nullptr; @@ -345,12 +352,6 @@ void DemodulatorThread::run() { 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;