First Commit
This commit is contained in:
850
externals/openal-soft/alc/backends/dsound.cpp
vendored
Normal file
850
externals/openal-soft/alc/backends/dsound.cpp
vendored
Normal file
@@ -0,0 +1,850 @@
|
||||
/**
|
||||
* 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 "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 "alnumeric.h"
|
||||
#include "comptr.h"
|
||||
#include "core/device.h"
|
||||
#include "core/helpers.h"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "strutils.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 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 X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
|
||||
|
||||
#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)
|
||||
{
|
||||
auto match_name = [&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; };
|
||||
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
|
||||
}
|
||||
|
||||
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
|
||||
{
|
||||
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(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~DSoundPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const char *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
ComPtr<IDirectSound> mDS;
|
||||
ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
|
||||
ComPtr<IDirectSoundBuffer> mBuffer;
|
||||
ComPtr<IDirectSoundNotify> mNotifies;
|
||||
HANDLE mNotifyEvent{nullptr};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(DSoundPlayback)
|
||||
};
|
||||
|
||||
DSoundPlayback::~DSoundPlayback()
|
||||
{
|
||||
mNotifies = nullptr;
|
||||
mBuffer = nullptr;
|
||||
mPrimaryBuffer = nullptr;
|
||||
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);
|
||||
mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t FrameStep{mDevice->channelsFromFmt()};
|
||||
uint 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);
|
||||
mDevice->handleDisconnect("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))
|
||||
{
|
||||
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
|
||||
if(WriteCnt2 > 0)
|
||||
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
|
||||
|
||||
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Buffer lock error: %#lx\n", err);
|
||||
mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Update old write cursor location
|
||||
LastCursor += WriteCnt1+WriteCnt2;
|
||||
LastCursor %= DSBCaps.dwBufferBytes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DSoundPlayback::open(const char *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())
|
||||
{
|
||||
GUID id{};
|
||||
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
|
||||
if(SUCCEEDED(hr))
|
||||
iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
|
||||
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%s\" not found", name};
|
||||
}
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
||||
hr = DS_OK;
|
||||
if(!mNotifyEvent)
|
||||
{
|
||||
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if(!mNotifyEvent) hr = E_FAIL;
|
||||
}
|
||||
|
||||
//DirectSound Init code
|
||||
ComPtr<IDirectSound> ds;
|
||||
if(SUCCEEDED(hr))
|
||||
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
|
||||
mNotifies = nullptr;
|
||||
mBuffer = nullptr;
|
||||
mPrimaryBuffer = nullptr;
|
||||
mDS = std::move(ds);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool DSoundPlayback::reset()
|
||||
{
|
||||
mNotifies = nullptr;
|
||||
mBuffer = nullptr;
|
||||
mPrimaryBuffer = nullptr;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
if(mDevice->Flags.test(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(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to get speaker config: 0x%08lx", hr};
|
||||
|
||||
speakers = DSSPEAKER_CONFIG(speakers);
|
||||
if(!mDevice->Flags.test(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 || speakers == DSSPEAKER_5POINT1_BACK)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else
|
||||
ERR("Unknown system speaker config: 0x%lx\n", speakers);
|
||||
}
|
||||
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
|
||||
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
|
||||
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
|
||||
/* fall-through */
|
||||
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
|
||||
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
|
||||
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
|
||||
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
|
||||
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
|
||||
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
|
||||
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
|
||||
}
|
||||
|
||||
retry_open:
|
||||
hr = S_OK;
|
||||
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
OutputType.Format.nBlockAlign = static_cast<WORD>(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;
|
||||
|
||||
mPrimaryBuffer = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(SUCCEEDED(hr) && !mPrimaryBuffer)
|
||||
{
|
||||
DSBUFFERDESC DSBDescription{};
|
||||
DSBDescription.dwSize = sizeof(DSBDescription);
|
||||
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
|
||||
}
|
||||
if(SUCCEEDED(hr))
|
||||
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
|
||||
}
|
||||
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
uint 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.getPtr(), 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))
|
||||
{
|
||||
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
|
||||
|
||||
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
assert(num_updates <= MAX_UPDATES);
|
||||
|
||||
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
|
||||
for(uint i{0};i < num_updates;++i)
|
||||
{
|
||||
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
|
||||
nots[i].hEventNotify = mNotifyEvent;
|
||||
}
|
||||
if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
|
||||
hr = E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
mNotifies = nullptr;
|
||||
mBuffer = nullptr;
|
||||
mPrimaryBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetEvent(mNotifyEvent);
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DSoundPlayback::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
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(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~DSoundCapture() override;
|
||||
|
||||
void open(const char *name) override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void captureSamples(al::byte *buffer, uint samples) override;
|
||||
uint availableSamples() override;
|
||||
|
||||
ComPtr<IDirectSoundCapture> mDSC;
|
||||
ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
|
||||
DWORD mBufferBytes{0u};
|
||||
DWORD mCursor{0u};
|
||||
|
||||
RingBufferPtr mRing;
|
||||
|
||||
DEF_NEWDEL(DSoundCapture)
|
||||
};
|
||||
|
||||
DSoundCapture::~DSoundCapture()
|
||||
{
|
||||
if(mDSCbuffer)
|
||||
{
|
||||
mDSCbuffer->Stop();
|
||||
mDSCbuffer = nullptr;
|
||||
}
|
||||
mDSC = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void DSoundCapture::open(const char *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())
|
||||
{
|
||||
GUID id{};
|
||||
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
|
||||
if(SUCCEEDED(hr))
|
||||
iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
|
||||
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%s\" not found", name};
|
||||
}
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
|
||||
WAVEFORMATEXTENSIBLE InputType{};
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: InputType.dwChannelMask = MONO; break;
|
||||
case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
|
||||
case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
|
||||
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
|
||||
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
|
||||
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
|
||||
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
InputType.Format.nBlockAlign = static_cast<WORD>(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);
|
||||
}
|
||||
|
||||
uint 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.getPtr(), nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
mRing = nullptr;
|
||||
mDSCbuffer = nullptr;
|
||||
mDSC = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
}
|
||||
|
||||
mBufferBytes = DSCBDescription.dwBufferBytes;
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
void DSoundCapture::start()
|
||||
{
|
||||
const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failure starting capture: 0x%lx", hr};
|
||||
}
|
||||
|
||||
void DSoundCapture::stop()
|
||||
{
|
||||
HRESULT hr{mDSCbuffer->Stop()};
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("stop failed: 0x%08lx\n", hr);
|
||||
mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
|
||||
}
|
||||
}
|
||||
|
||||
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
|
||||
{ mRing->read(buffer, samples); }
|
||||
|
||||
uint DSoundCapture::availableSamples()
|
||||
{
|
||||
if(!mDevice->Connected.load(std::memory_order_acquire))
|
||||
return static_cast<uint>(mRing->readSpace());
|
||||
|
||||
const uint FrameSize{mDevice->frameSizeFromFmt()};
|
||||
const DWORD BufferBytes{mBufferBytes};
|
||||
const DWORD LastCursor{mCursor};
|
||||
|
||||
DWORD ReadCursor{};
|
||||
void *ReadPtr1{}, *ReadPtr2{};
|
||||
DWORD ReadCnt1{}, ReadCnt2{};
|
||||
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
|
||||
if(!NumBytes) return static_cast<uint>(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 = ReadCursor;
|
||||
}
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("update failed: 0x%08lx\n", hr);
|
||||
mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
|
||||
}
|
||||
|
||||
return static_cast<uint>(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); }
|
||||
|
||||
std::string DSoundBackendFactory::probe(BackendType 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 BackendType::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 BackendType::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();
|
||||
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new DSoundPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new DSoundCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user