Skip to content

Commit 1f8c348

Browse files
generate downmixed stereo buffer for Windows (#668)
add stereo downmixing for Windows to apply if audio device is greater than 2 channels
1 parent ca5c3a7 commit 1f8c348

1 file changed

Lines changed: 280 additions & 24 deletions

File tree

input/winscap.c

Lines changed: 280 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
#include <audioclient.h>
88
#include <fcntl.h>
99
#include <io.h>
10+
#include <stdbool.h>
11+
#include <stddef.h>
12+
#include <stdint.h>
1013
#include <stdio.h>
1114
#include <stdlib.h>
1215
#include <windows.h>
@@ -15,6 +18,175 @@
1518

1619
#include "common.h"
1720

21+
static void make_stereo_waveformatex(const WAVEFORMATEX *orig, WAVEFORMATEX *stereo) {
22+
*stereo = *orig;
23+
stereo->wFormatTag = WAVE_FORMAT_PCM;
24+
stereo->nChannels = 2;
25+
stereo->wBitsPerSample = 16;
26+
stereo->nBlockAlign = stereo->nChannels * (stereo->wBitsPerSample / 8);
27+
stereo->nAvgBytesPerSec = stereo->nSamplesPerSec * stereo->nBlockAlign;
28+
stereo->cbSize = 0;
29+
}
30+
31+
// Downmix interleaved 16-bit PCM to stereo
32+
static void downmix_to_stereo_s16(const int16_t *src, int16_t *dst, size_t frames, int channels) {
33+
size_t i;
34+
for (i = 0; i < frames; ++i) {
35+
int left = 0, right = 0;
36+
if (channels >= 2) {
37+
left += src[i * channels + 0];
38+
right += src[i * channels + 1];
39+
}
40+
if (channels >= 3) {
41+
left += (int)(src[i * channels + 2] * 0.7f);
42+
right += (int)(src[i * channels + 2] * 0.7f);
43+
}
44+
if (channels >= 5) {
45+
left += (int)(src[i * channels + 4] * 0.7f);
46+
right += (int)(src[i * channels + 5] * 0.7f);
47+
}
48+
if (channels >= 7) {
49+
left += (int)(src[i * channels + 6] * 0.7f);
50+
right += (int)(src[i * channels + 7] * 0.7f);
51+
}
52+
if (left > 32767)
53+
left = 32767;
54+
if (left < -32768)
55+
left = -32768;
56+
if (right > 32767)
57+
right = 32767;
58+
if (right < -32768)
59+
right = -32768;
60+
dst[i * 2 + 0] = (int16_t)left;
61+
dst[i * 2 + 1] = (int16_t)right;
62+
}
63+
}
64+
65+
// Helper to convert 3 bytes (little-endian) to signed 32-bit int
66+
static inline int32_t s24_to_s32(const uint8_t *p) {
67+
int32_t val = p[0] | (p[1] << 8) | (p[2] << 16);
68+
// Sign-extend if negative
69+
if (val & 0x800000)
70+
val |= ~0xFFFFFF;
71+
return val;
72+
}
73+
74+
// Downmix interleaved 24-bit PCM to stereo
75+
static void downmix_to_stereo_s24(const uint8_t *src, int16_t *dst, size_t frames, int channels) {
76+
size_t i;
77+
for (i = 0; i < frames; ++i) {
78+
int left = 0, right = 0;
79+
80+
const uint8_t *frame = src + i * channels * 3;
81+
if (channels >= 2) {
82+
left += s24_to_s32(frame + 0 * 3) >> 8;
83+
right += s24_to_s32(frame + 1 * 3) >> 8;
84+
}
85+
if (channels >= 3) {
86+
int32_t c = s24_to_s32(frame + 2 * 3) >> 8;
87+
left += (int)(c * 0.7f);
88+
right += (int)(c * 0.7f);
89+
}
90+
if (channels >= 5) {
91+
int32_t c4 = s24_to_s32(frame + 4 * 3) >> 8;
92+
int32_t c5 = s24_to_s32(frame + 5 * 3) >> 8;
93+
left += (int)(c4 * 0.7f);
94+
right += (int)(c5 * 0.7f);
95+
}
96+
if (channels >= 7) {
97+
int32_t c6 = s24_to_s32(frame + 6 * 3) >> 8;
98+
int32_t c7 = s24_to_s32(frame + 7 * 3) >> 8;
99+
left += (int)(c6 * 0.7f);
100+
right += (int)(c7 * 0.7f);
101+
}
102+
// Clip to int16_t
103+
if (left > 32767)
104+
left = 32767;
105+
if (left < -32768)
106+
left = -32768;
107+
if (right > 32767)
108+
right = 32767;
109+
if (right < -32768)
110+
right = -32768;
111+
dst[i * 2 + 0] = (int16_t)left;
112+
dst[i * 2 + 1] = (int16_t)right;
113+
}
114+
}
115+
116+
// Downmix interleaved 32-bit PCM to stereo
117+
static void downmix_to_stereo_s32(const int32_t *src, int16_t *dst, size_t frames, int channels) {
118+
size_t i;
119+
for (i = 0; i < frames; ++i) {
120+
int64_t left = 0, right = 0;
121+
if (channels >= 2) {
122+
left += src[i * channels + 0];
123+
right += src[i * channels + 1];
124+
}
125+
if (channels >= 3) {
126+
left += (int64_t)(src[i * channels + 2] * 0.7f);
127+
right += (int64_t)(src[i * channels + 2] * 0.7f);
128+
}
129+
if (channels >= 5) {
130+
left += (int64_t)(src[i * channels + 4] * 0.7f);
131+
right += (int64_t)(src[i * channels + 5] * 0.7f);
132+
}
133+
if (channels >= 7) {
134+
left += (int64_t)(src[i * channels + 6] * 0.7f);
135+
right += (int64_t)(src[i * channels + 7] * 0.7f);
136+
}
137+
// Convert from 32-bit to 16-bit with clipping
138+
left >>= 16;
139+
right >>= 16;
140+
if (left > 32767)
141+
left = 32767;
142+
if (left < -32768)
143+
left = -32768;
144+
if (right > 32767)
145+
right = 32767;
146+
if (right < -32768)
147+
right = -32768;
148+
dst[i * 2 + 0] = (int16_t)left;
149+
dst[i * 2 + 1] = (int16_t)right;
150+
}
151+
}
152+
153+
// Downmix interleaved 32-bit float to stereo
154+
static void downmix_to_stereo_f32(const float *src, int16_t *dst, size_t frames, int channels) {
155+
size_t i;
156+
for (i = 0; i < frames; ++i) {
157+
float left = 0.0f, right = 0.0f;
158+
if (channels >= 2) {
159+
left += src[i * channels + 0];
160+
right += src[i * channels + 1];
161+
}
162+
if (channels >= 3) {
163+
left += src[i * channels + 2] * 0.7f;
164+
right += src[i * channels + 2] * 0.7f;
165+
}
166+
if (channels >= 5) {
167+
left += src[i * channels + 4] * 0.7f;
168+
right += src[i * channels + 5] * 0.7f;
169+
}
170+
if (channels >= 7) {
171+
left += src[i * channels + 6] * 0.7f;
172+
right += src[i * channels + 7] * 0.7f;
173+
}
174+
// Convert from float [-1.0, 1.0] to int16_t
175+
int l = (int)(left * 32767.0f);
176+
int r = (int)(right * 32767.0f);
177+
if (l > 32767)
178+
l = 32767;
179+
if (l < -32768)
180+
l = -32768;
181+
if (r > 32767)
182+
r = 32767;
183+
if (r < -32768)
184+
r = -32768;
185+
dst[i * 2 + 0] = (int16_t)l;
186+
dst[i * 2 + 1] = (int16_t)r;
187+
}
188+
}
189+
18190
// IID_IMMNotificationClient definition for linking
19191

20192
#if !defined(__MINGW32__) && !defined(__MINGW64__)
@@ -132,6 +304,45 @@ struct {
132304
{AUDCLNT_E_UNSUPPORTED_FORMAT, L"Requested sound format unsupported"},
133305
};
134306

307+
void write_silent_frame(struct audio_data *audio, IAudioCaptureClient *pCapture,
308+
UINT32 numFramesAvailable, UINT32 packetLength) {
309+
// Send one silent frame to the spectrometer
310+
int silent_channels = audio->channels;
311+
int silent_bytes = silent_channels * sizeof(int16_t); // 16-bit PCM
312+
int16_t silence[2] = {0};
313+
314+
write_to_cava_input_buffers(silent_channels, (unsigned char *)silence, audio);
315+
pCapture->lpVtbl->ReleaseBuffer(pCapture, numFramesAvailable);
316+
pCapture->lpVtbl->GetNextPacketSize(pCapture, &packetLength);
317+
}
318+
319+
void process_multichannel(UINT32 numFramesAvailable, const WAVEFORMATEX format, const void *pData,
320+
struct audio_data *audio, IAudioCaptureClient *pCapture,
321+
UINT32 packetLength, WAVEFORMATEX stereo_format) {
322+
323+
int16_t *stereo_buffer = (int16_t *)malloc(numFramesAvailable * 2 * sizeof(int16_t));
324+
if (format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT && format.wBitsPerSample == 32) {
325+
downmix_to_stereo_f32((const float *)pData, stereo_buffer, numFramesAvailable,
326+
format.nChannels);
327+
} else if (format.wBitsPerSample == 32) {
328+
downmix_to_stereo_s32((const int32_t *)pData, stereo_buffer, numFramesAvailable,
329+
format.nChannels);
330+
} else if (format.wBitsPerSample == 24) {
331+
downmix_to_stereo_s24((const uint8_t *)pData, stereo_buffer, numFramesAvailable,
332+
format.nChannels);
333+
} else if (format.wBitsPerSample == 16) {
334+
downmix_to_stereo_s16((const int16_t *)pData, stereo_buffer, numFramesAvailable,
335+
format.nChannels);
336+
} else {
337+
// Unsupported format, handle error
338+
write_silent_frame(audio, pCapture, numFramesAvailable, packetLength);
339+
return;
340+
}
341+
write_to_cava_input_buffers(numFramesAvailable * stereo_format.nChannels,
342+
(unsigned char *)stereo_buffer, audio);
343+
free(stereo_buffer);
344+
}
345+
135346
void input_winscap(void *data) {
136347

137348
static const GUID CLSID_MMDeviceEnumerator = {0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4,
@@ -145,13 +356,25 @@ void input_winscap(void *data) {
145356

146357
struct audio_data *audio = (struct audio_data *)data;
147358
pthread_mutex_lock(&audio->lock);
148-
CoInitialize(0);
359+
360+
HRESULT hr = CoInitialize(0);
361+
if (FAILED(hr)) {
362+
fwprintf(stderr, L"CoInitialize failed: 0x%08lx\n", hr);
363+
pthread_mutex_unlock(&audio->lock);
364+
return;
365+
}
149366

150367
WAVEFORMATEX *wfx = NULL;
151368
WAVEFORMATEXTENSIBLE *wfx_ext = NULL;
152369
IMMDeviceEnumerator *pEnumerator = NULL;
153-
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
154-
&IID_IMMDeviceEnumerator, (void **)&pEnumerator);
370+
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
371+
(void **)&pEnumerator);
372+
if (FAILED(hr)) {
373+
fwprintf(stderr, L"Failed to create device enumerator: 0x%08lx\n", hr);
374+
CoUninitialize();
375+
pthread_mutex_unlock(&audio->lock);
376+
return;
377+
}
155378

156379
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
157380

@@ -172,9 +395,16 @@ void input_winscap(void *data) {
172395

173396
hr = pClient->lpVtbl->GetMixFormat(pClient, &wfx);
174397

398+
if (FAILED(hr) || wfx == NULL) {
399+
fwprintf(stderr, L"Failed to GetMixFormat for client\n");
400+
continue;
401+
}
402+
175403
HRESULT hrInit = pClient->lpVtbl->Initialize(pClient, AUDCLNT_SHAREMODE_SHARED,
176-
AUDCLNT_STREAMFLAGS_LOOPBACK,
404+
AUDCLNT_STREAMFLAGS_LOOPBACK |
405+
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
177406
16 * REFTIMES_PER_MILLISEC, 0, wfx, 0);
407+
178408
if (FAILED(hrInit)) {
179409
//_com_error err(hrInit);
180410

@@ -192,7 +422,19 @@ void input_winscap(void *data) {
192422
pClient->lpVtbl->Release(pClient);
193423
if (pDevice)
194424
pDevice->lpVtbl->Release(pDevice);
195-
WaitForSingleObject(hEvent, INFINITE);
425+
WaitForSingleObject(hEvent, INFINITE); // may freeze, need to test on spacial device
426+
continue;
427+
}
428+
429+
// use event handling instead of polling
430+
HRESULT hrEvent = pClient->lpVtbl->SetEventHandle(pClient, hEvent);
431+
if (FAILED(hrEvent)) {
432+
fwprintf(stderr, L"SetEventHandle failed: 0x%08lx\n", hrEvent);
433+
// Clean up resources as needed
434+
if (pClient)
435+
pClient->lpVtbl->Release(pClient);
436+
if (pDevice)
437+
pDevice->lpVtbl->Release(pDevice);
196438
continue;
197439
}
198440

@@ -205,25 +447,33 @@ void input_winscap(void *data) {
205447

206448
WAVEFORMATEX format = *wfx;
207449

208-
DWORD dwDelay =
209-
(DWORD)(((double)REFTIMES_PER_SEC * bufferFrameCount / format.nSamplesPerSec) /
210-
REFTIMES_PER_MILLISEC / 2);
211-
212-
LPBYTE pSilence = (LPBYTE)malloc(bufferFrameCount * format.nBlockAlign);
213-
214-
ZeroMemory(pSilence, bufferFrameCount * format.nBlockAlign);
215-
216450
pClient->lpVtbl->Start(pClient);
217451

218-
audio->channels = format.nChannels;
219-
audio->rate = format.nSamplesPerSec;
220-
audio->format = format.wBitsPerSample;
221-
if (format.wBitsPerSample == 32)
222-
audio->IEEE_FLOAT = 1;
452+
WAVEFORMATEX stereo_format;
453+
make_stereo_waveformatex(&format, &stereo_format);
454+
455+
if (format.nChannels > 2) {
456+
audio->channels = 2;
457+
audio->rate = stereo_format.nSamplesPerSec;
458+
audio->format = stereo_format.wBitsPerSample;
459+
audio->IEEE_FLOAT = 0;
460+
} else {
461+
audio->channels = format.nChannels;
462+
audio->rate = format.nSamplesPerSec;
463+
audio->format = format.wBitsPerSample;
464+
if (format.wBitsPerSample == 32)
465+
audio->IEEE_FLOAT = 1;
466+
}
467+
223468
pthread_mutex_unlock(&audio->lock);
469+
224470
// deviceChanged
225471
while (!audio->terminate) {
226-
Sleep(dwDelay);
472+
DWORD waitResult = WaitForSingleObject(hEvent, INFINITE);
473+
if (waitResult != WAIT_OBJECT_0) {
474+
// Handle error or termination
475+
continue;
476+
}
227477

228478
UINT32 packetLength;
229479
HRESULT hrNext = pCapture->lpVtbl->GetNextPacketSize(pCapture, &packetLength);
@@ -250,19 +500,25 @@ void input_winscap(void *data) {
250500

251501
pCapture->lpVtbl->GetBuffer(pCapture, &pData, &numFramesAvailable, &flags, 0, 0);
252502

253-
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
254-
pData = pSilence;
503+
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
504+
write_silent_frame(audio, pCapture, numFramesAvailable, packetLength);
505+
continue;
506+
}
255507

256-
write_to_cava_input_buffers(numFramesAvailable * format.nChannels,
257-
(unsigned char *)pData, audio);
508+
if (format.nChannels > 2) {
509+
process_multichannel(numFramesAvailable, format, pData, audio, pCapture,
510+
packetLength, stereo_format);
511+
} else {
512+
write_to_cava_input_buffers(numFramesAvailable * format.nChannels,
513+
(unsigned char *)pData, audio);
514+
}
258515

259516
pCapture->lpVtbl->ReleaseBuffer(pCapture, numFramesAvailable);
260517
pCapture->lpVtbl->GetNextPacketSize(pCapture, &packetLength);
261518
}
262519
}
263520
deviceChanged = FALSE;
264521
pClient->lpVtbl->Stop(pClient);
265-
free(pSilence);
266522
if (pCapture)
267523
pCapture->lpVtbl->Release(pCapture);
268524
if (pClient)

0 commit comments

Comments
 (0)