self->client_needs_restart = FALSE;
self->adapter = gst_adapter_new ();
+ /* Extra event handles used for loopback */
+ self->loopback_event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
+ self->loopback_cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
+
CoInitializeEx (NULL, COINIT_MULTITHREADED);
}
self->capture_client = NULL;
}
+ if (self->loopback_client != NULL) {
+ IUnknown_Release (self->loopback_client);
+ self->loopback_client = NULL;
+ }
+
+ if (self->loopback_event_handle != NULL) {
+ CloseHandle (self->loopback_event_handle);
+ self->loopback_event_handle = NULL;
+ }
+
+ if (self->loopback_cancellable != NULL) {
+ CloseHandle (self->loopback_cancellable);
+ self->loopback_cancellable = NULL;
+ }
+
G_OBJECT_CLASS (parent_class)->dispose (object);
}
gboolean res = FALSE;
IAudioClient *client = NULL;
IMMDevice *device = NULL;
+ IMMDevice *loopback_device = NULL;
if (self->client)
return TRUE;
goto beach;
}
+ /* An oddness of wasapi loopback feature is that capture client will not
+ * provide any audio data if there is no outputting sound.
+ * To workaround this problem, probably we can add timeout around loop
+ * in this case but it's glitch prone. So, instead of timeout,
+ * we will keep pusing silence data to into wasapi client so that make audio
+ * client report audio data in any case
+ */
+ if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self),
+ eRender, self->role, self->device_strid,
+ &loopback_device, &self->loopback_client)) {
+ if (!self->device_strid)
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to get default device for loopback"));
+ else
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to open device %S", self->device_strid));
+ goto beach;
+
+ /* no need to hold this object */
+ IUnknown_Release (loopback_device);
+ }
+
self->client = client;
self->device = device;
res = TRUE;
self->client = NULL;
}
+ if (self->loopback_client != NULL) {
+ IUnknown_Release (self->loopback_client);
+ self->loopback_client = NULL;
+ }
+
return TRUE;
}
+static gpointer
+gst_wasapi_src_loopback_silence_feeding_thread (GstWasapiSrc * self)
+{
+ HRESULT hr;
+ UINT32 buffer_frames;
+ gboolean res G_GNUC_UNUSED = FALSE;
+ BYTE *data;
+ DWORD dwWaitResult;
+ HANDLE event_handle[2];
+ UINT32 padding;
+ UINT32 n_frames;
+
+ /* NOTE: if this task cause glitch, we need to consider thread priority
+ * adjusing. See gstaudioutilsprivate.c (e.g., AvSetMmThreadCharacteristics)
+ * for this context */
+
+ GST_INFO_OBJECT (self, "Run loopback silence feeding thread");
+
+ event_handle[0] = self->loopback_event_handle;
+ event_handle[1] = self->loopback_cancellable;
+
+ hr = IAudioClient_GetBufferSize (self->loopback_client, &buffer_frames);
+ HR_FAILED_GOTO (hr, IAudioClient::GetBufferSize, beach);
+
+ hr = IAudioClient_SetEventHandle (self->loopback_client,
+ self->loopback_event_handle);
+ HR_FAILED_GOTO (hr, IAudioClient::SetEventHandle, beach);
+
+ /* To avoid start-up glitches, before starting the streaming, we fill the
+ * buffer with silence as recommended by the documentation:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370879%28v=vs.85%29.aspx */
+ hr = IAudioRenderClient_GetBuffer (self->loopback_render_client,
+ buffer_frames, &data);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::GetBuffer, beach);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->loopback_render_client,
+ buffer_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::ReleaseBuffer, beach);
+
+ hr = IAudioClient_Start (self->loopback_client);
+ HR_FAILED_GOTO (hr, IAudioClock::Start, beach);
+
+ /* There is an OS bug prior to Windows 10, that is loopback capture client
+ * will not receive event (in case of event-driven mode).
+ * A guide for workaround this case is that signal it whenever render client
+ * writes data.
+ * See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
+ */
+
+ /* Signal for read thread to wakeup */
+ SetEvent (self->event_handle);
+
+ /* Ok, now we are ready for running for feeding silence data */
+ while (1) {
+ dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) {
+ GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+ (guint) dwWaitResult);
+ goto stop;
+ }
+
+ /* Stopping was requested from unprepare() */
+ if (dwWaitResult == WAIT_OBJECT_0 + 1) {
+ GST_DEBUG_OBJECT (self, "operation was cancelled");
+ goto stop;
+ }
+
+ hr = IAudioClient_GetCurrentPadding (self->loopback_client, &padding);
+ HR_FAILED_GOTO (hr, IAudioClock::Start, stop);
+
+ if (buffer_frames < padding) {
+ GST_WARNING_OBJECT (self,
+ "Current padding %d is too large (buffer size %d)",
+ padding, buffer_frames);
+ n_frames = 0;
+ } else {
+ n_frames = buffer_frames - padding;
+ }
+
+ hr = IAudioRenderClient_GetBuffer (self->loopback_render_client, n_frames,
+ &data);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::GetBuffer, stop);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->loopback_render_client,
+ n_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::ReleaseBuffer, stop);
+
+ /* Signal for read thread to wakeup */
+ SetEvent (self->event_handle);
+ }
+
+stop:
+ IAudioClient_Stop (self->loopback_client);
+
+beach:
+ GST_INFO_OBJECT (self, "Terminate loopback silence feeding thread");
+
+ return NULL;
+}
+
static gboolean
gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
{
goto beach;
}
+ /* In case loopback, spawn another dedicated thread for feeding silence data
+ * into wasapi render client */
+ if (self->loopback) {
+ /* don't need to be audioclient3 or low-latency since we will keep pushing
+ * silence data which is not varying over entire playback */
+ if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
+ self->loopback_client, self->mix_format, self->sharemode,
+ FALSE, FALSE, &devicep_frames))
+ goto beach;
+
+ if (!gst_wasapi_util_get_render_client (GST_ELEMENT (self),
+ self->loopback_client, &self->loopback_render_client)) {
+ goto beach;
+ }
+
+ self->loopback_thread = g_thread_new ("wasapi-loopback",
+ (GThreadFunc) gst_wasapi_src_loopback_silence_feeding_thread, self);
+ }
+
hr = IAudioClient_Start (self->client);
HR_FAILED_GOTO (hr, IAudioClock::Start, beach);
self->client_needs_restart = FALSE;
self->client_clock = NULL;
}
+ if (self->loopback_thread) {
+ GST_DEBUG_OBJECT (self, "loopback task thread is stopping");
+
+ SetEvent (self->loopback_cancellable);
+
+ g_thread_join (self->loopback_thread);
+ self->loopback_thread = NULL;
+ ResetEvent (self->loopback_cancellable);
+ GST_DEBUG_OBJECT (self, "loopback task thread has been stopped");
+ }
+
+ if (self->loopback_render_client != NULL) {
+ IUnknown_Release (self->loopback_render_client);
+ self->loopback_render_client = NULL;
+ }
+
self->client_clock_freq = 0;
CoUninitialize ();