mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-25 07:04:45 +00:00
keeping the alt 87514151c4 (diff-73a8dc1ce58605f6c5ea53548454c3bae516ec5132a29c9d7ff7edf9730c75be)
701 lines
20 KiB
C++
701 lines
20 KiB
C++
/**
|
|
* OpenAL cross platform audio library
|
|
* Copyright (C) 2024 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 "otherio.h"
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <winreg.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <memory.h>
|
|
|
|
#include <wtypes.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 <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <future>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "albit.h"
|
|
#include "alnumeric.h"
|
|
#include "althrd_setname.h"
|
|
#include "comptr.h"
|
|
#include "core/converter.h"
|
|
#include "core/device.h"
|
|
#include "core/helpers.h"
|
|
#include "core/logging.h"
|
|
#include "strutils.h"
|
|
|
|
|
|
/* A custom C++ interface that should be capable of interoperating with ASIO
|
|
* drivers.
|
|
*/
|
|
enum class ORIOError : LONG {
|
|
Okay = 0,
|
|
Success = 0x3f4847a0,
|
|
NotPresent = -1000,
|
|
HWMalfunction,
|
|
InvalidParameter,
|
|
InvalidMode,
|
|
SPNotAdvancing,
|
|
NoClock,
|
|
NoMemory,
|
|
};
|
|
|
|
/* A 64-bit integer or double, which has the most significant 32-bit word first. */
|
|
struct ORIO64Bit {
|
|
uint32_t hi;
|
|
uint32_t lo;
|
|
|
|
template<typename T>
|
|
auto as() const -> T = delete;
|
|
};
|
|
|
|
template<> [[nodiscard]]
|
|
auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; }
|
|
template<> [[nodiscard]]
|
|
auto ORIO64Bit::as() const -> int64_t { return static_cast<int64_t>(as<uint64_t>()); }
|
|
template<> [[nodiscard]]
|
|
auto ORIO64Bit::as() const -> double { return al::bit_cast<double>(as<uint64_t>()); }
|
|
|
|
|
|
enum class ORIOSampleType : LONG {
|
|
Int16BE = 0,
|
|
Int24BE = 1,
|
|
Int32BE = 2,
|
|
Float32BE = 3,
|
|
Float64BE = 4,
|
|
Int32BE16 = 8,
|
|
Int32BE18 = 9,
|
|
Int32BE20 = 10,
|
|
Int32BE24 = 11,
|
|
|
|
Int16LE = 16,
|
|
Int24LE = 17,
|
|
Int32LE = 18,
|
|
Float32LE = 19,
|
|
Float64LE = 20,
|
|
Int32LE16 = 24,
|
|
Int32LE18 = 25,
|
|
Int32LE20 = 26,
|
|
Int32LE24 = 27,
|
|
|
|
DSDInt8LSB1 = 32,
|
|
DSDInt8MSB1 = 33,
|
|
|
|
DSDInt8 = 40,
|
|
};
|
|
|
|
struct ORIOClockSource {
|
|
LONG mIndex;
|
|
LONG mAssocChannel;
|
|
LONG mAssocGroup;
|
|
LONG mIsCurrent;
|
|
std::array<char,32> mName;
|
|
};
|
|
|
|
struct ORIOChannelInfo {
|
|
LONG mChannel;
|
|
LONG mIsInput;
|
|
LONG mIsActive;
|
|
LONG mGroup;
|
|
ORIOSampleType mSampleType;
|
|
std::array<char,32> mName;
|
|
};
|
|
|
|
struct ORIOBufferInfo {
|
|
LONG mIsInput;
|
|
LONG mChannelNum;
|
|
std::array<void*,2> mBuffers;
|
|
};
|
|
|
|
struct ORIOTime {
|
|
struct TimeInfo {
|
|
double mSpeed;
|
|
ORIO64Bit mSystemTime;
|
|
ORIO64Bit mSamplePosition;
|
|
double mSampleRate;
|
|
ULONG mFlags;
|
|
std::array<char,12> mReserved;
|
|
};
|
|
struct TimeCode {
|
|
double mSpeed;
|
|
ORIO64Bit mTimeCodeSamples;
|
|
ULONG mFlags;
|
|
std::array<char,64> mFuture;
|
|
};
|
|
|
|
std::array<LONG,4> mReserved;
|
|
TimeInfo mTimeInfo;
|
|
TimeCode mTimeCode;
|
|
};
|
|
|
|
#ifdef _WIN64
|
|
#define ORIO_CALLBACK CALLBACK
|
|
#else
|
|
#define ORIO_CALLBACK
|
|
#endif
|
|
|
|
struct ORIOCallbacks {
|
|
void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept;
|
|
void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept;
|
|
auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG;
|
|
auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*;
|
|
};
|
|
|
|
/* COM interfaces don't include a virtual destructor in their pure-virtual
|
|
* classes, and we can't add one without breaking ABI.
|
|
*/
|
|
#ifdef __GNUC__
|
|
_Pragma("GCC diagnostic push")
|
|
_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
|
|
#endif
|
|
/* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */
|
|
struct ORIOiface : public IUnknown {
|
|
STDMETHOD_(LONG, Init)(void *sysHandle) = 0;
|
|
/* A fixed-length span should be passed exactly the same as one pointer.
|
|
* This ensures an appropriately-sized buffer for the driver.
|
|
*/
|
|
STDMETHOD_(void, GetDriverName)(al::span<char,32> name) = 0;
|
|
STDMETHOD_(LONG, GetDriverVersion)() = 0;
|
|
STDMETHOD_(void, GetErrorMessage)(al::span<char,124> message) = 0;
|
|
STDMETHOD_(ORIOError, Start)() = 0;
|
|
STDMETHOD_(ORIOError, Stop)() = 0;
|
|
STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0;
|
|
STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0;
|
|
STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0;
|
|
STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0;
|
|
STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0;
|
|
STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0;
|
|
STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0;
|
|
STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0;
|
|
STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0;
|
|
STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0;
|
|
STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0;
|
|
STDMETHOD_(ORIOError, DisposeBuffers)() = 0;
|
|
STDMETHOD_(ORIOError, ControlPanel)() = 0;
|
|
STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0;
|
|
STDMETHOD_(ORIOError, OutputReady)() = 0;
|
|
|
|
ORIOiface() = default;
|
|
ORIOiface(const ORIOiface&) = delete;
|
|
auto operator=(const ORIOiface&) -> ORIOiface& = delete;
|
|
~ORIOiface() = delete;
|
|
};
|
|
#ifdef __GNUC__
|
|
_Pragma("GCC diagnostic pop")
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
using namespace std::string_view_literals;
|
|
using std::chrono::nanoseconds;
|
|
using std::chrono::milliseconds;
|
|
using std::chrono::seconds;
|
|
|
|
|
|
struct DeviceEntry {
|
|
std::string mDrvName;
|
|
CLSID mDrvGuid{};
|
|
};
|
|
|
|
std::vector<DeviceEntry> gDeviceList;
|
|
|
|
|
|
struct KeyCloser {
|
|
void operator()(HKEY key) { RegCloseKey(key); }
|
|
};
|
|
using KeyPtr = std::unique_ptr<std::remove_pointer_t<HKEY>,KeyCloser>;
|
|
|
|
[[nodiscard]]
|
|
auto PopulateDeviceList() -> HRESULT
|
|
{
|
|
auto regbase = KeyPtr{};
|
|
auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ,
|
|
al::out_ptr(regbase));
|
|
if(res != ERROR_SUCCESS)
|
|
{
|
|
ERR("Error opening HKLM\\Software\\ASIO: {}", res);
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
auto numkeys = DWORD{};
|
|
auto maxkeylen = DWORD{};
|
|
res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, nullptr);
|
|
if(res != ERROR_SUCCESS)
|
|
{
|
|
ERR("Error querying HKLM\\Software\\ASIO info: {}", res);
|
|
return E_FAIL;
|
|
}
|
|
|
|
/* maxkeylen is the max number of unicode characters a subkey is. A unicode
|
|
* character can occupy two WCHARs, so ensure there's enough space for them
|
|
* and the null char.
|
|
*/
|
|
auto keyname = std::vector<WCHAR>(maxkeylen*2 + 1);
|
|
for(DWORD i{0};i < numkeys;++i)
|
|
{
|
|
auto namelen = static_cast<DWORD>(keyname.size());
|
|
res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr,
|
|
nullptr);
|
|
if(res != ERROR_SUCCESS)
|
|
{
|
|
ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res);
|
|
continue;
|
|
}
|
|
if(namelen == 0)
|
|
{
|
|
ERR("HKLM\\Software\\ASIO subkey {} is blank?", i);
|
|
continue;
|
|
}
|
|
auto subkeyname = wstr_to_utf8({keyname.data(), namelen});
|
|
|
|
auto subkey = KeyPtr{};
|
|
res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey));
|
|
if(res != ERROR_SUCCESS)
|
|
{
|
|
ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res);
|
|
continue;
|
|
}
|
|
|
|
auto idstr = std::array<WCHAR,48>{};
|
|
auto readsize = DWORD{idstr.size()*sizeof(WCHAR)};
|
|
res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(),
|
|
&readsize);
|
|
if(res != ERROR_SUCCESS)
|
|
{
|
|
ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res);
|
|
continue;
|
|
}
|
|
idstr.back() = 0;
|
|
|
|
auto guid = CLSID{};
|
|
if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr))
|
|
{
|
|
ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()),
|
|
as_unsigned(hr));
|
|
continue;
|
|
}
|
|
|
|
/* The CLSID is also used for the IID. */
|
|
auto iface = ComPtr<ORIOiface>{};
|
|
auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface));
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
#if !ALSOFT_UWP
|
|
if(!iface->Init(GetForegroundWindow()))
|
|
#else
|
|
if(!iface->Init(nullptr))
|
|
#endif
|
|
{
|
|
ERR("Failed to initialize {}", subkeyname);
|
|
continue;
|
|
}
|
|
auto drvname = std::array<char,32>{};
|
|
iface->GetDriverName(drvname);
|
|
auto drvver = iface->GetDriverVersion();
|
|
|
|
auto &entry = gDeviceList.emplace_back();
|
|
entry.mDrvName = drvname.data();
|
|
entry.mDrvGuid = guid;
|
|
|
|
TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
|
|
entry.mDrvName, drvver, 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]);
|
|
}
|
|
else
|
|
ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}",
|
|
subkeyname.c_str(), 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], as_unsigned(hr));
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
enum class MsgType {
|
|
OpenDevice,
|
|
ResetDevice,
|
|
StartDevice,
|
|
StopDevice,
|
|
CloseDevice,
|
|
|
|
QuitThread
|
|
};
|
|
|
|
constexpr const char *GetMessageTypeName(MsgType type) noexcept
|
|
{
|
|
switch(type)
|
|
{
|
|
case MsgType::OpenDevice: return "Open Device";
|
|
case MsgType::ResetDevice: return "Reset Device";
|
|
case MsgType::StartDevice: return "Start Device";
|
|
case MsgType::StopDevice: return "Stop Device";
|
|
case MsgType::CloseDevice: return "Close Device";
|
|
case MsgType::QuitThread: break;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
/* Proxy interface used by the message handler, to ensure COM objects are used
|
|
* on a thread where COM is initialized.
|
|
*/
|
|
struct OtherIOProxy {
|
|
OtherIOProxy() = default;
|
|
OtherIOProxy(const OtherIOProxy&) = delete;
|
|
OtherIOProxy(OtherIOProxy&&) = delete;
|
|
virtual ~OtherIOProxy() = default;
|
|
|
|
void operator=(const OtherIOProxy&) = delete;
|
|
void operator=(OtherIOProxy&&) = delete;
|
|
|
|
virtual HRESULT openProxy(std::string_view name) = 0;
|
|
virtual void closeProxy() = 0;
|
|
|
|
virtual HRESULT resetProxy() = 0;
|
|
virtual HRESULT startProxy() = 0;
|
|
virtual void stopProxy() = 0;
|
|
|
|
struct Msg {
|
|
MsgType mType;
|
|
OtherIOProxy *mProxy;
|
|
std::string_view mParam;
|
|
std::promise<HRESULT> mPromise;
|
|
|
|
explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
|
|
};
|
|
static inline std::deque<Msg> mMsgQueue;
|
|
static inline std::mutex mMsgQueueLock;
|
|
static inline std::condition_variable mMsgQueueCond;
|
|
|
|
auto pushMessage(MsgType type, std::string_view param={}) -> std::future<HRESULT>
|
|
{
|
|
auto promise = std::promise<HRESULT>{};
|
|
auto future = std::future<HRESULT>{promise.get_future()};
|
|
{
|
|
auto msglock = std::lock_guard{mMsgQueueLock};
|
|
mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
|
|
}
|
|
mMsgQueueCond.notify_one();
|
|
return future;
|
|
}
|
|
|
|
static auto popMessage() -> Msg
|
|
{
|
|
auto lock = std::unique_lock{mMsgQueueLock};
|
|
mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
|
|
auto msg = Msg{std::move(mMsgQueue.front())};
|
|
mMsgQueue.pop_front();
|
|
return msg;
|
|
}
|
|
|
|
static void messageHandler(std::promise<HRESULT> *promise);
|
|
};
|
|
|
|
void OtherIOProxy::messageHandler(std::promise<HRESULT> *promise)
|
|
{
|
|
TRACE("Starting COM message thread");
|
|
|
|
auto com = ComWrapper{COINIT_APARTMENTTHREADED};
|
|
if(!com)
|
|
{
|
|
WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status()));
|
|
promise->set_value(com.status());
|
|
return;
|
|
}
|
|
|
|
auto hr = PopulateDeviceList();
|
|
if(FAILED(hr))
|
|
{
|
|
promise->set_value(hr);
|
|
return;
|
|
}
|
|
|
|
promise->set_value(S_OK);
|
|
promise = nullptr;
|
|
|
|
TRACE("Starting message loop");
|
|
while(Msg msg{popMessage()})
|
|
{
|
|
TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")",
|
|
GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
|
|
static_cast<void*>(msg.mProxy), msg.mParam);
|
|
|
|
switch(msg.mType)
|
|
{
|
|
case MsgType::OpenDevice:
|
|
hr = msg.mProxy->openProxy(msg.mParam);
|
|
msg.mPromise.set_value(hr);
|
|
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);
|
|
continue;
|
|
|
|
case MsgType::QuitThread:
|
|
break;
|
|
}
|
|
ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)});
|
|
msg.mPromise.set_value(E_FAIL);
|
|
}
|
|
TRACE("Message loop finished");
|
|
}
|
|
|
|
|
|
struct OtherIOPlayback final : public BackendBase, OtherIOProxy {
|
|
explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
|
~OtherIOPlayback() final;
|
|
|
|
void mixerProc();
|
|
|
|
void open(std::string_view name) final;
|
|
auto openProxy(std::string_view name) -> HRESULT final;
|
|
void closeProxy() final;
|
|
auto reset() -> bool final;
|
|
auto resetProxy() -> HRESULT final;
|
|
void start() final;
|
|
auto startProxy() -> HRESULT final;
|
|
void stop() final;
|
|
void stopProxy() final;
|
|
|
|
HRESULT mOpenStatus{E_FAIL};
|
|
|
|
std::atomic<bool> mKillNow{true};
|
|
std::thread mThread;
|
|
};
|
|
|
|
OtherIOPlayback::~OtherIOPlayback()
|
|
{
|
|
if(SUCCEEDED(mOpenStatus))
|
|
pushMessage(MsgType::CloseDevice).wait();
|
|
}
|
|
|
|
void OtherIOPlayback::mixerProc()
|
|
{
|
|
const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
|
|
|
|
SetRTPriority();
|
|
althrd_setname(GetMixerThreadName());
|
|
|
|
auto done = int64_t{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. */
|
|
const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
|
|
* mDevice->mSampleRate).count()};
|
|
if(avail-done < mDevice->mUpdateSize)
|
|
{
|
|
std::this_thread::sleep_for(restTime);
|
|
continue;
|
|
}
|
|
while(avail-done >= mDevice->mUpdateSize)
|
|
{
|
|
mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
|
|
done += mDevice->mUpdateSize;
|
|
}
|
|
|
|
if(done >= mDevice->mSampleRate)
|
|
{
|
|
auto s = seconds{done/mDevice->mSampleRate};
|
|
start += s;
|
|
done -= mDevice->mSampleRate*s.count();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OtherIOPlayback::open(std::string_view name)
|
|
{
|
|
if(name.empty() && !gDeviceList.empty())
|
|
name = gDeviceList[0].mDrvName;
|
|
else
|
|
{
|
|
auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(),
|
|
[name](const DeviceEntry &entry) { return entry.mDrvName == name; });
|
|
if(iter == gDeviceList.cend())
|
|
throw al::backend_exception{al::backend_error::NoDevice,
|
|
"Device name \"{}\" not found", name};
|
|
}
|
|
|
|
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
|
|
if(FAILED(mOpenStatus))
|
|
throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name};
|
|
|
|
mDeviceName = name;
|
|
}
|
|
|
|
auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
void OtherIOPlayback::closeProxy()
|
|
{
|
|
}
|
|
|
|
auto OtherIOPlayback::reset() -> bool
|
|
{
|
|
return SUCCEEDED(pushMessage(MsgType::ResetDevice).get());
|
|
}
|
|
|
|
auto OtherIOPlayback::resetProxy() -> HRESULT
|
|
{
|
|
setDefaultWFXChannelOrder();
|
|
return S_OK;
|
|
}
|
|
|
|
void OtherIOPlayback::start()
|
|
{
|
|
auto hr = pushMessage(MsgType::StartDevice).get();
|
|
if(FAILED(hr))
|
|
throw al::backend_exception{al::backend_error::DeviceError,
|
|
"Failed to start playback: {:#x}", as_unsigned(hr)};
|
|
}
|
|
|
|
auto OtherIOPlayback::startProxy() -> HRESULT
|
|
{
|
|
try {
|
|
mKillNow.store(false, std::memory_order_release);
|
|
mThread = std::thread{&OtherIOPlayback::mixerProc, this};
|
|
return S_OK;
|
|
}
|
|
catch(std::exception& e) {
|
|
ERR("Failed to start mixing thread: {}", e.what());
|
|
}
|
|
return E_FAIL;
|
|
}
|
|
|
|
void OtherIOPlayback::stop()
|
|
{
|
|
pushMessage(MsgType::StopDevice).wait();
|
|
}
|
|
|
|
void OtherIOPlayback::stopProxy()
|
|
{
|
|
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
|
return;
|
|
mThread.join();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
auto OtherIOBackendFactory::init() -> bool
|
|
{
|
|
static HRESULT InitResult{E_FAIL};
|
|
if(FAILED(InitResult)) try
|
|
{
|
|
auto promise = std::promise<HRESULT>{};
|
|
auto future = promise.get_future();
|
|
|
|
std::thread{&OtherIOProxy::messageHandler, &promise}.detach();
|
|
InitResult = future.get();
|
|
}
|
|
catch(...) {
|
|
}
|
|
|
|
return SUCCEEDED(InitResult);
|
|
}
|
|
|
|
auto OtherIOBackendFactory::querySupport(BackendType type) -> bool
|
|
{ return type == BackendType::Playback; }
|
|
|
|
auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
|
{
|
|
std::vector<std::string> outnames;
|
|
|
|
switch(type)
|
|
{
|
|
case BackendType::Playback:
|
|
std::for_each(gDeviceList.cbegin(), gDeviceList.cend(),
|
|
[&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); });
|
|
break;
|
|
|
|
case BackendType::Capture:
|
|
break;
|
|
}
|
|
|
|
return outnames;
|
|
}
|
|
|
|
auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
|
|
{
|
|
if(type == BackendType::Playback)
|
|
return BackendPtr{new OtherIOPlayback{device}};
|
|
return nullptr;
|
|
}
|
|
|
|
auto OtherIOBackendFactory::getFactory() -> BackendFactory&
|
|
{
|
|
static auto factory = OtherIOBackendFactory{};
|
|
return factory;
|
|
}
|
|
|
|
auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
|
|
{
|
|
return alc::EventSupport::NoSupport;
|
|
}
|