mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2024-11-26 21:58:37 -05:00
Added #583: add periodic file generation, plus other options:
- Added a Recording menu, git commit -m Added
This commit is contained in:
parent
4d0f3a794d
commit
26deefd606
@ -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")) {
|
||||
|
@ -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<SDRManualDef> 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;
|
||||
|
185
src/AppFrame.cpp
185
src/AppFrame.cpp
@ -18,7 +18,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#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() ? "<Not Set>" : 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", "<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) {
|
||||
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;
|
||||
|
@ -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<int, wxMenuItem *> settingsMenuItems;
|
||||
|
||||
std::map<int, wxMenuItem *> audioSampleRateMenuItems;
|
||||
|
||||
//
|
||||
std::map<int, wxMenuItem *> recordingMenuItems;
|
||||
|
||||
std::map<int, wxMenuItem *> 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;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include "AudioFile.h"
|
||||
#include "CubicSDR.h"
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
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.
|
||||
|
@ -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;
|
||||
|
@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "AudioFileWAV.h"
|
||||
#include "CubicSDR.h"
|
||||
#include <iomanip>
|
||||
|
||||
//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();
|
||||
}
|
@ -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;
|
||||
|
@ -2,6 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "AudioSinkFileThread.h"
|
||||
#include <ctime>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -21,6 +21,8 @@ public:
|
||||
int channels;
|
||||
float peak;
|
||||
int type;
|
||||
boolean is_squelch_active = false;
|
||||
|
||||
std::vector<float> data;
|
||||
|
||||
AudioThreadInput() :
|
||||
|
@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include <memory>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
||||
#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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user