yeeted portaudio

This commit is contained in:
Ryzerth 2021-02-23 00:26:35 +01:00
parent 72bd3e9cc1
commit e4c062c9c2
7 changed files with 155 additions and 526 deletions

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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(&parameters, 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_() {

View File

@ -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.

View File

@ -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)

View File

@ -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(&parameters, 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_() {
}