mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2025-07-06 11:45:15 -04:00
Merge pull request #547 from cjcliffe/vso_ReBuffer_with_shared_ptr
Rebuffer automatic refcount management
This commit is contained in:
commit
3192ee0e71
@ -1612,6 +1612,9 @@ void AppFrame::OnIdle(wxIdleEvent& event) {
|
|||||||
updateDeviceParams();
|
updateDeviceParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//try to garbage collect the retired demodulators.
|
||||||
|
wxGetApp().getDemodMgr().garbageCollect();
|
||||||
|
|
||||||
DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
|
DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
|
||||||
|
|
||||||
if (demod && demod->isModemInitialized()) {
|
if (demod && demod->isModemInitialized()) {
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
#include "IOThread.h"
|
#include "IOThread.h"
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
|
|
||||||
std::mutex ReBufferGC::g_mutex;
|
|
||||||
std::set<ReferenceCounter *> ReBufferGC::garbage;
|
|
||||||
|
|
||||||
#define SPIN_WAIT_SLEEP_MS 5
|
#define SPIN_WAIT_SLEEP_MS 5
|
||||||
|
|
||||||
IOThread::IOThread() {
|
IOThread::IOThread() {
|
||||||
@ -130,7 +127,7 @@ bool IOThread::isTerminated(int waitMs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "ERROR: thread '" << typeid(*this).name() << "' has not terminated in time ! (> " << waitMs << " ms)" << std::endl;
|
std::cout << "ERROR: thread '" << typeid(*this).name() << "' has not terminated in time ! (> " << waitMs << " ms)" << std::endl << std::flush;
|
||||||
|
|
||||||
return terminated.load();
|
return terminated.load();
|
||||||
}
|
}
|
||||||
|
212
src/IOThread.h
212
src/IOThread.h
@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <deque>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
#include <climits>
|
||||||
#include "ThreadBlockingQueue.h"
|
#include "ThreadBlockingQueue.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
|
||||||
@ -23,163 +24,122 @@ struct map_string_less : public std::binary_function<std::string,std::string,boo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename PtrType>
|
||||||
class ReferenceCounter {
|
class ReBufferAge {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
PtrType ptr;
|
||||||
|
int age;
|
||||||
|
|
||||||
//default constructor, initialized with refcont 1, sounds very natural
|
virtual ~ReBufferAge() {};
|
||||||
ReferenceCounter() {
|
|
||||||
refCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// void setIndex(int idx) {
|
|
||||||
// std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
// index = idx;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int getIndex() {
|
|
||||||
// std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
// return index;
|
|
||||||
// }
|
|
||||||
|
|
||||||
void setRefCount(int rc) {
|
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
refCount = rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void decRefCount() {
|
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
refCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getRefCount() {
|
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
return refCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access to the own mutex protecting the ReferenceCounter, i.e the monitor of the class
|
|
||||||
std::recursive_mutex& getMonitor() const {
|
|
||||||
return m_mutex;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
//this is a basic mutex for all ReferenceCounter derivatives operations INCLUDING the counter itself for consistency !
|
|
||||||
mutable std::recursive_mutex m_mutex;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int refCount;
|
|
||||||
// int index;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define REBUFFER_GC_LIMIT 100
|
#define REBUFFER_GC_LIMIT 100
|
||||||
|
#define REBUFFER_WARNING_THRESHOLD 150
|
||||||
|
|
||||||
class ReBufferGC {
|
template<typename BufferType>
|
||||||
public:
|
|
||||||
static void garbageCollect() {
|
|
||||||
std::lock_guard < std::mutex > lock(g_mutex);
|
|
||||||
|
|
||||||
std::deque<ReferenceCounter *> garbageRemoval;
|
|
||||||
for (typename std::set<ReferenceCounter *>::iterator i = garbage.begin(); i != garbage.end(); i++) {
|
|
||||||
if ((*i)->getRefCount() <= 0) {
|
|
||||||
garbageRemoval.push_back(*i);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// std::cout << "Garbage in queue buffer idx #" << (*i)->getIndex() << ", " << (*i)->getRefCount() << " usage(s)" << std::endl;
|
|
||||||
std::cout << "Garbage in queue buffer with " << (*i)->getRefCount() << " usage(s)" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( garbageRemoval.size() ) {
|
|
||||||
std::cout << "Garbage collecting " << garbageRemoval.size() << " ReBuffer(s)" << std::endl;
|
|
||||||
while (!garbageRemoval.empty()) {
|
|
||||||
ReferenceCounter *ref = garbageRemoval.back();
|
|
||||||
garbageRemoval.pop_back();
|
|
||||||
garbage.erase(ref);
|
|
||||||
delete ref;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addGarbage(ReferenceCounter *ref) {
|
|
||||||
std::lock_guard < std::mutex > lock(g_mutex);
|
|
||||||
garbage.insert(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::mutex g_mutex;
|
|
||||||
static std::set<ReferenceCounter *> garbage;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<class BufferType = ReferenceCounter>
|
|
||||||
class ReBuffer {
|
class ReBuffer {
|
||||||
|
|
||||||
|
typedef typename std::shared_ptr<BufferType> ReBufferPtr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ReBuffer(std::string bufferId) : bufferId(bufferId) {
|
ReBuffer(std::string bufferId) : bufferId(bufferId) {
|
||||||
// indexCounter.store(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferType *getBuffer() {
|
/// Return a new ReBuffer_ptr usable by the application.
|
||||||
|
ReBufferPtr getBuffer() {
|
||||||
|
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
BufferType* buf = nullptr;
|
// iterate the ReBuffer_ptr list: if the std::shared_ptr count == 1, it means
|
||||||
for (outputBuffersI = outputBuffers.begin(); outputBuffersI != outputBuffers.end(); outputBuffersI++) {
|
//it is only referenced in outputBuffers itself, so available for re-use.
|
||||||
if (buf == nullptr && (*outputBuffersI)->getRefCount() <= 0) {
|
//else if the std::shared_ptr count <= 1, make it age.
|
||||||
buf = (*outputBuffersI);
|
//else the ReBuffer_ptr is in use, don't use it.
|
||||||
buf->setRefCount(1);
|
|
||||||
} else if ((*outputBuffersI)->getRefCount() <= 0) {
|
|
||||||
(*outputBuffersI)->decRefCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ReBufferPtr buf = nullptr;
|
||||||
|
|
||||||
|
outputBuffersI it = outputBuffers.begin();
|
||||||
|
|
||||||
|
while (it != outputBuffers.end()) {
|
||||||
|
|
||||||
|
long use = it->ptr.use_count();
|
||||||
|
//1. If we encounter a shared_ptr with a use count of 0, this
|
||||||
|
//is a bug since it is supposed to be at least 1, because it is referenced here.
|
||||||
|
//in this case, purge it from here and trace.
|
||||||
|
if (use == 0) {
|
||||||
|
it = outputBuffers.erase(it);
|
||||||
|
std::cout << "Warning: in ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "', found 1 dangling buffer !" << std::endl << std::flush;
|
||||||
|
}
|
||||||
|
else if (use == 1) {
|
||||||
|
if (buf == nullptr) {
|
||||||
|
it->age = 1; //select this one.
|
||||||
|
buf = it->ptr;
|
||||||
|
//std::cout << "**" << std::flush;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//make the other unused buffers age
|
||||||
|
it->age--;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
} //end while
|
||||||
|
|
||||||
|
//2.1 Garbage collect the oldest (last element) if it aged too much, and return the buffer
|
||||||
if (buf != nullptr) {
|
if (buf != nullptr) {
|
||||||
if (outputBuffers.back()->getRefCount() < -REBUFFER_GC_LIMIT) {
|
|
||||||
BufferType *ref = outputBuffers.back();
|
if (outputBuffers.back().age < -REBUFFER_GC_LIMIT) {
|
||||||
|
//by the nature of the shared_ptr, memory will ne deallocated automatically.
|
||||||
outputBuffers.pop_back();
|
outputBuffers.pop_back();
|
||||||
delete ref;
|
//std::cout << "--" << std::flush;
|
||||||
}
|
}
|
||||||
// buf->setIndex(indexCounter++);
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define REBUFFER_WARNING_THRESHOLD 100
|
|
||||||
if (outputBuffers.size() > REBUFFER_WARNING_THRESHOLD) {
|
if (outputBuffers.size() > REBUFFER_WARNING_THRESHOLD) {
|
||||||
std::cout << "Warning: ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "' exceeds threshold of '" << REBUFFER_WARNING_THRESHOLD << "'" << std::endl;
|
std::cout << "Warning: ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "' exceeds threshold of '" << REBUFFER_WARNING_THRESHOLD << "'" << std::endl << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
//by default created with refcount = 1
|
//3.We need to allocate a new buffer.
|
||||||
buf = new BufferType();
|
ReBufferAge < ReBufferPtr > newBuffer;
|
||||||
// buf->setIndex(indexCounter++);
|
|
||||||
outputBuffers.push_back(buf);
|
|
||||||
|
|
||||||
return buf;
|
//careful here: newBuffer.ptr is already constructed, so we need to set "in place" its
|
||||||
|
//ownership to a (new BufferType()).
|
||||||
|
newBuffer.ptr.reset(new BufferType());
|
||||||
|
newBuffer.age = 1;
|
||||||
|
|
||||||
|
outputBuffers.push_back(newBuffer);
|
||||||
|
|
||||||
|
//std::cout << "++" << std::flush;
|
||||||
|
|
||||||
|
return newBuffer.ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Purge the cache.
|
||||||
void purge() {
|
void purge() {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
// if (bufferId == "DemodulatorThreadBuffers") {
|
|
||||||
// std::cout << "'" << bufferId << "' purging.. total indexes: " << indexCounter.load() << std::endl;
|
// since outputBuffers are full std::shared_ptr,
|
||||||
// }
|
//purging if will effectively loose the local reference,
|
||||||
while (!outputBuffers.empty()) {
|
// so the std::shared_ptr will naturally be deallocated
|
||||||
BufferType *ref = outputBuffers.front();
|
//when their time comes.
|
||||||
outputBuffers.pop_front();
|
outputBuffers.clear();
|
||||||
if (ref->getRefCount() <= 0) {
|
|
||||||
delete ref;
|
|
||||||
} else {
|
|
||||||
// Something isn't done with it yet; throw it on the pile.. keep this as a bug indicator for now..
|
|
||||||
std::cout << "'" << bufferId << "' pushed garbage.." << std::endl;
|
|
||||||
ReBufferGC::addGarbage(ref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
//name of the buffer cache kind
|
||||||
std::string bufferId;
|
std::string bufferId;
|
||||||
std::deque<BufferType*> outputBuffers;
|
|
||||||
typename std::deque<BufferType*>::iterator outputBuffersI;
|
//the ReBuffer cache
|
||||||
|
std::vector< ReBufferAge < ReBufferPtr > > outputBuffers;
|
||||||
|
|
||||||
|
typedef typename std::vector< ReBufferAge < ReBufferPtr > >::iterator outputBuffersI;
|
||||||
|
|
||||||
|
//mutex protecting access to outputBuffers.
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
// std::atomic_int indexCounter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
std::map<int, AudioThread *> AudioThread::deviceController;
|
std::map<int, AudioThread *> AudioThread::deviceController;
|
||||||
std::map<int, int> AudioThread::deviceSampleRate;
|
std::map<int, int> AudioThread::deviceSampleRate;
|
||||||
@ -123,7 +125,7 @@ static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned
|
|||||||
if (srcmix->currentInput->sampleRate == src->getSampleRate()) {
|
if (srcmix->currentInput->sampleRate == src->getSampleRate()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
srcmix->currentInput->decRefCount();
|
|
||||||
}
|
}
|
||||||
srcmix->currentInput = nullptr;
|
srcmix->currentInput = nullptr;
|
||||||
} //end while
|
} //end while
|
||||||
@ -140,7 +142,7 @@ static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned
|
|||||||
if (!srcmix->inputQueue->empty()) {
|
if (!srcmix->inputQueue->empty()) {
|
||||||
srcmix->audioQueuePtr = 0;
|
srcmix->audioQueuePtr = 0;
|
||||||
if (srcmix->currentInput) {
|
if (srcmix->currentInput) {
|
||||||
srcmix->currentInput->decRefCount();
|
|
||||||
srcmix->currentInput = nullptr;
|
srcmix->currentInput = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +162,7 @@ static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned
|
|||||||
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
|
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
|
||||||
srcmix->audioQueuePtr = 0;
|
srcmix->audioQueuePtr = 0;
|
||||||
if (srcmix->currentInput) {
|
if (srcmix->currentInput) {
|
||||||
srcmix->currentInput->decRefCount();
|
|
||||||
srcmix->currentInput = nullptr;
|
srcmix->currentInput = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +189,7 @@ static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned
|
|||||||
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
|
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
|
||||||
srcmix->audioQueuePtr = 0;
|
srcmix->audioQueuePtr = 0;
|
||||||
if (srcmix->currentInput) {
|
if (srcmix->currentInput) {
|
||||||
srcmix->currentInput->decRefCount();
|
|
||||||
srcmix->currentInput = nullptr;
|
srcmix->currentInput = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,7 +431,9 @@ void AudioThread::run() {
|
|||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
AudioThreadCommand command;
|
AudioThreadCommand command;
|
||||||
|
|
||||||
cmdQueue.pop(command);
|
if (!cmdQueue.pop(command, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE) {
|
if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE) {
|
||||||
setupDevice(command.int_value);
|
setupDevice(command.int_value);
|
||||||
@ -444,19 +448,12 @@ void AudioThread::run() {
|
|||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
|
||||||
// Drain any remaining inputs, with a non-blocking pop
|
// Drain any remaining inputs, with a non-blocking pop
|
||||||
AudioThreadInput *ref;
|
if (inputQueue != nullptr) {
|
||||||
while (inputQueue && inputQueue->try_pop(ref)) {
|
inputQueue->flush();
|
||||||
|
|
||||||
if (ref) {
|
|
||||||
ref->decRefCount();
|
|
||||||
}
|
}
|
||||||
} //end while
|
|
||||||
|
|
||||||
//Nullify currentInput...
|
//Nullify currentInput...
|
||||||
if (currentInput) {
|
|
||||||
currentInput->setRefCount(0);
|
|
||||||
currentInput = nullptr;
|
currentInput = nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
//Stop
|
//Stop
|
||||||
if (deviceController[parameters.deviceId] != this) {
|
if (deviceController[parameters.deviceId] != this) {
|
||||||
@ -494,7 +491,6 @@ void AudioThread::setActive(bool state) {
|
|||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
|
||||||
AudioThreadInput *dummy;
|
|
||||||
if (state && !active && inputQueue) {
|
if (state && !active && inputQueue) {
|
||||||
deviceController[parameters.deviceId]->bindThread(this);
|
deviceController[parameters.deviceId]->bindThread(this);
|
||||||
} else if (!state && active) {
|
} else if (!state && active) {
|
||||||
@ -504,12 +500,7 @@ void AudioThread::setActive(bool state) {
|
|||||||
// Activity state changing, clear any inputs
|
// Activity state changing, clear any inputs
|
||||||
if(inputQueue) {
|
if(inputQueue) {
|
||||||
|
|
||||||
while (inputQueue->try_pop(dummy)) { // flush queue, non-blocking pop
|
inputQueue->flush();
|
||||||
|
|
||||||
if (dummy) {
|
|
||||||
dummy->decRefCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
active = state;
|
active = state;
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
#include "ThreadBlockingQueue.h"
|
#include "ThreadBlockingQueue.h"
|
||||||
#include "RtAudio.h"
|
#include "RtAudio.h"
|
||||||
#include "DemodDefs.h"
|
#include "DemodDefs.h"
|
||||||
|
|
||||||
class AudioThreadInput: public ReferenceCounter {
|
class AudioThreadInput {
|
||||||
public:
|
public:
|
||||||
long long frequency;
|
long long frequency;
|
||||||
int inputRate;
|
int inputRate;
|
||||||
@ -29,10 +29,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~AudioThreadInput() {
|
~AudioThreadInput() {
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<AudioThreadInput> AudioThreadInputPtr;
|
||||||
|
|
||||||
class AudioThreadCommand {
|
class AudioThreadCommand {
|
||||||
public:
|
public:
|
||||||
enum AudioThreadCommandEnum {
|
enum AudioThreadCommandEnum {
|
||||||
@ -47,12 +49,12 @@ public:
|
|||||||
int int_value;
|
int int_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef ThreadBlockingQueue<AudioThreadInput *> AudioThreadInputQueue;
|
typedef ThreadBlockingQueue<AudioThreadInputPtr> AudioThreadInputQueue;
|
||||||
typedef ThreadBlockingQueue<AudioThreadCommand> AudioThreadCommandQueue;
|
typedef ThreadBlockingQueue<AudioThreadCommand> AudioThreadCommandQueue;
|
||||||
|
|
||||||
class AudioThread : public IOThread {
|
class AudioThread : public IOThread {
|
||||||
public:
|
public:
|
||||||
AudioThreadInput *currentInput;
|
AudioThreadInputPtr currentInput;
|
||||||
AudioThreadInputQueue *inputQueue;
|
AudioThreadInputQueue *inputQueue;
|
||||||
std::atomic_uint audioQueuePtr;
|
std::atomic_uint audioQueuePtr;
|
||||||
std::atomic_uint underflowCount;
|
std::atomic_uint underflowCount;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "IOThread.h"
|
#include "IOThread.h"
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ public:
|
|||||||
std::string demodType;
|
std::string demodType;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DemodulatorThreadIQData: public ReferenceCounter {
|
class DemodulatorThreadIQData {
|
||||||
public:
|
public:
|
||||||
long long frequency;
|
long long frequency;
|
||||||
long long sampleRate;
|
long long sampleRate;
|
||||||
@ -48,7 +49,7 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
~DemodulatorThreadIQData() {
|
virtual ~DemodulatorThreadIQData() {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -56,7 +57,7 @@ public:
|
|||||||
class Modem;
|
class Modem;
|
||||||
class ModemKit;
|
class ModemKit;
|
||||||
|
|
||||||
class DemodulatorThreadPostIQData: public ReferenceCounter {
|
class DemodulatorThreadPostIQData {
|
||||||
public:
|
public:
|
||||||
std::vector<liquid_float_complex> data;
|
std::vector<liquid_float_complex> data;
|
||||||
|
|
||||||
@ -71,13 +72,13 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~DemodulatorThreadPostIQData() {
|
virtual ~DemodulatorThreadPostIQData() {
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class DemodulatorThreadAudioData: public ReferenceCounter {
|
class DemodulatorThreadAudioData {
|
||||||
public:
|
public:
|
||||||
long long frequency;
|
long long frequency;
|
||||||
unsigned int sampleRate;
|
unsigned int sampleRate;
|
||||||
@ -95,11 +96,13 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~DemodulatorThreadAudioData() {
|
virtual ~DemodulatorThreadAudioData() {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
typedef std::shared_ptr<DemodulatorThreadIQData> DemodulatorThreadIQDataPtr;
|
||||||
|
typedef std::shared_ptr<DemodulatorThreadPostIQData> DemodulatorThreadPostIQDataPtr;
|
||||||
|
|
||||||
typedef ThreadBlockingQueue<DemodulatorThreadIQData *> DemodulatorThreadInputQueue;
|
typedef ThreadBlockingQueue< DemodulatorThreadIQDataPtr > DemodulatorThreadInputQueue;
|
||||||
typedef ThreadBlockingQueue<DemodulatorThreadPostIQData *> DemodulatorThreadPostInputQueue;
|
typedef ThreadBlockingQueue< DemodulatorThreadPostIQDataPtr > DemodulatorThreadPostInputQueue;
|
||||||
typedef ThreadBlockingQueue<DemodulatorThreadControlCommand> DemodulatorThreadControlCommandQueue;
|
typedef ThreadBlockingQueue<DemodulatorThreadControlCommand> DemodulatorThreadControlCommandQueue;
|
||||||
|
@ -78,6 +78,7 @@ DemodulatorInstance::DemodulatorInstance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DemodulatorInstance::~DemodulatorInstance() {
|
DemodulatorInstance::~DemodulatorInstance() {
|
||||||
|
std::lock_guard < std::mutex > lockData(m_thread_control_mutex);
|
||||||
#if ENABLE_DIGITAL_LAB
|
#if ENABLE_DIGITAL_LAB
|
||||||
delete activeOutput;
|
delete activeOutput;
|
||||||
#endif
|
#endif
|
||||||
@ -89,7 +90,7 @@ DemodulatorInstance::~DemodulatorInstance() {
|
|||||||
delete threadQueueControl;
|
delete threadQueueControl;
|
||||||
delete pipeAudioData;
|
delete pipeAudioData;
|
||||||
|
|
||||||
wxGetApp().getBookmarkMgr().updateActiveList();
|
// wxGetApp().getBookmarkMgr().updateActiveList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DemodulatorInstance::setVisualOutputQueue(DemodulatorThreadOutputQueue *tQueue) {
|
void DemodulatorInstance::setVisualOutputQueue(DemodulatorThreadOutputQueue *tQueue) {
|
||||||
@ -97,6 +98,9 @@ void DemodulatorInstance::setVisualOutputQueue(DemodulatorThreadOutputQueue *tQu
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DemodulatorInstance::run() {
|
void DemodulatorInstance::run() {
|
||||||
|
|
||||||
|
std::lock_guard < std::mutex > lockData(m_thread_control_mutex);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,7 +132,7 @@ void DemodulatorInstance::run() {
|
|||||||
|
|
||||||
active = true;
|
active = true;
|
||||||
|
|
||||||
wxGetApp().getBookmarkMgr().updateActiveList();
|
// wxGetApp().getBookmarkMgr().updateActiveList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DemodulatorInstance::updateLabel(long long freq) {
|
void DemodulatorInstance::updateLabel(long long freq) {
|
||||||
@ -166,7 +170,8 @@ void DemodulatorInstance::setLabel(std::string labelStr) {
|
|||||||
|
|
||||||
bool DemodulatorInstance::isTerminated() {
|
bool DemodulatorInstance::isTerminated() {
|
||||||
|
|
||||||
//
|
std::lock_guard < std::mutex > lockData(m_thread_control_mutex);
|
||||||
|
|
||||||
bool audioTerminated = audioThread->isTerminated();
|
bool audioTerminated = audioThread->isTerminated();
|
||||||
bool demodTerminated = demodulatorThread->isTerminated();
|
bool demodTerminated = demodulatorThread->isTerminated();
|
||||||
bool preDemodTerminated = demodulatorPreThread->isTerminated();
|
bool preDemodTerminated = demodulatorPreThread->isTerminated();
|
||||||
|
@ -130,7 +130,7 @@ public:
|
|||||||
void closeOutput();
|
void closeOutput();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
DemodulatorThreadInputQueue* pipeIQInputData;
|
DemodulatorThreadInputQueue* pipeIQInputData;
|
||||||
DemodulatorThreadPostInputQueue* pipeIQDemodData;
|
DemodulatorThreadPostInputQueue* pipeIQDemodData;
|
||||||
AudioThreadInputQueue *pipeAudioData;
|
AudioThreadInputQueue *pipeAudioData;
|
||||||
@ -138,7 +138,8 @@ protected:
|
|||||||
DemodulatorThread *demodulatorThread;
|
DemodulatorThread *demodulatorThread;
|
||||||
DemodulatorThreadControlCommandQueue *threadQueueControl;
|
DemodulatorThreadControlCommandQueue *threadQueueControl;
|
||||||
|
|
||||||
private:
|
//protects child thread creation and termination
|
||||||
|
mutable std::mutex m_thread_control_mutex;
|
||||||
|
|
||||||
std::atomic<std::string *> label; //
|
std::atomic<std::string *> label; //
|
||||||
// User editable buffer, 16 bit string.
|
// User editable buffer, 16 bit string.
|
||||||
|
@ -163,6 +163,7 @@ void DemodulatorMgr::deleteThread(DemodulatorInstance *demod) {
|
|||||||
demod->terminate();
|
demod->terminate();
|
||||||
|
|
||||||
//Do not cleanup immediatly
|
//Do not cleanup immediatly
|
||||||
|
std::lock_guard < std::mutex > lock_deleted(deleted_demods_busy);
|
||||||
demods_deleted.push_back(demod);
|
demods_deleted.push_back(demod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,10 +226,6 @@ void DemodulatorMgr::setActiveDemodulator(DemodulatorInstance *demod, bool tempo
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
wxGetApp().getBookmarkMgr().updateActiveList();
|
wxGetApp().getBookmarkMgr().updateActiveList();
|
||||||
} else {
|
|
||||||
std::lock_guard < std::recursive_mutex > lock(demods_busy);
|
|
||||||
garbageCollect();
|
|
||||||
ReBufferGC::garbageCollect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeVisualDemodulator.load()) {
|
if (activeVisualDemodulator.load()) {
|
||||||
@ -281,25 +278,29 @@ DemodulatorInstance *DemodulatorMgr::getLastDemodulatorWith(const std::string& t
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private internal method, no need to protect it with demods_busy
|
|
||||||
void DemodulatorMgr::garbageCollect() {
|
void DemodulatorMgr::garbageCollect() {
|
||||||
if (demods_deleted.size()) {
|
|
||||||
|
|
||||||
std::vector<DemodulatorInstance *>::iterator i;
|
std::lock_guard < std::mutex > lock(deleted_demods_busy);
|
||||||
|
|
||||||
for (i = demods_deleted.begin(); i != demods_deleted.end(); i++) {
|
std::vector<DemodulatorInstance *>::iterator it = demods_deleted.begin();
|
||||||
if ((*i)->isTerminated()) {
|
|
||||||
DemodulatorInstance *deleted = (*i);
|
|
||||||
demods_deleted.erase(i);
|
|
||||||
|
|
||||||
std::cout << "Garbage collected demodulator instance " << deleted->getLabel() << std::endl;
|
while (it != demods_deleted.end()) {
|
||||||
|
|
||||||
|
if ((*it)->isTerminated()) {
|
||||||
|
|
||||||
|
DemodulatorInstance *deleted = (*it);
|
||||||
|
|
||||||
|
std::cout << "Garbage collected demodulator instance '" << deleted->getLabel() << "'... " << std::endl << std::flush;
|
||||||
|
demods_deleted.erase(it);
|
||||||
delete deleted;
|
delete deleted;
|
||||||
|
|
||||||
|
//only garbage collect 1 demod at a time.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
it++;
|
||||||
}
|
}
|
||||||
|
} //end while
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DemodulatorMgr::updateLastState() {
|
void DemodulatorMgr::updateLastState() {
|
||||||
@ -431,7 +432,6 @@ void DemodulatorMgr::saveInstance(DataNode *node, DemodulatorInstance *inst) {
|
|||||||
*settingsNode->newChild(msi->first.c_str()) = msi->second;
|
*settingsNode->newChild(msi->first.c_str()) = msi->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DemodulatorInstance *DemodulatorMgr::loadInstance(DataNode *node) {
|
DemodulatorInstance *DemodulatorMgr::loadInstance(DataNode *node) {
|
||||||
|
@ -68,10 +68,11 @@ public:
|
|||||||
|
|
||||||
DemodulatorInstance *loadInstance(DataNode *node);
|
DemodulatorInstance *loadInstance(DataNode *node);
|
||||||
|
|
||||||
private:
|
//to be called periodically to cleanup removed demodulators.
|
||||||
|
|
||||||
void garbageCollect();
|
void garbageCollect();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
std::vector<DemodulatorInstance *> demods;
|
std::vector<DemodulatorInstance *> demods;
|
||||||
std::vector<DemodulatorInstance *> demods_deleted;
|
std::vector<DemodulatorInstance *> demods_deleted;
|
||||||
|
|
||||||
@ -92,6 +93,8 @@ private:
|
|||||||
//because of the usage of public re-entrant methods
|
//because of the usage of public re-entrant methods
|
||||||
std::recursive_mutex demods_busy;
|
std::recursive_mutex demods_busy;
|
||||||
|
|
||||||
|
mutable std::mutex deleted_demods_busy;
|
||||||
|
|
||||||
std::map<std::string, ModemSettings> lastModemSettings;
|
std::map<std::string, ModemSettings> lastModemSettings;
|
||||||
std::map<int,RtAudio::DeviceInfo> outputDevices;
|
std::map<int,RtAudio::DeviceInfo> outputDevices;
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#include "CubicSDR.h"
|
#include "CubicSDR.h"
|
||||||
#include "DemodulatorInstance.h"
|
#include "DemodulatorInstance.h"
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
DemodulatorPreThread::DemodulatorPreThread(DemodulatorInstance *parent) : IOThread(), iqResampler(NULL), iqResampleRatio(1), cModem(nullptr), cModemKit(nullptr), iqInputQueue(NULL), iqOutputQueue(NULL)
|
DemodulatorPreThread::DemodulatorPreThread(DemodulatorInstance *parent) : IOThread(), iqResampler(NULL), iqResampleRatio(1), cModem(nullptr), cModemKit(nullptr), iqInputQueue(NULL), iqOutputQueue(NULL)
|
||||||
{
|
{
|
||||||
initialized.store(false);
|
initialized.store(false);
|
||||||
@ -71,9 +74,11 @@ void DemodulatorPreThread::run() {
|
|||||||
t_Worker = new std::thread(&DemodulatorWorkerThread::threadMain, workerThread);
|
t_Worker = new std::thread(&DemodulatorWorkerThread::threadMain, workerThread);
|
||||||
|
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
DemodulatorThreadIQData *inp;
|
DemodulatorThreadIQDataPtr inp;
|
||||||
|
|
||||||
iqInputQueue->pop(inp);
|
if (!iqInputQueue->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (frequencyChanged.load()) {
|
if (frequencyChanged.load()) {
|
||||||
currentFrequency.store(newFrequency);
|
currentFrequency.store(newFrequency);
|
||||||
@ -157,7 +162,7 @@ void DemodulatorPreThread::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cModem && cModemKit && abs(shiftFrequency) > (int) ((double) (inp->sampleRate / 2) * 1.5)) {
|
if (cModem && cModemKit && abs(shiftFrequency) > (int) ((double) (inp->sampleRate / 2) * 1.5)) {
|
||||||
inp->decRefCount();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +197,7 @@ void DemodulatorPreThread::run() {
|
|||||||
out_buf = temp_buf;
|
out_buf = temp_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
DemodulatorThreadPostIQData *resamp = buffers.getBuffer();
|
DemodulatorThreadPostIQDataPtr resamp = buffers.getBuffer();
|
||||||
|
|
||||||
size_t out_size = ceil((double) (bufSize) * iqResampleRatio) + 512;
|
size_t out_size = ceil((double) (bufSize) * iqResampleRatio) + 512;
|
||||||
|
|
||||||
@ -218,8 +223,6 @@ void DemodulatorPreThread::run() {
|
|||||||
iqOutputQueue->push(resamp);
|
iqOutputQueue->push(resamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
inp->decRefCount();
|
|
||||||
|
|
||||||
DemodulatorWorkerThreadResult result;
|
DemodulatorWorkerThreadResult result;
|
||||||
//process all worker results until
|
//process all worker results until
|
||||||
while (!stopping && workerResults->try_pop(result)) {
|
while (!stopping && workerResults->try_pop(result)) {
|
||||||
@ -277,11 +280,8 @@ void DemodulatorPreThread::run() {
|
|||||||
}
|
}
|
||||||
} //end while stopping
|
} //end while stopping
|
||||||
|
|
||||||
DemodulatorThreadPostIQData *tmp;
|
|
||||||
while (iqOutputQueue->try_pop(tmp)) {
|
|
||||||
|
|
||||||
tmp->decRefCount();
|
iqOutputQueue->flush();
|
||||||
}
|
|
||||||
buffers.purge();
|
buffers.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ int DemodulatorPreThread::getAudioSampleRate() {
|
|||||||
|
|
||||||
void DemodulatorPreThread::terminate() {
|
void DemodulatorPreThread::terminate() {
|
||||||
IOThread::terminate();
|
IOThread::terminate();
|
||||||
DemodulatorThreadIQData *inp = new DemodulatorThreadIQData; // push dummy to nudge queue
|
DemodulatorThreadIQDataPtr inp(new DemodulatorThreadIQData); // push dummy to nudge queue
|
||||||
|
|
||||||
//VSO: blocking push :
|
//VSO: blocking push :
|
||||||
iqInputQueue->push(inp);
|
iqInputQueue->push(inp);
|
||||||
|
@ -17,7 +17,7 @@ class DemodulatorPreThread : public IOThread {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
DemodulatorPreThread(DemodulatorInstance *parent);
|
DemodulatorPreThread(DemodulatorInstance *parent);
|
||||||
~DemodulatorPreThread();
|
virtual ~DemodulatorPreThread();
|
||||||
|
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#endif
|
#endif
|
||||||
@ -79,15 +82,16 @@ void DemodulatorThread::run() {
|
|||||||
ModemIQData modemData;
|
ModemIQData modemData;
|
||||||
|
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
DemodulatorThreadPostIQData *inp;
|
DemodulatorThreadPostIQDataPtr inp;
|
||||||
|
|
||||||
iqInputQueue->pop(inp);
|
if (!iqInputQueue->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
// std::lock_guard < std::mutex > lock(inp->m_mutex);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
size_t bufSize = inp->data.size();
|
size_t bufSize = inp->data.size();
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
inp->decRefCount();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +108,7 @@ void DemodulatorThread::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cModem || !cModemKit) {
|
if (!cModem || !cModemKit) {
|
||||||
inp->decRefCount();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +119,7 @@ void DemodulatorThread::run() {
|
|||||||
modemData.sampleRate = inp->sampleRate;
|
modemData.sampleRate = inp->sampleRate;
|
||||||
modemData.data.assign(inputData->begin(), inputData->end());
|
modemData.data.assign(inputData->begin(), inputData->end());
|
||||||
|
|
||||||
AudioThreadInput *ati = nullptr;
|
AudioThreadInputPtr ati = nullptr;
|
||||||
|
|
||||||
ModemAnalog *modemAnalog = (cModem->getType() == "analog")?((ModemAnalog *)cModem):nullptr;
|
ModemAnalog *modemAnalog = (cModem->getType() == "analog")?((ModemAnalog *)cModem):nullptr;
|
||||||
ModemDigital *modemDigital = (cModem->getType() == "digital")?((ModemDigital *)cModem):nullptr;
|
ModemDigital *modemDigital = (cModem->getType() == "digital")?((ModemDigital *)cModem):nullptr;
|
||||||
@ -133,7 +137,7 @@ void DemodulatorThread::run() {
|
|||||||
ati->data.resize(0);
|
ati->data.resize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
cModem->demodulate(cModemKit, &modemData, ati);
|
cModem->demodulate(cModemKit, &modemData, ati.get());
|
||||||
|
|
||||||
double currentSignalLevel = 0;
|
double currentSignalLevel = 0;
|
||||||
double sampleTime = double(inp->data.size()) / double(inp->sampleRate);
|
double sampleTime = double(inp->data.size()) / double(inp->sampleRate);
|
||||||
@ -225,7 +229,6 @@ void DemodulatorThread::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ati) {
|
} else if (ati) {
|
||||||
ati->setRefCount(0);
|
|
||||||
ati = nullptr;
|
ati = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +241,7 @@ void DemodulatorThread::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) {
|
if ((ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) {
|
||||||
AudioThreadInput *ati_vis = new AudioThreadInput;
|
AudioThreadInputPtr ati_vis(new AudioThreadInput);
|
||||||
|
|
||||||
ati_vis->sampleRate = inp->sampleRate;
|
ati_vis->sampleRate = inp->sampleRate;
|
||||||
ati_vis->inputRate = inp->sampleRate;
|
ati_vis->inputRate = inp->sampleRate;
|
||||||
@ -246,7 +249,7 @@ void DemodulatorThread::run() {
|
|||||||
size_t num_vis = DEMOD_VIS_SIZE;
|
size_t num_vis = DEMOD_VIS_SIZE;
|
||||||
if (modemDigital) {
|
if (modemDigital) {
|
||||||
if (ati) { // TODO: handle digital modems with audio output
|
if (ati) { // TODO: handle digital modems with audio output
|
||||||
ati->setRefCount(0);
|
|
||||||
ati = nullptr;
|
ati = nullptr;
|
||||||
}
|
}
|
||||||
ati_vis->data.resize(inputData->size());
|
ati_vis->data.resize(inputData->size());
|
||||||
@ -300,7 +303,7 @@ void DemodulatorThread::run() {
|
|||||||
|
|
||||||
if (!localAudioVisOutputQueue->try_push(ati_vis)) {
|
if (!localAudioVisOutputQueue->try_push(ati_vis)) {
|
||||||
//non-blocking push needed for audio vis out
|
//non-blocking push needed for audio vis out
|
||||||
ati_vis->setRefCount(0);
|
|
||||||
std::cout << "DemodulatorThread::run() cannot push ati_vis into localAudioVisOutputQueue, is full !" << std::endl;
|
std::cout << "DemodulatorThread::run() cannot push ati_vis into localAudioVisOutputQueue, is full !" << std::endl;
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
@ -310,12 +313,10 @@ void DemodulatorThread::run() {
|
|||||||
if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator()))) {
|
if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator()))) {
|
||||||
//non-blocking push needed for audio out
|
//non-blocking push needed for audio out
|
||||||
if (!audioOutputQueue->try_push(ati)) {
|
if (!audioOutputQueue->try_push(ati)) {
|
||||||
ati->decRefCount();
|
|
||||||
std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl;
|
std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl;
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ati->setRefCount(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,28 +336,12 @@ void DemodulatorThread::run() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inp->decRefCount();
|
|
||||||
}
|
}
|
||||||
// end while !stopping
|
// end while !stopping
|
||||||
|
|
||||||
// Purge any unused inputs, with a non-blocking pop
|
// Purge any unused inputs, with a non-blocking pop
|
||||||
DemodulatorThreadPostIQData *ref;
|
iqInputQueue->flush();
|
||||||
while (iqInputQueue->try_pop(ref)) {
|
audioOutputQueue->flush();
|
||||||
|
|
||||||
if (ref) { // May have other consumers; just decrement
|
|
||||||
ref->decRefCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioThreadInput *ref_audio;
|
|
||||||
while (audioOutputQueue->try_pop(ref_audio)) {
|
|
||||||
|
|
||||||
if (ref_audio) { // Originated here; set RefCount to 0
|
|
||||||
ref_audio->setRefCount(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputBuffers.purge();
|
outputBuffers.purge();
|
||||||
|
|
||||||
@ -365,7 +350,7 @@ void DemodulatorThread::run() {
|
|||||||
|
|
||||||
void DemodulatorThread::terminate() {
|
void DemodulatorThread::terminate() {
|
||||||
IOThread::terminate();
|
IOThread::terminate();
|
||||||
DemodulatorThreadPostIQData *inp = new DemodulatorThreadPostIQData; // push dummy to nudge queue
|
DemodulatorThreadPostIQDataPtr inp(new DemodulatorThreadPostIQData); // push dummy to nudge queue
|
||||||
|
|
||||||
//VSO: blocking push
|
//VSO: blocking push
|
||||||
iqInputQueue->push(inp);
|
iqInputQueue->push(inp);
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#include "AudioThread.h"
|
#include "AudioThread.h"
|
||||||
#include "Modem.h"
|
#include "Modem.h"
|
||||||
|
|
||||||
typedef ThreadBlockingQueue<AudioThreadInput *> DemodulatorThreadOutputQueue;
|
typedef ThreadBlockingQueue<AudioThreadInputPtr> DemodulatorThreadOutputQueue;
|
||||||
|
|
||||||
#define DEMOD_VIS_SIZE 2048
|
#define DEMOD_VIS_SIZE 2048
|
||||||
#define DEMOD_SIGNAL_MIN -30
|
#define DEMOD_SIGNAL_MIN -30
|
||||||
@ -22,7 +22,7 @@ class DemodulatorThread : public IOThread {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
DemodulatorThread(DemodulatorInstance *parent);
|
DemodulatorThread(DemodulatorInstance *parent);
|
||||||
~DemodulatorThread();
|
virtual ~DemodulatorThread();
|
||||||
|
|
||||||
void onBindOutput(std::string name, ThreadQueueBase *threadQueue);
|
void onBindOutput(std::string name, ThreadQueueBase *threadQueue);
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
#include "CubicSDR.h"
|
#include "CubicSDR.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
DemodulatorWorkerThread::DemodulatorWorkerThread() : IOThread(),
|
DemodulatorWorkerThread::DemodulatorWorkerThread() : IOThread(),
|
||||||
commandQueue(NULL), resultQueue(NULL), cModem(nullptr), cModemKit(nullptr) {
|
commandQueue(NULL), resultQueue(NULL), cModem(nullptr), cModemKit(nullptr) {
|
||||||
}
|
}
|
||||||
@ -31,7 +34,9 @@ void DemodulatorWorkerThread::run() {
|
|||||||
//we are waiting for the first command to show up (blocking!)
|
//we are waiting for the first command to show up (blocking!)
|
||||||
//then consuming the commands until done.
|
//then consuming the commands until done.
|
||||||
while (!done) {
|
while (!done) {
|
||||||
commandQueue->pop(command);
|
if (!commandQueue->pop(command, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (command.cmd) {
|
switch (command.cmd) {
|
||||||
case DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS:
|
case DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS:
|
||||||
|
@ -76,7 +76,7 @@ class DemodulatorWorkerThread : public IOThread {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
DemodulatorWorkerThread();
|
DemodulatorWorkerThread();
|
||||||
~DemodulatorWorkerThread();
|
virtual ~DemodulatorWorkerThread();
|
||||||
|
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
|
||||||
|
@ -1411,7 +1411,7 @@ void BookmarkView::onEnterWindow( wxMouseEvent& event ) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
setStatusText("You can mouse-drag a bookmark entry from one category to the next..etc. TODO: add more Bookmarks descriptions");
|
setStatusText("Drag & Drop to create / move bookmarks, Group and arrange bookmarks, quick Search by keywords.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "AudioThread.h"
|
#include "AudioThread.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#define MIN_BANDWIDTH 500
|
#define MIN_BANDWIDTH 500
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ public:
|
|||||||
int audioSampleRate;
|
int audioSampleRate;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModemIQData: public ReferenceCounter {
|
class ModemIQData {
|
||||||
public:
|
public:
|
||||||
std::vector<liquid_float_complex> data;
|
std::vector<liquid_float_complex> data;
|
||||||
long long sampleRate;
|
long long sampleRate;
|
||||||
@ -34,11 +35,13 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~ModemIQData() {
|
virtual ~ModemIQData() {
|
||||||
std::lock_guard < std::recursive_mutex > lock(m_mutex);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<ModemIQData> ModemIQDataPtr;
|
||||||
|
|
||||||
// Copy of SoapySDR::Range, original comments
|
// Copy of SoapySDR::Range, original comments
|
||||||
class ModemRange
|
class ModemRange
|
||||||
{
|
{
|
||||||
|
@ -24,13 +24,13 @@ int ModemAM::getDefaultSampleRate() {
|
|||||||
return 6000;
|
return 6000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModemAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
|
void ModemAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput* audioOut) {
|
||||||
ModemKitAnalog *amkit = (ModemKitAnalog *)kit;
|
ModemKitAnalog *amkit = (ModemKitAnalog *)kit;
|
||||||
|
|
||||||
initOutputBuffers(amkit,input);
|
initOutputBuffers(amkit,input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ void ModemDSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *a
|
|||||||
initOutputBuffers(amkit, input);
|
initOutputBuffers(amkit, input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ void ModemFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *au
|
|||||||
initOutputBuffers(fmkit, input);
|
initOutputBuffers(fmkit, input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ void ModemIQ::demodulate(ModemKit * /* kit */, ModemIQData *input, AudioThreadIn
|
|||||||
size_t bufSize = input->data.size();
|
size_t bufSize = input->data.size();
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ void ModemLSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *a
|
|||||||
initOutputBuffers(akit,input);
|
initOutputBuffers(akit,input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ void ModemNBFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *
|
|||||||
initOutputBuffers(fmkit, input);
|
initOutputBuffers(fmkit, input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ void ModemUSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *a
|
|||||||
initOutputBuffers(akit,input);
|
initOutputBuffers(akit,input);
|
||||||
|
|
||||||
if (!bufSize) {
|
if (!bufSize) {
|
||||||
input->decRefCount();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ThreadBlockingQueue.h>
|
#include <ThreadBlockingQueue.h>
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
FFTDataDistributor::FFTDataDistributor() : outputBuffers("FFTDataDistributorBuffers"), fftSize(DEFAULT_FFT_SIZE), linesPerSecond(DEFAULT_WATERFALL_LPS), lineRateAccum(0.0) {
|
FFTDataDistributor::FFTDataDistributor() : outputBuffers("FFTDataDistributorBuffers"), fftSize(DEFAULT_FFT_SIZE), linesPerSecond(DEFAULT_WATERFALL_LPS), lineRateAccum(0.0) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -28,8 +31,11 @@ void FFTDataDistributor::process() {
|
|||||||
if (!isAnyOutputEmpty()) {
|
if (!isAnyOutputEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DemodulatorThreadIQData *inp;
|
DemodulatorThreadIQDataPtr inp;
|
||||||
input->pop(inp);
|
|
||||||
|
if (!input->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (inp) {
|
if (inp) {
|
||||||
//Settings have changed, set new values and dump all previous samples stored in inputBuffer:
|
//Settings have changed, set new values and dump all previous samples stored in inputBuffer:
|
||||||
@ -73,7 +79,7 @@ void FFTDataDistributor::process() {
|
|||||||
memcpy(&inputBuffer.data[bufferOffset+bufferedItems],&inp->data[0], nbSamplesToAdd *sizeof(liquid_float_complex));
|
memcpy(&inputBuffer.data[bufferOffset+bufferedItems],&inp->data[0], nbSamplesToAdd *sizeof(liquid_float_complex));
|
||||||
bufferedItems += nbSamplesToAdd;
|
bufferedItems += nbSamplesToAdd;
|
||||||
//
|
//
|
||||||
inp->decRefCount();
|
|
||||||
} else {
|
} else {
|
||||||
//empty inp, wait for another.
|
//empty inp, wait for another.
|
||||||
continue;
|
continue;
|
||||||
@ -105,7 +111,8 @@ void FFTDataDistributor::process() {
|
|||||||
|
|
||||||
if (lineRateAccum >= 1.0) {
|
if (lineRateAccum >= 1.0) {
|
||||||
//each i represents a FFT computation
|
//each i represents a FFT computation
|
||||||
DemodulatorThreadIQData *outp = outputBuffers.getBuffer();
|
DemodulatorThreadIQDataPtr outp = outputBuffers.getBuffer();
|
||||||
|
|
||||||
outp->frequency = inputBuffer.frequency;
|
outp->frequency = inputBuffer.frequency;
|
||||||
outp->sampleRate = inputBuffer.sampleRate;
|
outp->sampleRate = inputBuffer.sampleRate;
|
||||||
outp->data.assign(inputBuffer.data.begin()+bufferOffset+i,
|
outp->data.assign(inputBuffer.data.begin()+bufferOffset+i,
|
||||||
|
@ -47,7 +47,7 @@ void ScopeVisualProcessor::process() {
|
|||||||
if (!isOutputEmpty()) {
|
if (!isOutputEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AudioThreadInput *audioInputData;
|
AudioThreadInputPtr audioInputData;
|
||||||
|
|
||||||
if (input->try_pop(audioInputData)) {
|
if (input->try_pop(audioInputData)) {
|
||||||
|
|
||||||
@ -56,11 +56,12 @@ void ScopeVisualProcessor::process() {
|
|||||||
}
|
}
|
||||||
size_t i, iMax = audioInputData->data.size();
|
size_t i, iMax = audioInputData->data.size();
|
||||||
if (!iMax) {
|
if (!iMax) {
|
||||||
delete audioInputData; //->decRefCount();
|
//discard audioInputData.
|
||||||
|
audioInputData = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopeRenderData *renderData = NULL;
|
ScopeRenderDataPtr renderData = nullptr;
|
||||||
|
|
||||||
if (scopeEnabled) {
|
if (scopeEnabled) {
|
||||||
iMax = audioInputData->data.size();
|
iMax = audioInputData->data.size();
|
||||||
@ -150,7 +151,7 @@ void ScopeVisualProcessor::process() {
|
|||||||
renderData->inputRate = audioInputData->inputRate;
|
renderData->inputRate = audioInputData->inputRate;
|
||||||
renderData->sampleRate = audioInputData->sampleRate;
|
renderData->sampleRate = audioInputData->sampleRate;
|
||||||
|
|
||||||
delete audioInputData; //->decRefCount();
|
audioInputData = nullptr; //->decRefCount();
|
||||||
|
|
||||||
double fft_ceil = 0, fft_floor = 1;
|
double fft_ceil = 0, fft_floor = 1;
|
||||||
|
|
||||||
@ -212,8 +213,6 @@ void ScopeVisualProcessor::process() {
|
|||||||
renderData->spectrum = true;
|
renderData->spectrum = true;
|
||||||
|
|
||||||
distribute(renderData);
|
distribute(renderData);
|
||||||
} else {
|
|
||||||
delete audioInputData; //->decRefCount();
|
|
||||||
}
|
}
|
||||||
} //end if try_pop()
|
} //end if try_pop()
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
#include "VisualProcessor.h"
|
#include "VisualProcessor.h"
|
||||||
#include "AudioThread.h"
|
#include "AudioThread.h"
|
||||||
#include "ScopePanel.h"
|
#include "ScopePanel.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class ScopeRenderData: public ReferenceCounter {
|
class ScopeRenderData {
|
||||||
public:
|
public:
|
||||||
std::vector<float> waveform_points;
|
std::vector<float> waveform_points;
|
||||||
ScopePanel::ScopeMode mode = ScopePanel::SCOPE_MODE_Y;
|
ScopePanel::ScopeMode mode = ScopePanel::SCOPE_MODE_Y;
|
||||||
@ -17,9 +18,15 @@ public:
|
|||||||
bool spectrum;
|
bool spectrum;
|
||||||
int fft_size;
|
int fft_size;
|
||||||
double fft_floor, fft_ceil;
|
double fft_floor, fft_ceil;
|
||||||
|
|
||||||
|
virtual ~ScopeRenderData() {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef ThreadBlockingQueue<ScopeRenderData *> ScopeRenderDataQueue;
|
typedef std::shared_ptr<ScopeRenderData> ScopeRenderDataPtr;
|
||||||
|
|
||||||
|
typedef ThreadBlockingQueue<ScopeRenderDataPtr> ScopeRenderDataQueue;
|
||||||
|
|
||||||
class ScopeVisualProcessor : public VisualProcessor<AudioThreadInput, ScopeRenderData> {
|
class ScopeVisualProcessor : public VisualProcessor<AudioThreadInput, ScopeRenderData> {
|
||||||
public:
|
public:
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#include "SpectrumVisualProcessor.h"
|
#include "SpectrumVisualProcessor.h"
|
||||||
#include "CubicSDR.h"
|
#include "CubicSDR.h"
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
SpectrumVisualProcessor::SpectrumVisualProcessor() : outputBuffers("SpectrumVisualProcessorBuffers") {
|
SpectrumVisualProcessor::SpectrumVisualProcessor() : outputBuffers("SpectrumVisualProcessorBuffers") {
|
||||||
lastInputBandwidth = 0;
|
lastInputBandwidth = 0;
|
||||||
@ -192,23 +194,19 @@ void SpectrumVisualProcessor::process() {
|
|||||||
fftSizeChanged.store(false);
|
fftSizeChanged.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
DemodulatorThreadIQData *iqData;
|
DemodulatorThreadIQDataPtr iqData;
|
||||||
|
|
||||||
input->pop(iqData);
|
if (!input->pop(iqData, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!iqData) {
|
if (!iqData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Start by locking concurrent access to iqData
|
|
||||||
std::lock_guard < std::recursive_mutex > lock(iqData->getMonitor());
|
|
||||||
|
|
||||||
//then get the busy_lock
|
//then get the busy_lock
|
||||||
std::lock_guard < std::mutex > busy_lock(busy_run);
|
std::lock_guard < std::mutex > busy_lock(busy_run);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool doPeak = peakHold.load() && (peakReset.load() == 0);
|
bool doPeak = peakHold.load() && (peakReset.load() == 0);
|
||||||
|
|
||||||
if (fft_result.size() != fftSizeInternal) {
|
if (fft_result.size() != fftSizeInternal) {
|
||||||
@ -246,7 +244,6 @@ void SpectrumVisualProcessor::process() {
|
|||||||
|
|
||||||
if (is_view.load()) {
|
if (is_view.load()) {
|
||||||
if (!iqData->sampleRate) {
|
if (!iqData->sampleRate) {
|
||||||
iqData->decRefCount();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -387,7 +384,7 @@ void SpectrumVisualProcessor::process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (execute) {
|
if (execute) {
|
||||||
SpectrumVisualData *output = outputBuffers.getBuffer();
|
SpectrumVisualDataPtr output = outputBuffers.getBuffer();
|
||||||
|
|
||||||
if (output->spectrum_points.size() != fftSize * 2) {
|
if (output->spectrum_points.size() != fftSize * 2) {
|
||||||
output->spectrum_points.resize(fftSize * 2);
|
output->spectrum_points.resize(fftSize * 2);
|
||||||
@ -599,9 +596,6 @@ void SpectrumVisualProcessor::process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iqData->decRefCount();
|
|
||||||
|
|
||||||
|
|
||||||
lastView = is_view.load();
|
lastView = is_view.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,20 +6,24 @@
|
|||||||
#include "VisualProcessor.h"
|
#include "VisualProcessor.h"
|
||||||
#include "DemodDefs.h"
|
#include "DemodDefs.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#define SPECTRUM_VZM 2
|
#define SPECTRUM_VZM 2
|
||||||
#define PEAK_RESET_COUNT 30
|
#define PEAK_RESET_COUNT 30
|
||||||
|
|
||||||
class SpectrumVisualData : public ReferenceCounter {
|
class SpectrumVisualData {
|
||||||
public:
|
public:
|
||||||
std::vector<float> spectrum_points;
|
std::vector<float> spectrum_points;
|
||||||
std::vector<float> spectrum_hold_points;
|
std::vector<float> spectrum_hold_points;
|
||||||
double fft_ceiling, fft_floor;
|
double fft_ceiling, fft_floor;
|
||||||
long long centerFreq;
|
long long centerFreq;
|
||||||
int bandwidth;
|
int bandwidth;
|
||||||
|
|
||||||
|
virtual ~SpectrumVisualData() {};
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef ThreadBlockingQueue<SpectrumVisualData *> SpectrumVisualDataQueue;
|
typedef std::shared_ptr<SpectrumVisualData> SpectrumVisualDataPtr;
|
||||||
|
typedef ThreadBlockingQueue<SpectrumVisualDataPtr> SpectrumVisualDataQueue;
|
||||||
|
|
||||||
class SpectrumVisualProcessor : public VisualProcessor<DemodulatorThreadIQData, SpectrumVisualData> {
|
class SpectrumVisualProcessor : public VisualProcessor<DemodulatorThreadIQData, SpectrumVisualData> {
|
||||||
public:
|
public:
|
||||||
|
@ -9,11 +9,16 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
template<typename InputDataType = ReferenceCounter, typename OutputDataType = ReferenceCounter>
|
template<typename InputDataType, typename OutputDataType>
|
||||||
class VisualProcessor {
|
class VisualProcessor {
|
||||||
|
|
||||||
//
|
//
|
||||||
typedef ThreadBlockingQueue<InputDataType*> VisualInputQueueType;
|
typedef std::shared_ptr<InputDataType> InputDataTypePtr;
|
||||||
typedef ThreadBlockingQueue<OutputDataType*> VisualOutputQueueType;
|
typedef std::shared_ptr<OutputDataType> OutputDataTypePtr;
|
||||||
|
|
||||||
|
typedef ThreadBlockingQueue<InputDataTypePtr> VisualInputQueueType;
|
||||||
|
typedef ThreadBlockingQueue<OutputDataTypePtr> VisualOutputQueueType;
|
||||||
|
|
||||||
typedef typename std::vector< VisualOutputQueueType *>::iterator outputs_i;
|
typedef typename std::vector< VisualOutputQueueType *>::iterator outputs_i;
|
||||||
public:
|
public:
|
||||||
virtual ~VisualProcessor() {
|
virtual ~VisualProcessor() {
|
||||||
@ -96,22 +101,19 @@ protected:
|
|||||||
//available outputs, previously set by attachOutput().
|
//available outputs, previously set by attachOutput().
|
||||||
//* \param[in] timeout The number of microseconds to wait to push an item in each one of the outputs, 0(default) means indefinite wait.
|
//* \param[in] timeout The number of microseconds to wait to push an item in each one of the outputs, 0(default) means indefinite wait.
|
||||||
//* \param[in] errorMessage an error message written on std::cout in case pf push timeout.
|
//* \param[in] errorMessage an error message written on std::cout in case pf push timeout.
|
||||||
void distribute(OutputDataType *item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT, const char* errorMessage = "") {
|
void distribute(OutputDataTypePtr item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT, const char* errorMessage = nullptr) {
|
||||||
|
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
//We will try to distribute 'output' among all 'outputs',
|
//We will try to distribute 'output' among all 'outputs',
|
||||||
//so 'output' will a-priori be shared among all 'outputs' so set its ref count to this
|
//so 'output' will a-priori be shared among all 'outputs'.
|
||||||
//amount.
|
|
||||||
item->setRefCount((int)outputs.size());
|
|
||||||
for (outputs_i it = outputs.begin(); it != outputs.end(); it++) {
|
for (outputs_i it = outputs.begin(); it != outputs.end(); it++) {
|
||||||
//if 'output' failed to be given to an outputs_i, dec its ref count accordingly.
|
//'output' can fail to be given to an outputs_i,
|
||||||
//blocking push, with a timeout
|
//using a blocking push, with a timeout
|
||||||
if (!(*it)->push(item, timeout, errorMessage)) {
|
if (!(*it)->push(item, timeout, errorMessage)) {
|
||||||
item->decRefCount();
|
//TODO : trace ?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now 'item' refcount matches the times 'item' has been successfully distributed,
|
|
||||||
//i.e shared among the outputs.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//the incoming data queue
|
//the incoming data queue
|
||||||
@ -127,7 +129,7 @@ protected:
|
|||||||
//Specialization much like VisualDataReDistributor, except
|
//Specialization much like VisualDataReDistributor, except
|
||||||
//the input (pointer) is directly re-dispatched
|
//the input (pointer) is directly re-dispatched
|
||||||
//to outputs, so that all output indeed SHARE the same instance.
|
//to outputs, so that all output indeed SHARE the same instance.
|
||||||
template<typename OutputDataType = ReferenceCounter>
|
template<typename OutputDataType>
|
||||||
class VisualDataDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
class VisualDataDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
||||||
protected:
|
protected:
|
||||||
virtual void process() {
|
virtual void process() {
|
||||||
@ -136,18 +138,14 @@ protected:
|
|||||||
|
|
||||||
if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
|
if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
|
||||||
if (inp) {
|
if (inp) {
|
||||||
inp->decRefCount();
|
//nothing
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inp) {
|
if (inp) {
|
||||||
int previousRefCount = inp->getRefCount();
|
|
||||||
VisualProcessor<OutputDataType, OutputDataType>::distribute(inp);
|
VisualProcessor<OutputDataType, OutputDataType>::distribute(inp);
|
||||||
//inp is now shared through the distribute(), which overwrite the previous ref count,
|
//inp is now shared through the distribute() call.
|
||||||
//so increment it properly.
|
|
||||||
int distributeRefCount = inp->getRefCount();
|
|
||||||
inp->setRefCount(previousRefCount + distributeRefCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,7 +153,7 @@ protected:
|
|||||||
|
|
||||||
//specialization class which process() take an input item and re-dispatch
|
//specialization class which process() take an input item and re-dispatch
|
||||||
//A COPY to every outputs, without further processing. This is a 1-to-n dispatcher.
|
//A COPY to every outputs, without further processing. This is a 1-to-n dispatcher.
|
||||||
template<typename OutputDataType = ReferenceCounter>
|
template<typename OutputDataType>
|
||||||
class VisualDataReDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
class VisualDataReDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
||||||
protected:
|
protected:
|
||||||
virtual void process() {
|
virtual void process() {
|
||||||
@ -164,15 +162,17 @@ protected:
|
|||||||
|
|
||||||
if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
|
if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
|
||||||
if (inp) {
|
if (inp) {
|
||||||
inp->decRefCount();
|
//nothing
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inp) {
|
if (inp) {
|
||||||
OutputDataType *outp = buffers.getBuffer();
|
OutputDataTypePtr outp = buffers.getBuffer();
|
||||||
|
|
||||||
|
//'deep copy' of the contents
|
||||||
(*outp) = (*inp);
|
(*outp) = (*inp);
|
||||||
inp->decRefCount();
|
|
||||||
VisualProcessor<OutputDataType, OutputDataType>::distribute(outp);
|
VisualProcessor<OutputDataType, OutputDataType>::distribute(outp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
|
//50 ms
|
||||||
|
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
|
||||||
|
|
||||||
SDRPostThread::SDRPostThread() : IOThread(), buffers("SDRPostThreadBuffers"), visualDataBuffers("SDRPostThreadVisualDataBuffers"), frequency(0) {
|
SDRPostThread::SDRPostThread() : IOThread(), buffers("SDRPostThreadBuffers"), visualDataBuffers("SDRPostThreadVisualDataBuffers"), frequency(0) {
|
||||||
iqDataInQueue = NULL;
|
iqDataInQueue = NULL;
|
||||||
iqDataOutQueue = NULL;
|
iqDataOutQueue = NULL;
|
||||||
@ -183,23 +186,25 @@ void SDRPostThread::run() {
|
|||||||
iqActiveDemodVisualQueue = static_cast<DemodulatorThreadInputQueue*>(getOutputQueue("IQActiveDemodVisualDataOutput"));
|
iqActiveDemodVisualQueue = static_cast<DemodulatorThreadInputQueue*>(getOutputQueue("IQActiveDemodVisualDataOutput"));
|
||||||
|
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
SDRThreadIQData *data_in;
|
SDRThreadIQDataPtr data_in;
|
||||||
|
|
||||||
iqDataInQueue->pop(data_in);
|
if (!iqDataInQueue->pop(data_in, HEARTBEAT_CHECK_PERIOD_MICROS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// std::lock_guard < std::mutex > lock(data_in->m_mutex);
|
// std::lock_guard < std::mutex > lock(data_in->m_mutex);
|
||||||
|
|
||||||
std::lock_guard < std::mutex > lock(busy_demod);
|
std::lock_guard < std::mutex > lock(busy_demod);
|
||||||
|
|
||||||
if (data_in && data_in->data.size()) {
|
if (data_in && data_in->data.size()) {
|
||||||
if(data_in->numChannels > 1) {
|
if(data_in->numChannels > 1) {
|
||||||
runPFBCH(data_in);
|
runPFBCH(data_in.get());
|
||||||
} else {
|
} else {
|
||||||
runSingleCH(data_in);
|
runSingleCH(data_in.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data_in) {
|
if (data_in) {
|
||||||
data_in->decRefCount();
|
//nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
bool doUpdate = false;
|
bool doUpdate = false;
|
||||||
@ -217,9 +222,8 @@ void SDRPostThread::run() {
|
|||||||
} //end while
|
} //end while
|
||||||
|
|
||||||
//Be safe, remove as many elements as possible
|
//Be safe, remove as many elements as possible
|
||||||
DemodulatorThreadIQData *visualDataDummy;
|
if (iqVisualQueue) {
|
||||||
while (iqVisualQueue && iqVisualQueue->try_pop(visualDataDummy)) {
|
iqVisualQueue->flush();
|
||||||
visualDataDummy->decRefCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffers.purge();
|
// buffers.purge();
|
||||||
@ -230,7 +234,7 @@ void SDRPostThread::run() {
|
|||||||
|
|
||||||
void SDRPostThread::terminate() {
|
void SDRPostThread::terminate() {
|
||||||
IOThread::terminate();
|
IOThread::terminate();
|
||||||
SDRThreadIQData *dummy = new SDRThreadIQData;
|
SDRThreadIQDataPtr dummy(new SDRThreadIQData);
|
||||||
//VSO: blocking push
|
//VSO: blocking push
|
||||||
iqDataInQueue->push(dummy);
|
iqDataInQueue->push(dummy);
|
||||||
}
|
}
|
||||||
@ -278,8 +282,8 @@ void SDRPostThread::runSingleCH(SDRThreadIQData *data_in) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (refCount) {
|
if (refCount) {
|
||||||
DemodulatorThreadIQData *demodDataOut = buffers.getBuffer();
|
DemodulatorThreadIQDataPtr demodDataOut = buffers.getBuffer();
|
||||||
demodDataOut->setRefCount(refCount);
|
|
||||||
demodDataOut->frequency = frequency;
|
demodDataOut->frequency = frequency;
|
||||||
demodDataOut->sampleRate = sampleRate;
|
demodDataOut->sampleRate = sampleRate;
|
||||||
|
|
||||||
@ -333,7 +337,7 @@ void SDRPostThread::runPFBCH(SDRThreadIQData *data_in) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iqDataOutQueue != NULL && !iqDataOutQueue->full()) {
|
if (iqDataOutQueue != NULL && !iqDataOutQueue->full()) {
|
||||||
DemodulatorThreadIQData *iqDataOut = visualDataBuffers.getBuffer();
|
DemodulatorThreadIQDataPtr iqDataOut = visualDataBuffers.getBuffer();
|
||||||
|
|
||||||
bool doVis = false;
|
bool doVis = false;
|
||||||
|
|
||||||
@ -341,8 +345,6 @@ void SDRPostThread::runPFBCH(SDRThreadIQData *data_in) {
|
|||||||
doVis = true;
|
doVis = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
iqDataOut->setRefCount(1 + (doVis?1:0));
|
|
||||||
|
|
||||||
iqDataOut->frequency = data_in->frequency;
|
iqDataOut->frequency = data_in->frequency;
|
||||||
iqDataOut->sampleRate = data_in->sampleRate;
|
iqDataOut->sampleRate = data_in->sampleRate;
|
||||||
iqDataOut->data.assign(data_in->data.begin(), data_in->data.begin() + dataSize);
|
iqDataOut->data.assign(data_in->data.begin(), data_in->data.begin() + dataSize);
|
||||||
@ -407,8 +409,7 @@ void SDRPostThread::runPFBCH(SDRThreadIQData *data_in) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DemodulatorThreadIQData *demodDataOut = buffers.getBuffer();
|
DemodulatorThreadIQDataPtr demodDataOut = buffers.getBuffer();
|
||||||
demodDataOut->setRefCount(demodChannelActive[i] + doDemodVis);
|
|
||||||
demodDataOut->frequency = chanCenters[i];
|
demodDataOut->frequency = chanCenters[i];
|
||||||
demodDataOut->sampleRate = chanBw;
|
demodDataOut->sampleRate = chanBw;
|
||||||
|
|
||||||
|
@ -207,17 +207,17 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
|
|||||||
//TODO: Add in doc the need to reduce SoapySDR device buffer length (if available) to restore higher fps.
|
//TODO: Add in doc the need to reduce SoapySDR device buffer length (if available) to restore higher fps.
|
||||||
|
|
||||||
//0. Retreive a new batch
|
//0. Retreive a new batch
|
||||||
SDRThreadIQData *dataOut = buffers.getBuffer();
|
SDRThreadIQDataPtr dataOut = buffers.getBuffer();
|
||||||
|
|
||||||
//resize to the target size immedialetly, to minimize later reallocs:
|
//resize to the target size immedialetly, to minimize later reallocs:
|
||||||
assureBufferMinSize(dataOut, nElems);
|
assureBufferMinSize(dataOut.get(), nElems);
|
||||||
|
|
||||||
//1.If overflow occured on the previous readStream(), transfer it in dataOut directly.
|
//1.If overflow occured on the previous readStream(), transfer it in dataOut directly.
|
||||||
if (numOverflow > 0) {
|
if (numOverflow > 0) {
|
||||||
int n_overflow = std::min(numOverflow, nElems);
|
int n_overflow = std::min(numOverflow, nElems);
|
||||||
|
|
||||||
//safety
|
//safety
|
||||||
assureBufferMinSize(dataOut, n_overflow);
|
assureBufferMinSize(dataOut.get(), n_overflow);
|
||||||
|
|
||||||
::memcpy(&dataOut->data[0], &overflowBuffer.data[0], n_overflow * sizeof(liquid_float_complex));
|
::memcpy(&dataOut->data[0], &overflowBuffer.data[0], n_overflow * sizeof(liquid_float_complex));
|
||||||
n_read = n_overflow;
|
n_read = n_overflow;
|
||||||
@ -266,7 +266,7 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
|
|||||||
float *pp = (float *)buffs[0];
|
float *pp = (float *)buffs[0];
|
||||||
|
|
||||||
//safety
|
//safety
|
||||||
assureBufferMinSize(dataOut, n_read + n_requested);
|
assureBufferMinSize(dataOut.get(), n_read + n_requested);
|
||||||
|
|
||||||
if (iq_swap.load()) {
|
if (iq_swap.load()) {
|
||||||
for (int i = 0; i < n_requested; i++) {
|
for (int i = 0; i < n_requested; i++) {
|
||||||
@ -315,7 +315,7 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
|
|||||||
float *pp = (float *)buffs[0];
|
float *pp = (float *)buffs[0];
|
||||||
|
|
||||||
//safety
|
//safety
|
||||||
assureBufferMinSize(dataOut, n_read + n_stream_read);
|
assureBufferMinSize(dataOut.get(), n_read + n_stream_read);
|
||||||
|
|
||||||
if (iq_swap.load()) {
|
if (iq_swap.load()) {
|
||||||
for (int i = 0; i < n_stream_read; i++) {
|
for (int i = 0; i < n_stream_read; i++) {
|
||||||
@ -349,8 +349,7 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
|
|||||||
|
|
||||||
if (!iqDataOutQueue->try_push(dataOut)) {
|
if (!iqDataOutQueue->try_push(dataOut)) {
|
||||||
//The rest of the system saturates,
|
//The rest of the system saturates,
|
||||||
//finally the push didn't suceeded, recycle dataOut immediatly.
|
//finally the push didn't suceeded.
|
||||||
dataOut->setRefCount(0);
|
|
||||||
|
|
||||||
std::cout << "SDRThread::readStream(): 3.2 iqDataOutQueue output queue is full, discard processing of the batch..." << std::endl;
|
std::cout << "SDRThread::readStream(): 3.2 iqDataOutQueue output queue is full, discard processing of the batch..." << std::endl;
|
||||||
|
|
||||||
@ -359,7 +358,6 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dataOut->setRefCount(0);
|
|
||||||
std::cout << "SDRThread::readStream(): 3.1 iqDataOutQueue output queue is full, discard processing of the batch..." << std::endl;
|
std::cout << "SDRThread::readStream(): 3.1 iqDataOutQueue output queue is full, discard processing of the batch..." << std::endl;
|
||||||
//saturation, let a chance to the other threads to consume the existing samples
|
//saturation, let a chance to the other threads to consume the existing samples
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
#include "ThreadBlockingQueue.h"
|
#include "ThreadBlockingQueue.h"
|
||||||
#include "DemodulatorMgr.h"
|
#include "DemodulatorMgr.h"
|
||||||
#include "SDRDeviceInfo.h"
|
#include "SDRDeviceInfo.h"
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
class SDRThreadIQData: public ReferenceCounter {
|
class SDRThreadIQData {
|
||||||
public:
|
public:
|
||||||
long long frequency;
|
long long frequency;
|
||||||
long long sampleRate;
|
long long sampleRate;
|
||||||
@ -35,12 +35,12 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~SDRThreadIQData() {
|
virtual ~SDRThreadIQData() {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
typedef std::shared_ptr<SDRThreadIQData> SDRThreadIQDataPtr;
|
||||||
typedef ThreadBlockingQueue<SDRThreadIQData *> SDRThreadIQDataQueue;
|
typedef ThreadBlockingQueue<SDRThreadIQDataPtr> SDRThreadIQDataQueue;
|
||||||
|
|
||||||
class SDRThread : public IOThread {
|
class SDRThread : public IOThread {
|
||||||
private:
|
private:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <deque>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -29,24 +29,22 @@ class ThreadQueueBase {
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
class ThreadBlockingQueue : public ThreadQueueBase {
|
class ThreadBlockingQueue : public ThreadQueueBase {
|
||||||
|
|
||||||
typedef typename std::vector<T>::value_type value_type;
|
typedef typename std::deque<T>::value_type value_type;
|
||||||
typedef typename std::vector<T>::size_type size_type;
|
typedef typename std::deque<T>::size_type size_type;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/*! Create safe blocking queue. */
|
/*! Create safe blocking queue. */
|
||||||
ThreadBlockingQueue() {
|
ThreadBlockingQueue() {
|
||||||
//at least 1 (== Exchanger)
|
//at least 1 (== Java SynchronizedQueue)
|
||||||
m_circular_buffer.resize(MIN_ITEM_NB);
|
m_max_num_items = MIN_ITEM_NB;
|
||||||
};
|
};
|
||||||
|
|
||||||
//Copy constructor
|
//Copy constructor
|
||||||
ThreadBlockingQueue(const ThreadBlockingQueue& sq) {
|
ThreadBlockingQueue(const ThreadBlockingQueue& sq) {
|
||||||
std::lock_guard < std::mutex > lock(sq.m_mutex);
|
std::lock_guard < std::mutex > lock(sq.m_mutex);
|
||||||
m_circular_buffer = sq.m_circular_buffer;
|
m_queue = sq.m_queue;
|
||||||
m_head = sq.m_head;
|
m_max_num_items = sq.m_max_num_items;
|
||||||
m_tail = sq.m_tail;
|
|
||||||
m_size = sq.m_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Destroy safe queue. */
|
/*! Destroy safe queue. */
|
||||||
@ -62,11 +60,10 @@ public:
|
|||||||
void set_max_num_items(unsigned int max_num_items) {
|
void set_max_num_items(unsigned int max_num_items) {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
if (max_num_items > (unsigned int)m_circular_buffer.size()) {
|
if (max_num_items > m_max_num_items) {
|
||||||
//Only raise the existing max size, never reduce it
|
//Only raise the existing max size, never reduce it
|
||||||
//for simplification sake at runtime.
|
//for simplification sake at runtime.
|
||||||
m_circular_buffer.resize(max_num_items);
|
m_max_num_items = max_num_items;
|
||||||
//m_head and m_tail stays valid for the new size.
|
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,35 +74,34 @@ public:
|
|||||||
* \param[in] item An item.
|
* \param[in] item An item.
|
||||||
* \param[in] timeout a max waiting timeout in microseconds for an item to be pushed.
|
* \param[in] timeout a max waiting timeout in microseconds for an item to be pushed.
|
||||||
* by default, = 0 means indefinite wait.
|
* by default, = 0 means indefinite wait.
|
||||||
* \param[in] errorMessage an error message written on std::cout in case of the timeout wait
|
* \param[in] errorMessage if != nullptr (is nullptr by default) an error message written on std::cout in case of the timeout wait
|
||||||
* \return true if an item was pushed into the queue, else a timeout has occured.
|
* \return true if an item was pushed into the queue, else a timeout has occured.
|
||||||
*/
|
*/
|
||||||
bool push(const value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT,const char* errorMessage = "") {
|
bool push(const value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT,const char* errorMessage = nullptr) {
|
||||||
std::unique_lock < std::mutex > lock(m_mutex);
|
std::unique_lock < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
||||||
m_cond_not_full.wait(lock, [this]() // Lambda funct
|
m_cond_not_full.wait(lock, [this]() // Lambda funct
|
||||||
{
|
{
|
||||||
return m_size < m_circular_buffer.size();
|
return m_queue.size() < m_max_num_items;
|
||||||
});
|
});
|
||||||
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_size >= m_circular_buffer.size()) {
|
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_queue.size() >= m_max_num_items) {
|
||||||
// if the value is below a threshold, consider it is a try_push()
|
// if the value is below a threshold, consider it is a try_push()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (false == m_cond_not_full.wait_for(lock, std::chrono::microseconds(timeout),
|
else if (false == m_cond_not_full.wait_for(lock, std::chrono::microseconds(timeout),
|
||||||
[this]() { return m_size < m_circular_buffer.size(); })) {
|
[this]() { return m_queue.size() < m_max_num_items; })) {
|
||||||
|
|
||||||
|
if (errorMessage != nullptr) {
|
||||||
std::thread::id currentThreadId = std::this_thread::get_id();
|
std::thread::id currentThreadId = std::this_thread::get_id();
|
||||||
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
||||||
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.push() has failed with timeout > " <<
|
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.push() has failed with timeout > " <<
|
||||||
(timeout * 0.001) << " ms, message: " << errorMessage << std::endl;
|
(timeout * 0.001) << " ms, message: " << errorMessage << std::endl << std::flush;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//m_tail is already the next valid place an item can be put
|
m_queue.push_back(item);
|
||||||
m_circular_buffer[m_tail] = item;
|
|
||||||
m_tail = nextIndex(m_tail, (int)m_circular_buffer.size());
|
|
||||||
m_size++;
|
|
||||||
|
|
||||||
m_cond_not_empty.notify_all();
|
m_cond_not_empty.notify_all();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -118,14 +114,11 @@ public:
|
|||||||
bool try_push(const value_type& item) {
|
bool try_push(const value_type& item) {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
if (m_size >= m_circular_buffer.size()) {
|
if (m_queue.size() >= m_max_num_items) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//m_tail is already the next valid place an item can be put
|
m_queue.push_back(item);
|
||||||
m_circular_buffer[m_tail] = item;
|
|
||||||
m_tail = nextIndex(m_tail, (int)m_circular_buffer.size());
|
|
||||||
m_size++;
|
|
||||||
m_cond_not_empty.notify_all();
|
m_cond_not_empty.notify_all();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -133,33 +126,35 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Pops item from the queue. If the queue is empty, blocks for timeout microseconds, or until item becomes available.
|
* Pops item from the queue. If the queue is empty, blocks for timeout microseconds, or until item becomes available.
|
||||||
* \param[in] timeout The number of microseconds to wait. O (default) means indefinite wait.
|
* \param[in] timeout The number of microseconds to wait. O (default) means indefinite wait.
|
||||||
* \param[in] errorMessage an error message written on std::cout in case of the timeout wait
|
* \param[in] errorMessage if != nullptr (is nullptr by default) an error message written on std::cout in case of the timeout wait
|
||||||
* \return true if get an item from the queue, false if no item is received before the timeout.
|
* \return true if get an item from the queue, false if no item is received before the timeout.
|
||||||
*/
|
*/
|
||||||
bool pop(value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT, const char* errorMessage = "") {
|
bool pop(value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT, const char* errorMessage = nullptr) {
|
||||||
std::unique_lock < std::mutex > lock(m_mutex);
|
std::unique_lock < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
||||||
m_cond_not_empty.wait(lock, [this]() // Lambda funct
|
m_cond_not_empty.wait(lock, [this]() // Lambda funct
|
||||||
{
|
{
|
||||||
return m_size > 0;
|
return !m_queue.empty();
|
||||||
});
|
});
|
||||||
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_size == 0) {
|
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_queue.empty()) {
|
||||||
// if the value is below a threshold, consider it is try_pop()
|
// if the value is below a threshold, consider it is try_pop()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (false == m_cond_not_empty.wait_for(lock, std::chrono::microseconds(timeout),
|
else if (false == m_cond_not_empty.wait_for(lock, std::chrono::microseconds(timeout),
|
||||||
[this]() { return m_size > 0; })) {
|
[this]() { return !m_queue.empty(); })) {
|
||||||
|
|
||||||
|
if (errorMessage != nullptr) {
|
||||||
std::thread::id currentThreadId = std::this_thread::get_id();
|
std::thread::id currentThreadId = std::this_thread::get_id();
|
||||||
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
||||||
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.pop() has failed with timeout > " <<
|
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.pop() has failed with timeout > " <<
|
||||||
(timeout * 0.001) << " ms, message: " << errorMessage << std::endl;
|
(timeout * 0.001) << " ms, message: " << errorMessage << std::endl << std::flush;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
item = m_circular_buffer[m_head];
|
item = m_queue.front();
|
||||||
m_head = nextIndex(m_head, (int)m_circular_buffer.size());
|
m_queue.pop_front();
|
||||||
m_size--;
|
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -172,13 +167,12 @@ public:
|
|||||||
bool try_pop(value_type& item) {
|
bool try_pop(value_type& item) {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
|
|
||||||
if (m_size == 0) {
|
if (m_queue.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
item = m_circular_buffer[m_head];
|
item = m_queue.front();
|
||||||
m_head = nextIndex(m_head, (int)m_circular_buffer.size());
|
m_queue.pop_front();
|
||||||
m_size--;
|
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -190,7 +184,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
size_type size() const {
|
size_type size() const {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
return m_size;
|
return m_queue.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +193,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool empty() const {
|
bool empty() const {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
return m_size == 0;
|
return m_queue.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,7 +202,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool full() const {
|
bool full() const {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
return (m_size >= m_circular_buffer.size());
|
return (m_queue.size() >= m_max_num_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,9 +210,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void flush() {
|
void flush() {
|
||||||
std::lock_guard < std::mutex > lock(m_mutex);
|
std::lock_guard < std::mutex > lock(m_mutex);
|
||||||
m_head = 0;
|
m_queue.clear();
|
||||||
m_tail = 0;
|
|
||||||
m_size = 0;
|
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,25 +222,22 @@ public:
|
|||||||
if (this != &sq) {
|
if (this != &sq) {
|
||||||
std::lock_guard < std::mutex > lock1(m_mutex);
|
std::lock_guard < std::mutex > lock1(m_mutex);
|
||||||
std::lock_guard < std::mutex > lock2(sq.m_mutex);
|
std::lock_guard < std::mutex > lock2(sq.m_mutex);
|
||||||
m_circular_buffer.swap(sq.m_circular_buffer);
|
m_queue.swap(sq.m_queue);
|
||||||
|
std::swap(m_max_num_items, sq.m_max_num_items);
|
||||||
|
|
||||||
std::swap(m_head, sq.m_head);
|
if (!m_queue.empty()) {
|
||||||
std::swap(m_tail, sq.m_tail);
|
|
||||||
std::swap(m_size, sq.m_size);
|
|
||||||
|
|
||||||
if (m_size > 0) {
|
|
||||||
m_cond_not_empty.notify_all();
|
m_cond_not_empty.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq.m_size > 0) {
|
if (!sq.m_queue.empty()) {
|
||||||
sq.m_cond_not_empty.notify_all();
|
sq.m_cond_not_empty.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_size < m_circular_buffer.size()) {
|
if (!m_queue.full()) {
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq.m_size < sq.m_circular_buffer.size()) {
|
if (!sq.m_queue.full()) {
|
||||||
sq.m_cond_not_full.notify_all();
|
sq.m_cond_not_full.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,17 +249,14 @@ public:
|
|||||||
std::lock_guard < std::mutex > lock1(m_mutex);
|
std::lock_guard < std::mutex > lock1(m_mutex);
|
||||||
std::lock_guard < std::mutex > lock2(sq.m_mutex);
|
std::lock_guard < std::mutex > lock2(sq.m_mutex);
|
||||||
|
|
||||||
m_circular_buffer = sq.m_circular_buffer;
|
m_queue = sq.m_queue;
|
||||||
|
m_max_num_items = sq.m_max_num_items;
|
||||||
|
|
||||||
m_head = sq.m_head;
|
if (!m_queue.empty()) {
|
||||||
m_tail = sq.m_tail;
|
|
||||||
m_size = sq.m_size;
|
|
||||||
|
|
||||||
if (m_size > 0) {
|
|
||||||
m_cond_not_empty.notify_all();
|
m_cond_not_empty.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_size < m_circular_buffer.size()) {
|
if (!m_queue.full()) {
|
||||||
m_cond_not_full.notify_all();
|
m_cond_not_full.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,27 +264,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// use a circular buffer structure to prevent allocations / reallocations (fixed array + modulo)
|
|
||||||
std::vector<T> m_circular_buffer;
|
|
||||||
|
|
||||||
/**
|
std::deque<T> m_queue;
|
||||||
* The 'head' index of the element at the head of the deque, 'tail'
|
|
||||||
* the next (valid !) index at which an element can be pushed.
|
|
||||||
* m_head == m_tail means empty.
|
|
||||||
*/
|
|
||||||
int m_head = 0, m_tail = 0;
|
|
||||||
|
|
||||||
//hold the current number of elements.
|
|
||||||
size_type m_size = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
inline int nextIndex(int index, int mod) const {
|
|
||||||
return (index + 1 == mod) ? 0 : index + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
std::condition_variable m_cond_not_empty;
|
std::condition_variable m_cond_not_empty;
|
||||||
std::condition_variable m_cond_not_full;
|
std::condition_variable m_cond_not_full;
|
||||||
|
size_t m_max_num_items = MIN_ITEM_NB;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*! Swaps the contents of two ThreadBlockingQueue objects. (external operator) */
|
/*! Swaps the contents of two ThreadBlockingQueue objects. (external operator) */
|
||||||
|
@ -104,7 +104,7 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
|||||||
wxPaintDC dc(this);
|
wxPaintDC dc(this);
|
||||||
const wxSize ClientSize = GetClientSize();
|
const wxSize ClientSize = GetClientSize();
|
||||||
|
|
||||||
ScopeRenderData *avData;
|
ScopeRenderDataPtr avData;
|
||||||
while (inputData.try_pop(avData)) {
|
while (inputData.try_pop(avData)) {
|
||||||
|
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
|||||||
if (avData->waveform_points.size()) {
|
if (avData->waveform_points.size()) {
|
||||||
scopePanel.setPoints(avData->waveform_points);
|
scopePanel.setPoints(avData->waveform_points);
|
||||||
}
|
}
|
||||||
avData->decRefCount();
|
|
||||||
} else {
|
} else {
|
||||||
if (avData->waveform_points.size()) {
|
if (avData->waveform_points.size()) {
|
||||||
spectrumPanel.setPoints(avData->waveform_points);
|
spectrumPanel.setPoints(avData->waveform_points);
|
||||||
@ -125,7 +125,6 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
|||||||
spectrumPanel.setShowDb(showDb);
|
spectrumPanel.setShowDb(showDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
avData->decRefCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
|||||||
wxPaintDC dc(this);
|
wxPaintDC dc(this);
|
||||||
const wxSize ClientSize = GetClientSize();
|
const wxSize ClientSize = GetClientSize();
|
||||||
|
|
||||||
SpectrumVisualData *vData;
|
SpectrumVisualDataPtr vData;
|
||||||
if (visualDataQueue.try_pop(vData)) {
|
if (visualDataQueue.try_pop(vData)) {
|
||||||
|
|
||||||
if (vData) {
|
if (vData) {
|
||||||
@ -62,7 +62,6 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
|||||||
spectrumPanel.setPeakPoints(vData->spectrum_hold_points);
|
spectrumPanel.setPeakPoints(vData->spectrum_hold_points);
|
||||||
spectrumPanel.setFloorValue(vData->fft_floor);
|
spectrumPanel.setFloorValue(vData->fft_floor);
|
||||||
spectrumPanel.setCeilValue(vData->fft_ceiling);
|
spectrumPanel.setCeilValue(vData->fft_ceiling);
|
||||||
vData->decRefCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ void WaterfallCanvas::processInputQueue() {
|
|||||||
if (linesPerSecond) {
|
if (linesPerSecond) {
|
||||||
if (lpsIndex >= targetVis) {
|
if (lpsIndex >= targetVis) {
|
||||||
while (lpsIndex >= targetVis) {
|
while (lpsIndex >= targetVis) {
|
||||||
SpectrumVisualData *vData;
|
SpectrumVisualDataPtr vData;
|
||||||
|
|
||||||
if (visualDataQueue.try_pop(vData)) {
|
if (visualDataQueue.try_pop(vData)) {
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ void WaterfallCanvas::processInputQueue() {
|
|||||||
waterfallPanel.setPoints(vData->spectrum_points);
|
waterfallPanel.setPoints(vData->spectrum_points);
|
||||||
}
|
}
|
||||||
waterfallPanel.step();
|
waterfallPanel.step();
|
||||||
vData->decRefCount();
|
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
lpsIndex-=targetVis;
|
lpsIndex-=targetVis;
|
||||||
@ -915,13 +915,7 @@ void WaterfallCanvas::setLinesPerSecond(int lps) {
|
|||||||
linesPerSecond = lps;
|
linesPerSecond = lps;
|
||||||
|
|
||||||
//empty all
|
//empty all
|
||||||
SpectrumVisualData *vData;
|
visualDataQueue.flush();
|
||||||
while (visualDataQueue.try_pop(vData)) {
|
|
||||||
|
|
||||||
if (vData) {
|
|
||||||
vData->decRefCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaterfallCanvas::setMinBandwidth(int min) {
|
void WaterfallCanvas::setMinBandwidth(int min) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user