Skip to content

Commit 06faf71

Browse files
committed
Implement CUBEB_STREAM_PREF_EXCLUSIVE
1 parent 57b4d6b commit 06faf71

5 files changed

Lines changed: 131 additions & 22 deletions

File tree

include/cubeb/cubeb.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,16 @@ enum {
220220

221221
/** Miscellaneous stream preferences. */
222222
typedef enum {
223-
CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
224-
CUBEB_STREAM_PREF_LOOPBACK = 0x01 /**< Request a loopback stream. Should be
225-
specified on the input params and an
226-
output device to loopback from should
227-
be passed in place of an input device. */
223+
CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
224+
CUBEB_STREAM_PREF_LOOPBACK = 0x01, /**< Request a loopback stream. Should be
225+
specified on the input params and an
226+
output device to loopback from should
227+
be passed in place of an input device. */
228+
CUBEB_STREAM_PREF_EXCLUSIVE = 0x02 /**< (Windows / WASAPI only) Request that
229+
this stream is run in exclusive mode
230+
which results in lower latency but
231+
doesn't allow other applications to
232+
use the same device */
228233
} cubeb_stream_prefs;
229234

230235
/** Stream format initialization parameters. */

src/cubeb.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,11 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
309309
return r;
310310
}
311311

312+
if ((output_stream_params && output_stream_params->prefs) & CUBEB_STREAM_PREF_EXCLUSIVE || (input_stream_params && input_stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)) {
313+
if (strcmp(cubeb_get_backend_id(context), "wasapi") != 0)
314+
return CUBEB_ERROR_NOT_SUPPORTED;
315+
}
316+
312317
r = context->ops->stream_init(context, stream, stream_name,
313318
input_device,
314319
input_stream_params,

src/cubeb_wasapi.cpp

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -524,9 +524,20 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
524524

525525
int wasapi_stream_reset_default_device(cubeb_stream * stm);
526526

527+
/* Helper for making get_input_buffer work in exclusive mode */
528+
HRESULT get_next_packet_size(cubeb_stream * stm, PUINT32 next)
529+
{
530+
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE) {
531+
*next = stm->input_buffer_frame_count;
532+
return S_OK;
533+
} else {
534+
return stm->capture_client->GetNextPacketSize(next);
535+
}
536+
}
537+
527538
/* This helper grabs all the frames available from a capture client, put them in
528539
* linear_input_buffer. linear_input_buffer should be cleared before the
529-
* callback exits. This helper does not work with exclusive mode streams. */
540+
* callback exits. */
530541
bool get_input_buffer(cubeb_stream * stm)
531542
{
532543
XASSERT(has_input(stm));
@@ -543,9 +554,9 @@ bool get_input_buffer(cubeb_stream * stm)
543554
// single packet each time. However, if we're pulling from the stream we may
544555
// need to grab multiple packets worth of frames that have accumulated (so
545556
// need a loop).
546-
for (hr = stm->capture_client->GetNextPacketSize(&next);
557+
for (hr = get_next_packet_size(stm, &next);
547558
next > 0;
548-
hr = stm->capture_client->GetNextPacketSize(&next)) {
559+
hr = get_next_packet_size(stm, &next)) {
549560
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
550561
// Application can recover from this error. More info
551562
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
@@ -619,7 +630,11 @@ bool get_input_buffer(cubeb_stream * stm)
619630
LOG("FAILED to release intput buffer");
620631
return false;
621632
}
622-
offset += input_stream_samples;
633+
634+
offset += input_stream_samples;
635+
636+
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
637+
break;
623638
}
624639

625640
XASSERT(stm->linear_input_buffer->length() >= offset);
@@ -1297,9 +1312,13 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten
12971312
return CUBEB_ERROR;
12981313
}
12991314

1300-
/* The second parameter is for exclusive mode, that we don't use. */
13011315
REFERENCE_TIME default_period;
1302-
hr = client->GetDevicePeriod(&default_period, NULL);
1316+
1317+
if (params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
1318+
hr = client->GetDevicePeriod(NULL, &default_period);
1319+
else
1320+
hr = client->GetDevicePeriod(&default_period, NULL);
1321+
13031322
if (FAILED(hr)) {
13041323
LOG("Could not get device period: %lx", hr);
13051324
return CUBEB_ERROR;
@@ -1391,7 +1410,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAV
13911410

13921411
/* Check if wasapi will accept our channel layout request. */
13931412
WAVEFORMATEX * closest;
1394-
HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
1413+
HRESULT hr = audio_client->IsFormatSupported(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
13951414
mix_format.get(),
13961415
&closest);
13971416
if (hr == S_FALSE) {
@@ -1518,11 +1537,27 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
15181537
mix_params->channels = mix_format->nChannels;
15191538
mix_params->layout = mask_to_channel_layout(mix_format.get());
15201539

1521-
LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
1522-
stream_params->format, stream_params->rate, stream_params->channels,
1523-
stream_params->layout,
1524-
mix_params->format, mix_params->rate, mix_params->channels,
1525-
mix_params->layout);
1540+
if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) {
1541+
LOG("Stream using undefined layout! Any mixing may be unpredictable!\n");
1542+
} else if (mix_format->nChannels != mix_params->channels) {
1543+
// The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be
1544+
// different from the mix_params->channels. 6 channel ouput with stereo
1545+
// layout is acceptable in Windows. If this happens, it should not downmix
1546+
// audio according to layout.
1547+
LOG("Channel count is different from the layout standard!\n");
1548+
}
1549+
1550+
1551+
if (stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
1552+
LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
1553+
stream_params->format, stream_params->rate, stream_params->channels,
1554+
mix_params->format, mix_params->rate, mix_params->channels);
1555+
else
1556+
LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
1557+
stream_params->format, stream_params->rate, stream_params->channels,
1558+
stream_params->layout,
1559+
mix_params->format, mix_params->rate, mix_params->channels,
1560+
mix_params->layout);
15261561

15271562
DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
15281563

@@ -1534,12 +1569,51 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
15341569
flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
15351570
}
15361571

1537-
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
1572+
hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
15381573
flags,
15391574
frames_to_hns(stm, stm->latency),
1540-
0,
1575+
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? frames_to_hns(stm, stm->latency) : 0,
15411576
mix_format.get(),
15421577
NULL);
1578+
1579+
// Try to realign the buffer size
1580+
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
1581+
{
1582+
LOG("Buffer size misaligned, trying to realign");
1583+
1584+
audio_client.reset();
1585+
1586+
hr = device->Activate(__uuidof(IAudioClient),
1587+
CLSCTX_INPROC_SERVER,
1588+
NULL, audio_client.receive_vpp());
1589+
1590+
if (FAILED(hr)) {
1591+
LOG("Unable to reactivate audio client for %s: %lx", DIRECTION_NAME, hr);
1592+
return CUBEB_ERROR;
1593+
}
1594+
1595+
REFERENCE_TIME realigned_time = frames_to_hns(stm, stm->latency) * *buffer_frame_count / mix_format->nSamplesPerSec + 0.5;
1596+
1597+
1598+
hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
1599+
flags,
1600+
realigned_time,
1601+
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? realigned_time : 0,
1602+
mix_format.get(),
1603+
NULL);
1604+
1605+
if (FAILED(hr)) {
1606+
LOG("Unable to initialize realigned audio client for %s: %lx.", DIRECTION_NAME, hr);
1607+
return CUBEB_ERROR;
1608+
}
1609+
}
1610+
1611+
if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT)
1612+
{
1613+
LOG("The requested format is not supported by the current device.");
1614+
return CUBEB_ERROR_INVALID_FORMAT;
1615+
}
1616+
15431617
if (FAILED(hr)) {
15441618
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
15451619
return CUBEB_ERROR;
@@ -2105,6 +2179,10 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
21052179
return CUBEB_ERROR;
21062180
}
21072181

2182+
// Exclusive mode doesn't support setting the volume
2183+
if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
2184+
return CUBEB_ERROR_NOT_SUPPORTED;
2185+
21082186
if (stream_set_volume(stm, volume) != CUBEB_OK) {
21092187
return CUBEB_ERROR;
21102188
}

test/common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#endif
1414
#include <objbase.h>
1515
#include <windows.h>
16+
#include <objbase.h>
1617
#else
1718
#include <unistd.h>
1819
#endif
@@ -93,6 +94,11 @@ void print_log(const char * msg, ...)
9394
* override. */
9495
int common_init(cubeb ** ctx, char const * ctx_name)
9596
{
97+
#ifdef _WIN32
98+
// Exclusive mode needs this
99+
CoInitialize(NULL);
100+
#endif
101+
96102
int r;
97103
char const * backend;
98104
char const * ctx_backend;

test/test_audio.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ int supports_channel_count(string backend_id, int nchannels)
9494
(backend_id != "opensl" && backend_id != "audiotrack");
9595
}
9696

97-
int run_test(int num_channels, int sampling_rate, int is_float)
97+
int run_test(int num_channels, int sampling_rate, int is_float, bool exclusive=false)
9898
{
9999
int r = CUBEB_OK;
100100

@@ -123,13 +123,18 @@ int run_test(int num_channels, int sampling_rate, int is_float)
123123
params.rate = sampling_rate;
124124
params.channels = num_channels;
125125
params.layout = CUBEB_LAYOUT_UNDEFINED;
126-
params.prefs = CUBEB_STREAM_PREF_NONE;
126+
params.prefs = exclusive ? CUBEB_STREAM_PREF_EXCLUSIVE : CUBEB_STREAM_PREF_NONE;
127127

128128
synth_state synth(params.channels, params.rate);
129129

130130
cubeb_stream *stream = NULL;
131131
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
132132
4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
133+
if (r == CUBEB_ERROR_INVALID_FORMAT)
134+
{
135+
fprintf(stderr, "Format not supported. Not treating as a failure.\n");
136+
return CUBEB_OK;
137+
}
133138
if (r != CUBEB_OK) {
134139
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
135140
return r;
@@ -172,14 +177,19 @@ int run_panning_volume_test(int is_float)
172177
params.rate = 44100;
173178
params.channels = 2;
174179
params.layout = CUBEB_LAYOUT_STEREO;
175-
params.prefs = CUBEB_STREAM_PREF_NONE;
180+
params.prefs = CUBEB_STREAM_PREF_EXCLUSIVE;
176181

177182
synth_state synth(params.channels, params.rate);
178183

179184
cubeb_stream *stream = NULL;
180185
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
181186
4096, is_float ? &data_cb<float> : &data_cb<short>,
182187
state_cb_audio, &synth);
188+
if (r == CUBEB_ERROR_INVALID_FORMAT)
189+
{
190+
fprintf(stderr, "Format not supported. Not treating as a failure.\n");
191+
return CUBEB_OK;
192+
}
183193
if (r != CUBEB_OK) {
184194
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
185195
return r;
@@ -248,6 +258,11 @@ TEST(cubeb, run_channel_rate_test)
248258
fprintf(stderr, "--------------------------\n");
249259
ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
250260
ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
261+
#ifdef _WIN32
262+
// Run exclusive mdoe tests on Windows
263+
ASSERT_EQ(run_test(channels, freq, 0, true), CUBEB_OK);
264+
ASSERT_EQ(run_test(channels, freq, 1, true), CUBEB_OK);
265+
#endif
251266
}
252267
}
253268
}

0 commit comments

Comments
 (0)