#include <QObject>template<class T>class QRingBuffer{public: QRingBuffer(quint16 size) : buffer(new T[size]), head(0), tail(0), bufferSize(size){} ~QRingBuffer(){delete[] buffer;} void addSample(T sample) //ЗДЕСЬ ВОЗНИКАЕТ ОШИБКА ЧТЕНИЯ ПАМЯТИ { if (++head >= bufferSize) head -= bufferSize; buffer[head] = sample; head++; } T getSample() { if (++tail >= bufferSize) tail -= bufferSize; return buffer[tail++]; }private: T *buffer; quint16 bufferSize, head, tail;};
#include <QObject>#include <QThread>#include "qringbuffer.h"extern "C"{#include <portaudio.h>}#define SAMPLE_RATE (44100)#define PA_SAMPLE_SIZE (paInt16)#define NUM_CHANNELS (1)#define FRAMES_PER_BUFFER (512)#define RINGBUFFER_SIZE (4)#define SILENCE (0)typedef short SAMPLE;struct DataShuttle{ char syncFlag; QRingBuffer<SAMPLE> *ringBuffer;};class DSPEngine : public QObject{ Q_OBJECTpublic slots: //бесконечный цикл обработки сигнала запускается в отдельном потоке void startEngine(void *userData);private: DataShuttle *data; void process();signals: //Сигнал с пиковым значением для индикатора void levelChanged(quint16);};
#include "dspengine.h"void DSPEngine::startEngine(void *userData){ //получаем указатель на структуру данных data = (DataShuttle*)userData; //В бесконечном цикле ждём флага синхронизации с callback //(это означает, что кольцевой буфер готов и мы можем читать из него) while (true) { if (data->syncFlag == 'r') { data->syncFlag = 'w'; process(); } }}void DSPEngine::process(){ //Считываем сэмплы из кольцевого буфера SAMPLE *samples = nullptr; quint16 bufferSize = FRAMES_PER_BUFFER * NUM_CHANNELS; for (int i = 0; i < bufferSize; i++) samples[i] = data->ringBuffer->getSample(); //Получаем пиковое значение для индикации сигнала quint16 max = 0; for (int x = 0; x < bufferSize; x++) if(samples[x] > max) max = samples[x]; emit levelChanged(max);}
#include <QMainWindow>#include "dspengine.h"QT_BEGIN_NAMESPACEnamespace Ui { class BlackBox; }QT_END_NAMESPACEclass BlackBox : public QMainWindow{ Q_OBJECT QThread dspThread;public: BlackBox(QWidget *parent = nullptr); ~BlackBox();private slots: void listen(int indx); void indicate(quint16 val);private: Ui::BlackBox *ui; bool initialize(); void errorHandler(PaError error); bool queryAudioInputs(); PaStreamParameters inputParameters; void ctrlsDisable(); PaStream *inStream;signals: //Сигнал для запуска обработчика в отдельном потоке void startDSP(void *data);};
#include "blackbox.h"#include "ui_blackbox.h"BlackBox::BlackBox(QWidget *parent) : QMainWindow(parent) , ui(new Ui::BlackBox){ ui->setupUi(this); //инициализируем PortAudio if (!initialize()) ctrlsDisable(); else { //подключаем выбор устройства записи connect(ui->devBox,SIGNAL(currentIndexChanged(int)), this, SLOT(listen(int))); //создаём поток обработчика сигнала DSPEngine *dspEngine = new DSPEngine; dspEngine->moveToThread(&dspThread); //подключаем сигналы-слоты для общения между потоками connect(&dspThread, &QThread::finished, dspEngine, &QObject::deleteLater); connect(this, &BlackBox::startDSP, dspEngine, &DSPEngine::startEngine); connect(dspEngine, &DSPEngine::levelChanged, this, &BlackBox::indicate); //стартуем отдельный поток dspThread.start(); }}BlackBox::~BlackBox(){ dspThread.quit(); dspThread.wait(); delete ui;}//это наш callback, который получает данные с устройства записи и заполняет кольцевой буфферstatic int recordCallBack(const void *inBuffer, void *outBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData){ DataShuttle *data = (DataShuttle*)userData; const SAMPLE *rptr = (const SAMPLE*)inBuffer; quint16 bufferLength = framesPerBuffer * NUM_CHANNELS; (void)outBuffer; (void)timeInfo; (void)statusFlags; for (int i = 0; i < bufferLength; i++) data->ringBuffer->addSample(rptr[i]); //с помощью флагов заботимся о том, //чтобы буффер успел заполниться дважды, //прежде чем его считает обработчик if (data->syncFlag == 'w') data->syncFlag = 'r'; else data->syncFlag = 'w'; return paContinue;}void BlackBox::errorHandler(PaError error){ ui->statusbar->showMessage(QString(Pa_GetErrorText(error)), 0);}void BlackBox::ctrlsDisable(){ ui->devBox->setDisabled(true); ui->startButton->setDisabled(true); ui->pathEdit->setDisabled(true); ui->pathButton->setDisabled(true);}bool BlackBox::initialize(){ int err = Pa_Initialize(); if(err != paNoError) { errorHandler(err); return false; } else { return queryAudioInputs(); }}bool BlackBox::queryAudioInputs(){ int numDevices = Pa_GetDeviceCount(); if (numDevices < 1) { ui->statusbar->showMessage(numDevices < 0 ? QString(Pa_GetErrorText(numDevices)) : "No input device found!", 0); return false; } else { for (int i = 0; i < numDevices; i++) { const PaDeviceInfo *devInfo; devInfo = Pa_GetDeviceInfo(i); if(devInfo->maxInputChannels > 0) ui->devBox->addItem(QString(devInfo->name), qVariantFromValue(i)); } return true; }}//функция запускает обработку данных с устройства записи по схеме// PortAudio -> Callback -> RingBuffer -> DSP -> indicatorvoid BlackBox::listen(int indx){ //создаём параметры входящего потокаы inputParameters.device = ui->devBox->itemData(indx).value<int>(); inputParameters.channelCount = NUM_CHANNELS; inputParameters.sampleFormat = PA_SAMPLE_SIZE; inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency; inputParameters.hostApiSpecificStreamInfo = nullptr; //Создаём структуру данных для обмена между потоками обработчика и Callback-функции DataShuttle sampleData; //Создаём кольцевой буффер sampleData.ringBuffer = new QRingBuffer<SAMPLE>(FRAMES_PER_BUFFER * RINGBUFFER_SIZE); //Устанавливаем флаг для пропуска 1 цикла записи в буффер без чтения sampleData.syncFlag = 's'; //запускаем поток PortAudio Pa_OpenStream(&inStream, &inputParameters, NULL, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, &recordCallBack, &sampleData); Pa_StartStream(inStream); //Запускаем обработчик данных emit startDSP(&sampleData);}void BlackBox::indicate(quint16 val){ ui->progressBar->setValue(val);}
rptr[i]
void addSample(T sample) //ЗДЕСЬ ВОЗНИКАЕТ ОШИБКА ЧТЕНИЯ ПАМЯТИ { if (++head >= bufferSize) head -= bufferSize; buffer[head] = sample; head++; }
void addSample(T sample) //ЗДЕСЬ ВОЗНИКАЕТ ОШИБКА ЧТЕНИЯ ПАМЯТИ { buffer[head] = sample; head = (head + 1) % bufferSize; }
void addSample(T sample) { if (++head >= bufferSize) head -= bufferSize; buffer[head] = sample; //ТЕПЕРЬ ЗДЕСЬ ВОЗНИКАЕТ ОШИБКА ЧТЕНИЯ ПАМЯТИ head++; }
void addSample(T sample) { buffer[head] = sample; head = (head + 1) % bufferSize; }
for (int i = 0; i < bufferLength; i++) qDebug() << rptr[i]; //data->ringBuffer->addSample(rptr[i]);
C++ (Qt)template<class T>class CRingBuffer {public: CRingBuffer( size_t size ) : buf(size + 1), first(0), next(0) { assert(size > 0); } void push_back( const T & val ) { buf[next] = val; next = (next + 1) % buf.size(); if (next == first) first = (first + 1) % buf.size(); } T pop_front( void ) { assert(first != next); T val = buf[first]; first = (first + 1) % buf.size(); return val; } size_t size( void ) const { return (next >= first) ? (next- first) : (buf.size() - (first - next)); } private:// data std::vector<T> buf; size_t first; // first written element index size_t next; // index of next element to write};