mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-14 12:17:11 +01:00
299 lines
8.6 KiB
C++
299 lines
8.6 KiB
C++
#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: */ "audio_sink",
|
|
/* Description: */ "Audio 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.acquire();
|
|
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.acquire();
|
|
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(("##_audio_sink_dev_"+_streamName).c_str(), &devId, txtDevList.c_str())) {
|
|
selectById(devId);
|
|
config.acquire();
|
|
config.conf[_streamName]["device"] = devList[devId].name;
|
|
config.release(true);
|
|
}
|
|
|
|
ImGui::SetNextItemWidth(menuWidth);
|
|
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
|
sampleRate = sampleRates[srId];
|
|
_stream->setSampleRate(sampleRate);
|
|
if (running) {
|
|
doStop();
|
|
doStart();
|
|
}
|
|
config.acquire();
|
|
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;
|
|
opts.streamName = _streamName;
|
|
|
|
try {
|
|
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
|
stereoPacker.setSampleCount(bufferFrames);
|
|
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; }
|
|
|
|
// For debug purposes only...
|
|
// if (nBufferFrames != count) { spdlog::warn("Buffer size mismatch, wanted {0}, was asked for {1}", count, nBufferFrames); }
|
|
// for (int i = 0; i < count; i++) {
|
|
// if (_this->stereoPacker.out.readBuf[i].l == NAN || _this->stereoPacker.out.readBuf[i].r == NAN) { spdlog::error("NAN in audio data"); }
|
|
// if (_this->stereoPacker.out.readBuf[i].l == INFINITY || _this->stereoPacker.out.readBuf[i].r == INFINITY) { spdlog::error("INFINITY in audio data"); }
|
|
// if (_this->stereoPacker.out.readBuf[i].l == -INFINITY || _this->stereoPacker.out.readBuf[i].r == -INFINITY) { spdlog::error("-INFINITY in audio data"); }
|
|
// }
|
|
|
|
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("Audio", provider);
|
|
}
|
|
|
|
~AudioSinkModule() {
|
|
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
|
sigpath::sinkManager.unregisterSinkProvider("Audio");
|
|
}
|
|
|
|
void postInit() {}
|
|
|
|
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_(void* instance) {
|
|
delete (AudioSinkModule*)instance;
|
|
}
|
|
|
|
MOD_EXPORT void _END_() {
|
|
config.disableAutoSave();
|
|
config.save();
|
|
} |