Skip to content

Commit 51d2fa5

Browse files
committed
Implement CUBEB_STREAM_PREF_EXCLUSIVE
1 parent 475d97f commit 51d2fa5

5 files changed

Lines changed: 114 additions & 16 deletions

File tree

include/cubeb/cubeb.h

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

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

227232
/** Stream format initialization parameters. */

src/cubeb.c

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

321+
if ((output_stream_params && output_stream_params->prefs) & CUBEB_STREAM_PREF_EXCLUSIVE || (input_stream_params && input_stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)) {
322+
if (strcmp(cubeb_get_backend_id(context), "wasapi") != 0)
323+
return CUBEB_ERROR_NOT_SUPPORTED;
324+
}
325+
321326
r = context->ops->stream_init(context, stream, stream_name,
322327
input_device,
323328
input_stream_params,

src/cubeb_wasapi.cpp

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -617,9 +617,20 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
617617

618618
int wasapi_stream_reset_default_device(cubeb_stream * stm);
619619

620+
/* Helper for making get_input_buffer work in exclusive mode */
621+
HRESULT get_next_packet_size(cubeb_stream * stm, PUINT32 next)
622+
{
623+
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE) {
624+
*next = stm->input_buffer_frame_count;
625+
return S_OK;
626+
} else {
627+
return stm->capture_client->GetNextPacketSize(next);
628+
}
629+
}
630+
620631
/* This helper grabs all the frames available from a capture client, put them in
621632
* linear_input_buffer. linear_input_buffer should be cleared before the
622-
* callback exits. This helper does not work with exclusive mode streams. */
633+
* callback exits. */
623634
bool get_input_buffer(cubeb_stream * stm)
624635
{
625636
XASSERT(has_input(stm));
@@ -636,9 +647,9 @@ bool get_input_buffer(cubeb_stream * stm)
636647
// single packet each time. However, if we're pulling from the stream we may
637648
// need to grab multiple packets worth of frames that have accumulated (so
638649
// need a loop).
639-
for (hr = stm->capture_client->GetNextPacketSize(&next);
650+
for (hr = get_next_packet_size(stm, &next);
640651
next > 0;
641-
hr = stm->capture_client->GetNextPacketSize(&next)) {
652+
hr = get_next_packet_size(stm, &next)) {
642653
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
643654
// Application can recover from this error. More info
644655
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
@@ -705,6 +716,9 @@ bool get_input_buffer(cubeb_stream * stm)
705716
return false;
706717
}
707718
offset += packet_size;
719+
720+
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
721+
break;
708722
}
709723

710724
XASSERT(stm->linear_input_buffer->length() >= offset);
@@ -1395,8 +1409,11 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten
13951409
return CUBEB_ERROR;
13961410
}
13971411

1398-
/* The second parameter is for exclusive mode, that we don't use. */
1399-
hr = client->GetDevicePeriod(&default_period, NULL);
1412+
if (params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
1413+
hr = client->GetDevicePeriod(NULL, &default_period);
1414+
else
1415+
hr = client->GetDevicePeriod(&default_period, NULL);
1416+
14001417
if (FAILED(hr)) {
14011418
LOG("Could not get device period: %lx", hr);
14021419
return CUBEB_ERROR;
@@ -1531,7 +1548,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAV
15311548

15321549
/* Check if wasapi will accept our channel layout request. */
15331550
WAVEFORMATEX * closest;
1534-
HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
1551+
HRESULT hr = audio_client->IsFormatSupported(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
15351552
mix_format.get(),
15361553
&closest);
15371554
if (hr == S_FALSE) {
@@ -1665,6 +1682,13 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
16651682
// audio according to layout.
16661683
LOG("Channel count is different from the layout standard!\n");
16671684
}
1685+
1686+
1687+
if (stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
1688+
LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
1689+
stream_params->format, stream_params->rate, stream_params->channels,
1690+
mix_params->format, mix_params->rate, mix_params->channels);
1691+
else
16681692
LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]",
16691693
stream_params->format, stream_params->rate, stream_params->channels,
16701694
CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name,
@@ -1681,12 +1705,51 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
16811705
flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
16821706
}
16831707

1684-
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
1708+
hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
16851709
flags,
16861710
frames_to_hns(stm, stm->latency),
1687-
0,
1711+
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? frames_to_hns(stm, stm->latency) : 0,
16881712
mix_format.get(),
16891713
NULL);
1714+
1715+
// Try to realign the buffer size
1716+
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
1717+
{
1718+
LOG("Buffer size misaligned, trying to realign");
1719+
1720+
audio_client.reset();
1721+
1722+
hr = device->Activate(__uuidof(IAudioClient),
1723+
CLSCTX_INPROC_SERVER,
1724+
NULL, audio_client.receive_vpp());
1725+
1726+
if (FAILED(hr)) {
1727+
LOG("Unable to reactivate audio client for %s: %lx", DIRECTION_NAME, hr);
1728+
return CUBEB_ERROR;
1729+
}
1730+
1731+
REFERENCE_TIME realigned_time = frames_to_hns(stm, stm->latency) * *buffer_frame_count / mix_format->nSamplesPerSec + 0.5;
1732+
1733+
1734+
hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
1735+
flags,
1736+
realigned_time,
1737+
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? realigned_time : 0,
1738+
mix_format.get(),
1739+
NULL);
1740+
1741+
if (FAILED(hr)) {
1742+
LOG("Unable to initialize realigned audio client for %s: %lx.", DIRECTION_NAME, hr);
1743+
return CUBEB_ERROR;
1744+
}
1745+
}
1746+
1747+
if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT)
1748+
{
1749+
LOG("The requested format is not supported by the current device.");
1750+
return CUBEB_ERROR_INVALID_FORMAT;
1751+
}
1752+
16901753
if (FAILED(hr)) {
16911754
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
16921755
return CUBEB_ERROR;
@@ -2244,6 +2307,10 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
22442307
return CUBEB_ERROR;
22452308
}
22462309

2310+
// Exclusive mode doesn't support setting the volume
2311+
if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
2312+
return CUBEB_ERROR_NOT_SUPPORTED;
2313+
22472314
if (stream_set_volume(stm, volume) != CUBEB_OK) {
22482315
return CUBEB_ERROR;
22492316
}

test/common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#define WIN32_LEAN_AND_MEAN
1313
#endif
1414
#include <windows.h>
15+
#include <objbase.h>
1516
#else
1617
#include <unistd.h>
1718
#endif
@@ -114,6 +115,11 @@ void print_log(const char * msg, ...)
114115
* override. */
115116
int common_init(cubeb ** ctx, char const * ctx_name)
116117
{
118+
#ifdef _WIN32
119+
// Exclusive mode needs this
120+
CoInitialize(NULL);
121+
#endif
122+
117123
int r;
118124
char const * backend;
119125
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, layout_info layout, int sampling_rate, int is_float)
97+
int run_test(int num_channels, layout_info layout, 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, layout_info layout, int sampling_rate, int is_flo
123123
params.rate = sampling_rate;
124124
params.channels = num_channels;
125125
params.layout = layout.layout;
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;
@@ -250,6 +260,11 @@ TEST(cubeb, run_channel_rate_test)
250260
if (layout.channels == channels) {
251261
ASSERT_EQ(run_test(channels, layout, freq, 0), CUBEB_OK);
252262
ASSERT_EQ(run_test(channels, layout, freq, 1), CUBEB_OK);
263+
#ifdef _WIN32
264+
// Run exclusive mdoe tests on Windows
265+
ASSERT_EQ(run_test(channels, layout, freq, 0, true), CUBEB_OK);
266+
ASSERT_EQ(run_test(channels, layout, freq, 1, true), CUBEB_OK);
267+
#endif
253268
}
254269
}
255270
}

0 commit comments

Comments
 (0)