Add PulseAudio backend. Various Xlib fixes

This commit is contained in:
Jack Andersen
2018-05-06 12:44:31 -10:00
parent b5f1657dff
commit edf2c1e34d
10 changed files with 700 additions and 318 deletions

View File

@@ -1,20 +1,13 @@
#include <memory>
#include <list>
#include <thread>
#include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include <alsa/asoundlib.h>
#include <signal.h>
static inline double TimespecToDouble(struct timespec& ts)
{
return ts.tv_sec + ts.tv_nsec / 1.0e9;
}
#include "LinuxMidi.hpp"
namespace boo
{
static logvisor::Module Log("boo::ALSA");
logvisor::Module ALSALog("boo::ALSA");
static const uint64_t StereoChans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_FR);
@@ -40,7 +33,7 @@ static const uint64_t S71Chans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_SL) |
(1 << SND_CHMAP_SR);
struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
struct ALSAAudioVoiceEngine : LinuxMidi
{
snd_pcm_t* m_pcm;
snd_pcm_uframes_t m_bufSize;
@@ -129,7 +122,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
/* Open device */
if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, 0))
{
Log.report(logvisor::Error, "unable to allocate ALSA voice");
ALSALog.report(logvisor::Error, "unable to allocate ALSA voice");
return;
}
@@ -144,7 +137,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set interleaved mode. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set interleaved mode. %s\n", snd_strerror(errr));
return;
}
@@ -172,7 +165,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "unsupported audio formats on default ALSA device");
ALSALog.report(logvisor::Error, "unsupported audio formats on default ALSA device");
return;
}
@@ -181,7 +174,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set format. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set format. %s\n", snd_strerror(errr));
return;
}
@@ -193,7 +186,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set channels number. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set channels number. %s\n", snd_strerror(errr));
return;
}
@@ -211,7 +204,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "unsupported audio sample rates on default ALSA device");
ALSALog.report(logvisor::Error, "unsupported audio sample rates on default ALSA device");
return;
}
@@ -220,7 +213,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set rate. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set rate. %s\n", snd_strerror(errr));
return;
}
m_mixInfo.m_sampleRate = bestRate;
@@ -232,7 +225,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set period size. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set period size. %s\n", snd_strerror(errr));
return;
}
@@ -242,7 +235,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set buffer size. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set buffer size. %s\n", snd_strerror(errr));
return;
}
@@ -252,7 +245,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
Log.report(logvisor::Error, "Can't set harware parameters. %s\n", snd_strerror(errr));
ALSALog.report(logvisor::Error, "Can't set harware parameters. %s\n", snd_strerror(errr));
return;
}
@@ -308,7 +301,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_pcm_close(m_pcm);
m_pcm = nullptr;
snd_pcm_free_chmaps(chmaps);
Log.report(logvisor::Error, "unable to find matching ALSA voice chmap");
ALSALog.report(logvisor::Error, "unable to find matching ALSA voice chmap");
return;
}
chmapOut.m_channelCount = chCount;
@@ -373,7 +366,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
{
snd_pcm_prepare(m_pcm);
frames = snd_pcm_avail_update(m_pcm);
Log.report(logvisor::Warning, "ALSA underrun %ld frames", frames);
ALSALog.report(logvisor::Warning, "ALSA underrun %ld frames", frames);
}
else
return;
@@ -403,234 +396,6 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
}
}
}
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
{
std::vector<std::pair<std::string, std::string>> ret;
int status;
int card = -1; /* use -1 to prime the pump of iterating through card list */
if ((status = snd_card_next(&card)) < 0)
return {};
if (card < 0)
return {};
while (card >= 0)
{
snd_ctl_t *ctl;
char name[32];
int device = -1;
int status;
sprintf(name, "hw:%d", card);
if ((status = snd_ctl_open(&ctl, name, 0)) < 0)
continue;
do {
status = snd_ctl_rawmidi_next_device(ctl, &device);
if (status < 0)
break;
if (device >= 0)
{
snd_rawmidi_info_t *info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info_set_device(info, device);
sprintf(name + strlen(name), ",%d", device);
ret.push_back(std::make_pair(name, snd_rawmidi_info_get_name(info)));
}
} while (device >= 0);
snd_ctl_close(ctl);
if ((status = snd_card_next(&card)) < 0)
break;
}
return ret;
}
/* Empty handler for SIGQUIT */
static void _sigquit(int) {}
static void MIDIReceiveProc(snd_rawmidi_t* midi, const ReceiveFunctor& receiver, bool& running)
{
struct sigaction s;
s.sa_handler = _sigquit;
sigemptyset(&s.sa_mask);
s.sa_flags = 0;
sigaction(SIGQUIT, &s, nullptr);
snd_rawmidi_status_t* midiStatus;
snd_rawmidi_status_malloc(&midiStatus);
uint8_t buf[512];
while (running)
{
snd_htimestamp_t ts;
snd_rawmidi_status(midi, midiStatus);
snd_rawmidi_status_get_tstamp(midiStatus, &ts);
int rdBytes = snd_rawmidi_read(midi, buf, 512);
if (rdBytes < 0)
{
if (rdBytes != -EINTR)
Log.report(logvisor::Error, "MIDI connection lost");
running = false;
break;
}
receiver(std::vector<uint8_t>(std::cbegin(buf), std::cbegin(buf) + rdBytes), TimespecToDouble(ts));
}
snd_rawmidi_status_free(midiStatus);
}
struct MIDIIn : public IMIDIIn
{
bool m_midiRunning = true;
snd_rawmidi_t* m_midi;
std::thread m_midiThread;
MIDIIn(snd_rawmidi_t* midi, bool virt, ReceiveFunctor&& receiver)
: IMIDIIn(virt, std::move(receiver)), m_midi(midi),
m_midiThread(std::bind(MIDIReceiveProc, m_midi, m_receiver, m_midiRunning)) {}
~MIDIIn()
{
m_midiRunning = false;
pthread_kill(m_midiThread.native_handle(), SIGQUIT);
if (m_midiThread.joinable())
m_midiThread.join();
snd_rawmidi_close(m_midi);
}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midi, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
};
struct MIDIOut : public IMIDIOut
{
snd_rawmidi_t* m_midi;
MIDIOut(snd_rawmidi_t* midi, bool virt)
: IMIDIOut(virt), m_midi(midi) {}
~MIDIOut() {snd_rawmidi_close(m_midi);}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midi, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t send(const void* buf, size_t len) const
{
return size_t(std::max(0l, snd_rawmidi_write(m_midi, buf, len)));
}
};
struct MIDIInOut : public IMIDIInOut
{
bool m_midiRunning = true;
snd_rawmidi_t* m_midiIn;
snd_rawmidi_t* m_midiOut;
std::thread m_midiThread;
MIDIInOut(snd_rawmidi_t* midiIn, snd_rawmidi_t* midiOut, bool virt, ReceiveFunctor&& receiver)
: IMIDIInOut(virt, std::move(receiver)), m_midiIn(midiIn), m_midiOut(midiOut),
m_midiThread(std::bind(MIDIReceiveProc, m_midiIn, m_receiver, m_midiRunning)) {}
~MIDIInOut()
{
m_midiRunning = false;
pthread_kill(m_midiThread.native_handle(), SIGQUIT);
if (m_midiThread.joinable())
m_midiThread.join();
snd_rawmidi_close(m_midiIn);
snd_rawmidi_close(m_midiOut);
}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midiIn, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t send(const void* buf, size_t len) const
{
return size_t(std::max(0l, snd_rawmidi_write(m_midiOut, buf, len)));
}
};
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(&midi, nullptr, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true, std::move(receiver));
}
std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(nullptr, &midi, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver)
{
int status;
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
status = snd_rawmidi_open(&midiIn, &midiOut, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true, std::move(receiver));
}
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver)
{
snd_rawmidi_t* midi;
int status = snd_rawmidi_open(&midi, nullptr, name, 0);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true, std::move(receiver));
}
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
{
snd_rawmidi_t* midi;
int status = snd_rawmidi_open(nullptr, &midi, name, 0);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver)
{
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
int status = snd_rawmidi_open(&midiIn, &midiOut, name, 0);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true, std::move(receiver));
}
bool useMIDILock() const {return true;}
};
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()

252
lib/audiodev/LinuxMidi.hpp Normal file
View File

@@ -0,0 +1,252 @@
#ifndef BOO_LINUXMIDI_HPP
#define BOO_LINUXMIDI_HPP
#include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include <thread>
#include <alsa/asoundlib.h>
#include <signal.h>
namespace boo
{
extern logvisor::Module ALSALog;
static inline double TimespecToDouble(struct timespec& ts)
{
return ts.tv_sec + ts.tv_nsec / 1.0e9;
}
struct LinuxMidi : BaseAudioVoiceEngine
{
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
{
std::vector<std::pair<std::string, std::string>> ret;
int status;
int card = -1; /* use -1 to prime the pump of iterating through card list */
if ((status = snd_card_next(&card)) < 0)
return {};
if (card < 0)
return {};
while (card >= 0)
{
snd_ctl_t *ctl;
char name[32];
int device = -1;
int status;
sprintf(name, "hw:%d", card);
if ((status = snd_ctl_open(&ctl, name, 0)) < 0)
continue;
do {
status = snd_ctl_rawmidi_next_device(ctl, &device);
if (status < 0)
break;
if (device >= 0)
{
snd_rawmidi_info_t *info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info_set_device(info, device);
sprintf(name + strlen(name), ",%d", device);
ret.push_back(std::make_pair(name, snd_rawmidi_info_get_name(info)));
}
} while (device >= 0);
snd_ctl_close(ctl);
if ((status = snd_card_next(&card)) < 0)
break;
}
return ret;
}
static void MIDIFreeProc(void* midiStatus)
{
snd_rawmidi_status_free((snd_rawmidi_status_t*)midiStatus);
}
static void MIDIReceiveProc(snd_rawmidi_t* midi, const ReceiveFunctor& receiver)
{
snd_rawmidi_status_t* midiStatus;
snd_rawmidi_status_malloc(&midiStatus);
pthread_cleanup_push(MIDIFreeProc, midiStatus);
uint8_t buf[512];
while (true)
{
snd_htimestamp_t ts;
snd_rawmidi_status(midi, midiStatus);
snd_rawmidi_status_get_tstamp(midiStatus, &ts);
int rdBytes = snd_rawmidi_read(midi, buf, 512);
if (rdBytes < 0)
{
if (rdBytes != -EINTR)
{
ALSALog.report(logvisor::Error, "MIDI connection lost");
break;
}
continue;
}
int oldtype;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldtype);
receiver(std::vector<uint8_t>(std::cbegin(buf), std::cbegin(buf) + rdBytes), TimespecToDouble(ts));
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldtype);
pthread_testcancel();
}
pthread_cleanup_pop(1);
}
struct MIDIIn : public IMIDIIn
{
snd_rawmidi_t* m_midi;
std::thread m_midiThread;
MIDIIn(snd_rawmidi_t* midi, bool virt, ReceiveFunctor&& receiver)
: IMIDIIn(virt, std::move(receiver)), m_midi(midi),
m_midiThread(std::bind(MIDIReceiveProc, m_midi, m_receiver)) {}
~MIDIIn()
{
pthread_cancel(m_midiThread.native_handle());
if (m_midiThread.joinable())
m_midiThread.join();
snd_rawmidi_close(m_midi);
}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midi, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
};
struct MIDIOut : public IMIDIOut
{
snd_rawmidi_t* m_midi;
MIDIOut(snd_rawmidi_t* midi, bool virt)
: IMIDIOut(virt), m_midi(midi) {}
~MIDIOut() {snd_rawmidi_close(m_midi);}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midi, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t send(const void* buf, size_t len) const
{
return size_t(std::max(0l, snd_rawmidi_write(m_midi, buf, len)));
}
};
struct MIDIInOut : public IMIDIInOut
{
snd_rawmidi_t* m_midiIn;
snd_rawmidi_t* m_midiOut;
std::thread m_midiThread;
MIDIInOut(snd_rawmidi_t* midiIn, snd_rawmidi_t* midiOut, bool virt, ReceiveFunctor&& receiver)
: IMIDIInOut(virt, std::move(receiver)), m_midiIn(midiIn), m_midiOut(midiOut),
m_midiThread(std::bind(MIDIReceiveProc, m_midiIn, m_receiver)) {}
~MIDIInOut()
{
pthread_cancel(m_midiThread.native_handle());
if (m_midiThread.joinable())
m_midiThread.join();
snd_rawmidi_close(m_midiIn);
snd_rawmidi_close(m_midiOut);
}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midiIn, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t send(const void* buf, size_t len) const
{
return size_t(std::max(0l, snd_rawmidi_write(m_midiOut, buf, len)));
}
};
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(&midi, nullptr, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true, std::move(receiver));
}
std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(nullptr, &midi, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver)
{
int status;
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
status = snd_rawmidi_open(&midiIn, &midiOut, "virtual", 0);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true, std::move(receiver));
}
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver)
{
snd_rawmidi_t* midi;
int status = snd_rawmidi_open(&midi, nullptr, name, 0);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true, std::move(receiver));
}
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
{
snd_rawmidi_t* midi;
int status = snd_rawmidi_open(nullptr, &midi, name, 0);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver)
{
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
int status = snd_rawmidi_open(&midiIn, &midiOut, name, 0);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true, std::move(receiver));
}
bool useMIDILock() const {return true;}
};
}
#endif // BOO_LINUXMIDI_HPP

352
lib/audiodev/PulseAudio.cpp Normal file
View File

@@ -0,0 +1,352 @@
#include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include "boo/boo.hpp"
#include "LinuxMidi.hpp"
#include <pulse/pulseaudio.h>
#include <unistd.h>
namespace boo
{
static logvisor::Module Log("boo::PulseAudio");
logvisor::Module ALSALog("boo::ALSA");
static const uint64_t StereoChans = (1 << PA_CHANNEL_POSITION_FRONT_LEFT) |
(1 << PA_CHANNEL_POSITION_FRONT_RIGHT);
static const uint64_t QuadChans = (1 << PA_CHANNEL_POSITION_FRONT_LEFT) |
(1 << PA_CHANNEL_POSITION_FRONT_RIGHT) |
(1 << PA_CHANNEL_POSITION_REAR_LEFT) |
(1 << PA_CHANNEL_POSITION_REAR_RIGHT);
static const uint64_t S51Chans = (1 << PA_CHANNEL_POSITION_FRONT_LEFT) |
(1 << PA_CHANNEL_POSITION_FRONT_RIGHT) |
(1 << PA_CHANNEL_POSITION_REAR_LEFT) |
(1 << PA_CHANNEL_POSITION_REAR_RIGHT) |
(1 << PA_CHANNEL_POSITION_FRONT_CENTER) |
(1 << PA_CHANNEL_POSITION_LFE);
static const uint64_t S71Chans = (1 << PA_CHANNEL_POSITION_FRONT_LEFT) |
(1 << PA_CHANNEL_POSITION_FRONT_RIGHT) |
(1 << PA_CHANNEL_POSITION_REAR_LEFT) |
(1 << PA_CHANNEL_POSITION_REAR_RIGHT) |
(1 << PA_CHANNEL_POSITION_FRONT_CENTER) |
(1 << PA_CHANNEL_POSITION_LFE) |
(1 << PA_CHANNEL_POSITION_SIDE_LEFT) |
(1 << PA_CHANNEL_POSITION_SIDE_RIGHT);
struct PulseAudioVoiceEngine : LinuxMidi
{
pa_mainloop* m_mainloop = nullptr;
pa_context* m_ctx = nullptr;
pa_stream* m_stream = nullptr;
std::string m_sinkName;
pa_sample_spec m_sampleSpec = {};
pa_channel_map m_chanMap = {};
int _paWaitReady()
{
int retval = 0;
while (pa_context_get_state(m_ctx) < PA_CONTEXT_READY)
pa_mainloop_iterate(m_mainloop, 1, &retval);
return retval;
}
int _paStreamWaitReady()
{
int retval = 0;
while (pa_stream_get_state(m_stream) < PA_STREAM_READY)
pa_mainloop_iterate(m_mainloop, 1, &retval);
return retval;
}
int _paIterate(pa_operation* op)
{
int retval = 0;
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
pa_mainloop_iterate(m_mainloop, 1, &retval);
return retval;
}
PulseAudioVoiceEngine()
{
if (!(m_mainloop = pa_mainloop_new()))
{
Log.report(logvisor::Error, "Unable to pa_mainloop_new()");
return;
}
pa_mainloop_api* mlApi = pa_mainloop_get_api(m_mainloop);
pa_proplist* propList = pa_proplist_new();
pa_proplist_sets(propList, PA_PROP_APPLICATION_ICON_NAME, APP->getUniqueName().data());
char pidStr[16];
snprintf(pidStr, 16, "%d", int(getpid()));
pa_proplist_sets(propList, PA_PROP_APPLICATION_PROCESS_ID, pidStr);
if (!(m_ctx = pa_context_new_with_proplist(mlApi, APP->getFriendlyName().data(), propList)))
{
Log.report(logvisor::Error, "Unable to pa_context_new_with_proplist()");
pa_mainloop_free(m_mainloop);
m_mainloop = nullptr;
return;
}
pa_operation* op;
size_t periodSz;
if (pa_context_connect(m_ctx, nullptr, PA_CONTEXT_NOFLAGS, nullptr))
{
Log.report(logvisor::Error, "Unable to pa_context_connect()");
goto err;
}
_paWaitReady();
op = pa_context_get_server_info(m_ctx, pa_server_info_cb_t(_getServerInfoReply), this);
_paIterate(op);
pa_operation_unref(op);
m_sampleSpec.format = PA_SAMPLE_INVALID;
op = pa_context_get_sink_info_by_name(m_ctx, m_sinkName.c_str(), pa_sink_info_cb_t(_getSinkInfoReply), this);
_paIterate(op);
pa_operation_unref(op);
if (m_sampleSpec.format == PA_SAMPLE_INVALID)
{
Log.report(logvisor::Error, "Unable to setup audio stream");
goto err;
}
m_5msFrames = m_sampleSpec.rate * 5 / 1000;
periodSz = m_5msFrames * 4;
m_mixInfo.m_sampleRate = m_sampleSpec.rate;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32;
m_mixInfo.m_bitsPerSample = 32;
m_mixInfo.m_periodFrames = periodSz;
if (!(m_stream = pa_stream_new(m_ctx, "master", &m_sampleSpec, &m_chanMap)))
{
Log.report(logvisor::Error, "Unable to pa_stream_new(): %s", pa_strerror(pa_context_errno(m_ctx)));
goto err;
}
pa_buffer_attr bufAttr;
bufAttr.minreq = uint32_t(periodSz * m_sampleSpec.channels * sizeof(float));
bufAttr.maxlength = bufAttr.minreq * 6;
bufAttr.tlength = bufAttr.maxlength;
bufAttr.prebuf = UINT32_MAX;
bufAttr.fragsize = UINT32_MAX;
if (pa_stream_connect_playback(m_stream, m_sinkName.c_str(), &bufAttr,
pa_stream_flags_t(PA_STREAM_START_UNMUTED | PA_STREAM_EARLY_REQUESTS),
nullptr, nullptr))
{
Log.report(logvisor::Error, "Unable to pa_stream_connect_playback()");
goto err;
}
_paStreamWaitReady();
return;
err:
if (m_stream)
{
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
m_stream = nullptr;
}
pa_context_disconnect(m_ctx);
pa_context_unref(m_ctx);
m_ctx = nullptr;
pa_mainloop_free(m_mainloop);
m_mainloop = nullptr;
}
~PulseAudioVoiceEngine()
{
if (m_stream)
{
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
}
if (m_ctx)
{
pa_context_disconnect(m_ctx);
pa_context_unref(m_ctx);
}
if (m_mainloop)
{
pa_mainloop_free(m_mainloop);
}
}
static void _getServerInfoReply(pa_context* c, const pa_server_info* i, PulseAudioVoiceEngine* userdata)
{
userdata->m_sinkName = i->default_sink_name;
}
void _parseAudioChannelSet(const pa_channel_map* chm)
{
m_chanMap = *chm;
ChannelMap& chmapOut = m_mixInfo.m_channelMap;
m_mixInfo.m_channels = AudioChannelSet::Unknown;
static const std::array<AudioChannelSet, 4> testSets =
{{AudioChannelSet::Surround71, AudioChannelSet::Surround51,
AudioChannelSet::Quad, AudioChannelSet::Stereo}};
for (AudioChannelSet set : testSets)
{
uint64_t chBits = 0;
chmapOut.m_channelCount = chm->channels;
for (unsigned c=0 ; c<chm->channels ; ++c)
{
chBits |= 1 << chm->map[c];
switch (chm->map[c])
{
case PA_CHANNEL_POSITION_FRONT_LEFT:
chmapOut.m_channels[c] = AudioChannel::FrontLeft;
break;
case PA_CHANNEL_POSITION_FRONT_RIGHT:
chmapOut.m_channels[c] = AudioChannel::FrontRight;
break;
case PA_CHANNEL_POSITION_REAR_LEFT:
chmapOut.m_channels[c] = AudioChannel::RearLeft;
break;
case PA_CHANNEL_POSITION_REAR_RIGHT:
chmapOut.m_channels[c] = AudioChannel::RearRight;
break;
case PA_CHANNEL_POSITION_FRONT_CENTER:
chmapOut.m_channels[c] = AudioChannel::FrontCenter;
break;
case PA_CHANNEL_POSITION_LFE:
chmapOut.m_channels[c] = AudioChannel::LFE;
break;
case PA_CHANNEL_POSITION_SIDE_LEFT:
chmapOut.m_channels[c] = AudioChannel::SideLeft;
break;
case PA_CHANNEL_POSITION_SIDE_RIGHT:
chmapOut.m_channels[c] = AudioChannel::SideRight;
break;
default:
chmapOut.m_channels[c] = AudioChannel::Unknown;
break;
}
}
switch (set)
{
case AudioChannelSet::Stereo:
{
if ((chBits & StereoChans) == StereoChans)
{
m_mixInfo.m_channels = AudioChannelSet::Stereo;
return;
}
break;
}
case AudioChannelSet::Quad:
{
if ((chBits & QuadChans) == QuadChans)
{
m_mixInfo.m_channels = AudioChannelSet::Quad;
return;
}
break;
}
case AudioChannelSet::Surround51:
{
if ((chBits & S51Chans) == S51Chans)
{
m_mixInfo.m_channels = AudioChannelSet::Surround51;
return;
}
break;
}
case AudioChannelSet::Surround71:
{
if ((chBits & S71Chans) == S71Chans)
{
m_mixInfo.m_channels = AudioChannelSet::Surround71;
return;
}
break;
}
default: break;
}
}
}
static void _getSinkInfoReply(pa_context *c, const pa_sink_info* i, int eol, PulseAudioVoiceEngine* userdata)
{
if (!i)
return;
userdata->m_sampleSpec.format = PA_SAMPLE_FLOAT32;
userdata->m_sampleSpec.rate = i->sample_spec.rate;
userdata->m_sampleSpec.channels = i->sample_spec.channels;
userdata->_parseAudioChannelSet(&i->channel_map);
}
std::vector<float> m_finalFlt;
void pumpAndMixVoices()
{
if (!m_stream)
{
/* Dummy pump mode - use failsafe defaults for 1/60sec of samples */
m_mixInfo.m_sampleRate = 32000.0;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32;
m_5msFrames = 32000 / 60;
m_mixInfo.m_periodFrames = m_5msFrames;
m_mixInfo.m_channels = AudioChannelSet::Stereo;
m_mixInfo.m_channelMap.m_channelCount = 2;
m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
if (m_finalFlt.size() < m_5msFrames * 2)
m_finalFlt.resize(m_5msFrames * 2);
_pumpAndMixVoices(m_5msFrames, m_finalFlt.data());
return;
}
size_t writableSz = pa_stream_writable_size(m_stream);
size_t frameSz = m_mixInfo.m_channelMap.m_channelCount * sizeof(float);
size_t writableFrames = writableSz / frameSz;
size_t writablePeriods = writableFrames / m_mixInfo.m_periodFrames;
int retval;
if (!writablePeriods)
{
pa_mainloop_iterate(m_mainloop, 1, &retval);
return;
}
void* data;
size_t periodSz = m_mixInfo.m_periodFrames * frameSz;
size_t nbytes = writablePeriods * periodSz;
if (pa_stream_begin_write(m_stream, &data, &nbytes))
{
pa_stream_state_t st = pa_stream_get_state(m_stream);
Log.report(logvisor::Error, "Unable to pa_stream_begin_write(): %s %d",
pa_strerror(pa_context_errno(m_ctx)), st);
pa_mainloop_iterate(m_mainloop, 1, &retval);
return;
}
writablePeriods = nbytes / periodSz;
size_t periodSamples = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount;
for (int p=0 ; p<writablePeriods ; ++p)
_pumpAndMixVoices(m_mixInfo.m_periodFrames, reinterpret_cast<float*>(data) + p * periodSamples);
if (pa_stream_write(m_stream, data, nbytes, nullptr, 0, PA_SEEK_RELATIVE))
Log.report(logvisor::Error, "Unable to pa_stream_write()");
pa_mainloop_iterate(m_mainloop, 1, &retval);
}
};
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
{
return std::make_unique<PulseAudioVoiceEngine>();
}
}