mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2026-04-19 06:42:43 +00:00
new modole system
This commit is contained in:
349
core/src/io/audio.h
Normal file
349
core/src/io/audio.h
Normal file
@@ -0,0 +1,349 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <fstream>
|
||||
#include <portaudio.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace io {
|
||||
class AudioSink {
|
||||
public:
|
||||
enum {
|
||||
MONO,
|
||||
STEREO,
|
||||
_TYPE_COUNT
|
||||
};
|
||||
|
||||
struct AudioDevice_t {
|
||||
std::string name;
|
||||
int index;
|
||||
int channels;
|
||||
std::vector<float> sampleRates;
|
||||
std::string txtSampleRates;
|
||||
};
|
||||
|
||||
AudioSink() {
|
||||
|
||||
}
|
||||
|
||||
AudioSink(int bufferSize) {
|
||||
_bufferSize = bufferSize;
|
||||
monoBuffer = new float[_bufferSize];
|
||||
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||
_volume = 1.0f;
|
||||
Pa_Initialize();
|
||||
|
||||
devTxtList = "";
|
||||
int devCount = Pa_GetDeviceCount();
|
||||
devIndex = Pa_GetDefaultOutputDevice();
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
PaStreamParameters outputParams;
|
||||
outputParams.sampleFormat = paFloat32;
|
||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||
for(int i = 0; i < devCount; i++) {
|
||||
deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (deviceInfo->maxOutputChannels < 1) {
|
||||
continue;
|
||||
}
|
||||
AudioDevice_t dev;
|
||||
dev.name = deviceInfo->name;
|
||||
dev.index = i;
|
||||
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
|
||||
dev.sampleRates.clear();
|
||||
dev.txtSampleRates = "";
|
||||
for (int j = 0; j < 6; j++) {
|
||||
outputParams.channelCount = dev.channels;
|
||||
outputParams.device = dev.index;
|
||||
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
|
||||
if (err != paFormatIsSupported) {
|
||||
continue;
|
||||
}
|
||||
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += '\0';
|
||||
}
|
||||
if (dev.sampleRates.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (i == devIndex) {
|
||||
devListIndex = devices.size();
|
||||
defaultDev = devListIndex;
|
||||
}
|
||||
devices.push_back(dev);
|
||||
deviceNames.push_back(deviceInfo->name);
|
||||
devTxtList += deviceInfo->name;
|
||||
devTxtList += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void init(int bufferSize) {
|
||||
_bufferSize = bufferSize;
|
||||
monoBuffer = new float[_bufferSize];
|
||||
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||
_volume = 1.0f;
|
||||
Pa_Initialize();
|
||||
|
||||
devTxtList = "";
|
||||
int devCount = Pa_GetDeviceCount();
|
||||
devIndex = Pa_GetDefaultOutputDevice();
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
PaStreamParameters outputParams;
|
||||
outputParams.sampleFormat = paFloat32;
|
||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||
for(int i = 0; i < devCount; i++) {
|
||||
deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (deviceInfo->maxOutputChannels < 1) {
|
||||
continue;
|
||||
}
|
||||
AudioDevice_t dev;
|
||||
dev.name = deviceInfo->name;
|
||||
dev.index = i;
|
||||
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
|
||||
dev.sampleRates.clear();
|
||||
dev.txtSampleRates = "";
|
||||
for (int j = 0; j < 6; j++) {
|
||||
outputParams.channelCount = dev.channels;
|
||||
outputParams.device = dev.index;
|
||||
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
|
||||
if (err != paFormatIsSupported) {
|
||||
continue;
|
||||
}
|
||||
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += '\0';
|
||||
}
|
||||
if (dev.sampleRates.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (i == devIndex) {
|
||||
devListIndex = devices.size();
|
||||
defaultDev = devListIndex;
|
||||
}
|
||||
devices.push_back(dev);
|
||||
deviceNames.push_back(deviceInfo->name);
|
||||
devTxtList += deviceInfo->name;
|
||||
devTxtList += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void setMonoInput(dsp::stream<float>* input) {
|
||||
_monoInput = input;
|
||||
}
|
||||
|
||||
void setStereoInput(dsp::stream<dsp::StereoFloat_t>* input) {
|
||||
_stereoInput = input;
|
||||
}
|
||||
|
||||
void setVolume(float volume) {
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
AudioDevice_t dev = devices[devListIndex];
|
||||
PaStreamParameters outputParams;
|
||||
deviceInfo = Pa_GetDeviceInfo(dev.index);
|
||||
outputParams.channelCount = 2;
|
||||
outputParams.sampleFormat = paFloat32;
|
||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||
outputParams.device = dev.index;
|
||||
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
||||
PaError err;
|
||||
if (streamType == MONO) {
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, 0,
|
||||
(dev.channels == 2) ? _mono_to_stereo_callback : _mono_to_mono_callback, this);
|
||||
}
|
||||
else {
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, 0,
|
||||
(dev.channels == 2) ? _stereo_to_stereo_callback : _stereo_to_mono_callback, this);
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
err = Pa_StartStream(stream);
|
||||
if (err != 0) {
|
||||
spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
spdlog::info("Audio device open.");
|
||||
running = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
if (streamType == MONO) {
|
||||
_monoInput->stopReader();
|
||||
}
|
||||
else {
|
||||
_stereoInput->stopReader();
|
||||
}
|
||||
Pa_StopStream(stream);
|
||||
Pa_CloseStream(stream);
|
||||
if (streamType == MONO) {
|
||||
_monoInput->clearReadStop();
|
||||
}
|
||||
else {
|
||||
_stereoInput->clearReadStop();
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
void setBlockSize(int blockSize) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
_bufferSize = blockSize;
|
||||
delete[] monoBuffer;
|
||||
delete[] stereoBuffer;
|
||||
monoBuffer = new float[_bufferSize];
|
||||
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setDevice(int id) {
|
||||
if (devListIndex == id) {
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
devListIndex = id;
|
||||
devIndex = devices[id].index;
|
||||
}
|
||||
|
||||
void setToDefault() {
|
||||
setDevice(defaultDev);
|
||||
}
|
||||
|
||||
int getDeviceId() {
|
||||
return devListIndex;
|
||||
}
|
||||
|
||||
void setStreamType(int type) {
|
||||
streamType = type;
|
||||
}
|
||||
|
||||
std::string devTxtList;
|
||||
std::vector<AudioDevice_t> devices;
|
||||
std::vector<std::string> deviceNames;
|
||||
|
||||
private:
|
||||
static int _mono_to_mono_callback(const void *input,
|
||||
void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
float* outbuf = (float*)output;
|
||||
if (_this->_monoInput->read(_this->monoBuffer, frameCount) < 0) {
|
||||
memset(outbuf, 0, sizeof(float) * frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
float vol = powf(_this->_volume, 2);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
outbuf[i] = _this->monoBuffer[i] * vol;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stereo_to_stereo_callback(const void *input,
|
||||
void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
|
||||
if (_this->_stereoInput->read(_this->stereoBuffer, frameCount) < 0) {
|
||||
memset(outbuf, 0, sizeof(dsp::StereoFloat_t) * frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note: Calculate the power in the UI instead of here
|
||||
|
||||
float vol = powf(_this->_volume, 2);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
outbuf[i].l = _this->stereoBuffer[i].l * vol;
|
||||
outbuf[i].r = _this->stereoBuffer[i].r * vol;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _mono_to_stereo_callback(const void *input,
|
||||
void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
|
||||
if (_this->_monoInput->read(_this->monoBuffer, frameCount) < 0) {
|
||||
memset(outbuf, 0, sizeof(dsp::StereoFloat_t) * frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
float vol = powf(_this->_volume, 2);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
outbuf[i].l = _this->monoBuffer[i] * vol;
|
||||
outbuf[i].r = _this->monoBuffer[i] * vol;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stereo_to_mono_callback(const void *input,
|
||||
void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
float* outbuf = (float*)output;
|
||||
if (_this->_stereoInput->read(_this->stereoBuffer, frameCount) < 0) {
|
||||
memset(outbuf, 0, sizeof(float) * frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note: Calculate the power in the UI instead of here
|
||||
|
||||
float vol = powf(_this->_volume, 2);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
outbuf[i] = ((_this->stereoBuffer[i].l + _this->stereoBuffer[i].r) / 2.0f) * vol;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float POSSIBLE_SAMP_RATE[6] = {
|
||||
48000.0f,
|
||||
44100.0f,
|
||||
24000.0f,
|
||||
22050.0f,
|
||||
12000.0f,
|
||||
11025.0f
|
||||
};
|
||||
|
||||
int streamType;
|
||||
int devIndex;
|
||||
int devListIndex;
|
||||
int defaultDev;
|
||||
float _sampleRate;
|
||||
int _bufferSize;
|
||||
dsp::stream<float>* _monoInput;
|
||||
dsp::stream<dsp::StereoFloat_t>* _stereoInput;
|
||||
float* monoBuffer;
|
||||
dsp::StereoFloat_t* stereoBuffer;
|
||||
float _volume = 1.0f;
|
||||
PaStream *stream;
|
||||
bool running = false;
|
||||
};
|
||||
};
|
||||
184
core/src/io/soapy.h
Normal file
184
core/src/io/soapy.h
Normal file
@@ -0,0 +1,184 @@
|
||||
#include <string>
|
||||
#include <SoapySDR/Device.hpp>
|
||||
#include <SoapySDR/Modules.hpp>
|
||||
#include <SoapySDR/Logger.hpp>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace io {
|
||||
class SoapyWrapper {
|
||||
public:
|
||||
SoapyWrapper() {
|
||||
|
||||
}
|
||||
|
||||
void init() {
|
||||
SoapySDR::registerLogHandler(_logHandler);
|
||||
//SoapySDR::Device::make("");
|
||||
|
||||
output.init(64000);
|
||||
currentGains = new float[1];
|
||||
refresh();
|
||||
if (devList.size() == 0) {
|
||||
dev = NULL;
|
||||
return;
|
||||
}
|
||||
setDevice(devList[0]);
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (devList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
dev = SoapySDR::Device::make(args);
|
||||
_stream = dev->setupStream(SOAPY_SDR_RX, "CF32");
|
||||
dev->activateStream(_stream);
|
||||
running = true;
|
||||
_workerThread = std::thread(_worker, this);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
running = false;
|
||||
dev->deactivateStream(_stream);
|
||||
dev->closeStream(_stream);
|
||||
_workerThread.join();
|
||||
SoapySDR::Device::unmake(dev);
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
devList = SoapySDR::Device::enumerate();
|
||||
txtDevList = "";
|
||||
devNameList.clear();
|
||||
if (devList.size() == 0) {
|
||||
txtDevList += '\0';
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
txtDevList += devList[i]["label"];
|
||||
txtDevList += '\0';
|
||||
devNameList.push_back(devList[i]["label"]);
|
||||
}
|
||||
}
|
||||
|
||||
void setDevice(SoapySDR::Kwargs devArgs) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
args = devArgs;
|
||||
dev = SoapySDR::Device::make(devArgs);
|
||||
txtSampleRateList = "";
|
||||
sampleRates = dev->listSampleRates(SOAPY_SDR_RX, 0);
|
||||
for (int i = 0; i < sampleRates.size(); i++) {
|
||||
txtSampleRateList += std::to_string((int)sampleRates[i]);
|
||||
txtSampleRateList += '\0';
|
||||
}
|
||||
_sampleRate = sampleRates[0];
|
||||
|
||||
gainList = dev->listGains(SOAPY_SDR_RX, 0);
|
||||
gainRanges.clear();
|
||||
if (gainList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
delete[] currentGains;
|
||||
currentGains = new float[gainList.size()];
|
||||
for (int i = 0; i < gainList.size(); i++) {
|
||||
gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i]));
|
||||
SoapySDR::Range rng = dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i]);
|
||||
|
||||
spdlog::info("{0}: {1} -> {2} (Step: {3})", gainList[i], rng.minimum(), rng.maximum(), rng.step());
|
||||
|
||||
currentGains[i] = rng.minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
if (running || dev == NULL) {
|
||||
return;
|
||||
}
|
||||
_sampleRate = sampleRate;
|
||||
dev->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
|
||||
}
|
||||
|
||||
void setFrequency(float freq) {
|
||||
if (dev == NULL) {
|
||||
return;
|
||||
}
|
||||
dev->setFrequency(SOAPY_SDR_RX, 0, freq);
|
||||
}
|
||||
|
||||
void setGain(int gainId, float gain) {
|
||||
if (dev == NULL) {
|
||||
return;
|
||||
}
|
||||
currentGains[gainId] = gain;
|
||||
dev->setGain(SOAPY_SDR_RX, 0, gainList[gainId], gain);
|
||||
}
|
||||
|
||||
bool isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
SoapySDR::KwargsList devList;
|
||||
std::vector<std::string> devNameList;
|
||||
std::string txtDevList;
|
||||
|
||||
std::vector<double> sampleRates;
|
||||
std::string txtSampleRateList;
|
||||
|
||||
std::vector<std::string> gainList;
|
||||
std::vector<SoapySDR::Range> gainRanges;
|
||||
float* currentGains;
|
||||
|
||||
dsp::stream<dsp::complex_t> output;
|
||||
|
||||
private:
|
||||
static void _worker(SoapyWrapper* _this) {
|
||||
int blockSize = _this->_sampleRate / 200.0f;
|
||||
dsp::complex_t* buf = new dsp::complex_t[blockSize];
|
||||
int flags = 0;
|
||||
long long timeMs = 0;
|
||||
|
||||
while (_this->running) {
|
||||
int res = _this->dev->readStream(_this->_stream, (void**)&buf, blockSize, flags, timeMs);
|
||||
if (res < 1) {
|
||||
continue;
|
||||
}
|
||||
_this->output.write(buf, res);
|
||||
}
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
static void _logHandler(const SoapySDRLogLevel lvl, const char* message) {
|
||||
if (lvl == SOAPY_SDR_FATAL || lvl == SOAPY_SDR_CRITICAL || lvl == SOAPY_SDR_ERROR) {
|
||||
spdlog::error(message);
|
||||
}
|
||||
else if (lvl == SOAPY_SDR_WARNING) {
|
||||
spdlog::warn(message);
|
||||
}
|
||||
else if (lvl == SOAPY_SDR_NOTICE | SOAPY_SDR_INFO) {
|
||||
spdlog::info(message);
|
||||
}
|
||||
}
|
||||
|
||||
SoapySDR::Kwargs args;
|
||||
SoapySDR::Device* dev;
|
||||
SoapySDR::Stream* _stream;
|
||||
std::thread _workerThread;
|
||||
bool running = false;
|
||||
float _sampleRate = 0;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user