SDRPlusPlus/sink_modules/audio_sink/src/main.cpp

321 wiersze
9.5 KiB
C++
Czysty Zwykły widok Historia

2020-11-11 23:53:38 +00:00
#include <imgui.h>
2020-12-22 19:00:51 +00:00
#include <module.h>
2020-11-11 23:53:38 +00:00
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
2022-06-15 14:08:28 +00:00
#include <dsp/buffer/packer.h>
#include <dsp/convert/stereo_to_mono.h>
#include <utils/flog.h>
2021-02-22 23:26:35 +00:00
#include <RtAudio.h>
#include <config.h>
2022-02-24 20:24:46 +00:00
#include <core.h>
2020-11-11 23:53:38 +00:00
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
2020-12-08 03:36:37 +00:00
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
2021-02-22 23:26:35 +00:00
ConfigManager config;
2020-11-11 23:53:38 +00:00
class AudioSink : SinkManager::Sink {
public:
2020-11-29 19:55:00 +00:00
AudioSink(SinkManager::Stream* stream, std::string streamName) {
2020-11-11 23:53:38 +00:00
_stream = stream;
2020-11-29 19:55:00 +00:00
_streamName = streamName;
2020-11-30 04:51:33 +00:00
s2m.init(_stream->sinkOut);
2021-02-22 23:26:35 +00:00
monoPacker.init(&s2m.out, 512);
stereoPacker.init(_stream->sinkOut, 512);
#if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&reportErrorsAsException);
#endif
2021-02-22 23:26:35 +00:00
bool created = false;
std::string device = "";
config.acquire();
2021-02-22 23:26:35 +00:00
if (!config.conf.contains(_streamName)) {
created = true;
config.conf[_streamName]["device"] = "";
config.conf[_streamName]["devices"] = json({});
}
device = config.conf[_streamName]["device"];
config.release(created);
RtAudio::DeviceInfo info;
#if RTAUDIO_VERSION_MAJOR >= 6
for (int i : audio.getDeviceIds()) {
#else
int count = audio.getDeviceCount();
2021-02-22 23:26:35 +00:00
for (int i = 0; i < count; i++) {
#endif
try {
info = audio.getDeviceInfo(i);
if (info.outputChannels == 0) { continue; }
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
devList.push_back(info);
deviceIds.push_back(i);
txtDevList += info.name;
txtDevList += '\0';
}
catch (const std::exception& e) {
flog::error("AudioSinkModule Error getting audio device info: id={0}: {1}", i, e.what());
}
2020-11-22 17:26:48 +00:00
}
2021-02-22 23:26:35 +00:00
selectByName(device);
2020-11-11 23:53:38 +00:00
}
~AudioSink() {
2023-02-15 16:28:02 +00:00
stop();
2020-11-22 17:26:48 +00:00
}
void start() {
2023-02-15 16:28:02 +00:00
if (running) { return; }
running = doStart();
2020-11-22 17:26:48 +00:00
}
void stop() {
2023-02-15 16:28:02 +00:00
if (!running) { return; }
2020-11-29 19:55:00 +00:00
doStop();
running = false;
2020-11-11 23:53:38 +00:00
}
2021-02-22 23:26:35 +00:00
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();
2021-02-22 23:26:35 +00:00
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(); }
}
2020-11-11 23:53:38 +00:00
void menuHandler() {
float menuWidth = ImGui::GetContentRegionAvail().x;
2020-11-30 04:51:33 +00:00
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) {
2021-02-22 23:26:35 +00:00
selectById(devId);
config.acquire();
2021-02-22 23:26:35 +00:00
config.conf[_streamName]["device"] = devList[devId].name;
config.release(true);
2020-11-29 19:55:00 +00:00
}
2020-11-30 04:51:33 +00:00
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
2021-02-22 23:26:35 +00:00
sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
2020-11-29 19:55:00 +00:00
if (running) {
doStop();
doStart();
}
config.acquire();
2021-02-22 23:26:35 +00:00
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
config.release(true);
2020-11-29 19:55:00 +00:00
}
2020-11-11 23:53:38 +00:00
}
#if RTAUDIO_VERSION_MAJOR >= 6
static void reportErrorsAsException(RtAudioErrorType type, const std::string& errorText) {
switch (type) {
case RtAudioErrorType::RTAUDIO_NO_ERROR:
return;
case RtAudioErrorType::RTAUDIO_WARNING:
case RtAudioErrorType::RTAUDIO_NO_DEVICES_FOUND:
case RtAudioErrorType::RTAUDIO_DEVICE_DISCONNECT:
flog::warn("AudioSink: {0} ({1})", errorText, (int)type);
break;
default:
throw std::runtime_error(errorText);
}
}
#endif
2020-11-11 23:53:38 +00:00
private:
2023-02-15 16:28:02 +00:00
bool doStart() {
2021-02-22 23:26:35 +00:00
RtAudio::StreamParameters parameters;
parameters.deviceId = deviceIds[devId];
parameters.nChannels = 2;
unsigned int bufferFrames = sampleRate / 60;
RtAudio::StreamOptions opts;
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
2021-02-28 15:32:57 +00:00
opts.streamName = _streamName;
2021-02-22 23:26:35 +00:00
try {
audio.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
2021-07-24 17:53:57 +00:00
stereoPacker.setSampleCount(bufferFrames);
2021-02-22 23:26:35 +00:00
audio.startStream();
stereoPacker.start();
2020-11-29 19:55:00 +00:00
}
catch (const std::exception& e) {
flog::error("Could not open audio device {0}", e.what());
2023-02-15 16:28:02 +00:00
return false;
2020-11-29 19:55:00 +00:00
}
flog::info("RtAudio stream open");
2023-02-15 16:28:02 +00:00
return true;
2020-11-29 19:55:00 +00:00
}
void doStop() {
2020-12-25 18:58:52 +00:00
s2m.stop();
2021-02-22 23:26:35 +00:00
monoPacker.stop();
stereoPacker.stop();
monoPacker.out.stopReader();
stereoPacker.out.stopReader();
audio.stopStream();
audio.closeStream();
monoPacker.out.clearReadStop();
stereoPacker.out.clearReadStop();
2020-11-29 19:55:00 +00:00
}
static int callback(void* outputBuffer, void* inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
2020-11-29 19:55:00 +00:00
AudioSink* _this = (AudioSink*)userData;
2021-02-22 23:26:35 +00:00
int count = _this->stereoPacker.out.read();
if (count < 0) { return 0; }
// For debug purposes only...
// if (nBufferFrames != count) { flog::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) { flog::error("NAN in audio data"); }
// if (_this->stereoPacker.out.readBuf[i].l == INFINITY || _this->stereoPacker.out.readBuf[i].r == INFINITY) { flog::error("INFINITY in audio data"); }
// if (_this->stereoPacker.out.readBuf[i].l == -INFINITY || _this->stereoPacker.out.readBuf[i].r == -INFINITY) { flog::error("-INFINITY in audio data"); }
// }
2021-02-22 23:26:35 +00:00
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
_this->stereoPacker.out.flush();
2020-11-29 19:55:00 +00:00
return 0;
}
2020-11-11 23:53:38 +00:00
SinkManager::Stream* _stream;
2022-06-15 14:08:28 +00:00
dsp::convert::StereoToMono s2m;
dsp::buffer::Packer<float> monoPacker;
dsp::buffer::Packer<dsp::stereo_t> stereoPacker;
2021-02-20 14:27:43 +00:00
2020-11-29 19:55:00 +00:00
std::string _streamName;
2020-11-22 17:26:48 +00:00
int srId = 0;
int devCount;
int devId = 0;
2020-11-29 19:55:00 +00:00
bool running = false;
2020-11-22 17:26:48 +00:00
2021-02-22 23:26:35 +00:00
unsigned int defaultDevId = 0;
std::vector<RtAudio::DeviceInfo> devList;
std::vector<unsigned int> deviceIds;
2020-11-22 17:26:48 +00:00
std::string txtDevList;
2020-11-11 23:53:38 +00:00
2021-02-22 23:26:35 +00:00
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
RtAudio audio;
2020-11-11 23:53:38 +00:00
};
2020-12-08 03:36:37 +00:00
class AudioSinkModule : public ModuleManager::Instance {
2020-11-11 23:53:38 +00:00
public:
AudioSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
2020-12-25 23:42:15 +00:00
2020-11-11 23:53:38 +00:00
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Audio");
2020-11-11 23:53:38 +00:00
}
2021-07-26 01:11:51 +00:00
void postInit() {}
2020-12-08 03:36:37 +00:00
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
2020-11-11 23:53:38 +00:00
private:
2020-11-29 19:55:00 +00:00
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
2020-11-11 23:53:38 +00:00
}
std::string name;
2020-12-08 03:36:37 +00:00
bool enabled = true;
2020-11-11 23:53:38 +00:00
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
2021-02-22 23:26:35 +00:00
json def = json({});
config.setPath(core::args["root"].s() + "/audio_sink_config.json");
2021-02-22 23:26:35 +00:00
config.load(def);
config.enableAutoSave();
2020-11-11 23:53:38 +00:00
}
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
AudioSinkModule* instance = new AudioSinkModule(name);
return instance;
}
2021-04-27 14:48:31 +00:00
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (AudioSinkModule*)instance;
2020-11-11 23:53:38 +00:00
}
2020-12-08 03:36:37 +00:00
MOD_EXPORT void _END_() {
2021-04-27 14:48:31 +00:00
config.disableAutoSave();
config.save();
2023-07-02 15:03:52 +00:00
}