wasapi2: Add support for exclusive mode

Add "exclusive" property and try exclusive mode streaming

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9586>
This commit is contained in:
Seungha Yang 2025-08-20 06:56:27 +09:00 committed by GStreamer Marge Bot
parent 370499875c
commit 18b5398960
4 changed files with 373 additions and 52 deletions

View File

@ -193,6 +193,9 @@ struct RbufCtx
HANDLE render_event;
GstCaps *caps = nullptr;
WAVEFORMATEX *mix_format = nullptr;
std::vector<guint8> exclusive_staging;
size_t exclusive_staging_filled = 0;
size_t exclusive_period_bytes = 0;
UINT32 period = 0;
UINT32 client_buf_size = 0;
@ -200,6 +203,7 @@ struct RbufCtx
bool is_default = false;
bool running = false;
bool error_posted = false;
bool is_exclusive = false;
};
typedef std::shared_ptr<RbufCtx> RbufCtxPtr;
@ -279,6 +283,7 @@ struct CommandSetDevice : public CommandData
GstWasapi2EndpointClass endpoint_class;
guint pid = 0;
gboolean low_latency = FALSE;
gboolean exclusive = FALSE;
};
struct CommandUpdateDevice : public CommandData
@ -404,6 +409,7 @@ struct RbufCtxDesc
gint64 latency_time;
WAVEFORMATEX *mix_format = nullptr;
gboolean low_latency = FALSE;
gboolean exclusive = FALSE;
HANDLE event_handle;
};
@ -466,6 +472,105 @@ initialize_audio_client3 (IAudioClient * client_handle,
return hr;
}
static HRESULT
initialize_audio_client_exclusive (IMMDevice * device,
ComPtr<IAudioClient> & client, WAVEFORMATEX * wfx, guint * period,
RbufCtxDesc * desc)
{
/* Format must be validated by caller */
auto hr = client->IsFormatSupported (AUDCLNT_SHAREMODE_EXCLUSIVE,
wfx, nullptr);
if (hr != S_OK)
return E_FAIL;
REFERENCE_TIME min_hns = 0;
REFERENCE_TIME max_hns = 0;
REFERENCE_TIME default_period = 0;
REFERENCE_TIME min_hns_period = 0;
{
ComPtr<IAudioClient2> client2;
hr = client->QueryInterface (IID_PPV_ARGS (&client2));
if (SUCCEEDED (hr)) {
hr = client2->GetBufferSizeLimits (wfx, TRUE, &min_hns, &max_hns);
if (FAILED (hr) || min_hns == 0 || max_hns == 0) {
min_hns = 0;
max_hns = 0;
} else {
auto min_gst = static_cast <GstClockTime> (min_hns) * 100;
auto max_gst = static_cast <GstClockTime> (max_hns) * 100;
GST_DEBUG ("GetBufferSizeLimits - min: %" GST_TIME_FORMAT ", max: %"
GST_TIME_FORMAT, GST_TIME_ARGS (min_gst), GST_TIME_ARGS (max_gst));
}
}
}
hr = client->GetDevicePeriod (&default_period, &min_hns_period);
if (!gst_wasapi2_result (hr))
return hr;
auto min_gst = static_cast <GstClockTime> (min_hns_period) * 100;
auto default_gst = static_cast <GstClockTime> (default_period) * 100;
GST_DEBUG ("GetDevicePeriod - default: %" GST_TIME_FORMAT ", min: %"
GST_TIME_FORMAT, GST_TIME_ARGS (default_gst), GST_TIME_ARGS (min_gst));
min_hns = MAX (min_hns, min_hns_period);
if (max_hns == 0)
max_hns = default_period;
REFERENCE_TIME target = min_hns;
if (!desc->low_latency && desc->latency_time > 0)
target = desc->latency_time * 10;
if (target < min_hns)
target = min_hns;
if (target > max_hns)
target = max_hns;
DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_NOPERSIST ;
hr = client->Initialize (AUDCLNT_SHAREMODE_EXCLUSIVE, flags,
target, target, wfx, nullptr);
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
UINT32 buffer_size = 0;
GST_DEBUG ("Buffer size not aligned, opening device again");
hr = client->GetBufferSize (&buffer_size);
if (!gst_wasapi2_result (hr) || buffer_size == 0)
return E_FAIL;
client.Reset ();
hr = device->Activate (__uuidof (IAudioClient), CLSCTX_ALL, nullptr,
&client);
if (!gst_wasapi2_result (hr))
return hr;
target = (GST_SECOND / 100) * buffer_size / wfx->nSamplesPerSec;
hr = client->Initialize (AUDCLNT_SHAREMODE_EXCLUSIVE,
flags, target, target, wfx, nullptr);
}
if (!gst_wasapi2_result (hr))
return hr;
UINT32 buffer_size = 0;
hr = client->GetBufferSize (&buffer_size);
if (!gst_wasapi2_result (hr) || buffer_size == 0) {
client.Reset ();
return E_FAIL;
}
if (period)
*period = buffer_size;
GST_DEBUG ("Opened in exclusive mode");
return S_OK;
}
static HRESULT
initialize_audio_client (IAudioClient * client_handle,
WAVEFORMATEX * mix_format, guint * period,
@ -541,19 +646,9 @@ initialize_audio_client (IAudioClient * client_handle,
return S_OK;
}
static WAVEFORMATEX *
copy_wave_format (WAVEFORMATEX * src)
{
size_t total_size = sizeof (WAVEFORMATEX) + src->cbSize;
WAVEFORMATEX *dst = (WAVEFORMATEX *) CoTaskMemAlloc (total_size);
memcpy (dst, src, total_size);
return dst;
}
static void
gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
RbufCtxDesc * desc)
RbufCtxDesc * desc, GPtrArray * exclusive_formats)
{
HRESULT hr = S_OK;
Wasapi2ActivationHandler *activator = nullptr;
@ -566,12 +661,19 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
auto endpoint_class = desc->endpoint_class;
if ((endpoint_class == GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE ||
gst_wasapi2_is_process_loopback_class (endpoint_class)) &&
desc->exclusive) {
GST_WARNING ("Loopback + exclusive is not supported configuration");
desc->exclusive = FALSE;
}
switch (endpoint_class) {
case GST_WASAPI2_ENDPOINT_CLASS_CAPTURE:
if (desc->device_id.empty () ||
is_equal_device_id (desc->device_id.c_str (),
gst_wasapi2_get_default_device_id (eCapture))) {
if (gst_wasapi2_can_automatic_stream_routing ()) {
if (gst_wasapi2_can_automatic_stream_routing () && !desc->exclusive) {
Wasapi2ActivationHandler::CreateInstance (&activator,
gst_wasapi2_get_default_device_id_wide (eCapture), nullptr);
GST_LOG ("Creating default capture device");
@ -592,7 +694,7 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
if (desc->device_id.empty () ||
is_equal_device_id (desc->device_id.c_str (),
gst_wasapi2_get_default_device_id (eRender))) {
if (gst_wasapi2_can_automatic_stream_routing ()) {
if (gst_wasapi2_can_automatic_stream_routing () && !desc->exclusive) {
Wasapi2ActivationHandler::CreateInstance (&activator,
gst_wasapi2_get_default_device_id_wide (eRender), nullptr);
GST_LOG ("Creating default render device");
@ -683,11 +785,37 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
}
}
if (desc->exclusive) {
if (!device) {
GST_WARNING ("IMMDevice is unavailable");
return;
}
ComPtr < IPropertyStore > prop;
hr = device->OpenPropertyStore (STGM_READ, &prop);
if (!gst_wasapi2_result (hr))
return;
gst_wasapi2_get_exclusive_formats (ctx->client.Get (), prop.Get (),
exclusive_formats);
if (exclusive_formats->len == 0) {
GST_WARNING ("Couldn't get exclusive mode formats");
desc->exclusive = false;
}
}
DWORD stream_flags = 0;
if (!desc->mix_format) {
ctx->client->GetMixFormat (&ctx->mix_format);
if (!ctx->mix_format && gst_wasapi2_is_process_loopback_class (endpoint_class)) {
ctx->mix_format = gst_wasapi2_get_default_mix_format ();
if (desc->exclusive) {
g_assert (exclusive_formats->len > 0);
auto format = (WAVEFORMATEX *) g_ptr_array_index (exclusive_formats, 0);
ctx->mix_format = gst_wasapi2_copy_wfx (format);
} else {
ctx->client->GetMixFormat (&ctx->mix_format);
if (!ctx->mix_format && gst_wasapi2_is_process_loopback_class (endpoint_class)) {
ctx->mix_format = gst_wasapi2_get_default_mix_format ();
}
}
if (!ctx->mix_format) {
@ -695,12 +823,14 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
return;
}
} else {
/* TODO: support exclusive mode device swich */
/* Check format support */
WAVEFORMATEX *closest = nullptr;
hr = ctx->client->IsFormatSupported (AUDCLNT_SHAREMODE_SHARED,
desc->mix_format, &closest);
if (hr == S_OK) {
ctx->mix_format = copy_wave_format (desc->mix_format);
ctx->mix_format = gst_wasapi2_copy_wfx (desc->mix_format);
/* format supported */
} else if (hr == S_FALSE) {
if (!closest) {
@ -731,7 +861,7 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
gst_caps_unref (new_caps);
gst_caps_unref (old_caps);
CoTaskMemFree (closest);
ctx->mix_format = copy_wave_format (desc->mix_format);
ctx->mix_format = gst_wasapi2_copy_wfx (desc->mix_format);
stream_flags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
} else {
@ -746,13 +876,30 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
}
}
gst_wasapi2_util_parse_waveformatex (ctx->mix_format,
&ctx->caps, nullptr);
gst_wasapi2_util_parse_waveformatex (ctx->mix_format, &ctx->caps, nullptr);
hr = E_FAIL;
if (desc->exclusive) {
hr = initialize_audio_client_exclusive (device.Get (), ctx->client,
ctx->mix_format, &ctx->period, desc);
if (FAILED (hr)) {
desc->exclusive = false;
if (!ctx->client) {
hr = device->Activate (__uuidof (IAudioClient), CLSCTX_ALL,
nullptr, &ctx->client);
if (!gst_wasapi2_result (hr)) {
GST_WARNING ("Couldn't get IAudioClient from IMMDevice");
return;
}
}
}
}
/* Try IAudioClient3 if low-latency is requested */
if (desc->low_latency && !gst_wasapi2_is_loopback_class (endpoint_class) &&
!gst_wasapi2_is_process_loopback_class (endpoint_class)) {
if (FAILED (hr) && desc->low_latency &&
!gst_wasapi2_is_loopback_class (endpoint_class) &&
!gst_wasapi2_is_process_loopback_class (endpoint_class) &&
!desc->exclusive) {
hr = initialize_audio_client3 (ctx->client.Get (), ctx->mix_format,
&ctx->period, stream_flags);
}
@ -795,9 +942,11 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
}
}
hr = ctx->client->GetService (IID_PPV_ARGS (&ctx->stream_volume));
if (!gst_wasapi2_result (hr))
GST_WARNING ("Couldn't get ISimpleAudioVolume interface");
if (!desc->exclusive) {
hr = ctx->client->GetService (IID_PPV_ARGS (&ctx->stream_volume));
if (!gst_wasapi2_result (hr))
GST_WARNING ("Couldn't get ISimpleAudioVolume interface");
}
hr = ctx->client->GetBufferSize (&ctx->client_buf_size);
if (!gst_wasapi2_result (hr)) {
@ -841,7 +990,7 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
return;
}
if (device) {
if (device && !desc->exclusive) {
hr = device->Activate (__uuidof (IAudioEndpointVolume),
CLSCTX_ALL, nullptr, &ctx->endpoint_volume);
if (gst_wasapi2_result (hr)) {
@ -864,20 +1013,30 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
/* Preroll data with silent data */
if (ctx->render_client && !ctx->dummy_client) {
UINT32 padding = 0;
auto hr = ctx->client->GetCurrentPadding (&padding);
if (SUCCEEDED (hr) && padding < ctx->client_buf_size) {
auto can_write = ctx->client_buf_size - padding;
if (can_write > ctx->period)
can_write = ctx->period;
if (desc->exclusive) {
BYTE *data;
hr = ctx->render_client->GetBuffer (can_write, &data);
hr = ctx->render_client->GetBuffer (ctx->client_buf_size, &data);
if (SUCCEEDED (hr)) {
GST_DEBUG ("Prefill %u frames", can_write);
ctx->render_client->ReleaseBuffer (can_write,
GST_DEBUG ("Prefill %u frames", ctx->client_buf_size);
ctx->render_client->ReleaseBuffer (ctx->client_buf_size,
AUDCLNT_BUFFERFLAGS_SILENT);
}
} else {
UINT32 padding = 0;
auto hr = ctx->client->GetCurrentPadding (&padding);
if (SUCCEEDED (hr) && padding < ctx->client_buf_size) {
auto can_write = ctx->client_buf_size - padding;
if (can_write > ctx->period)
can_write = ctx->period;
BYTE *data;
hr = ctx->render_client->GetBuffer (can_write, &data);
if (SUCCEEDED (hr)) {
GST_DEBUG ("Prefill %u frames", can_write);
ctx->render_client->ReleaseBuffer (can_write,
AUDCLNT_BUFFERFLAGS_SILENT);
}
}
}
}
@ -890,6 +1049,17 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
ctx->is_default = is_default;
ctx->endpoint_class = endpoint_class;
ctx->is_exclusive = desc->exclusive;
/* Allocates staging buffer for exclusive mode, since we should fill
* endpoint buffer at once */
if (ctx->is_exclusive && ctx->render_client) {
GstAudioInfo info;
gst_audio_info_from_caps (&info, ctx->caps);
ctx->exclusive_period_bytes = ctx->period * GST_AUDIO_INFO_BPF (&info);
ctx->exclusive_staging.resize (ctx->exclusive_period_bytes);
ctx->exclusive_staging_filled = 0;
}
desc->ctx = ctx;
}
@ -915,18 +1085,22 @@ struct Wasapi2DeviceManager
interrupt_handle = CreateEvent (nullptr, FALSE, FALSE, nullptr);
com_thread = g_thread_new ("Wasapi2DeviceManager",
(GThreadFunc) device_manager_com_thread, this);
exclusive_formats = g_ptr_array_new_with_free_func ((GDestroyNotify)
gst_wasapi2_free_wfx);
}
~Wasapi2DeviceManager ()
{
CloseHandle (shutdown_handle);
CloseHandle (interrupt_handle);
g_ptr_array_unref (exclusive_formats);
}
RbufCtxPtr
CreateCtx (const std::string & device_id,
GstWasapi2EndpointClass endpoint_class, guint pid, gint64 buffer_time,
gint64 latency_time, gboolean low_latency, WAVEFORMATEX * mix_format)
gint64 latency_time, gboolean low_latency, gboolean exclusive,
WAVEFORMATEX * mix_format)
{
auto desc = std::make_shared<RbufCtxDesc> ();
desc->device_id = device_id;
@ -935,8 +1109,9 @@ struct Wasapi2DeviceManager
desc->buffer_time = buffer_time;
desc->latency_time = latency_time;
desc->low_latency = low_latency;
desc->exclusive = exclusive;
if (mix_format)
desc->mix_format = copy_wave_format (mix_format);
desc->mix_format = gst_wasapi2_copy_wfx (mix_format);
{
std::lock_guard <std::mutex> lk (lock);
@ -952,7 +1127,8 @@ struct Wasapi2DeviceManager
void
CreateCtxAsync (GstWasapi2Rbuf * rbuf, const std::string & device_id,
GstWasapi2EndpointClass endpoint_class, guint pid, gint64 buffer_time,
gint64 latency_time, gboolean low_latency, WAVEFORMATEX * mix_format)
gint64 latency_time, gboolean low_latency, gboolean exclusive,
WAVEFORMATEX * mix_format)
{
auto desc = std::make_shared<RbufCtxDesc> ();
desc->rbuf = (GstWasapi2Rbuf *) gst_object_ref (rbuf);
@ -962,8 +1138,9 @@ struct Wasapi2DeviceManager
desc->buffer_time = buffer_time;
desc->latency_time = latency_time;
desc->low_latency = low_latency;
desc->exclusive = exclusive;
if (mix_format)
desc->mix_format = copy_wave_format (mix_format);
desc->mix_format = gst_wasapi2_copy_wfx (mix_format);
{
std::lock_guard <std::mutex> lk (lock);
@ -977,6 +1154,7 @@ struct Wasapi2DeviceManager
HANDLE shutdown_handle;
HANDLE interrupt_handle;
GThread *com_thread;
GPtrArray *exclusive_formats;
};
static gpointer
@ -1007,7 +1185,11 @@ device_manager_com_thread (gpointer manager)
self->queue.pop ();
lk.unlock ();
GST_LOG ("Creating new context");
gst_wasapi2_device_manager_create_ctx (enumerator.Get (), desc.get ());
g_ptr_array_set_size (self->exclusive_formats, 0);
gst_wasapi2_device_manager_create_ctx (enumerator.Get (), desc.get (),
self->exclusive_formats);
g_ptr_array_set_size (self->exclusive_formats, 0);
if (desc->mix_format)
CoTaskMemFree (desc->mix_format);
@ -1063,6 +1245,7 @@ struct GstWasapi2RbufPrivate
GstWasapi2EndpointClass endpoint_class;
guint pid;
gboolean low_latency = FALSE;
gboolean exclusive = FALSE;
std::shared_ptr<RbufCtx> ctx;
std::atomic<bool> monitor_device_mute;
@ -1271,7 +1454,7 @@ gst_wasapi2_rbuf_create_ctx (GstWasapi2Rbuf * self)
return inst->CreateCtx (priv->device_id, priv->endpoint_class,
priv->pid, buffer_time, latency_time, priv->low_latency,
priv->mix_format);
priv->exclusive, priv->mix_format);
}
static void
@ -1295,7 +1478,7 @@ gst_wasapi2_rbuf_create_ctx_async (GstWasapi2Rbuf * self)
inst->CreateCtxAsync (self, priv->device_id, priv->endpoint_class,
priv->pid, buffer_time, latency_time, priv->low_latency,
priv->mix_format);
priv->exclusive, priv->mix_format);
}
static gboolean
@ -1646,6 +1829,75 @@ gst_wasapi2_rbuf_process_write (GstWasapi2Rbuf * self)
return S_OK;
}
static HRESULT
gst_wasapi2_rbuf_process_write_exclusive (GstWasapi2Rbuf * self)
{
auto rb = GST_AUDIO_RING_BUFFER_CAST (self);
auto priv = self->priv;
HRESULT hr;
BYTE *data = nullptr;
if (!priv->ctx || !priv->ctx->render_client) {
GST_ERROR_OBJECT (self, "IAudioRenderClient is not available");
return E_FAIL;
}
auto & ctx = priv->ctx;
auto client = priv->ctx->client;
auto render_client = priv->ctx->render_client;
while (ctx->exclusive_staging_filled < ctx->exclusive_staging.size ()) {
gint segment;
guint8 *readptr;
gint len;
if (!gst_audio_ring_buffer_prepare_read (rb, &segment, &readptr, &len))
break;
len -= priv->segoffset;
if (len <= 0)
break;
auto remain = ctx->exclusive_period_bytes - ctx->exclusive_staging_filled;
auto to_copy = (guint) MIN ((guint) len, (guint) remain);
memcpy (ctx->exclusive_staging.data () + ctx->exclusive_staging_filled,
readptr + priv->segoffset, to_copy);
priv->segoffset += to_copy;
ctx->exclusive_staging_filled += to_copy;
if (priv->segoffset == rb->spec.segsize) {
gst_audio_ring_buffer_clear (rb, segment);
gst_audio_ring_buffer_advance (rb, 1);
priv->segoffset = 0;
}
}
hr = render_client->GetBuffer (ctx->period, &data);
if (!gst_wasapi2_result (hr))
return hr;
GST_LOG_OBJECT (self, "Writing %d frames offset at %" G_GUINT64_FORMAT,
(guint) ctx->period, priv->write_frame_offset);
priv->write_frame_offset += ctx->period;
if (ctx->exclusive_staging_filled < ctx->exclusive_period_bytes) {
GST_LOG_OBJECT (self, "Staging buffer not filled %d < %d",
(guint) ctx->exclusive_staging_filled,
(guint) ctx->exclusive_period_bytes);
hr = render_client->ReleaseBuffer (ctx->period, AUDCLNT_BUFFERFLAGS_SILENT);
gst_wasapi2_result (hr);
} else {
memcpy (data, ctx->exclusive_staging.data (), ctx->exclusive_period_bytes);
hr = ctx->render_client->ReleaseBuffer (ctx->period, 0);
gst_wasapi2_result (hr);
ctx->exclusive_staging_filled = 0;
}
return S_OK;
}
static HRESULT
fill_loopback_silence (GstWasapi2Rbuf * self)
{
@ -1835,6 +2087,8 @@ gst_wasapi2_rbuf_process_start (GstWasapi2Rbuf * self, gboolean reset_offset)
priv->expected_position = 0;
if (priv->ctx) {
priv->ctx->exclusive_staging_filled = 0;
auto hr = priv->ctx->Start ();
if (!gst_wasapi2_result (hr)) {
@ -1994,7 +2248,10 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
if (SUCCEEDED (hr))
hr = gst_wasapi2_rbuf_process_read (self);
} else {
hr = gst_wasapi2_rbuf_process_write (self);
if (priv->ctx->is_exclusive)
hr = gst_wasapi2_rbuf_process_write_exclusive (self);
else
hr = gst_wasapi2_rbuf_process_write (self);
}
if (FAILED (hr)) {
@ -2097,6 +2354,7 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
priv->endpoint_class = scmd->endpoint_class;
priv->pid = scmd->pid;
priv->low_latency = scmd->low_latency;
priv->exclusive = scmd->exclusive;
if (priv->opened) {
GST_DEBUG_OBJECT (self,
@ -2162,7 +2420,7 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
priv->ctx->SetVolume (priv->volume);
priv->opened = true;
priv->mix_format = copy_wave_format (priv->ctx->mix_format);
priv->mix_format = gst_wasapi2_copy_wfx (priv->ctx->mix_format);
cmd->hr = S_OK;
} else {
gst_clear_caps (&priv->caps);
@ -2226,7 +2484,7 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
priv->ctx->SetVolume (priv->volume);
ClearMixFormat (&priv->mix_format);
priv->mix_format = copy_wave_format (priv->ctx->mix_format);
priv->mix_format = gst_wasapi2_copy_wfx (priv->ctx->mix_format);
} else {
waitables[0] = dummy_render;
waitables[1] = dummy_capture;
@ -2341,7 +2599,8 @@ gst_wasapi2_rbuf_new (gpointer parent, GstWasapi2RbufCallback callback)
void
gst_wasapi2_rbuf_set_device (GstWasapi2Rbuf * rbuf, const gchar * device_id,
GstWasapi2EndpointClass endpoint_class, guint pid, gboolean low_latency)
GstWasapi2EndpointClass endpoint_class, guint pid, gboolean low_latency,
gboolean exclusive)
{
auto cmd = std::make_shared < CommandSetDevice > ();
@ -2350,6 +2609,7 @@ gst_wasapi2_rbuf_set_device (GstWasapi2Rbuf * rbuf, const gchar * device_id,
cmd->endpoint_class = endpoint_class;
cmd->pid = pid;
cmd->low_latency = low_latency;
cmd->exclusive = exclusive;
gst_wasapi2_rbuf_push_command (rbuf, cmd);

View File

@ -38,7 +38,8 @@ void gst_wasapi2_rbuf_set_device (GstWasapi2Rbuf * rbuf,
const gchar * device_id,
GstWasapi2EndpointClass endpoint_class,
guint pid,
gboolean low_latency);
gboolean low_latency,
gboolean exclusive);
GstCaps * gst_wasapi2_rbuf_get_caps (GstWasapi2Rbuf * rbuf);

View File

@ -61,6 +61,7 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
#define DEFAULT_MUTE FALSE
#define DEFAULT_VOLUME 1.0
#define DEFAULT_CONTINUE_ON_ERROR FALSE
#define DEFAULT_EXCLUSIVE FALSE
enum
{
@ -71,6 +72,7 @@ enum
PROP_VOLUME,
PROP_DISPATCHER,
PROP_CONTINUE_ON_ERROR,
PROP_EXCLUSIVE,
};
/* *INDENT-OFF* */
@ -91,6 +93,7 @@ struct GstWasapi2SinkPrivate
gchar *device_id = nullptr;;
gboolean low_latency = DEFAULT_LOW_LATENCY;
gboolean continue_on_error = DEFAULT_CONTINUE_ON_ERROR;
gboolean exclusive = DEFAULT_EXCLUSIVE;
};
/* *INDENT-ON* */
@ -189,6 +192,17 @@ gst_wasapi2_sink_class_init (GstWasapi2SinkClass * klass)
DEFAULT_CONTINUE_ON_ERROR, (GParamFlags) (GST_PARAM_MUTABLE_READY |
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
/**
* GstWasapi2Sink:exclusive:
*
* Since: 1.28
*/
g_object_class_install_property (gobject_class, PROP_EXCLUSIVE,
g_param_spec_boolean ("exclusive", "Exclusive",
"Open the device in exclusive mode",
DEFAULT_EXCLUSIVE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
gst_element_class_add_static_pad_template (element_class, &sink_template);
gst_element_class_set_static_metadata (element_class, "Wasapi2Sink",
"Sink/Audio/Hardware",
@ -222,7 +236,8 @@ gst_wasapi2_sink_init (GstWasapi2Sink * self)
priv->rbuf = gst_wasapi2_rbuf_new (self, gst_wasapi2_sink_on_invalidated);
gst_wasapi2_rbuf_set_device (priv->rbuf, nullptr,
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, DEFAULT_LOW_LATENCY);
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, DEFAULT_LOW_LATENCY,
DEFAULT_EXCLUSIVE);
self->priv = priv;
}
@ -251,7 +266,7 @@ gst_wasapi2_sink_set_device (GstWasapi2Sink * self, bool updated)
return;
gst_wasapi2_rbuf_set_device (priv->rbuf, priv->device_id,
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, priv->low_latency);
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, priv->low_latency, priv->exclusive);
}
static void
@ -303,6 +318,18 @@ gst_wasapi2_sink_set_property (GObject * object, guint prop_id,
gst_wasapi2_rbuf_set_continue_on_error (priv->rbuf,
priv->continue_on_error);
break;
case PROP_EXCLUSIVE:
{
auto new_val = g_value_get_boolean (value);
bool updated = false;
if (new_val != priv->exclusive) {
priv->exclusive = new_val;
updated = true;
}
gst_wasapi2_sink_set_device (self, updated);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -334,6 +361,9 @@ gst_wasapi2_sink_get_property (GObject * object, guint prop_id,
case PROP_CONTINUE_ON_ERROR:
g_value_set_boolean (value, priv->continue_on_error);
break;
case PROP_EXCLUSIVE:
g_value_set_boolean (value, priv->exclusive);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;

View File

@ -122,6 +122,7 @@ gst_wasapi2_src_loopback_mode_get_type (void)
#define DEFAULT_LOOPBACK_MODE GST_WASAPI2_SRC_LOOPBACK_DEFAULT
#define DEFAULT_LOOPBACK_SILENCE_ON_DEVICE_MUTE FALSE
#define DEFAULT_CONTINUE_ON_ERROR FALSE
#define DEFAULT_EXCLUSIVE FALSE
enum
{
@ -136,6 +137,7 @@ enum
PROP_LOOPBACK_TARGET_PID,
PROP_LOOPBACK_SILENCE_ON_DEVICE_MUTE,
PROP_CONTINUE_ON_ERROR,
PROP_EXCLUSIVE,
};
/* *INDENT-OFF* */
@ -161,6 +163,7 @@ struct GstWasapi2SrcPrivate
gboolean loopback_silence_on_device_mute =
DEFAULT_LOOPBACK_SILENCE_ON_DEVICE_MUTE;
gboolean continue_on_error = DEFAULT_CONTINUE_ON_ERROR;
gboolean exclusive = DEFAULT_EXCLUSIVE;
};
/* *INDENT-ON* */
@ -323,6 +326,17 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
DEFAULT_CONTINUE_ON_ERROR, (GParamFlags) (GST_PARAM_MUTABLE_READY |
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
/**
* GstWasapi2Src:exclusive:
*
* Since: 1.28
*/
g_object_class_install_property (gobject_class, PROP_EXCLUSIVE,
g_param_spec_boolean ("exclusive", "Exclusive",
"Open the device in exclusive mode",
DEFAULT_EXCLUSIVE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_set_static_metadata (element_class, "Wasapi2Src",
"Source/Audio/Hardware",
@ -361,7 +375,8 @@ gst_wasapi2_src_init (GstWasapi2Src * self)
priv->rbuf = gst_wasapi2_rbuf_new (self, gst_wasapi2_src_on_invalidated);
gst_wasapi2_rbuf_set_device (priv->rbuf, nullptr,
GST_WASAPI2_ENDPOINT_CLASS_CAPTURE, 0, DEFAULT_LOW_LATENCY);
GST_WASAPI2_ENDPOINT_CLASS_CAPTURE, 0, DEFAULT_LOW_LATENCY,
DEFAULT_EXCLUSIVE);
self->priv = priv;
}
@ -402,7 +417,7 @@ gst_wasapi2_src_set_device (GstWasapi2Src * self, bool updated)
}
gst_wasapi2_rbuf_set_device (priv->rbuf, priv->device_id, device_class,
priv->loopback_pid, priv->low_latency);
priv->loopback_pid, priv->low_latency, priv->exclusive);
}
static void
@ -495,6 +510,18 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
gst_wasapi2_rbuf_set_continue_on_error (priv->rbuf,
priv->continue_on_error);
break;
case PROP_EXCLUSIVE:
{
auto new_val = g_value_get_boolean (value);
bool updated = false;
if (new_val != priv->exclusive) {
priv->exclusive = new_val;
updated = true;
}
gst_wasapi2_src_set_device (self, updated);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -538,6 +565,9 @@ gst_wasapi2_src_get_property (GObject * object, guint prop_id,
case PROP_CONTINUE_ON_ERROR:
g_value_set_boolean (value, priv->continue_on_error);
break;
case PROP_EXCLUSIVE:
g_value_set_boolean (value, priv->exclusive);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;