diff --git a/CMakeLists.txt b/CMakeLists.txt index ce75fe7..24cd20e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -348,6 +348,9 @@ SET (cubicsdr_sources src/modules/modem/analog/ModemUSB.cpp src/audio/AudioThread.cpp src/audio/AudioSinkThread.cpp + src/audio/AudioSinkFileThread.cpp + src/audio/AudioFile.cpp + src/audio/AudioFileWAV.cpp src/util/Gradient.cpp src/util/Timer.cpp src/util/MouseTracker.cpp @@ -453,6 +456,9 @@ SET (cubicsdr_headers src/modules/modem/analog/ModemUSB.h src/audio/AudioThread.h src/audio/AudioSinkThread.h + src/audio/AudioSinkFileThread.h + src/audio/AudioFile.h + src/audio/AudioFileWAV.h src/util/Gradient.h src/util/Timer.h src/util/ThreadBlockingQueue.h diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp new file mode 100644 index 0000000..063d6dd --- /dev/null +++ b/src/audio/AudioFile.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFile.h" +#include "CubicSDR.h" + +AudioFile::AudioFile() { + +} + +AudioFile::~AudioFile() { + +} + +void AudioFile::setOutputFileName(std::string filename) { + filenameBase = filename; +} + +std::string AudioFile::getOutputFileName() { + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + // TODO: Handle invalid chars, etc.. + std::string filenameBaseSafe = filenameBase; + + return recPath + filePathSeparator + filenameBaseSafe + getSuffix() + "." + getExtension(); +} + diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h new file mode 100644 index 0000000..a982e4f --- /dev/null +++ b/src/audio/AudioFile.h @@ -0,0 +1,26 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioThread.h" + +class AudioFile +{ + +public: + AudioFile(); + virtual ~AudioFile(); + + virtual void setOutputFileName(std::string filename); + virtual std::string getExtension() = 0; + virtual std::string getSuffix() = 0; + virtual std::string getOutputFileName(); + + virtual bool writeToFile(AudioThreadInputPtr input) = 0; + virtual bool closeFile() = 0; + +protected: + std::string filenameBase; + +}; \ No newline at end of file diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp new file mode 100644 index 0000000..afe5224 --- /dev/null +++ b/src/audio/AudioFileWAV.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFileWAV.h" +// #include "WavFileFormatHandlerStuff.h" + +AudioFileWAV::AudioFileWAV() : AudioFile() { +} + +AudioFileWAV::~AudioFileWAV() { +} + + +std::string AudioFileWAV::getExtension() +{ + return "wav"; +} + +std::string AudioFileWAV::getSuffix() +{ + return suffix; +} + +bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) +{ + if (!outputFileStream.is_open()) { + suffix = ""; + std::string ofName = getOutputFileName(); + + // Check if file exists, sequence the suffix? + + outputFileStream.open(ofName.c_str(), std::ios::out | std::ios::binary); + } + + // write input data to wav file + + return true; +} + +bool AudioFileWAV::closeFile() +{ + if (outputFileStream.is_open()) { + outputFileStream.close(); + } + + return true; +} diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h new file mode 100644 index 0000000..2787ac3 --- /dev/null +++ b/src/audio/AudioFileWAV.h @@ -0,0 +1,26 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioFile.h" + +#include + +class AudioFileWAV : public AudioFile { + +public: + AudioFileWAV(); + ~AudioFileWAV(); + + std::string getExtension(); + std::string getSuffix(); + + bool writeToFile(AudioThreadInputPtr input); + bool closeFile(); + +protected: + std::ofstream outputFileStream; + std::string suffix; + +}; \ No newline at end of file diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp new file mode 100644 index 0000000..93fc182 --- /dev/null +++ b/src/audio/AudioSinkFileThread.cpp @@ -0,0 +1,34 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioSinkFileThread.h" + +AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { + +} + +AudioSinkFileThread::~AudioSinkFileThread() { + if (outputFileHandler != nullptr) { + outputFileHandler->closeFile(); + } +} + +void AudioSinkFileThread::sink(AudioThreadInputPtr input) { + if (!outputFileHandler) { + return; + } + // forward to output file handler + outputFileHandler->writeToFile(input); +} + +void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) { + // close, set new parameters, adjust file name sequence and re-open? + if (!outputFileHandler) { + return; + } +} + +void AudioSinkFileThread::setOutput(AudioFile * output) { + outputFileHandler = output; + outputFileHandler->setOutputFileName(sinkName); +} diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h new file mode 100644 index 0000000..4e3930e --- /dev/null +++ b/src/audio/AudioSinkFileThread.h @@ -0,0 +1,24 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioSinkThread.h" +#include "AudioFile.h" + +class AudioSinkFileThread : public AudioSinkThread { + +public: + AudioSinkFileThread(); + ~AudioSinkFileThread(); + + void sink(AudioThreadInputPtr input); + void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + + void setOutput(AudioFile *output); + +protected: + AudioFile *outputFileHandler = nullptr; + +}; + diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp index 85c4e5a..14c58ed 100644 --- a/src/audio/AudioSinkThread.cpp +++ b/src/audio/AudioSinkThread.cpp @@ -5,19 +5,16 @@ #define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) -AudioSinkThread::AudioSinkThread() -{ +AudioSinkThread::AudioSinkThread() { inputQueuePtr = std::make_shared(); setInputQueue("input", inputQueuePtr); } -AudioSinkThread::~AudioSinkThread() -{ +AudioSinkThread::~AudioSinkThread() { } -void AudioSinkThread::run() -{ +void AudioSinkThread::run() { #ifdef __APPLE__ pthread_t tID = pthread_self(); // ID of this thread int priority = sched_get_priority_max(SCHED_RR) - 1; @@ -46,38 +43,18 @@ void AudioSinkThread::run() inputRef.sampleRate = inp->sampleRate; } } +} - //Thread termination, prevent fancy things to happen, lock the whole thing: - std::lock_guard lock(m_mutex); - - // Drain any remaining inputs, with a non-blocking pop +void AudioSinkThread::terminate() { + IOThread::terminate(); inputQueuePtr->flush(); } -void AudioSinkThread::terminate() -{ - IOThread::terminate(); -} - -void AudioSinkThread::sink(AudioThreadInputPtr * input) -{ - // do something with the audio data -} - -void AudioSinkThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) -{ - // handle changes in stream properties -} - -void AudioSinkThread::setSinkName(std::string sinkName_in) -{ +void AudioSinkThread::setSinkName(std::string sinkName_in) { sinkName = sinkName_in; } -std::string AudioSinkThread::getSinkName() -{ +std::string AudioSinkThread::getSinkName() { return sinkName; } - - diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h index a8876d8..d01fa64 100644 --- a/src/audio/AudioSinkThread.h +++ b/src/audio/AudioSinkThread.h @@ -15,8 +15,8 @@ public: virtual void run(); virtual void terminate(); - virtual void sink(AudioThreadInputPtr *input); - virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + virtual void sink(AudioThreadInputPtr input) = 0; + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; virtual void setSinkName(std::string sinkName_in); virtual std::string getSinkName(); @@ -25,4 +25,5 @@ protected: std::recursive_mutex m_mutex; AudioThreadInputQueuePtr inputQueuePtr; std::string sinkName; + }; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 13f481b..6886b9f 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -106,6 +106,7 @@ DemodulatorInstance::~DemodulatorInstance() { delete demodulatorPreThread; delete demodulatorThread; delete audioThread; + delete audioSinkThread; break; } @@ -181,6 +182,10 @@ void DemodulatorInstance::terminate() { // std::cout << "Terminating demodulator preprocessor thread.." << std::endl; demodulatorPreThread->terminate(); + if (audioSinkThread != nullptr) { + audioSinkThread->terminate(); + } + //that will actually unblock the currently blocked push(). pipeIQInputData->flush(); pipeAudioData->flush(); @@ -204,6 +209,7 @@ bool DemodulatorInstance::isTerminated() { bool audioTerminated = audioThread->isTerminated(); bool demodTerminated = demodulatorThread->isTerminated(); bool preDemodTerminated = demodulatorPreThread->isTerminated(); + bool audioSinkTerminated = (audioSinkThread == nullptr) || audioSinkThread->isTerminated(); //Cleanup the worker threads, if the threads are indeed terminated. // threads are linked as t_PreDemod ==> t_Demod ==> t_Audio @@ -240,14 +246,27 @@ bool DemodulatorInstance::isTerminated() { if (audioTerminated) { if (t_Audio) { +#ifdef __APPLE__ + pthread_join(t_PreDemod, NULL); +#else t_Audio->join(); - delete t_Audio; +#endif t_Audio = nullptr; } } - bool terminated = audioTerminated && demodTerminated && preDemodTerminated; + if (audioSinkTerminated) { + + if (t_AudioSink != nullptr) { + t_AudioSink->join(); + + delete t_AudioSink; + t_AudioSink = nullptr; + } + } + + bool terminated = audioTerminated && demodTerminated && preDemodTerminated && audioSinkTerminated; return terminated; } diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index 846aa86..f36d635 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -11,6 +11,7 @@ #include "ModemDigital.h" #include "ModemAnalog.h" #include "AudioThread.h" +#include "AudioSinkThread.h" #if ENABLE_DIGITAL_LAB #include "DigitalConsole.h" @@ -139,6 +140,10 @@ private: DemodulatorThread *demodulatorThread; DemodulatorThreadControlCommandQueuePtr threadQueueControl; + AudioSinkThread *audioSinkThread = nullptr; + std::thread *t_AudioSink = nullptr; + AudioThreadInputQueuePtr audioSinkInputQueue; + //protects child thread creation and termination std::recursive_mutex m_thread_control_mutex;