Skip to content

Commit

Permalink
Implement CUBEB_STREAM_PREF_EXCLUSIVE
Browse files Browse the repository at this point in the history
  • Loading branch information
spycrab committed Mar 27, 2018
1 parent 57b4d6b commit 06faf71
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 22 deletions.
15 changes: 10 additions & 5 deletions include/cubeb/cubeb.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,16 @@ enum {

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

/** Stream format initialization parameters. */
Expand Down
5 changes: 5 additions & 0 deletions src/cubeb.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return r;
}

if ((output_stream_params && output_stream_params->prefs) & CUBEB_STREAM_PREF_EXCLUSIVE || (input_stream_params && input_stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)) {
if (strcmp(cubeb_get_backend_id(context), "wasapi") != 0)
return CUBEB_ERROR_NOT_SUPPORTED;
}

r = context->ops->stream_init(context, stream, stream_name,
input_device,
input_stream_params,
Expand Down
106 changes: 92 additions & 14 deletions src/cubeb_wasapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,20 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,

int wasapi_stream_reset_default_device(cubeb_stream * stm);

/* Helper for making get_input_buffer work in exclusive mode */
HRESULT get_next_packet_size(cubeb_stream * stm, PUINT32 next)
{
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE) {
*next = stm->input_buffer_frame_count;
return S_OK;
} else {
return stm->capture_client->GetNextPacketSize(next);
}
}

/* This helper grabs all the frames available from a capture client, put them in
* linear_input_buffer. linear_input_buffer should be cleared before the
* callback exits. This helper does not work with exclusive mode streams. */
* callback exits. */
bool get_input_buffer(cubeb_stream * stm)
{
XASSERT(has_input(stm));
Expand All @@ -543,9 +554,9 @@ bool get_input_buffer(cubeb_stream * stm)
// single packet each time. However, if we're pulling from the stream we may
// need to grab multiple packets worth of frames that have accumulated (so
// need a loop).
for (hr = stm->capture_client->GetNextPacketSize(&next);
for (hr = get_next_packet_size(stm, &next);
next > 0;
hr = stm->capture_client->GetNextPacketSize(&next)) {
hr = get_next_packet_size(stm, &next)) {
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
// Application can recover from this error. More info
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
Expand Down Expand Up @@ -619,7 +630,11 @@ bool get_input_buffer(cubeb_stream * stm)
LOG("FAILED to release intput buffer");
return false;
}
offset += input_stream_samples;

offset += input_stream_samples;

if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
break;
}

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

/* The second parameter is for exclusive mode, that we don't use. */
REFERENCE_TIME default_period;
hr = client->GetDevicePeriod(&default_period, NULL);

if (params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
hr = client->GetDevicePeriod(NULL, &default_period);
else
hr = client->GetDevicePeriod(&default_period, NULL);

if (FAILED(hr)) {
LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
Expand Down Expand Up @@ -1391,7 +1410,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAV

/* Check if wasapi will accept our channel layout request. */
WAVEFORMATEX * closest;
HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
HRESULT hr = audio_client->IsFormatSupported(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
mix_format.get(),
&closest);
if (hr == S_FALSE) {
Expand Down Expand Up @@ -1518,11 +1537,27 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
mix_params->channels = mix_format->nChannels;
mix_params->layout = mask_to_channel_layout(mix_format.get());

LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
stream_params->format, stream_params->rate, stream_params->channels,
stream_params->layout,
mix_params->format, mix_params->rate, mix_params->channels,
mix_params->layout);
if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) {
LOG("Stream using undefined layout! Any mixing may be unpredictable!\n");
} else if (mix_format->nChannels != mix_params->channels) {
// The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be
// different from the mix_params->channels. 6 channel ouput with stereo
// layout is acceptable in Windows. If this happens, it should not downmix
// audio according to layout.
LOG("Channel count is different from the layout standard!\n");
}


if (stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
stream_params->format, stream_params->rate, stream_params->channels,
mix_params->format, mix_params->rate, mix_params->channels);
else
LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
stream_params->format, stream_params->rate, stream_params->channels,
stream_params->layout,
mix_params->format, mix_params->rate, mix_params->channels,
mix_params->layout);

DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST;

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

hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
flags,
frames_to_hns(stm, stm->latency),
0,
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? frames_to_hns(stm, stm->latency) : 0,
mix_format.get(),
NULL);

// Try to realign the buffer size
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
{
LOG("Buffer size misaligned, trying to realign");

audio_client.reset();

hr = device->Activate(__uuidof(IAudioClient),
CLSCTX_INPROC_SERVER,
NULL, audio_client.receive_vpp());

if (FAILED(hr)) {
LOG("Unable to reactivate audio client for %s: %lx", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}

REFERENCE_TIME realigned_time = frames_to_hns(stm, stm->latency) * *buffer_frame_count / mix_format->nSamplesPerSec + 0.5;


hr = audio_client->Initialize(stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
flags,
realigned_time,
stream_params->prefs & CUBEB_STREAM_PREF_EXCLUSIVE ? realigned_time : 0,
mix_format.get(),
NULL);

if (FAILED(hr)) {
LOG("Unable to initialize realigned audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
}

if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT)
{
LOG("The requested format is not supported by the current device.");
return CUBEB_ERROR_INVALID_FORMAT;
}

if (FAILED(hr)) {
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
Expand Down Expand Up @@ -2105,6 +2179,10 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_ERROR;
}

// Exclusive mode doesn't support setting the volume
if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_EXCLUSIVE)
return CUBEB_ERROR_NOT_SUPPORTED;

if (stream_set_volume(stm, volume) != CUBEB_OK) {
return CUBEB_ERROR;
}
Expand Down
6 changes: 6 additions & 0 deletions test/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#endif
#include <objbase.h>
#include <windows.h>
#include <objbase.h>
#else
#include <unistd.h>
#endif
Expand Down Expand Up @@ -93,6 +94,11 @@ void print_log(const char * msg, ...)
* override. */
int common_init(cubeb ** ctx, char const * ctx_name)
{
#ifdef _WIN32
// Exclusive mode needs this
CoInitialize(NULL);
#endif

int r;
char const * backend;
char const * ctx_backend;
Expand Down
21 changes: 18 additions & 3 deletions test/test_audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ int supports_channel_count(string backend_id, int nchannels)
(backend_id != "opensl" && backend_id != "audiotrack");
}

int run_test(int num_channels, int sampling_rate, int is_float)
int run_test(int num_channels, int sampling_rate, int is_float, bool exclusive=false)
{
int r = CUBEB_OK;

Expand Down Expand Up @@ -123,13 +123,18 @@ int run_test(int num_channels, int sampling_rate, int is_float)
params.rate = sampling_rate;
params.channels = num_channels;
params.layout = CUBEB_LAYOUT_UNDEFINED;
params.prefs = CUBEB_STREAM_PREF_NONE;
params.prefs = exclusive ? CUBEB_STREAM_PREF_EXCLUSIVE : CUBEB_STREAM_PREF_NONE;

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

cubeb_stream *stream = NULL;
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
if (r == CUBEB_ERROR_INVALID_FORMAT)
{
fprintf(stderr, "Format not supported. Not treating as a failure.\n");
return CUBEB_OK;
}
if (r != CUBEB_OK) {
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
return r;
Expand Down Expand Up @@ -172,14 +177,19 @@ int run_panning_volume_test(int is_float)
params.rate = 44100;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
params.prefs = CUBEB_STREAM_PREF_NONE;
params.prefs = CUBEB_STREAM_PREF_EXCLUSIVE;

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

cubeb_stream *stream = NULL;
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
4096, is_float ? &data_cb<float> : &data_cb<short>,
state_cb_audio, &synth);
if (r == CUBEB_ERROR_INVALID_FORMAT)
{
fprintf(stderr, "Format not supported. Not treating as a failure.\n");
return CUBEB_OK;
}
if (r != CUBEB_OK) {
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
return r;
Expand Down Expand Up @@ -248,6 +258,11 @@ TEST(cubeb, run_channel_rate_test)
fprintf(stderr, "--------------------------\n");
ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
#ifdef _WIN32
// Run exclusive mdoe tests on Windows
ASSERT_EQ(run_test(channels, freq, 0, true), CUBEB_OK);
ASSERT_EQ(run_test(channels, freq, 1, true), CUBEB_OK);
#endif
}
}
}

0 comments on commit 06faf71

Please sign in to comment.