wasapi: Fix infinite loop when the device disappears
authorNirbheek Chauhan <nirbheek@centricular.com>
Mon, 14 Jan 2019 20:33:23 +0000 (02:03 +0530)
committerNirbheek Chauhan <nirbheek@centricular.com>
Mon, 14 Jan 2019 21:59:58 +0000 (03:29 +0530)
When the audio device goes away during playback or capture, we were
going into an infinite loop of AUDCLNT_E_DEVICE_INVALIDATED. Return -1
and post an error message so the ringbuffer thread exits with an error.

sys/wasapi/gstwasapisink.c
sys/wasapi/gstwasapisrc.c
sys/wasapi/gstwasapiutil.h

index 1e3b07a..eda3b83 100644 (file)
@@ -458,7 +458,7 @@ gst_wasapi_sink_get_can_frames (GstWasapiSink * self)
 
   /* Frames the card hasn't rendered yet */
   hr = IAudioClient_GetCurrentPadding (self->client, &n_frames_padding);
-  HR_FAILED_RET (hr, IAudioClient::GetCurrentPadding, -1);
+  HR_FAILED_ELEMENT_ERROR_RET (hr, IAudioClient::GetCurrentPadding, self, -1);
 
   GST_DEBUG_OBJECT (self, "%i unread frames (padding)", n_frames_padding);
 
@@ -604,8 +604,8 @@ gst_wasapi_sink_write (GstAudioSink * asink, gpointer data, guint length)
   GST_OBJECT_LOCK (self);
   if (self->client_needs_restart) {
     hr = IAudioClient_Start (self->client);
-    HR_FAILED_AND (hr, IAudioClient::Start, GST_OBJECT_UNLOCK (self);
-        goto beach);
+    HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
+        GST_OBJECT_UNLOCK (self); goto err);
     self->client_needs_restart = FALSE;
   }
   GST_OBJECT_UNLOCK (self);
@@ -619,32 +619,43 @@ gst_wasapi_sink_write (GstAudioSink * asink, gpointer data, guint length)
     if (dwWaitResult != WAIT_OBJECT_0) {
       GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
           (guint) dwWaitResult);
-      goto beach;
+      goto err;
     }
 
     can_frames = gst_wasapi_sink_get_can_frames (self);
+    if (can_frames < 0) {
+      GST_ERROR_OBJECT (self, "Error getting frames to write to");
+      goto err;
+    }
     /* In exclusive mode we need to fill the whole buffer in one go or
      * GetBuffer will error out */
     if (can_frames != have_frames) {
       GST_ERROR_OBJECT (self,
           "Need at %i frames to write for exclusive mode, but got %i",
           can_frames, have_frames);
-      written_len = -1;
-      goto beach;
+      goto err;
     }
   } else {
     /* In shared mode we can write parts of the buffer, so only wait
      * in case we can't write anything */
     can_frames = gst_wasapi_sink_get_can_frames (self);
+    if (can_frames < 0) {
+      GST_ERROR_OBJECT (self, "Error getting frames to write to");
+      goto err;
+    }
 
     if (can_frames == 0) {
       dwWaitResult = WaitForSingleObject (self->event_handle, INFINITE);
       if (dwWaitResult != WAIT_OBJECT_0) {
         GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
             (guint) dwWaitResult);
-        goto beach;
+        goto err;
       }
       can_frames = gst_wasapi_sink_get_can_frames (self);
+      if (can_frames < 0) {
+        GST_ERROR_OBJECT (self, "Error getting frames to write to");
+        goto err;
+      }
     }
   }
 
@@ -658,19 +669,24 @@ gst_wasapi_sink_write (GstAudioSink * asink, gpointer data, guint length)
 
   hr = IAudioRenderClient_GetBuffer (self->render_client, n_frames,
       (BYTE **) & dst);
-  HR_FAILED_AND (hr, IAudioRenderClient::GetBuffer, goto beach);
+  HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioRenderClient::GetBuffer, self,
+      goto err);
 
   memcpy (dst, data, write_len);
 
   hr = IAudioRenderClient_ReleaseBuffer (self->render_client, n_frames,
       self->mute ? AUDCLNT_BUFFERFLAGS_SILENT : 0);
-  HR_FAILED_AND (hr, IAudioRenderClient::ReleaseBuffer, goto beach);
+  HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioRenderClient::ReleaseBuffer, self,
+      goto err);
 
   written_len = write_len;
 
-beach:
-
+out:
   return written_len;
+
+err:
+  written_len = -1;
+  goto out;
 }
 
 static guint
index 3d28b5c..6c11658 100644 (file)
@@ -562,7 +562,8 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
   GST_OBJECT_LOCK (self);
   if (self->client_needs_restart) {
     hr = IAudioClient_Start (self->client);
-    HR_FAILED_AND (hr, IAudioClient::Start, length = 0; goto beach);
+    HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
+        GST_OBJECT_UNLOCK (self); goto err);
     self->client_needs_restart = FALSE;
   }
   GST_OBJECT_UNLOCK (self);
@@ -576,23 +577,22 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
     if (dwWaitResult != WAIT_OBJECT_0) {
       GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
           (guint) dwWaitResult);
-      length = 0;
-      goto beach;
+      goto err;
     }
 
     hr = IAudioCaptureClient_GetBuffer (self->capture_client,
         (BYTE **) & from, &have_frames, &flags, NULL, NULL);
     if (hr != S_OK) {
-      gchar *msg = gst_wasapi_util_hresult_to_string (hr);
-      if (hr == AUDCLNT_S_BUFFER_EMPTY)
+      if (hr == AUDCLNT_S_BUFFER_EMPTY) {
+        gchar *msg = gst_wasapi_util_hresult_to_string (hr);
         GST_WARNING_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s"
             ", retrying", msg);
-      else
-        GST_ERROR_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s",
-            msg);
-      g_free (msg);
-      length = 0;
-      goto beach;
+        g_free (msg);
+        length = 0;
+        goto out;
+      }
+      HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::GetBuffer, self,
+          goto err);
     }
 
     if (flags != 0)
@@ -629,13 +629,17 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
 
     /* Always release all captured buffers if we've captured any at all */
     hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, have_frames);
-    HR_FAILED_AND (hr, IAudioClock::ReleaseBuffer, goto beach);
+    HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::ReleaseBuffer, self,
+        goto err);
   }
 
 
-beach:
-
+out:
   return length;
+
+err:
+  length = -1;
+  goto out;
 }
 
 static guint
index 502c763..599a603 100644 (file)
 
 /* Standard error path */
 #define HR_FAILED_AND(hr,func,and) \
-  do { \
+  G_STMT_START { \
     if (FAILED (hr)) { \
       gchar *msg = gst_wasapi_util_hresult_to_string (hr); \
       GST_ERROR_OBJECT (self, #func " failed (%x): %s", (guint) hr, msg); \
       g_free (msg); \
       and; \
     } \
-  } while (0)
+  } G_STMT_END
 
 #define HR_FAILED_RET(hr,func,ret) HR_FAILED_AND(hr,func,return ret)
 
 #define HR_FAILED_GOTO(hr,func,where) HR_FAILED_AND(hr,func,res = FALSE; goto where)
 
+#define HR_FAILED_ELEMENT_ERROR_AND(hr,func,el,and) \
+  G_STMT_START { \
+    if (FAILED (hr)) { \
+      gchar *msg = gst_wasapi_util_hresult_to_string (hr); \
+      GST_ERROR_OBJECT (el, #func " failed (%x): %s", (guint) hr, msg); \
+      if (GST_IS_AUDIO_SRC (el)) \
+        GST_ELEMENT_ERROR(el, RESOURCE, READ, \
+            (#func " failed (%x): %s", (guint) hr, msg), (NULL)); \
+      else \
+        GST_ELEMENT_ERROR(el, RESOURCE, WRITE, \
+            (#func " failed (%x): %s", (guint) hr, msg), (NULL)); \
+      g_free (msg); \
+      and; \
+    } \
+  } G_STMT_END
+
+#define HR_FAILED_ELEMENT_ERROR_RET(hr,func,el,ret) \
+  HR_FAILED_ELEMENT_ERROR_AND(hr,func,el,return ret)
+
+
 /* Device role enum property */
 typedef enum
 {