287 lines
8.1 KiB
C++
Raw Normal View History

2020-11-12 00:53:38 +01:00
#include <imgui.h>
2020-12-22 20:00:51 +01:00
#include <module.h>
2020-11-12 00:53:38 +01:00
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
2020-11-22 18:26:48 +01:00
#include <portaudio.h>
#include <dsp/audio.h>
2020-12-06 17:57:44 +01:00
#include <spdlog/spdlog.h>
2020-11-12 00:53:38 +01:00
#define CONCAT(a, b) ((std::string(a) + b).c_str())
2020-12-08 04:36:37 +01:00
SDRPP_MOD_INFO {
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
2020-11-12 00:53:38 +01:00
class AudioSink : SinkManager::Sink {
public:
2020-11-22 18:26:48 +01:00
struct AudioDevice_t {
std::string name;
int index;
int channels;
2020-11-29 20:55:00 +01:00
int srId;
2020-11-22 18:26:48 +01:00
std::vector<double> sampleRates;
std::string txtSampleRates;
};
2020-11-29 20:55:00 +01:00
AudioSink(SinkManager::Stream* stream, std::string streamName) {
2020-11-12 00:53:38 +01:00
_stream = stream;
2020-11-29 20:55:00 +01:00
_streamName = streamName;
2020-11-30 05:51:33 +01:00
s2m.init(_stream->sinkOut);
2020-11-22 18:26:48 +01:00
monoRB.init(&s2m.out);
2020-11-30 05:51:33 +01:00
stereoRB.init(_stream->sinkOut);
2020-11-22 18:26:48 +01:00
// Initialize PortAudio
Pa_Initialize();
devCount = Pa_GetDeviceCount();
devId = Pa_GetDefaultOutputDevice();
const PaDeviceInfo *deviceInfo;
PaStreamParameters outputParams;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
// Gather hardware info
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 == devId) {
devListId = devices.size();
defaultDev = devListId;
}
2020-11-29 20:55:00 +01:00
dev.srId = 0;
2020-11-30 05:51:33 +01:00
AudioDevice_t* _dev = new AudioDevice_t;
*_dev = dev;
devices.push_back(_dev);
2020-11-22 18:26:48 +01:00
deviceNames.push_back(deviceInfo->name);
txtDevList += deviceInfo->name;
txtDevList += '\0';
}
2020-11-29 20:55:00 +01:00
// Load config from file
2020-11-12 00:53:38 +01:00
}
~AudioSink() {
2020-11-30 05:51:33 +01:00
for (auto const& dev : devices) {
delete dev;
}
2020-11-22 18:26:48 +01:00
}
void start() {
2020-11-29 20:55:00 +01:00
if (running) {
return;
}
doStart();
running = true;
2020-11-22 18:26:48 +01:00
}
void stop() {
2020-11-29 20:55:00 +01:00
if (!running) {
return;
}
doStop();
running = false;
2020-11-12 00:53:38 +01:00
}
void menuHandler() {
2020-11-30 05:51:33 +01:00
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
2020-11-29 20:55:00 +01:00
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devListId, txtDevList.c_str())) {
// TODO: Load SR from config
if (running) {
doStop();
doStart();
}
// TODO: Save to config
}
2020-11-30 05:51:33 +01:00
AudioDevice_t* dev = devices[devListId];
2020-11-12 00:53:38 +01:00
2020-11-30 05:51:33 +01:00
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) {
_stream->setSampleRate(dev->sampleRates[dev->srId]);
2020-11-29 20:55:00 +01:00
if (running) {
doStop();
doStart();
}
// TODO: Save to config
}
2020-11-12 00:53:38 +01:00
}
private:
2020-11-29 20:55:00 +01:00
void doStart() {
const PaDeviceInfo *deviceInfo;
2020-11-30 05:51:33 +01:00
AudioDevice_t* dev = devices[devListId];
2020-11-29 20:55:00 +01:00
PaStreamParameters outputParams;
2020-11-30 05:51:33 +01:00
deviceInfo = Pa_GetDeviceInfo(dev->index);
2020-11-29 20:55:00 +01:00
outputParams.channelCount = 2;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
2020-11-30 05:51:33 +01:00
outputParams.device = dev->index;
2020-11-29 20:55:00 +01:00
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
PaError err;
2020-11-30 05:51:33 +01:00
float sampleRate = dev->sampleRates[dev->srId];
2020-12-06 17:26:42 +01:00
int bufferSize = sampleRate / 60.0f;
2020-11-29 20:55:00 +01:00
2020-11-30 05:51:33 +01:00
if (dev->channels == 2) {
2020-12-04 20:12:36 +01:00
stereoRB.data.setMaxLatency(bufferSize * 2);
2020-11-30 05:51:33 +01:00
stereoRB.start();
2020-12-06 17:26:42 +01:00
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this);
2020-11-29 20:55:00 +01:00
}
else {
2020-12-04 20:12:36 +01:00
monoRB.data.setMaxLatency(bufferSize * 2);
2020-11-30 05:51:33 +01:00
monoRB.start();
2020-12-06 17:26:42 +01:00
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this);
2020-11-29 20:55:00 +01:00
}
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 doStop() {
2020-11-30 05:51:33 +01:00
monoRB.stop();
stereoRB.stop();
2020-11-29 20:55:00 +01:00
monoRB.data.stopReader();
stereoRB.data.stopReader();
Pa_StopStream(stream);
Pa_CloseStream(stream);
monoRB.data.clearReadStop();
stereoRB.data.clearReadStop();
}
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->monoRB.data.read((float*)output, frameCount);
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;
}
2020-11-22 18:26:48 +01:00
2020-11-12 00:53:38 +01:00
SinkManager::Stream* _stream;
2020-11-22 18:26:48 +01:00
dsp::StereoToMono s2m;
dsp::RingBufferSink<float> monoRB;
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
2020-11-29 20:55:00 +01:00
std::string _streamName;
PaStream *stream;
2020-11-22 18:26:48 +01:00
int srId = 0;
int devCount;
int devId = 0;
int devListId = 0;
int defaultDev = 0;
2020-11-29 20:55:00 +01:00
bool running = false;
2020-11-22 18:26:48 +01:00
const double POSSIBLE_SAMP_RATE[6] = {
48000.0f,
44100.0f,
24000.0f,
22050.0f,
12000.0f,
11025.0f
};
2020-11-30 05:51:33 +01:00
std::vector<AudioDevice_t*> devices;
2020-11-22 18:26:48 +01:00
std::vector<std::string> deviceNames;
std::string txtDevList;
2020-11-12 00:53:38 +01:00
};
2020-12-08 04:36:37 +01:00
class AudioSinkModule : public ModuleManager::Instance {
2020-11-12 00:53:38 +01:00
public:
AudioSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
}
2020-12-08 04:36:37 +01:00
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
2020-11-12 00:53:38 +01:00
private:
2020-11-29 20:55:00 +01:00
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
2020-11-12 00:53:38 +01:00
}
std::string name;
2020-12-08 04:36:37 +01:00
bool enabled = true;
2020-11-12 00:53:38 +01:00
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
// Nothing here
2020-11-29 20:55:00 +01:00
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
2020-11-12 00:53:38 +01:00
}
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
AudioSinkModule* instance = new AudioSinkModule(name);
return instance;
}
MOD_EXPORT void _DELETE_INSTANCE_() {
}
2020-12-08 04:36:37 +01:00
MOD_EXPORT void _END_() {
2020-11-12 00:53:38 +01:00
}