mirror of https://github.com/axmolengine/axmol.git
435 lines
12 KiB
C++
435 lines
12 KiB
C++
/**
|
|
* 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"
|
|
|
|
#ifdef _WIN32
|
|
# define WIN32_LEAN_AND_MEAN 1
|
|
# include <Windows.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "alc/backends/xaudio2.h"
|
|
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "almalloc.h"
|
|
#include "alnumeric.h"
|
|
#include "core/device.h"
|
|
#include "core/logging.h"
|
|
|
|
#include <xaudio2.h>
|
|
|
|
#ifdef _WIN32
|
|
# define DEVNAME_PREFIX "OpenAL Soft on "
|
|
#else
|
|
# define DEVNAME_PREFIX ""
|
|
#endif
|
|
|
|
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
|
|
|
|
class VoiceCallback;
|
|
|
|
struct XAudio2Backend final : public BackendBase
|
|
{
|
|
XAudio2Backend(DeviceBase* device) noexcept;
|
|
~XAudio2Backend() override;
|
|
|
|
IXAudio2* audioengine;
|
|
IXAudio2MasteringVoice* masteringvoice;
|
|
IXAudio2SourceVoice* source;
|
|
|
|
VoiceCallback* callback;
|
|
void mix(int len) noexcept;
|
|
|
|
void open(const char* name) override;
|
|
bool reset() override;
|
|
void start() override;
|
|
void stop() override;
|
|
|
|
uint mDeviceID{0};
|
|
uint mFrameSize{0};
|
|
|
|
uint mFrequency{0u};
|
|
DevFmtChannels mFmtChans{};
|
|
DevFmtType mFmtType{};
|
|
uint mUpdateSize{0u};
|
|
|
|
BYTE* mBuffer{nullptr};
|
|
size_t mBufferSize{0};
|
|
|
|
DEF_NEWDEL(XAudio2Backend)
|
|
};
|
|
|
|
class VoiceCallback : public IXAudio2VoiceCallback
|
|
{
|
|
public:
|
|
XAudio2Backend* master;
|
|
VoiceCallback(XAudio2Backend* backend) : master(backend) {}
|
|
~VoiceCallback() {}
|
|
|
|
STDMETHOD_(void, OnStreamEnd()) { ; }
|
|
STDMETHOD_(void, OnVoiceProcessingPassEnd(THIS)) {}
|
|
STDMETHOD_(void, OnVoiceProcessingPassStart(THIS_ UINT32 SamplesRequired)) { master->mix(SamplesRequired); }
|
|
STDMETHOD_(void, OnBufferEnd(THIS_ void* pBufferContext)) {}
|
|
STDMETHOD_(void, OnBufferStart(THIS_ void* pBufferContext)) {}
|
|
STDMETHOD_(void, OnLoopEnd(THIS_ void* pBufferContext)) {}
|
|
STDMETHOD_(void, OnVoiceError(THIS_ void* pBufferContext, HRESULT Error)) {}
|
|
};
|
|
|
|
XAudio2Backend::XAudio2Backend(DeviceBase* device) noexcept : BackendBase{device}
|
|
{
|
|
XAudio2Create(&audioengine);
|
|
HRESULT hr = audioengine->CreateMasteringVoice(&masteringvoice);
|
|
source = nullptr;
|
|
callback = new VoiceCallback(this);
|
|
}
|
|
|
|
XAudio2Backend::~XAudio2Backend()
|
|
{
|
|
delete callback;
|
|
|
|
if (masteringvoice)
|
|
masteringvoice->DestroyVoice();
|
|
|
|
if (source)
|
|
source->DestroyVoice();
|
|
|
|
if (mBuffer != NULL)
|
|
delete[] mBuffer;
|
|
|
|
audioengine->Release();
|
|
}
|
|
|
|
void XAudio2Backend::mix(int len) noexcept
|
|
{
|
|
const auto ulen = static_cast<unsigned int>(len);
|
|
assert((ulen % mFrameSize) == 0);
|
|
|
|
HRESULT hr;
|
|
size_t samples = ulen / mFrameSize;
|
|
size_t bufferRegionSize = samples * mFrameSize;
|
|
if (mBufferSize < ulen)
|
|
{
|
|
if (mBuffer != NULL)
|
|
delete[] mBuffer;
|
|
mBufferSize = bufferRegionSize;
|
|
mBuffer = new (std::nothrow) BYTE[mBufferSize];
|
|
}
|
|
|
|
mDevice->renderSamples(mBuffer, samples, mDevice->channelsFromFmt());
|
|
|
|
XAUDIO2_BUFFER bufferRegionToSubmit;
|
|
memset(&bufferRegionToSubmit, 0, sizeof(bufferRegionToSubmit));
|
|
bufferRegionToSubmit.AudioBytes = bufferRegionSize;
|
|
bufferRegionToSubmit.pAudioData = mBuffer;
|
|
|
|
hr = source->SubmitSourceBuffer(&bufferRegionToSubmit);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("SubmitSourceBuffer() failed: 0x%08lx\n", hr);
|
|
}
|
|
}
|
|
|
|
void XAudio2Backend::open(const char* name)
|
|
{
|
|
|
|
WAVEFORMATEX wf;
|
|
|
|
wf.wFormatTag = WAVE_FORMAT_PCM;
|
|
wf.nChannels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
|
|
wf.wBitsPerSample = 16;
|
|
wf.nSamplesPerSec = static_cast<int>(mDevice->Frequency);
|
|
wf.nAvgBytesPerSec = wf.nChannels * wf.nSamplesPerSec * wf.wBitsPerSample / 8;
|
|
wf.nBlockAlign = wf.wBitsPerSample * wf.nChannels / 8;
|
|
wf.cbSize = 0;
|
|
|
|
audioengine->CreateSourceVoice(&source, &wf, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback, NULL, NULL);
|
|
|
|
DevFmtType devtype = DevFmtShort;
|
|
mFrameSize = BytesFromDevFmt(devtype) * wf.nChannels;
|
|
mFrequency = static_cast<uint>(wf.nSamplesPerSec);
|
|
mFmtChans = (wf.nChannels == 1) ? DevFmtMono : DevFmtStereo;
|
|
mFmtType = devtype;
|
|
mUpdateSize = 8192;
|
|
|
|
mDevice->DeviceName = name ? name : defaultDeviceName;
|
|
}
|
|
|
|
bool XAudio2Backend::reset()
|
|
{
|
|
mDevice->Frequency = mFrequency;
|
|
mDevice->FmtChans = mFmtChans;
|
|
mDevice->FmtType = mFmtType;
|
|
mDevice->UpdateSize = mUpdateSize;
|
|
mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
|
|
setDefaultWFXChannelOrder();
|
|
return true;
|
|
}
|
|
|
|
void XAudio2Backend::start()
|
|
{
|
|
if (source)
|
|
source->Start();
|
|
}
|
|
|
|
void XAudio2Backend::stop()
|
|
{
|
|
if (source)
|
|
source->Stop();
|
|
}
|
|
|
|
#include <chrono>
|
|
#include <ppltasks.h>
|
|
#include <mutex>
|
|
#include <Windows.Media.Capture.h>
|
|
#include <Windows.Media.MediaProperties.h>
|
|
#include <Windows.Storage.Streams.h>
|
|
using namespace Concurrency;
|
|
|
|
using namespace Windows::Media::Capture;
|
|
using namespace Windows::Media::MediaProperties;
|
|
using namespace Windows::Storage::Streams;
|
|
|
|
struct XAudio2Capture;
|
|
|
|
ref class XAudio2Delegates sealed
|
|
{
|
|
XAudio2Capture* capture;
|
|
internal : XAudio2Delegates(XAudio2Capture* cap) { capture = cap; };
|
|
|
|
public:
|
|
void OnFailed(Windows::Media::Capture::MediaCapture ^ sender,
|
|
Windows::Media::Capture::MediaCaptureFailedEventArgs ^ errorEventArgs);
|
|
};
|
|
|
|
// This is called XAudio2, because it is supposed to be UWP driver, but XAudio2 doesn't handle capture at all. We'll use
|
|
// MediaCapture instead
|
|
struct XAudio2Capture final : public BackendBase
|
|
{
|
|
XAudio2Capture(DeviceBase* device) noexcept;
|
|
~XAudio2Capture() override;
|
|
|
|
MediaCapture ^ capture { nullptr };
|
|
IRandomAccessStream ^ buffer { nullptr };
|
|
IInputStream ^ instream { nullptr };
|
|
XAudio2Delegates ^ delegates;
|
|
bool record{false};
|
|
|
|
void open(const char* name) override;
|
|
void start() override;
|
|
void stop() override;
|
|
void captureSamples(al::byte* buffer, uint samples) override;
|
|
uint availableSamples() override;
|
|
|
|
void failed();
|
|
|
|
BYTE mBuffer[8192];
|
|
size_t mSkip{0};
|
|
size_t mBufferSize{0};
|
|
size_t mFrameSize{0};
|
|
std::recursive_mutex mutex;
|
|
|
|
DEF_NEWDEL(XAudio2Capture)
|
|
};
|
|
|
|
void XAudio2Delegates::OnFailed(Windows::Media::Capture::MediaCapture ^ sender,
|
|
Windows::Media::Capture::MediaCaptureFailedEventArgs ^ errorEventArgs)
|
|
{
|
|
capture->failed();
|
|
}
|
|
|
|
XAudio2Capture::XAudio2Capture(DeviceBase* device) noexcept : BackendBase{device}
|
|
{
|
|
delegates = ref new XAudio2Delegates(this);
|
|
}
|
|
|
|
XAudio2Capture::~XAudio2Capture()
|
|
{
|
|
stop();
|
|
delegates = nullptr;
|
|
}
|
|
|
|
void XAudio2Capture::captureSamples(al::byte* buffer, uint samples)
|
|
{
|
|
int nsmp = availableSamples();
|
|
if (samples > nsmp)
|
|
samples = nsmp;
|
|
if (!samples)
|
|
return;
|
|
size_t tt = samples * mFrameSize;
|
|
mutex.lock();
|
|
memcpy(buffer, mBuffer + mSkip, tt);
|
|
memmove(mBuffer, mBuffer + mSkip + tt, mBufferSize - mSkip - tt);
|
|
mBufferSize -= tt;
|
|
mutex.unlock();
|
|
}
|
|
|
|
uint XAudio2Capture::availableSamples()
|
|
{
|
|
if (!record)
|
|
return 0;
|
|
mutex.lock();
|
|
size_t cur = std::max(mBufferSize - mSkip, (size_t)0) / mFrameSize;
|
|
size_t room = sizeof(mBuffer) - mBufferSize;
|
|
mutex.unlock();
|
|
if (room > 1024)
|
|
{
|
|
IBuffer ^ buf = ref new Buffer(room);
|
|
create_task(instream->ReadAsync(buf, room, InputStreamOptions::Partial)).then([=](IBuffer ^ ibuf) {
|
|
size_t blen = ibuf->Length;
|
|
auto reader = Windows::Storage::Streams::DataReader::FromBuffer(ibuf);
|
|
mutex.lock();
|
|
reader->ReadBytes(::Platform::ArrayReference<BYTE>(mBuffer + mBufferSize, blen));
|
|
mBufferSize += blen;
|
|
mutex.unlock();
|
|
});
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
void XAudio2Capture::failed()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
void XAudio2Capture::open(const char* name)
|
|
{
|
|
DevFmtType devtype = DevFmtShort;
|
|
mFrameSize = BytesFromDevFmt(devtype) * 2;
|
|
mDevice->DeviceName = name ? name : defaultDeviceName;
|
|
}
|
|
|
|
void XAudio2Capture::start()
|
|
{
|
|
if (!record)
|
|
{
|
|
buffer = ref new InMemoryRandomAccessStream();
|
|
if (capture != nullptr)
|
|
{
|
|
capture->StopRecordAsync();
|
|
}
|
|
try
|
|
{
|
|
MediaCaptureInitializationSettings ^ settings = ref new MediaCaptureInitializationSettings();
|
|
settings->StreamingCaptureMode = StreamingCaptureMode::Audio;
|
|
settings->AudioDeviceId = Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId(
|
|
Windows::Media::Devices::AudioDeviceRole::Default);
|
|
|
|
capture = ref new MediaCapture();
|
|
create_task(capture->InitializeAsync(settings))
|
|
.then([this](void) {
|
|
capture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(
|
|
delegates, &XAudio2Delegates::OnFailed);
|
|
mBufferSize = 0;
|
|
mSkip = 82;
|
|
return capture->StartRecordToStreamAsync(
|
|
MediaEncodingProfile::CreateWav(AudioEncodingQuality::High), buffer);
|
|
})
|
|
.then([this](void) {
|
|
instream = buffer->GetInputStreamAt(0);
|
|
record = true;
|
|
})
|
|
.then([this](task<void> t) {
|
|
try
|
|
{
|
|
t.get();
|
|
}
|
|
catch (Platform::Exception ^ e)
|
|
{
|
|
instream = nullptr;
|
|
buffer = nullptr;
|
|
capture = nullptr;
|
|
record = false;
|
|
}
|
|
});
|
|
while (buffer && (!record))
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
catch (Platform::Exception ^ e)
|
|
{
|
|
instream = nullptr;
|
|
buffer = nullptr;
|
|
capture = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XAudio2Capture::stop()
|
|
{
|
|
if (record)
|
|
{
|
|
if (capture)
|
|
{
|
|
create_task(capture->StopRecordAsync()).then([=]() {
|
|
record = false;
|
|
buffer = nullptr;
|
|
capture = nullptr;
|
|
instream = nullptr;
|
|
});
|
|
while (buffer)
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
else
|
|
record = false;
|
|
}
|
|
}
|
|
|
|
BackendFactory& XAudio2BackendFactory::getFactory()
|
|
{
|
|
static XAudio2BackendFactory factory{};
|
|
return factory;
|
|
}
|
|
|
|
bool XAudio2BackendFactory::init()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool XAudio2BackendFactory::querySupport(BackendType type)
|
|
{
|
|
return type == BackendType::Playback || type == BackendType::Capture;
|
|
}
|
|
|
|
std::string XAudio2BackendFactory::probe(BackendType type)
|
|
{
|
|
std::string outnames;
|
|
|
|
/* Includes null char. */
|
|
outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
|
|
|
|
return outnames;
|
|
}
|
|
|
|
BackendPtr XAudio2BackendFactory::createBackend(DeviceBase* device, BackendType type)
|
|
{
|
|
if (type == BackendType::Playback)
|
|
return BackendPtr{new XAudio2Backend{device}};
|
|
|
|
if (type == BackendType::Capture)
|
|
return BackendPtr{new XAudio2Capture{device}};
|
|
|
|
return nullptr;
|
|
}
|