CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/UWP/XAudioSoundStream.cpp
Views: 1401
1
#include "pch.h"
2
3
#include <XAudio2.h>
4
5
#include <algorithm>
6
#include <cstdint>
7
8
#include "Common/Log.h"
9
#include "Common/Thread/ThreadUtil.h"
10
#include "XAudioSoundStream.h"
11
12
#include <process.h>
13
14
const size_t BUFSIZE = 32 * 1024;
15
16
class XAudioBackend : public WindowsAudioBackend {
17
public:
18
XAudioBackend();
19
~XAudioBackend() override;
20
21
bool Init(HWND window, StreamCallback callback, int sampleRate) override; // If fails, can safely delete the object
22
int GetSampleRate() const override { return sampleRate_; }
23
24
private:
25
bool RunSound();
26
bool CreateBuffer();
27
void PollLoop();
28
29
StreamCallback callback_ = nullptr;
30
31
IXAudio2 *xaudioDevice = nullptr;
32
IXAudio2MasteringVoice *xaudioMaster = nullptr;
33
IXAudio2SourceVoice *xaudioVoice = nullptr;
34
35
int sampleRate_ = 0;
36
37
char realtimeBuffer_[BUFSIZE]{};
38
uint32_t cursor_ = 0;
39
40
HANDLE thread_ = 0;
41
HANDLE exitEvent_ = 0;
42
43
bool exit = false;
44
};
45
46
// TODO: Get rid of this
47
static XAudioBackend *g_dsound;
48
49
XAudioBackend::XAudioBackend() {
50
exitEvent_ = CreateEvent(nullptr, true, true, L"");
51
}
52
53
inline int RoundDown128(int x) {
54
return x & (~127);
55
}
56
57
bool XAudioBackend::CreateBuffer() {
58
if FAILED(xaudioDevice->CreateMasteringVoice(&xaudioMaster, 2, sampleRate_, 0, 0, NULL))
59
return false;
60
61
WAVEFORMATEX waveFormat;
62
waveFormat.cbSize = sizeof(waveFormat);
63
waveFormat.nAvgBytesPerSec = sampleRate_ * 4;
64
waveFormat.nBlockAlign = 4;
65
waveFormat.nChannels = 2;
66
waveFormat.nSamplesPerSec = sampleRate_;
67
waveFormat.wBitsPerSample = 16;
68
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
69
70
if FAILED(xaudioDevice->CreateSourceVoice(&xaudioVoice, &waveFormat, 0, 1.0, nullptr, nullptr, nullptr))
71
return false;
72
73
xaudioVoice->SetFrequencyRatio(1.0);
74
return true;
75
}
76
77
bool XAudioBackend::RunSound() {
78
if FAILED(XAudio2Create(&xaudioDevice, 0, XAUDIO2_DEFAULT_PROCESSOR)) {
79
xaudioDevice = NULL;
80
return false;
81
}
82
83
XAUDIO2_DEBUG_CONFIGURATION dbgCfg;
84
ZeroMemory(&dbgCfg, sizeof(dbgCfg));
85
dbgCfg.TraceMask = XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL;
86
//dbgCfg.BreakMask = XAUDIO2_LOG_ERRORS;
87
xaudioDevice->SetDebugConfiguration(&dbgCfg);
88
89
if (!CreateBuffer()) {
90
xaudioDevice->Release();
91
xaudioDevice = NULL;
92
return false;
93
}
94
95
cursor_ = 0;
96
97
if FAILED(xaudioVoice->Start(0, XAUDIO2_COMMIT_NOW)) {
98
xaudioDevice->Release();
99
xaudioDevice = NULL;
100
return false;
101
}
102
103
thread_ = (HANDLE)_beginthreadex(0, 0, [](void* param)
104
{
105
SetCurrentThreadName("XAudio2");
106
XAudioBackend *backend = (XAudioBackend *)param;
107
backend->PollLoop();
108
return 0U;
109
}, (void *)this, 0, 0);
110
SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
111
112
return true;
113
}
114
115
XAudioBackend::~XAudioBackend() {
116
if (!xaudioDevice)
117
return;
118
119
if (!xaudioVoice)
120
return;
121
122
exit = true;
123
WaitForSingleObject(exitEvent_, INFINITE);
124
CloseHandle(exitEvent_);
125
126
xaudioDevice->Release();
127
}
128
129
bool XAudioBackend::Init(HWND window, StreamCallback _callback, int sampleRate) {
130
callback_ = _callback;
131
sampleRate_ = sampleRate;
132
return RunSound();
133
}
134
135
void XAudioBackend::PollLoop() {
136
ResetEvent(exitEvent_);
137
138
while (!exit) {
139
XAUDIO2_VOICE_STATE state;
140
xaudioVoice->GetState(&state);
141
142
// TODO: Still plenty of tuning to do here.
143
// 4 seems to work fine.
144
if (state.BuffersQueued > 4) {
145
Sleep(1);
146
continue;
147
}
148
149
uint32_t bytesRequired = (sampleRate_ * 4) / 100;
150
151
uint32_t bytesLeftInBuffer = BUFSIZE - cursor_;
152
uint32_t readCount = std::min(bytesRequired, bytesLeftInBuffer);
153
154
// realtimeBuffer_ is just used as a ring of scratch space to be submitted, since SubmitSourceBuffer doesn't
155
// take ownership of the data. It needs to be big enough to fit the max number of buffers we check for
156
// above, which it is, easily.
157
158
int stereoSamplesRendered = (*callback_)((short*)&realtimeBuffer_[cursor_], readCount / 4, sampleRate_);
159
int numBytesRendered = 2 * sizeof(short) * stereoSamplesRendered;
160
161
XAUDIO2_BUFFER xaudioBuffer{};
162
xaudioBuffer.pAudioData = (const BYTE*)&realtimeBuffer_[cursor_];
163
xaudioBuffer.AudioBytes = numBytesRendered;
164
165
if FAILED(xaudioVoice->SubmitSourceBuffer(&xaudioBuffer, NULL)) {
166
WARN_LOG(Log::Audio, "XAudioBackend: Failed writing bytes");
167
}
168
cursor_ += numBytesRendered;
169
if (cursor_ >= BUFSIZE) {
170
cursor_ = 0;
171
bytesLeftInBuffer = BUFSIZE;
172
}
173
}
174
175
SetEvent(exitEvent_);
176
}
177
178
WindowsAudioBackend *CreateAudioBackend(AudioBackendType type) {
179
// Only one type available on UWP.
180
return new XAudioBackend();
181
}
182
183