summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/wasapi2/gstwasapi2client.cpp299
-rw-r--r--sys/wasapi2/gstwasapi2client.h2
-rw-r--r--sys/wasapi2/gstwasapi2sink.c20
-rw-r--r--sys/wasapi2/gstwasapi2src.c20
4 files changed, 214 insertions, 127 deletions
diff --git a/sys/wasapi2/gstwasapi2client.cpp b/sys/wasapi2/gstwasapi2client.cpp
index e2f162783..390bedf2c 100644
--- a/sys/wasapi2/gstwasapi2client.cpp
+++ b/sys/wasapi2/gstwasapi2client.cpp
@@ -68,12 +68,13 @@ class GstWasapiDeviceActivator
{
public:
GstWasapiDeviceActivator ()
- : listener_(nullptr)
{
+ g_weak_ref_init (&listener_, nullptr);
}
~GstWasapiDeviceActivator ()
{
+ g_weak_ref_set (&listener_, nullptr);
}
HRESULT
@@ -82,7 +83,7 @@ public:
if (!listener)
return E_INVALIDARG;
- listener_ = listener;
+ g_weak_ref_set (&listener_, listener);
if (dispatcher) {
ComPtr<IInspectable> inspectable =
@@ -104,113 +105,130 @@ public:
HRESULT hr = S_OK;
HRESULT hr_async_op = S_OK;
ComPtr<IUnknown> audio_interface;
+ GstWasapi2Client *client;
- if (!listener_) {
+ client = (GstWasapi2Client *) g_weak_ref_get (&listener_);
+
+ if (!client) {
+ this->Release ();
GST_WARNING ("No listener was configured");
return S_OK;
}
- GST_INFO_OBJECT (listener_, "AsyncOperation done");
+ GST_INFO_OBJECT (client, "AsyncOperation done");
hr = async_op->GetActivateResult(&hr_async_op, &audio_interface);
if (!gst_wasapi2_result (hr)) {
- GST_WARNING_OBJECT (listener_, "Failed to get activate result, hr: 0x%x", hr);
+ GST_WARNING_OBJECT (client, "Failed to get activate result, hr: 0x%x", hr);
goto done;
}
if (!gst_wasapi2_result (hr_async_op)) {
- GST_WARNING_OBJECT (listener_, "Failed to activate device");
+ GST_WARNING_OBJECT (client, "Failed to activate device");
goto done;
}
hr = audio_interface.As (&audio_client);
if (!gst_wasapi2_result (hr)) {
- GST_ERROR_OBJECT (listener_, "Failed to get IAudioClient3 interface");
+ GST_ERROR_OBJECT (client, "Failed to get IAudioClient3 interface");
goto done;
}
done:
/* Should call this method anyway, listener will wait this event */
- gst_wasapi2_client_on_device_activated (listener_, audio_client.Get());
-
+ gst_wasapi2_client_on_device_activated (client, audio_client.Get());
+ gst_object_unref (client);
/* return S_OK anyway, but listener can know it's succeeded or not
* by passed IAudioClient handle via gst_wasapi2_client_on_device_activated
*/
- return S_OK;
- }
-
- HRESULT
- ActivateDeviceAsync(const std::wstring &device_id)
- {
- return runOnUIThread (INFINITE,
- [this, device_id] {
- ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
- HRESULT hr = S_OK;
- hr = ActivateAudioInterfaceAsync (device_id.c_str (),
- __uuidof(IAudioClient3), nullptr, this, &async_op);
+ this->Release ();
- /* for debugging */
- gst_wasapi2_result (hr);
-
- return hr;
- });
+ return S_OK;
}
- template <typename CB>
HRESULT
- runOnUIThread (DWORD timeout, CB && cb)
+ ActivateDeviceAsync(const std::wstring &device_id)
{
ComPtr<IAsyncAction> async_action;
+ bool run_async = false;
HRESULT hr;
- HRESULT hr_cb;
- boolean can_now;
- DWORD wait_ret;
- if (!dispatcher_)
- return cb();
+ auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
+ IDispatchedHandler, FtmBase>>([this, device_id]{
+ ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
+ HRESULT async_hr = S_OK;
- hr = dispatcher_->get_HasThreadAccess (&can_now);
+ async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
+ __uuidof(IAudioClient3), nullptr, this, &async_op);
- if (FAILED (hr))
- return hr;
+ /* for debugging */
+ gst_wasapi2_result (async_hr);
- if (can_now)
- return cb ();
+ return async_hr;
+ });
- Event event (CreateEventEx (NULL, NULL, CREATE_EVENT_MANUAL_RESET,
- EVENT_ALL_ACCESS));
+ if (dispatcher_) {
+ boolean can_now;
+ hr = dispatcher_->get_HasThreadAccess (&can_now);
- if (!event.IsValid())
- return E_FAIL;
-
- auto handler =
- Callback<Implements<RuntimeClassFlags<ClassicCom>,
- IDispatchedHandler, FtmBase>>([&hr_cb, &cb, &event] {
- hr_cb = cb ();
- SetEvent (event.Get());
- return S_OK;
- });
+ if (!gst_wasapi2_result (hr))
+ return hr;
- hr = dispatcher_->RunAsync (CoreDispatcherPriority_Normal,
- handler.Get(), &async_action);
+ if (!can_now)
+ run_async = true;
+ }
- if (FAILED (hr))
- return hr;
+ if (run_async && dispatcher_) {
+ hr = dispatcher_->RunAsync (CoreDispatcherPriority_Normal,
+ work_item.Get (), &async_action);
+ } else {
+ hr = work_item->Invoke ();
+ }
- wait_ret = WaitForSingleObject (event.Get(), timeout);
- if (wait_ret != WAIT_OBJECT_0)
- return E_FAIL;
+ /* We should hold activator object until activation callback has executed,
+ * because OS doesn't hold reference of this callback COM object.
+ * otherwise access violation would happen
+ * See https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-activateaudiointerfaceasync
+ *
+ * This reference count will be decreased by self later on callback,
+ * which will be called from device worker thread.
+ */
+ if (gst_wasapi2_result (hr))
+ this->AddRef ();
return hr;
}
private:
- GstWasapi2Client * listener_;
+ GWeakRef listener_;
ComPtr<ICoreDispatcher> dispatcher_;
};
+typedef enum
+{
+ GST_WASAPI2_CLIENT_ACTIVATE_FAILED = -1,
+ GST_WASAPI2_CLIENT_ACTIVATE_INIT = 0,
+ GST_WASAPI2_CLIENT_ACTIVATE_WAIT,
+ GST_WASAPI2_CLIENT_ACTIVATE_DONE,
+} GstWasapi2ClientActivateState;
+
+enum
+{
+ PROP_0,
+ PROP_DEVICE,
+ PROP_DEVICE_NAME,
+ PROP_DEVICE_INDEX,
+ PROP_DEVICE_CLASS,
+ PROP_LOW_LATENCY,
+ PROP_DISPATCHER,
+};
+
+#define DEFAULT_DEVICE_INDEX -1
+#define DEFAULT_DEVICE_CLASS GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE
+#define DEFAULT_LOW_LATENCY FALSE
+
struct _GstWasapi2Client
{
GstObject parent;
@@ -226,6 +244,7 @@ struct _GstWasapi2Client
IAudioCaptureClient *audio_capture_client;
IAudioRenderClient *audio_render_client;
ISimpleAudioVolume *audio_volume;
+ GstWasapiDeviceActivator *activator;
WAVEFORMATEX *mix_format;
GstCaps *supported_caps;
@@ -252,24 +271,9 @@ struct _GstWasapi2Client
/* To wait ActivateCompleted event */
GMutex init_lock;
GCond init_cond;
- gboolean init_done;
-};
-
-enum
-{
- PROP_0,
- PROP_DEVICE,
- PROP_DEVICE_NAME,
- PROP_DEVICE_INDEX,
- PROP_DEVICE_CLASS,
- PROP_LOW_LATENCY,
- PROP_DISPATCHER,
+ GstWasapi2ClientActivateState activate_state;
};
-#define DEFAULT_DEVICE_INDEX -1
-#define DEFAULT_DEVICE_CLASS GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE
-#define DEFAULT_LOW_LATENCY FALSE
-
GType
gst_wasapi2_client_device_class_get_type (void)
{
@@ -357,6 +361,7 @@ gst_wasapi2_client_init (GstWasapi2Client * self)
g_mutex_init (&self->init_lock);
g_cond_init (&self->init_cond);
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_INIT;
self->context = g_main_context_new ();
self->loop = g_main_loop_new (self->context, FALSE);
@@ -366,6 +371,7 @@ static void
gst_wasapi2_client_constructed (GObject * object)
{
GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
+ ComPtr<GstWasapiDeviceActivator> activator;
/* Create a new thread to ensure that COM thread can be MTA thread.
* We cannot ensure whether CoInitializeEx() was called outside of here for
@@ -516,8 +522,11 @@ gst_wasapi2_client_on_device_activated (GstWasapi2Client * self,
if (audio_client) {
audio_client->AddRef();
self->audio_client = audio_client;
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_DONE;
+ } else {
+ GST_WARNING_OBJECT (self, "IAudioClient is unavailable");
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
}
- self->init_done = TRUE;
g_cond_broadcast (&self->init_cond);
g_mutex_unlock (&self->init_lock);
}
@@ -565,12 +574,11 @@ gst_wasapi2_client_get_default_device_id (GstWasapi2Client * self)
return ret;
}
-static void
-gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
+static gboolean
+gst_wasapi2_client_activate_async (GstWasapi2Client * self,
+ GstWasapiDeviceActivator * activator)
{
HRESULT hr;
- GSource *source;
- ComPtr<GstWasapiDeviceActivator> activator;
ComPtr<IDeviceInformationStatics> device_info_static;
ComPtr<IAsyncOperation<DeviceInformationCollection*>> async_op;
ComPtr<IVectorView<DeviceInformation*>> device_list;
@@ -586,18 +594,11 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
std::string target_device_name;
gboolean use_default_device = FALSE;
- g_main_context_push_thread_default (self->context);
-
GST_INFO_OBJECT (self,
"requested device info, device-class: %s, device: %s, device-index: %d",
self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE ? "capture" :
"render", GST_STR_NULL (self->device_id), self->device_index);
- hr = MakeAndInitialize<GstWasapiDeviceActivator> (&activator,
- self, self->dispatcher);
- if (!gst_wasapi2_result (hr))
- goto run_loop;
-
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE) {
device_class = DeviceClass::DeviceClass_AudioCapture;
} else {
@@ -607,7 +608,7 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
default_device_id_wstring = gst_wasapi2_client_get_default_device_id (self);
if (default_device_id_wstring.empty ()) {
GST_WARNING_OBJECT (self, "Couldn't get default device id");
- goto run_loop;
+ goto failed;
}
default_device_id = convert_wstring_to_string (default_device_id_wstring);
@@ -647,29 +648,29 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
hr = GetActivationFactory (hstr_device_info.Get(), &device_info_static);
if (!gst_wasapi2_result (hr))
- goto run_loop;
+ goto failed;
hr = device_info_static->FindAllAsyncDeviceClass (device_class, &async_op);
device_info_static.Reset ();
if (!gst_wasapi2_result (hr))
- goto run_loop;
+ goto failed;
hr = SyncWait<DeviceInformationCollection*>(async_op.Get ());
if (!gst_wasapi2_result (hr))
- goto run_loop;
+ goto failed;
hr = async_op->GetResults (&device_list);
async_op.Reset ();
if (!gst_wasapi2_result (hr))
- goto run_loop;
+ goto failed;
hr = device_list->get_Size (&count);
if (!gst_wasapi2_result (hr))
- goto run_loop;
+ goto failed;
if (count == 0) {
GST_WARNING_OBJECT (self, "No available device");
- goto run_loop;
+ goto failed;
}
/* device_index 0 will be assigned for default device
@@ -677,7 +678,7 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
if (self->device_index >= 0 && self->device_index > (gint) count) {
GST_WARNING_OBJECT (self, "Device index %d is unavailable",
self->device_index);
- goto run_loop;
+ goto failed;
}
GST_DEBUG_OBJECT (self, "Available device count: %d", count);
@@ -767,7 +768,7 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
if (target_device_id_wstring.empty ()) {
GST_WARNING_OBJECT (self, "Couldn't find target device");
- goto run_loop;
+ goto failed;
}
activate:
@@ -783,18 +784,70 @@ activate:
hr = activator->ActivateDeviceAsync (target_device_id_wstring);
if (!gst_wasapi2_result (hr)) {
GST_WARNING_OBJECT (self, "Failed to activate device");
+ goto failed;
+ }
+
+ g_mutex_lock (&self->lock);
+ if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_INIT)
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_WAIT;
+ g_mutex_unlock (&self->lock);
+
+ return TRUE;
+
+failed:
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
+
+ return FALSE;
+}
+
+static const gchar *
+activate_state_to_string (GstWasapi2ClientActivateState state)
+{
+ switch (state) {
+ case GST_WASAPI2_CLIENT_ACTIVATE_FAILED:
+ return "FAILED";
+ case GST_WASAPI2_CLIENT_ACTIVATE_INIT:
+ return "INIT";
+ case GST_WASAPI2_CLIENT_ACTIVATE_WAIT:
+ return "WAIT";
+ case GST_WASAPI2_CLIENT_ACTIVATE_DONE:
+ return "DONE";
+ }
+
+ g_assert_not_reached ();
+
+ return "Undefined";
+}
+
+static gpointer
+gst_wasapi2_client_thread_func (GstWasapi2Client * self)
+{
+ RoInitializeWrapper initialize (RO_INIT_MULTITHREADED);
+ GSource *source;
+ HRESULT hr;
+ ComPtr<GstWasapiDeviceActivator> activator;
+
+ hr = MakeAndInitialize<GstWasapiDeviceActivator> (&activator,
+ self, self->dispatcher);
+ if (!gst_wasapi2_result (hr)) {
+ GST_ERROR_OBJECT (self, "Could not create activator object");
+ self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
goto run_loop;
}
- /* Wait ActivateCompleted event */
- GST_DEBUG_OBJECT (self, "Wait device activation");
- g_mutex_lock (&self->init_lock);
- while (!self->init_done)
- g_cond_wait (&self->init_cond, &self->init_lock);
- g_mutex_unlock (&self->init_lock);
- GST_DEBUG_OBJECT (self, "Done device activation");
+ gst_wasapi2_client_activate_async (self, activator.Get ());
+
+ if (!self->dispatcher) {
+ /* In case that dispatcher is unavailable, wait activation synchroniously */
+ GST_DEBUG_OBJECT (self, "Wait device activation");
+ gst_wasapi2_client_ensure_activation (self);
+ GST_DEBUG_OBJECT (self, "Device activation result %s",
+ activate_state_to_string (self->activate_state));
+ }
run_loop:
+ g_main_context_push_thread_default (self->context);
+
source = g_idle_source_new ();
g_source_set_callback (source,
(GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, NULL);
@@ -829,19 +882,11 @@ run_loop:
self->audio_client = NULL;
}
- GST_DEBUG_OBJECT (self, "Exit thread function");
-
- return;
-}
-
-static gpointer
-gst_wasapi2_client_thread_func (GstWasapi2Client * self)
-{
- RoInitializeWrapper initialize (RO_INIT_MULTITHREADED);
+ /* Reset explicitly to ensure that it happens before
+ * RoInitializeWrapper dtor is called */
+ activator.Reset ();
- /* Wrap thread function so that ensure everything happens inside of
- * RoInitializeWrapper */
- gst_wasapi2_client_thread_func_internal (self);
+ GST_DEBUG_OBJECT (self, "Exit thread function");
return NULL;
}
@@ -1766,6 +1811,22 @@ gst_wasapi2_client_get_volume (GstWasapi2Client * client, gfloat * volume)
return TRUE;
}
+gboolean
+gst_wasapi2_client_ensure_activation (GstWasapi2Client * client)
+{
+ g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), FALSE);
+
+ /* should not happen */
+ g_assert (client->activate_state != GST_WASAPI2_CLIENT_ACTIVATE_INIT);
+
+ g_mutex_lock (&client->init_lock);
+ while (client->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_WAIT)
+ g_cond_wait (&client->init_cond, &client->init_lock);
+ g_mutex_unlock (&client->init_lock);
+
+ return client->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_DONE;
+}
+
static HRESULT
find_dispatcher (ICoreDispatcher ** dispatcher)
{
@@ -1775,17 +1836,17 @@ find_dispatcher (ICoreDispatcher ** dispatcher)
ComPtr<ICoreApplication> core_app;
hr = GetActivationFactory (hstr_core_app.Get(), &core_app);
- if (!gst_wasapi2_result (hr))
+ if (FAILED (hr))
return hr;
ComPtr<ICoreApplicationView> core_app_view;
hr = core_app->GetCurrentView (&core_app_view);
- if (!gst_wasapi2_result (hr))
+ if (FAILED (hr))
return hr;
ComPtr<ICoreWindow> core_window;
hr = core_app_view->get_CoreWindow (&core_window);
- if (!gst_wasapi2_result (hr))
+ if (FAILED (hr))
return hr;
return core_window->get_Dispatcher (dispatcher);
@@ -1807,7 +1868,7 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
HRESULT hr;
hr = find_dispatcher (&core_dispatcher);
- if (gst_wasapi2_result (hr)) {
+ if (SUCCEEDED (hr)) {
GST_DEBUG ("UI dispatcher is available");
dispatcher = core_dispatcher.Get ();
} else {
@@ -1826,7 +1887,7 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
* RoInitializeWrapper dtor is called */
core_dispatcher.Reset ();
- if (!self->audio_client) {
+ if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_FAILED) {
gst_object_unref (self);
return NULL;
}
diff --git a/sys/wasapi2/gstwasapi2client.h b/sys/wasapi2/gstwasapi2client.h
index 7c9bd129c..4b9d3d41f 100644
--- a/sys/wasapi2/gstwasapi2client.h
+++ b/sys/wasapi2/gstwasapi2client.h
@@ -70,6 +70,8 @@ gboolean gst_wasapi2_client_set_volume (GstWasapi2Client * client,
gboolean gst_wasapi2_client_get_volume (GstWasapi2Client * client,
gfloat * volume);
+gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client);
+
GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
gboolean low_latency,
gint device_index,
diff --git a/sys/wasapi2/gstwasapi2sink.c b/sys/wasapi2/gstwasapi2sink.c
index 280f837ba..4471105f3 100644
--- a/sys/wasapi2/gstwasapi2sink.c
+++ b/sys/wasapi2/gstwasapi2sink.c
@@ -286,14 +286,21 @@ gst_wasapi2_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
GstWasapi2Sink *self = GST_WASAPI2_SINK (bsink);
GstCaps *caps = NULL;
- /* store one caps here so that we can return device caps even if
- * audioclient was closed due to unprepare() */
- if (!self->cached_caps && self->client)
- self->cached_caps = gst_wasapi2_client_get_caps (self->client);
+ /* In case of UWP, device activation might not be finished yet */
+ if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) {
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
+ ("Failed to activate device"));
+ return NULL;
+ }
if (self->client)
caps = gst_wasapi2_client_get_caps (self->client);
+ /* store one caps here so that we can return device caps even if
+ * audioclient was closed due to unprepare() */
+ if (!self->cached_caps && caps)
+ self->cached_caps = gst_caps_ref (caps);
+
if (!caps && self->cached_caps)
caps = gst_caps_ref (self->cached_caps);
@@ -374,6 +381,11 @@ gst_wasapi2_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
goto done;
}
+ if (!gst_wasapi2_client_ensure_activation (self->client)) {
+ GST_ERROR_OBJECT (self, "Couldn't activate audio device");
+ goto done;
+ }
+
if (!gst_wasapi2_client_open (self->client, spec, bsink->ringbuffer)) {
GST_ERROR_OBJECT (self, "Couldn't open audio client");
goto done;
diff --git a/sys/wasapi2/gstwasapi2src.c b/sys/wasapi2/gstwasapi2src.c
index f6a0fce76..6d2e7907a 100644
--- a/sys/wasapi2/gstwasapi2src.c
+++ b/sys/wasapi2/gstwasapi2src.c
@@ -283,14 +283,21 @@ gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
GstWasapi2Src *self = GST_WASAPI2_SRC (bsrc);
GstCaps *caps = NULL;
- /* store one caps here so that we can return device caps even if
- * audioclient was closed due to unprepare() */
- if (!self->cached_caps && self->client)
- self->cached_caps = gst_wasapi2_client_get_caps (self->client);
+ /* In case of UWP, device activation might not be finished yet */
+ if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) {
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
+ ("Failed to activate device"));
+ return NULL;
+ }
if (self->client)
caps = gst_wasapi2_client_get_caps (self->client);
+ /* store one caps here so that we can return device caps even if
+ * audioclient was closed due to unprepare() */
+ if (!self->cached_caps && caps)
+ self->cached_caps = gst_caps_ref (caps);
+
if (!caps && self->cached_caps)
caps = gst_caps_ref (self->cached_caps);
@@ -371,6 +378,11 @@ gst_wasapi2_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
goto done;
}
+ if (!gst_wasapi2_client_ensure_activation (self->client)) {
+ GST_ERROR_OBJECT (self, "Couldn't activate audio device");
+ goto done;
+ }
+
if (!gst_wasapi2_client_open (self->client, spec, bsrc->ringbuffer)) {
GST_ERROR_OBJECT (self, "Couldn't open audio client");
goto done;