wasapi2: Add continue-on-error property
If enabled, wasapi2src/sink will post a warning message instead of an error, when device failures occur, such as open failure, I/O error, or device removal. The element will continue to produce/consume audio buffers and behave as if a capture/render device were active, allowing pipeline to keep running even when no audio endpoint is available Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9326>
This commit is contained in:
parent
37637d9611
commit
0d2157e801
@ -78,6 +78,11 @@ GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_rbuf_debug);
|
||||
|
||||
static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
|
||||
|
||||
/* Defined for _WIN32_WINNT >= _NT_TARGET_VERSION_WIN10_RS4 */
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
#endif
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
@ -85,7 +90,8 @@ static gpointer device_manager_com_thread (gpointer manager);
|
||||
|
||||
struct RbufCtx
|
||||
{
|
||||
RbufCtx ()
|
||||
RbufCtx () = delete;
|
||||
RbufCtx (const std::string & id) : device_id (id)
|
||||
{
|
||||
capture_event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
render_event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
@ -182,6 +188,7 @@ struct RbufCtx
|
||||
ComPtr<IAudioStreamVolume> stream_volume;
|
||||
ComPtr<IAudioEndpointVolume> endpoint_volume;
|
||||
ComPtr<IAudioEndpointVolumeCallback> volume_callback;
|
||||
std::string device_id;
|
||||
std::vector<float> volumes;
|
||||
std::atomic<bool> endpoint_muted = { false };
|
||||
HANDLE capture_event;
|
||||
@ -194,6 +201,7 @@ struct RbufCtx
|
||||
UINT32 dummy_buf_size = 0;
|
||||
bool is_default = false;
|
||||
bool running = false;
|
||||
bool error_posted = false;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<RbufCtx> RbufCtxPtr;
|
||||
@ -277,8 +285,10 @@ struct CommandSetDevice : public CommandData
|
||||
|
||||
struct CommandUpdateDevice : public CommandData
|
||||
{
|
||||
CommandUpdateDevice () : CommandData (CommandType::UpdateDevice) {}
|
||||
CommandUpdateDevice (const std::string & id)
|
||||
: CommandData (CommandType::UpdateDevice), device_id (id) {}
|
||||
std::shared_ptr<RbufCtx> ctx;
|
||||
std::string device_id;
|
||||
};
|
||||
|
||||
struct CommandGetCaps : public CommandData
|
||||
@ -638,7 +648,7 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
|
||||
/* For debug */
|
||||
gst_wasapi2_result (hr);
|
||||
|
||||
auto ctx = std::make_shared<RbufCtx> ();
|
||||
auto ctx = std::make_shared<RbufCtx> (desc->device_id);
|
||||
if (activator) {
|
||||
activator->ActivateAsync ();
|
||||
activator->GetClient (&ctx->client, INFINITE);
|
||||
@ -913,7 +923,7 @@ struct Wasapi2DeviceManager
|
||||
RbufCtxPtr
|
||||
CreateCtx (const std::string & device_id,
|
||||
GstWasapi2EndpointClass endpoint_class, guint pid, gint64 buffer_time,
|
||||
gint64 latency_time, gboolean low_latency)
|
||||
gint64 latency_time, gboolean low_latency, WAVEFORMATEX * mix_format)
|
||||
{
|
||||
auto desc = std::make_shared<RbufCtxDesc> ();
|
||||
desc->device_id = device_id;
|
||||
@ -922,6 +932,8 @@ struct Wasapi2DeviceManager
|
||||
desc->buffer_time = buffer_time;
|
||||
desc->latency_time = latency_time;
|
||||
desc->low_latency = low_latency;
|
||||
if (mix_format)
|
||||
desc->mix_format = copy_wave_format (mix_format);
|
||||
|
||||
{
|
||||
std::lock_guard <std::mutex> lk (lock);
|
||||
@ -993,19 +1005,18 @@ device_manager_com_thread (gpointer manager)
|
||||
lk.unlock ();
|
||||
GST_LOG ("Creating new context");
|
||||
gst_wasapi2_device_manager_create_ctx (enumerator.Get (), desc.get ());
|
||||
|
||||
if (desc->mix_format)
|
||||
CoTaskMemFree (desc->mix_format);
|
||||
|
||||
SetEvent (desc->event_handle);
|
||||
|
||||
if (desc->rbuf) {
|
||||
if (desc->mix_format)
|
||||
CoTaskMemFree (desc->mix_format);
|
||||
auto cmd = std::make_shared < CommandUpdateDevice > (desc->device_id);
|
||||
cmd->ctx = std::move (desc->ctx);
|
||||
|
||||
if (desc->ctx) {
|
||||
auto cmd = std::make_shared < CommandUpdateDevice > ();
|
||||
cmd->ctx = std::move (desc->ctx);
|
||||
|
||||
gst_wasapi2_rbuf_push_command (desc->rbuf, cmd);
|
||||
WaitForSingleObject (cmd->event_handle, INFINITE);
|
||||
}
|
||||
gst_wasapi2_rbuf_push_command (desc->rbuf, cmd);
|
||||
WaitForSingleObject (cmd->event_handle, INFINITE);
|
||||
|
||||
gst_object_unref (desc->rbuf);
|
||||
}
|
||||
@ -1034,6 +1045,8 @@ struct GstWasapi2RbufPrivate
|
||||
{
|
||||
command_handle = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
g_weak_ref_init (&parent, nullptr);
|
||||
|
||||
QueryPerformanceFrequency (&qpc_freq);
|
||||
}
|
||||
|
||||
~GstWasapi2RbufPrivate ()
|
||||
@ -1063,13 +1076,26 @@ struct GstWasapi2RbufPrivate
|
||||
|
||||
std::atomic<float> volume = { 1.0 };
|
||||
std::atomic<bool> mute = { false };
|
||||
std::atomic<bool> allow_dummy = { false };
|
||||
|
||||
bool is_first = true;
|
||||
gint segoffset = 0;
|
||||
guint64 write_frame_offset = 0;
|
||||
guint64 expected_position = 0;
|
||||
|
||||
HANDLE fallback_timer = nullptr;
|
||||
bool fallback_timer_armed = false;
|
||||
UINT64 fallback_frames_processed = 0;
|
||||
bool configured_allow_dummy = false;
|
||||
|
||||
LARGE_INTEGER qpc_freq;
|
||||
LARGE_INTEGER fallback_qpc_base;
|
||||
|
||||
HANDLE monitor_timer = nullptr;
|
||||
bool monitor_timer_armed = false;
|
||||
|
||||
GWeakRef parent;
|
||||
GstWasapi2RbufCallback invalidated_cb;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
@ -1160,7 +1186,8 @@ gst_wasapi2_rbuf_finalize (GObject * object)
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_post_open_error (GstWasapi2Rbuf * self)
|
||||
gst_wasapi2_rbuf_post_open_error (GstWasapi2Rbuf * self,
|
||||
const gchar * device_id)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
auto parent = g_weak_ref_get (&priv->parent);
|
||||
@ -1168,8 +1195,16 @@ gst_wasapi2_rbuf_post_open_error (GstWasapi2Rbuf * self)
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, OPEN_READ_WRITE,
|
||||
(nullptr), ("Failed to open device"));
|
||||
priv->invalidated_cb (parent);
|
||||
|
||||
if (priv->configured_allow_dummy) {
|
||||
GST_ELEMENT_WARNING (parent, RESOURCE, OPEN_READ_WRITE,
|
||||
(nullptr), ("Failed to open device %s", GST_STR_NULL (device_id)));
|
||||
} else {
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, OPEN_READ_WRITE,
|
||||
(nullptr), ("Failed to open device %s", GST_STR_NULL (device_id)));
|
||||
}
|
||||
|
||||
g_object_unref (parent);
|
||||
}
|
||||
|
||||
@ -1184,12 +1219,28 @@ gst_wasapi2_rbuf_post_io_error (GstWasapi2Rbuf * self, HRESULT hr,
|
||||
GST_ERROR_OBJECT (self, "Posting I/O error %s (hr: 0x%x)", error_msg,
|
||||
(guint) hr);
|
||||
|
||||
priv->invalidated_cb (parent);
|
||||
|
||||
if (is_write) {
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, WRITE,
|
||||
("Failed to write to device"), ("%s, hr: 0x%x", error_msg, (guint) hr));
|
||||
if (priv->configured_allow_dummy) {
|
||||
GST_ELEMENT_WARNING (parent, RESOURCE, WRITE,
|
||||
("Failed to write to device"), ("%s, hr: 0x%x", error_msg,
|
||||
(guint) hr));
|
||||
} else {
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, WRITE,
|
||||
("Failed to write to device"), ("%s, hr: 0x%x", error_msg,
|
||||
(guint) hr));
|
||||
}
|
||||
} else {
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, READ,
|
||||
("Failed to read from device"), ("%s hr: 0x%x", error_msg, (guint) hr));
|
||||
if (priv->configured_allow_dummy) {
|
||||
GST_ELEMENT_WARNING (parent, RESOURCE, READ,
|
||||
("Failed to read from device"), ("%s hr: 0x%x", error_msg,
|
||||
(guint) hr));
|
||||
} else {
|
||||
GST_ELEMENT_ERROR (parent, RESOURCE, READ,
|
||||
("Failed to read from device"), ("%s hr: 0x%x", error_msg,
|
||||
(guint) hr));
|
||||
}
|
||||
}
|
||||
|
||||
g_free (error_msg);
|
||||
@ -1216,7 +1267,8 @@ gst_wasapi2_rbuf_create_ctx (GstWasapi2Rbuf * self)
|
||||
auto inst = Wasapi2DeviceManager::GetInstance ();
|
||||
|
||||
return inst->CreateCtx (priv->device_id, priv->endpoint_class,
|
||||
priv->pid, buffer_time, latency_time, priv->low_latency);
|
||||
priv->pid, buffer_time, latency_time, priv->low_latency,
|
||||
priv->mix_format);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1255,12 +1307,7 @@ gst_wasapi2_rbuf_open_device (GstAudioRingBuffer * buf)
|
||||
|
||||
WaitForSingleObject (cmd->event_handle, INFINITE);
|
||||
|
||||
if (!gst_wasapi2_result (cmd->hr)) {
|
||||
GST_ERROR_OBJECT (self, "Couldn't set context");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return gst_wasapi2_result (cmd->hr);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -1291,12 +1338,7 @@ gst_wasapi2_rbuf_acquire (GstAudioRingBuffer * buf,
|
||||
|
||||
WaitForSingleObject (cmd->event_handle, INFINITE);
|
||||
|
||||
if (!gst_wasapi2_result (cmd->hr)) {
|
||||
gst_wasapi2_rbuf_post_open_error (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return gst_wasapi2_result (cmd->hr);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -1323,12 +1365,7 @@ gst_wasapi2_rbuf_start_internal (GstWasapi2Rbuf * self)
|
||||
|
||||
WaitForSingleObject (cmd->event_handle, INFINITE);
|
||||
|
||||
if (!gst_wasapi2_result (cmd->hr)) {
|
||||
gst_wasapi2_rbuf_post_open_error (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return gst_wasapi2_result (cmd->hr);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -1646,24 +1683,22 @@ fill_loopback_silence (GstWasapi2Rbuf * self)
|
||||
return gst_wasapi2_result (hr);
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
static void
|
||||
gst_wasapi2_rbuf_process_acquire (GstWasapi2Rbuf * self,
|
||||
GstAudioRingBufferSpec * spec)
|
||||
{
|
||||
auto buf = GST_AUDIO_RING_BUFFER (self);
|
||||
auto priv = self->priv;
|
||||
if (!priv->ctx)
|
||||
priv->ctx = gst_wasapi2_rbuf_create_ctx (self);
|
||||
|
||||
if (!priv->ctx) {
|
||||
GST_ERROR_OBJECT (self, "No context configured");
|
||||
return E_FAIL;
|
||||
guint client_buf_size = 0;
|
||||
gint period_frames = 480;
|
||||
|
||||
if (priv->ctx) {
|
||||
client_buf_size = priv->ctx->client_buf_size;
|
||||
period_frames = priv->ctx->period;
|
||||
}
|
||||
|
||||
auto & ctx = priv->ctx;
|
||||
|
||||
gint bpf = GST_AUDIO_INFO_BPF (&buf->spec.info);
|
||||
gint period_frames = ctx->period;
|
||||
gint rate = GST_AUDIO_INFO_RATE (&buf->spec.info);
|
||||
gint target_frames = rate / 2; /* 500ms duration */
|
||||
|
||||
@ -1677,11 +1712,11 @@ gst_wasapi2_rbuf_process_acquire (GstWasapi2Rbuf * self,
|
||||
|
||||
GST_INFO_OBJECT (self,
|
||||
"Buffer size: %d frames, period: %d frames, segsize: %d bytes, "
|
||||
"segtotal: %d", ctx->client_buf_size, ctx->period,
|
||||
"segtotal: %d", client_buf_size, period_frames,
|
||||
spec->segsize, spec->segtotal);
|
||||
|
||||
GstAudioChannelPosition *position = nullptr;
|
||||
gst_wasapi2_util_waveformatex_to_channel_mask (ctx->mix_format, &position);
|
||||
gst_wasapi2_util_waveformatex_to_channel_mask (priv->mix_format, &position);
|
||||
if (position)
|
||||
gst_audio_ring_buffer_set_channel_positions (buf, position);
|
||||
g_free (position);
|
||||
@ -1690,8 +1725,6 @@ gst_wasapi2_rbuf_process_acquire (GstWasapi2Rbuf * self,
|
||||
buf->memory = (guint8 *) g_malloc (buf->size);
|
||||
gst_audio_format_info_fill_silence (buf->spec.info.finfo,
|
||||
buf->memory, buf->size);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
@ -1704,12 +1737,87 @@ gst_wasapi2_rbuf_process_release (GstWasapi2Rbuf * self)
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
gst_wasapi2_rbuf_process_start (GstWasapi2Rbuf * self)
|
||||
static void
|
||||
gst_wasapi2_rbuf_start_fallback_timer (GstWasapi2Rbuf * self)
|
||||
{
|
||||
auto rb = GST_AUDIO_RING_BUFFER_CAST (self);
|
||||
auto priv = self->priv;
|
||||
|
||||
if (priv->fallback_timer_armed || !priv->configured_allow_dummy)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Start fallback timer");
|
||||
|
||||
auto period_frames = rb->spec.segsize / GST_AUDIO_INFO_BPF (&rb->spec.info);
|
||||
UINT64 period_100ns = (10000000ULL * period_frames) /
|
||||
GST_AUDIO_INFO_RATE (&rb->spec.info);
|
||||
|
||||
LARGE_INTEGER due_time;
|
||||
due_time.QuadPart = -static_cast < LONGLONG > (period_100ns);
|
||||
|
||||
SetWaitableTimer (priv->fallback_timer,
|
||||
&due_time,
|
||||
static_cast < LONG > (period_100ns / 10000), nullptr, nullptr, FALSE);
|
||||
|
||||
QueryPerformanceCounter (&priv->fallback_qpc_base);
|
||||
priv->fallback_frames_processed = 0;
|
||||
priv->fallback_timer_armed = true;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_stop_fallback_timer (GstWasapi2Rbuf * self)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
|
||||
if (!priv->ctx) {
|
||||
if (!priv->fallback_timer_armed)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Stop fallback timer");
|
||||
|
||||
CancelWaitableTimer (priv->fallback_timer);
|
||||
priv->fallback_timer_armed = false;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_start_monitor_timer (GstWasapi2Rbuf * self)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
|
||||
if (priv->monitor_timer_armed)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Start monitor timer");
|
||||
|
||||
/* Run 15ms timer to monitor device status */
|
||||
LARGE_INTEGER due_time;
|
||||
due_time.QuadPart = -1500000LL;
|
||||
|
||||
SetWaitableTimer (priv->monitor_timer,
|
||||
&due_time, 15, nullptr, nullptr, FALSE);
|
||||
|
||||
priv->monitor_timer_armed = true;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_stop_monitor_timer (GstWasapi2Rbuf * self)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
|
||||
if (!priv->monitor_timer_armed)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Stop monitor timer");
|
||||
|
||||
CancelWaitableTimer (priv->monitor_timer);
|
||||
priv->monitor_timer_armed = false;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
gst_wasapi2_rbuf_process_start (GstWasapi2Rbuf * self, gboolean reset_offset)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
|
||||
if (!priv->ctx && !priv->configured_allow_dummy) {
|
||||
GST_WARNING_OBJECT (self, "No context to start");
|
||||
return E_FAIL;
|
||||
}
|
||||
@ -1717,16 +1825,31 @@ gst_wasapi2_rbuf_process_start (GstWasapi2Rbuf * self)
|
||||
if (priv->running)
|
||||
return S_OK;
|
||||
|
||||
auto hr = priv->ctx->Start ();
|
||||
if (gst_wasapi2_result (hr)) {
|
||||
priv->running = true;
|
||||
priv->is_first = true;
|
||||
priv->is_first = true;
|
||||
if (reset_offset)
|
||||
priv->segoffset = 0;
|
||||
priv->write_frame_offset = 0;
|
||||
priv->expected_position = 0;
|
||||
priv->write_frame_offset = 0;
|
||||
priv->expected_position = 0;
|
||||
|
||||
if (priv->ctx) {
|
||||
auto hr = priv->ctx->Start ();
|
||||
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't start device");
|
||||
gst_wasapi2_rbuf_post_open_error (self, priv->ctx->device_id.c_str ());
|
||||
if (!priv->configured_allow_dummy)
|
||||
return hr;
|
||||
|
||||
gst_wasapi2_rbuf_start_fallback_timer (self);
|
||||
}
|
||||
} else {
|
||||
gst_wasapi2_rbuf_start_fallback_timer (self);
|
||||
}
|
||||
|
||||
return hr;
|
||||
gst_wasapi2_rbuf_start_monitor_timer (self);
|
||||
priv->running = true;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -1753,9 +1876,73 @@ gst_wasapi2_rbuf_process_stop (GstWasapi2Rbuf * self)
|
||||
priv->write_frame_offset = 0;
|
||||
priv->expected_position = 0;
|
||||
|
||||
gst_wasapi2_rbuf_stop_fallback_timer (self);
|
||||
gst_wasapi2_rbuf_stop_monitor_timer (self);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_discard_frames (GstWasapi2Rbuf * self, guint frames)
|
||||
{
|
||||
auto rb = GST_AUDIO_RING_BUFFER_CAST (self);
|
||||
auto priv = self->priv;
|
||||
guint len = frames * GST_AUDIO_INFO_BPF (&rb->spec.info);
|
||||
|
||||
while (len > 0) {
|
||||
gint seg;
|
||||
guint8 *ptr;
|
||||
gint avail;
|
||||
|
||||
if (!gst_audio_ring_buffer_prepare_read (rb, &seg, &ptr, &avail))
|
||||
return;
|
||||
|
||||
avail -= priv->segoffset;
|
||||
gint to_consume = MIN ((gint) len, avail);
|
||||
|
||||
priv->segoffset += to_consume;
|
||||
len -= to_consume;
|
||||
|
||||
if (priv->segoffset == rb->spec.segsize) {
|
||||
gst_audio_ring_buffer_clear (rb, seg);
|
||||
gst_audio_ring_buffer_advance (rb, 1);
|
||||
priv->segoffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_rbuf_insert_silence_frames (GstWasapi2Rbuf * self, guint frames)
|
||||
{
|
||||
auto rb = GST_AUDIO_RING_BUFFER_CAST (self);
|
||||
auto priv = self->priv;
|
||||
guint bpf = GST_AUDIO_INFO_BPF (&rb->spec.info);
|
||||
guint len = frames * bpf;
|
||||
|
||||
while (len > 0) {
|
||||
gint segment;
|
||||
guint8 *writeptr;
|
||||
gint avail;
|
||||
|
||||
if (!gst_audio_ring_buffer_prepare_read (rb, &segment, &writeptr, &avail))
|
||||
break;
|
||||
|
||||
avail -= priv->segoffset;
|
||||
gint to_write = MIN ((gint) len, avail);
|
||||
|
||||
gst_audio_format_info_fill_silence (rb->spec.info.finfo,
|
||||
writeptr + priv->segoffset, to_write);
|
||||
|
||||
priv->segoffset += to_write;
|
||||
len -= to_write;
|
||||
|
||||
if (priv->segoffset == rb->spec.segsize) {
|
||||
gst_audio_ring_buffer_advance (rb, 1);
|
||||
priv->segoffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
{
|
||||
@ -1771,7 +1958,22 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
auto dummy_render = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
auto dummy_capture = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
HANDLE waitables[] = { dummy_render, dummy_capture, priv->command_handle };
|
||||
priv->fallback_timer = CreateWaitableTimerExW (nullptr,
|
||||
nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
|
||||
if (!priv->fallback_timer) {
|
||||
GST_WARNING_OBJECT (self,
|
||||
"High-resolution timer not available, using default");
|
||||
priv->fallback_timer = CreateWaitableTimer (nullptr, FALSE, nullptr);
|
||||
}
|
||||
|
||||
/* Another timer to detect device-removed state, since I/O event
|
||||
* would not be singalled on device-removed state */
|
||||
priv->monitor_timer = CreateWaitableTimer (nullptr, FALSE, nullptr);
|
||||
|
||||
HANDLE waitables[] = { dummy_render, dummy_capture,
|
||||
priv->fallback_timer, priv->monitor_timer, priv->command_handle
|
||||
};
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Entering loop");
|
||||
|
||||
@ -1792,8 +1994,10 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
hr = gst_wasapi2_rbuf_process_write (self);
|
||||
}
|
||||
|
||||
if (FAILED (hr))
|
||||
if (FAILED (hr)) {
|
||||
gst_wasapi2_rbuf_post_io_error (self, hr, TRUE);
|
||||
gst_wasapi2_rbuf_start_fallback_timer (self);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
@ -1807,11 +2011,57 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
hr = S_OK;
|
||||
}
|
||||
|
||||
if (FAILED (hr))
|
||||
if (FAILED (hr)) {
|
||||
gst_wasapi2_rbuf_post_io_error (self, hr, FALSE);
|
||||
gst_wasapi2_rbuf_start_fallback_timer (self);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 2:
|
||||
{
|
||||
if (!priv->running || !priv->fallback_timer_armed)
|
||||
break;
|
||||
|
||||
LARGE_INTEGER qpc_now;
|
||||
QueryPerformanceCounter (&qpc_now);
|
||||
|
||||
LONGLONG elapsed = qpc_now.QuadPart - priv->fallback_qpc_base.QuadPart;
|
||||
UINT64 elapsed_100ns = elapsed * 10000000ULL / priv->qpc_freq.QuadPart;
|
||||
UINT32 rate = priv->mix_format->nSamplesPerSec;
|
||||
UINT64 expected_frames = (elapsed_100ns * rate) / 10000000ULL;
|
||||
UINT64 delta = expected_frames - priv->fallback_frames_processed;
|
||||
|
||||
if (delta > 0) {
|
||||
GST_TRACE_OBJECT (self,
|
||||
"procssing fallback %u frames", (guint) delta);
|
||||
|
||||
if (priv->endpoint_class == GST_WASAPI2_ENDPOINT_CLASS_RENDER)
|
||||
gst_wasapi2_rbuf_discard_frames (self, (guint) delta);
|
||||
else
|
||||
gst_wasapi2_rbuf_insert_silence_frames (self, (guint) delta);
|
||||
|
||||
priv->fallback_frames_processed += delta;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 3:
|
||||
{
|
||||
if (!priv->running || !priv->ctx || !priv->monitor_timer_armed)
|
||||
break;
|
||||
|
||||
UINT32 dummy;
|
||||
auto hr = priv->ctx->client->GetCurrentPadding (&dummy);
|
||||
if (hr == AUDCLNT_E_DEVICE_INVALIDATED && !priv->ctx->error_posted) {
|
||||
priv->ctx->error_posted = true;
|
||||
gst_wasapi2_rbuf_post_io_error (self, AUDCLNT_E_DEVICE_INVALIDATED,
|
||||
priv->endpoint_class == GST_WASAPI2_ENDPOINT_CLASS_RENDER);
|
||||
gst_wasapi2_rbuf_start_fallback_timer (self);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 4:
|
||||
/* Wakeup event for event processing */
|
||||
break;
|
||||
default:
|
||||
@ -1861,30 +2111,41 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
if (priv->opened) {
|
||||
GST_DEBUG_OBJECT (self, "Updating device");
|
||||
|
||||
priv->ctx = ucmd->ctx;
|
||||
waitables[0] = priv->ctx->render_event;
|
||||
waitables[1] = priv->ctx->capture_event;
|
||||
gst_wasapi2_rbuf_stop_fallback_timer (self);
|
||||
|
||||
if (priv->mute)
|
||||
priv->ctx->SetVolume (0);
|
||||
else
|
||||
priv->ctx->SetVolume (priv->volume);
|
||||
priv->ctx = ucmd->ctx;
|
||||
|
||||
if (priv->ctx) {
|
||||
waitables[0] = priv->ctx->render_event;
|
||||
waitables[1] = priv->ctx->capture_event;
|
||||
|
||||
if (priv->mute)
|
||||
priv->ctx->SetVolume (0);
|
||||
else
|
||||
priv->ctx->SetVolume (priv->volume);
|
||||
} else {
|
||||
waitables[0] = dummy_render;
|
||||
waitables[1] = dummy_capture;
|
||||
|
||||
gst_wasapi2_rbuf_post_open_error (self,
|
||||
ucmd->device_id.c_str ());
|
||||
if (!priv->configured_allow_dummy) {
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->running) {
|
||||
auto hr = priv->ctx->Start ();
|
||||
if (gst_wasapi2_result (hr)) {
|
||||
priv->is_first = true;
|
||||
priv->write_frame_offset = 0;
|
||||
priv->expected_position = 0;
|
||||
} else {
|
||||
gst_wasapi2_rbuf_post_open_error (self);
|
||||
}
|
||||
priv->running = false;
|
||||
gst_wasapi2_rbuf_process_start (self, FALSE);
|
||||
}
|
||||
}
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
}
|
||||
case CommandType::Open:
|
||||
priv->configured_allow_dummy = priv->allow_dummy;
|
||||
ClearMixFormat (&priv->mix_format);
|
||||
priv->ctx = gst_wasapi2_rbuf_create_ctx (self);
|
||||
|
||||
if (priv->ctx) {
|
||||
@ -1898,14 +2159,27 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
priv->ctx->SetVolume (priv->volume);
|
||||
|
||||
priv->opened = true;
|
||||
ClearMixFormat (&priv->mix_format);
|
||||
priv->mix_format = copy_wave_format (priv->ctx->mix_format);
|
||||
cmd->hr = S_OK;
|
||||
} else {
|
||||
gst_clear_caps (&priv->caps);
|
||||
waitables[0] = dummy_render;
|
||||
waitables[1] = dummy_capture;
|
||||
cmd->hr = E_FAIL;
|
||||
gst_wasapi2_rbuf_post_open_error (self, priv->device_id.c_str ());
|
||||
|
||||
if (priv->configured_allow_dummy) {
|
||||
priv->mix_format = gst_wasapi2_get_default_mix_format ();
|
||||
|
||||
auto scaps = gst_static_caps_get (&template_caps);
|
||||
gst_wasapi2_util_parse_waveformatex (priv->mix_format,
|
||||
scaps, &priv->caps, nullptr);
|
||||
gst_caps_unref (scaps);
|
||||
priv->opened = true;
|
||||
|
||||
cmd->hr = S_OK;
|
||||
} else {
|
||||
cmd->hr = E_FAIL;
|
||||
}
|
||||
}
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
@ -1918,22 +2192,58 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
SetEvent (cmd->event_handle);
|
||||
priv->opened = false;
|
||||
ClearMixFormat (&priv->mix_format);
|
||||
gst_wasapi2_rbuf_stop_fallback_timer (self);
|
||||
break;
|
||||
case CommandType::Acquire:
|
||||
{
|
||||
auto acquire_cmd =
|
||||
std::dynamic_pointer_cast < CommandAcquire > (cmd);
|
||||
cmd->hr = gst_wasapi2_rbuf_process_acquire (self,
|
||||
acquire_cmd->spec);
|
||||
|
||||
if (!priv->ctx) {
|
||||
priv->ctx = gst_wasapi2_rbuf_create_ctx (self);
|
||||
if (!priv->ctx) {
|
||||
GST_WARNING_OBJECT (self, "No context configured");
|
||||
gst_wasapi2_rbuf_post_open_error (self,
|
||||
priv->device_id.c_str ());
|
||||
if (!priv->configured_allow_dummy) {
|
||||
cmd->hr = E_FAIL;
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
priv->opened = true;
|
||||
if (priv->ctx) {
|
||||
waitables[0] = priv->ctx->render_event;
|
||||
waitables[1] = priv->ctx->capture_event;
|
||||
gst_caps_replace (&priv->caps, priv->ctx->caps);
|
||||
|
||||
if (priv->mute)
|
||||
priv->ctx->SetVolume (0);
|
||||
else
|
||||
priv->ctx->SetVolume (priv->volume);
|
||||
|
||||
ClearMixFormat (&priv->mix_format);
|
||||
priv->mix_format = copy_wave_format (priv->ctx->mix_format);
|
||||
} else {
|
||||
waitables[0] = dummy_render;
|
||||
waitables[1] = dummy_capture;
|
||||
}
|
||||
|
||||
gst_wasapi2_rbuf_process_acquire (self, acquire_cmd->spec);
|
||||
|
||||
cmd->hr = S_OK;
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
}
|
||||
case CommandType::Release:
|
||||
cmd->hr = gst_wasapi2_rbuf_process_release (self);
|
||||
gst_wasapi2_rbuf_stop_fallback_timer (self);
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
case CommandType::Start:
|
||||
cmd->hr = gst_wasapi2_rbuf_process_start (self);
|
||||
cmd->hr = gst_wasapi2_rbuf_process_start (self, TRUE);
|
||||
SetEvent (cmd->event_handle);
|
||||
break;
|
||||
case CommandType::Stop:
|
||||
@ -1982,6 +2292,12 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
|
||||
CloseHandle (dummy_render);
|
||||
CloseHandle (dummy_capture);
|
||||
|
||||
CancelWaitableTimer (priv->monitor_timer);
|
||||
CloseHandle (priv->monitor_timer);
|
||||
|
||||
CancelWaitableTimer (priv->fallback_timer);
|
||||
CloseHandle (priv->fallback_timer);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -2008,12 +2324,13 @@ gst_wasapi2_rbuf_delay (GstAudioRingBuffer * buf)
|
||||
}
|
||||
|
||||
GstWasapi2Rbuf *
|
||||
gst_wasapi2_rbuf_new (gpointer parent)
|
||||
gst_wasapi2_rbuf_new (gpointer parent, GstWasapi2RbufCallback callback)
|
||||
{
|
||||
auto self = (GstWasapi2Rbuf *) g_object_new (GST_TYPE_WASAPI2_RBUF, nullptr);
|
||||
gst_object_ref_sink (self);
|
||||
|
||||
auto priv = self->priv;
|
||||
priv->invalidated_cb = callback;
|
||||
g_weak_ref_set (&priv->parent, parent);
|
||||
priv->thread = g_thread_new ("GstWasapi2Rbuf",
|
||||
(GThreadFunc) gst_wasapi2_rbuf_loop_thread, self);
|
||||
@ -2097,3 +2414,11 @@ gst_wasapi2_rbuf_set_device_mute_monitoring (GstWasapi2Rbuf * rbuf,
|
||||
|
||||
priv->monitor_device_mute.store (value, std::memory_order_release);
|
||||
}
|
||||
|
||||
void
|
||||
gst_wasapi2_rbuf_set_continue_on_error (GstWasapi2Rbuf * rbuf, gboolean value)
|
||||
{
|
||||
auto priv = rbuf->priv;
|
||||
|
||||
priv->allow_dummy = value;
|
||||
}
|
||||
|
@ -29,7 +29,10 @@ G_BEGIN_DECLS
|
||||
G_DECLARE_FINAL_TYPE (GstWasapi2Rbuf, gst_wasapi2_rbuf,
|
||||
GST, WASAPI2_RBUF, GstAudioRingBuffer);
|
||||
|
||||
GstWasapi2Rbuf * gst_wasapi2_rbuf_new (gpointer parent);
|
||||
typedef void (*GstWasapi2RbufCallback) (gpointer elem);
|
||||
|
||||
GstWasapi2Rbuf * gst_wasapi2_rbuf_new (gpointer parent,
|
||||
GstWasapi2RbufCallback callback);
|
||||
|
||||
void gst_wasapi2_rbuf_set_device (GstWasapi2Rbuf * rbuf,
|
||||
const gchar * device_id,
|
||||
@ -52,5 +55,8 @@ gdouble gst_wasapi2_rbuf_get_volume (GstWasapi2Rbuf * rbuf);
|
||||
void gst_wasapi2_rbuf_set_device_mute_monitoring (GstWasapi2Rbuf * rbuf,
|
||||
gboolean value);
|
||||
|
||||
void gst_wasapi2_rbuf_set_continue_on_error (GstWasapi2Rbuf * rbuf,
|
||||
gboolean value);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "gstwasapi2util.h"
|
||||
#include "gstwasapi2rbuf.h"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_sink_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_sink_debug
|
||||
@ -59,6 +60,7 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
#define DEFAULT_LOW_LATENCY FALSE
|
||||
#define DEFAULT_MUTE FALSE
|
||||
#define DEFAULT_VOLUME 1.0
|
||||
#define DEFAULT_CONTINUE_ON_ERROR FALSE
|
||||
|
||||
enum
|
||||
{
|
||||
@ -68,6 +70,7 @@ enum
|
||||
PROP_MUTE,
|
||||
PROP_VOLUME,
|
||||
PROP_DISPATCHER,
|
||||
PROP_CONTINUE_ON_ERROR,
|
||||
};
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
@ -82,10 +85,12 @@ struct GstWasapi2SinkPrivate
|
||||
GstWasapi2Rbuf *rbuf = nullptr;
|
||||
|
||||
std::mutex lock;
|
||||
std::atomic<bool> device_invalidated = { false };
|
||||
|
||||
/* properties */
|
||||
gchar *device_id = nullptr;;
|
||||
gboolean low_latency = DEFAULT_LOW_LATENCY;
|
||||
gboolean continue_on_error = DEFAULT_CONTINUE_ON_ERROR;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
@ -166,6 +171,24 @@ gst_wasapi2_sink_class_init (GstWasapi2SinkClass * klass)
|
||||
(GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_WRITABLE |
|
||||
G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
/**
|
||||
* GstWasapi2Sink:continue-on-error:
|
||||
*
|
||||
* If enabled, wasapi2sink will post a warning message instead of an error,
|
||||
* when device failures occur, such as open failure, I/O error,
|
||||
* or device removal.
|
||||
* The element will continue to consume audio buffers and behave as if
|
||||
* a render device were active, allowing pipeline to keep running even when
|
||||
* no audio endpoint is available
|
||||
*
|
||||
* Since: 1.28
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_CONTINUE_ON_ERROR,
|
||||
g_param_spec_boolean ("continue-on-error", "Continue On Error",
|
||||
"Continue running and consume buffers on device failure",
|
||||
DEFAULT_CONTINUE_ON_ERROR, (GParamFlags) (GST_PARAM_MUTABLE_READY |
|
||||
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",
|
||||
@ -181,12 +204,23 @@ gst_wasapi2_sink_class_init (GstWasapi2SinkClass * klass)
|
||||
0, "Windows audio session API sink");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_sink_on_invalidated (gpointer elem)
|
||||
{
|
||||
auto self = GST_WASAPI2_SINK (elem);
|
||||
auto priv = self->priv;
|
||||
|
||||
GST_WARNING_OBJECT (self, "Device invalidated");
|
||||
|
||||
priv->device_invalidated = true;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_sink_init (GstWasapi2Sink * self)
|
||||
{
|
||||
auto priv = new GstWasapi2SinkPrivate ();
|
||||
|
||||
priv->rbuf = gst_wasapi2_rbuf_new (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);
|
||||
|
||||
@ -205,6 +239,21 @@ gst_wasapi2_sink_finalize (GObject * object)
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_sink_set_device (GstWasapi2Sink * self, bool updated)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
bool expected = true;
|
||||
bool set_device = priv->device_invalidated.compare_exchange_strong (expected,
|
||||
false);
|
||||
|
||||
if (!set_device && !updated)
|
||||
return;
|
||||
|
||||
gst_wasapi2_rbuf_set_device (priv->rbuf, priv->device_id,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, priv->low_latency);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
@ -218,22 +267,26 @@ gst_wasapi2_sink_set_property (GObject * object, guint prop_id,
|
||||
case PROP_DEVICE:
|
||||
{
|
||||
auto new_val = g_value_get_string (value);
|
||||
bool updated = false;
|
||||
if (g_strcmp0 (new_val, priv->device_id) != 0) {
|
||||
g_free (priv->device_id);
|
||||
priv->device_id = g_strdup (new_val);
|
||||
gst_wasapi2_rbuf_set_device (priv->rbuf, priv->device_id,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, priv->low_latency);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_sink_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_LOW_LATENCY:
|
||||
{
|
||||
auto new_val = g_value_get_boolean (value);
|
||||
bool updated = false;
|
||||
if (new_val != priv->low_latency) {
|
||||
priv->low_latency = new_val;
|
||||
gst_wasapi2_rbuf_set_device (priv->rbuf, priv->device_id,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_RENDER, 0, priv->low_latency);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_sink_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_MUTE:
|
||||
@ -245,6 +298,11 @@ gst_wasapi2_sink_set_property (GObject * object, guint prop_id,
|
||||
case PROP_DISPATCHER:
|
||||
/* Unused */
|
||||
break;
|
||||
case PROP_CONTINUE_ON_ERROR:
|
||||
priv->continue_on_error = g_value_get_boolean (value);
|
||||
gst_wasapi2_rbuf_set_continue_on_error (priv->rbuf,
|
||||
priv->continue_on_error);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -273,6 +331,9 @@ gst_wasapi2_sink_get_property (GObject * object, guint prop_id,
|
||||
case PROP_VOLUME:
|
||||
g_value_set_double (value, gst_wasapi2_rbuf_get_volume (priv->rbuf));
|
||||
break;
|
||||
case PROP_CONTINUE_ON_ERROR:
|
||||
g_value_set_boolean (value, priv->continue_on_error);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "gstwasapi2util.h"
|
||||
#include "gstwasapi2rbuf.h"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_src_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_src_debug
|
||||
@ -120,6 +121,7 @@ gst_wasapi2_src_loopback_mode_get_type (void)
|
||||
#define DEFAULT_LOOPBACK FALSE
|
||||
#define DEFAULT_LOOPBACK_MODE GST_WASAPI2_SRC_LOOPBACK_DEFAULT
|
||||
#define DEFAULT_LOOPBACK_SILENCE_ON_DEVICE_MUTE FALSE
|
||||
#define DEFAULT_CONTINUE_ON_ERROR FALSE
|
||||
|
||||
enum
|
||||
{
|
||||
@ -133,6 +135,7 @@ enum
|
||||
PROP_LOOPBACK_MODE,
|
||||
PROP_LOOPBACK_TARGET_PID,
|
||||
PROP_LOOPBACK_SILENCE_ON_DEVICE_MUTE,
|
||||
PROP_CONTINUE_ON_ERROR,
|
||||
};
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
@ -147,6 +150,7 @@ struct GstWasapi2SrcPrivate
|
||||
GstWasapi2Rbuf *rbuf = nullptr;
|
||||
|
||||
std::mutex lock;
|
||||
std::atomic<bool> device_invalidated = { false };
|
||||
|
||||
/* properties */
|
||||
gchar *device_id = nullptr;
|
||||
@ -156,6 +160,7 @@ struct GstWasapi2SrcPrivate
|
||||
guint loopback_pid = 0;
|
||||
gboolean loopback_silence_on_device_mute =
|
||||
DEFAULT_LOOPBACK_SILENCE_ON_DEVICE_MUTE;
|
||||
gboolean continue_on_error = DEFAULT_CONTINUE_ON_ERROR;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
@ -300,6 +305,24 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
||||
(GParamFlags) (GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE |
|
||||
G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
/**
|
||||
* GstWasapi2Src:continue-on-error:
|
||||
*
|
||||
* If enabled, wasapi2src will post a warning message instead of an error,
|
||||
* when device failures occur, such as open failure, I/O error,
|
||||
* or device removal.
|
||||
* The element will continue to produce audio buffers and behave as if
|
||||
* a capture device were active, allowing pipeline to keep running even when
|
||||
* no audio endpoint is available
|
||||
*
|
||||
* Since: 1.28
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_CONTINUE_ON_ERROR,
|
||||
g_param_spec_boolean ("continue-on-error", "Continue On Error",
|
||||
"Continue running and produce buffers on device failure",
|
||||
DEFAULT_CONTINUE_ON_ERROR, (GParamFlags) (GST_PARAM_MUTABLE_READY |
|
||||
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",
|
||||
@ -320,12 +343,23 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_src_on_invalidated (gpointer elem)
|
||||
{
|
||||
auto self = GST_WASAPI2_SRC (elem);
|
||||
auto priv = self->priv;
|
||||
|
||||
GST_WARNING_OBJECT (self, "Device invalidated");
|
||||
|
||||
priv->device_invalidated = true;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_src_init (GstWasapi2Src * self)
|
||||
{
|
||||
auto priv = new GstWasapi2SrcPrivate ();
|
||||
|
||||
priv->rbuf = gst_wasapi2_rbuf_new (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);
|
||||
|
||||
@ -343,10 +377,16 @@ gst_wasapi2_src_finalize (GObject * object)
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_src_set_device (GstWasapi2Src * self)
|
||||
gst_wasapi2_src_set_device (GstWasapi2Src * self, bool updated)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
GstWasapi2EndpointClass device_class = GST_WASAPI2_ENDPOINT_CLASS_CAPTURE;
|
||||
bool expected = true;
|
||||
bool set_device = priv->device_invalidated.compare_exchange_strong (expected,
|
||||
false);
|
||||
|
||||
if (!set_device && !updated)
|
||||
return;
|
||||
|
||||
if (priv->loopback_pid) {
|
||||
if (priv->loopback_mode == GST_WASAPI2_SRC_LOOPBACK_INCLUDE_PROCESS_TREE) {
|
||||
@ -378,20 +418,26 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
|
||||
case PROP_DEVICE:
|
||||
{
|
||||
auto new_val = g_value_get_string (value);
|
||||
bool updated = false;
|
||||
if (g_strcmp0 (new_val, priv->device_id) != 0) {
|
||||
g_free (priv->device_id);
|
||||
priv->device_id = g_strdup (new_val);
|
||||
gst_wasapi2_src_set_device (self);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_src_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_LOW_LATENCY:
|
||||
{
|
||||
auto new_val = g_value_get_boolean (value);
|
||||
bool updated = false;
|
||||
if (new_val != priv->low_latency) {
|
||||
priv->low_latency = new_val;
|
||||
gst_wasapi2_src_set_device (self);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_src_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_MUTE:
|
||||
@ -406,28 +452,37 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
|
||||
case PROP_LOOPBACK:
|
||||
{
|
||||
auto new_val = g_value_get_boolean (value);
|
||||
bool updated = false;
|
||||
if (new_val != priv->loopback) {
|
||||
priv->loopback = new_val;
|
||||
gst_wasapi2_src_set_device (self);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_src_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_LOOPBACK_MODE:
|
||||
{
|
||||
auto new_val = (GstWasapi2SrcLoopbackMode) g_value_get_enum (value);
|
||||
bool updated = false;
|
||||
if (new_val != priv->loopback_mode) {
|
||||
priv->loopback_mode = new_val;
|
||||
gst_wasapi2_src_set_device (self);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_src_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_LOOPBACK_TARGET_PID:
|
||||
{
|
||||
auto new_val = g_value_get_uint (value);
|
||||
bool updated = false;
|
||||
if (new_val != priv->loopback_pid) {
|
||||
priv->loopback_pid = new_val;
|
||||
gst_wasapi2_src_set_device (self);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
gst_wasapi2_src_set_device (self, updated);
|
||||
break;
|
||||
}
|
||||
case PROP_LOOPBACK_SILENCE_ON_DEVICE_MUTE:
|
||||
@ -435,6 +490,11 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
|
||||
gst_wasapi2_rbuf_set_device_mute_monitoring (priv->rbuf,
|
||||
priv->loopback_silence_on_device_mute);
|
||||
break;
|
||||
case PROP_CONTINUE_ON_ERROR:
|
||||
priv->continue_on_error = g_value_get_boolean (value);
|
||||
gst_wasapi2_rbuf_set_continue_on_error (priv->rbuf,
|
||||
priv->continue_on_error);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -475,6 +535,9 @@ gst_wasapi2_src_get_property (GObject * object, guint prop_id,
|
||||
case PROP_LOOPBACK_SILENCE_ON_DEVICE_MUTE:
|
||||
g_value_set_boolean (value, priv->loopback_silence_on_device_mute);
|
||||
break;
|
||||
case PROP_CONTINUE_ON_ERROR:
|
||||
g_value_set_boolean (value, priv->continue_on_error);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user