mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-25 02:18:30 +01:00
yeeted portaudio
This commit is contained in:
parent
72bd3e9cc1
commit
e4c062c9c2
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
run: sudo apt update
|
run: sudo apt update
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev portaudio19-dev libhackrf-dev librtlsdr-dev
|
run: sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
# Some projects don't allow in-source building, so create a separate build directory
|
# Some projects don't allow in-source building, so create a separate build directory
|
||||||
|
@ -12,9 +12,7 @@ option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Depedencies: libsd
|
|||||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON)
|
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON)
|
||||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" OFF)
|
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" OFF)
|
||||||
option(OPT_BUILD_RTL_SDR_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
|
option(OPT_BUILD_RTL_SDR_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
|
||||||
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: portaudio)" ON)
|
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON)
|
||||||
option(OPT_BUILD_RTAUDIO_SINK "Build RtAudio Sink Module (Depedencies: rtaudio)" OFF)
|
|
||||||
|
|
||||||
# Core of SDR++
|
# Core of SDR++
|
||||||
add_subdirectory("core")
|
add_subdirectory("core")
|
||||||
|
|
||||||
@ -64,10 +62,6 @@ if (OPT_BUILD_AUDIO_SINK)
|
|||||||
add_subdirectory("audio_sink")
|
add_subdirectory("audio_sink")
|
||||||
endif (OPT_BUILD_AUDIO_SINK)
|
endif (OPT_BUILD_AUDIO_SINK)
|
||||||
|
|
||||||
if (OPT_BUILD_RTAUDIO_SINK)
|
|
||||||
add_subdirectory("rtaudio_sink")
|
|
||||||
endif (OPT_BUILD_RTAUDIO_SINK)
|
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||||
else()
|
else()
|
||||||
|
@ -16,18 +16,23 @@ target_link_libraries(audio_sink PRIVATE sdrpp_core)
|
|||||||
set_target_properties(audio_sink PROPERTIES PREFIX "")
|
set_target_properties(audio_sink PROPERTIES PREFIX "")
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
find_package(portaudio CONFIG REQUIRED)
|
# Lib path
|
||||||
target_link_libraries(sdrpp_core PUBLIC portaudio)
|
target_link_directories(sdrpp_core PUBLIC "C:/Program Files (x86)/RtAudio/lib")
|
||||||
|
|
||||||
|
# Misc headers
|
||||||
|
target_include_directories(sdrpp_core PUBLIC "C:/Program Files (x86)/RtAudio/include/rtaudio")
|
||||||
|
|
||||||
|
target_link_libraries(sdrpp_core PUBLIC rtaudio)
|
||||||
else (MSVC)
|
else (MSVC)
|
||||||
find_package(PkgConfig)
|
find_package(PkgConfig)
|
||||||
|
|
||||||
pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0)
|
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
|
||||||
|
|
||||||
target_include_directories(sdrpp_core PUBLIC ${PORTAUDIO_INCLUDE_DIRS})
|
target_include_directories(sdrpp_core PUBLIC ${RTAUDIO_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_directories(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARY_DIRS})
|
target_link_directories(sdrpp_core PUBLIC ${RTAUDIO_LIBRARY_DIRS})
|
||||||
|
|
||||||
target_link_libraries(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARIES})
|
target_link_libraries(sdrpp_core PUBLIC ${RTAUDIO_LIBRARIES})
|
||||||
|
|
||||||
endif (MSVC)
|
endif (MSVC)
|
||||||
|
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <signal_path/signal_path.h>
|
#include <signal_path/signal_path.h>
|
||||||
#include <signal_path/sink.h>
|
#include <signal_path/sink.h>
|
||||||
#include <portaudio.h>
|
|
||||||
#include <dsp/audio.h>
|
#include <dsp/audio.h>
|
||||||
#include <dsp/processing.h>
|
#include <dsp/processing.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <RtAudio.h>
|
||||||
|
#include <config.h>
|
||||||
|
#include <options.h>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@ -18,85 +20,46 @@ SDRPP_MOD_INFO {
|
|||||||
/* Max instances */ 1
|
/* Max instances */ 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ConfigManager config;
|
||||||
|
|
||||||
class AudioSink : SinkManager::Sink {
|
class AudioSink : SinkManager::Sink {
|
||||||
public:
|
public:
|
||||||
struct AudioDevice_t {
|
|
||||||
std::string name;
|
|
||||||
int index;
|
|
||||||
int channels;
|
|
||||||
int srId;
|
|
||||||
std::vector<double> sampleRates;
|
|
||||||
std::string txtSampleRates;
|
|
||||||
};
|
|
||||||
|
|
||||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
_streamName = streamName;
|
_streamName = streamName;
|
||||||
s2m.init(_stream->sinkOut);
|
s2m.init(_stream->sinkOut);
|
||||||
monoRB.init(&s2m.out);
|
monoPacker.init(&s2m.out, 512);
|
||||||
stereoRB.init(_stream->sinkOut);
|
stereoPacker.init(_stream->sinkOut, 512);
|
||||||
|
|
||||||
// monoPacker.init(&s2m.out, 240);
|
bool created = false;
|
||||||
// stereoPacker.init(_stream->sinkOut, 240);
|
std::string device = "";
|
||||||
|
config.aquire();
|
||||||
|
if (!config.conf.contains(_streamName)) {
|
||||||
|
created = true;
|
||||||
|
config.conf[_streamName]["device"] = "";
|
||||||
|
config.conf[_streamName]["devices"] = json({});
|
||||||
|
}
|
||||||
|
device = config.conf[_streamName]["device"];
|
||||||
|
config.release(created);
|
||||||
|
|
||||||
// Initialize PortAudio
|
int count = audio.getDeviceCount();
|
||||||
devCount = Pa_GetDeviceCount();
|
RtAudio::DeviceInfo info;
|
||||||
devId = Pa_GetDefaultOutputDevice();
|
for (int i = 0; i < count; i++) {
|
||||||
const PaDeviceInfo *deviceInfo;
|
info = audio.getDeviceInfo(i);
|
||||||
PaStreamParameters outputParams;
|
if (!info.probed) { continue; }
|
||||||
outputParams.sampleFormat = paFloat32;
|
if (info.outputChannels == 0) { continue; }
|
||||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
||||||
|
devList.push_back(info);
|
||||||
// Gather hardware info
|
deviceIds.push_back(i);
|
||||||
for(int i = 0; i < devCount; i++) {
|
txtDevList += info.name;
|
||||||
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 == devId) {
|
|
||||||
devListId = devices.size();
|
|
||||||
defaultDev = devListId;
|
|
||||||
_stream->setSampleRate(dev.sampleRates[0]);
|
|
||||||
}
|
|
||||||
dev.srId = 0;
|
|
||||||
|
|
||||||
AudioDevice_t* _dev = new AudioDevice_t;
|
|
||||||
*_dev = dev;
|
|
||||||
devices.push_back(_dev);
|
|
||||||
|
|
||||||
deviceNames.push_back(deviceInfo->name);
|
|
||||||
txtDevList += deviceInfo->name;
|
|
||||||
txtDevList += '\0';
|
txtDevList += '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load config from file
|
selectByName(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
~AudioSink() {
|
~AudioSink() {
|
||||||
for (auto const& dev : devices) {
|
|
||||||
delete dev;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
@ -114,162 +77,155 @@ public:
|
|||||||
doStop();
|
doStop();
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectFirst() {
|
||||||
|
selectById(defaultDevId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectByName(std::string name) {
|
||||||
|
for (int i = 0; i < devList.size(); i++) {
|
||||||
|
if (devList[i].name == name) {
|
||||||
|
selectById(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectById(int id) {
|
||||||
|
devId = id;
|
||||||
|
bool created = false;
|
||||||
|
config.aquire();
|
||||||
|
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
|
||||||
|
created = true;
|
||||||
|
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
|
||||||
|
}
|
||||||
|
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
|
||||||
|
config.release(created);
|
||||||
|
|
||||||
|
sampleRates = devList[id].sampleRates;
|
||||||
|
sampleRatesTxt = "";
|
||||||
|
char buf[256];
|
||||||
|
bool found = false;
|
||||||
|
unsigned int defaultId = 0;
|
||||||
|
unsigned int defaultSr = devList[id].preferredSampleRate;
|
||||||
|
for (int i = 0; i < sampleRates.size(); i++) {
|
||||||
|
if (sampleRates[i] == sampleRate) {
|
||||||
|
found = true;
|
||||||
|
srId = i;
|
||||||
|
}
|
||||||
|
if (sampleRates[i] == defaultSr) {
|
||||||
|
defaultId = i;
|
||||||
|
}
|
||||||
|
sprintf(buf, "%d", sampleRates[i]);
|
||||||
|
sampleRatesTxt += buf;
|
||||||
|
sampleRatesTxt += '\0';
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
sampleRate = defaultSr;
|
||||||
|
srId = defaultId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream->setSampleRate(sampleRate);
|
||||||
|
|
||||||
|
if (running) { doStop(); }
|
||||||
|
if (running) { doStart(); }
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler() {
|
void menuHandler() {
|
||||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
ImGui::SetNextItemWidth(menuWidth);
|
||||||
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devListId, txtDevList.c_str())) {
|
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devId, txtDevList.c_str())) {
|
||||||
// TODO: Load SR from config
|
selectById(devId);
|
||||||
if (running) {
|
config.aquire();
|
||||||
doStop();
|
config.conf[_streamName]["device"] = devList[devId].name;
|
||||||
doStart();
|
config.release(true);
|
||||||
}
|
|
||||||
// TODO: Save to config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioDevice_t* dev = devices[devListId];
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
ImGui::SetNextItemWidth(menuWidth);
|
||||||
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) {
|
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
||||||
_stream->setSampleRate(dev->sampleRates[dev->srId]);
|
sampleRate = sampleRates[srId];
|
||||||
|
_stream->setSampleRate(sampleRate);
|
||||||
if (running) {
|
if (running) {
|
||||||
doStop();
|
doStop();
|
||||||
doStart();
|
doStart();
|
||||||
}
|
}
|
||||||
// TODO: Save to config
|
config.aquire();
|
||||||
|
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
|
||||||
|
config.release(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void doStart() {
|
void doStart() {
|
||||||
const PaDeviceInfo *deviceInfo;
|
RtAudio::StreamParameters parameters;
|
||||||
AudioDevice_t* dev = devices[devListId];
|
parameters.deviceId = deviceIds[devId];
|
||||||
PaStreamParameters outputParams;
|
parameters.nChannels = 2;
|
||||||
deviceInfo = Pa_GetDeviceInfo(dev->index);
|
unsigned int bufferFrames = sampleRate / 60;
|
||||||
outputParams.channelCount = 2;
|
RtAudio::StreamOptions opts;
|
||||||
outputParams.sampleFormat = paFloat32;
|
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
|
||||||
outputParams.device = dev->index;
|
|
||||||
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
|
||||||
PaError err;
|
|
||||||
|
|
||||||
float sampleRate = dev->sampleRates[dev->srId];
|
stereoPacker.setSampleCount(bufferFrames);
|
||||||
int bufferSize = sampleRate / 60.0f;
|
|
||||||
|
|
||||||
if (dev->channels == 2) {
|
try {
|
||||||
stereoRB.data.setMaxLatency(bufferSize * 2);
|
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
||||||
stereoRB.start();
|
audio.startStream();
|
||||||
// stereoPacker.setSampleCount(bufferSize);
|
stereoPacker.start();
|
||||||
// stereoPacker.start();
|
|
||||||
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this);
|
|
||||||
//err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, bufferSize, 0, _stereo_cb, this);
|
|
||||||
}
|
}
|
||||||
else {
|
catch ( RtAudioError& e ) {
|
||||||
monoRB.data.setMaxLatency(bufferSize * 2);
|
spdlog::error("Could not open audio device");
|
||||||
monoRB.start();
|
|
||||||
// stereoPacker.setSampleCount(bufferSize);
|
|
||||||
// monoPacker.start();
|
|
||||||
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this);
|
|
||||||
//err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, bufferSize, 0, _mono_cb, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != 0) {
|
|
||||||
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Pa_StartStream(stream);
|
spdlog::info("RtAudio stream open");
|
||||||
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 doStop() {
|
void doStop() {
|
||||||
s2m.stop();
|
s2m.stop();
|
||||||
monoRB.stop();
|
monoPacker.stop();
|
||||||
stereoRB.stop();
|
stereoPacker.stop();
|
||||||
// monoPacker.stop();
|
monoPacker.out.stopReader();
|
||||||
// stereoPacker.stop();
|
stereoPacker.out.stopReader();
|
||||||
monoRB.data.stopReader();
|
audio.stopStream();
|
||||||
stereoRB.data.stopReader();
|
audio.closeStream();
|
||||||
// monoPacker.out.stopReader();
|
monoPacker.out.clearReadStop();
|
||||||
// stereoPacker.out.stopReader();
|
stereoPacker.out.clearReadStop();
|
||||||
Pa_StopStream(stream);
|
|
||||||
Pa_CloseStream(stream);
|
|
||||||
monoRB.data.clearReadStop();
|
|
||||||
stereoRB.data.clearReadStop();
|
|
||||||
// monoPacker.out.clearReadStop();
|
|
||||||
// stereoPacker.out.clearWriteStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
|
static int callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) {
|
||||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
|
||||||
AudioSink* _this = (AudioSink*)userData;
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
_this->monoRB.data.read((float*)output, frameCount);
|
int count = _this->stereoPacker.out.read();
|
||||||
|
if (count < 0) { return 0; }
|
||||||
|
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
|
||||||
|
_this->stereoPacker.out.flush();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _stereo_cb(const void *input, void *output, unsigned long frameCount,
|
|
||||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
|
||||||
AudioSink* _this = (AudioSink*)userData;
|
|
||||||
_this->stereoRB.data.read((dsp::stereo_t*)output, frameCount);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static int _mono_cb(const void *input, void *output, unsigned long frameCount,
|
|
||||||
// const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
|
||||||
// AudioSink* _this = (AudioSink*)userData;
|
|
||||||
// if (_this->monoPacker.out.read() < 0) { return 0; }
|
|
||||||
// memcpy((float*)output, _this->monoPacker.out.readBuf, frameCount * sizeof(float));
|
|
||||||
// _this->monoPacker.out.flush();
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static int _stereo_cb(const void *input, void *output, unsigned long frameCount,
|
|
||||||
// const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
|
||||||
// AudioSink* _this = (AudioSink*)userData;
|
|
||||||
// if (_this->stereoPacker.out.read() < 0) { spdlog::warn("CB killed"); return 0; }
|
|
||||||
// memcpy((dsp::stereo_t*)output, _this->stereoPacker.out.readBuf, frameCount * sizeof(dsp::stereo_t));
|
|
||||||
// _this->stereoPacker.out.flush();
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
SinkManager::Stream* _stream;
|
SinkManager::Stream* _stream;
|
||||||
dsp::StereoToMono s2m;
|
dsp::StereoToMono s2m;
|
||||||
dsp::RingBufferSink<float> monoRB;
|
dsp::Packer<float> monoPacker;
|
||||||
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
|
dsp::Packer<dsp::stereo_t> stereoPacker;
|
||||||
|
|
||||||
// dsp::Packer<float> monoPacker;
|
|
||||||
// dsp::Packer<dsp::stereo_t> stereoPacker;
|
|
||||||
|
|
||||||
std::string _streamName;
|
std::string _streamName;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
int srId = 0;
|
int srId = 0;
|
||||||
int devCount;
|
int devCount;
|
||||||
int devId = 0;
|
int devId = 0;
|
||||||
int devListId = 0;
|
|
||||||
int defaultDev = 0;
|
|
||||||
bool running = false;
|
bool running = false;
|
||||||
|
|
||||||
const double POSSIBLE_SAMP_RATE[6] = {
|
unsigned int defaultDevId = 0;
|
||||||
48000.0f,
|
|
||||||
44100.0f,
|
|
||||||
24000.0f,
|
|
||||||
22050.0f,
|
|
||||||
12000.0f,
|
|
||||||
11025.0f
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<AudioDevice_t*> devices;
|
std::vector<RtAudio::DeviceInfo> devList;
|
||||||
std::vector<std::string> deviceNames;
|
std::vector<unsigned int> deviceIds;
|
||||||
std::string txtDevList;
|
std::string txtDevList;
|
||||||
|
|
||||||
|
std::vector<unsigned int> sampleRates;
|
||||||
|
std::string sampleRatesTxt;
|
||||||
|
unsigned int sampleRate = 48000;
|
||||||
|
|
||||||
|
RtAudio audio;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioSinkModule : public ModuleManager::Instance {
|
class AudioSinkModule : public ModuleManager::Instance {
|
||||||
@ -279,13 +235,11 @@ public:
|
|||||||
provider.create = create_sink;
|
provider.create = create_sink;
|
||||||
provider.ctx = this;
|
provider.ctx = this;
|
||||||
|
|
||||||
Pa_Initialize();
|
|
||||||
|
|
||||||
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
~AudioSinkModule() {
|
~AudioSinkModule() {
|
||||||
Pa_Terminate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable() {
|
void enable() {
|
||||||
@ -312,8 +266,10 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
// Nothing here
|
json def = json({});
|
||||||
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
|
config.setPath(options::opts.root + "/audio_sink_config.json");
|
||||||
|
config.load(def);
|
||||||
|
config.enableAutoSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
||||||
@ -322,7 +278,8 @@ MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _DELETE_INSTANCE_() {
|
MOD_EXPORT void _DELETE_INSTANCE_() {
|
||||||
|
config.disableAutoSave();
|
||||||
|
config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _END_() {
|
MOD_EXPORT void _END_() {
|
||||||
|
@ -35,7 +35,7 @@ Download the latest release from [the Releases page](https://github.com/Alexandr
|
|||||||
|
|
||||||
Then, run:
|
Then, run:
|
||||||
```sh
|
```sh
|
||||||
sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev portaudio19-dev libhackrf-dev
|
sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev
|
||||||
sudo dpkg -i sdrpp_debian_amd64.deb
|
sudo dpkg -i sdrpp_debian_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -52,10 +52,10 @@ TODO
|
|||||||
* cmake
|
* cmake
|
||||||
* vcpkg
|
* vcpkg
|
||||||
* PothosSDR (This will install libraires for most SDRs)
|
* PothosSDR (This will install libraires for most SDRs)
|
||||||
|
* rtaudio
|
||||||
|
|
||||||
After this, install the following depencies using vcpkg:
|
After this, install the following depencies using vcpkg:
|
||||||
* fftw3
|
* fftw3
|
||||||
* portaudio
|
|
||||||
* glfw
|
* glfw
|
||||||
* glew
|
* glew
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ Next install dependencies based on the modules you wish to build:
|
|||||||
* soapy_source: SoapySDR + drivers for each SDRs (see SoapySDR docs)
|
* soapy_source: SoapySDR + drivers for each SDRs (see SoapySDR docs)
|
||||||
* airspyhf_source: libairspyhf
|
* airspyhf_source: libairspyhf
|
||||||
* plutosdr_source: libiio, libad9361
|
* plutosdr_source: libiio, libad9361
|
||||||
* audio_sink: portaudio
|
* audio_sink: librtaudio-dev
|
||||||
|
|
||||||
Note: make sure you're using GCC 8 or later as older versions do not have `std::filesystem` built-in.
|
Note: make sure you're using GCC 8 or later as older versions do not have `std::filesystem` built-in.
|
||||||
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
|
||||||
project(rtaudio_sink)
|
|
||||||
|
|
||||||
if (MSVC)
|
|
||||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
|
||||||
else()
|
|
||||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
|
||||||
endif (MSVC)
|
|
||||||
|
|
||||||
file(GLOB SRC "src/*.cpp")
|
|
||||||
|
|
||||||
include_directories("src/")
|
|
||||||
|
|
||||||
add_library(rtaudio_sink SHARED ${SRC})
|
|
||||||
target_link_libraries(rtaudio_sink PRIVATE sdrpp_core)
|
|
||||||
set_target_properties(rtaudio_sink PROPERTIES PREFIX "")
|
|
||||||
|
|
||||||
if (MSVC)
|
|
||||||
# Lib path
|
|
||||||
target_link_directories(sdrpp_core PUBLIC "C:/Program Files (x86)/RtAudio/lib")
|
|
||||||
|
|
||||||
# Misc headers
|
|
||||||
target_include_directories(sdrpp_core PUBLIC "C:/Program Files (x86)/RtAudio/include/rtaudio")
|
|
||||||
|
|
||||||
target_link_libraries(sdrpp_core PUBLIC rtaudio)
|
|
||||||
else (MSVC)
|
|
||||||
find_package(PkgConfig)
|
|
||||||
|
|
||||||
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
|
|
||||||
|
|
||||||
target_include_directories(sdrpp_core PUBLIC ${RTAUDIO_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
target_link_directories(sdrpp_core PUBLIC ${RTAUDIO_LIBRARY_DIRS})
|
|
||||||
|
|
||||||
target_link_libraries(sdrpp_core PUBLIC ${RTAUDIO_LIBRARIES})
|
|
||||||
|
|
||||||
endif (MSVC)
|
|
||||||
|
|
||||||
# Install directives
|
|
||||||
install(TARGETS rtaudio_sink DESTINATION lib/sdrpp/plugins)
|
|
@ -1,287 +0,0 @@
|
|||||||
#include <imgui.h>
|
|
||||||
#include <module.h>
|
|
||||||
#include <gui/gui.h>
|
|
||||||
#include <signal_path/signal_path.h>
|
|
||||||
#include <signal_path/sink.h>
|
|
||||||
#include <dsp/audio.h>
|
|
||||||
#include <dsp/processing.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <RtAudio.h>
|
|
||||||
#include <config.h>
|
|
||||||
#include <options.h>
|
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
|
||||||
|
|
||||||
SDRPP_MOD_INFO {
|
|
||||||
/* Name: */ "rtaudio_sink",
|
|
||||||
/* Description: */ "RtAudio sink module for SDR++",
|
|
||||||
/* Author: */ "Ryzerth",
|
|
||||||
/* Version: */ 0, 1, 0,
|
|
||||||
/* Max instances */ 1
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigManager config;
|
|
||||||
|
|
||||||
class AudioSink : SinkManager::Sink {
|
|
||||||
public:
|
|
||||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
|
||||||
_stream = stream;
|
|
||||||
_streamName = streamName;
|
|
||||||
s2m.init(_stream->sinkOut);
|
|
||||||
monoPacker.init(&s2m.out, 512);
|
|
||||||
stereoPacker.init(_stream->sinkOut, 512);
|
|
||||||
|
|
||||||
bool created = false;
|
|
||||||
std::string device = "";
|
|
||||||
config.aquire();
|
|
||||||
if (!config.conf.contains(_streamName)) {
|
|
||||||
created = true;
|
|
||||||
config.conf[_streamName]["device"] = "";
|
|
||||||
config.conf[_streamName]["devices"] = json({});
|
|
||||||
}
|
|
||||||
device = config.conf[_streamName]["device"];
|
|
||||||
config.release(created);
|
|
||||||
|
|
||||||
int count = audio.getDeviceCount();
|
|
||||||
RtAudio::DeviceInfo info;
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
info = audio.getDeviceInfo(i);
|
|
||||||
if (!info.probed) { continue; }
|
|
||||||
if (info.outputChannels == 0) { continue; }
|
|
||||||
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
|
||||||
devList.push_back(info);
|
|
||||||
deviceIds.push_back(i);
|
|
||||||
txtDevList += info.name;
|
|
||||||
txtDevList += '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
selectByName(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AudioSink() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
if (running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doStart();
|
|
||||||
running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
if (!running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doStop();
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectFirst() {
|
|
||||||
selectById(defaultDevId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectByName(std::string name) {
|
|
||||||
for (int i = 0; i < devList.size(); i++) {
|
|
||||||
if (devList[i].name == name) {
|
|
||||||
selectById(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectById(int id) {
|
|
||||||
devId = id;
|
|
||||||
bool created = false;
|
|
||||||
config.aquire();
|
|
||||||
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
|
|
||||||
created = true;
|
|
||||||
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
|
|
||||||
}
|
|
||||||
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
|
|
||||||
config.release(created);
|
|
||||||
|
|
||||||
sampleRates = devList[id].sampleRates;
|
|
||||||
sampleRatesTxt = "";
|
|
||||||
char buf[256];
|
|
||||||
bool found = false;
|
|
||||||
unsigned int defaultId = 0;
|
|
||||||
unsigned int defaultSr = devList[id].preferredSampleRate;
|
|
||||||
for (int i = 0; i < sampleRates.size(); i++) {
|
|
||||||
if (sampleRates[i] == sampleRate) {
|
|
||||||
found = true;
|
|
||||||
srId = i;
|
|
||||||
}
|
|
||||||
if (sampleRates[i] == defaultSr) {
|
|
||||||
defaultId = i;
|
|
||||||
}
|
|
||||||
sprintf(buf, "%d", sampleRates[i]);
|
|
||||||
sampleRatesTxt += buf;
|
|
||||||
sampleRatesTxt += '\0';
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
sampleRate = defaultSr;
|
|
||||||
srId = defaultId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_stream->setSampleRate(sampleRate);
|
|
||||||
|
|
||||||
if (running) { doStop(); }
|
|
||||||
if (running) { doStart(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void menuHandler() {
|
|
||||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
|
||||||
if (ImGui::Combo(("##_rtaudio_sink_dev_"+_streamName).c_str(), &devId, txtDevList.c_str())) {
|
|
||||||
selectById(devId);
|
|
||||||
config.aquire();
|
|
||||||
config.conf[_streamName]["device"] = devList[devId].name;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
|
||||||
if (ImGui::Combo(("##_rtaudio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
|
||||||
sampleRate = sampleRates[srId];
|
|
||||||
_stream->setSampleRate(sampleRate);
|
|
||||||
if (running) {
|
|
||||||
doStop();
|
|
||||||
doStart();
|
|
||||||
}
|
|
||||||
config.aquire();
|
|
||||||
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void doStart() {
|
|
||||||
RtAudio::StreamParameters parameters;
|
|
||||||
parameters.deviceId = deviceIds[devId];
|
|
||||||
parameters.nChannels = 2;
|
|
||||||
unsigned int bufferFrames = sampleRate / 60;
|
|
||||||
RtAudio::StreamOptions opts;
|
|
||||||
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
|
|
||||||
|
|
||||||
stereoPacker.setSampleCount(bufferFrames);
|
|
||||||
|
|
||||||
try {
|
|
||||||
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
|
||||||
audio.startStream();
|
|
||||||
stereoPacker.start();
|
|
||||||
}
|
|
||||||
catch ( RtAudioError& e ) {
|
|
||||||
spdlog::error("Could not open audio device");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("RtAudio stream open");
|
|
||||||
}
|
|
||||||
|
|
||||||
void doStop() {
|
|
||||||
s2m.stop();
|
|
||||||
monoPacker.stop();
|
|
||||||
stereoPacker.stop();
|
|
||||||
monoPacker.out.stopReader();
|
|
||||||
stereoPacker.out.stopReader();
|
|
||||||
audio.stopStream();
|
|
||||||
audio.closeStream();
|
|
||||||
monoPacker.out.clearReadStop();
|
|
||||||
stereoPacker.out.clearReadStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) {
|
|
||||||
AudioSink* _this = (AudioSink*)userData;
|
|
||||||
int count = _this->stereoPacker.out.read();
|
|
||||||
if (count < 0) { return 0; }
|
|
||||||
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
|
|
||||||
_this->stereoPacker.out.flush();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SinkManager::Stream* _stream;
|
|
||||||
dsp::StereoToMono s2m;
|
|
||||||
dsp::Packer<float> monoPacker;
|
|
||||||
dsp::Packer<dsp::stereo_t> stereoPacker;
|
|
||||||
|
|
||||||
std::string _streamName;
|
|
||||||
|
|
||||||
int srId = 0;
|
|
||||||
int devCount;
|
|
||||||
int devId = 0;
|
|
||||||
bool running = false;
|
|
||||||
|
|
||||||
unsigned int defaultDevId = 0;
|
|
||||||
|
|
||||||
std::vector<RtAudio::DeviceInfo> devList;
|
|
||||||
std::vector<unsigned int> deviceIds;
|
|
||||||
std::string txtDevList;
|
|
||||||
|
|
||||||
std::vector<unsigned int> sampleRates;
|
|
||||||
std::string sampleRatesTxt;
|
|
||||||
unsigned int sampleRate = 48000;
|
|
||||||
|
|
||||||
RtAudio audio;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class AudioSinkModule : public ModuleManager::Instance {
|
|
||||||
public:
|
|
||||||
AudioSinkModule(std::string name) {
|
|
||||||
this->name = name;
|
|
||||||
provider.create = create_sink;
|
|
||||||
provider.ctx = this;
|
|
||||||
|
|
||||||
sigpath::sinkManager.registerSinkProvider("RtAudio", provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AudioSinkModule() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void enable() {
|
|
||||||
enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void disable() {
|
|
||||||
enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEnabled() {
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
|
||||||
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
bool enabled = true;
|
|
||||||
SinkManager::SinkProvider provider;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
|
||||||
json def = json({});
|
|
||||||
config.setPath(options::opts.root + "/audio_sink_config.json");
|
|
||||||
config.load(def);
|
|
||||||
config.enableAutoSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
|
||||||
AudioSinkModule* instance = new AudioSinkModule(name);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT void _DELETE_INSTANCE_() {
|
|
||||||
config.disableAutoSave();
|
|
||||||
config.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT void _END_() {
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user