diff options
author | Chris Robinson <[email protected]> | 2019-07-28 18:56:04 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2019-07-28 18:56:04 -0700 |
commit | cb3e96e75640730b9391f0d2d922eecd9ee2ce79 (patch) | |
tree | 23520551bddb2a80354e44da47f54201fdc084f0 /Alc/backends | |
parent | 93e60919c8f387c36c267ca9faa1ac653254aea6 (diff) |
Rename Alc to alc
Diffstat (limited to 'Alc/backends')
36 files changed, 0 insertions, 12680 deletions
diff --git a/Alc/backends/alsa.cpp b/Alc/backends/alsa.cpp deleted file mode 100644 index c133df68..00000000 --- a/Alc/backends/alsa.cpp +++ /dev/null @@ -1,1288 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/alsa.h" - -#include <algorithm> -#include <atomic> -#include <cassert> -#include <cerrno> -#include <chrono> -#include <cstring> -#include <exception> -#include <functional> -#include <memory> -#include <string> -#include <thread> -#include <utility> - -#include "AL/al.h" - -#include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alu.h" -#include "compat.h" -#include "logging.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" - -#include <alsa/asoundlib.h> - - -namespace { - -constexpr ALCchar alsaDevice[] = "ALSA Default"; - - -#ifdef HAVE_DYNLOAD -#define ALSA_FUNCS(MAGIC) \ - MAGIC(snd_strerror); \ - MAGIC(snd_pcm_open); \ - MAGIC(snd_pcm_close); \ - MAGIC(snd_pcm_nonblock); \ - MAGIC(snd_pcm_frames_to_bytes); \ - MAGIC(snd_pcm_bytes_to_frames); \ - MAGIC(snd_pcm_hw_params_malloc); \ - MAGIC(snd_pcm_hw_params_free); \ - MAGIC(snd_pcm_hw_params_any); \ - MAGIC(snd_pcm_hw_params_current); \ - MAGIC(snd_pcm_hw_params_set_access); \ - MAGIC(snd_pcm_hw_params_set_format); \ - MAGIC(snd_pcm_hw_params_set_channels); \ - MAGIC(snd_pcm_hw_params_set_periods_near); \ - MAGIC(snd_pcm_hw_params_set_rate_near); \ - MAGIC(snd_pcm_hw_params_set_rate); \ - MAGIC(snd_pcm_hw_params_set_rate_resample); \ - MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ - MAGIC(snd_pcm_hw_params_set_period_time_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ - MAGIC(snd_pcm_hw_params_set_period_size_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ - MAGIC(snd_pcm_hw_params_get_period_time_min); \ - MAGIC(snd_pcm_hw_params_get_period_time_max); \ - MAGIC(snd_pcm_hw_params_get_buffer_size); \ - MAGIC(snd_pcm_hw_params_get_period_size); \ - MAGIC(snd_pcm_hw_params_get_access); \ - MAGIC(snd_pcm_hw_params_get_periods); \ - MAGIC(snd_pcm_hw_params_test_format); \ - MAGIC(snd_pcm_hw_params_test_channels); \ - MAGIC(snd_pcm_hw_params); \ - MAGIC(snd_pcm_sw_params_malloc); \ - MAGIC(snd_pcm_sw_params_current); \ - MAGIC(snd_pcm_sw_params_set_avail_min); \ - MAGIC(snd_pcm_sw_params_set_stop_threshold); \ - MAGIC(snd_pcm_sw_params); \ - MAGIC(snd_pcm_sw_params_free); \ - MAGIC(snd_pcm_prepare); \ - MAGIC(snd_pcm_start); \ - MAGIC(snd_pcm_resume); \ - MAGIC(snd_pcm_reset); \ - MAGIC(snd_pcm_wait); \ - MAGIC(snd_pcm_delay); \ - MAGIC(snd_pcm_state); \ - MAGIC(snd_pcm_avail_update); \ - MAGIC(snd_pcm_areas_silence); \ - MAGIC(snd_pcm_mmap_begin); \ - MAGIC(snd_pcm_mmap_commit); \ - MAGIC(snd_pcm_readi); \ - MAGIC(snd_pcm_writei); \ - MAGIC(snd_pcm_drain); \ - MAGIC(snd_pcm_drop); \ - MAGIC(snd_pcm_recover); \ - MAGIC(snd_pcm_info_malloc); \ - MAGIC(snd_pcm_info_free); \ - MAGIC(snd_pcm_info_set_device); \ - MAGIC(snd_pcm_info_set_subdevice); \ - MAGIC(snd_pcm_info_set_stream); \ - MAGIC(snd_pcm_info_get_name); \ - MAGIC(snd_ctl_pcm_next_device); \ - MAGIC(snd_ctl_pcm_info); \ - MAGIC(snd_ctl_open); \ - MAGIC(snd_ctl_close); \ - MAGIC(snd_ctl_card_info_malloc); \ - MAGIC(snd_ctl_card_info_free); \ - MAGIC(snd_ctl_card_info); \ - MAGIC(snd_ctl_card_info_get_name); \ - MAGIC(snd_ctl_card_info_get_id); \ - MAGIC(snd_card_next); \ - MAGIC(snd_config_update_free_global) - -static void *alsa_handle; -#define MAKE_FUNC(f) decltype(f) * p##f -ALSA_FUNCS(MAKE_FUNC); -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define snd_strerror psnd_strerror -#define snd_pcm_open psnd_pcm_open -#define snd_pcm_close psnd_pcm_close -#define snd_pcm_nonblock psnd_pcm_nonblock -#define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes -#define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames -#define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc -#define snd_pcm_hw_params_free psnd_pcm_hw_params_free -#define snd_pcm_hw_params_any psnd_pcm_hw_params_any -#define snd_pcm_hw_params_current psnd_pcm_hw_params_current -#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access -#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format -#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels -#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near -#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near -#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate -#define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample -#define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near -#define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near -#define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near -#define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near -#define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min -#define snd_pcm_hw_params_get_buffer_time_min psnd_pcm_hw_params_get_buffer_time_min -#define snd_pcm_hw_params_get_buffer_time_max psnd_pcm_hw_params_get_buffer_time_max -#define snd_pcm_hw_params_get_period_time_min psnd_pcm_hw_params_get_period_time_min -#define snd_pcm_hw_params_get_period_time_max psnd_pcm_hw_params_get_period_time_max -#define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size -#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size -#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access -#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods -#define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format -#define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels -#define snd_pcm_hw_params psnd_pcm_hw_params -#define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc -#define snd_pcm_sw_params_current psnd_pcm_sw_params_current -#define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min -#define snd_pcm_sw_params_set_stop_threshold psnd_pcm_sw_params_set_stop_threshold -#define snd_pcm_sw_params psnd_pcm_sw_params -#define snd_pcm_sw_params_free psnd_pcm_sw_params_free -#define snd_pcm_prepare psnd_pcm_prepare -#define snd_pcm_start psnd_pcm_start -#define snd_pcm_resume psnd_pcm_resume -#define snd_pcm_reset psnd_pcm_reset -#define snd_pcm_wait psnd_pcm_wait -#define snd_pcm_delay psnd_pcm_delay -#define snd_pcm_state psnd_pcm_state -#define snd_pcm_avail_update psnd_pcm_avail_update -#define snd_pcm_areas_silence psnd_pcm_areas_silence -#define snd_pcm_mmap_begin psnd_pcm_mmap_begin -#define snd_pcm_mmap_commit psnd_pcm_mmap_commit -#define snd_pcm_readi psnd_pcm_readi -#define snd_pcm_writei psnd_pcm_writei -#define snd_pcm_drain psnd_pcm_drain -#define snd_pcm_drop psnd_pcm_drop -#define snd_pcm_recover psnd_pcm_recover -#define snd_pcm_info_malloc psnd_pcm_info_malloc -#define snd_pcm_info_free psnd_pcm_info_free -#define snd_pcm_info_set_device psnd_pcm_info_set_device -#define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice -#define snd_pcm_info_set_stream psnd_pcm_info_set_stream -#define snd_pcm_info_get_name psnd_pcm_info_get_name -#define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device -#define snd_ctl_pcm_info psnd_ctl_pcm_info -#define snd_ctl_open psnd_ctl_open -#define snd_ctl_close psnd_ctl_close -#define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc -#define snd_ctl_card_info_free psnd_ctl_card_info_free -#define snd_ctl_card_info psnd_ctl_card_info -#define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name -#define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id -#define snd_card_next psnd_card_next -#define snd_config_update_free_global psnd_config_update_free_global -#endif -#endif - - -struct DevMap { - std::string name; - std::string device_name; -}; - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -const char *prefix_name(snd_pcm_stream_t stream) -{ - assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE); - return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix"; -} - -al::vector<DevMap> probe_devices(snd_pcm_stream_t stream) -{ - al::vector<DevMap> devlist; - - snd_ctl_card_info_t *info; - snd_ctl_card_info_malloc(&info); - snd_pcm_info_t *pcminfo; - snd_pcm_info_malloc(&pcminfo); - - devlist.emplace_back(DevMap{alsaDevice, - GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture", - "default")}); - - if(stream == SND_PCM_STREAM_PLAYBACK) - { - const char *customdevs; - const char *next{GetConfigValue(nullptr, "alsa", "custom-devices", "")}; - while((customdevs=next) != nullptr && customdevs[0]) - { - next = strchr(customdevs, ';'); - const char *sep{strchr(customdevs, '=')}; - if(!sep) - { - std::string spec{next ? std::string(customdevs, next++) : std::string(customdevs)}; - ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); - continue; - } - - const char *oldsep{sep++}; - devlist.emplace_back(DevMap{std::string(customdevs, oldsep), - next ? std::string(sep, next++) : std::string(sep)}); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); - } - } - - const std::string main_prefix{ - ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; - - int card{-1}; - int err{snd_card_next(&card)}; - for(;err >= 0 && card >= 0;err = snd_card_next(&card)) - { - std::string name{"hw:" + std::to_string(card)}; - - snd_ctl_t *handle; - if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0) - { - ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); - continue; - } - if((err=snd_ctl_card_info(handle, info)) < 0) - { - ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); - snd_ctl_close(handle); - continue; - } - - const char *cardname{snd_ctl_card_info_get_name(info)}; - const char *cardid{snd_ctl_card_info_get_id(info)}; - name = prefix_name(stream); - name += '-'; - name += cardid; - const std::string card_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; - - int dev{-1}; - while(1) - { - if(snd_ctl_pcm_next_device(handle, &dev) < 0) - ERR("snd_ctl_pcm_next_device failed\n"); - if(dev < 0) break; - - snd_pcm_info_set_device(pcminfo, dev); - snd_pcm_info_set_subdevice(pcminfo, 0); - snd_pcm_info_set_stream(pcminfo, stream); - if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) - { - if(err != -ENOENT) - ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); - continue; - } - - /* "prefix-cardid-dev" */ - name = prefix_name(stream); - name += '-'; - name += cardid; - name += '-'; - name += std::to_string(dev); - const std::string device_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)}; - - /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ - name = cardname; - name += ", "; - name += snd_pcm_info_get_name(pcminfo); - name += " (CARD="; - name += cardid; - name += ",DEV="; - name += std::to_string(dev); - name += ')'; - - /* "devprefixCARD=cardid,DEV=dev" */ - std::string device{device_prefix}; - device += "CARD="; - device += cardid; - device += ",DEV="; - device += std::to_string(dev); - - devlist.emplace_back(DevMap{std::move(name), std::move(device)}); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); - } - snd_ctl_close(handle); - } - if(err < 0) - ERR("snd_card_next failed: %s\n", snd_strerror(err)); - - snd_pcm_info_free(pcminfo); - snd_ctl_card_info_free(info); - - return devlist; -} - - -int verify_state(snd_pcm_t *handle) -{ - snd_pcm_state_t state{snd_pcm_state(handle)}; - - int err; - switch(state) - { - case SND_PCM_STATE_OPEN: - case SND_PCM_STATE_SETUP: - case SND_PCM_STATE_PREPARED: - case SND_PCM_STATE_RUNNING: - case SND_PCM_STATE_DRAINING: - case SND_PCM_STATE_PAUSED: - /* All Okay */ - break; - - case SND_PCM_STATE_XRUN: - if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0) - return err; - break; - case SND_PCM_STATE_SUSPENDED: - if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0) - return err; - break; - case SND_PCM_STATE_DISCONNECTED: - return -ENODEV; - } - - return state; -} - - -struct AlsaPlayback final : public BackendBase { - AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~AlsaPlayback() override; - - int mixerProc(); - int mixerNoMMapProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - ClockLatency getClockLatency() override; - - snd_pcm_t *mPcmHandle{nullptr}; - - al::vector<char> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(AlsaPlayback) -}; - -AlsaPlayback::~AlsaPlayback() -{ - if(mPcmHandle) - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; -} - - -int AlsaPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t num_updates{mDevice->BufferSize / update_size}; - while(!mKillNow.load(std::memory_order_acquire)) - { - int state{verify_state(mPcmHandle)}; - if(state < 0) - { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); - break; - } - - snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; - if(avail < 0) - { - ERR("available update failed: %s\n", snd_strerror(avail)); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) > update_size*(num_updates+1)) - { - WARN("available samples exceeds the buffer size\n"); - snd_pcm_reset(mPcmHandle); - continue; - } - - // make sure there's frames to process - if(static_cast<snd_pcm_uframes_t>(avail) < update_size) - { - if(state != SND_PCM_STATE_RUNNING) - { - int err{snd_pcm_start(mPcmHandle)}; - if(err < 0) - { - ERR("start failed: %s\n", snd_strerror(err)); - continue; - } - } - if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); - continue; - } - avail -= avail%update_size; - - // it is possible that contiguous areas are smaller, thus we use a loop - lock(); - while(avail > 0) - { - snd_pcm_uframes_t frames{static_cast<snd_pcm_uframes_t>(avail)}; - - const snd_pcm_channel_area_t *areas{}; - snd_pcm_uframes_t offset{}; - int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; - if(err < 0) - { - ERR("mmap begin error: %s\n", snd_strerror(err)); - break; - } - - char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)}; - aluMixData(mDevice, WritePtr, frames); - - snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; - if(commitres < 0 || (commitres-frames) != 0) - { - ERR("mmap commit error: %s\n", - snd_strerror(commitres >= 0 ? -EPIPE : commitres)); - break; - } - - avail -= frames; - } - unlock(); - } - - return 0; -} - -int AlsaPlayback::mixerNoMMapProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; - while(!mKillNow.load(std::memory_order_acquire)) - { - int state{verify_state(mPcmHandle)}; - if(state < 0) - { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); - break; - } - - snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; - if(avail < 0) - { - ERR("available update failed: %s\n", snd_strerror(avail)); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size) - { - WARN("available samples exceeds the buffer size\n"); - snd_pcm_reset(mPcmHandle); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) < update_size) - { - if(state != SND_PCM_STATE_RUNNING) - { - int err{snd_pcm_start(mPcmHandle)}; - if(err < 0) - { - ERR("start failed: %s\n", snd_strerror(err)); - continue; - } - } - if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); - continue; - } - - lock(); - char *WritePtr{mBuffer.data()}; - avail = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - aluMixData(mDevice, WritePtr, avail); - while(avail > 0) - { - snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, avail)}; - switch(ret) - { - case -EAGAIN: - continue; -#if ESTRPIPE != EPIPE - case -ESTRPIPE: -#endif - case -EPIPE: - case -EINTR: - ret = snd_pcm_recover(mPcmHandle, ret, 1); - if(ret < 0) - avail = 0; - break; - default: - if(ret >= 0) - { - WritePtr += snd_pcm_frames_to_bytes(mPcmHandle, ret); - avail -= ret; - } - break; - } - if(ret < 0) - { - ret = snd_pcm_prepare(mPcmHandle); - if(ret < 0) break; - } - } - unlock(); - } - - return 0; -} - - -ALCenum AlsaPlayback::open(const ALCchar *name) -{ - const char *driver{}; - if(name) - { - if(PlaybackDevices.empty()) - PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); - - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - driver = iter->device_name.c_str(); - } - else - { - name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "device", "default"); - } - - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; - if(err < 0) - { - ERR("Could not open playback device '%s': %s\n", driver, snd_strerror(err)); - return ALC_OUT_OF_MEMORY; - } - - /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ - snd_config_update_free_global(); - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean AlsaPlayback::reset() -{ - snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; - switch(mDevice->FmtType) - { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; - } - - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)}; - ALuint periodLen{static_cast<ALuint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; - ALuint bufferLen{static_cast<ALuint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; - ALuint rate{mDevice->Frequency}; - - snd_pcm_uframes_t periodSizeInFrames{}; - snd_pcm_uframes_t bufferSizeInFrames{}; - snd_pcm_sw_params_t *sp{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access{}; - const char *funcerr{}; - int err{}; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); - /* set interleaved access */ - if(!allowmmap || snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) - { - /* No mmap */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); - } - /* test and set format (implicitly sets sample bits) */ - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) < 0) - { - static const struct { - snd_pcm_format_t format; - DevFmtType fmttype; - } formatlist[] = { - { SND_PCM_FORMAT_FLOAT, DevFmtFloat }, - { SND_PCM_FORMAT_S32, DevFmtInt }, - { SND_PCM_FORMAT_U32, DevFmtUInt }, - { SND_PCM_FORMAT_S16, DevFmtShort }, - { SND_PCM_FORMAT_U16, DevFmtUShort }, - { SND_PCM_FORMAT_S8, DevFmtByte }, - { SND_PCM_FORMAT_U8, DevFmtUByte }, - }; - - for(const auto &fmt : formatlist) - { - format = fmt.format; - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) >= 0) - { - mDevice->FmtType = fmt.fmttype; - break; - } - } - } - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); - /* test and set channels (implicitly sets frame bits) */ - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, mDevice->channelsFromFmt()) < 0) - { - static const DevFmtChannels channellist[] = { - DevFmtStereo, - DevFmtQuad, - DevFmtX51, - DevFmtX71, - DevFmtMono, - }; - - for(const auto &chan : channellist) - { - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, ChannelsFromDevFmt(chan, 0)) >= 0) - { - mDevice->FmtChans = chan; - mDevice->mAmbiOrder = 0; - break; - } - } - } - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); - /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) || - !mDevice->Flags.get<FrequencyRequest>()) - { - if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 0) < 0) - ERR("Failed to disable ALSA resampler\n"); - } - else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 1) < 0) - ERR("Failed to enable ALSA resampler\n"); - CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp, &rate, nullptr)); - /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp, &periodLen, nullptr)) < 0) - ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); - /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp, &bufferLen, nullptr)) < 0) - ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); - /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); - - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); - CHECK(snd_pcm_hw_params_get_buffer_size(hp, &bufferSizeInFrames)); - snd_pcm_hw_params_free(hp); - hp = nullptr; - - snd_pcm_sw_params_malloc(&sp); - CHECK(snd_pcm_sw_params_current(mPcmHandle, sp)); - CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp, periodSizeInFrames)); - CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp, bufferSizeInFrames)); - CHECK(snd_pcm_sw_params(mPcmHandle, sp)); -#undef CHECK - snd_pcm_sw_params_free(sp); - sp = nullptr; - - mDevice->BufferSize = bufferSizeInFrames; - mDevice->UpdateSize = periodSizeInFrames; - mDevice->Frequency = rate; - - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; - -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - if(sp) snd_pcm_sw_params_free(sp); - return ALC_FALSE; -} - -ALCboolean AlsaPlayback::start() -{ - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access; - const char *funcerr; - int err; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_current(mPcmHandle, hp)); - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); -#undef CHECK - if(0) - { - error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - return ALC_FALSE; - } - snd_pcm_hw_params_free(hp); - hp = nullptr; - - int (AlsaPlayback::*thread_func)(){}; - if(access == SND_PCM_ACCESS_RW_INTERLEAVED) - { - mBuffer.resize(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)); - thread_func = &AlsaPlayback::mixerNoMMapProc; - } - else - { - err = snd_pcm_prepare(mPcmHandle); - if(err < 0) - { - ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err)); - return ALC_FALSE; - } - thread_func = &AlsaPlayback::mixerProc; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(thread_func), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - mBuffer.clear(); - return ALC_FALSE; -} - -void AlsaPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - mBuffer.clear(); -} - -ClockLatency AlsaPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - snd_pcm_sframes_t delay{}; - int err{snd_pcm_delay(mPcmHandle, &delay)}; - if(err < 0) - { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); - delay = 0; - } - ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct AlsaCapture final : public BackendBase { - AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~AlsaCapture() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - ClockLatency getClockLatency() override; - - snd_pcm_t *mPcmHandle{nullptr}; - - al::vector<char> mBuffer; - - bool mDoCapture{false}; - RingBufferPtr mRing{nullptr}; - - snd_pcm_sframes_t mLastAvail{0}; - - DEF_NEWDEL(AlsaCapture) -}; - -AlsaCapture::~AlsaCapture() -{ - if(mPcmHandle) - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; -} - - -ALCenum AlsaCapture::open(const ALCchar *name) -{ - const char *driver{}; - if(name) - { - if(CaptureDevices.empty()) - CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); - - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - driver = iter->device_name.c_str(); - } - else - { - name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "capture", "default"); - } - - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; - if(err < 0) - { - ERR("Could not open capture device '%s': %s\n", driver, snd_strerror(err)); - return ALC_INVALID_VALUE; - } - - /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ - snd_config_update_free_global(); - - snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; - switch(mDevice->FmtType) - { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; - } - - snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(bufferSizeInFrames, 25*mDevice->Frequency/1000)}; - - bool needring{false}; - const char *funcerr{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); - /* set interleaved access */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); - /* set format (implicitly sets sample bits) */ - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); - /* set channels (implicitly sets frame bits) */ - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); - /* set rate (implicitly constrains period/buffer parameters) */ - CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp, mDevice->Frequency, 0)); - /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp, &bufferSizeInFrames) < 0) - { - TRACE("Buffer too large, using intermediate ring buffer\n"); - needring = true; - CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp, &bufferSizeInFrames)); - } - /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp, &periodSizeInFrames, nullptr)); - /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); -#undef CHECK - snd_pcm_hw_params_free(hp); - hp = nullptr; - - if(needring) - { - mRing = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("ring buffer create failed\n"); - goto error2; - } - } - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; - -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - -error2: - mRing = nullptr; - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; - - return ALC_INVALID_VALUE; -} - - -ALCboolean AlsaCapture::start() -{ - int err{snd_pcm_prepare(mPcmHandle)}; - if(err < 0) - ERR("prepare failed: %s\n", snd_strerror(err)); - else - { - err = snd_pcm_start(mPcmHandle); - if(err < 0) - ERR("start failed: %s\n", snd_strerror(err)); - } - if(err < 0) - { - aluHandleDisconnect(mDevice, "Capture state failure: %s", snd_strerror(err)); - return ALC_FALSE; - } - - mDoCapture = true; - return ALC_TRUE; -} - -void AlsaCapture::stop() -{ - /* OpenAL requires access to unread audio after stopping, but ALSA's - * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's - * available now so it'll be available later after the drop. - */ - ALCuint avail{availableSamples()}; - if(!mRing && avail > 0) - { - /* The ring buffer implicitly captures when checking availability. - * Direct access needs to explicitly capture it into temp storage. */ - al::vector<char> temp(snd_pcm_frames_to_bytes(mPcmHandle, avail)); - captureSamples(temp.data(), avail); - mBuffer = std::move(temp); - } - int err{snd_pcm_drop(mPcmHandle)}; - if(err < 0) - ERR("drop failed: %s\n", snd_strerror(err)); - mDoCapture = false; -} - -ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - if(mRing) - { - mRing->read(buffer, samples); - return ALC_NO_ERROR; - } - - mLastAvail -= samples; - while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0) - { - snd_pcm_sframes_t amt{0}; - - if(!mBuffer.empty()) - { - /* First get any data stored from the last stop */ - amt = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples; - - amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); - memcpy(buffer, mBuffer.data(), amt); - - mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); - amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); - } - else if(mDoCapture) - amt = snd_pcm_readi(mPcmHandle, buffer, samples); - if(amt < 0) - { - ERR("read error: %s\n", snd_strerror(amt)); - - if(amt == -EAGAIN) - continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) - { - amt = snd_pcm_start(mPcmHandle); - if(amt >= 0) - amt = snd_pcm_avail_update(mPcmHandle); - } - if(amt < 0) - { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); - break; - } - /* If the amount available is less than what's asked, we lost it - * during recovery. So just give silence instead. */ - if(static_cast<snd_pcm_uframes_t>(amt) < samples) - break; - continue; - } - - buffer = static_cast<ALbyte*>(buffer) + amt; - samples -= amt; - } - if(samples > 0) - memset(buffer, ((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0), - snd_pcm_frames_to_bytes(mPcmHandle, samples)); - - return ALC_NO_ERROR; -} - -ALCuint AlsaCapture::availableSamples() -{ - snd_pcm_sframes_t avail{0}; - if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture) - avail = snd_pcm_avail_update(mPcmHandle); - if(avail < 0) - { - ERR("avail update failed: %s\n", snd_strerror(avail)); - - if((avail=snd_pcm_recover(mPcmHandle, avail, 1)) >= 0) - { - if(mDoCapture) - avail = snd_pcm_start(mPcmHandle); - if(avail >= 0) - avail = snd_pcm_avail_update(mPcmHandle); - } - if(avail < 0) - { - ERR("restore error: %s\n", snd_strerror(avail)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(avail)); - } - } - - if(!mRing) - { - if(avail < 0) avail = 0; - avail += snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - if(avail > mLastAvail) mLastAvail = avail; - return mLastAvail; - } - - while(avail > 0) - { - auto vec = mRing->getWriteVector(); - if(vec.first.len == 0) break; - - snd_pcm_sframes_t amt{std::min<snd_pcm_sframes_t>(vec.first.len, avail)}; - amt = snd_pcm_readi(mPcmHandle, vec.first.buf, amt); - if(amt < 0) - { - ERR("read error: %s\n", snd_strerror(amt)); - - if(amt == -EAGAIN) - continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) - { - if(mDoCapture) - amt = snd_pcm_start(mPcmHandle); - if(amt >= 0) - amt = snd_pcm_avail_update(mPcmHandle); - } - if(amt < 0) - { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); - break; - } - avail = amt; - continue; - } - - mRing->writeAdvance(amt); - avail -= amt; - } - - return mRing->readSpace(); -} - -ClockLatency AlsaCapture::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - snd_pcm_sframes_t delay{}; - int err{snd_pcm_delay(mPcmHandle, &delay)}; - if(err < 0) - { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); - delay = 0; - } - ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - -} // namespace - - -bool AlsaBackendFactory::init() -{ - bool error{false}; - -#ifdef HAVE_DYNLOAD - if(!alsa_handle) - { - std::string missing_funcs; - - alsa_handle = LoadLib("libasound.so.2"); - if(!alsa_handle) - { - WARN("Failed to load %s\n", "libasound.so.2"); - return ALC_FALSE; - } - - error = ALC_FALSE; -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ -} while(0) - ALSA_FUNCS(LOAD_FUNC); -#undef LOAD_FUNC - - if(error) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(alsa_handle); - alsa_handle = nullptr; - } - } -#endif - - return !error; -} - -bool AlsaBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void AlsaBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - switch(type) - { - case DevProbe::Playback: - PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new AlsaPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new AlsaCapture{device}}; - return nullptr; -} - -BackendFactory &AlsaBackendFactory::getFactory() -{ - static AlsaBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/alsa.h b/Alc/backends/alsa.h deleted file mode 100644 index fb9de006..00000000 --- a/Alc/backends/alsa.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_ALSA_H -#define BACKENDS_ALSA_H - -#include "backends/base.h" - -struct AlsaBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_ALSA_H */ diff --git a/Alc/backends/base.cpp b/Alc/backends/base.cpp deleted file mode 100644 index a7d47c6d..00000000 --- a/Alc/backends/base.cpp +++ /dev/null @@ -1,58 +0,0 @@ - -#include "config.h" - -#include <cstdlib> - -#include <thread> - -#include "alcmain.h" -#include "alu.h" - -#include "backends/base.h" - - -ClockLatency GetClockLatency(ALCdevice *device) -{ - BackendBase *backend{device->Backend.get()}; - ClockLatency ret{backend->getClockLatency()}; - ret.Latency += device->FixedLatency; - return ret; -} - - -/* BackendBase method implementations. */ -BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device} -{ } - -BackendBase::~BackendBase() = default; - -ALCboolean BackendBase::reset() -{ return ALC_FALSE; } - -ALCenum BackendBase::captureSamples(void*, ALCuint) -{ return ALC_INVALID_DEVICE; } - -ALCuint BackendBase::availableSamples() -{ return 0; } - -ClockLatency BackendBase::getClockLatency() -{ - ClockLatency ret; - - ALuint refcount; - do { - while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); - ret.ClockTime = GetDeviceClockTime(mDevice); - std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != mDevice->MixCount.load(std::memory_order_relaxed)); - - /* NOTE: The device will generally have about all but one periods filled at - * any given time during playback. Without a more accurate measurement from - * the output, this is an okay approximation. - */ - ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)}; - ret.Latency /= mDevice->Frequency; - - return ret; -} diff --git a/Alc/backends/base.h b/Alc/backends/base.h deleted file mode 100644 index 437e31d9..00000000 --- a/Alc/backends/base.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef ALC_BACKENDS_BASE_H -#define ALC_BACKENDS_BASE_H - -#include <memory> -#include <chrono> -#include <string> -#include <mutex> - -#include "alcmain.h" - - -struct ClockLatency { - std::chrono::nanoseconds ClockTime; - std::chrono::nanoseconds Latency; -}; - -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - -ClockLatency GetClockLatency(ALCdevice *device); - -struct BackendBase { - virtual ALCenum open(const ALCchar *name) = 0; - - virtual ALCboolean reset(); - virtual ALCboolean start() = 0; - virtual void stop() = 0; - - virtual ALCenum captureSamples(void *buffer, ALCuint samples); - virtual ALCuint availableSamples(); - - virtual ClockLatency getClockLatency(); - - virtual void lock() { mMutex.lock(); } - virtual void unlock() { mMutex.unlock(); } - - ALCdevice *mDevice; - - std::recursive_mutex mMutex; - - BackendBase(ALCdevice *device) noexcept; - virtual ~BackendBase(); -}; -using BackendPtr = std::unique_ptr<BackendBase>; -using BackendUniqueLock = std::unique_lock<BackendBase>; -using BackendLockGuard = std::lock_guard<BackendBase>; - -enum class BackendType { - Playback, - Capture -}; - -enum class DevProbe { - Playback, - Capture -}; - - -struct BackendFactory { - virtual bool init() = 0; - - virtual bool querySupport(BackendType type) = 0; - - virtual void probe(DevProbe type, std::string *outnames) = 0; - - virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; -}; - -#endif /* ALC_BACKENDS_BASE_H */ diff --git a/Alc/backends/coreaudio.cpp b/Alc/backends/coreaudio.cpp deleted file mode 100644 index b4b46382..00000000 --- a/Alc/backends/coreaudio.cpp +++ /dev/null @@ -1,709 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/coreaudio.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "converter.h" -#include "backends/base.h" - -#include <unistd.h> -#include <AudioUnit/AudioUnit.h> -#include <AudioToolbox/AudioToolbox.h> - - -namespace { - -static const ALCchar ca_device[] = "CoreAudio Default"; - - -struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~CoreAudioPlayback() override; - - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - AudioUnit mAudioUnit; - - ALuint mFrameSize{0u}; - AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - DEF_NEWDEL(CoreAudioPlayback) -}; - -CoreAudioPlayback::~CoreAudioPlayback() -{ - AudioUnitUninitialize(mAudioUnit); - AudioComponentInstanceDispose(mAudioUnit); -} - - -OSStatus CoreAudioPlayback::MixerProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) -{ - return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp*, UInt32, UInt32, AudioBufferList *ioData) -{ - lock(); - aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize); - unlock(); - return noErr; -} - - -ALCenum CoreAudioPlayback::open(const ALCchar *name) -{ - if(!name) - name = ca_device; - else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; - - /* open the default output unit */ - AudioComponentDescription desc{}; - desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; -#else - desc.componentSubType = kAudioUnitSubType_DefaultOutput; -#endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; - if(comp == nullptr) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } - - OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; - if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } - - /* init and start the default audio unit... */ - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - AudioComponentInstanceDispose(mAudioUnit); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean CoreAudioPlayback::reset() -{ - OSStatus err{AudioUnitUninitialize(mAudioUnit)}; - if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); - - /* retrieve default output unit's properties (output side) */ - AudioStreamBasicDescription streamFormat{}; - auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription)); - err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 0, &streamFormat, &size); - if(err != noErr || size != sizeof(AudioStreamBasicDescription)) - { - ERR("AudioUnitGetProperty failed\n"); - return ALC_FALSE; - } - -#if 0 - TRACE("Output streamFormat of default output unit -\n"); - TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket); - TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame); - TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel); - TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket); - TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame); - TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); -#endif - - /* set default output unit's input side to match output side */ - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, size); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - if(mDevice->Frequency != streamFormat.mSampleRate) - { - mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} * - streamFormat.mSampleRate / mDevice->Frequency); - mDevice->Frequency = streamFormat.mSampleRate; - } - - /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? eg, 6.0 vs 5.1 */ - switch(streamFormat.mChannelsPerFrame) - { - case 1: - mDevice->FmtChans = DevFmtMono; - break; - case 2: - mDevice->FmtChans = DevFmtStereo; - break; - case 4: - mDevice->FmtChans = DevFmtQuad; - break; - case 6: - mDevice->FmtChans = DevFmtX51; - break; - case 7: - mDevice->FmtChans = DevFmtX61; - break; - case 8: - mDevice->FmtChans = DevFmtX71; - break; - default: - ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame); - mDevice->FmtChans = DevFmtStereo; - streamFormat.mChannelsPerFrame = 2; - break; - } - SetDefaultWFXChannelOrder(mDevice); - - /* use channel count and sample rate from the default output unit's current - * parameters, but reset everything else */ - streamFormat.mFramesPerPacket = 1; - streamFormat.mFormatFlags = 0; - switch(mDevice->FmtType) - { - case DevFmtUByte: - mDevice->FmtType = DevFmtByte; - /* fall-through */ - case DevFmtByte: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 8; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 16; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 32; - break; - case DevFmtFloat: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; - streamFormat.mBitsPerChannel = 32; - break; - } - streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * - streamFormat.mBitsPerChannel / 8; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian | - kLinearPCMFormatFlagIsPacked; - - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(AudioStreamBasicDescription)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - /* setup callback */ - mFrameSize = mDevice->frameSizeFromFmt(); - AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; - input.inputProcRefCon = this; - - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - /* init the default audio unit... */ - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - return ALC_FALSE; - } - - return ALC_TRUE; -} - -ALCboolean CoreAudioPlayback::start() -{ - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; - if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void CoreAudioPlayback::stop() -{ - OSStatus err{AudioOutputUnitStop(mAudioUnit)}; - if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); -} - - -struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~CoreAudioCapture() override; - - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, - UInt32 inNumberFrames, AudioBufferList *ioData); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - AudioUnit mAudioUnit{0}; - - ALuint mFrameSize{0u}; - AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - SampleConverterPtr mConverter; - - RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(CoreAudioCapture) -}; - -CoreAudioCapture::~CoreAudioCapture() -{ - if(mAudioUnit) - AudioComponentInstanceDispose(mAudioUnit); - mAudioUnit = 0; -} - - -OSStatus CoreAudioCapture::RecordProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) -{ - return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames, - AudioBufferList*) -{ - AudioUnitRenderActionFlags flags = 0; - union { - ALbyte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; - AudioBufferList list; - } audiobuf = { { 0 } }; - - auto rec_vec = mRing->getWriteVector(); - inNumberFrames = minz(inNumberFrames, rec_vec.first.len+rec_vec.second.len); - - // Fill the ringbuffer's two segments with data from the input device - if(rec_vec.first.len >= inNumberFrames) - { - audiobuf.list.mNumberBuffers = 1; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame; - } - else - { - const size_t remaining{inNumberFrames-rec_vec.first.len}; - audiobuf.list.mNumberBuffers = 2; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = rec_vec.first.len * mFormat.mBytesPerFrame; - audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[1].mData = rec_vec.second.buf; - audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame; - } - OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers, - inNumberFrames, &audiobuf.list)}; - if(err != noErr) - { - ERR("AudioUnitRender error: %d\n", err); - return err; - } - - mRing->writeAdvance(inNumberFrames); - return noErr; -} - - -ALCenum CoreAudioCapture::open(const ALCchar *name) -{ - AudioStreamBasicDescription requestedFormat; // The application requested format - AudioStreamBasicDescription hardwareFormat; // The hardware format - AudioStreamBasicDescription outputFormat; // The AudioUnit output format - AURenderCallbackStruct input; - AudioComponentDescription desc; - UInt32 outputFrameCount; - UInt32 propertySize; - AudioObjectPropertyAddress propertyAddress; - UInt32 enableIO; - AudioComponent comp; - OSStatus err; - - if(!name) - name = ca_device; - else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; - - desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; -#else - desc.componentSubType = kAudioUnitSubType_HALOutput; -#endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - // Search for component with given description - comp = AudioComponentFindNext(NULL, &desc); - if(comp == NULL) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } - - // Open the component - err = AudioComponentInstanceNew(comp, &mAudioUnit); - if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } - - // Turn off AudioUnit output - enableIO = 0; - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Turn on AudioUnit input - enableIO = 1; - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - -#if !TARGET_OS_IOS - { - // Get the default input device - AudioDeviceID inputDevice = kAudioDeviceUnknown; - - propertySize = sizeof(AudioDeviceID); - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &inputDevice); - if(err != noErr) - { - ERR("AudioObjectGetPropertyData failed\n"); - return ALC_INVALID_VALUE; - } - if(inputDevice == kAudioDeviceUnknown) - { - ERR("No input device found\n"); - return ALC_INVALID_VALUE; - } - - // Track the input device - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - } -#endif - - // set capture callback - input.inputProc = CoreAudioCapture::RecordProcC; - input.inputProcRefCon = this; - - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Initialize the device - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - return ALC_INVALID_VALUE; - } - - // Get the hardware format - propertySize = sizeof(AudioStreamBasicDescription); - err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 1, &hardwareFormat, &propertySize); - if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) - { - ERR("AudioUnitGetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Set up the requested format description - switch(mDevice->FmtType) - { - case DevFmtUByte: - requestedFormat.mBitsPerChannel = 8; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtShort: - requestedFormat.mBitsPerChannel = 16; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtInt: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtFloat: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - - switch(mDevice->FmtChans) - { - case DevFmtMono: - requestedFormat.mChannelsPerFrame = 1; - break; - case DevFmtStereo: - requestedFormat.mChannelsPerFrame = 2; - break; - - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - ERR("%s not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_VALUE; - } - - requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; - requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; - requestedFormat.mSampleRate = mDevice->Frequency; - requestedFormat.mFormatID = kAudioFormatLinearPCM; - requestedFormat.mReserved = 0; - requestedFormat.mFramesPerPacket = 1; - - // save requested format description for later use - mFormat = requestedFormat; - mFrameSize = mDevice->frameSizeFromFmt(); - - // Use intermediate format for sample rate conversion (outputFormat) - // Set sample rate to the same as hardware for resampling later - outputFormat = requestedFormat; - outputFormat.mSampleRate = hardwareFormat.mSampleRate; - - // The output format should be the requested format, but using the hardware sample rate - // This is because the AudioUnit will automatically scale other properties, except for sample rate - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 1, (void*)&outputFormat, sizeof(outputFormat)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Set the AudioUnit output format frame count - uint64_t FrameCount64{mDevice->UpdateSize}; - FrameCount64 = (FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) / - mDevice->Frequency; - FrameCount64 += MAX_RESAMPLE_PADDING*2; - if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2) - { - ERR("FrameCount too large\n"); - return ALC_INVALID_VALUE; - } - - outputFrameCount = static_cast<uint32_t>(FrameCount64); - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed: %d\n", err); - return ALC_INVALID_VALUE; - } - - // Set up sample converter if needed - if(outputFormat.mSampleRate != mDevice->Frequency) - mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, - mFormat.mChannelsPerFrame, hardwareFormat.mSampleRate, mDevice->Frequency, - BSinc24Resampler); - - mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false); - if(!mRing) return ALC_INVALID_VALUE; - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - - -ALCboolean CoreAudioCapture::start() -{ - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; - if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void CoreAudioCapture::stop() -{ - OSStatus err{AudioOutputUnitStop(mAudioUnit)}; - if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); -} - -ALCenum CoreAudioCapture::captureSamples(void *buffer, ALCuint samples) -{ - if(!mConverter) - { - mRing->read(buffer, samples); - return ALC_NO_ERROR; - } - - auto rec_vec = mRing->getReadVector(); - const void *src0{rec_vec.first.buf}; - auto src0len = static_cast<ALsizei>(rec_vec.first.len); - auto got = static_cast<ALuint>(mConverter->convert(&src0, &src0len, buffer, samples)); - size_t total_read{rec_vec.first.len - src0len}; - if(got < samples && !src0len && rec_vec.second.len > 0) - { - const void *src1{rec_vec.second.buf}; - auto src1len = static_cast<ALsizei>(rec_vec.second.len); - got += static_cast<ALuint>(mConverter->convert(&src1, &src1len, - static_cast<char*>(buffer)+got, samples-got)); - total_read += rec_vec.second.len - src1len; - } - - mRing->readAdvance(total_read); - return ALC_NO_ERROR; -} - -ALCuint CoreAudioCapture::availableSamples() -{ - if(!mConverter) return mRing->readSpace(); - return mConverter->availableOut(mRing->readSpace()); -} - -} // namespace - -BackendFactory &CoreAudioBackendFactory::getFactory() -{ - static CoreAudioBackendFactory factory{}; - return factory; -} - -bool CoreAudioBackendFactory::init() { return true; } - -bool CoreAudioBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(ca_device, sizeof(ca_device)); - break; - } -} - -BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new CoreAudioPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CoreAudioCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/coreaudio.h b/Alc/backends/coreaudio.h deleted file mode 100644 index 37b9ebe5..00000000 --- a/Alc/backends/coreaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_COREAUDIO_H -#define BACKENDS_COREAUDIO_H - -#include "backends/base.h" - -struct CoreAudioBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_COREAUDIO_H */ diff --git a/Alc/backends/dsound.cpp b/Alc/backends/dsound.cpp deleted file mode 100644 index 5a156d54..00000000 --- a/Alc/backends/dsound.cpp +++ /dev/null @@ -1,938 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/dsound.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <cguid.h> -#include <mmreg.h> -#ifndef _WAVEFORMATEXTENSIBLE_ -#include <ks.h> -#include <ksmedia.h> -#endif - -#include <atomic> -#include <cassert> -#include <thread> -#include <string> -#include <vector> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "compat.h" -#include "threads.h" - -/* MinGW-w64 needs this for some unknown reason now. */ -using LPCWAVEFORMATEX = const WAVEFORMATEX*; -#include <dsound.h> - - -#ifndef DSSPEAKER_5POINT1 -# define DSSPEAKER_5POINT1 0x00000006 -#endif -#ifndef DSSPEAKER_5POINT1_BACK -# define DSSPEAKER_5POINT1_BACK 0x00000006 -#endif -#ifndef DSSPEAKER_7POINT1 -# define DSSPEAKER_7POINT1 0x00000007 -#endif -#ifndef DSSPEAKER_7POINT1_SURROUND -# define DSSPEAKER_7POINT1_SURROUND 0x00000008 -#endif -#ifndef DSSPEAKER_5POINT1_SURROUND -# define DSSPEAKER_5POINT1_SURROUND 0x00000009 -#endif - - -/* Some headers seem to define these as macros for __uuidof, which is annoying - * since some headers don't declare them at all. Hopefully the ifdef is enough - * to tell if they need to be declared. - */ -#ifndef KSDATAFORMAT_SUBTYPE_PCM -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif -#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif - -namespace { - -#define DEVNAME_HEAD "OpenAL Soft on " - - -#ifdef HAVE_DYNLOAD -void *ds_handle; -HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); -HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); -HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter); -HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); - -#ifndef IN_IDE_PARSER -#define DirectSoundCreate pDirectSoundCreate -#define DirectSoundEnumerateW pDirectSoundEnumerateW -#define DirectSoundCaptureCreate pDirectSoundCaptureCreate -#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW -#endif -#endif - - -#define MAX_UPDATES 128 - -struct DevMap { - std::string name; - GUID guid; - - template<typename T0, typename T1> - DevMap(T0&& name_, T1&& guid_) - : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)} - { } -}; - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - -BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) -{ - if(!guid) - return TRUE; - - auto& devices = *static_cast<al::vector<DevMap>*>(data); - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; - - int count{1}; - std::string newname{basename}; - while(checkName(devices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - devices.emplace_back(std::move(newname), *guid); - const DevMap &newentry = devices.back(); - - OLECHAR *guidstr{nullptr}; - HRESULT hr{StringFromCLSID(*guid, &guidstr)}; - if(SUCCEEDED(hr)) - { - TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr); - CoTaskMemFree(guidstr); - } - - return TRUE; -} - - -struct DSoundPlayback final : public BackendBase { - DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~DSoundPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - IDirectSound *mDS{nullptr}; - IDirectSoundBuffer *mPrimaryBuffer{nullptr}; - IDirectSoundBuffer *mBuffer{nullptr}; - IDirectSoundNotify *mNotifies{nullptr}; - HANDLE mNotifyEvent{nullptr}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(DSoundPlayback) -}; - -DSoundPlayback::~DSoundPlayback() -{ - if(mNotifies) - mNotifies->Release(); - mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); - mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - - if(mDS) - mDS->Release(); - mDS = nullptr; - if(mNotifyEvent) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int DSoundPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - DSBCAPS DSBCaps{}; - DSBCaps.dwSize = sizeof(DSBCaps); - HRESULT err{mBuffer->GetCaps(&DSBCaps)}; - if(FAILED(err)) - { - ERR("Failed to get buffer caps: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err); - return 1; - } - - ALsizei FrameSize{mDevice->frameSizeFromFmt()}; - DWORD FragSize{mDevice->UpdateSize * FrameSize}; - - bool Playing{false}; - DWORD LastCursor{0u}; - mBuffer->GetCurrentPosition(&LastCursor, nullptr); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - // Get current play cursor - DWORD PlayCursor; - mBuffer->GetCurrentPosition(&PlayCursor, nullptr); - DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes; - - if(avail < FragSize) - { - if(!Playing) - { - err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); - if(FAILED(err)) - { - ERR("Failed to play buffer: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err); - return 1; - } - Playing = true; - } - - avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); - if(avail != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); - continue; - } - avail -= avail%FragSize; - - // Lock output buffer - void *WritePtr1, *WritePtr2; - DWORD WriteCnt1{0u}, WriteCnt2{0u}; - err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); - - // If the buffer is lost, restore it and lock - if(err == DSERR_BUFFERLOST) - { - WARN("Buffer lost, restoring...\n"); - err = mBuffer->Restore(); - if(SUCCEEDED(err)) - { - Playing = false; - LastCursor = 0; - err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, - &WritePtr2, &WriteCnt2, 0); - } - } - - if(SUCCEEDED(err)) - { - lock(); - aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize); - if(WriteCnt2 > 0) - aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize); - unlock(); - - mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); - } - else - { - ERR("Buffer lock error: %#lx\n", err); - aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err); - return 1; - } - - // Update old write cursor location - LastCursor += WriteCnt1+WriteCnt2; - LastCursor %= DSBCaps.dwBufferBytes; - } - - return 0; -} - -ALCenum DSoundPlayback::open(const ALCchar *name) -{ - HRESULT hr; - if(PlaybackDevices.empty()) - { - /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); - } - - const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) - { - name = PlaybackDevices[0].name.c_str(); - guid = &PlaybackDevices[0].guid; - } - else - { - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - guid = &iter->guid; - } - - hr = DS_OK; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(!mNotifyEvent) hr = E_FAIL; - - //DirectSound Init code - if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, &mDS, nullptr); - if(SUCCEEDED(hr)) - hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); - if(FAILED(hr)) - { - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean DSoundPlayback::reset() -{ - if(mNotifies) - mNotifies->Release(); - mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); - mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtFloat: - if(mDevice->Flags.get<SampleTypeRequest>()) - break; - /* fall-through */ - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - break; - } - - WAVEFORMATEXTENSIBLE OutputType{}; - DWORD speakers; - HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; - if(SUCCEEDED(hr)) - { - speakers = DSSPEAKER_CONFIG(speakers); - if(!mDevice->Flags.get<ChannelsRequest>()) - { - if(speakers == DSSPEAKER_MONO) - mDevice->FmtChans = DevFmtMono; - else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) - mDevice->FmtChans = DevFmtStereo; - else if(speakers == DSSPEAKER_QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(speakers == DSSPEAKER_5POINT1_SURROUND) - mDevice->FmtChans = DevFmtX51; - else if(speakers == DSSPEAKER_5POINT1_BACK) - mDevice->FmtChans = DevFmtX51Rear; - else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) - mDevice->FmtChans = DevFmtX71; - else - ERR("Unknown system speaker config: 0x%lx\n", speakers); - } - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - speakers == DSSPEAKER_HEADPHONE); - - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - } - -retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = mDevice->channelsFromFmt(); - OutputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = 0; - } - - if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - if(mDevice->FmtType == DevFmtFloat) - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - } - else - { - if(SUCCEEDED(hr) && !mPrimaryBuffer) - { - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); - } - if(SUCCEEDED(hr)) - hr = mPrimaryBuffer->SetFormat(&OutputType.Format); - } - - if(SUCCEEDED(hr)) - { - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - if(num_updates > MAX_UPDATES) - num_updates = MAX_UPDATES; - mDevice->BufferSize = mDevice->UpdateSize * num_updates; - - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | - DSBCAPS_GLOBALFOCUS; - DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; - DSBDescription.lpwfxFormat = &OutputType.Format; - - hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); - if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - } - - if(SUCCEEDED(hr)) - { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); - if(SUCCEEDED(hr)) - { - auto Notifies = static_cast<IDirectSoundNotify*>(ptr); - mNotifies = Notifies; - - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - assert(num_updates <= MAX_UPDATES); - - std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots; - for(ALuint i{0};i < num_updates;++i) - { - nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; - nots[i].hEventNotify = mNotifyEvent; - } - if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) - hr = E_FAIL; - } - } - - if(FAILED(hr)) - { - if(mNotifies) - mNotifies->Release(); - mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); - mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - return ALC_FALSE; - } - - ResetEvent(mNotifyEvent); - SetDefaultWFXChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean DSoundPlayback::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void DSoundPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - mBuffer->Stop(); -} - - -struct DSoundCapture final : public BackendBase { - DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~DSoundCapture() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - IDirectSoundCapture *mDSC{nullptr}; - IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; - DWORD mBufferBytes{0u}; - DWORD mCursor{0u}; - - RingBufferPtr mRing; - - DEF_NEWDEL(DSoundCapture) -}; - -DSoundCapture::~DSoundCapture() -{ - if(mDSCbuffer) - { - mDSCbuffer->Stop(); - mDSCbuffer->Release(); - mDSCbuffer = nullptr; - } - - if(mDSC) - mDSC->Release(); - mDSC = nullptr; -} - - -ALCenum DSoundCapture::open(const ALCchar *name) -{ - HRESULT hr; - if(CaptureDevices.empty()) - { - /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); - } - - const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) - { - name = CaptureDevices[0].name.c_str(); - guid = &CaptureDevices[0].guid; - } - else - { - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - guid = &iter->guid; - } - - switch(mDevice->FmtType) - { - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_ENUM; - - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - } - - WAVEFORMATEXTENSIBLE InputType{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - InputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtStereo: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtAmbi3D: - WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_ENUM; - } - - InputType.Format.wFormatTag = WAVE_FORMAT_PCM; - InputType.Format.nChannels = mDevice->channelsFromFmt(); - InputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8; - InputType.Format.nSamplesPerSec = mDevice->Frequency; - InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign; - InputType.Format.cbSize = 0; - InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; - if(mDevice->FmtType == DevFmtFloat) - InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - } - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - - DSCBUFFERDESC DSCBDescription{}; - DSCBDescription.dwSize = sizeof(DSCBDescription); - DSCBDescription.dwFlags = 0; - DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign; - DSCBDescription.lpwfxFormat = &InputType.Format; - - //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); - if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); - if(SUCCEEDED(hr)) - { - mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false); - if(!mRing) hr = DSERR_OUTOFMEMORY; - } - - if(FAILED(hr)) - { - ERR("Device init failed: 0x%08lx\n", hr); - - mRing = nullptr; - if(mDSCbuffer) - mDSCbuffer->Release(); - mDSCbuffer = nullptr; - if(mDSC) - mDSC->Release(); - mDSC = nullptr; - - return ALC_INVALID_VALUE; - } - - mBufferBytes = DSCBDescription.dwBufferBytes; - SetDefaultWFXChannelOrder(mDevice); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean DSoundCapture::start() -{ - HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; - if(FAILED(hr)) - { - ERR("start failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void DSoundCapture::stop() -{ - HRESULT hr{mDSCbuffer->Stop()}; - if(FAILED(hr)) - { - ERR("stop failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr); - } -} - -ALCenum DSoundCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint DSoundCapture::availableSamples() -{ - if(!mDevice->Connected.load(std::memory_order_acquire)) - return static_cast<ALCuint>(mRing->readSpace()); - - ALsizei FrameSize{mDevice->frameSizeFromFmt()}; - DWORD BufferBytes{mBufferBytes}; - DWORD LastCursor{mCursor}; - - DWORD ReadCursor; - void *ReadPtr1, *ReadPtr2; - DWORD ReadCnt1, ReadCnt2; - HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)}; - if(SUCCEEDED(hr)) - { - DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes}; - if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace()); - hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); - } - if(SUCCEEDED(hr)) - { - mRing->write(ReadPtr1, ReadCnt1/FrameSize); - if(ReadPtr2 != nullptr && ReadCnt2 > 0) - mRing->write(ReadPtr2, ReadCnt2/FrameSize); - hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); - mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes; - } - - if(FAILED(hr)) - { - ERR("update failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr); - } - - return static_cast<ALCuint>(mRing->readSpace()); -} - -} // namespace - - -BackendFactory &DSoundBackendFactory::getFactory() -{ - static DSoundBackendFactory factory{}; - return factory; -} - -bool DSoundBackendFactory::init() -{ -#ifdef HAVE_DYNLOAD - if(!ds_handle) - { - ds_handle = LoadLib("dsound.dll"); - if(!ds_handle) - { - ERR("Failed to load dsound.dll\n"); - return false; - } - -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \ - if(!p##f) \ - { \ - CloseLib(ds_handle); \ - ds_handle = nullptr; \ - return false; \ - } \ -} while(0) - LOAD_FUNC(DirectSoundCreate); - LOAD_FUNC(DirectSoundEnumerateW); - LOAD_FUNC(DirectSoundCaptureCreate); - LOAD_FUNC(DirectSoundCaptureEnumerateW); -#undef LOAD_FUNC - } -#endif - return true; -} - -bool DSoundBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - - /* Initialize COM to prevent name truncation */ - HRESULT hr; - HRESULT hrcom{CoInitialize(nullptr)}; - switch(type) - { - case DevProbe::Playback: - PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } - if(SUCCEEDED(hrcom)) - CoUninitialize(); -} - -BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new DSoundPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new DSoundCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/dsound.h b/Alc/backends/dsound.h deleted file mode 100644 index 6bef0bfc..00000000 --- a/Alc/backends/dsound.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_DSOUND_H -#define BACKENDS_DSOUND_H - -#include "backends/base.h" - -struct DSoundBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_DSOUND_H */ diff --git a/Alc/backends/jack.cpp b/Alc/backends/jack.cpp deleted file mode 100644 index 3f81d08c..00000000 --- a/Alc/backends/jack.cpp +++ /dev/null @@ -1,562 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/jack.h" - -#include <cstdlib> -#include <cstdio> -#include <memory.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#include <jack/jack.h> -#include <jack/ringbuffer.h> - - -namespace { - -constexpr ALCchar jackDevice[] = "JACK Default"; - - -#ifdef HAVE_DYNLOAD -#define JACK_FUNCS(MAGIC) \ - MAGIC(jack_client_open); \ - MAGIC(jack_client_close); \ - MAGIC(jack_client_name_size); \ - MAGIC(jack_get_client_name); \ - MAGIC(jack_connect); \ - MAGIC(jack_activate); \ - MAGIC(jack_deactivate); \ - MAGIC(jack_port_register); \ - MAGIC(jack_port_unregister); \ - MAGIC(jack_port_get_buffer); \ - MAGIC(jack_port_name); \ - MAGIC(jack_get_ports); \ - MAGIC(jack_free); \ - MAGIC(jack_get_sample_rate); \ - MAGIC(jack_set_error_function); \ - MAGIC(jack_set_process_callback); \ - MAGIC(jack_set_buffer_size_callback); \ - MAGIC(jack_set_buffer_size); \ - MAGIC(jack_get_buffer_size); - -void *jack_handle; -#define MAKE_FUNC(f) decltype(f) * p##f -JACK_FUNCS(MAKE_FUNC); -decltype(jack_error_callback) * pjack_error_callback; -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define jack_client_open pjack_client_open -#define jack_client_close pjack_client_close -#define jack_client_name_size pjack_client_name_size -#define jack_get_client_name pjack_get_client_name -#define jack_connect pjack_connect -#define jack_activate pjack_activate -#define jack_deactivate pjack_deactivate -#define jack_port_register pjack_port_register -#define jack_port_unregister pjack_port_unregister -#define jack_port_get_buffer pjack_port_get_buffer -#define jack_port_name pjack_port_name -#define jack_get_ports pjack_get_ports -#define jack_free pjack_free -#define jack_get_sample_rate pjack_get_sample_rate -#define jack_set_error_function pjack_set_error_function -#define jack_set_process_callback pjack_set_process_callback -#define jack_set_buffer_size_callback pjack_set_buffer_size_callback -#define jack_set_buffer_size pjack_set_buffer_size -#define jack_get_buffer_size pjack_get_buffer_size -#define jack_error_callback (*pjack_error_callback) -#endif -#endif - - -jack_options_t ClientOptions = JackNullOption; - -ALCboolean jack_load() -{ - ALCboolean error = ALC_FALSE; - -#ifdef HAVE_DYNLOAD - if(!jack_handle) - { - std::string missing_funcs; - -#ifdef _WIN32 -#define JACKLIB "libjack.dll" -#else -#define JACKLIB "libjack.so.0" -#endif - jack_handle = LoadLib(JACKLIB); - if(!jack_handle) - { - WARN("Failed to load %s\n", JACKLIB); - return ALC_FALSE; - } - - error = ALC_FALSE; -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \ - if(p##f == nullptr) { \ - error = ALC_TRUE; \ - missing_funcs += "\n" #f; \ - } \ -} while(0) - JACK_FUNCS(LOAD_FUNC); -#undef LOAD_FUNC - /* Optional symbols. These don't exist in all versions of JACK. */ -#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)) - LOAD_SYM(jack_error_callback); -#undef LOAD_SYM - - if(error) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(jack_handle); - jack_handle = nullptr; - } - } -#endif - - return !error; -} - - -struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~JackPlayback() override; - - static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg); - int bufferSizeNotify(jack_nframes_t numframes); - - static int processC(jack_nframes_t numframes, void *arg); - int process(jack_nframes_t numframes); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - - jack_client_t *mClient{nullptr}; - jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{}; - - RingBufferPtr mRing; - al::semaphore mSem; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(JackPlayback) -}; - -JackPlayback::~JackPlayback() -{ - if(!mClient) - return; - - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); - jack_client_close(mClient); - mClient = nullptr; -} - - -int JackPlayback::bufferSizeNotifyC(jack_nframes_t numframes, void *arg) -{ return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); } - -int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) -{ - std::lock_guard<std::mutex> _{mDevice->StateLock}; - mDevice->UpdateSize = numframes; - mDevice->BufferSize = numframes*2; - - const char *devname{mDevice->DeviceName.c_str()}; - ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; - - TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize); - - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) - { - ERR("Failed to reallocate ringbuffer\n"); - aluHandleDisconnect(mDevice, "Failed to reallocate %u-sample buffer", bufsize); - } - return 0; -} - - -int JackPlayback::processC(jack_nframes_t numframes, void *arg) -{ return static_cast<JackPlayback*>(arg)->process(numframes); } - -int JackPlayback::process(jack_nframes_t numframes) -{ - jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS]; - ALsizei numchans{0}; - for(auto port : mPort) - { - if(!port) break; - out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes)); - } - - auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, data.first.len)}; - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat - { - ALfloat ret{*in}; - in += numchans; - return ret; - } - ); - data.first.buf += sizeof(ALfloat); - return outbuf + todo; - } - ); - jack_nframes_t total{todo}; - - todo = minu(numframes-total, data.second.len); - if(todo > 0) - { - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat - { - ALfloat ret{*in}; - in += numchans; - return ret; - } - ); - data.second.buf += sizeof(ALfloat); - return outbuf + todo; - } - ); - total += todo; - } - - mRing->readAdvance(total); - mSem.post(); - - if(numframes > total) - { - todo = numframes-total; - std::transform(out, out+numchans, out, - [todo](ALfloat *outbuf) -> ALfloat* - { - std::fill_n(outbuf, todo, 0.0f); - return outbuf + todo; - } - ); - } - - return 0; -} - -int JackPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - if(mRing->writeSpace() < mDevice->UpdateSize) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - auto data = mRing->getWriteVector(); - auto todo = static_cast<ALuint>(data.first.len + data.second.len); - todo -= todo%mDevice->UpdateSize; - - ALuint len1{minu(data.first.len, todo)}; - ALuint len2{minu(data.second.len, todo-len1)}; - - aluMixData(mDevice, data.first.buf, len1); - if(len2 > 0) - aluMixData(mDevice, data.second.buf, len2); - mRing->writeAdvance(todo); - } - unlock(); - - return 0; -} - - -ALCenum JackPlayback::open(const ALCchar *name) -{ - if(!name) - name = jackDevice; - else if(strcmp(name, jackDevice) != 0) - return ALC_INVALID_VALUE; - - const char *client_name{"alsoft"}; - jack_status_t status; - mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); - if(mClient == nullptr) - { - ERR("jack_client_open() failed, status = 0x%02x\n", status); - return ALC_INVALID_VALUE; - } - if((status&JackServerStarted)) - TRACE("JACK server started\n"); - if((status&JackNameNotUnique)) - { - client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got `%s' instead\n", client_name); - } - - jack_set_process_callback(mClient, &JackPlayback::processC, this); - jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean JackPlayback::reset() -{ - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); - - /* Ignore the requested buffer metrics and just keep one JACK-sized buffer - * ready for when requested. - */ - mDevice->Frequency = jack_get_sample_rate(mClient); - mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; - - const char *devname{mDevice->DeviceName.c_str()}; - ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; - - /* Force 32-bit float output. */ - mDevice->FmtType = DevFmtFloat; - - ALsizei numchans{mDevice->channelsFromFmt()}; - auto ports_end = std::begin(mPort) + numchans; - auto bad_port = std::find_if_not(std::begin(mPort), ports_end, - [this](jack_port_t *&port) -> bool - { - std::string name{"channel_" + std::to_string(&port - mPort + 1)}; - port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - return port != nullptr; - } - ); - if(bad_port != ports_end) - { - ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == std::begin(mPort)) return ALC_FALSE; - - if(bad_port == std::begin(mPort)+1) - mDevice->FmtChans = DevFmtMono; - else - { - ports_end = mPort+2; - while(bad_port != ports_end) - { - jack_port_unregister(mClient, *(--bad_port)); - *bad_port = nullptr; - } - mDevice->FmtChans = DevFmtStereo; - } - numchans = std::distance(std::begin(mPort), bad_port); - } - - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) - { - ERR("Failed to allocate ringbuffer\n"); - return ALC_FALSE; - } - - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean JackPlayback::start() -{ - if(jack_activate(mClient)) - { - ERR("Failed to activate client\n"); - return ALC_FALSE; - } - - const char **ports{jack_get_ports(mClient, nullptr, nullptr, - JackPortIsPhysical|JackPortIsInput)}; - if(ports == nullptr) - { - ERR("No physical playback ports found\n"); - jack_deactivate(mClient); - return ALC_FALSE; - } - std::mismatch(std::begin(mPort), std::end(mPort), ports, - [this](const jack_port_t *port, const char *pname) -> bool - { - if(!port) return false; - if(!pname) - { - ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); - return false; - } - if(jack_connect(mClient, jack_port_name(port), pname)) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port), - pname); - return true; - } - ); - jack_free(ports); - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - jack_deactivate(mClient); - return ALC_FALSE; -} - -void JackPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); - - jack_deactivate(mClient); -} - - -ClockLatency JackPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace()}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -void jack_msg_handler(const char *message) -{ - WARN("%s\n", message); -} - -} // namespace - -bool JackBackendFactory::init() -{ - if(!jack_load()) - return false; - - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) - ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer); - - void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; - jack_set_error_function(jack_msg_handler); - jack_status_t status; - jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)}; - jack_set_error_function(old_error_cb); - if(!client) - { - WARN("jack_client_open() failed, 0x%02x\n", status); - if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) - ERR("Unable to connect to JACK server\n"); - return false; - } - - jack_client_close(client); - return true; -} - -bool JackBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback); } - -void JackBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(jackDevice, sizeof(jackDevice)); - break; - - case DevProbe::Capture: - break; - } -} - -BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new JackPlayback{device}}; - return nullptr; -} - -BackendFactory &JackBackendFactory::getFactory() -{ - static JackBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/jack.h b/Alc/backends/jack.h deleted file mode 100644 index 10beebfb..00000000 --- a/Alc/backends/jack.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_JACK_H -#define BACKENDS_JACK_H - -#include "backends/base.h" - -struct JackBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_JACK_H */ diff --git a/Alc/backends/loopback.cpp b/Alc/backends/loopback.cpp deleted file mode 100644 index 4a1c641a..00000000 --- a/Alc/backends/loopback.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by Chris Robinson - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/loopback.h" - -#include "alcmain.h" -#include "alu.h" - - -namespace { - -struct LoopbackBackend final : public BackendBase { - LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { } - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - DEF_NEWDEL(LoopbackBackend) -}; - - -ALCenum LoopbackBackend::open(const ALCchar *name) -{ - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean LoopbackBackend::reset() -{ - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean LoopbackBackend::start() -{ return ALC_TRUE; } - -void LoopbackBackend::stop() -{ } - -} // namespace - - -bool LoopbackBackendFactory::init() -{ return true; } - -bool LoopbackBackendFactory::querySupport(BackendType) -{ return true; } - -void LoopbackBackendFactory::probe(DevProbe, std::string*) -{ } - -BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType) -{ return BackendPtr{new LoopbackBackend{device}}; } - -BackendFactory &LoopbackBackendFactory::getFactory() -{ - static LoopbackBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/loopback.h b/Alc/backends/loopback.h deleted file mode 100644 index 09c085b8..00000000 --- a/Alc/backends/loopback.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_LOOPBACK_H -#define BACKENDS_LOOPBACK_H - -#include "backends/base.h" - -struct LoopbackBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_LOOPBACK_H */ diff --git a/Alc/backends/null.cpp b/Alc/backends/null.cpp deleted file mode 100644 index ae58cb8b..00000000 --- a/Alc/backends/null.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2010 by Chris Robinson - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/null.h" - -#include <exception> -#include <atomic> -#include <chrono> -#include <cstdint> -#include <cstring> -#include <functional> -#include <thread> - -#include "alcmain.h" -#include "almalloc.h" -#include "alu.h" -#include "logging.h" -#include "threads.h" - - -namespace { - -using std::chrono::seconds; -using std::chrono::milliseconds; -using std::chrono::nanoseconds; - -constexpr ALCchar nullDevice[] = "No Output"; - - -struct NullBackend final : public BackendBase { - NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(NullBackend) -}; - -int NullBackend::mixerProc() -{ - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - int64_t done{0}; - auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto now = std::chrono::steady_clock::now(); - - /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) - { - std::this_thread::sleep_for(restTime); - continue; - } - while(avail-done >= mDevice->UpdateSize) - { - lock(); - aluMixData(mDevice, nullptr, mDevice->UpdateSize); - unlock(); - done += mDevice->UpdateSize; - } - - /* For every completed second, increment the start time and reduce the - * samples done. This prevents the difference between the start time - * and current time from growing too large, while maintaining the - * correct number of samples to render. - */ - if(done >= mDevice->Frequency) - { - seconds s{done/mDevice->Frequency}; - start += s; - done -= mDevice->Frequency*s.count(); - } - } - - return 0; -} - - -ALCenum NullBackend::open(const ALCchar *name) -{ - if(!name) - name = nullDevice; - else if(strcmp(name, nullDevice) != 0) - return ALC_INVALID_VALUE; - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean NullBackend::reset() -{ - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean NullBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void NullBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); -} - -} // namespace - - -bool NullBackendFactory::init() -{ return true; } - -bool NullBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback); } - -void NullBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(nullDevice, sizeof(nullDevice)); - break; - case DevProbe::Capture: - break; - } -} - -BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new NullBackend{device}}; - return nullptr; -} - -BackendFactory &NullBackendFactory::getFactory() -{ - static NullBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/null.h b/Alc/backends/null.h deleted file mode 100644 index f19d5b4d..00000000 --- a/Alc/backends/null.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_NULL_H -#define BACKENDS_NULL_H - -#include "backends/base.h" - -struct NullBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_NULL_H */ diff --git a/Alc/backends/opensl.cpp b/Alc/backends/opensl.cpp deleted file mode 100644 index b34dc0cb..00000000 --- a/Alc/backends/opensl.cpp +++ /dev/null @@ -1,936 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* This is an OpenAL backend for Android using the native audio APIs based on - * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app - * bundled with NDK. - */ - -#include "config.h" - -#include "backends/opensl.h" - -#include <stdlib.h> -#include <jni.h> - -#include <new> -#include <array> -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#include <SLES/OpenSLES.h> -#include <SLES/OpenSLES_Android.h> -#include <SLES/OpenSLES_AndroidConfiguration.h> - - -namespace { - -/* Helper macros */ -#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) -#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS -#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS - - -constexpr ALCchar opensl_device[] = "OpenSL"; - - -SLuint32 GetChannelMask(DevFmtChannels chans) -{ - switch(chans) - { - case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; - case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; - case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_CENTER| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtAmbi3D: - break; - } - return 0; -} - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX -SLuint32 GetTypeRepresentation(DevFmtType type) -{ - switch(type) - { - case DevFmtUByte: - case DevFmtUShort: - case DevFmtUInt: - return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT; - case DevFmtByte: - case DevFmtShort: - case DevFmtInt: - return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - case DevFmtFloat: - return SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } - return 0; -} -#endif - -const char *res_str(SLresult result) -{ - switch(result) - { - case SL_RESULT_SUCCESS: return "Success"; - case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; - case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; - case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; - case SL_RESULT_RESOURCE_ERROR: return "Resource error"; - case SL_RESULT_RESOURCE_LOST: return "Resource lost"; - case SL_RESULT_IO_ERROR: return "I/O error"; - case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; - case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; - case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; - case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; - case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; - case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; - case SL_RESULT_INTERNAL_ERROR: return "Internal error"; - case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; - case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; - case SL_RESULT_CONTROL_LOST: return "Control lost"; -#ifdef SL_RESULT_READONLY - case SL_RESULT_READONLY: return "ReadOnly"; -#endif -#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED - case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; -#endif -#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE - case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; -#endif - } - return "Unknown error code"; -} - -#define PRINTERR(x, s) do { \ - if(UNLIKELY((x) != SL_RESULT_SUCCESS)) \ - ERR("%s: %s\n", (s), res_str((x))); \ -} while(0) - - -struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~OpenSLPlayback() override; - - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - - /* engine interfaces */ - SLObjectItf mEngineObj{nullptr}; - SLEngineItf mEngine{nullptr}; - - /* output mix interfaces */ - SLObjectItf mOutputMix{nullptr}; - - /* buffer queue player interfaces */ - SLObjectItf mBufferQueueObj{nullptr}; - - RingBufferPtr mRing{nullptr}; - al::semaphore mSem; - - ALsizei mFrameSize{0}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OpenSLPlayback) -}; - -OpenSLPlayback::~OpenSLPlayback() -{ - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - if(mOutputMix) - VCALL0(mOutputMix,Destroy)(); - mOutputMix = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; -} - - -/* this callback handler is called every time a buffer finishes playing */ -void OpenSLPlayback::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast<OpenSLPlayback*>(context)->process(bq); } - -void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) -{ - /* A note on the ringbuffer usage: The buffer queue seems to hold on to the - * pointer passed to the Enqueue method, rather than copying the audio. - * Consequently, the ringbuffer contains the audio that is currently queued - * and waiting to play. This process() callback is called when a buffer is - * finished, so we simply move the read pointer up to indicate the space is - * available for writing again, and wake up the mixer thread to mix and - * queue more audio. - */ - mRing->readAdvance(1); - - mSem.post(); -} - -int OpenSLPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - SLPlayItf player; - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY"); - } - - lock(); - if(SL_RESULT_SUCCESS != result) - aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result); - - while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - if(mRing->writeSpace() == 0) - { - SLuint32 state{0}; - - result = VCALL(player,GetPlayState)(&state); - PRINTERR(result, "player->GetPlayState"); - if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING) - { - result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING); - PRINTERR(result, "player->SetPlayState"); - } - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result); - break; - } - - if(mRing->writeSpace() == 0) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - } - - auto data = mRing->getWriteVector(); - aluMixData(mDevice, data.first.buf, data.first.len*mDevice->UpdateSize); - if(data.second.len > 0) - aluMixData(mDevice, data.second.buf, data.second.len*mDevice->UpdateSize); - - size_t todo{data.first.len + data.second.len}; - mRing->writeAdvance(todo); - - for(size_t i{0};i < todo;i++) - { - if(!data.first.len) - { - data.first = data.second; - data.second.buf = nullptr; - data.second.len = 0; - } - - result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); - PRINTERR(result, "bufferQueue->Enqueue"); - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result); - break; - } - - data.first.len--; - data.first.buf += mDevice->UpdateSize*mFrameSize; - } - } - unlock(); - - return 0; -} - - -ALCenum OpenSLPlayback::open(const ALCchar *name) -{ - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; - - // create engine - SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr); - PRINTERR(result, "engine->CreateOutputMix"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "outputMix->Realize"); - } - - if(SL_RESULT_SUCCESS != result) - { - if(mOutputMix) - VCALL0(mOutputMix,Destroy)(); - mOutputMix = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; - - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OpenSLPlayback::reset() -{ - SLDataLocator_AndroidSimpleBufferQueue loc_bufq; - SLDataLocator_OutputMix loc_outmix; - SLDataSource audioSrc; - SLDataSink audioSnk; - SLresult result; - - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - mRing = nullptr; - -#if 0 - if(!mDevice->Flags.get<FrequencyRequest>()) - { - /* FIXME: Disabled until I figure out how to get the Context needed for - * the getSystemService call. - */ - JNIEnv *env = Android_GetJNIEnv(); - jobject jctx = Android_GetContext(); - - /* Get necessary stuff for using java.lang.Integer, - * android.content.Context, and android.media.AudioManager. - */ - jclass int_cls = JCALL(env,FindClass)("java/lang/Integer"); - jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls, - "parseInt", "(Ljava/lang/String;)I" - ); - TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint); - - jclass ctx_cls = JCALL(env,FindClass)("android/content/Context"); - jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls, - "AUDIO_SERVICE", "Ljava/lang/String;" - ); - jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls, - "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" - ); - TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n", - ctx_cls, ctx_audsvc, ctx_getSysSvc); - - jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager"); - jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls, - "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;" - ); - jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls, - "getProperty", "(Ljava/lang/String;)Ljava/lang/String;" - ); - TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n", - audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty); - - const char *strchars; - jstring strobj; - - /* Now make the calls. */ - //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE); - strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc); - jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); - strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate); - jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //int sampleRate = Integer.parseInt(srateStr); - sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr); - - strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr); - TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars); - JCALL(env,ReleaseStringUTFChars)(srateStr, strchars); - - if(!sampleRate) sampleRate = device->Frequency; - else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE); - } -#endif - - mDevice->FmtChans = DevFmtStereo; - mDevice->FmtType = DevFmtShort; - - SetDefaultWFXChannelOrder(mDevice); - mFrameSize = mDevice->frameSizeFromFmt(); - - - const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; - const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; - - loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX - SLAndroidDataFormat_PCM_EX format_pcm{}; - format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; -#endif - - audioSrc.pLocator = &loc_bufq; - audioSrc.pFormat = &format_pcm; - - loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = mOutputMix; - audioSnk.pLocator = &loc_outmix; - audioSnk.pFormat = nullptr; - - - result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), - ids.data(), reqs.data()); - PRINTERR(result, "engine->CreateAudioPlayer"); - if(SL_RESULT_SUCCESS == result) - { - /* Set the stream type to "media" (games, music, etc), if possible. */ - SLAndroidConfigurationItf config; - result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); - if(SL_RESULT_SUCCESS == result) - { - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType, - sizeof(streamType)); - PRINTERR(result, "config->SetConfiguration"); - } - - /* Clear any error since this was optional. */ - result = SL_RESULT_SUCCESS; - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "bufferQueue->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - try { - mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true); - } - catch(std::exception& e) { - ERR("Failed allocating ring buffer %ux%ux%u: %s\n", mDevice->UpdateSize, - num_updates, mFrameSize, e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } - } - - if(SL_RESULT_SUCCESS != result) - { - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - return ALC_FALSE; - } - - return ALC_TRUE; -} - -ALCboolean OpenSLPlayback::start() -{ - mRing->reset(); - - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS != result) - return ALC_FALSE; - - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - if(SL_RESULT_SUCCESS != result) return ALC_FALSE; - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OpenSLPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); - - SLPlayItf player; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)}; - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); - PRINTERR(result, "player->SetPlayState"); - } - - SLAndroidSimpleBufferQueueItf bufferQueue; - result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL0(bufferQueue,Clear)(); - PRINTERR(result, "bufferQueue->Clear"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr); - PRINTERR(result, "bufferQueue->RegisterCallback"); - } - if(SL_RESULT_SUCCESS == result) - { - SLAndroidSimpleBufferQueueState state; - do { - std::this_thread::yield(); - result = VCALL(bufferQueue,GetState)(&state); - } while(SL_RESULT_SUCCESS == result && state.count > 0); - PRINTERR(result, "bufferQueue->GetState"); - } -} - -ClockLatency OpenSLPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct OpenSLCapture final : public BackendBase { - OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~OpenSLCapture() override; - - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - /* engine interfaces */ - SLObjectItf mEngineObj{nullptr}; - SLEngineItf mEngine; - - /* recording interfaces */ - SLObjectItf mRecordObj{nullptr}; - - RingBufferPtr mRing{nullptr}; - ALCuint mSplOffset{0u}; - - ALsizei mFrameSize{0}; - - DEF_NEWDEL(OpenSLCapture) -}; - -OpenSLCapture::~OpenSLCapture() -{ - if(mRecordObj) - VCALL0(mRecordObj,Destroy)(); - mRecordObj = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; -} - - -void OpenSLCapture::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast<OpenSLCapture*>(context)->process(bq); } - -void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) -{ - /* A new chunk has been written into the ring buffer, advance it. */ - mRing->writeAdvance(1); -} - - -ALCenum OpenSLCapture::open(const ALCchar* name) -{ - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; - - SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - mFrameSize = mDevice->frameSizeFromFmt(); - /* Ensure the total length is at least 100ms */ - ALsizei length{maxi(mDevice->BufferSize, mDevice->Frequency/10)}; - /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - ALsizei update_len{clampi(mDevice->BufferSize/3, mDevice->Frequency/100, - mDevice->Frequency/100*5)}; - ALsizei num_updates{(length+update_len-1) / update_len}; - - try { - mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false); - - mDevice->UpdateSize = update_len; - mDevice->BufferSize = mRing->writeSpace() * update_len; - } - catch(std::exception& e) { - ERR("Failed to allocate ring buffer: %s\n", e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } - } - if(SL_RESULT_SUCCESS == result) - { - const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; - const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; - - SLDataLocator_IODevice loc_dev{}; - loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; - loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT; - loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; - loc_dev.device = nullptr; - - SLDataSource audioSrc{}; - audioSrc.pLocator = &loc_dev; - audioSrc.pFormat = nullptr; - - SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; - loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX - SLAndroidDataFormat_PCM_EX format_pcm{}; - format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; -#endif - - SLDataSink audioSnk{}; - audioSnk.pLocator = &loc_bq; - audioSnk.pFormat = &format_pcm; - - result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, - ids.size(), ids.data(), reqs.data()); - PRINTERR(result, "engine->CreateAudioRecorder"); - } - if(SL_RESULT_SUCCESS == result) - { - /* Set the record preset to "generic", if possible. */ - SLAndroidConfigurationItf config; - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); - if(SL_RESULT_SUCCESS == result) - { - SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC; - result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset, - sizeof(preset)); - PRINTERR(result, "config->SetConfiguration"); - } - - /* Clear any error since this was optional. */ - result = SL_RESULT_SUCCESS; - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "recordObj->Realize"); - } - - SLAndroidSimpleBufferQueueItf bufferQueue; - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - } - if(SL_RESULT_SUCCESS == result) - { - const ALuint chunk_size{mDevice->UpdateSize * mFrameSize}; - - auto data = mRing->getWriteVector(); - for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) - { - result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); - } - for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) - { - result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); - } - } - - if(SL_RESULT_SUCCESS != result) - { - if(mRecordObj) - VCALL0(mRecordObj,Destroy)(); - mRecordObj = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; - - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OpenSLCapture::start() -{ - SLRecordItf record; - SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); - - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); - PRINTERR(result, "record->SetRecordState"); - } - - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result); - return ALC_FALSE; - } - - return ALC_TRUE; -} - -void OpenSLCapture::stop() -{ - SLRecordItf record; - SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); - - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED); - PRINTERR(result, "record->SetRecordState"); - } -} - -ALCenum OpenSLCapture::captureSamples(void* buffer, ALCuint samples) -{ - ALsizei chunk_size = mDevice->UpdateSize * mFrameSize; - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result; - ALCuint i; - - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); - - /* Read the desired samples from the ring buffer then advance its read - * pointer. - */ - auto data = mRing->getReadVector(); - for(i = 0;i < samples;) - { - ALCuint rem{minu(samples - i, mDevice->UpdateSize - mSplOffset)}; - memcpy((ALCbyte*)buffer + i*mFrameSize, data.first.buf + mSplOffset*mFrameSize, - rem * mFrameSize); - - mSplOffset += rem; - if(mSplOffset == mDevice->UpdateSize) - { - /* Finished a chunk, reset the offset and advance the read pointer. */ - mSplOffset = 0; - - mRing->readAdvance(1); - result = VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); - if(SL_RESULT_SUCCESS != result) break; - - data.first.len--; - if(!data.first.len) - data.first = data.second; - else - data.first.buf += chunk_size; - } - - i += rem; - } - - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", result); - return ALC_INVALID_DEVICE; - } - - return ALC_NO_ERROR; -} - -ALCuint OpenSLCapture::availableSamples() -{ return mRing->readSpace()*mDevice->UpdateSize - mSplOffset; } - -} // namespace - -bool OSLBackendFactory::init() { return true; } - -bool OSLBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void OSLBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(opensl_device, sizeof(opensl_device)); - break; - } -} - -BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new OpenSLPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new OpenSLCapture{device}}; - return nullptr; -} - -BackendFactory &OSLBackendFactory::getFactory() -{ - static OSLBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/opensl.h b/Alc/backends/opensl.h deleted file mode 100644 index 809aa339..00000000 --- a/Alc/backends/opensl.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_OSL_H -#define BACKENDS_OSL_H - -#include "backends/base.h" - -struct OSLBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_OSL_H */ diff --git a/Alc/backends/oss.cpp b/Alc/backends/oss.cpp deleted file mode 100644 index 8cfe9e96..00000000 --- a/Alc/backends/oss.cpp +++ /dev/null @@ -1,751 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/oss.h" - -#include <fcntl.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <cerrno> -#include <cstdio> -#include <cstring> -#include <exception> -#include <functional> -#include <memory> -#include <new> -#include <string> -#include <thread> -#include <utility> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alu.h" -#include "logging.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" - -#include <sys/soundcard.h> - -/* - * The OSS documentation talks about SOUND_MIXER_READ, but the header - * only contains MIXER_READ. Play safe. Same for WRITE. - */ -#ifndef SOUND_MIXER_READ -#define SOUND_MIXER_READ MIXER_READ -#endif -#ifndef SOUND_MIXER_WRITE -#define SOUND_MIXER_WRITE MIXER_WRITE -#endif - -#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000) -#define ALC_OSS_COMPAT -#endif -#ifndef SNDCTL_AUDIOINFO -#define ALC_OSS_COMPAT -#endif - -/* - * FreeBSD strongly discourages the use of specific devices, - * such as those returned in oss_audioinfo.devnode - */ -#ifdef __FreeBSD__ -#define ALC_OSS_DEVNODE_TRUC -#endif - -namespace { - -constexpr char DefaultName[] = "OSS Default"; -std::string DefaultPlayback{"/dev/dsp"}; -std::string DefaultCapture{"/dev/dsp"}; - -struct DevMap { - std::string name; - std::string device_name; -}; - -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -#ifdef ALC_OSS_COMPAT - -#define DSP_CAP_OUTPUT 0x00020000 -#define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector<DevMap> *devlist, int type) -{ - devlist->emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); -} - -#else - -void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen) -{ -#ifdef ALC_OSS_DEVNODE_TRUC - for(size_t i{0};i < plen;i++) - { - if(path[i] == '.') - { - if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0) - hlen = hlen + i - plen; - plen = i; - } - } -#endif - if(handle[0] == '\0') - { - handle = path; - hlen = plen; - } - - std::string basename{handle, hlen}; - basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end()); - std::string devname{path, plen}; - devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end()); - - auto iter = std::find_if(list->cbegin(), list->cend(), - [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; } - ); - if(iter != list->cend()) - return; - - int count{1}; - std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - - list->emplace_back(DevMap{std::move(newname), std::move(devname)}); - const DevMap &entry = list->back(); - - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); -} - -void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag) -{ - int fd{open("/dev/mixer", O_RDONLY)}; - if(fd < 0) - { - TRACE("Could not open /dev/mixer: %s\n", strerror(errno)); - goto done; - } - - oss_sysinfo si; - if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1) - { - TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno)); - goto done; - } - - for(int i{0};i < si.numaudios;i++) - { - oss_audioinfo ai; - ai.dev = i; - if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1) - { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno)); - continue; - } - if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') - continue; - - const char *handle; - size_t len; - if(ai.handle[0] != '\0') - { - len = strnlen(ai.handle, sizeof(ai.handle)); - handle = ai.handle; - } - else - { - len = strnlen(ai.name, sizeof(ai.name)); - handle = ai.name; - } - - ALCossListAppend(devlist, handle, len, ai.devnode, - strnlen(ai.devnode, sizeof(ai.devnode))); - } - -done: - if(fd >= 0) - close(fd); - fd = -1; - - const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; - auto iter = std::find_if(devlist->cbegin(), devlist->cend(), - [defdev](const DevMap &entry) -> bool - { return entry.device_name == defdev; } - ); - if(iter == devlist->cend()) - devlist->insert(devlist->begin(), DevMap{DefaultName, defdev}); - else - { - DevMap entry{std::move(*iter)}; - devlist->erase(iter); - devlist->insert(devlist->begin(), std::move(entry)); - } - devlist->shrink_to_fit(); -} - -#endif - -int log2i(ALCuint x) -{ - int y = 0; - while (x > 1) - { - x >>= 1; - y++; - } - return y; -} - - -struct OSSPlayback final : public BackendBase { - OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~OSSPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - int mFd{-1}; - - al::vector<ALubyte> mMixData; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OSSPlayback) -}; - -OSSPlayback::~OSSPlayback() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - - -int OSSPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLOUT; - - unlock(); - int pret{poll(&pollitem, 1, 1000)}; - lock(); - if(pret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - else if(pret == 0) - { - WARN("poll timeout\n"); - continue; - } - - ALubyte *write_ptr{mMixData.data()}; - size_t to_write{mMixData.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) - { - ssize_t wrote{write(mFd, write_ptr, to_write)}; - if(wrote < 0) - { - if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) - continue; - ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed writing playback samples: %s", - strerror(errno)); - break; - } - - to_write -= wrote; - write_ptr += wrote; - } - } - unlock(); - - return 0; -} - - -ALCenum OSSPlayback::open(const ALCchar *name) -{ - const char *devname{DefaultPlayback.c_str()}; - if(!name) - name = DefaultName; - else - { - if(PlaybackDevices.empty()) - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - devname = iter->device_name.c_str(); - } - - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OSSPlayback::reset() -{ - int numFragmentsLogSize; - int log2FragmentSize; - unsigned int periods; - audio_buf_info info; - ALuint frameSize; - int numChannels; - int ossFormat; - int ossSpeed; - const char *err; - - switch(mDevice->FmtType) - { - case DevFmtByte: - ossFormat = AFMT_S8; - break; - case DevFmtUByte: - ossFormat = AFMT_U8; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - ossFormat = AFMT_S16_NE; - break; - } - - periods = mDevice->BufferSize / mDevice->UpdateSize; - numChannels = mDevice->channelsFromFmt(); - ossSpeed = mDevice->Frequency; - frameSize = numChannels * mDevice->bytesFromFmt(); - /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - log2FragmentSize = maxi(log2i(mDevice->UpdateSize*frameSize), 4); - numFragmentsLogSize = (periods << 16) | log2FragmentSize; - -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} - /* Don't fail if SETFRAGMENT fails. We can handle just about anything - * that's reported back via GETOSPACE */ - ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - return ALC_FALSE; - } -#undef CHECKERR - - if(mDevice->channelsFromFmt() != numChannels) - { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - numChannels); - return ALC_FALSE; - } - - if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || - (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || - (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), - ossFormat); - return ALC_FALSE; - } - - mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = info.fragsize / frameSize; - mDevice->BufferSize = info.fragments * mDevice->UpdateSize; - - SetDefaultChannelOrder(mDevice); - - mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - - return ALC_TRUE; -} - -ALCboolean OSSPlayback::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OSSPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); -} - - -struct OSScapture final : public BackendBase { - OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~OSScapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - int mFd{-1}; - - RingBufferPtr mRing{nullptr}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OSScapture) -}; - -OSScapture::~OSScapture() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - - -int OSScapture::recordProc() -{ - SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - while(!mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLIN; - - int sret{poll(&pollitem, 1, 1000)}; - if(sret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno)); - break; - } - else if(sret == 0) - { - WARN("poll timeout\n"); - continue; - } - - auto vec = mRing->getWriteVector(); - if(vec.first.len > 0) - { - ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; - if(amt < 0) - { - ERR("read failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed reading capture samples: %s", - strerror(errno)); - break; - } - mRing->writeAdvance(amt/frame_size); - } - } - - return 0; -} - - -ALCenum OSScapture::open(const ALCchar *name) -{ - const char *devname{DefaultCapture.c_str()}; - if(!name) - name = DefaultName; - else - { - if(CaptureDevices.empty()) - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - devname = iter->device_name.c_str(); - } - - mFd = ::open(devname, O_RDONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - int ossFormat{}; - switch(mDevice->FmtType) - { - case DevFmtByte: - ossFormat = AFMT_S8; - break; - case DevFmtUByte: - ossFormat = AFMT_U8; - break; - case DevFmtShort: - ossFormat = AFMT_S16_NE; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - - int periods{4}; - int numChannels{mDevice->channelsFromFmt()}; - int frameSize{numChannels * mDevice->bytesFromFmt()}; - int ossSpeed{static_cast<int>(mDevice->Frequency)}; - int log2FragmentSize{log2i(mDevice->BufferSize * frameSize / periods)}; - - /* according to the OSS spec, 16 bytes are the minimum */ - log2FragmentSize = std::max(log2FragmentSize, 4); - int numFragmentsLogSize{(periods << 16) | log2FragmentSize}; - - audio_buf_info info; - const char *err; -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } -#undef CHECKERR - - if(mDevice->channelsFromFmt() != numChannels) - { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - numChannels); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } - - if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || - (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || - (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ossFormat); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } - - mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false); - if(!mRing) - { - ERR("Ring buffer create failed\n"); - close(mFd); - mFd = -1; - return ALC_OUT_OF_MEMORY; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OSScapture::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OSScapture::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); -} - -ALCenum OSScapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint OSScapture::availableSamples() -{ return mRing->readSpace(); } - -} // namespace - - -BackendFactory &OSSBackendFactory::getFactory() -{ - static OSSBackendFactory factory{}; - return factory; -} - -bool OSSBackendFactory::init() -{ - if(auto devopt = ConfigValueStr(nullptr, "oss", "device")) - DefaultPlayback = std::move(*devopt); - if(auto capopt = ConfigValueStr(nullptr, "oss", "capture")) - DefaultCapture = std::move(*capopt); - - return true; -} - -bool OSSBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void OSSBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { -#ifdef HAVE_STAT - struct stat buf; - if(stat(entry.device_name.c_str(), &buf) == 0) -#endif - { - /* Includes null char. */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - } - }; - - switch(type) - { - case DevProbe::Playback: - PlaybackDevices.clear(); - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices.clear(); - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new OSSPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new OSScapture{device}}; - return nullptr; -} diff --git a/Alc/backends/oss.h b/Alc/backends/oss.h deleted file mode 100644 index 9e63d7b6..00000000 --- a/Alc/backends/oss.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_OSS_H -#define BACKENDS_OSS_H - -#include "backends/base.h" - -struct OSSBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_OSS_H */ diff --git a/Alc/backends/portaudio.cpp b/Alc/backends/portaudio.cpp deleted file mode 100644 index 73e972c5..00000000 --- a/Alc/backends/portaudio.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/portaudio.h" - -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "ringbuffer.h" -#include "compat.h" - -#include <portaudio.h> - - -namespace { - -constexpr ALCchar pa_device[] = "PortAudio Default"; - - -#ifdef HAVE_DYNLOAD -void *pa_handle; -#define MAKE_FUNC(x) decltype(x) * p##x -MAKE_FUNC(Pa_Initialize); -MAKE_FUNC(Pa_Terminate); -MAKE_FUNC(Pa_GetErrorText); -MAKE_FUNC(Pa_StartStream); -MAKE_FUNC(Pa_StopStream); -MAKE_FUNC(Pa_OpenStream); -MAKE_FUNC(Pa_CloseStream); -MAKE_FUNC(Pa_GetDefaultOutputDevice); -MAKE_FUNC(Pa_GetDefaultInputDevice); -MAKE_FUNC(Pa_GetStreamInfo); -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define Pa_Initialize pPa_Initialize -#define Pa_Terminate pPa_Terminate -#define Pa_GetErrorText pPa_GetErrorText -#define Pa_StartStream pPa_StartStream -#define Pa_StopStream pPa_StopStream -#define Pa_OpenStream pPa_OpenStream -#define Pa_CloseStream pPa_CloseStream -#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice -#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice -#define Pa_GetStreamInfo pPa_GetStreamInfo -#endif -#endif - - -struct PortPlayback final : public BackendBase { - PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~PortPlayback() override; - - static int writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - PaStream *mStream{nullptr}; - PaStreamParameters mParams{}; - ALuint mUpdateSize{0u}; - - DEF_NEWDEL(PortPlayback) -}; - -PortPlayback::~PortPlayback() -{ - PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; - if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); - mStream = nullptr; -} - - -int PortPlayback::writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) -{ - return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortPlayback::writeCallback(const void*, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, - const PaStreamCallbackFlags) -{ - lock(); - aluMixData(mDevice, outputBuffer, framesPerBuffer); - unlock(); - return 0; -} - - -ALCenum PortPlayback::open(const ALCchar *name) -{ - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; - - mUpdateSize = mDevice->UpdateSize; - - auto devidopt = ConfigValueInt(nullptr, "port", "device"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency); - mParams.hostApiSpecificStreamInfo = nullptr; - - mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - - switch(mDevice->FmtType) - { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtUShort: - /* fall-through */ - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtUInt: - /* fall-through */ - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; - } - -retry_open: - PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize, - paNoFlag, &PortPlayback::writeCallbackC, this)}; - if(err != paNoError) - { - if(mParams.sampleFormat == paFloat32) - { - mParams.sampleFormat = paInt16; - goto retry_open; - } - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; - -} - -ALCboolean PortPlayback::reset() -{ - const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = streamInfo->sampleRate; - mDevice->UpdateSize = mUpdateSize; - - if(mParams.sampleFormat == paInt8) - mDevice->FmtType = DevFmtByte; - else if(mParams.sampleFormat == paUInt8) - mDevice->FmtType = DevFmtUByte; - else if(mParams.sampleFormat == paInt16) - mDevice->FmtType = DevFmtShort; - else if(mParams.sampleFormat == paInt32) - mDevice->FmtType = DevFmtInt; - else if(mParams.sampleFormat == paFloat32) - mDevice->FmtType = DevFmtFloat; - else - { - ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat); - return ALC_FALSE; - } - - if(mParams.channelCount == 2) - mDevice->FmtChans = DevFmtStereo; - else if(mParams.channelCount == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unexpected channel count: %u\n", mParams.channelCount); - return ALC_FALSE; - } - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean PortPlayback::start() -{ - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void PortPlayback::stop() -{ - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); -} - - -struct PortCapture final : public BackendBase { - PortCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~PortCapture() override; - - static int readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - PaStream *mStream{nullptr}; - PaStreamParameters mParams; - - RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(PortCapture) -}; - -PortCapture::~PortCapture() -{ - PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; - if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); - mStream = nullptr; -} - - -int PortCapture::readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void* userData) -{ - return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortCapture::readCallback(const void *inputBuffer, void*, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, - const PaStreamCallbackFlags) -{ - mRing->write(inputBuffer, framesPerBuffer); - return 0; -} - - -ALCenum PortCapture::open(const ALCchar *name) -{ - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - ALsizei frame_size{mDevice->frameSizeFromFmt()}; - - mRing = CreateRingBuffer(samples, frame_size, false); - if(!mRing) return ALC_INVALID_VALUE; - - auto devidopt = ConfigValueInt(nullptr, "port", "capture"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = 0.0f; - mParams.hostApiSpecificStreamInfo = nullptr; - - switch(mDevice->FmtType) - { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; - case DevFmtUInt: - case DevFmtUShort: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - mParams.channelCount = mDevice->channelsFromFmt(); - - PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, - paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; - if(err != paNoError) - { - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - - -ALCboolean PortCapture::start() -{ - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void PortCapture::stop() -{ - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); -} - - -ALCuint PortCapture::availableSamples() -{ return mRing->readSpace(); } - -ALCenum PortCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -} // namespace - - -bool PortBackendFactory::init() -{ - PaError err; - -#ifdef HAVE_DYNLOAD - if(!pa_handle) - { -#ifdef _WIN32 -# define PALIB "portaudio.dll" -#elif defined(__APPLE__) && defined(__MACH__) -# define PALIB "libportaudio.2.dylib" -#elif defined(__OpenBSD__) -# define PALIB "libportaudio.so" -#else -# define PALIB "libportaudio.so.2" -#endif - - pa_handle = LoadLib(PALIB); - if(!pa_handle) - return false; - -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \ - if(p##f == nullptr) \ - { \ - CloseLib(pa_handle); \ - pa_handle = nullptr; \ - return false; \ - } \ -} while(0) - LOAD_FUNC(Pa_Initialize); - LOAD_FUNC(Pa_Terminate); - LOAD_FUNC(Pa_GetErrorText); - LOAD_FUNC(Pa_StartStream); - LOAD_FUNC(Pa_StopStream); - LOAD_FUNC(Pa_OpenStream); - LOAD_FUNC(Pa_CloseStream); - LOAD_FUNC(Pa_GetDefaultOutputDevice); - LOAD_FUNC(Pa_GetDefaultInputDevice); - LOAD_FUNC(Pa_GetStreamInfo); -#undef LOAD_FUNC - - if((err=Pa_Initialize()) != paNoError) - { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); - CloseLib(pa_handle); - pa_handle = nullptr; - return false; - } - } -#else - if((err=Pa_Initialize()) != paNoError) - { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); - return false; - } -#endif - return true; -} - -bool PortBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void PortBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(pa_device, sizeof(pa_device)); - break; - } -} - -BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PortPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new PortCapture{device}}; - return nullptr; -} - -BackendFactory &PortBackendFactory::getFactory() -{ - static PortBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/portaudio.h b/Alc/backends/portaudio.h deleted file mode 100644 index 082e9020..00000000 --- a/Alc/backends/portaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_PORTAUDIO_H -#define BACKENDS_PORTAUDIO_H - -#include "backends/base.h" - -struct PortBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_PORTAUDIO_H */ diff --git a/Alc/backends/pulseaudio.cpp b/Alc/backends/pulseaudio.cpp deleted file mode 100644 index da209c8d..00000000 --- a/Alc/backends/pulseaudio.cpp +++ /dev/null @@ -1,1532 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2009 by Konstantinos Natsakis <[email protected]> - * Copyright (C) 2010 by Chris Robinson <[email protected]> - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/pulseaudio.h" - -#include <poll.h> -#include <cstring> - -#include <array> -#include <string> -#include <vector> -#include <atomic> -#include <thread> -#include <algorithm> -#include <condition_variable> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" -#include "alexcpt.h" - -#include <pulse/pulseaudio.h> - - -namespace { - -#ifdef HAVE_DYNLOAD -#define PULSE_FUNCS(MAGIC) \ - MAGIC(pa_mainloop_new); \ - MAGIC(pa_mainloop_free); \ - MAGIC(pa_mainloop_set_poll_func); \ - MAGIC(pa_mainloop_run); \ - MAGIC(pa_mainloop_get_api); \ - MAGIC(pa_context_new); \ - MAGIC(pa_context_unref); \ - MAGIC(pa_context_get_state); \ - MAGIC(pa_context_disconnect); \ - MAGIC(pa_context_set_state_callback); \ - MAGIC(pa_context_errno); \ - MAGIC(pa_context_connect); \ - MAGIC(pa_context_get_server_info); \ - MAGIC(pa_context_get_sink_info_by_name); \ - MAGIC(pa_context_get_sink_info_list); \ - MAGIC(pa_context_get_source_info_by_name); \ - MAGIC(pa_context_get_source_info_list); \ - MAGIC(pa_stream_new); \ - MAGIC(pa_stream_unref); \ - MAGIC(pa_stream_drop); \ - MAGIC(pa_stream_get_state); \ - MAGIC(pa_stream_peek); \ - MAGIC(pa_stream_write); \ - MAGIC(pa_stream_connect_record); \ - MAGIC(pa_stream_connect_playback); \ - MAGIC(pa_stream_readable_size); \ - MAGIC(pa_stream_writable_size); \ - MAGIC(pa_stream_is_corked); \ - MAGIC(pa_stream_cork); \ - MAGIC(pa_stream_is_suspended); \ - MAGIC(pa_stream_get_device_name); \ - MAGIC(pa_stream_get_latency); \ - MAGIC(pa_stream_set_write_callback); \ - MAGIC(pa_stream_set_buffer_attr); \ - MAGIC(pa_stream_get_buffer_attr); \ - MAGIC(pa_stream_get_sample_spec); \ - MAGIC(pa_stream_get_time); \ - MAGIC(pa_stream_set_read_callback); \ - MAGIC(pa_stream_set_state_callback); \ - MAGIC(pa_stream_set_moved_callback); \ - MAGIC(pa_stream_set_underflow_callback); \ - MAGIC(pa_stream_new_with_proplist); \ - MAGIC(pa_stream_disconnect); \ - MAGIC(pa_stream_set_buffer_attr_callback); \ - MAGIC(pa_stream_begin_write); \ - MAGIC(pa_channel_map_init_auto); \ - MAGIC(pa_channel_map_parse); \ - MAGIC(pa_channel_map_snprint); \ - MAGIC(pa_channel_map_equal); \ - MAGIC(pa_channel_map_superset); \ - MAGIC(pa_operation_get_state); \ - MAGIC(pa_operation_unref); \ - MAGIC(pa_sample_spec_valid); \ - MAGIC(pa_frame_size); \ - MAGIC(pa_strerror); \ - MAGIC(pa_path_get_filename); \ - MAGIC(pa_get_binary_name); \ - MAGIC(pa_xmalloc); \ - MAGIC(pa_xfree); - -void *pulse_handle; -#define MAKE_FUNC(x) decltype(x) * p##x -PULSE_FUNCS(MAKE_FUNC) -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define pa_mainloop_new ppa_mainloop_new -#define pa_mainloop_free ppa_mainloop_free -#define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func -#define pa_mainloop_run ppa_mainloop_run -#define pa_mainloop_get_api ppa_mainloop_get_api -#define pa_context_new ppa_context_new -#define pa_context_unref ppa_context_unref -#define pa_context_get_state ppa_context_get_state -#define pa_context_disconnect ppa_context_disconnect -#define pa_context_set_state_callback ppa_context_set_state_callback -#define pa_context_errno ppa_context_errno -#define pa_context_connect ppa_context_connect -#define pa_context_get_server_info ppa_context_get_server_info -#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name -#define pa_context_get_sink_info_list ppa_context_get_sink_info_list -#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name -#define pa_context_get_source_info_list ppa_context_get_source_info_list -#define pa_stream_new ppa_stream_new -#define pa_stream_unref ppa_stream_unref -#define pa_stream_disconnect ppa_stream_disconnect -#define pa_stream_drop ppa_stream_drop -#define pa_stream_set_write_callback ppa_stream_set_write_callback -#define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr -#define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr -#define pa_stream_get_sample_spec ppa_stream_get_sample_spec -#define pa_stream_get_time ppa_stream_get_time -#define pa_stream_set_read_callback ppa_stream_set_read_callback -#define pa_stream_set_state_callback ppa_stream_set_state_callback -#define pa_stream_set_moved_callback ppa_stream_set_moved_callback -#define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback -#define pa_stream_connect_record ppa_stream_connect_record -#define pa_stream_connect_playback ppa_stream_connect_playback -#define pa_stream_readable_size ppa_stream_readable_size -#define pa_stream_writable_size ppa_stream_writable_size -#define pa_stream_is_corked ppa_stream_is_corked -#define pa_stream_cork ppa_stream_cork -#define pa_stream_is_suspended ppa_stream_is_suspended -#define pa_stream_get_device_name ppa_stream_get_device_name -#define pa_stream_get_latency ppa_stream_get_latency -#define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback -#define pa_stream_begin_write ppa_stream_begin_write*/ -#define pa_channel_map_init_auto ppa_channel_map_init_auto -#define pa_channel_map_parse ppa_channel_map_parse -#define pa_channel_map_snprint ppa_channel_map_snprint -#define pa_channel_map_equal ppa_channel_map_equal -#define pa_channel_map_superset ppa_channel_map_superset -#define pa_operation_get_state ppa_operation_get_state -#define pa_operation_unref ppa_operation_unref -#define pa_sample_spec_valid ppa_sample_spec_valid -#define pa_frame_size ppa_frame_size -#define pa_strerror ppa_strerror -#define pa_stream_get_state ppa_stream_get_state -#define pa_stream_peek ppa_stream_peek -#define pa_stream_write ppa_stream_write -#define pa_xfree ppa_xfree -#define pa_path_get_filename ppa_path_get_filename -#define pa_get_binary_name ppa_get_binary_name -#define pa_xmalloc ppa_xmalloc -#endif /* IN_IDE_PARSER */ - -#endif - - -constexpr pa_channel_map MonoChanMap{ - 1, {PA_CHANNEL_POSITION_MONO} -}, StereoChanMap{ - 2, {PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT} -}, QuadChanMap{ - 4, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT - } -}, X51ChanMap{ - 6, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT - } -}, X51RearChanMap{ - 6, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT - } -}, X61ChanMap{ - 7, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_REAR_CENTER, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT - } -}, X71ChanMap{ - 8, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT - } -}; - -size_t ChannelFromPulse(pa_channel_position_t chan) -{ - switch(chan) - { - case PA_CHANNEL_POSITION_INVALID: break; - case PA_CHANNEL_POSITION_MONO: return FrontCenter; - case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft; - case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight; - case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter; - case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter; - case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft; - case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight; - case PA_CHANNEL_POSITION_LFE: return LFE; - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break; - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break; - case PA_CHANNEL_POSITION_SIDE_LEFT: return SideLeft; - case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight; - case PA_CHANNEL_POSITION_AUX0: return Aux0; - case PA_CHANNEL_POSITION_AUX1: return Aux1; - case PA_CHANNEL_POSITION_AUX2: return Aux2; - case PA_CHANNEL_POSITION_AUX3: return Aux3; - case PA_CHANNEL_POSITION_AUX4: return Aux4; - case PA_CHANNEL_POSITION_AUX5: return Aux5; - case PA_CHANNEL_POSITION_AUX6: return Aux6; - case PA_CHANNEL_POSITION_AUX7: return Aux7; - case PA_CHANNEL_POSITION_AUX8: return Aux8; - case PA_CHANNEL_POSITION_AUX9: return Aux9; - case PA_CHANNEL_POSITION_AUX10: return Aux10; - case PA_CHANNEL_POSITION_AUX11: return Aux11; - case PA_CHANNEL_POSITION_AUX12: return Aux12; - case PA_CHANNEL_POSITION_AUX13: return Aux13; - case PA_CHANNEL_POSITION_AUX14: return Aux14; - case PA_CHANNEL_POSITION_AUX15: return Aux15; - case PA_CHANNEL_POSITION_AUX16: break; - case PA_CHANNEL_POSITION_AUX17: break; - case PA_CHANNEL_POSITION_AUX18: break; - case PA_CHANNEL_POSITION_AUX19: break; - case PA_CHANNEL_POSITION_AUX20: break; - case PA_CHANNEL_POSITION_AUX21: break; - case PA_CHANNEL_POSITION_AUX22: break; - case PA_CHANNEL_POSITION_AUX23: break; - case PA_CHANNEL_POSITION_AUX24: break; - case PA_CHANNEL_POSITION_AUX25: break; - case PA_CHANNEL_POSITION_AUX26: break; - case PA_CHANNEL_POSITION_AUX27: break; - case PA_CHANNEL_POSITION_AUX28: break; - case PA_CHANNEL_POSITION_AUX29: break; - case PA_CHANNEL_POSITION_AUX30: break; - case PA_CHANNEL_POSITION_AUX31: break; - case PA_CHANNEL_POSITION_TOP_CENTER: break; - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft; - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight; - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break; - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft; - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight; - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break; - case PA_CHANNEL_POSITION_MAX: break; - } - throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan}; -} - -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) -{ - device->RealOut.ChannelIndex.fill(-1); - for(int i{0};i < chanmap.channels;++i) - device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = i; -} - - -/* *grumble* Don't use enums for bitflags. */ -inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) -{ return pa_stream_flags_t(int(lhs) | int(rhs)); } -inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) -{ - lhs = pa_stream_flags_t(int(lhs) | int(rhs)); - return lhs; -} -inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) -{ - lhs = pa_context_flags_t(int(lhs) | int(rhs)); - return lhs; -} - -inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) -{ - lhs = pa_stream_flags_t(int(lhs) & rhs); - return lhs; -} - - -/* Global flags and properties */ -pa_context_flags_t pulse_ctx_flags; - -pa_mainloop *pulse_mainloop{nullptr}; - -std::mutex pulse_lock; -std::condition_variable pulse_condvar; - -int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) -{ - auto plock = static_cast<std::unique_lock<std::mutex>*>(userdata); - plock->unlock(); - int r{poll(ufds, nfds, timeout)}; - plock->lock(); - return r; -} - -int pulse_mainloop_thread() -{ - SetRTPriority(); - - std::unique_lock<std::mutex> plock{pulse_lock}; - pulse_mainloop = pa_mainloop_new(); - - pa_mainloop_set_poll_func(pulse_mainloop, pulse_poll_func, &plock); - pulse_condvar.notify_all(); - - int ret{}; - pa_mainloop_run(pulse_mainloop, &ret); - - pa_mainloop_free(pulse_mainloop); - pulse_mainloop = nullptr; - - return ret; -} - - -/* PulseAudio Event Callbacks */ -void context_state_callback(pa_context *context, void* /*pdata*/) -{ - pa_context_state_t state{pa_context_get_state(context)}; - if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) - pulse_condvar.notify_all(); -} - -void stream_state_callback(pa_stream *stream, void* /*pdata*/) -{ - pa_stream_state_t state{pa_stream_get_state(stream)}; - if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) - pulse_condvar.notify_all(); -} - -void stream_success_callback(pa_stream* /*stream*/, int /*success*/, void* /*pdata*/) -{ - pulse_condvar.notify_all(); -} - -void wait_for_operation(pa_operation *op, std::unique_lock<std::mutex> &plock) -{ - if(op) - { - while(pa_operation_get_state(op) == PA_OPERATION_RUNNING) - pulse_condvar.wait(plock); - pa_operation_unref(op); - } -} - - -pa_context *connect_context(std::unique_lock<std::mutex> &plock) -{ - const char *name{"OpenAL Soft"}; - - const PathNamePair &binname = GetProcBinary(); - if(!binname.fname.empty()) - name = binname.fname.c_str(); - - if(UNLIKELY(!pulse_mainloop)) - { - std::thread{pulse_mainloop_thread}.detach(); - while(!pulse_mainloop) - pulse_condvar.wait(plock); - } - - pa_context *context{pa_context_new(pa_mainloop_get_api(pulse_mainloop), name)}; - if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_context_new() failed"}; - - pa_context_set_state_callback(context, context_state_callback, nullptr); - - int err; - if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) - { - pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) - { - if(!PA_CONTEXT_IS_GOOD(state)) - { - err = pa_context_errno(context); - if(err > 0) err = -err; - break; - } - - pulse_condvar.wait(plock); - } - } - pa_context_set_state_callback(context, nullptr, nullptr); - - if(err < 0) - { - pa_context_unref(context); - throw al::backend_exception{ALC_INVALID_VALUE, "Context did not connect (%s)", - pa_strerror(err)}; - } - - return context; -} - - -void pulse_close(pa_context *context, pa_stream *stream) -{ - std::lock_guard<std::mutex> _{pulse_lock}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } - - pa_context_disconnect(context); - pa_context_unref(context); -} - - -struct DevMap { - std::string name; - std::string device_name; -}; - -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -pa_stream *pulse_connect_stream(const char *device_name, std::unique_lock<std::mutex> &plock, - pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, - pa_channel_map *chanmap, BackendType type) -{ - const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; - pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)}; - if(!stream) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_stream_new() failed (%s)", - pa_strerror(pa_context_errno(context))}; - - pa_stream_set_state_callback(stream, stream_state_callback, nullptr); - - int err{(type==BackendType::Playback) ? - pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : - pa_stream_connect_record(stream, device_name, attr, flags)}; - if(err < 0) - { - pa_stream_unref(stream); - throw al::backend_exception{ALC_INVALID_VALUE, "%s did not connect (%s)", stream_id, - pa_strerror(err)}; - } - - pa_stream_state_t state; - while((state=pa_stream_get_state(stream)) != PA_STREAM_READY) - { - if(!PA_STREAM_IS_GOOD(state)) - { - int err{pa_context_errno(context)}; - pa_stream_unref(stream); - throw al::backend_exception{ALC_INVALID_VALUE, "%s did not get ready (%s)", stream_id, - pa_strerror(err)}; - } - - pulse_condvar.wait(plock); - } - pa_stream_set_state_callback(stream, nullptr, nullptr); - - return stream; -} - - -void device_sink_callback(pa_context*, const pa_sink_info *info, int eol, void*) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != PlaybackDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(PlaybackDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = PlaybackDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} - -void probePlaybackDevices() -{ - PlaybackDevices.clear(); - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_context *context{connect_context(plock)}; - - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, - nullptr, BackendType::Playback)}; - pa_operation *op{pa_context_get_sink_info_by_name(context, - pa_stream_get_device_name(stream), device_sink_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_sink_info_list(context, device_sink_callback, nullptr); - wait_for_operation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - -void device_source_callback(pa_context*, const pa_source_info *info, int eol, void*) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != CaptureDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} - -void probeCaptureDevices() -{ - CaptureDevices.clear(); - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_context *context{connect_context(plock)}; - - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 1; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, nullptr, - BackendType::Capture)}; - pa_operation *op{pa_context_get_source_info_by_name(context, - pa_stream_get_device_name(stream), device_source_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_source_info_list(context, device_source_callback, nullptr); - wait_for_operation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - -struct PulsePlayback final : public BackendBase { - PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~PulsePlayback() override; - - static void bufferAttrCallbackC(pa_stream *stream, void *pdata); - void bufferAttrCallback(pa_stream *stream); - - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); - - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); - - static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata); - void streamWriteCallback(pa_stream *stream, size_t nbytes); - - static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol); - - static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol); - - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - - std::string mDeviceName; - - pa_buffer_attr mAttr; - pa_sample_spec mSpec; - - pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; - - ALuint mFrameSize{0u}; - - DEF_NEWDEL(PulsePlayback) -}; - -PulsePlayback::~PulsePlayback() -{ - if(!mContext) - return; - - pulse_close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} - - -void PulsePlayback::bufferAttrCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); } - -void PulsePlayback::bufferAttrCallback(pa_stream *stream) -{ - /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new - * buffer attributes? Changing UpdateSize will change the ALC_REFRESH - * property, which probably shouldn't change between device resets. But - * leaving it alone means ALC_REFRESH will be off. - */ - mAttr = *(pa_stream_get_buffer_attr(stream)); - TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); -} - -void PulsePlayback::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->contextStateCallback(context); } - -void PulsePlayback::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Playback state failure"); - } - pulse_condvar.notify_all(); -} - -void PulsePlayback::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); } - -void PulsePlayback::streamStateCallback(pa_stream *stream) -{ - if(pa_stream_get_state(stream) == PA_STREAM_FAILED) - { - ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Playback stream failure"); - } - pulse_condvar.notify_all(); -} - -void PulsePlayback::streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); } - -void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) -{ - void *buf{pa_xmalloc(nbytes)}; - aluMixData(mDevice, buf, nbytes/mFrameSize); - - int ret{pa_stream_write(stream, buf, nbytes, pa_xfree, 0, PA_SEEK_RELATIVE)}; - if(UNLIKELY(ret != PA_OK)) - ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); -} - -void PulsePlayback::sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); } - -void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) -{ - struct ChannelMap { - DevFmtChannels chans; - pa_channel_map map; - }; - static constexpr std::array<ChannelMap,7> chanmaps{{ - { DevFmtX71, X71ChanMap }, - { DevFmtX61, X61ChanMap }, - { DevFmtX51, X51ChanMap }, - { DevFmtX51Rear, X51RearChanMap }, - { DevFmtQuad, QuadChanMap }, - { DevFmtStereo, StereoChanMap }, - { DevFmtMono, MonoChanMap } - }}; - - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - auto chanmap = std::find_if(chanmaps.cbegin(), chanmaps.cend(), - [info](const ChannelMap &chanmap) -> bool - { return pa_channel_map_superset(&info->channel_map, &chanmap.map); } - ); - if(chanmap != chanmaps.cend()) - { - if(!mDevice->Flags.get<ChannelsRequest>()) - mDevice->FmtChans = chanmap->chans; - } - else - { - char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; - pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); - WARN("Failed to find format for channel map:\n %s\n", chanmap_str); - } - - if(info->active_port) - TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0); -} - -void PulsePlayback::sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); } - -void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - mDevice->DeviceName = info->description; -} - -void PulsePlayback::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); } - -void PulsePlayback::streamMovedCallback(pa_stream *stream) -{ - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); -} - - -ALCenum PulsePlayback::open(const ALCchar *name) -{ - const char *pulse_name{nullptr}; - const char *dev_name{nullptr}; - - if(name) - { - if(PlaybackDevices.empty()) - probePlaybackDevices(); - - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; - pulse_name = iter->device_name.c_str(); - dev_name = iter->name.c_str(); - } - - std::unique_lock<std::mutex> plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulsePlayback::contextStateCallbackC, this); - - pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) - flags |= PA_STREAM_DONT_MOVE; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - if(!pulse_name) - { - pulse_name = getenv("ALSOFT_PULSE_DEFAULT"); - if(pulse_name && !pulse_name[0]) pulse_name = nullptr; - } - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr, - BackendType::Playback); - - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - mFrameSize = pa_frame_size(pa_stream_get_sample_spec(mStream)); - - mDeviceName = pa_stream_get_device_name(mStream); - if(!dev_name) - { - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkNameCallbackC, this)}; - wait_for_operation(op, plock); - } - else - mDevice->DeviceName = dev_name; - - return ALC_NO_ERROR; -} - -ALCboolean PulsePlayback::reset() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - if(mStream) - { - pa_stream_set_state_callback(mStream, nullptr, nullptr); - pa_stream_set_moved_callback(mStream, nullptr, nullptr); - pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); - pa_stream_disconnect(mStream); - pa_stream_unref(mStream); - mStream = nullptr; - } - - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkInfoCallbackC, this)}; - wait_for_operation(op, plock); - - pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | - PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) - flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0)) - { - /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some - * reason. So if the user wants to adjust the overall device latency, - * we can't ask to get write signals as soon as minreq is reached. - */ - flags &= ~PA_STREAM_EARLY_REQUESTS; - flags |= PA_STREAM_ADJUST_LATENCY; - } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) || - !mDevice->Flags.get<FrequencyRequest>()) - flags |= PA_STREAM_FIX_RATE; - - pa_channel_map chanmap{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - } - SetChannelOrderFromMap(mDevice, chanmap); - - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; - } - mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); - if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample spec"}; - - mAttr.maxlength = -1; - mAttr.tlength = mDevice->BufferSize * pa_frame_size(&mSpec); - mAttr.prebuf = 0; - mAttr.minreq = mDevice->UpdateSize * pa_frame_size(&mSpec); - mAttr.fragsize = -1; - - mStream = pulse_connect_stream(mDeviceName.c_str(), plock, mContext, flags, &mAttr, &mSpec, - &chanmap, BackendType::Playback); - - pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - - mSpec = *(pa_stream_get_sample_spec(mStream)); - mFrameSize = pa_frame_size(&mSpec); - - if(mDevice->Frequency != mSpec.rate) - { - /* Server updated our playback rate, so modify the buffer attribs - * accordingly. - */ - const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency; - const ALuint perlen{static_cast<ALuint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, - 8192.0))}; - const ALuint buflen{static_cast<ALuint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, - std::numeric_limits<int>::max()/mFrameSize))}; - - mAttr.maxlength = -1; - mAttr.tlength = buflen * mFrameSize; - mAttr.prebuf = 0; - mAttr.minreq = perlen * mFrameSize; - - op = pa_stream_set_buffer_attr(mStream, &mAttr, stream_success_callback, nullptr); - wait_for_operation(op, plock); - - mDevice->Frequency = mSpec.rate; - } - - pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this); - bufferAttrCallback(mStream); - - mDevice->BufferSize = mAttr.tlength / mFrameSize; - mDevice->UpdateSize = mAttr.minreq / mFrameSize; - - /* HACK: prebuf should be 0 as that's what we set it to. However on some - * systems it comes back as non-0, so we have to make sure the device will - * write enough audio to start playback. The lack of manual start control - * may have unintended consequences, but it's better than not starting at - * all. - */ - if(mAttr.prebuf != 0) - { - ALuint len{mAttr.prebuf / mFrameSize}; - if(len <= mDevice->BufferSize) - ERR("Non-0 prebuf, %u samples (%u bytes), device has %u samples\n", - len, mAttr.prebuf, mDevice->BufferSize); - } - - return ALC_TRUE; -} - -ALCboolean PulsePlayback::start() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); - - return ALC_TRUE; -} - -void PulsePlayback::stop() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); -} - - -ClockLatency PulsePlayback::getClockLatency() -{ - ClockLatency ret; - pa_usec_t latency; - int neg, err; - - { std::lock_guard<std::mutex> _{pulse_lock}; - ret.ClockTime = GetDeviceClockTime(mDevice); - err = pa_stream_get_latency(mStream, &latency, &neg); - } - - if(UNLIKELY(err != 0)) - { - /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon - * after starting the stream and no timing info has been received from - * the server yet. Should we wait, possibly stalling the app, or give a - * dummy value? Either way, it shouldn't be 0. */ - if(err != -PA_ERR_NODATA) - ERR("Failed to get stream latency: 0x%x\n", err); - latency = 0; - neg = 0; - } - else if(UNLIKELY(neg)) - latency = 0; - ret.Latency = std::chrono::microseconds{latency}; - - return ret; -} - - -void PulsePlayback::lock() -{ pulse_lock.lock(); } - -void PulsePlayback::unlock() -{ pulse_lock.unlock(); } - - -struct PulseCapture final : public BackendBase { - PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~PulseCapture() override; - - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); - - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); - - static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata); - void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol); - - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - - std::string mDeviceName; - - ALCuint mLastReadable{0u}; - al::byte mSilentVal{}; - - al::span<const al::byte> mCapBuffer; - ssize_t mCapLen{0}; - - pa_buffer_attr mAttr{}; - pa_sample_spec mSpec{}; - - pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; - - DEF_NEWDEL(PulseCapture) -}; - -PulseCapture::~PulseCapture() -{ - if(!mContext) - return; - - pulse_close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} - -void PulseCapture::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast<PulseCapture*>(pdata)->contextStateCallback(context); } - -void PulseCapture::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Capture state failure"); - } - pulse_condvar.notify_all(); -} - -void PulseCapture::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); } - -void PulseCapture::streamStateCallback(pa_stream *stream) -{ - if(pa_stream_get_state(stream) == PA_STREAM_FAILED) - { - ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Capture stream failure"); - } - pulse_condvar.notify_all(); -} - -void PulseCapture::sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) -{ static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); } - -void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - mDevice->DeviceName = info->description; -} - -void PulseCapture::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); } - -void PulseCapture::streamMovedCallback(pa_stream *stream) -{ - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); -} - - -ALCenum PulseCapture::open(const ALCchar *name) -{ - const char *pulse_name{nullptr}; - if(name) - { - if(CaptureDevices.empty()) - probeCaptureDevices(); - - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; - pulse_name = iter->device_name.c_str(); - mDevice->DeviceName = iter->name; - } - - std::unique_lock<std::mutex> plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulseCapture::contextStateCallbackC, this); - - pa_channel_map chanmap{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtChannelsString(mDevice->FmtChans)}; - } - SetChannelOrderFromMap(mDevice, chanmap); - - switch(mDevice->FmtType) - { - case DevFmtUByte: - mSilentVal = al::byte(0x80); - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; - } - mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); - if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample format"}; - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - - mAttr.minreq = -1; - mAttr.prebuf = -1; - mAttr.maxlength = samples * pa_frame_size(&mSpec); - mAttr.tlength = -1; - mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * pa_frame_size(&mSpec); - - pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) - flags |= PA_STREAM_DONT_MOVE; - - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, - BackendType::Capture); - - pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); - pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); - - mDeviceName = pa_stream_get_device_name(mStream); - if(mDevice->DeviceName.empty()) - { - pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(), - &PulseCapture::sourceNameCallbackC, this)}; - wait_for_operation(op, plock); - } - - return ALC_NO_ERROR; -} - -ALCboolean PulseCapture::start() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); - return ALC_TRUE; -} - -void PulseCapture::stop() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); -} - -ALCenum PulseCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - al::span<al::byte> dstbuf{static_cast<al::byte*>(buffer), samples * pa_frame_size(&mSpec)}; - - /* Capture is done in fragment-sized chunks, so we loop until we get all - * that's available */ - mLastReadable -= dstbuf.size(); - std::lock_guard<std::mutex> _{pulse_lock}; - while(!dstbuf.empty()) - { - if(mCapBuffer.empty()) - { - if(UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire))) - break; - const pa_stream_state_t state{pa_stream_get_state(mStream)}; - if(UNLIKELY(!PA_STREAM_IS_GOOD(state))) - { - aluHandleDisconnect(mDevice, "Bad capture state: %u", state); - break; - } - const void *capbuf; - size_t caplen; - if(UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0)) - { - aluHandleDisconnect(mDevice, "Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); - break; - } - if(caplen == 0) break; - if(UNLIKELY(!capbuf)) - mCapLen = -static_cast<ssize_t>(caplen); - else - mCapLen = static_cast<ssize_t>(caplen); - mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen}; - } - - const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; - if(UNLIKELY(mCapLen < 0)) - std::fill_n(dstbuf.begin(), rem, mSilentVal); - else - std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); - dstbuf = dstbuf.subspan(rem); - mCapBuffer = mCapBuffer.subspan(rem); - - if(mCapBuffer.empty()) - { - pa_stream_drop(mStream); - mCapLen = 0; - } - } - if(!dstbuf.empty()) - std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); - - return ALC_NO_ERROR; -} - -ALCuint PulseCapture::availableSamples() -{ - size_t readable{mCapBuffer.size()}; - - if(mDevice->Connected.load(std::memory_order_acquire)) - { - std::lock_guard<std::mutex> _{pulse_lock}; - size_t got{pa_stream_readable_size(mStream)}; - if(static_cast<ssize_t>(got) < 0) - { - ERR("pa_stream_readable_size() failed: %s\n", pa_strerror(got)); - aluHandleDisconnect(mDevice, "Failed getting readable size: %s", pa_strerror(got)); - } - else - { - const auto caplen = static_cast<size_t>(std::abs(mCapLen)); - if(got > caplen) readable += got - caplen; - } - } - - readable = std::min<size_t>(readable, std::numeric_limits<ALCuint>::max()); - mLastReadable = std::max(mLastReadable, static_cast<ALCuint>(readable)); - return mLastReadable / pa_frame_size(&mSpec); -} - - -ClockLatency PulseCapture::getClockLatency() -{ - ClockLatency ret; - pa_usec_t latency; - int neg, err; - - { std::lock_guard<std::mutex> _{pulse_lock}; - ret.ClockTime = GetDeviceClockTime(mDevice); - err = pa_stream_get_latency(mStream, &latency, &neg); - } - - if(UNLIKELY(err != 0)) - { - ERR("Failed to get stream latency: 0x%x\n", err); - latency = 0; - neg = 0; - } - else if(UNLIKELY(neg)) - latency = 0; - ret.Latency = std::chrono::microseconds{latency}; - - return ret; -} - - -void PulseCapture::lock() -{ pulse_lock.lock(); } - -void PulseCapture::unlock() -{ pulse_lock.unlock(); } - -} // namespace - - -bool PulseBackendFactory::init() -{ -#ifdef HAVE_DYNLOAD - if(!pulse_handle) - { - bool ret{true}; - std::string missing_funcs; - -#ifdef _WIN32 -#define PALIB "libpulse-0.dll" -#elif defined(__APPLE__) && defined(__MACH__) -#define PALIB "libpulse.0.dylib" -#else -#define PALIB "libpulse.so.0" -#endif - pulse_handle = LoadLib(PALIB); - if(!pulse_handle) - { - WARN("Failed to load %s\n", PALIB); - return false; - } - -#define LOAD_FUNC(x) do { \ - p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x)); \ - if(!(p##x)) { \ - ret = false; \ - missing_funcs += "\n" #x; \ - } \ -} while(0) - PULSE_FUNCS(LOAD_FUNC) -#undef LOAD_FUNC - - if(!ret) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(pulse_handle); - pulse_handle = nullptr; - return false; - } - } -#endif /* HAVE_DYNLOAD */ - - pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1)) - pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_context *context{connect_context(plock)}; - pa_context_disconnect(context); - pa_context_unref(context); - return true; - } - catch(...) { - return false; - } -} - -bool PulseBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void PulseBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - switch(type) - { - case DevProbe::Playback: - probePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - probeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PulsePlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new PulseCapture{device}}; - return nullptr; -} - -BackendFactory &PulseBackendFactory::getFactory() -{ - static PulseBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/pulseaudio.h b/Alc/backends/pulseaudio.h deleted file mode 100644 index 40f3e305..00000000 --- a/Alc/backends/pulseaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_PULSEAUDIO_H -#define BACKENDS_PULSEAUDIO_H - -#include "backends/base.h" - -class PulseBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_PULSEAUDIO_H */ diff --git a/Alc/backends/qsa.cpp b/Alc/backends/qsa.cpp deleted file mode 100644 index 64ed53aa..00000000 --- a/Alc/backends/qsa.cpp +++ /dev/null @@ -1,953 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011-2013 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/qsa.h" - -#include <stdlib.h> -#include <stdio.h> -#include <sched.h> -#include <errno.h> -#include <memory.h> -#include <poll.h> - -#include <thread> -#include <memory> -#include <algorithm> - -#include "alcmain.h" -#include "alu.h" -#include "threads.h" - -#include <sys/asoundlib.h> -#include <sys/neutrino.h> - - -namespace { - -struct qsa_data { - snd_pcm_t* pcmHandle{nullptr}; - int audio_fd{-1}; - - snd_pcm_channel_setup_t csetup{}; - snd_pcm_channel_params_t cparams{}; - - ALvoid* buffer{nullptr}; - ALsizei size{0}; - - std::atomic<ALenum> mKillNow{AL_TRUE}; - std::thread mThread; -}; - -struct DevMap { - ALCchar* name; - int card; - int dev; -}; - -al::vector<DevMap> DeviceNameMap; -al::vector<DevMap> CaptureNameMap; - -constexpr ALCchar qsaDevice[] = "QSA Default"; - -constexpr struct { - int32_t format; -} formatlist[] = { - {SND_PCM_SFMT_FLOAT_LE}, - {SND_PCM_SFMT_S32_LE}, - {SND_PCM_SFMT_U32_LE}, - {SND_PCM_SFMT_S16_LE}, - {SND_PCM_SFMT_U16_LE}, - {SND_PCM_SFMT_S8}, - {SND_PCM_SFMT_U8}, - {0}, -}; - -constexpr struct { - int32_t rate; -} ratelist[] = { - {192000}, - {176400}, - {96000}, - {88200}, - {48000}, - {44100}, - {32000}, - {24000}, - {22050}, - {16000}, - {12000}, - {11025}, - {8000}, - {0}, -}; - -constexpr struct { - int32_t channels; -} channellist[] = { - {8}, - {7}, - {6}, - {4}, - {2}, - {1}, - {0}, -}; - -void deviceList(int type, al::vector<DevMap> *devmap) -{ - snd_ctl_t* handle; - snd_pcm_info_t pcminfo; - int max_cards, card, err, dev; - DevMap entry; - char name[1024]; - snd_ctl_hw_info info; - - max_cards = snd_cards(); - if(max_cards < 0) - return; - - std::for_each(devmap->begin(), devmap->end(), - [](const DevMap &entry) -> void - { free(entry.name); } - ); - devmap->clear(); - - entry.name = strdup(qsaDevice); - entry.card = 0; - entry.dev = 0; - devmap->push_back(entry); - - for(card = 0;card < max_cards;card++) - { - if((err=snd_ctl_open(&handle, card)) < 0) - continue; - - if((err=snd_ctl_hw_info(handle, &info)) < 0) - { - snd_ctl_close(handle); - continue; - } - - for(dev = 0;dev < (int)info.pcmdevs;dev++) - { - if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0) - continue; - - if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) || - (type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE))) - { - snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev); - entry.name = strdup(name); - entry.card = card; - entry.dev = dev; - - devmap->push_back(entry); - TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev); - } - } - snd_ctl_close(handle); - } -} - - -/* Wrappers to use an old-style backend with the new interface. */ -struct PlaybackWrapper final : public BackendBase { - PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~PlaybackWrapper() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::unique_ptr<qsa_data> mExtraData; - - DEF_NEWDEL(PlaybackWrapper) -}; - - -FORCE_ALIGN static int qsa_proc_playback(void *ptr) -{ - PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr); - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - sched_param param; - char* write_ptr; - ALint len; - int sret; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - /* Increase default 10 priority to 11 to avoid jerky sound */ - SchedGet(0, 0, ¶m); - param.sched_priority=param.sched_curpriority+1; - SchedSet(0, 0, SCHED_NOCHANGE, ¶m); - - const ALint frame_size = device->frameSizeFromFmt(); - - self->lock(); - while(!data->mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - self->unlock(); - sret = poll(&pollitem, 1, 2000); - self->lock(); - if(sret == -1) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll error: %s\n", strerror(errno)); - aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - if(sret == 0) - { - ERR("poll timeout\n"); - continue; - } - - len = data->size; - write_ptr = static_cast<char*>(data->buffer); - aluMixData(device, write_ptr, len/frame_size); - while(len>0 && !data->mKillNow.load(std::memory_order_acquire)) - { - int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len); - if(wrote <= 0) - { - if(errno==EAGAIN || errno==EWOULDBLOCK) - continue; - - memset(&status, 0, sizeof(status)); - status.channel = SND_PCM_CHANNEL_PLAYBACK; - - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've underrun the buffer */ - if(status.status == SND_PCM_STATUS_UNDERRUN || - status.status == SND_PCM_STATUS_READY) - { - if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0) - { - aluHandleDisconnect(device, "Playback recovery failed"); - break; - } - } - } - else - { - write_ptr += wrote; - len -= wrote; - } - } - } - self->unlock(); - - return 0; -} - -/************/ -/* Playback */ -/************/ - -static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int status; - - std::unique_ptr<qsa_data> data{new qsa_data{}}; - data->mKillNow.store(AL_TRUE, std::memory_order_relaxed); - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK); - else - { - if(DeviceNameMap.empty()) - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - - auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == DeviceNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->buffer!=NULL) - { - free(data->buffer); - data->buffer=NULL; - } - - snd_pcm_close(data->pcmHandle); - - self->mExtraData = nullptr; -} - -static ALCboolean qsa_reset_playback(PlaybackWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - int32_t format=-1; - - switch(device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on writes */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS); - - // configure a sound channel - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK; - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.start_mode=SND_PCM_START_FULL; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize; - data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - int original_rate=data->cparams.format.rate; - int original_voices=data->cparams.format.voices; - int original_format=data->cparams.format.format; - int it; - int jt; - - for (it=0; it<1; it++) - { - /* Check for second pass */ - if (it==1) - { - original_rate=ratelist[0].rate; - original_voices=channellist[0].channels; - original_format=formatlist[0].format; - } - - do { - /* At first downgrade sample format */ - jt=0; - do { - if (formatlist[jt].format==data->cparams.format.format) - { - data->cparams.format.format=formatlist[jt+1].format; - break; - } - if (formatlist[jt].format==0) - { - data->cparams.format.format=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.format==0) - { - data->cparams.format.format=original_format; - - /* At secod downgrade sample rate */ - jt=0; - do { - if (ratelist[jt].rate==data->cparams.format.rate) - { - data->cparams.format.rate=ratelist[jt+1].rate; - break; - } - if (ratelist[jt].rate==0) - { - data->cparams.format.rate=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.rate==0) - { - data->cparams.format.rate=original_rate; - data->cparams.format.format=original_format; - - /* At third downgrade channels number */ - jt=0; - do { - if(channellist[jt].channels==data->cparams.format.voices) - { - data->cparams.format.voices=channellist[jt+1].channels; - break; - } - if (channellist[jt].channels==0) - { - data->cparams.format.voices=0; - break; - } - jt++; - } while(1); - } - - if (data->cparams.format.voices==0) - { - break; - } - } - - data->cparams.buf.block.frag_size=device->UpdateSize* - data->cparams.format.voices* - snd_pcm_format_width(data->cparams.format.format)/8; - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - continue; - } - else - { - break; - } - } while(1); - - if (data->cparams.format.voices!=0) - { - break; - } - } - - if (data->cparams.format.voices==0) - { - return ALC_FALSE; - } - } - - if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0) - { - return ALC_FALSE; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK; - if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0) - { - return ALC_FALSE; - } - - /* now fill back to the our AL device */ - device->Frequency=data->cparams.format.rate; - - switch (data->cparams.format.voices) - { - case 1: - device->FmtChans=DevFmtMono; - break; - case 2: - device->FmtChans=DevFmtStereo; - break; - case 4: - device->FmtChans=DevFmtQuad; - break; - case 6: - device->FmtChans=DevFmtX51; - break; - case 7: - device->FmtChans=DevFmtX61; - break; - case 8: - device->FmtChans=DevFmtX71; - break; - default: - device->FmtChans=DevFmtMono; - break; - } - - switch (data->cparams.format.format) - { - case SND_PCM_SFMT_S8: - device->FmtType=DevFmtByte; - break; - case SND_PCM_SFMT_U8: - device->FmtType=DevFmtUByte; - break; - case SND_PCM_SFMT_S16_LE: - device->FmtType=DevFmtShort; - break; - case SND_PCM_SFMT_U16_LE: - device->FmtType=DevFmtUShort; - break; - case SND_PCM_SFMT_S32_LE: - device->FmtType=DevFmtInt; - break; - case SND_PCM_SFMT_U32_LE: - device->FmtType=DevFmtUInt; - break; - case SND_PCM_SFMT_FLOAT_LE: - device->FmtType=DevFmtFloat; - break; - default: - device->FmtType=DevFmtShort; - break; - } - - SetDefaultChannelOrder(device); - - device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt(); - device->NumUpdates=data->csetup.buf.block.frags; - - data->size=data->csetup.buf.block.frag_size; - data->buffer=malloc(data->size); - if (!data->buffer) - { - return ALC_FALSE; - } - - return ALC_TRUE; -} - -static ALCboolean qsa_start_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - try { - data->mKillNow.store(AL_FALSE, std::memory_order_release); - data->mThread = std::thread(qsa_proc_playback, self); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -static void qsa_stop_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable()) - return; - data->mThread.join(); -} - - -PlaybackWrapper::~PlaybackWrapper() -{ - if(mExtraData) - qsa_close_playback(this); -} - -ALCenum PlaybackWrapper::open(const ALCchar *name) -{ return qsa_open_playback(this, name); } - -ALCboolean PlaybackWrapper::reset() -{ return qsa_reset_playback(this); } - -ALCboolean PlaybackWrapper::start() -{ return qsa_start_playback(this); } - -void PlaybackWrapper::stop() -{ qsa_stop_playback(this); } - - -/***********/ -/* Capture */ -/***********/ - -struct CaptureWrapper final : public BackendBase { - CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~CaptureWrapper() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::unique_ptr<qsa_data> mExtraData; - - DEF_NEWDEL(CaptureWrapper) -}; - -static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int format=-1; - int status; - - std::unique_ptr<qsa_data> data{new qsa_data{}}; - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE); - else - { - if(CaptureNameMap.empty()) - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - - auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == CaptureNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - - switch (device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on reads */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - - /* configure a sound channel */ - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.channel=SND_PCM_CHANNEL_CAPTURE; - data->cparams.start_mode=SND_PCM_START_GO; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_VALUE; - } - - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->pcmHandle!=nullptr) - snd_pcm_close(data->pcmHandle); - data->pcmHandle = nullptr; - - self->mExtraData = nullptr; -} - -static void qsa_start_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - int rstatus; - - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - return; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_CAPTURE; - if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0) - { - ERR("capture setup failed: %s\n", snd_strerror(rstatus)); - return; - } - - snd_pcm_capture_go(data->pcmHandle); -} - -static void qsa_stop_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - snd_pcm_capture_flush(data->pcmHandle); -} - -static ALCuint qsa_available_samples(CaptureWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - ALint frame_size = device->frameSizeFromFmt(); - ALint free_size; - int rstatus; - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus)); - return 0; - } - - snd_pcm_capture_go(data->pcmHandle); - return 0; - } - - free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags; - free_size-=status.free; - - return free_size/frame_size; -} - -static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - char* read_ptr; - snd_pcm_channel_status_t status; - int selectret; - int bytes_read; - ALint frame_size=device->frameSizeFromFmt(); - ALint len=samples*frame_size; - int rstatus; - - read_ptr = static_cast<char*>(buffer); - - while (len>0) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - bytes_read=0; - selectret = poll(&pollitem, 1, 2000); - switch (selectret) - { - case -1: - aluHandleDisconnect(device, "Failed to check capture samples"); - return ALC_INVALID_DEVICE; - case 0: - break; - default: - bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len); - break; - } - - if (bytes_read<=0) - { - if ((errno==EAGAIN) || (errno==EWOULDBLOCK)) - { - continue; - } - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've overrun the buffer */ - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", - snd_strerror(rstatus)); - return ALC_INVALID_DEVICE; - } - snd_pcm_capture_go(data->pcmHandle); - } - } - else - { - read_ptr+=bytes_read; - len-=bytes_read; - } - } - - return ALC_NO_ERROR; -} - - -CaptureWrapper::~CaptureWrapper() -{ - if(mExtraData) - qsa_close_capture(this); -} - -ALCenum CaptureWrapper::open(const ALCchar *name) -{ return qsa_open_capture(this, name); } - -ALCboolean CaptureWrapper::start() -{ qsa_start_capture(this); return ALC_TRUE; } - -void CaptureWrapper::stop() -{ qsa_stop_capture(this); } - -ALCenum CaptureWrapper::captureSamples(void *buffer, ALCuint samples) -{ return qsa_capture_samples(this, buffer, samples); } - -ALCuint CaptureWrapper::availableSamples() -{ return qsa_available_samples(this); } - -} // namespace - - -bool QSABackendFactory::init() -{ return true; } - -bool QSABackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void QSABackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - const char *n = entry.name; - if(n && n[0]) - outnames->append(n, strlen(n)+1); - }; - - switch (type) - { - case DevProbe::Playback: - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device); - break; - case DevProbe::Capture: - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device); - break; - } -} - -BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PlaybackWrapper{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CaptureWrapper{device}}; - return nullptr; -} - -BackendFactory &QSABackendFactory::getFactory() -{ - static QSABackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/qsa.h b/Alc/backends/qsa.h deleted file mode 100644 index da548bba..00000000 --- a/Alc/backends/qsa.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_QSA_H -#define BACKENDS_QSA_H - -#include "backends/base.h" - -struct QSABackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_QSA_H */ diff --git a/Alc/backends/sdl2.cpp b/Alc/backends/sdl2.cpp deleted file mode 100644 index 97547959..00000000 --- a/Alc/backends/sdl2.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2018 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/sdl2.h" - -#include <stdlib.h> -#include <SDL2/SDL.h> - -#include <string> - -#include "alcmain.h" -#include "alu.h" -#include "threads.h" -#include "compat.h" - - -namespace { - -#ifdef _WIN32 -#define DEVNAME_PREFIX "OpenAL Soft on " -#else -#define DEVNAME_PREFIX "" -#endif - -constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; - -struct Sdl2Backend final : public BackendBase { - Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { } - ~Sdl2Backend() override; - - static void audioCallbackC(void *ptr, Uint8 *stream, int len); - void audioCallback(Uint8 *stream, int len); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - void lock() override; - void unlock() override; - - SDL_AudioDeviceID mDeviceID{0u}; - ALsizei mFrameSize{0}; - - ALuint mFrequency{0u}; - DevFmtChannels mFmtChans{}; - DevFmtType mFmtType{}; - ALuint mUpdateSize{0u}; - - DEF_NEWDEL(Sdl2Backend) -}; - -Sdl2Backend::~Sdl2Backend() -{ - if(mDeviceID) - SDL_CloseAudioDevice(mDeviceID); - mDeviceID = 0; -} - -void Sdl2Backend::audioCallbackC(void *ptr, Uint8 *stream, int len) -{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); } - -void Sdl2Backend::audioCallback(Uint8 *stream, int len) -{ - assert((len % mFrameSize) == 0); - aluMixData(mDevice, stream, len / mFrameSize); -} - -ALCenum Sdl2Backend::open(const ALCchar *name) -{ - SDL_AudioSpec want{}, have{}; - want.freq = mDevice->Frequency; - switch(mDevice->FmtType) - { - case DevFmtUByte: want.format = AUDIO_U8; break; - case DevFmtByte: want.format = AUDIO_S8; break; - case DevFmtUShort: want.format = AUDIO_U16SYS; break; - case DevFmtShort: want.format = AUDIO_S16SYS; break; - case DevFmtUInt: /* fall-through */ - case DevFmtInt: want.format = AUDIO_S32SYS; break; - case DevFmtFloat: want.format = AUDIO_F32; break; - } - want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = mDevice->UpdateSize; - want.callback = &Sdl2Backend::audioCallbackC; - want.userdata = this; - - /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't - * necessarily the first in the list. - */ - if(!name || strcmp(name, defaultDeviceName) == 0) - mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - else - { - const size_t prefix_len = strlen(DEVNAME_PREFIX); - if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - else - mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - if(mDeviceID == 0) - return ALC_INVALID_VALUE; - - mDevice->Frequency = have.freq; - if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; - else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; - else - { - ERR("Got unhandled SDL channel count: %d\n", (int)have.channels); - return ALC_INVALID_VALUE; - } - switch(have.format) - { - case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; - case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; - case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; - case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; - case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; - case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; - default: - ERR("Got unsupported SDL format: 0x%04x\n", have.format); - return ALC_INVALID_VALUE; - } - mDevice->UpdateSize = have.samples; - mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */ - - mFrameSize = mDevice->frameSizeFromFmt(); - mFrequency = mDevice->Frequency; - mFmtChans = mDevice->FmtChans; - mFmtType = mDevice->FmtType; - mUpdateSize = mDevice->UpdateSize; - - mDevice->DeviceName = name ? name : defaultDeviceName; - return ALC_NO_ERROR; -} - -ALCboolean Sdl2Backend::reset() -{ - mDevice->Frequency = mFrequency; - mDevice->FmtChans = mFmtChans; - mDevice->FmtType = mFmtType; - mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean Sdl2Backend::start() -{ - SDL_PauseAudioDevice(mDeviceID, 0); - return ALC_TRUE; -} - -void Sdl2Backend::stop() -{ SDL_PauseAudioDevice(mDeviceID, 1); } - -void Sdl2Backend::lock() -{ SDL_LockAudioDevice(mDeviceID); } - -void Sdl2Backend::unlock() -{ SDL_UnlockAudioDevice(mDeviceID); } - -} // namespace - -BackendFactory &SDL2BackendFactory::getFactory() -{ - static SDL2BackendFactory factory{}; - return factory; -} - -bool SDL2BackendFactory::init() -{ return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); } - -bool SDL2BackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void SDL2BackendFactory::probe(DevProbe type, std::string *outnames) -{ - if(type != DevProbe::Playback) - return; - - int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; - - /* Includes null char. */ - outnames->append(defaultDeviceName, sizeof(defaultDeviceName)); - for(int i{0};i < num_devices;++i) - { - std::string name{DEVNAME_PREFIX}; - name += SDL_GetAudioDeviceName(i, SDL_FALSE); - if(!name.empty()) - outnames->append(name.c_str(), name.length()+1); - } -} - -BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new Sdl2Backend{device}}; - return nullptr; -} diff --git a/Alc/backends/sdl2.h b/Alc/backends/sdl2.h deleted file mode 100644 index 041d47ee..00000000 --- a/Alc/backends/sdl2.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SDL2_H -#define BACKENDS_SDL2_H - -#include "backends/base.h" - -struct SDL2BackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_SDL2_H */ diff --git a/Alc/backends/sndio.cpp b/Alc/backends/sndio.cpp deleted file mode 100644 index 587f67bb..00000000 --- a/Alc/backends/sndio.cpp +++ /dev/null @@ -1,495 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/sndio.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "threads.h" -#include "vector.h" -#include "ringbuffer.h" - -#include <sndio.h> - - -namespace { - -static const ALCchar sndio_device[] = "SndIO Default"; - - -struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~SndioPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - sio_hdl *mSndHandle{nullptr}; - - al::vector<ALubyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SndioPlayback) -}; - -SndioPlayback::~SndioPlayback() -{ - if(mSndHandle) - sio_close(mSndHandle); - mSndHandle = nullptr; -} - -int SndioPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto WritePtr = static_cast<ALubyte*>(mBuffer.data()); - size_t len{mBuffer.size()}; - - lock(); - aluMixData(mDevice, WritePtr, len/frameSize); - unlock(); - while(len > 0 && !mKillNow.load(std::memory_order_acquire)) - { - size_t wrote{sio_write(mSndHandle, WritePtr, len)}; - if(wrote == 0) - { - ERR("sio_write failed\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); - break; - } - - len -= wrote; - WritePtr += wrote; - } - } - - return 0; -} - - -ALCenum SndioPlayback::open(const ALCchar *name) -{ - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; - - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SndioPlayback::reset() -{ - sio_par par; - sio_initpar(&par); - - par.rate = mDevice->Frequency; - par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1); - - switch(mDevice->FmtType) - { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; - } - par.le = SIO_LE_NATIVE; - - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_FALSE; - } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_FALSE; - } - - mDevice->Frequency = par.rate; - mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo); - - if(par.bits == 8 && par.sig == 1) - mDevice->FmtType = DevFmtByte; - else if(par.bits == 8 && par.sig == 0) - mDevice->FmtType = DevFmtUByte; - else if(par.bits == 16 && par.sig == 1) - mDevice->FmtType = DevFmtShort; - else if(par.bits == 16 && par.sig == 0) - mDevice->FmtType = DevFmtUShort; - else if(par.bits == 32 && par.sig == 1) - mDevice->FmtType = DevFmtInt; - else if(par.bits == 32 && par.sig == 0) - mDevice->FmtType = DevFmtUInt; - else - { - ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); - return ALC_FALSE; - } - - SetDefaultChannelOrder(mDevice); - - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.bufsz + par.round; - - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); - - return ALC_TRUE; -} - -ALCboolean SndioPlayback::start() -{ - if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - sio_stop(mSndHandle); - return ALC_FALSE; -} - -void SndioPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); -} - - -struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~SndioCapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - sio_hdl *mSndHandle{nullptr}; - - RingBufferPtr mRing; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SndioCapture) -}; - -SndioCapture::~SndioCapture() -{ - if(mSndHandle) - sio_close(mSndHandle); - mSndHandle = nullptr; -} - -int SndioCapture::recordProc() -{ - SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); - - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - if(todo == 0) - { - static char junk[4096]; - sio_read(mSndHandle, junk, - minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); - continue; - } - - size_t total{0u}; - data.first.len *= frameSize; - data.second.len *= frameSize; - todo = minz(todo, mDevice->UpdateSize) * frameSize; - while(total < todo) - { - if(!data.first.len) - data.first = data.second; - - size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; - if(!got) - { - aluHandleDisconnect(mDevice, "Failed to read capture samples"); - break; - } - - data.first.buf += got; - data.first.len -= got; - total += got; - } - mRing->writeAdvance(total / frameSize); - } - - return 0; -} - - -ALCenum SndioCapture::open(const ALCchar *name) -{ - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; - - mSndHandle = sio_open(nullptr, SIO_REC, 0); - if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } - - sio_par par; - sio_initpar(&par); - - switch(mDevice->FmtType) - { - case DevFmtByte: - par.bps = 1; - par.sig = 1; - break; - case DevFmtUByte: - par.bps = 1; - par.sig = 0; - break; - case DevFmtShort: - par.bps = 2; - par.sig = 1; - break; - case DevFmtUShort: - par.bps = 2; - par.sig = 0; - break; - case DevFmtInt: - par.bps = 4; - par.sig = 1; - break; - case DevFmtUInt: - par.bps = 4; - par.sig = 0; - break; - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - par.bits = par.bps * 8; - par.le = SIO_LE_NATIVE; - par.msb = SIO_LE_NATIVE ? 0 : 1; - par.rchan = mDevice->channelsFromFmt(); - par.rate = mDevice->Frequency; - - par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz, mDevice->Frequency/40); - - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.appbufsz; - - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_INVALID_VALUE; - } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_INVALID_VALUE; - } - - if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) || - (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) || - (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) || - (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) || - (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) || - (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) || - mDevice->channelsFromFmt() != (ALsizei)par.rchan || - mDevice->Frequency != par.rate) - { - ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n", - DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate); - return ALC_INVALID_VALUE; - } - - mRing = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false); - if(!mRing) - { - ERR("Failed to allocate %u-byte ringbuffer\n", mDevice->BufferSize*par.bps*par.rchan); - return ALC_OUT_OF_MEMORY; - } - - SetDefaultChannelOrder(mDevice); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SndioCapture::start() -{ - if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { - } - sio_stop(mSndHandle); - return ALC_FALSE; -} - -void SndioCapture::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); -} - -ALCenum SndioCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint SndioCapture::availableSamples() -{ return mRing->readSpace(); } - -} // namespace - -BackendFactory &SndIOBackendFactory::getFactory() -{ - static SndIOBackendFactory factory{}; - return factory; -} - -bool SndIOBackendFactory::init() -{ return true; } - -bool SndIOBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void SndIOBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(sndio_device, sizeof(sndio_device)); - break; - } -} - -BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new SndioPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new SndioCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/sndio.h b/Alc/backends/sndio.h deleted file mode 100644 index 1ed63d5e..00000000 --- a/Alc/backends/sndio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SNDIO_H -#define BACKENDS_SNDIO_H - -#include "backends/base.h" - -struct SndIOBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_SNDIO_H */ diff --git a/Alc/backends/solaris.cpp b/Alc/backends/solaris.cpp deleted file mode 100644 index 584f6e66..00000000 --- a/Alc/backends/solaris.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/solaris.h" - -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/time.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> -#include <unistd.h> -#include <errno.h> -#include <poll.h> -#include <math.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "threads.h" -#include "vector.h" -#include "compat.h" - -#include <sys/audioio.h> - - -namespace { - -constexpr ALCchar solaris_device[] = "Solaris Default"; - -std::string solaris_driver{"/dev/audio"}; - - -struct SolarisBackend final : public BackendBase { - SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { } - ~SolarisBackend() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - int mFd{-1}; - - al::vector<ALubyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SolarisBackend) -}; - -SolarisBackend::~SolarisBackend() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - -int SolarisBackend::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLOUT; - - unlock(); - int pret{poll(&pollitem, 1, 1000)}; - lock(); - if(pret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s", - strerror(errno)); - break; - } - else if(pret == 0) - { - WARN("poll timeout\n"); - continue; - } - - ALubyte *write_ptr{mBuffer.data()}; - size_t to_write{mBuffer.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) - { - ssize_t wrote{write(mFd, write_ptr, to_write)}; - if(wrote < 0) - { - if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) - continue; - ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to write playback samples: %s", - strerror(errno)); - break; - } - - to_write -= wrote; - write_ptr += wrote; - } - } - unlock(); - - return 0; -} - - -ALCenum SolarisBackend::open(const ALCchar *name) -{ - if(!name) - name = solaris_device; - else if(strcmp(name, solaris_device) != 0) - return ALC_INVALID_VALUE; - - mFd = ::open(solaris_driver.c_str(), O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", solaris_driver.c_str(), strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SolarisBackend::reset() -{ - audio_info_t info; - AUDIO_INITINFO(&info); - - info.play.sample_rate = mDevice->Frequency; - - if(mDevice->FmtChans != DevFmtMono) - mDevice->FmtChans = DevFmtStereo; - ALsizei numChannels{mDevice->channelsFromFmt()}; - info.play.channels = numChannels; - - switch(mDevice->FmtType) - { - case DevFmtByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; - case DevFmtUByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR8; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - info.play.precision = 16; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; - } - - ALsizei frameSize{numChannels * mDevice->bytesFromFmt()}; - info.play.buffer_size = mDevice->BufferSize * frameSize; - - if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) - { - ERR("ioctl failed: %s\n", strerror(errno)); - return ALC_FALSE; - } - - if(mDevice->channelsFromFmt() != (ALsizei)info.play.channels) - { - ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - info.play.channels); - return ALC_FALSE; - } - - if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) || - (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) || - (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) || - (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt))) - { - ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType), - info.play.precision, info.play.encoding); - return ALC_FALSE; - } - - mDevice->Frequency = info.play.sample_rate; - mDevice->BufferSize = info.play.buffer_size / frameSize; - mDevice->UpdateSize = mDevice->BufferSize / 2; - - SetDefaultChannelOrder(mDevice); - - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); - - return ALC_TRUE; -} - -ALCboolean SolarisBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void SolarisBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, AUDIO_DRAIN) < 0) - ERR("Error draining device: %s\n", strerror(errno)); -} - -} // namespace - -BackendFactory &SolarisBackendFactory::getFactory() -{ - static SolarisBackendFactory factory{}; - return factory; -} - -bool SolarisBackendFactory::init() -{ - if(auto devopt = ConfigValueStr(nullptr, "solaris", "device")) - solaris_driver = std::move(*devopt); - return true; -} - -bool SolarisBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void SolarisBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - { -#ifdef HAVE_STAT - struct stat buf; - if(stat(solaris_driver.c_str(), &buf) == 0) -#endif - outnames->append(solaris_device, sizeof(solaris_device)); - } - break; - - case DevProbe::Capture: - break; - } -} - -BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new SolarisBackend{device}}; - return nullptr; -} diff --git a/Alc/backends/solaris.h b/Alc/backends/solaris.h deleted file mode 100644 index 98b10593..00000000 --- a/Alc/backends/solaris.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SOLARIS_H -#define BACKENDS_SOLARIS_H - -#include "backends/base.h" - -struct SolarisBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_SOLARIS_H */ diff --git a/Alc/backends/wasapi.cpp b/Alc/backends/wasapi.cpp deleted file mode 100644 index bd009463..00000000 --- a/Alc/backends/wasapi.cpp +++ /dev/null @@ -1,1763 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/wasapi.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <wtypes.h> -#include <mmdeviceapi.h> -#include <audioclient.h> -#include <cguid.h> -#include <devpropdef.h> -#include <mmreg.h> -#include <propsys.h> -#include <propkey.h> -#include <devpkey.h> -#ifndef _WAVEFORMATEXTENSIBLE_ -#include <ks.h> -#include <ksmedia.h> -#endif - -#include <deque> -#include <mutex> -#include <atomic> -#include <thread> -#include <vector> -#include <string> -#include <future> -#include <algorithm> -#include <functional> -#include <condition_variable> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "compat.h" -#include "converter.h" -#include "threads.h" - - -/* Some headers seem to define these as macros for __uuidof, which is annoying - * since some headers don't declare them at all. Hopefully the ifdef is enough - * to tell if they need to be declared. - */ -#ifndef KSDATAFORMAT_SUBTYPE_PCM -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif -#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif - -DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); - - -namespace { - -#define MONO SPEAKER_FRONT_CENTER -#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) -#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) -#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) -#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) -#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) -#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) -#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) - -#define REFTIME_PER_SEC ((REFERENCE_TIME)10000000) - -#define DEVNAME_HEAD "OpenAL Soft on " - - -/* Scales the given value using 64-bit integer math, ceiling the result. */ -inline int64_t ScaleCeil(int64_t val, int64_t new_scale, int64_t old_scale) -{ - return (val*new_scale + old_scale-1) / old_scale; -} - - -struct PropVariant { - PROPVARIANT mProp; - -public: - PropVariant() { PropVariantInit(&mProp); } - ~PropVariant() { clear(); } - - void clear() { PropVariantClear(&mProp); } - - PROPVARIANT* get() noexcept { return &mProp; } - - PROPVARIANT& operator*() noexcept { return mProp; } - const PROPVARIANT& operator*() const noexcept { return mProp; } - - PROPVARIANT* operator->() noexcept { return &mProp; } - const PROPVARIANT* operator->() const noexcept { return &mProp; } -}; - -struct DevMap { - std::string name; - std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent. - std::wstring devid; - - template<typename T0, typename T1, typename T2> - DevMap(T0&& name_, T1&& guid_, T2&& devid_) - : name{std::forward<T0>(name_)} - , endpoint_guid{std::forward<T1>(guid_)} - , devid{std::forward<T2>(devid_)} - { } -}; - -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -using NameGUIDPair = std::pair<std::string,std::string>; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) -{ - std::string name{DEVNAME_HEAD}; - std::string guid; - - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); - if(FAILED(hr)) - { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return { name+"Unknown Device Name", "Unknown Device GUID" }; - } - - PropVariant pvprop; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get()); - if(FAILED(hr)) - { - WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += "Unknown Device Name"; - } - else if(pvprop->vt == VT_LPWSTR) - name += wstr_to_utf8(pvprop->pwszVal); - else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += "Unknown Device Name"; - } - - pvprop.clear(); - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get()); - if(FAILED(hr)) - { - WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = "Unknown Device GUID"; - } - else if(pvprop->vt == VT_LPWSTR) - guid = wstr_to_utf8(pvprop->pwszVal); - else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = "Unknown Device GUID"; - } - - ps->Release(); - - return {name, guid}; -} - -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) -{ - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); - if(FAILED(hr)) - { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; - } - - PropVariant pvform; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get()); - if(FAILED(hr)) - WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); - else if(pvform->vt == VT_UI4) - *formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); - else if(pvform->vt == VT_EMPTY) - *formfactor = UnknownFormFactor; - else - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); - - ps->Release(); -} - - -void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list) -{ - std::string basename, guidstr; - std::tie(basename, guidstr) = get_device_name_and_guid(device); - - int count{1}; - std::string newname{basename}; - while(checkName(list, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(guidstr), devid); - const DevMap &newentry = list.back(); - - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); -} - -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; - - HRESULT hr = device->GetId(&devid); - if(FAILED(hr)) - { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; - } - - return devid; -} - -HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list) -{ - IMMDeviceCollection *coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)}; - if(FAILED(hr)) - { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return hr; - } - - IMMDevice *defdev{nullptr}; - WCHAR *defdevid{nullptr}; - UINT count{0}; - hr = coll->GetCount(&count); - if(SUCCEEDED(hr) && count > 0) - { - list.clear(); - list.reserve(count); - - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, &defdev); - } - if(SUCCEEDED(hr) && defdev != nullptr) - { - defdevid = get_device_id(defdev); - if(defdevid) - add_device(defdev, defdevid, list); - } - - for(UINT i{0};i < count;++i) - { - IMMDevice *device; - hr = coll->Item(i, &device); - if(FAILED(hr)) continue; - - WCHAR *devid{get_device_id(device)}; - if(devid) - { - if(!defdevid || wcscmp(devid, defdevid) != 0) - add_device(device, devid, list); - CoTaskMemFree(devid); - } - device->Release(); - } - - if(defdev) defdev->Release(); - if(defdevid) CoTaskMemFree(defdevid); - coll->Release(); - - return S_OK; -} - - -bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) -{ - *out = WAVEFORMATEXTENSIBLE{}; - if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - { - *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format); - out->Format.cbSize = sizeof(*out) - sizeof(out->Format); - } - else if(in->wFormatTag == WAVE_FORMAT_PCM) - { - out->Format = *in; - out->Format.cbSize = 0; - out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) - { - out->Format = *in; - out->Format.cbSize = 0; - out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - } - else - { - ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); - return false; - } - return true; -} - -void TraceFormat(const char *msg, const WAVEFORMATEX *format) -{ - constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; - if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) - { - class GuidPrinter { - char mMsg[64]; - - public: - GuidPrinter(const GUID &guid) - { - std::snprintf(mMsg, al::size(mMsg), - "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", - DWORD{guid.Data1}, guid.Data2, guid.Data3, - guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], - guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - } - const char *c_str() const { return mMsg; } - }; - - const WAVEFORMATEXTENSIBLE *fmtex{ - CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n" - " Samples = %d\n" - " ChannelMask = 0x%lx\n" - " SubFormat = %s\n", - msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec, - fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, - fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, - GuidPrinter{fmtex->SubFormat}.c_str()); - } - else - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n", - msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec, - format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize); -} - - -enum class MsgType : unsigned int { - OpenDevice, - ResetDevice, - StartDevice, - StopDevice, - CloseDevice, - EnumeratePlayback, - EnumerateCapture, - QuitThread, - - Count -}; - -constexpr char MessageStr[static_cast<unsigned int>(MsgType::Count)][20]{ - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Playback", - "Enumerate Capture", - "Quit" -}; - - -/* Proxy interface used by the message handler. */ -struct WasapiProxy { - virtual HRESULT openProxy() = 0; - virtual void closeProxy() = 0; - - virtual HRESULT resetProxy() = 0; - virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; - - struct Msg { - MsgType mType; - WasapiProxy *mProxy; - std::promise<HRESULT> mPromise; - }; - static std::deque<Msg> mMsgQueue; - static std::mutex mMsgQueueLock; - static std::condition_variable mMsgQueueCond; - - std::future<HRESULT> pushMessage(MsgType type) - { - std::promise<HRESULT> promise; - std::future<HRESULT> future{promise.get_future()}; - { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static std::future<HRESULT> pushMessageStatic(MsgType type) - { - std::promise<HRESULT> promise; - std::future<HRESULT> future{promise.get_future()}; - { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static bool popMessage(Msg &msg) - { - std::unique_lock<std::mutex> lock{mMsgQueueLock}; - while(mMsgQueue.empty()) - mMsgQueueCond.wait(lock); - msg = std::move(mMsgQueue.front()); - mMsgQueue.pop_front(); - return msg.mType != MsgType::QuitThread; - } - - static int messageHandler(std::promise<HRESULT> *promise); -}; -std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue; -std::mutex WasapiProxy::mMsgQueueLock; -std::condition_variable WasapiProxy::mMsgQueueCond; - -int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) -{ - TRACE("Starting message thread\n"); - - HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(FAILED(cohr)) - { - WARN("Failed to initialize COM: 0x%08lx\n", cohr); - promise->set_value(cohr); - return 0; - } - - void *ptr{}; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(FAILED(hr)) - { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); - promise->set_value(hr); - CoUninitialize(); - return 0; - } - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - Enumerator->Release(); - Enumerator = nullptr; - CoUninitialize(); - - TRACE("Message thread initialization complete\n"); - promise->set_value(S_OK); - promise = nullptr; - - TRACE("Starting message loop\n"); - ALuint deviceCount{0}; - Msg msg; - while(popMessage(msg)) - { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", - MessageStr[static_cast<unsigned int>(msg.mType)], static_cast<unsigned int>(msg.mType), - msg.mProxy); - - switch(msg.mType) - { - case MsgType::OpenDevice: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); - msg.mPromise.set_value(hr); - - if(FAILED(hr)) - { - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - } - continue; - - case MsgType::ResetDevice: - hr = msg.mProxy->resetProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StartDevice: - hr = msg.mProxy->startProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StopDevice: - msg.mProxy->stopProxy(); - msg.mPromise.set_value(S_OK); - continue; - - case MsgType::CloseDevice: - msg.mProxy->closeProxy(); - msg.mPromise.set_value(S_OK); - - if(--deviceCount == 0) - CoUninitialize(); - continue; - - case MsgType::EnumeratePlayback: - case MsgType::EnumerateCapture: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else - { - Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - - if(msg.mType == MsgType::EnumeratePlayback) - hr = probe_devices(Enumerator, eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - hr = probe_devices(Enumerator, eCapture, CaptureDevices); - msg.mPromise.set_value(hr); - - Enumerator->Release(); - Enumerator = nullptr; - } - - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - continue; - - default: - ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg.mType)); - msg.mPromise.set_value(E_FAIL); - continue; - } - } - TRACE("Message loop finished\n"); - - return 0; -} - - -struct WasapiPlayback final : public BackendBase, WasapiProxy { - WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~WasapiPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; - void closeProxy() override; - - ALCboolean reset() override; - HRESULT resetProxy() override; - ALCboolean start() override; - HRESULT startProxy() override; - void stop() override; - void stopProxy() override; - - ClockLatency getClockLatency() override; - - std::wstring mDevId; - - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioRenderClient *mRender{nullptr}; - HANDLE mNotifyEvent{nullptr}; - - std::atomic<UINT32> mPadding{0u}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WasapiPlayback) -}; - -WasapiPlayback::~WasapiPlayback() -{ - pushMessage(MsgType::CloseDevice).wait(); - - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int WasapiPlayback::mixerProc() -{ - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(FAILED(hr)) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); - return 1; - } - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const ALuint update_size{mDevice->UpdateSize}; - const UINT32 buffer_len{mDevice->BufferSize}; - while(!mKillNow.load(std::memory_order_relaxed)) - { - UINT32 written; - hr = mClient->GetCurrentPadding(&written); - if(FAILED(hr)) - { - ERR("Failed to get padding: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr); - break; - } - mPadding.store(written, std::memory_order_relaxed); - - ALuint len{buffer_len - written}; - if(len < update_size) - { - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - continue; - } - - BYTE *buffer; - hr = mRender->GetBuffer(len, &buffer); - if(SUCCEEDED(hr)) - { - lock(); - aluMixData(mDevice, buffer, len); - mPadding.store(written + len, std::memory_order_relaxed); - unlock(); - hr = mRender->ReleaseBuffer(len, 0); - } - if(FAILED(hr)) - { - ERR("Failed to buffer data: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to send playback samples: 0x%08lx", hr); - break; - } - } - mPadding.store(0u, std::memory_order_release); - - CoUninitialize(); - return 0; -} - - -ALCenum WasapiPlayback::open(const ALCchar *name) -{ - HRESULT hr{S_OK}; - - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) - { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) - { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - - hr = E_FAIL; - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == PlaybackDevices.cend()) - { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == PlaybackDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } - } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - return ALC_NO_ERROR; -} - -HRESULT WasapiPlayback::openProxy() -{ - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mMMDev); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); - } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) - { - mClient = static_cast<IAudioClient*>(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; - } - - if(FAILED(hr)) - { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; - } - - return hr; -} - -void WasapiPlayback::closeProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; -} - - -ALCboolean WasapiPlayback::reset() -{ - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiPlayback::resetProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - void *ptr; - HRESULT hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(FAILED(hr)) - { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); - return hr; - } - mClient = static_cast<IAudioClient*>(ptr); - - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); - if(FAILED(hr)) - { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; - } - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - - const REFERENCE_TIME per_time{mDevice->UpdateSize * REFTIME_PER_SEC / mDevice->Frequency}; - const REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; - - if(!mDevice->Flags.get<FrequencyRequest>()) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!mDevice->Flags.get<ChannelsRequest>()) - { - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) - mDevice->FmtChans = DevFmtX71; - else - ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); - } - - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; - } - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; - } - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - - TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); - if(FAILED(hr)) - { - ERR("Failed to check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); - } - if(FAILED(hr)) - { - ERR("Failed to find a supported format: 0x%08lx\n", hr); - return hr; - } - - if(wfx != nullptr) - { - TraceFormat("Got playback format", wfx); - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) - mDevice->FmtChans = DevFmtX71; - else - { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - } - - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - if(OutputType.Format.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - mDevice->FmtType = DevFmtInt; - else - { - mDevice->FmtType = DevFmtShort; - OutputType.Format.wBitsPerSample = 16; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - mDevice->FmtType = DevFmtFloat; - OutputType.Format.wBitsPerSample = 32; - } - else - { - ERR("Unhandled format sub-type\n"); - mDevice->FmtType = DevFmtShort; - if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - } - - EndpointFormFactor formfactor = UnknownFormFactor; - get_device_formfactor(mMMDev, &formfactor); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - (formfactor == Headphones || formfactor == Headset)); - - SetDefaultWFXChannelOrder(mDevice); - - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); - if(FAILED(hr)) - { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); - return hr; - } - - UINT32 buffer_len, min_len; - REFERENCE_TIME min_per; - hr = mClient->GetDevicePeriod(&min_per, nullptr); - if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); - if(FAILED(hr)) - { - ERR("Failed to get audio buffer info: 0x%08lx\n", hr); - return hr; - } - - /* Find the nearest multiple of the period size to the update size */ - if(min_per < per_time) - min_per *= maxi64((per_time + min_per/2) / min_per, 1); - min_len = (UINT32)ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC); - min_len = minu(min_len, buffer_len/2); - - mDevice->UpdateSize = min_len; - mDevice->BufferSize = buffer_len; - - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - - return hr; -} - - -ALCboolean WasapiPlayback::start() -{ - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiPlayback::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr = mClient->Start(); - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - void *ptr; - hr = mClient->GetService(IID_IAudioRenderClient, &ptr); - if(SUCCEEDED(hr)) - { - mRender = static_cast<IAudioRenderClient*>(ptr); - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; - } - catch(...) { - mRender->Release(); - mRender = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - - if(FAILED(hr)) - mClient->Stop(); - - return hr; -} - - -void WasapiPlayback::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiPlayback::stopProxy() -{ - if(!mRender || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mRender->Release(); - mRender = nullptr; - mClient->Stop(); -} - - -ClockLatency WasapiPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~WasapiCapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; - void closeProxy() override; - - HRESULT resetProxy() override; - ALCboolean start() override; - HRESULT startProxy() override; - void stop() override; - void stopProxy() override; - - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::wstring mDevId; - - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioCaptureClient *mCapture{nullptr}; - HANDLE mNotifyEvent{nullptr}; - - ChannelConverterPtr mChannelConv; - SampleConverterPtr mSampleConv; - RingBufferPtr mRing; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WasapiCapture) -}; - -WasapiCapture::~WasapiCapture() -{ - pushMessage(MsgType::CloseDevice).wait(); - - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int WasapiCapture::recordProc() -{ - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(FAILED(hr)) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); - return 1; - } - - althrd_setname(RECORD_THREAD_NAME); - - al::vector<float> samples; - while(!mKillNow.load(std::memory_order_relaxed)) - { - UINT32 avail; - hr = mCapture->GetNextPacketSize(&avail); - if(FAILED(hr)) - ERR("Failed to get next packet size: 0x%08lx\n", hr); - else if(avail > 0) - { - UINT32 numsamples; - DWORD flags; - BYTE *rdata; - - hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); - if(FAILED(hr)) - ERR("Failed to get capture buffer: 0x%08lx\n", hr); - else - { - if(mChannelConv) - { - samples.resize(numsamples*2); - mChannelConv->convert(rdata, samples.data(), numsamples); - rdata = reinterpret_cast<BYTE*>(samples.data()); - } - - auto data = mRing->getWriteVector(); - - size_t dstframes; - if(mSampleConv) - { - const ALvoid *srcdata{rdata}; - auto srcframes = static_cast<ALsizei>(numsamples); - - dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast<ALsizei>(minz(data.first.len, INT_MAX))); - if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) - { - /* If some source samples remain, all of the first dest - * block was filled, and there's space in the second - * dest block, do another run for the second block. - */ - dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast<ALsizei>(minz(data.second.len, INT_MAX))); - } - } - else - { - const auto framesize = static_cast<ALuint>(mDevice->frameSizeFromFmt()); - size_t len1 = minz(data.first.len, numsamples); - size_t len2 = minz(data.second.len, numsamples-len1); - - memcpy(data.first.buf, rdata, len1*framesize); - if(len2 > 0) - memcpy(data.second.buf, rdata+len1*framesize, len2*framesize); - dstframes = len1 + len2; - } - - mRing->writeAdvance(dstframes); - - hr = mCapture->ReleaseBuffer(numsamples); - if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr); - } - } - - if(FAILED(hr)) - { - aluHandleDisconnect(mDevice, "Failed to capture samples: 0x%08lx", hr); - break; - } - - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - } - - CoUninitialize(); - return 0; -} - - -ALCenum WasapiCapture::open(const ALCchar *name) -{ - HRESULT hr{S_OK}; - - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) - { - ERR("Failed to create notify event: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == CaptureDevices.cend()) - { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == CaptureDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } - } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - hr = pushMessage(MsgType::ResetDevice).get(); - if(FAILED(hr)) - { - if(hr == E_OUTOFMEMORY) - return ALC_OUT_OF_MEMORY; - return ALC_INVALID_VALUE; - } - - return ALC_NO_ERROR; -} - -HRESULT WasapiCapture::openProxy() -{ - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &mMMDev); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); - } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) - { - mClient = static_cast<IAudioClient*>(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; - } - - if(FAILED(hr)) - { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; - } - - return hr; -} - -void WasapiCapture::closeProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; -} - -HRESULT WasapiCapture::resetProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; - if(FAILED(hr)) - { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); - return hr; - } - mClient = static_cast<IAudioClient*>(ptr); - - // Make sure buffer is at least 100ms in size - REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; - buf_time = maxu64(buf_time, REFTIME_PER_SEC/10); - - WAVEFORMATEXTENSIBLE OutputType; - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; - - case DevFmtAmbi3D: - return E_FAIL; - } - switch(mDevice->FmtType) - { - /* NOTE: Signedness doesn't matter, the converter will handle it. */ - case DevFmtByte: - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtShort: - case DevFmtUShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtInt: - case DevFmtUInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format); - - TraceFormat("Requesting capture format", &OutputType.Format); - WAVEFORMATEX *wfx; - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); - if(FAILED(hr)) - { - ERR("Failed to check format support: 0x%08lx\n", hr); - return hr; - } - - mSampleConv = nullptr; - mChannelConv = nullptr; - - if(wfx != nullptr) - { - TraceFormat("Got capture format", wfx); - if(!(wfx->nChannels == OutputType.Format.nChannels || - (wfx->nChannels == 1 && OutputType.Format.nChannels == 2) || - (wfx->nChannels == 2 && OutputType.Format.nChannels == 1))) - { - ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample, - wfx->nSamplesPerSec); - CoTaskMemFree(wfx); - return E_FAIL; - } - - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - } - - DevFmtType srcType; - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - if(OutputType.Format.wBitsPerSample == 8) - srcType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - srcType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - srcType = DevFmtInt; - else - { - ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample); - return E_FAIL; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - if(OutputType.Format.wBitsPerSample == 32) - srcType = DevFmtFloat; - else - { - ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample); - return E_FAIL; - } - } - else - { - ERR("Unhandled format sub-type\n"); - return E_FAIL; - } - - if(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2) - { - mChannelConv = CreateChannelConverter(srcType, DevFmtStereo, mDevice->FmtChans); - if(!mChannelConv) - { - ERR("Failed to create %s stereo-to-mono converter\n", DevFmtTypeString(srcType)); - return E_FAIL; - } - TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType)); - /* The channel converter always outputs float, so change the input type - * for the resampler/type-converter. - */ - srcType = DevFmtFloat; - } - else if(mDevice->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1) - { - mChannelConv = CreateChannelConverter(srcType, DevFmtMono, mDevice->FmtChans); - if(!mChannelConv) - { - ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); - return E_FAIL; - } - TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); - srcType = DevFmtFloat; - } - - if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) - { - mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), - OutputType.Format.nSamplesPerSec, mDevice->Frequency, BSinc24Resampler); - if(!mSampleConv) - { - ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); - return E_FAIL; - } - TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); - } - - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); - if(FAILED(hr)) - { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); - return hr; - } - - UINT32 buffer_len; - REFERENCE_TIME min_per; - hr = mClient->GetDevicePeriod(&min_per, nullptr); - if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); - if(FAILED(hr)) - { - ERR("Failed to get buffer size: 0x%08lx\n", hr); - return hr; - } - mDevice->UpdateSize = static_cast<ALuint>(ScaleCeil(min_per, mDevice->Frequency, - REFTIME_PER_SEC)); - mDevice->BufferSize = buffer_len; - - buffer_len = maxu(mDevice->BufferSize, buffer_len); - mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("Failed to allocate capture ring buffer\n"); - return E_OUTOFMEMORY; - } - - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - - return hr; -} - - -ALCboolean WasapiCapture::start() -{ - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiCapture::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - void *ptr; - hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); - if(SUCCEEDED(hr)) - { - mCapture = static_cast<IAudioCaptureClient*>(ptr); - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; - } - catch(...) { - mCapture->Release(); - mCapture = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - - if(FAILED(hr)) - { - mClient->Stop(); - mClient->Reset(); - } - - return hr; -} - - -void WasapiCapture::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiCapture::stopProxy() -{ - if(!mCapture || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mCapture->Release(); - mCapture = nullptr; - mClient->Stop(); - mClient->Reset(); -} - - -ALCuint WasapiCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } - -ALCenum WasapiCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -} // namespace - - -bool WasapiBackendFactory::init() -{ - static HRESULT InitResult{E_FAIL}; - - if(FAILED(InitResult)) try - { - std::promise<HRESULT> promise; - auto future = promise.get_future(); - - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); - InitResult = future.get(); - } - catch(...) { - } - - return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; -} - -bool WasapiBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void WasapiBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - HRESULT hr{}; - switch(type) - { - case DevProbe::Playback: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get(); - if(SUCCEEDED(hr)) - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get(); - if(SUCCEEDED(hr)) - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WasapiPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new WasapiCapture{device}}; - return nullptr; -} - -BackendFactory &WasapiBackendFactory::getFactory() -{ - static WasapiBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/wasapi.h b/Alc/backends/wasapi.h deleted file mode 100644 index 067dd259..00000000 --- a/Alc/backends/wasapi.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WASAPI_H -#define BACKENDS_WASAPI_H - -#include "backends/base.h" - -struct WasapiBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_WASAPI_H */ diff --git a/Alc/backends/wave.cpp b/Alc/backends/wave.cpp deleted file mode 100644 index 67ed7e79..00000000 --- a/Alc/backends/wave.cpp +++ /dev/null @@ -1,402 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/wave.h" - -#include <algorithm> -#include <atomic> -#include <cerrno> -#include <chrono> -#include <cstdint> -#include <cstdio> -#include <cstring> -#include <exception> -#include <functional> -#include <thread> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alu.h" -#include "compat.h" -#include "logging.h" -#include "threads.h" -#include "vector.h" - - -namespace { - -using std::chrono::seconds; -using std::chrono::milliseconds; -using std::chrono::nanoseconds; - -constexpr ALCchar waveDevice[] = "Wave File Writer"; - -constexpr ALubyte SUBTYPE_PCM[]{ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71 -}; -constexpr ALubyte SUBTYPE_FLOAT[]{ - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71 -}; - -constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{ - 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, - 0xca, 0x00, 0x00, 0x00 -}; - -constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{ - 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, - 0xca, 0x00, 0x00, 0x00 -}; - -void fwrite16le(ALushort val, FILE *f) -{ - ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) }; - fwrite(data, 1, 2, f); -} - -void fwrite32le(ALuint val, FILE *f) -{ - ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff), - static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) }; - fwrite(data, 1, 4, f); -} - - -struct WaveBackend final : public BackendBase { - WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { } - ~WaveBackend() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - FILE *mFile{nullptr}; - long mDataStart{-1}; - - al::vector<ALbyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WaveBackend) -}; - -WaveBackend::~WaveBackend() -{ - if(mFile) - fclose(mFile); - mFile = nullptr; -} - -int WaveBackend::mixerProc() -{ - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - - althrd_setname(MIXER_THREAD_NAME); - - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - int64_t done{0}; - auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto now = std::chrono::steady_clock::now(); - - /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast<seconds>((now-start) * - mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) - { - std::this_thread::sleep_for(restTime); - continue; - } - while(avail-done >= mDevice->UpdateSize) - { - lock(); - aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize); - unlock(); - done += mDevice->UpdateSize; - - if(!IS_LITTLE_ENDIAN) - { - const ALsizei bytesize{mDevice->bytesFromFmt()}; - ALsizei i; - - if(bytesize == 2) - { - ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data()); - const auto len = static_cast<ALsizei>(mBuffer.size() / 2); - for(i = 0;i < len;i++) - { - ALushort samp = samples[i]; - samples[i] = (samp>>8) | (samp<<8); - } - } - else if(bytesize == 4) - { - ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data()); - const auto len = static_cast<ALsizei>(mBuffer.size() / 4); - for(i = 0;i < len;i++) - { - ALuint samp = samples[i]; - samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) | - ((samp<<8)&0x00ff0000) | (samp<<24); - } - } - } - - size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - (void)fs; - if(ferror(mFile)) - { - ERR("Error writing to file\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); - break; - } - } - - /* For every completed second, increment the start time and reduce the - * samples done. This prevents the difference between the start time - * and current time from growing too large, while maintaining the - * correct number of samples to render. - */ - if(done >= mDevice->Frequency) - { - seconds s{done/mDevice->Frequency}; - start += s; - done -= mDevice->Frequency*s.count(); - } - } - - return 0; -} - -ALCenum WaveBackend::open(const ALCchar *name) -{ - const char *fname{GetConfigValue(nullptr, "wave", "file", "")}; - if(!fname[0]) return ALC_INVALID_VALUE; - - if(!name) - name = waveDevice; - else if(strcmp(name, waveDevice) != 0) - return ALC_INVALID_VALUE; - -#ifdef _WIN32 - { - std::wstring wname = utf8_to_wstr(fname); - mFile = _wfopen(wname.c_str(), L"wb"); - } -#else - mFile = fopen(fname, "wb"); -#endif - if(!mFile) - { - ERR("Could not open file '%s': %s\n", fname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean WaveBackend::reset() -{ - ALuint channels=0, bytes=0, chanmask=0; - int isbformat = 0; - size_t val; - - fseek(mFile, 0, SEEK_SET); - clearerr(mFile); - - if(GetConfigValueBool(nullptr, "wave", "bformat", 0)) - { - mDevice->FmtChans = DevFmtAmbi3D; - mDevice->mAmbiOrder = 1; - } - - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - } - switch(mDevice->FmtChans) - { - case DevFmtMono: chanmask = 0x04; break; - case DevFmtStereo: chanmask = 0x01 | 0x02; break; - case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; - case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; - case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break; - case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; - case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; - case DevFmtAmbi3D: - /* .amb output requires FuMa */ - mDevice->mAmbiOrder = mini(mDevice->mAmbiOrder, 3); - mDevice->mAmbiLayout = AmbiLayout::FuMa; - mDevice->mAmbiScale = AmbiNorm::FuMa; - isbformat = 1; - chanmask = 0; - break; - } - bytes = mDevice->bytesFromFmt(); - channels = mDevice->channelsFromFmt(); - - rewind(mFile); - - fputs("RIFF", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close - - fputs("WAVE", mFile); - - fputs("fmt ", mFile); - fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE - - // 16-bit val, format type id (extensible: 0xFFFE) - fwrite16le(0xFFFE, mFile); - // 16-bit val, channel count - fwrite16le(channels, mFile); - // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile); - // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile); - // 16-bit val, frame size - fwrite16le(channels * bytes, mFile); - // 16-bit val, bits per sample - fwrite16le(bytes * 8, mFile); - // 16-bit val, extra byte count - fwrite16le(22, mFile); - // 16-bit val, valid bits per sample - fwrite16le(bytes * 8, mFile); - // 32-bit val, channel mask - fwrite32le(chanmask, mFile); - // 16 byte GUID, sub-type format - val = fwrite((mDevice->FmtType == DevFmtFloat) ? - (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : - (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); - (void)val; - - fputs("data", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close - - if(ferror(mFile)) - { - ERR("Error writing header: %s\n", strerror(errno)); - return ALC_FALSE; - } - mDataStart = ftell(mFile); - - SetDefaultWFXChannelOrder(mDevice); - - const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; - mBuffer.resize(bufsize); - - return ALC_TRUE; -} - -ALCboolean WaveBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WaveBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - long size{ftell(mFile)}; - if(size > 0) - { - long dataLen{size - mDataStart}; - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(dataLen, mFile); // 'data' header len - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(size-8, mFile); // 'WAVE' header len - } -} - -} // namespace - - -bool WaveBackendFactory::init() -{ return true; } - -bool WaveBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void WaveBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(waveDevice, sizeof(waveDevice)); - break; - case DevProbe::Capture: - break; - } -} - -BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WaveBackend{device}}; - return nullptr; -} - -BackendFactory &WaveBackendFactory::getFactory() -{ - static WaveBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/wave.h b/Alc/backends/wave.h deleted file mode 100644 index b9b62d7f..00000000 --- a/Alc/backends/wave.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WAVE_H -#define BACKENDS_WAVE_H - -#include "backends/base.h" - -struct WaveBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_WAVE_H */ diff --git a/Alc/backends/winmm.cpp b/Alc/backends/winmm.cpp deleted file mode 100644 index cd32e95b..00000000 --- a/Alc/backends/winmm.cpp +++ /dev/null @@ -1,640 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/winmm.h" - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <windows.h> -#include <mmsystem.h> - -#include <array> -#include <atomic> -#include <thread> -#include <vector> -#include <string> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#ifndef WAVE_FORMAT_IEEE_FLOAT -#define WAVE_FORMAT_IEEE_FLOAT 0x0003 -#endif - -namespace { - -#define DEVNAME_HEAD "OpenAL Soft on " - - -al::vector<std::string> PlaybackDevices; -al::vector<std::string> CaptureDevices; - -bool checkName(const al::vector<std::string> &list, const std::string &name) -{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); } - -void ProbePlaybackDevices(void) -{ - PlaybackDevices.clear(); - - ALuint numdevs{waveOutGetNumDevs()}; - PlaybackDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) - { - std::string dname; - - WAVEOUTCAPSW WaveCaps{}; - if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) - { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; - - int count{1}; - std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - dname = std::move(newname); - - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); - } - PlaybackDevices.emplace_back(std::move(dname)); - } -} - -void ProbeCaptureDevices(void) -{ - CaptureDevices.clear(); - - ALuint numdevs{waveInGetNumDevs()}; - CaptureDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) - { - std::string dname; - - WAVEINCAPSW WaveCaps{}; - if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) - { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; - - int count{1}; - std::string newname{basename}; - while(checkName(CaptureDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - dname = std::move(newname); - - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); - } - CaptureDevices.emplace_back(std::move(dname)); - } -} - - -struct WinMMPlayback final : public BackendBase { - WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~WinMMPlayback() override; - - static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::atomic<ALuint> mWritable{0u}; - al::semaphore mSem; - int mIdx{0}; - std::array<WAVEHDR,4> mWaveBuffer{}; - - HWAVEOUT mOutHdl{nullptr}; - - WAVEFORMATEX mFormat{}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WinMMPlayback) -}; - -WinMMPlayback::~WinMMPlayback() -{ - if(mOutHdl) - waveOutClose(mOutHdl); - mOutHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); -} - - -void CALLBACK WinMMPlayback::waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); } - -/* WinMMPlayback::waveOutProc - * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is - * completed and returns to the application (for more data) - */ -void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) -{ - if(msg != WOM_DONE) return; - mWritable.fetch_add(1, std::memory_order_acq_rel); - mSem.post(); -} - -FORCE_ALIGN int WinMMPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - ALsizei todo = mWritable.load(std::memory_order_acquire); - if(todo < 1) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - int widx{mIdx}; - do { - WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); - - aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize); - mWritable.fetch_sub(1, std::memory_order_acq_rel); - waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); - } while(--todo); - mIdx = widx; - } - unlock(); - - return 0; -} - - -ALCenum WinMMPlayback::open(const ALCchar *name) -{ - if(PlaybackDevices.empty()) - ProbePlaybackDevices(); - - // Find the Device ID matching the deviceName if valid - auto iter = name ? - std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : - PlaybackDevices.cbegin(); - if(iter == PlaybackDevices.cend()) return ALC_INVALID_VALUE; - auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter)); - -retry_open: - mFormat = WAVEFORMATEX{}; - if(mDevice->FmtType == DevFmtFloat) - { - mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - mFormat.wBitsPerSample = 32; - } - else - { - mFormat.wFormatTag = WAVE_FORMAT_PCM; - if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; - else - mFormat.wBitsPerSample = 16; - } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMPlayback::waveOutProcC, - reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { - if(mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - ERR("waveOutOpen failed: %u\n", res); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = PlaybackDevices[DeviceID]; - return ALC_NO_ERROR; -} - -ALCboolean WinMMPlayback::reset() -{ - mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} * - mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3; - mDevice->UpdateSize = mDevice->BufferSize / 4; - mDevice->Frequency = mFormat.nSamplesPerSec; - - if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) - { - if(mFormat.wBitsPerSample == 32) - mDevice->FmtType = DevFmtFloat; - else - { - ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; - } - } - else if(mFormat.wFormatTag == WAVE_FORMAT_PCM) - { - if(mFormat.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(mFormat.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else - { - ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; - } - } - else - { - ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); - return ALC_FALSE; - } - - if(mFormat.nChannels == 2) - mDevice->FmtChans = DevFmtStereo; - else if(mFormat.nChannels == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unhandled channel count: %d\n", mFormat.nChannels); - return ALC_FALSE; - } - SetDefaultWFXChannelOrder(mDevice); - - ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()}; - - al_free(mWaveBuffer[0].lpData); - mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size())); - mWaveBuffer[0].dwBufferLength = BufferSize; - for(size_t i{1};i < mWaveBuffer.size();i++) - { - mWaveBuffer[i] = WAVEHDR{}; - mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; - mWaveBuffer[i].dwBufferLength = BufferSize; - } - mIdx = 0; - - return ALC_TRUE; -} - -ALCboolean WinMMPlayback::start() -{ - try { - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); } - ); - mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release); - - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WinMMPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size()) - mSem.wait(); - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } - ); - mWritable.store(0, std::memory_order_release); -} - - -struct WinMMCapture final : public BackendBase { - WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~WinMMCapture() override; - - static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); - - int captureProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::atomic<ALuint> mReadable{0u}; - al::semaphore mSem; - int mIdx{0}; - std::array<WAVEHDR,4> mWaveBuffer{}; - - HWAVEIN mInHdl{nullptr}; - - RingBufferPtr mRing{nullptr}; - - WAVEFORMATEX mFormat{}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WinMMCapture) -}; - -WinMMCapture::~WinMMCapture() -{ - // Close the Wave device - if(mInHdl) - waveInClose(mInHdl); - mInHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); -} - -void CALLBACK WinMMCapture::waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); } - -/* WinMMCapture::waveInProc - * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is - * completed and returns to the application (with more data). - */ -void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) -{ - if(msg != WIM_DATA) return; - mReadable.fetch_add(1, std::memory_order_acq_rel); - mSem.post(); -} - -int WinMMCapture::captureProc() -{ - althrd_setname(RECORD_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - ALuint todo{mReadable.load(std::memory_order_acquire)}; - if(todo < 1) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - int widx{mIdx}; - do { - WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); - - mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); - mReadable.fetch_sub(1, std::memory_order_acq_rel); - waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); - } while(--todo); - mIdx = widx; - } - unlock(); - - return 0; -} - - -ALCenum WinMMCapture::open(const ALCchar *name) -{ - if(CaptureDevices.empty()) - ProbeCaptureDevices(); - - // Find the Device ID matching the deviceName if valid - auto iter = name ? - std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : - CaptureDevices.cbegin(); - if(iter == CaptureDevices.cend()) return ALC_INVALID_VALUE; - auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter)); - - switch(mDevice->FmtChans) - { - case DevFmtMono: - case DevFmtStereo: - break; - - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - return ALC_INVALID_ENUM; - } - - switch(mDevice->FmtType) - { - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - return ALC_INVALID_ENUM; - } - - mFormat = WAVEFORMATEX{}; - mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ? - WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; - mFormat.nChannels = mDevice->channelsFromFmt(); - mFormat.wBitsPerSample = mDevice->bytesFromFmt() * 8; - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMCapture::waveInProcC, - reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { - ERR("waveInOpen failed: %u\n", res); - return ALC_INVALID_VALUE; - } - - // Ensure each buffer is 50ms each - DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; - BufferSize -= (BufferSize % mFormat.nBlockAlign); - - // Allocate circular memory buffer for the captured audio - // Make sure circular buffer is at least 100ms in size - ALuint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); - - mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false); - if(!mRing) return ALC_INVALID_VALUE; - - al_free(mWaveBuffer[0].lpData); - mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4)); - mWaveBuffer[0].dwBufferLength = BufferSize; - for(size_t i{1};i < mWaveBuffer.size();++i) - { - mWaveBuffer[i] = WAVEHDR{}; - mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; - mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; - } - - mDevice->DeviceName = CaptureDevices[DeviceID]; - return ALC_NO_ERROR; -} - -ALCboolean WinMMCapture::start() -{ - try { - for(size_t i{0};i < mWaveBuffer.size();++i) - { - waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - } - - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; - - waveInStart(mInHdl); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WinMMCapture::stop() -{ - waveInStop(mInHdl); - - mKillNow.store(true, std::memory_order_release); - if(mThread.joinable()) - { - mSem.post(); - mThread.join(); - } - - waveInReset(mInHdl); - for(size_t i{0};i < mWaveBuffer.size();++i) - waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - - mReadable.store(0, std::memory_order_release); - mIdx = 0; -} - -ALCenum WinMMCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint WinMMCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } - -} // namespace - - -bool WinMMBackendFactory::init() -{ return true; } - -bool WinMMBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void WinMMBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const std::string &dname) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - if(!dname.empty()) - outnames->append(dname.c_str(), dname.length()+1); - }; - switch(type) - { - case DevProbe::Playback: - ProbePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - ProbeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WinMMPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new WinMMCapture{device}}; - return nullptr; -} - -BackendFactory &WinMMBackendFactory::getFactory() -{ - static WinMMBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/winmm.h b/Alc/backends/winmm.h deleted file mode 100644 index e357ec19..00000000 --- a/Alc/backends/winmm.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WINMM_H -#define BACKENDS_WINMM_H - -#include "backends/base.h" - -struct WinMMBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_WINMM_H */ |