mirror of https://github.com/AxioDL/boo.git
Remove ALSA audio backend
This commit is contained in:
parent
fe7f671c0b
commit
5e58e989a8
|
@ -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")
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue