From 43376ff4162496a90c354b6774f30dfe54487ca6 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Thu, 28 Jan 2016 13:53:51 -1000 Subject: [PATCH] Initial audio interface classes --- CMakeLists.txt | 19 +- include/boo/audiodev/AudioMatrix.hpp | 56 ++++++ include/boo/audiodev/IAudioVoice.hpp | 64 +++++++ include/boo/audiodev/IAudioVoiceAllocator.hpp | 29 ++++ lib/audiodev/ALSA.cpp | 163 ++++++++++++++++++ lib/audiodev/AQS.cpp | 0 lib/audiodev/AudioMatrix.cpp | 73 ++++++++ lib/audiodev/XAudio2.cpp | 0 8 files changed, 397 insertions(+), 7 deletions(-) create mode 100644 include/boo/audiodev/AudioMatrix.hpp create mode 100644 include/boo/audiodev/IAudioVoice.hpp create mode 100644 include/boo/audiodev/IAudioVoiceAllocator.hpp create mode 100644 lib/audiodev/ALSA.cpp create mode 100644 lib/audiodev/AQS.cpp create mode 100644 lib/audiodev/AudioMatrix.cpp create mode 100644 lib/audiodev/XAudio2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 890db9a..14a4784 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,8 @@ if(WIN32) lib/inputdev/HIDListenerWinUSB.cpp lib/inputdev/HIDDeviceWinUSB.cpp lib/graphicsdev/D3D11.cpp - lib/graphicsdev/D3D12.cpp) + lib/graphicsdev/D3D12.cpp + lib/audiodev/XAudio2.cpp) list(APPEND PLAT_HDRS include/boo/graphicsdev/D3D.hpp) @@ -47,11 +48,12 @@ elseif(APPLE) lib/mac/CocoaCommon.hpp lib/inputdev/HIDListenerIOKit.cpp lib/inputdev/HIDDeviceIOKit.cpp - lib/graphicsdev/Metal.mm) -set_source_files_properties(lib/mac/ApplicationCocoa.mm - lib/mac/WindowCocoa.mm - lib/graphicsdev/Metal.mm - PROPERTIES COMPILE_FLAGS -fobjc-arc) + lib/graphicsdev/Metal.mm + lib/audiodev/AQS.cpp) + set_source_files_properties(lib/mac/ApplicationCocoa.mm + lib/mac/WindowCocoa.mm + lib/graphicsdev/Metal.mm + PROPERTIES COMPILE_FLAGS -fobjc-arc) list(APPEND PLAT_HDRS include/boo/graphicsdev/Metal.hpp) @@ -78,7 +80,8 @@ else(NOT GEKKO) lib/inputdev/HIDDeviceUdev.cpp lib/graphicsdev/GL.cpp lib/graphicsdev/GLX.cpp - lib/graphicsdev/glew.c) + lib/graphicsdev/glew.c + lib/audiodev/ALSA.cpp) # list(APPEND PLAT_HDRS ) @@ -156,6 +159,8 @@ add_library(Boo include/boo/IGraphicsContext.hpp include/boo/graphicsdev/IGraphicsDataFactory.hpp include/boo/graphicsdev/IGraphicsCommandQueue.hpp + include/boo/audiodev/IAudioVoice.hpp + include/boo/audiodev/IAudioVoiceAllocator.hpp include/boo/IWindow.hpp include/boo/IApplication.hpp include/boo/System.hpp diff --git a/include/boo/audiodev/AudioMatrix.hpp b/include/boo/audiodev/AudioMatrix.hpp new file mode 100644 index 0000000..0c062a5 --- /dev/null +++ b/include/boo/audiodev/AudioMatrix.hpp @@ -0,0 +1,56 @@ +#ifndef BOO_AUDIOMATRIX_HPP +#define BOO_AUDIOMATRIX_HPP + +#include "IAudioVoice.hpp" +#include +#include + +namespace boo +{ + +class AudioMatrixMono +{ + AudioChannelSet m_setOut; + float m_coefs[8]; + std::vector m_interleaveBuf; +public: + AudioMatrixMono(AudioChannelSet setOut) + : m_setOut(setOut) {setDefaultMatrixCoefficients();} + + AudioChannelSet setOut() const {return m_setOut;} + void setDefaultMatrixCoefficients(); + void setMatrixCoefficients(const float coefs[8]) + { + for (int i=0 ; i<8 ; ++i) + m_coefs[i] = coefs[i]; + } + + void bufferMonoSampleData(IAudioVoice& voice, const int16_t* data, size_t samples); +}; + +class AudioMatrixStereo +{ + AudioChannelSet m_setOut; + float m_coefs[8][2]; + std::vector m_interleaveBuf; +public: + AudioMatrixStereo(AudioChannelSet setOut) + : m_setOut(setOut) {setDefaultMatrixCoefficients();} + + AudioChannelSet setOut() const {return m_setOut;} + void setDefaultMatrixCoefficients(); + void setMatrixCoefficients(const float coefs[8][2]) + { + for (int i=0 ; i<8 ; ++i) + { + m_coefs[i][0] = coefs[i][0]; + m_coefs[i][1] = coefs[i][1]; + } + } + + void bufferStereoSampleData(IAudioVoice& voice, const int16_t* data, size_t frames); +}; + +} + +#endif // BOO_AUDIOMATRIX_HPP diff --git a/include/boo/audiodev/IAudioVoice.hpp b/include/boo/audiodev/IAudioVoice.hpp new file mode 100644 index 0000000..2393743 --- /dev/null +++ b/include/boo/audiodev/IAudioVoice.hpp @@ -0,0 +1,64 @@ +#ifndef BOO_IAUDIOVOICE_HPP +#define BOO_IAUDIOVOICE_HPP + +#include +#include + +namespace boo +{ + +enum class AudioChannelSet +{ + Stereo, + Quad, + Surround51, + Surround71 +}; + +enum class AudioChannel +{ + FrontLeft, + FrontRight, + RearLeft, + RearRight, + FrontCenter, + LFE, + SideLeft, + SideRight +}; + +struct ChannelMap +{ + unsigned m_channelCount = 0; + AudioChannel m_channels[8] = {}; +}; + +static inline unsigned ChannelCount(AudioChannelSet layout) +{ + switch (layout) + { + case AudioChannelSet::Stereo: + return 2; + case AudioChannelSet::Quad: + return 4; + case AudioChannelSet::Surround51: + return 6; + case AudioChannelSet::Surround71: + return 8; + } + return 0; +} + +struct IAudioVoice +{ + /** Get voice's actual channel-map based on client request and HW capabilities */ + virtual const ChannelMap& channelMap() const=0; + + /** Called by client in response to IAudioVoiceCallback::needsNextBuffer() + * Supplying channel-interleaved sample data */ + virtual void bufferSampleData(const int16_t* data, size_t frames)=0; +}; + +} + +#endif // BOO_IAUDIOVOICE_HPP diff --git a/include/boo/audiodev/IAudioVoiceAllocator.hpp b/include/boo/audiodev/IAudioVoiceAllocator.hpp new file mode 100644 index 0000000..f5e60e4 --- /dev/null +++ b/include/boo/audiodev/IAudioVoiceAllocator.hpp @@ -0,0 +1,29 @@ +#ifndef BOO_IAUDIOVOICEALLOCATOR_HPP +#define BOO_IAUDIOVOICEALLOCATOR_HPP + +#include "IAudioVoice.hpp" +#include + +namespace boo +{ + +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; +}; + +struct IAudioVoiceAllocator +{ + /** 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. */ + virtual std::unique_ptr allocateNewVoice(AudioChannelSet layoutOut, + unsigned sampleRate, + IAudioVoiceCallback* cb)=0; +}; + +} + +#endif // BOO_IAUDIOVOICEALLOCATOR_HPP diff --git a/lib/audiodev/ALSA.cpp b/lib/audiodev/ALSA.cpp new file mode 100644 index 0000000..4f6cb8f --- /dev/null +++ b/lib/audiodev/ALSA.cpp @@ -0,0 +1,163 @@ +#include "boo/audiodev/IAudioVoiceAllocator.hpp" +#include +#include + +namespace boo +{ +static LogVisor::LogModule Log("boo::ALSA"); + +struct ALSAAudioVoice : IAudioVoice +{ + 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) + { + if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC) < 0) + { + Log.report(LogVisor::Error, "unable to allocate ALSA voice"); + return; + } + + unsigned chCount = ChannelCount(set); + int err; + while ((err = snd_pcm_set_params(m_pcm, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, + chCount, sampleRate, 1, 100000)) < 0) + { + if (set == AudioChannelSet::Stereo) + break; + set = AudioChannelSet(int(set) - 1); + chCount = ChannelCount(set); + } + if (err < 0) + { + snd_pcm_close(m_pcm); + m_pcm = nullptr; + Log.report(LogVisor::Error, "unable to set ALSA voice params"); + return; + } + + snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm); + 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* chm = &(*chmap)->map; + uint64_t chBits = 0; + for (int c=0 ; cchannels ; ++c) + chBits |= 1 << chm->pos[c]; + + 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; + } + + if (good) + { + foundChmap = chm; + break; + } + } + } + + 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); + + 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); + } + + ~ALSAAudioVoice() + { + if (m_handler) + snd_async_del_handler(m_handler); + if (m_pcm) + snd_pcm_close(m_pcm); + } + + void bufferSampleData(const int16_t* data, size_t frames) + { + if (m_pcm) + snd_pcm_writei(m_pcm, data, frames); + } +}; + +struct ALSAAudioVoiceAllocator : IAudioVoiceAllocator +{ + std::unique_ptr allocateNewVoice(AudioChannelSet layoutOut, + unsigned sampleRate, + IAudioVoiceCallback* cb) + { + ALSAAudioVoice* newVoice = new ALSAAudioVoice(layoutOut, sampleRate, cb); + std::unique_ptr ret(newVoice); + if (!newVoice->m_pcm) + return {}; + return ret; + } +}; + +} diff --git a/lib/audiodev/AQS.cpp b/lib/audiodev/AQS.cpp new file mode 100644 index 0000000..e69de29 diff --git a/lib/audiodev/AudioMatrix.cpp b/lib/audiodev/AudioMatrix.cpp new file mode 100644 index 0000000..4d7920e --- /dev/null +++ b/lib/audiodev/AudioMatrix.cpp @@ -0,0 +1,73 @@ +#include "boo/audiodev/AudioMatrix.hpp" +#include + +namespace boo +{ + +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; + break; + case AudioChannelSet::Surround51: + case AudioChannelSet::Surround71: + m_coefs[AudioChannel::FrontCenter] = 1.0; + break; + } +} + +void AudioMatrixMono::bufferMonoSampleData(IAudioVoice& voice, const int16_t* data, size_t samples) +{ + const ChannelMap& chmap = voice.channelMap(); + m_interleaveBuf.clear(); + m_interleaveBuf.reserve(samples * chmap.m_channelCount); + for (size_t s=0 ; s