Merge pull request #4 from cjcliffe/audio_thread

New Audio thread and Demodulator thread
This commit is contained in:
Charles J. Cliffe 2014-11-16 19:16:52 -05:00
commit 3614c2ad9f
22 changed files with 735 additions and 298 deletions

View File

@ -90,7 +90,12 @@ SET (cubicsdr_sources
src/AppFrame.cpp
src/SDRThreadQueue.cpp
src/SDRThreadTask.cpp
src/Demodulator.cpp
src/DemodulatorThread.cpp
src/DemodulatorThreadQueue.cpp
src/DemodulatorThreadTask.cpp
src/AudioThread.cpp
src/AudioThreadQueue.cpp
src/AudioThreadTask.cpp
src/Gradient.cpp
src/visual/ScopeCanvas.cpp
src/visual/ScopeContext.cpp
@ -109,7 +114,12 @@ SET (cubicsdr_headers
src/CubicSDRDefs.h
src/SDRThreadQueue.h
src/SDRThreadTask.h
src/Demodulator.h
src/DemodulatorThread.h
src/DemodulatorThreadQueue.h
src/DemodulatorThreadTask.h
src/AudioThread.h
src/AudioThreadQueue.h
src/AudioThreadTask.h
src/Gradient.h
src/visual/ScopeCanvas.h
src/visual/ScopeContext.h

View File

@ -12,11 +12,16 @@
#include <vector>
#include "SDRThread.h"
#include "DemodulatorThread.h"
#include "AudioThread.h"
#include "CubicSDR.h"
wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
//EVT_MENU(wxID_NEW, AppFrame::OnNewWindow)
EVT_MENU(wxID_CLOSE, AppFrame::OnClose)
EVT_THREAD(EVENT_SDR_INPUT, AppFrame::OnEventInput)
EVT_THREAD(EVENT_DEMOD_INPUT, AppFrame::OnDemodInput)
EVT_THREAD(EVENT_AUDIO_INPUT, AppFrame::OnAudioInput)
EVT_IDLE(AppFrame::OnIdle)
wxEND_EVENT_TABLE()
@ -49,26 +54,43 @@ AppFrame::AppFrame() :
SetMenuBar(menuBar);
CreateStatusBar();
SetClientSize(1280, 400);
SetClientSize(1280, 600);
Centre();
Show();
m_pQueue = new SDRThreadQueue(this);
t_SDR = new SDRThread(m_pQueue);
threadQueueSDR = new SDRThreadQueue(this);
t_SDR = new SDRThread(threadQueueSDR);
if (t_SDR->Run() != wxTHREAD_NO_ERROR) {
wxLogError
("Can't create the thread!");
("Can't create the SDR thread!");
delete t_SDR;
t_SDR = NULL;
}
threadQueueDemod = new DemodulatorThreadQueue(this);
t_Demod = new DemodulatorThread(threadQueueDemod);
if (t_Demod->Run() != wxTHREAD_NO_ERROR) {
wxLogError
("Can't create the Demodulator thread!");
delete t_Demod;
t_Demod = NULL;
}
threadQueueAudio = new AudioThreadQueue(this);
t_Audio = new AudioThread(threadQueueAudio);
if (t_Audio->Run() != wxTHREAD_NO_ERROR) {
wxLogError
("Can't create the Audio thread!");
delete t_Audio;
t_Audio = NULL;
}
// t_IQBuffer = new IQBufferThread(this);
// if (t_IQBuffer->Run() != wxTHREAD_NO_ERROR) {
// wxLogError
// ("Can't create the thread!");
// delete t_IQBuffer;
t_IQBuffer = NULL;
// t_IQBuffer = NULL;
// }
// static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
@ -99,9 +121,11 @@ AppFrame::~AppFrame() {
// }
// }
delete t_SDR;
// delete t_SDR;
// delete t_IQBuffer;
delete m_pQueue;
delete threadQueueAudio;
delete threadQueueDemod;
delete threadQueueSDR;
}
void AppFrame::OnClose(wxCommandEvent& WXUNUSED(event)) {
@ -114,19 +138,66 @@ void AppFrame::OnNewWindow(wxCommandEvent& WXUNUSED(event)) {
new AppFrame();
}
// SDR IQ -> Demodulator
void AppFrame::OnEventInput(wxThreadEvent& event) {
std::vector<signed char> *new_buffer = event.GetPayload<std::vector<signed char> *>();
// std::cout << new_buffer->size() << std::endl;
if (new_buffer->size()) {
DemodulatorThreadTask task = DemodulatorThreadTask(DemodulatorThreadTask::DEMOD_THREAD_DATA);
task.setData(*new_buffer);
threadQueueDemod->addTask(task, DemodulatorThreadQueue::DEMOD_PRIORITY_HIGHEST);
test_demod.writeBuffer(new_buffer);
scopeCanvas->setWaveformPoints(test_demod.waveform_points);
spectrumCanvas->setData(new_buffer);
waterfallCanvas->setData(new_buffer);
// test_demod.writeBuffer(new_buffer);
// scopeCanvas->setWaveformPoints(test_demod.waveform_points);
spectrumCanvas->setData(new_buffer);
waterfallCanvas->setData(new_buffer);
} else {
std::cout << "Incoming IQ data empty?" << std::endl;
}
delete new_buffer;
}
// Demodulator -> Audio
void AppFrame::OnDemodInput(wxThreadEvent& event) {
std::vector<float> *new_buffer = event.GetPayload<std::vector<float> *>();
if (new_buffer->size()) {
AudioThreadTask task = AudioThreadTask(AudioThreadTask::AUDIO_THREAD_DATA);
task.setData(*new_buffer);
threadQueueAudio->addTask(task, AudioThreadQueue::AUDIO_PRIORITY_HIGHEST);
if (scopeCanvas->waveform_points.size() != new_buffer->size() * 2) {
scopeCanvas->waveform_points.resize(new_buffer->size() * 2);
}
for (int i = 0, iMax = new_buffer->size(); i < iMax; i++) {
scopeCanvas->waveform_points[i * 2 + 1] = (*new_buffer)[i] * 0.5f;
scopeCanvas->waveform_points[i * 2] = ((double) i / (double) iMax);
}
} else {
std::cout << "Incoming Demod data empty?" << std::endl;
}
delete new_buffer;
}
// Audio -> Visual
void AppFrame::OnAudioInput(wxThreadEvent& event) {
// std::vector<float> *new_buffer = event.GetPayload<std::vector<float> *>();
//
// if (new_buffer->size()) {
// AudioThreadTask task = AudioThreadTask(AudioThreadTask::AUDIO_THREAD_DATA);
// task.setData(*new_buffer);
// threadQueueAudio->addTask(task, AudioThreadQueue::AUDIO_PRIORITY_HIGHEST);
// } else {
// std::cout << "Incoming Demod data empty?" << std::endl;
// }
// delete new_buffer;
}
void AppFrame::OnIdle(wxIdleEvent& event) {
wxGetApp().Yield();
event.Skip();
}
@ -134,7 +205,7 @@ void AppFrame::setFrequency(unsigned int freq) {
frequency = freq;
SDRThreadTask task = SDRThreadTask(SDRThreadTask::SDR_THREAD_TUNING);
task.setUInt(freq);
m_pQueue->addTask(task, SDRThreadQueue::SDR_PRIORITY_HIGHEST);
threadQueueSDR->addTask(task, SDRThreadQueue::SDR_PRIORITY_HIGHEST);
}
int AppFrame::getFrequency() {

View File

@ -3,10 +3,12 @@
#include "wx/frame.h"
#include "PrimaryGLContext.h"
#include "SDRThread.h"
#include "AudioThread.h"
#include "DemodulatorThread.h"
#include "ScopeCanvas.h"
#include "SpectrumCanvas.h"
#include "WaterfallCanvas.h"
#include "Demodulator.h"
// Define a new frame type
class AppFrame: public wxFrame {
@ -14,6 +16,9 @@ public:
AppFrame();
~AppFrame();
void OnEventInput(wxThreadEvent& event);
void OnDemodInput(wxThreadEvent& event);
void OnAudioInput(wxThreadEvent& event);
void setFrequency(unsigned int freq);
int getFrequency();
@ -26,13 +31,17 @@ private:
ScopeCanvas *scopeCanvas;
SpectrumCanvas *spectrumCanvas;
WaterfallCanvas *waterfallCanvas;
SDRThread *t_SDR;
IQBufferThread *t_IQBuffer;
wxCriticalSection m_pThreadCS;
SDRThreadQueue* m_pQueue;
unsigned int frequency;
Demodulator test_demod;
SDRThread *t_SDR;
SDRThreadQueue* threadQueueSDR;
AudioThread *t_Audio;
AudioThreadQueue* threadQueueAudio;
DemodulatorThread *t_Demod;
DemodulatorThreadQueue* threadQueueDemod;
// IQBufferThread *t_IQBuffer;
wxCriticalSection m_pThreadCS;
unsigned int frequency;
// event table
wxDECLARE_EVENT_TABLE();

123
src/AudioThread.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "AudioThread.h"
#include "CubicSDRDefs.h"
#include <vector>
//wxDEFINE_EVENT(wxEVT_COMMAND_AudioThread_INPUT, wxThreadEvent);
AudioThread::AudioThread(AudioThreadQueue* pQueue, int id) :
wxThread(wxTHREAD_DETACHED), m_pQueue(pQueue), m_ID(id), audio_queue_ptr(0) {
}
AudioThread::~AudioThread() {
PaError err;
err = Pa_StopStream(stream);
err = Pa_CloseStream(stream);
Pa_Terminate();
}
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void *userData) {
AudioThread *src = (AudioThread *) userData;
float *out = (float*) outputBuffer;
if (!src->audio_queue.size()) {
for (int i = 0; i < framesPerBuffer * 2; i++) {
out[i] = 0;
}
return paContinue;
}
std::vector<float> nextBuffer = src->audio_queue.front();
for (int i = 0; i < framesPerBuffer * 2; i++) {
out[i] = nextBuffer[src->audio_queue_ptr];
src->audio_queue_ptr++;
if (src->audio_queue_ptr == nextBuffer.size()) {
src->audio_queue.pop();
src->audio_queue_ptr = 0;
if (!src->audio_queue.size()) {
break;
}
nextBuffer = src->audio_queue.front();
}
}
return paContinue;
}
wxThread::ExitCode AudioThread::Entry() {
PaError err;
err = Pa_Initialize();
if (err != paNoError) {
std::cout << "Error starting :(\n";
return (wxThread::ExitCode) 1;
}
int preferred_device = -1;
#ifdef WIN32
wchar_t dev_str[255];
memset(dev_str, 0, sizeof(wchar_t) * 255);
std::wstring env_name(L"PA_RECOMMENDED_OUTPUT_DEVICE");
GetEnvironmentVariable(env_name.c_str(), dev_str, 255);
std::wstring env_result(dev_str);
int env_dev = _wtoi(env_result.c_str());
if (env_dev || env_result.length()) {
std::cout << "Using preferred PortAudio device PA_RECOMMENDED_OUTPUT_DEVICE=" << env_result.c_str() << std::endl;
preferred_device = env_dev;
} else {
std::cout << "Environment variable PA_RECOMMENDED_OUTPUT_DEVICE not set, using PortAudio defaults." << std::endl;
}
#endif
outputParameters.device = (preferred_device != -1) ? preferred_device : Pa_GetDefaultOutputDevice();
if (outputParameters.device == paNoDevice) {
std::cout << "Error: No default output device.\n";
}
outputParameters.channelCount = 2; /* Stereo output, most likely supported. */
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
stream = NULL;
err = Pa_OpenStream(&stream, NULL, &outputParameters, AUDIO_FREQUENCY, 1024, paClipOff, &patestCallback, this);
err = Pa_StartStream(stream);
if (err != paNoError) {
std::cout << "Error starting stream: " << Pa_GetErrorText(err) << std::endl;
std::cout << "\tPortAudio error: " << Pa_GetErrorText(err) << std::endl;
}
while (!TestDestroy()) {
if (m_pQueue->stackSize()) {
while (m_pQueue->stackSize()) {
AudioThreadTask task = m_pQueue->pop(); // pop a task from the queue. this will block the worker thread if queue is empty
switch (task.m_cmd) {
case AudioThreadTask::AUDIO_THREAD_DATA:
audio_queue.push(task.getData());
break;
}
}
} else {
this->Yield();
this->Sleep(1);
}
}
std::cout << std::endl << "Audio Thread Done." << std::endl << std::endl;
return (wxThread::ExitCode) 0;
}

47
src/AudioThread.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <queue>
#include <vector>
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/thread.h"
#include "AudioThreadQueue.h"
#include "portaudio.h"
#ifdef WIN32
#include "pa_stream.h"
#include "pa_debugprint.h"
#endif
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void *userData);
// declare a new type of event, to be used by our AudioThread class:
//wxDECLARE_EVENT(wxEVT_COMMAND_AudioThread_COMPLETED, wxThreadEvent);
//wxDECLARE_EVENT(wxEVT_COMMAND_AudioThread_UPDATE, wxThreadEvent);
//wxDECLARE_EVENT(wxEVT_COMMAND_AudioThread_INPUT, wxThreadEvent);
enum {
EVENT_AUDIO_INPUT = wxID_HIGHEST + 3
};
class AudioThread: public wxThread {
public:
std::queue<std::vector<float> > audio_queue;
unsigned int audio_queue_ptr;
AudioThread(AudioThreadQueue* pQueue, int id = 0);
~AudioThread();
protected:
virtual ExitCode Entry();
AudioThreadQueue* m_pQueue;
int m_ID;
PaStreamParameters outputParameters;
PaStream *stream;
};

43
src/AudioThreadQueue.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "AudioThreadQueue.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
AudioThreadQueue::AudioThreadQueue(wxEvtHandler* pParent) :
m_pParent(pParent) {
}
void AudioThreadQueue::addTask(const AudioThreadTask& task, const AUDIO_PRIORITY& priority) {
wxMutexLocker lock(m_MutexQueue);
m_Tasks.insert(std::make_pair(priority, task));
m_QueueCount.Post();
}
AudioThreadTask AudioThreadQueue::pop() {
AudioThreadTask element;
m_QueueCount.Wait();
m_MutexQueue.Lock();
element = (m_Tasks.begin())->second;
m_Tasks.erase(m_Tasks.begin());
m_MutexQueue.Unlock();
return element;
}
void AudioThreadQueue::report(const AudioThreadTask::AUDIO_THREAD_COMMAND& cmd, const wxString& sArg, int iArg) {
wxCommandEvent evt(wxEVT_THREAD, cmd);
evt.SetString(sArg);
evt.SetInt(iArg);
m_pParent->AddPendingEvent(evt);
}
size_t AudioThreadQueue::stackSize() {
wxMutexLocker lock(m_MutexQueue);
return m_Tasks.size();
}
wxEvtHandler* AudioThreadQueue::getHandler() {
return m_pParent;
}

28
src/AudioThreadQueue.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <map>
#include "AudioThreadTask.h"
#include "wx/event.h"
class AudioThreadQueue {
public:
enum AUDIO_PRIORITY {
AUDIO_PRIORITY_HIGHEST, AUDIO_PRIORITY_HIGHER, AUDIO_PRIORITY_NORMAL, AUDIO_PRIORITY_BELOW_NORMAL, AUDIO_PRIORITY_LOW, AUDIO_PRIORITY_IDLE
};
AudioThreadQueue(wxEvtHandler* pParent);
void addTask(const AudioThreadTask& task, const AUDIO_PRIORITY& priority = AUDIO_PRIORITY_NORMAL);
void report(const AudioThreadTask::AUDIO_THREAD_COMMAND& cmd, const wxString& sArg = wxEmptyString, int iArg = 0);
AudioThreadTask pop();
size_t stackSize();
wxEvtHandler* getHandler();
private:
wxEvtHandler* m_pParent;
std::multimap<AUDIO_PRIORITY, AudioThreadTask> m_Tasks;
wxMutex m_MutexQueue;
wxSemaphore m_QueueCount;
};

9
src/AudioThreadTask.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "AudioThreadTask.h"
void AudioThreadTask::setData(std::vector<float> &data_in) {
data = data_in;
}
std::vector<float> &AudioThreadTask::getData() {
return data;
}

28
src/AudioThreadTask.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <vector>
#include "wx/defs.h"
#include "wx/string.h"
class AudioThreadTask {
public:
enum AUDIO_THREAD_COMMAND {
AUDIO_THREAD_EXIT = wxID_EXIT, AUDIO_THREAD_NULL = wxID_HIGHEST + 1, AUDIO_THREAD_STARTED, AUDIO_THREAD_PROCESS, AUDIO_THREAD_ERROR, AUDIO_THREAD_DATA
};
AudioThreadTask() :
m_cmd(AUDIO_THREAD_NULL) {
}
AudioThreadTask(AUDIO_THREAD_COMMAND cmd) :
m_cmd(cmd) {
}
void setData(std::vector<float> &data_in);
std::vector<float> &getData();
std::vector<float> data;
AUDIO_THREAD_COMMAND m_cmd;
};

View File

@ -1,8 +1,8 @@
#pragma once
#define BUF_SIZE (16 * 32 * 256)
#define BUF_SIZE (16 * 32 * 128)
#define SRATE 2000000
#define FFT_SIZE 2048
#define DEFAULT_FREQ 98900000
#define AUDIO_FREQUENCY 48000

View File

@ -1,217 +0,0 @@
#include "Demodulator.h"
#ifdef WIN32
#include <windows.h>
#endif
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void *userData) {
Demodulator *src = (Demodulator *) userData;
float *out = (float*) outputBuffer;
if (!src->audio_queue.size()) {
for (int i = 0; i < framesPerBuffer * 2; i++) {
out[i] = 0;
}
return paContinue;
}
std::vector<float> *nextBuffer = src->audio_queue.front();
for (int i = 0; i < framesPerBuffer * 2; i++) {
out[i] = (*nextBuffer)[src->audio_queue_ptr];
src->audio_queue_ptr++;
if (src->audio_queue_ptr == nextBuffer->size()) {
src->audio_queue.pop();
delete nextBuffer;
src->audio_queue_ptr = 0;
if (!src->audio_queue.size()) {
break;
}
nextBuffer = src->audio_queue.front();
}
}
return paContinue;
}
Demodulator::Demodulator() {
bandwidth = 200000;
resample_ratio = (float) (bandwidth) / (float) SRATE;
wbfm_frequency = 100000;
wbfm_resample_ratio = (float) (wbfm_frequency) / (float) bandwidth;
audio_frequency = 48000;
audio_resample_ratio = (float) (audio_frequency) / (float) wbfm_frequency;
PaError err;
err = Pa_Initialize();
if (err != paNoError) {
std::cout << "Error starting :(\n";
}
int preferred_device = -1;
#ifdef WIN32
wchar_t dev_str[255];
memset(dev_str, 0, sizeof(wchar_t) * 255);
std::wstring env_name(L"PA_RECOMMENDED_OUTPUT_DEVICE");
GetEnvironmentVariable(env_name.c_str(), dev_str, 255);
std::wstring env_result(dev_str);
int env_dev = _wtoi(env_result.c_str());
if (env_dev || env_result.length()) {
std::cout << "Using preferred PortAudio device PA_RECOMMENDED_OUTPUT_DEVICE=" << env_result.c_str() << std::endl;
preferred_device = env_dev;
} else {
std::cout << "Environment variable PA_RECOMMENDED_OUTPUT_DEVICE not set, using PortAudio defaults." << std::endl;
}
#endif
outputParameters.device = (preferred_device != -1) ? preferred_device : Pa_GetDefaultOutputDevice();
if (outputParameters.device == paNoDevice) {
std::cout << "Error: No default output device.\n";
}
outputParameters.channelCount = 2; /* Stereo output, most likely supported. */
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
stream = NULL;
err = Pa_OpenStream(&stream, NULL, &outputParameters, audio_frequency, 1024, paClipOff, &patestCallback, this);
err = Pa_StartStream(stream);
if (err != paNoError) {
std::cout << "Error starting stream: " << Pa_GetErrorText(err) << std::endl;
std::cout << "\tPortAudio error: " << Pa_GetErrorText(err) << std::endl;
}
float fc = 0.5f * ((float)bandwidth / (float)SRATE) * 0.75; // filter cutoff frequency
float ft = 0.05f; // filter transition
float As = 60.0f; // stop-band attenuation [dB]
float mu = 0.0f; // fractional timing offset
// estimate required filter length and generate filter
unsigned int h_len = estimate_req_filter_len(ft, As);
float h[h_len];
liquid_firdes_kaiser(h_len, fc, As, mu, h);
fir_filter = firfilt_crcf_create(h, h_len);
h_len = estimate_req_filter_len(ft, As);
liquid_firdes_kaiser(h_len, 32000.0/(float)wbfm_frequency, As, mu, h);
fir_audio_filter = firfilt_crcf_create(h, h_len);
// create multi-stage arbitrary resampler object
resampler = msresamp_crcf_create(resample_ratio, As);
msresamp_crcf_print(resampler);
wbfm_resampler = msresamp_crcf_create(wbfm_resample_ratio, As);
msresamp_crcf_print(wbfm_resampler);
audio_resampler = msresamp_crcf_create(audio_resample_ratio, As);
msresamp_crcf_print(audio_resampler);
float kf = 0.75; // modulation factor
fdem = freqdem_create(kf);
freqdem_print(fdem);
}
Demodulator::~Demodulator() {
PaError err;
err = Pa_StopStream(stream);
err = Pa_CloseStream(stream);
Pa_Terminate();
}
void Demodulator::writeBuffer(std::vector<signed char> *data) {
liquid_float_complex filtered_input[BUF_SIZE / 2];
for (int i = 0; i < BUF_SIZE / 2; i++) {
liquid_float_complex x;
liquid_float_complex y;
x.real = (float) (*data)[i * 2] / 127.0f;
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
firfilt_crcf_push(fir_filter, x); // push input sample
firfilt_crcf_execute(fir_filter, &y); // compute output
filtered_input[i] = y;
}
int out_size = ceil((float) (BUF_SIZE / 2) * resample_ratio);
liquid_float_complex resampled_output[out_size];
unsigned int num_written; // number of values written to buffer
msresamp_crcf_execute(resampler, filtered_input, (BUF_SIZE / 2), resampled_output, &num_written);
float waveform_ceil = 0, waveform_floor = 0;
float pcm = 0;
for (int i = 0; i < num_written; i++) {
freqdem_demodulate(fdem, resampled_output[i], &pcm);
resampled_output[i].real = (float) pcm;
resampled_output[i].imag = 0;
if (waveform_ceil < resampled_output[i].real) {
waveform_ceil = resampled_output[i].real;
}
if (waveform_floor > resampled_output[i].real) {
waveform_floor = resampled_output[i].real;
}
}
int wbfm_out_size = ceil((float) (num_written) * wbfm_resample_ratio);
liquid_float_complex resampled_wbfm_output[wbfm_out_size];
unsigned int num_wbfm_written;
msresamp_crcf_execute(wbfm_resampler, resampled_output, num_written, resampled_wbfm_output, &num_wbfm_written);
for (int i = 0; i < num_wbfm_written; i++) {
firfilt_crcf_push(fir_audio_filter, resampled_wbfm_output[i]);
firfilt_crcf_execute(fir_audio_filter, &resampled_wbfm_output[i]);
}
int audio_out_size = ceil((float) (num_wbfm_written) * audio_resample_ratio);
liquid_float_complex resampled_audio_output[audio_out_size];
unsigned int num_audio_written;
msresamp_crcf_execute(audio_resampler, resampled_wbfm_output, num_wbfm_written, resampled_audio_output, &num_audio_written);
std::vector<float> *newBuffer = new std::vector<float>;
newBuffer->resize(num_audio_written * 2);
for (int i = 0; i < num_audio_written; i++) {
liquid_float_complex y = resampled_audio_output[i];
(*newBuffer)[i * 2] = y.real;
(*newBuffer)[i * 2 + 1] = y.real;
}
audio_queue.push(newBuffer);
if (waveform_points.size() != num_audio_written * 2) {
waveform_points.resize(num_audio_written * 2);
}
for (int i = 0, iMax = waveform_points.size() / 2; i < iMax; i++) {
waveform_points[i * 2 + 1] = resampled_audio_output[i].real * 0.5f;
waveform_points[i * 2] = ((double) i / (double) iMax);
}
}

View File

@ -1,53 +0,0 @@
#pragma once
#include <vector>
#include <queue>
#include <cstring>
#include <iostream>
#include <math.h>
#include "CubicSDRDefs.h"
#include "liquid/liquid.h"
#include "portaudio.h"
#ifdef WIN32
#include "pa_stream.h"
#include "pa_debugprint.h"
#endif
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void *userData);
class Demodulator {
public:
std::queue<std::vector<float> *> audio_queue;
unsigned int audio_queue_ptr;
std::vector<float> waveform_points;
Demodulator();
~Demodulator();
void writeBuffer(std::vector<signed char> *data);
private:
firfilt_crcf fir_filter;
firfilt_crcf fir_audio_filter;
unsigned int bandwidth;
msresamp_crcf resampler;
float resample_ratio;
msresamp_crcf wbfm_resampler;
float wbfm_resample_ratio;
unsigned int wbfm_frequency;
msresamp_crcf audio_resampler;
float audio_resample_ratio;
unsigned int audio_frequency;
PaStreamParameters outputParameters;
PaStream *stream;
freqdem fdem;
};

174
src/DemodulatorThread.cpp Normal file
View File

@ -0,0 +1,174 @@
#include "DemodulatorThread.h"
#include "CubicSDRDefs.h"
#include <vector>
//wxDEFINE_EVENT(wxEVT_COMMAND_DemodulatorThread_INPUT, wxThreadEvent);
DemodulatorThread::DemodulatorThread(DemodulatorThreadQueue* pQueue, int id) :
wxThread(wxTHREAD_DETACHED), m_pQueue(pQueue), m_ID(id) {
bandwidth = 200000;
resample_ratio = (float) (bandwidth) / (float) SRATE;
wbfm_frequency = 100000;
wbfm_resample_ratio = (float) (wbfm_frequency) / (float) bandwidth;
audio_frequency = AUDIO_FREQUENCY;
audio_resample_ratio = (float) (audio_frequency) / (float) wbfm_frequency;
float fc = 0.5f * ((float) bandwidth / (float) SRATE) * 0.75; // filter cutoff frequency
float ft = 0.05f; // filter transition
float As = 60.0f; // stop-band attenuation [dB]
float mu = 0.0f; // fractional timing offset
// estimate required filter length and generate filter
unsigned int h_len = estimate_req_filter_len(ft, As);
float h[h_len];
liquid_firdes_kaiser(h_len, fc, As, mu, h);
fir_filter = firfilt_crcf_create(h, h_len);
h_len = estimate_req_filter_len(ft, As);
liquid_firdes_kaiser(h_len, 32000.0 / (float) wbfm_frequency, As, mu, h);
fir_audio_filter = firfilt_crcf_create(h, h_len);
// create multi-stage arbitrary resampler object
resampler = msresamp_crcf_create(resample_ratio, As);
msresamp_crcf_print(resampler);
wbfm_resampler = msresamp_crcf_create(wbfm_resample_ratio, As);
msresamp_crcf_print(wbfm_resampler);
audio_resampler = msresamp_crcf_create(audio_resample_ratio, As);
msresamp_crcf_print(audio_resampler);
float kf = 0.75; // modulation factor
fdem = freqdem_create(kf);
freqdem_print(fdem);
}
DemodulatorThread::~DemodulatorThread() {
}
wxThread::ExitCode DemodulatorThread::Entry() {
while (!TestDestroy()) {
if (m_pQueue->stackSize()) {
while (m_pQueue->stackSize()) {
DemodulatorThreadTask task = m_pQueue->pop(); // pop a task from the queue. this will block the worker thread if queue is empty
switch (task.m_cmd) {
case DemodulatorThreadTask::DEMOD_THREAD_DATA:
std::vector<signed char> *data = &task.getData();
if (data->size()) {
liquid_float_complex filtered_input[BUF_SIZE / 2];
for (int i = 0; i < BUF_SIZE / 2; i++) {
liquid_float_complex x;
liquid_float_complex y;
x.real = (float) (*data)[i * 2] / 127.0f;
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
firfilt_crcf_push(fir_filter, x); // push input sample
firfilt_crcf_execute(fir_filter, &y); // compute output
filtered_input[i] = y;
}
int out_size = ceil((float) (BUF_SIZE / 2) * resample_ratio);
liquid_float_complex resampled_output[out_size];
unsigned int num_written; // number of values written to buffer
msresamp_crcf_execute(resampler, filtered_input, (BUF_SIZE / 2), resampled_output, &num_written);
float waveform_ceil = 0, waveform_floor = 0;
float pcm = 0;
for (int i = 0; i < num_written; i++) {
freqdem_demodulate(fdem, resampled_output[i], &pcm);
resampled_output[i].real = (float) pcm;
resampled_output[i].imag = 0;
if (waveform_ceil < resampled_output[i].real) {
waveform_ceil = resampled_output[i].real;
}
if (waveform_floor > resampled_output[i].real) {
waveform_floor = resampled_output[i].real;
}
}
int wbfm_out_size = ceil((float) (num_written) * wbfm_resample_ratio);
liquid_float_complex resampled_wbfm_output[wbfm_out_size];
unsigned int num_wbfm_written;
msresamp_crcf_execute(wbfm_resampler, resampled_output, num_written, resampled_wbfm_output, &num_wbfm_written);
for (int i = 0; i < num_wbfm_written; i++) {
firfilt_crcf_push(fir_audio_filter, resampled_wbfm_output[i]);
firfilt_crcf_execute(fir_audio_filter, &resampled_wbfm_output[i]);
}
int audio_out_size = ceil((float) (num_wbfm_written) * audio_resample_ratio);
liquid_float_complex resampled_audio_output[audio_out_size];
unsigned int num_audio_written;
msresamp_crcf_execute(audio_resampler, resampled_wbfm_output, num_wbfm_written, resampled_audio_output, &num_audio_written);
std::vector<float> *newBuffer = new std::vector<float>;
newBuffer->resize(num_audio_written * 2);
for (int i = 0; i < num_audio_written; i++) {
liquid_float_complex y = resampled_audio_output[i];
(*newBuffer)[i * 2] = y.real;
(*newBuffer)[i * 2 + 1] = y.real;
}
if (!TestDestroy()) {
wxThreadEvent event(wxEVT_THREAD, EVENT_DEMOD_INPUT);
event.SetPayload(newBuffer);
wxQueueEvent(m_pQueue->getHandler(), event.Clone());
} else {
delete newBuffer;
}
// std::vector<float> *newBuffer = new std::vector<float>;
// newBuffer->resize(num_audio_written * 2);
// for (int i = 0; i < num_audio_written; i++) {
// liquid_float_complex y = resampled_audio_output[i];
//
// (*newBuffer)[i * 2] = y.real;
// (*newBuffer)[i * 2 + 1] = y.real;
// }
// if (waveform_points.size() != num_audio_written * 2) {
// waveform_points.resize(num_audio_written * 2);
// }
//
// for (int i = 0, iMax = waveform_points.size() / 2; i < iMax; i++) {
// waveform_points[i * 2 + 1] = resampled_audio_output[i].real * 0.5f;
// waveform_points[i * 2] = ((double) i / (double) iMax);
// }
}
// audio_queue.push(newBuffer);
break;
}
}
} else {
this->Yield();
this->Sleep(1);
}
}
std::cout << std::endl << "Demodulator Thread Done." << std::endl << std::endl;
return (wxThread::ExitCode) 0;
}

57
src/DemodulatorThread.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <queue>
#include <vector>
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/thread.h"
#include "DemodulatorThreadQueue.h"
#include "liquid/liquid.h"
// declare a new type of event, to be used by our DemodulatorThread class:
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_COMPLETED, wxThreadEvent);
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_UPDATE, wxThreadEvent);
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_INPUT, wxThreadEvent);
enum {
EVENT_DEMOD_INPUT = wxID_HIGHEST + 2
};
class DemodulatorThread: public wxThread {
public:
std::queue<std::vector<float> *> audio_queue;
unsigned int audio_queue_ptr;
DemodulatorThread(DemodulatorThreadQueue* pQueue, int id = 0);
~DemodulatorThread();
protected:
virtual ExitCode Entry();
DemodulatorThreadQueue* m_pQueue;
int m_ID;
firfilt_crcf fir_filter;
firfilt_crcf fir_audio_filter;
unsigned int bandwidth;
msresamp_crcf resampler;
float resample_ratio;
msresamp_crcf wbfm_resampler;
float wbfm_resample_ratio;
unsigned int wbfm_frequency;
msresamp_crcf audio_resampler;
float audio_resample_ratio;
unsigned int audio_frequency;
freqdem fdem;
};

View File

@ -0,0 +1,43 @@
#include "DemodulatorThreadQueue.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
DemodulatorThreadQueue::DemodulatorThreadQueue(wxEvtHandler* pParent) :
m_pParent(pParent) {
}
void DemodulatorThreadQueue::addTask(const DemodulatorThreadTask& task, const DEMOD_PRIORITY& priority) {
wxMutexLocker lock(m_MutexQueue);
m_Tasks.insert(std::make_pair(priority, task));
m_QueueCount.Post();
}
DemodulatorThreadTask DemodulatorThreadQueue::pop() {
DemodulatorThreadTask element;
m_QueueCount.Wait();
m_MutexQueue.Lock();
element = (m_Tasks.begin())->second;
m_Tasks.erase(m_Tasks.begin());
m_MutexQueue.Unlock();
return element;
}
void DemodulatorThreadQueue::report(const DemodulatorThreadTask::DEMOD_THREAD_COMMAND& cmd, const wxString& sArg, int iArg) {
wxCommandEvent evt(wxEVT_THREAD, cmd);
evt.SetString(sArg);
evt.SetInt(iArg);
m_pParent->AddPendingEvent(evt);
}
size_t DemodulatorThreadQueue::stackSize() {
wxMutexLocker lock(m_MutexQueue);
return m_Tasks.size();
}
wxEvtHandler* DemodulatorThreadQueue::getHandler() {
return m_pParent;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <map>
#include "DemodulatorThreadTask.h"
#include "wx/event.h"
class DemodulatorThreadQueue {
public:
enum DEMOD_PRIORITY {
DEMOD_PRIORITY_HIGHEST, DEMOD_PRIORITY_HIGHER, DEMOD_PRIORITY_NORMAL, DEMOD_PRIORITY_BELOW_NORMAL, DEMOD_PRIORITY_LOW, DEMOD_PRIORITY_IDLE
};
DemodulatorThreadQueue(wxEvtHandler* pParent);
void addTask(const DemodulatorThreadTask& task, const DEMOD_PRIORITY& priority = DEMOD_PRIORITY_NORMAL);
void report(const DemodulatorThreadTask::DEMOD_THREAD_COMMAND& cmd, const wxString& sArg = wxEmptyString, int iArg = 0);
DemodulatorThreadTask pop();
size_t stackSize();
wxEvtHandler* getHandler();
private:
wxEvtHandler* m_pParent;
std::multimap<DEMOD_PRIORITY, DemodulatorThreadTask> m_Tasks;
wxMutex m_MutexQueue;
wxSemaphore m_QueueCount;
};

View File

@ -0,0 +1,8 @@
#include "DemodulatorThreadTask.h"
void DemodulatorThreadTask::setData(std::vector<signed char> &data_in) {
data = data_in;
}
std::vector<signed char> &DemodulatorThreadTask::getData() {
return data;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <vector>
#include "wx/defs.h"
#include "wx/string.h"
class DemodulatorThreadTask {
public:
enum DEMOD_THREAD_COMMAND {
DEMOD_THREAD_EXIT = wxID_EXIT, DEMOD_THREAD_NULL = wxID_HIGHEST + 1, DEMOD_THREAD_STARTED, DEMOD_THREAD_PROCESS, DEMOD_THREAD_ERROR, DEMOD_THREAD_DATA
};
DemodulatorThreadTask() :
m_cmd(DEMOD_THREAD_NULL) {
}
DemodulatorThreadTask(DEMOD_THREAD_COMMAND cmd) :
m_cmd(cmd) {
}
void setData(std::vector<signed char> &data_in);
std::vector<signed char> &getData();
DEMOD_THREAD_COMMAND m_cmd;
std::vector<signed char> data;
};

View File

@ -1,6 +1,7 @@
#include "SDRThread.h"
#include "CubicSDRDefs.h"
#include <vector>
#include "CubicSDR.h"
//wxDEFINE_EVENT(wxEVT_COMMAND_SDRThread_INPUT, wxThreadEvent);
@ -159,6 +160,9 @@ wxThread::ExitCode SDRThread::Entry() {
} else {
delete new_buffer;
}
} else {
this->Yield();
this->Sleep(1);
}
}
std::cout << std::endl << "Done." << std::endl << std::endl;

View File

@ -9,7 +9,6 @@
#include "wx/thread.h"
#include "SDRThread.h"
#include "IQBufferThread.h"
#include "SDRThreadQueue.h"

View File

@ -12,6 +12,8 @@
class ScopeCanvas: public wxGLCanvas {
public:
std::vector<float> waveform_points;
ScopeCanvas(wxWindow *parent, int *attribList = NULL);
~ScopeCanvas();
@ -23,7 +25,6 @@ private:
void OnIdle(wxIdleEvent &event);
wxWindow *parent;
std::vector<float> waveform_points;
ScopeContext *glContext;
// event table

View File

@ -9,7 +9,6 @@
#include "SpectrumContext.h"
#include "fftw3.h"
#include "Demodulator.h"
class SpectrumCanvas: public wxGLCanvas {
public: