Remove ALSA audio backend

This commit is contained in:
Jack Andersen 2018-08-18 12:08:58 -10:00
parent fe7f671c0b
commit 5e58e989a8
8 changed files with 171 additions and 463 deletions

View File

@ -191,15 +191,12 @@ else(NOT GEKKO)
find_path(PULSEAUDIO_INCLUDE_DIR find_path(PULSEAUDIO_INCLUDE_DIR
NAMES pulse/pulseaudio.h) NAMES pulse/pulseaudio.h)
if(PULSEAUDIO_INCLUDE_DIR-NOTFOUND)
message(FATAL_ERROR "Unix build of boo requires pulseaudio")
endif()
if (PULSEAUDIO_INCLUDE_DIR)
list(APPEND PLAT_SRCS lib/audiodev/PulseAudio.cpp) list(APPEND PLAT_SRCS lib/audiodev/PulseAudio.cpp)
list(APPEND _BOO_SYS_LIBS pulse) list(APPEND _BOO_SYS_LIBS pulse)
message(STATUS "Using PulseAudio backend")
else()
list(APPEND PLAT_SRCS lib/audiodev/ALSA.cpp)
message(STATUS "Using ALSA backend")
endif()
if(DBUS_INCLUDE_DIR-NOTFOUND) if(DBUS_INCLUDE_DIR-NOTFOUND)
message(FATAL_ERROR "Unix build of boo requires dbus") message(FATAL_ERROR "Unix build of boo requires dbus")

View File

@ -64,8 +64,17 @@ struct IAudioVoiceEngine
/** Enable or disable Lt/Rt surround encoding. If successful, getAvailableSet() will return Surround51 */ /** Enable or disable Lt/Rt surround encoding. If successful, getAvailableSet() will return Surround51 */
virtual bool enableLtRt(bool enable)=0; virtual bool enableLtRt(bool enable)=0;
/** Get list of MIDI devices found on system */ /** Get current Audio output in use */
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const=0; virtual std::string getCurrentAudioOutput() const=0;
/** Set current Audio output to use */
virtual bool setCurrentAudioOutput(const char* name)=0;
/** Get list of Audio output devices found on system */
virtual std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const=0;
/** Get list of MIDI input devices found on system */
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const=0;
/** Create ad-hoc MIDI in port and register with system */ /** Create ad-hoc MIDI in port and register with system */
virtual std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)=0; virtual std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)=0;

View File

@ -1,404 +0,0 @@
#include <memory>
#include <list>
#include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include "LinuxMidi.hpp"
namespace boo
{
logvisor::Module ALSALog("boo::ALSA");
static const uint64_t StereoChans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_FR);
static const uint64_t QuadChans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_FR) |
(1 << SND_CHMAP_RL) |
(1 << SND_CHMAP_RR);
static const uint64_t S51Chans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_FR) |
(1 << SND_CHMAP_RL) |
(1 << SND_CHMAP_RR) |
(1 << SND_CHMAP_FC) |
(1 << SND_CHMAP_LFE);
static const uint64_t S71Chans = (1 << SND_CHMAP_FL) |
(1 << SND_CHMAP_FR) |
(1 << SND_CHMAP_RL) |
(1 << SND_CHMAP_RR) |
(1 << SND_CHMAP_FC) |
(1 << SND_CHMAP_LFE) |
(1 << SND_CHMAP_SL) |
(1 << SND_CHMAP_SR);
struct ALSAAudioVoiceEngine : LinuxMidi
{
snd_pcm_t* m_pcm;
snd_pcm_uframes_t m_bufSize;
snd_pcm_uframes_t m_periodSize;
std::vector<int16_t> m_final16;
std::vector<int32_t> m_final32;
std::vector<float> m_finalFlt;
~ALSAAudioVoiceEngine()
{
if (m_pcm)
{
snd_pcm_drain(m_pcm);
snd_pcm_close(m_pcm);
}
}
AudioChannelSet _getAvailableSet()
{
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm);
if (!chmaps)
return AudioChannelSet::Stereo;
static const std::array<AudioChannelSet, 4> testSets =
{{AudioChannelSet::Surround71, AudioChannelSet::Surround51,
AudioChannelSet::Quad, AudioChannelSet::Stereo}};
for (AudioChannelSet set : testSets)
{
for (snd_pcm_chmap_query_t** chmap = chmaps ; *chmap != nullptr ; ++chmap)
{
snd_pcm_chmap_t* chm = &(*chmap)->map;
uint64_t chBits = 0;
for (unsigned c=0 ; c<chm->channels ; ++c)
chBits |= 1 << chm->pos[c];
switch (set)
{
case AudioChannelSet::Stereo:
{
if ((chBits & StereoChans) == StereoChans)
{
snd_pcm_free_chmaps(chmaps);
return AudioChannelSet::Stereo;
}
break;
}
case AudioChannelSet::Quad:
{
if ((chBits & QuadChans) == QuadChans)
{
snd_pcm_free_chmaps(chmaps);
return AudioChannelSet::Quad;
}
break;
}
case AudioChannelSet::Surround51:
{
if ((chBits & S51Chans) == S51Chans)
{
snd_pcm_free_chmaps(chmaps);
return AudioChannelSet::Surround51;
}
break;
}
case AudioChannelSet::Surround71:
{
if ((chBits & S71Chans) == S71Chans)
{
snd_pcm_free_chmaps(chmaps);
return AudioChannelSet::Surround71;
}
break;
}
default: break;
}
}
}
snd_pcm_free_chmaps(chmaps);
return AudioChannelSet::Unknown;
}
ALSAAudioVoiceEngine()
{
/* Open device */
if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, 0))
{
ALSALog.report(logvisor::Error, "unable to allocate ALSA voice");
return;
}
/* Query audio card for best supported format amd sample-rate */
snd_pcm_hw_params_t* hwParams;
snd_pcm_hw_params_malloc(&hwParams);
snd_pcm_hw_params_any(m_pcm, hwParams);
int errr;
if ((errr = snd_pcm_hw_params_set_access(m_pcm, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set interleaved mode. %s\n", snd_strerror(errr));
return;
}
snd_pcm_format_t bestFmt;
if (!snd_pcm_hw_params_test_format(m_pcm, hwParams, SND_PCM_FORMAT_FLOAT))
{
bestFmt = SND_PCM_FORMAT_FLOAT;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32;
}
else if (!snd_pcm_hw_params_test_format(m_pcm, hwParams, SND_PCM_FORMAT_S32))
{
bestFmt = SND_PCM_FORMAT_S32;
m_mixInfo.m_sampleFormat = SOXR_INT32_I;
m_mixInfo.m_bitsPerSample = 32;
}
else if (!snd_pcm_hw_params_test_format(m_pcm, hwParams, SND_PCM_FORMAT_S16))
{
bestFmt = SND_PCM_FORMAT_S16;
m_mixInfo.m_sampleFormat = SOXR_INT16_I;
m_mixInfo.m_bitsPerSample = 16;
}
else
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "unsupported audio formats on default ALSA device");
return;
}
if ((errr = snd_pcm_hw_params_set_format(m_pcm, hwParams, bestFmt)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set format. %s\n", snd_strerror(errr));
return;
}
/* Query audio card for channel map */
m_mixInfo.m_channels = _getAvailableSet();
unsigned int chCount = ChannelCount(m_mixInfo.m_channels);
if ((errr = snd_pcm_hw_params_set_channels_near(m_pcm, hwParams, &chCount)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set channels number. %s\n", snd_strerror(errr));
return;
}
unsigned int bestRate;
if (!snd_pcm_hw_params_test_rate(m_pcm, hwParams, 96000, 0))
{
bestRate = 96000;
}
else if (!snd_pcm_hw_params_test_rate(m_pcm, hwParams, 48000, 0))
{
bestRate = 48000;
}
else
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "unsupported audio sample rates on default ALSA device");
return;
}
if ((errr = snd_pcm_hw_params_set_rate_near(m_pcm, hwParams, &bestRate, 0)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set rate. %s\n", snd_strerror(errr));
return;
}
m_mixInfo.m_sampleRate = bestRate;
m_5msFrames = bestRate * 5 / 1000;
snd_pcm_uframes_t periodSz = m_5msFrames * 4;
if ((errr = snd_pcm_hw_params_set_period_size_near(m_pcm, hwParams, &periodSz, 0)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set period size. %s\n", snd_strerror(errr));
return;
}
snd_pcm_uframes_t bufSz = periodSz * 2;
if ((errr = snd_pcm_hw_params_set_buffer_size_near(m_pcm, hwParams, &bufSz)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set buffer size. %s\n", snd_strerror(errr));
return;
}
/* Write parameters */
if ((errr = snd_pcm_hw_params(m_pcm, hwParams)))
{
snd_pcm_hw_params_free(hwParams);
snd_pcm_close(m_pcm);
m_pcm = nullptr;
ALSALog.report(logvisor::Error, "Can't set harware parameters. %s\n", snd_strerror(errr));
return;
}
snd_pcm_hw_params_free(hwParams);
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm);
ChannelMap& chmapOut = m_mixInfo.m_channelMap;
if (chmaps)
{
snd_pcm_chmap_t* foundChmap = nullptr;
for (snd_pcm_chmap_query_t** chmap = chmaps ; *chmap != nullptr ; ++chmap)
{
if ((*chmap)->map.channels == chCount)
{
snd_pcm_chmap_t* chm = &(*chmap)->map;
uint64_t chBits = 0;
for (unsigned c=0 ; c<chm->channels ; ++c)
chBits |= 1 << chm->pos[c];
bool good = false;
switch (m_mixInfo.m_channels)
{
case AudioChannelSet::Stereo:
if ((chBits & StereoChans) == StereoChans)
good = true;
break;
case AudioChannelSet::Quad:
if ((chBits & QuadChans) == QuadChans)
good = true;
break;
case AudioChannelSet::Surround51:
if ((chBits & S51Chans) == S51Chans)
good = true;
break;
case AudioChannelSet::Surround71:
if ((chBits & S71Chans) == S71Chans)
good = true;
break;
default: break;
}
if (good)
{
foundChmap = chm;
break;
}
}
}
if (!foundChmap)
{
snd_pcm_close(m_pcm);
m_pcm = nullptr;
snd_pcm_free_chmaps(chmaps);
ALSALog.report(logvisor::Error, "unable to find matching ALSA voice chmap");
return;
}
chmapOut.m_channelCount = chCount;
for (unsigned c=0 ; c<foundChmap->channels ; ++c)
chmapOut.m_channels[c] = AudioChannel(foundChmap->pos[c] - 3);
snd_pcm_set_chmap(m_pcm, foundChmap);
snd_pcm_free_chmaps(chmaps);
}
else
{
chmapOut.m_channelCount = 2;
chmapOut.m_channels[0] = AudioChannel::FrontLeft;
chmapOut.m_channels[1] = AudioChannel::FrontRight;
}
snd_pcm_get_params(m_pcm, &m_bufSize, &m_periodSize);
snd_pcm_prepare(m_pcm);
m_mixInfo.m_periodFrames = m_periodSize;
/* Allocate master mix space */
switch (m_mixInfo.m_sampleFormat)
{
case SOXR_INT16_I:
m_final16.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
break;
case SOXR_INT32_I:
m_final32.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
break;
case SOXR_FLOAT32_I:
m_finalFlt.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
break;
default:
break;
}
}
void pumpAndMixVoices()
{
if (!m_pcm)
{
/* 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;
_pumpAndMixVoices(m_5msFrames, (float*)nullptr);
return;
}
snd_pcm_sframes_t frames = snd_pcm_avail_update(m_pcm);
if (frames < 0)
{
snd_pcm_state_t st = snd_pcm_state(m_pcm);
if (st == SND_PCM_STATE_XRUN)
{
snd_pcm_prepare(m_pcm);
frames = snd_pcm_avail_update(m_pcm);
ALSALog.report(logvisor::Warning, "ALSA underrun %ld frames", frames);
}
else
return;
}
if (frames < 0)
return;
snd_pcm_sframes_t buffers = frames / m_periodSize;
for (snd_pcm_sframes_t b=0 ; b<buffers ; ++b)
{
switch (m_mixInfo.m_sampleFormat)
{
case SOXR_INT16_I:
_pumpAndMixVoices(m_periodSize, m_final16.data());
snd_pcm_writei(m_pcm, m_final16.data(), m_periodSize);
break;
case SOXR_INT32_I:
_pumpAndMixVoices(m_periodSize, m_final32.data());
snd_pcm_writei(m_pcm, m_final32.data(), m_periodSize);
break;
case SOXR_FLOAT32_I:
_pumpAndMixVoices(m_periodSize, m_finalFlt.data());
snd_pcm_writei(m_pcm, m_finalFlt.data(), m_periodSize);
break;
default:
break;
}
}
}
};
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
{
return std::make_unique<ALSAAudioVoiceEngine>();
}
}

View File

@ -91,6 +91,16 @@ template void BaseAudioVoiceEngine::_pumpAndMixVoices<int16_t>(size_t frames, in
template void BaseAudioVoiceEngine::_pumpAndMixVoices<int32_t>(size_t frames, int32_t* dataOut); template void BaseAudioVoiceEngine::_pumpAndMixVoices<int32_t>(size_t frames, int32_t* dataOut);
template void BaseAudioVoiceEngine::_pumpAndMixVoices<float>(size_t frames, float* dataOut); template void BaseAudioVoiceEngine::_pumpAndMixVoices<float>(size_t frames, float* dataOut);
void BaseAudioVoiceEngine::_resetSampleRate()
{
if (m_voiceHead)
for (boo::AudioVoice& vox : *m_voiceHead)
vox._resetSampleRate(vox.m_sampleRateIn);
if (m_submixHead)
for (boo::AudioSubmix& smx : *m_submixHead)
smx._resetOutputSampleRate();
}
ObjToken<IAudioVoice> ObjToken<IAudioVoice>
BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate, BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb, IAudioVoiceCallback* cb,

View File

@ -53,6 +53,8 @@ protected:
template <typename T> template <typename T>
void _pumpAndMixVoices(size_t frames, T* dataOut); void _pumpAndMixVoices(size_t frames, T* dataOut);
void _resetSampleRate();
public: public:
BaseAudioVoiceEngine() : m_mainSubmix(std::make_unique<AudioSubmix>(*this, nullptr, -1, false)) {} BaseAudioVoiceEngine() : m_mainSubmix(std::make_unique<AudioSubmix>(*this, nullptr, -1, false)) {}
~BaseAudioVoiceEngine(); ~BaseAudioVoiceEngine();

View File

@ -19,7 +19,7 @@ static inline double TimespecToDouble(struct timespec& ts)
struct LinuxMidi : BaseAudioVoiceEngine struct LinuxMidi : BaseAudioVoiceEngine
{ {
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const
{ {
std::vector<std::pair<std::string, std::string>> ret; std::vector<std::pair<std::string, std::string>> ret;
int status; int status;
@ -30,6 +30,9 @@ struct LinuxMidi : BaseAudioVoiceEngine
if (card < 0) if (card < 0)
return {}; return {};
snd_rawmidi_info_t* info;
snd_rawmidi_info_malloc(&info);
while (card >= 0) while (card >= 0)
{ {
snd_ctl_t *ctl; snd_ctl_t *ctl;
@ -46,9 +49,9 @@ struct LinuxMidi : BaseAudioVoiceEngine
break; break;
if (device >= 0) if (device >= 0)
{ {
snd_rawmidi_info_t *info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info_set_device(info, device); snd_rawmidi_info_set_device(info, device);
if (snd_rawmidi_info_get_stream(info) != SND_RAWMIDI_STREAM_INPUT)
continue;
sprintf(name + strlen(name), ",%d", device); sprintf(name + strlen(name), ",%d", device);
ret.push_back(std::make_pair(name, snd_rawmidi_info_get_name(info))); ret.push_back(std::make_pair(name, snd_rawmidi_info_get_name(info)));
} }
@ -60,6 +63,8 @@ struct LinuxMidi : BaseAudioVoiceEngine
break; break;
} }
snd_rawmidi_info_free(info);
return ret; return ret;
} }

View File

@ -41,6 +41,7 @@ struct PulseAudioVoiceEngine : LinuxMidi
pa_context* m_ctx = nullptr; pa_context* m_ctx = nullptr;
pa_stream* m_stream = nullptr; pa_stream* m_stream = nullptr;
std::string m_sinkName; std::string m_sinkName;
bool m_handleMove = false;
pa_sample_spec m_sampleSpec = {}; pa_sample_spec m_sampleSpec = {};
pa_channel_map m_chanMap = {}; pa_channel_map m_chanMap = {};
@ -60,7 +61,7 @@ struct PulseAudioVoiceEngine : LinuxMidi
return retval; return retval;
} }
int _paIterate(pa_operation* op) int _paIterate(pa_operation* op) const
{ {
int retval = 0; int retval = 0;
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
@ -68,42 +69,16 @@ struct PulseAudioVoiceEngine : LinuxMidi
return retval; return retval;
} }
PulseAudioVoiceEngine() bool _setupSink()
{ {
if (!(m_mainloop = pa_mainloop_new())) if (m_stream)
{ {
Log.report(logvisor::Error, "Unable to pa_mainloop_new()"); pa_stream_disconnect(m_stream);
return; pa_stream_unref(m_stream);
} m_stream = nullptr;
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; pa_operation* op;
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; 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); op = pa_context_get_sink_info_by_name(m_ctx, m_sinkName.c_str(), pa_sink_info_cb_t(_getSinkInfoReply), this);
_paIterate(op); _paIterate(op);
@ -142,9 +117,12 @@ struct PulseAudioVoiceEngine : LinuxMidi
goto err; goto err;
} }
pa_stream_set_moved_callback(m_stream, pa_stream_notify_cb_t(_streamMoved), this);
_paStreamWaitReady(); _paStreamWaitReady();
return; _resetSampleRate();
return true;
err: err:
if (m_stream) if (m_stream)
{ {
@ -152,6 +130,50 @@ struct PulseAudioVoiceEngine : LinuxMidi
pa_stream_unref(m_stream); pa_stream_unref(m_stream);
m_stream = nullptr; m_stream = nullptr;
} }
return false;
}
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;
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);
if (!_setupSink())
goto err;
return;
err:
pa_context_disconnect(m_ctx); pa_context_disconnect(m_ctx);
pa_context_unref(m_ctx); pa_context_unref(m_ctx);
m_ctx = nullptr; m_ctx = nullptr;
@ -177,6 +199,12 @@ struct PulseAudioVoiceEngine : LinuxMidi
} }
} }
static void _streamMoved(pa_stream* p, PulseAudioVoiceEngine* userdata)
{
userdata->m_sinkName = pa_stream_get_device_name(p);
userdata->m_handleMove = true;
}
static void _getServerInfoReply(pa_context* c, const pa_server_info* i, PulseAudioVoiceEngine* userdata) static void _getServerInfoReply(pa_context* c, const pa_server_info* i, PulseAudioVoiceEngine* userdata)
{ {
userdata->m_sinkName = i->default_sink_name; userdata->m_sinkName = i->default_sink_name;
@ -274,7 +302,7 @@ struct PulseAudioVoiceEngine : LinuxMidi
} }
} }
static void _getSinkInfoReply(pa_context *c, const pa_sink_info* i, int eol, PulseAudioVoiceEngine* userdata) static void _getSinkInfoReply(pa_context* c, const pa_sink_info* i, int eol, PulseAudioVoiceEngine* userdata)
{ {
if (!i) if (!i)
return; return;
@ -284,6 +312,59 @@ struct PulseAudioVoiceEngine : LinuxMidi
userdata->_parseAudioChannelSet(&i->channel_map); userdata->_parseAudioChannelSet(&i->channel_map);
} }
mutable std::vector<std::pair<std::string, std::string>> m_sinks;
static void _getSinkInfoListReply(pa_context* c, const pa_sink_info* i, int eol, PulseAudioVoiceEngine* userdata)
{
if (i)
userdata->m_sinks.push_back(std::make_pair(i->name, i->description));
}
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const
{
pa_operation* op = pa_context_get_sink_info_list(m_ctx, pa_sink_info_cb_t(_getSinkInfoListReply), (void*)this);
_paIterate(op);
pa_operation_unref(op);
std::vector<std::pair<std::string, std::string>> ret;
ret.swap(m_sinks);
return ret;
}
std::string getCurrentAudioOutput() const
{
return m_sinkName;
}
bool m_sinkOk = false;
static void _checkAudioSinkReply(pa_context* c, const pa_sink_info* i, int eol, PulseAudioVoiceEngine* userdata)
{
if (i)
userdata->m_sinkOk = true;
}
bool setCurrentAudioOutput(const char* name)
{
m_sinkOk = false;
pa_operation* op;
op = pa_context_get_sink_info_by_name(m_ctx, name, pa_sink_info_cb_t(_checkAudioSinkReply), this);
_paIterate(op);
pa_operation_unref(op);
if (m_sinkOk)
{
m_sinkName = name;
return _setupSink();
}
return false;
}
void _doIterate()
{
int retval;
pa_mainloop_iterate(m_mainloop, 1, &retval);
if (m_handleMove)
{
m_handleMove = false;
_setupSink();
}
}
void pumpAndMixVoices() void pumpAndMixVoices()
{ {
if (!m_stream) if (!m_stream)
@ -307,10 +388,9 @@ struct PulseAudioVoiceEngine : LinuxMidi
size_t writableFrames = writableSz / frameSz; size_t writableFrames = writableSz / frameSz;
size_t writablePeriods = writableFrames / m_mixInfo.m_periodFrames; size_t writablePeriods = writableFrames / m_mixInfo.m_periodFrames;
int retval;
if (!writablePeriods) if (!writablePeriods)
{ {
pa_mainloop_iterate(m_mainloop, 1, &retval); _doIterate();
return; return;
} }
@ -322,7 +402,7 @@ struct PulseAudioVoiceEngine : LinuxMidi
pa_stream_state_t st = pa_stream_get_state(m_stream); pa_stream_state_t st = pa_stream_get_state(m_stream);
Log.report(logvisor::Error, "Unable to pa_stream_begin_write(): %s %d", Log.report(logvisor::Error, "Unable to pa_stream_begin_write(): %s %d",
pa_strerror(pa_context_errno(m_ctx)), st); pa_strerror(pa_context_errno(m_ctx)), st);
pa_mainloop_iterate(m_mainloop, 1, &retval); _doIterate();
return; return;
} }
@ -333,7 +413,7 @@ struct PulseAudioVoiceEngine : LinuxMidi
if (pa_stream_write(m_stream, data, nbytes, nullptr, 0, PA_SEEK_RELATIVE)) if (pa_stream_write(m_stream, data, nbytes, nullptr, 0, PA_SEEK_RELATIVE))
Log.report(logvisor::Error, "Unable to pa_stream_write()"); Log.report(logvisor::Error, "Unable to pa_stream_write()");
pa_mainloop_iterate(m_mainloop, 1, &retval); _doIterate();
} }
}; };

View File

@ -13,7 +13,22 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
return boo::AudioChannelSet::Stereo; return boo::AudioChannelSet::Stereo;
} }
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const std::string getCurrentAudioOutput() const
{
return "wavout";
}
bool setCurrentAudioOutput(const char* name)
{
return false;
}
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const
{
return {{"wavout", "WAVOut"}};
}
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const
{ {
return {}; return {};
} }
@ -161,13 +176,7 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
m_mixInfo.m_periodFrames = periodFrames; m_mixInfo.m_periodFrames = periodFrames;
m_mixInfo.m_sampleRate = sampleRate; m_mixInfo.m_sampleRate = sampleRate;
_buildAudioRenderClient(); _buildAudioRenderClient();
_resetSampleRate();
if (m_voiceHead)
for (boo::AudioVoice& vox : *m_voiceHead)
vox._resetSampleRate(vox.m_sampleRateIn);
if (m_submixHead)
for (boo::AudioSubmix& smx : *m_submixHead)
smx._resetOutputSampleRate();
} }
void pumpAndMixVoices() void pumpAndMixVoices()