From e1964f57a936db15b6cf455857cfe5d292092a34 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Mon, 7 Mar 2016 21:09:58 -1000 Subject: [PATCH] Updates to support pumped audio voices (ALSA only for now) --- CMakeLists.txt | 5 +- include/boo/audiodev/AudioMatrix.hpp | 16 +- include/boo/audiodev/IAudioVoice.hpp | 6 +- include/boo/audiodev/IAudioVoiceAllocator.hpp | 13 +- lib/audiodev/ALSA.cpp | 294 +++++++++++++----- lib/audiodev/AQS.cpp | 5 + lib/audiodev/AudioMatrix.cpp | 29 +- lib/audiodev/XAudio2.cpp | 5 + 8 files changed, 258 insertions(+), 115 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4f570..3468b32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ else(NOT GEKKO) endif() include_directories(${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) - list(APPEND _BOO_SYS_LIBS X11 Xi GL ${DBUS_LIBRARY} pthread) + list(APPEND _BOO_SYS_LIBS X11 Xi GL asound ${DBUS_LIBRARY} pthread) unset(VULKAN_LIBRARY CACHE) find_library(VULKAN_LIBRARY vulkan) @@ -172,8 +172,9 @@ add_library(boo lib/inputdev/GenericPad.cpp include/boo/inputdev/GenericPad.hpp lib/inputdev/DeviceBase.cpp include/boo/inputdev/DeviceBase.hpp lib/inputdev/DeviceSignature.cpp include/boo/inputdev/DeviceSignature.hpp - include/boo/inputdev/IHIDListener.hpp lib/inputdev/IHIDDevice.hpp + lib/audiodev/AudioMatrix.cpp + include/boo/inputdev/IHIDListener.hpp include/boo/IGraphicsContext.hpp include/boo/graphicsdev/IGraphicsDataFactory.hpp include/boo/graphicsdev/IGraphicsCommandQueue.hpp diff --git a/include/boo/audiodev/AudioMatrix.hpp b/include/boo/audiodev/AudioMatrix.hpp index 0c062a5..2069de4 100644 --- a/include/boo/audiodev/AudioMatrix.hpp +++ b/include/boo/audiodev/AudioMatrix.hpp @@ -10,14 +10,14 @@ namespace boo class AudioMatrixMono { - AudioChannelSet m_setOut; + AudioChannelSet m_setOut = AudioChannelSet::Stereo; float m_coefs[8]; std::vector m_interleaveBuf; public: - AudioMatrixMono(AudioChannelSet setOut) - : m_setOut(setOut) {setDefaultMatrixCoefficients();} + AudioMatrixMono() {setDefaultMatrixCoefficients();} - AudioChannelSet setOut() const {return m_setOut;} + AudioChannelSet audioChannelSet() const {return m_setOut;} + void setAudioChannelSet(AudioChannelSet set) {m_setOut = set;} void setDefaultMatrixCoefficients(); void setMatrixCoefficients(const float coefs[8]) { @@ -30,14 +30,14 @@ public: class AudioMatrixStereo { - AudioChannelSet m_setOut; + AudioChannelSet m_setOut = AudioChannelSet::Stereo; float m_coefs[8][2]; std::vector m_interleaveBuf; public: - AudioMatrixStereo(AudioChannelSet setOut) - : m_setOut(setOut) {setDefaultMatrixCoefficients();} + AudioMatrixStereo() {setDefaultMatrixCoefficients();} - AudioChannelSet setOut() const {return m_setOut;} + AudioChannelSet audioChannelSet() const {return m_setOut;} + void setAudioChannelSet(AudioChannelSet set) {m_setOut = set;} void setDefaultMatrixCoefficients(); void setMatrixCoefficients(const float coefs[8][2]) { diff --git a/include/boo/audiodev/IAudioVoice.hpp b/include/boo/audiodev/IAudioVoice.hpp index d7822f8..f1c9b76 100644 --- a/include/boo/audiodev/IAudioVoice.hpp +++ b/include/boo/audiodev/IAudioVoice.hpp @@ -12,7 +12,8 @@ enum class AudioChannelSet Stereo, Quad, Surround51, - Surround71 + Surround71, + Unknown = 0xff }; enum class AudioChannel @@ -46,12 +47,15 @@ static inline unsigned ChannelCount(AudioChannelSet layout) return 6; case AudioChannelSet::Surround71: return 8; + default: break; } return 0; } struct IAudioVoice { + virtual ~IAudioVoice() = default; + /** Get voice's actual channel-map based on client request and HW capabilities */ virtual const ChannelMap& channelMap() const=0; diff --git a/include/boo/audiodev/IAudioVoiceAllocator.hpp b/include/boo/audiodev/IAudioVoiceAllocator.hpp index f279b11..2e79835 100644 --- a/include/boo/audiodev/IAudioVoiceAllocator.hpp +++ b/include/boo/audiodev/IAudioVoiceAllocator.hpp @@ -11,11 +11,13 @@ struct IAudioVoiceCallback { /** boo calls this on behalf of the audio platform to request more audio * frames from the client */ - virtual void needsNextBuffer(IAudioVoice* voice, size_t frames)=0; + virtual void needsNextBuffer(IAudioVoice& voice, size_t frames)=0; }; struct IAudioVoiceAllocator { + virtual ~IAudioVoiceAllocator() = default; + /** Client calls this to request allocation of new mixer-voice. * Returns empty unique_ptr if necessary resources aren't available. * ChannelLayout automatically reduces to maximum-supported layout by HW. @@ -25,8 +27,17 @@ struct IAudioVoiceAllocator virtual std::unique_ptr allocateNewVoice(AudioChannelSet layoutOut, unsigned sampleRate, IAudioVoiceCallback* cb)=0; + + /** Client may use this to determine current speaker-setup */ + virtual AudioChannelSet getAvailableSet()=0; + + /** Ensure all voices' platform buffers are filled as much as possible */ + virtual void pumpVoices()=0; }; +/** Obtain host platform's voice allocator */ +std::unique_ptr NewAudioVoiceAllocator(); + } #endif // BOO_IAUDIOVOICEALLOCATOR_HPP diff --git a/lib/audiodev/ALSA.cpp b/lib/audiodev/ALSA.cpp index 7067453..234bb2d 100644 --- a/lib/audiodev/ALSA.cpp +++ b/lib/audiodev/ALSA.cpp @@ -1,31 +1,32 @@ +#include +#include #include "boo/audiodev/IAudioVoiceAllocator.hpp" #include "logvisor/logvisor.hpp" #include +#include namespace boo { static logvisor::Module Log("boo::ALSA"); +struct ALSAAudioVoiceAllocator; struct ALSAAudioVoice : IAudioVoice { + ALSAAudioVoiceAllocator& m_parent; + std::list::iterator m_parentIt; + ChannelMap m_map; IAudioVoiceCallback* m_cb; snd_pcm_t* m_pcm = nullptr; - snd_async_handler_t* m_handler = nullptr; snd_pcm_uframes_t m_bufSize; snd_pcm_uframes_t m_periodSize; const ChannelMap& channelMap() const {return m_map;} - static void Callback(snd_async_handler_t* handler) - { - ALSAAudioVoice* voice = static_cast(snd_async_handler_get_callback_private(handler)); - voice->m_cb->needsNextBuffer(voice, voice->m_periodSize); - } - - ALSAAudioVoice(AudioChannelSet set, unsigned sampleRate, IAudioVoiceCallback* cb) - : m_cb(cb) + ALSAAudioVoice(ALSAAudioVoiceAllocator& parent, AudioChannelSet set, + unsigned sampleRate, IAudioVoiceCallback* cb) + : m_parent(parent), m_cb(cb) { if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC) < 0) { @@ -52,96 +53,92 @@ struct ALSAAudioVoice : IAudioVoice } snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm); - if (!chmaps) + if (chmaps) { - snd_pcm_close(m_pcm); - m_pcm = nullptr; - Log.report(logvisor::Error, "unable to query ALSA voice chmaps"); - return; - } - 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* foundChmap = nullptr; + for (snd_pcm_chmap_query_t** chmap = chmaps ; *chmap != nullptr ; ++chmap) { - snd_pcm_chmap_t* chm = &(*chmap)->map; - uint64_t chBits = 0; - for (int c=0 ; cchannels ; ++c) - chBits |= 1 << chm->pos[c]; - - bool good = false; - switch (set) + if ((*chmap)->map.channels == chCount) { - case AudioChannelSet::Stereo: - if ((chBits & (1 << SND_CHMAP_FL)) != 0 && - (chBits & (1 << SND_CHMAP_FR)) != 0) - good = true; - break; - case AudioChannelSet::Quad: - if ((chBits & (1 << SND_CHMAP_FL)) != 0 && - (chBits & (1 << SND_CHMAP_FR)) != 0 && - (chBits & (1 << SND_CHMAP_RL)) != 0 && - (chBits & (1 << SND_CHMAP_RR)) != 0) - good = true; - break; - case AudioChannelSet::Surround51: - if ((chBits & (1 << SND_CHMAP_FL)) != 0 && - (chBits & (1 << SND_CHMAP_FR)) != 0 && - (chBits & (1 << SND_CHMAP_RL)) != 0 && - (chBits & (1 << SND_CHMAP_RR)) != 0 && - (chBits & (1 << SND_CHMAP_FC)) != 0 && - (chBits & (1 << SND_CHMAP_LFE)) != 0) - good = true; - break; - case AudioChannelSet::Surround71: - if ((chBits & (1 << SND_CHMAP_FL)) != 0 && - (chBits & (1 << SND_CHMAP_FR)) != 0 && - (chBits & (1 << SND_CHMAP_RL)) != 0 && - (chBits & (1 << SND_CHMAP_RR)) != 0 && - (chBits & (1 << SND_CHMAP_FC)) != 0 && - (chBits & (1 << SND_CHMAP_LFE)) != 0 && - (chBits & (1 << SND_CHMAP_SL)) != 0 && - (chBits & (1 << SND_CHMAP_SR)) != 0) - good = true; - break; - } + snd_pcm_chmap_t* chm = &(*chmap)->map; + uint64_t chBits = 0; + for (int c=0 ; cchannels ; ++c) + chBits |= 1 << chm->pos[c]; - if (good) - { - foundChmap = chm; - break; + bool good = false; + switch (set) + { + case AudioChannelSet::Stereo: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0) + good = true; + break; + case AudioChannelSet::Quad: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0) + good = true; + break; + case AudioChannelSet::Surround51: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0 && + (chBits & (1 << SND_CHMAP_FC)) != 0 && + (chBits & (1 << SND_CHMAP_LFE)) != 0) + good = true; + break; + case AudioChannelSet::Surround71: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0 && + (chBits & (1 << SND_CHMAP_FC)) != 0 && + (chBits & (1 << SND_CHMAP_LFE)) != 0 && + (chBits & (1 << SND_CHMAP_SL)) != 0 && + (chBits & (1 << SND_CHMAP_SR)) != 0) + good = true; + break; + default: break; + } + + if (good) + { + foundChmap = chm; + break; + } } } - } - if (!foundChmap) - { - snd_pcm_close(m_pcm); - m_pcm = nullptr; + if (!foundChmap) + { + snd_pcm_close(m_pcm); + m_pcm = nullptr; + snd_pcm_free_chmaps(chmaps); + Log.report(logvisor::Error, "unable to find matching ALSA voice chmap"); + return; + } + m_map.m_channelCount = chCount; + for (int c=0 ; cchannels ; ++c) + m_map.m_channels[c] = AudioChannel(foundChmap->pos[c] - 3); + snd_pcm_set_chmap(m_pcm, foundChmap); snd_pcm_free_chmaps(chmaps); - Log.report(logvisor::Error, "unable to find matching ALSA voice chmap"); - return; } - m_map.m_channelCount = chCount; - for (int c=0 ; cchannels ; ++c) - m_map.m_channels[c] = AudioChannel(foundChmap->pos[c] - 3); - snd_pcm_set_chmap(m_pcm, foundChmap); - snd_pcm_free_chmaps(chmaps); + else + { + m_map.m_channelCount = 2; + m_map.m_channels[0] = AudioChannel::FrontLeft; + m_map.m_channels[1] = AudioChannel::FrontRight; + } - snd_async_add_pcm_handler(&m_handler, m_pcm, snd_async_callback_t(Callback), this); snd_pcm_get_params(m_pcm, &m_bufSize, &m_periodSize); + snd_pcm_prepare(m_pcm); - for (int i=0 ; i<3 ; ++i) - m_cb->needsNextBuffer(this, m_periodSize); + pump(); } - ~ALSAAudioVoice() - { - if (m_handler) - snd_async_del_handler(m_handler); - if (m_pcm) - snd_pcm_close(m_pcm); - } + ~ALSAAudioVoice(); void bufferSampleData(const int16_t* data, size_t frames) { @@ -160,20 +157,145 @@ struct ALSAAudioVoice : IAudioVoice if (m_pcm) snd_pcm_drain(m_pcm); } + + void pump() + { + snd_pcm_sframes_t frames = snd_pcm_avail(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(m_pcm); + fprintf(stderr, "REC %ld\n", frames); + } + else + return; + } + if (frames < 0) + return; + snd_pcm_sframes_t buffers = frames / m_periodSize; + for (snd_pcm_sframes_t b=0 ; bneedsNextBuffer(*this, m_periodSize); + } }; struct ALSAAudioVoiceAllocator : IAudioVoiceAllocator { + std::list m_allocatedVoices; + std::unique_ptr allocateNewVoice(AudioChannelSet layoutOut, unsigned sampleRate, IAudioVoiceCallback* cb) { - ALSAAudioVoice* newVoice = new ALSAAudioVoice(layoutOut, sampleRate, cb); + ALSAAudioVoice* newVoice = new ALSAAudioVoice(*this, layoutOut, sampleRate, cb); + newVoice->m_parentIt = m_allocatedVoices.insert(m_allocatedVoices.end(), newVoice); std::unique_ptr ret(newVoice); if (!newVoice->m_pcm) return {}; return ret; } + + AudioChannelSet getAvailableSet() + { + snd_pcm_t* pcm; + if (snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC) < 0) + { + Log.report(logvisor::Error, "unable to allocate ALSA voice"); + return AudioChannelSet::Unknown; + } + + snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(pcm); + if (!chmaps) + { + snd_pcm_close(pcm); + return AudioChannelSet::Stereo; + } + static const std::array 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 (int c=0 ; cchannels ; ++c) + chBits |= 1 << chm->pos[c]; + + switch (set) + { + case AudioChannelSet::Stereo: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0) + { + snd_pcm_free_chmaps(chmaps); + return AudioChannelSet::Stereo; + } + break; + case AudioChannelSet::Quad: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0) + { + snd_pcm_free_chmaps(chmaps); + return AudioChannelSet::Quad; + } + break; + case AudioChannelSet::Surround51: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0 && + (chBits & (1 << SND_CHMAP_FC)) != 0 && + (chBits & (1 << SND_CHMAP_LFE)) != 0) + { + snd_pcm_free_chmaps(chmaps); + return AudioChannelSet::Surround51; + } + break; + case AudioChannelSet::Surround71: + if ((chBits & (1 << SND_CHMAP_FL)) != 0 && + (chBits & (1 << SND_CHMAP_FR)) != 0 && + (chBits & (1 << SND_CHMAP_RL)) != 0 && + (chBits & (1 << SND_CHMAP_RR)) != 0 && + (chBits & (1 << SND_CHMAP_FC)) != 0 && + (chBits & (1 << SND_CHMAP_LFE)) != 0 && + (chBits & (1 << SND_CHMAP_SL)) != 0 && + (chBits & (1 << SND_CHMAP_SR)) != 0) + { + snd_pcm_free_chmaps(chmaps); + return AudioChannelSet::Surround71; + } + break; + default: break; + } + } + } + + snd_pcm_free_chmaps(chmaps); + return AudioChannelSet::Unknown; + } + + void pumpVoices() + { + for (ALSAAudioVoice* vox : m_allocatedVoices) + vox->pump(); + } }; +ALSAAudioVoice::~ALSAAudioVoice() +{ + if (m_pcm) + snd_pcm_close(m_pcm); + m_parent.m_allocatedVoices.erase(m_parentIt); +} + +std::unique_ptr NewAudioVoiceAllocator() +{ + return std::make_unique(); +} + } diff --git a/lib/audiodev/AQS.cpp b/lib/audiodev/AQS.cpp index a5ba772..3df0b98 100644 --- a/lib/audiodev/AQS.cpp +++ b/lib/audiodev/AQS.cpp @@ -218,4 +218,9 @@ struct AQSAudioVoiceAllocator : IAudioVoiceAllocator } }; +std::unique_ptr NewAudioVoiceAllocator() +{ + return std::make_unique(); +} + } diff --git a/lib/audiodev/AudioMatrix.cpp b/lib/audiodev/AudioMatrix.cpp index 8ded57d..a01b730 100644 --- a/lib/audiodev/AudioMatrix.cpp +++ b/lib/audiodev/AudioMatrix.cpp @@ -9,16 +9,16 @@ void AudioMatrixMono::setDefaultMatrixCoefficients() memset(m_coefs, 0, sizeof(m_coefs)); switch (m_setOut) { - case AudioChannelSet::Mono: case AudioChannelSet::Stereo: case AudioChannelSet::Quad: - m_coefs[AudioChannel::FrontLeft] = 1.0; - m_coefs[AudioChannel::FrontRight] = 1.0; + m_coefs[int(AudioChannel::FrontLeft)] = 1.0; + m_coefs[int(AudioChannel::FrontRight)] = 1.0; break; case AudioChannelSet::Surround51: case AudioChannelSet::Surround71: - m_coefs[AudioChannel::FrontCenter] = 1.0; + m_coefs[int(AudioChannel::FrontCenter)] = 1.0; break; + default: break; } } @@ -34,7 +34,7 @@ void AudioMatrixMono::bufferMonoSampleData(IAudioVoice& voice, const int16_t* da if (ch == AudioChannel::Unknown) m_interleaveBuf.push_back(0); else - m_interleaveBuf.push_back(data[0] * m_coefs[ch]); + m_interleaveBuf.push_back(data[0] * m_coefs[int(ch)]); } voice.bufferSampleData(m_interleaveBuf.data(), samples); } @@ -44,22 +44,17 @@ void AudioMatrixStereo::setDefaultMatrixCoefficients() memset(m_coefs, 0, sizeof(m_coefs)); switch (m_setOut) { - case AudioChannelSet::Mono: - m_coefs[AudioChannel::FrontLeft][0] = 0.5; - m_coefs[AudioChannel::FrontLeft][1] = 0.5; - m_coefs[AudioChannel::FrontRight][0] = 0.5; - m_coefs[AudioChannel::FrontRight][1] = 0.5; - break; case AudioChannelSet::Stereo: case AudioChannelSet::Quad: - m_coefs[AudioChannel::FrontLeft][0] = 1.0; - m_coefs[AudioChannel::FrontRight][1] = 1.0; + m_coefs[int(AudioChannel::FrontLeft)][0] = 1.0; + m_coefs[int(AudioChannel::FrontRight)][1] = 1.0; break; case AudioChannelSet::Surround51: case AudioChannelSet::Surround71: - m_coefs[AudioChannel::FrontLeft][0] = 1.0; - m_coefs[AudioChannel::FrontRight][1] = 1.0; + m_coefs[int(AudioChannel::FrontLeft)][0] = 1.0; + m_coefs[int(AudioChannel::FrontRight)][1] = 1.0; break; + default: break; } } @@ -75,8 +70,8 @@ void AudioMatrixStereo::bufferStereoSampleData(IAudioVoice& voice, const int16_t if (ch == AudioChannel::Unknown) m_interleaveBuf.push_back(0); else - m_interleaveBuf.push_back(data[0] * m_coefs[ch][0] + - data[1] * m_coefs[ch][1]); + m_interleaveBuf.push_back(data[0] * m_coefs[int(ch)][0] + + data[1] * m_coefs[int(ch)][1]); } voice.bufferSampleData(m_interleaveBuf.data(), frames); } diff --git a/lib/audiodev/XAudio2.cpp b/lib/audiodev/XAudio2.cpp index 3f9e6bd..0253204 100644 --- a/lib/audiodev/XAudio2.cpp +++ b/lib/audiodev/XAudio2.cpp @@ -203,4 +203,9 @@ struct XA2AudioVoiceAllocator : IAudioVoiceAllocator } }; +std::unique_ptr NewAudioVoiceAllocator() +{ + return std::make_unique(); +} + }