mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2026-06-21 15:18:50 -04:00
Added #583: add periodic file generation, plus other options:
- Added a Recording menu, git commit -m Added
This commit is contained in:
@@ -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() :
|
||||
|
||||
Reference in New Issue
Block a user