mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-12 09:15:29 +00:00
wasapi2: Activate device asynchronously if required
In case of UWP, documentation from MS is saying that ActivateAudioInterfaceAsync() method should be called from UI thread. And the resulting callback might not happen until user interaction has been made. So we cannot wait the activation result on constructed() method. and therefore we should return gst_wasapi2_client_new() immediately without waiting the result if wasapi2 elements are running on UWP application. In addition to async operation fix, this commit includes COM object reference counting issue around ActivateAudioInterfaceAsync() call. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1466>
This commit is contained in:
parent
b10afc574e
commit
9e56d20279
4 changed files with 214 additions and 127 deletions
|
@ -68,12 +68,13 @@ class GstWasapiDeviceActivator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GstWasapiDeviceActivator ()
|
GstWasapiDeviceActivator ()
|
||||||
: listener_(nullptr)
|
|
||||||
{
|
{
|
||||||
|
g_weak_ref_init (&listener_, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
~GstWasapiDeviceActivator ()
|
~GstWasapiDeviceActivator ()
|
||||||
{
|
{
|
||||||
|
g_weak_ref_set (&listener_, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
|
@ -82,7 +83,7 @@ public:
|
||||||
if (!listener)
|
if (!listener)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
listener_ = listener;
|
g_weak_ref_set (&listener_, listener);
|
||||||
|
|
||||||
if (dispatcher) {
|
if (dispatcher) {
|
||||||
ComPtr<IInspectable> inspectable =
|
ComPtr<IInspectable> inspectable =
|
||||||
|
@ -104,113 +105,130 @@ public:
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
HRESULT hr_async_op = S_OK;
|
HRESULT hr_async_op = S_OK;
|
||||||
ComPtr<IUnknown> audio_interface;
|
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");
|
GST_WARNING ("No listener was configured");
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
GST_INFO_OBJECT (listener_, "AsyncOperation done");
|
GST_INFO_OBJECT (client, "AsyncOperation done");
|
||||||
|
|
||||||
hr = async_op->GetActivateResult(&hr_async_op, &audio_interface);
|
hr = async_op->GetActivateResult(&hr_async_op, &audio_interface);
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr)) {
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr_async_op)) {
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = audio_interface.As (&audio_client);
|
hr = audio_interface.As (&audio_client);
|
||||||
if (!gst_wasapi2_result (hr)) {
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
/* Should call this method anyway, listener will wait this event */
|
/* 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
|
/* return S_OK anyway, but listener can know it's succeeded or not
|
||||||
* by passed IAudioClient handle via gst_wasapi2_client_on_device_activated
|
* by passed IAudioClient handle via gst_wasapi2_client_on_device_activated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
this->Release ();
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
ActivateDeviceAsync(const std::wstring &device_id)
|
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);
|
|
||||||
|
|
||||||
/* for debugging */
|
|
||||||
gst_wasapi2_result (hr);
|
|
||||||
|
|
||||||
return hr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename CB>
|
|
||||||
HRESULT
|
|
||||||
runOnUIThread (DWORD timeout, CB && cb)
|
|
||||||
{
|
{
|
||||||
ComPtr<IAsyncAction> async_action;
|
ComPtr<IAsyncAction> async_action;
|
||||||
|
bool run_async = false;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
HRESULT hr_cb;
|
|
||||||
boolean can_now;
|
|
||||||
DWORD wait_ret;
|
|
||||||
|
|
||||||
if (!dispatcher_)
|
auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
|
||||||
return cb();
|
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))
|
/* for debugging */
|
||||||
return hr;
|
gst_wasapi2_result (async_hr);
|
||||||
|
|
||||||
if (can_now)
|
return async_hr;
|
||||||
return cb ();
|
});
|
||||||
|
|
||||||
Event event (CreateEventEx (NULL, NULL, CREATE_EVENT_MANUAL_RESET,
|
if (dispatcher_) {
|
||||||
EVENT_ALL_ACCESS));
|
boolean can_now;
|
||||||
|
hr = dispatcher_->get_HasThreadAccess (&can_now);
|
||||||
|
|
||||||
if (!event.IsValid())
|
if (!gst_wasapi2_result (hr))
|
||||||
return E_FAIL;
|
return hr;
|
||||||
|
|
||||||
auto handler =
|
if (!can_now)
|
||||||
Callback<Implements<RuntimeClassFlags<ClassicCom>,
|
run_async = true;
|
||||||
IDispatchedHandler, FtmBase>>([&hr_cb, &cb, &event] {
|
}
|
||||||
hr_cb = cb ();
|
|
||||||
SetEvent (event.Get());
|
|
||||||
return S_OK;
|
|
||||||
});
|
|
||||||
|
|
||||||
hr = dispatcher_->RunAsync (CoreDispatcherPriority_Normal,
|
if (run_async && dispatcher_) {
|
||||||
handler.Get(), &async_action);
|
hr = dispatcher_->RunAsync (CoreDispatcherPriority_Normal,
|
||||||
|
work_item.Get (), &async_action);
|
||||||
|
} else {
|
||||||
|
hr = work_item->Invoke ();
|
||||||
|
}
|
||||||
|
|
||||||
if (FAILED (hr))
|
/* We should hold activator object until activation callback has executed,
|
||||||
return hr;
|
* because OS doesn't hold reference of this callback COM object.
|
||||||
|
* otherwise access violation would happen
|
||||||
wait_ret = WaitForSingleObject (event.Get(), timeout);
|
* See https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-activateaudiointerfaceasync
|
||||||
if (wait_ret != WAIT_OBJECT_0)
|
*
|
||||||
return E_FAIL;
|
* 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;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GstWasapi2Client * listener_;
|
GWeakRef listener_;
|
||||||
ComPtr<ICoreDispatcher> dispatcher_;
|
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
|
struct _GstWasapi2Client
|
||||||
{
|
{
|
||||||
GstObject parent;
|
GstObject parent;
|
||||||
|
@ -226,6 +244,7 @@ struct _GstWasapi2Client
|
||||||
IAudioCaptureClient *audio_capture_client;
|
IAudioCaptureClient *audio_capture_client;
|
||||||
IAudioRenderClient *audio_render_client;
|
IAudioRenderClient *audio_render_client;
|
||||||
ISimpleAudioVolume *audio_volume;
|
ISimpleAudioVolume *audio_volume;
|
||||||
|
GstWasapiDeviceActivator *activator;
|
||||||
|
|
||||||
WAVEFORMATEX *mix_format;
|
WAVEFORMATEX *mix_format;
|
||||||
GstCaps *supported_caps;
|
GstCaps *supported_caps;
|
||||||
|
@ -252,24 +271,9 @@ struct _GstWasapi2Client
|
||||||
/* To wait ActivateCompleted event */
|
/* To wait ActivateCompleted event */
|
||||||
GMutex init_lock;
|
GMutex init_lock;
|
||||||
GCond init_cond;
|
GCond init_cond;
|
||||||
gboolean init_done;
|
GstWasapi2ClientActivateState activate_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
GType
|
GType
|
||||||
gst_wasapi2_client_device_class_get_type (void)
|
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_mutex_init (&self->init_lock);
|
||||||
g_cond_init (&self->init_cond);
|
g_cond_init (&self->init_cond);
|
||||||
|
self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_INIT;
|
||||||
|
|
||||||
self->context = g_main_context_new ();
|
self->context = g_main_context_new ();
|
||||||
self->loop = g_main_loop_new (self->context, FALSE);
|
self->loop = g_main_loop_new (self->context, FALSE);
|
||||||
|
@ -366,6 +371,7 @@ static void
|
||||||
gst_wasapi2_client_constructed (GObject * object)
|
gst_wasapi2_client_constructed (GObject * object)
|
||||||
{
|
{
|
||||||
GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
|
GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
|
||||||
|
ComPtr<GstWasapiDeviceActivator> activator;
|
||||||
|
|
||||||
/* Create a new thread to ensure that COM thread can be MTA thread.
|
/* Create a new thread to ensure that COM thread can be MTA thread.
|
||||||
* We cannot ensure whether CoInitializeEx() was called outside of here for
|
* 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) {
|
if (audio_client) {
|
||||||
audio_client->AddRef();
|
audio_client->AddRef();
|
||||||
self->audio_client = audio_client;
|
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_cond_broadcast (&self->init_cond);
|
||||||
g_mutex_unlock (&self->init_lock);
|
g_mutex_unlock (&self->init_lock);
|
||||||
}
|
}
|
||||||
|
@ -565,12 +574,11 @@ gst_wasapi2_client_get_default_device_id (GstWasapi2Client * self)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static gboolean
|
||||||
gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
|
gst_wasapi2_client_activate_async (GstWasapi2Client * self,
|
||||||
|
GstWasapiDeviceActivator * activator)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
GSource *source;
|
|
||||||
ComPtr<GstWasapiDeviceActivator> activator;
|
|
||||||
ComPtr<IDeviceInformationStatics> device_info_static;
|
ComPtr<IDeviceInformationStatics> device_info_static;
|
||||||
ComPtr<IAsyncOperation<DeviceInformationCollection*>> async_op;
|
ComPtr<IAsyncOperation<DeviceInformationCollection*>> async_op;
|
||||||
ComPtr<IVectorView<DeviceInformation*>> device_list;
|
ComPtr<IVectorView<DeviceInformation*>> device_list;
|
||||||
|
@ -586,18 +594,11 @@ gst_wasapi2_client_thread_func_internal (GstWasapi2Client * self)
|
||||||
std::string target_device_name;
|
std::string target_device_name;
|
||||||
gboolean use_default_device = FALSE;
|
gboolean use_default_device = FALSE;
|
||||||
|
|
||||||
g_main_context_push_thread_default (self->context);
|
|
||||||
|
|
||||||
GST_INFO_OBJECT (self,
|
GST_INFO_OBJECT (self,
|
||||||
"requested device info, device-class: %s, device: %s, device-index: %d",
|
"requested device info, device-class: %s, device: %s, device-index: %d",
|
||||||
self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE ? "capture" :
|
self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE ? "capture" :
|
||||||
"render", GST_STR_NULL (self->device_id), self->device_index);
|
"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) {
|
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE) {
|
||||||
device_class = DeviceClass::DeviceClass_AudioCapture;
|
device_class = DeviceClass::DeviceClass_AudioCapture;
|
||||||
} else {
|
} 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);
|
default_device_id_wstring = gst_wasapi2_client_get_default_device_id (self);
|
||||||
if (default_device_id_wstring.empty ()) {
|
if (default_device_id_wstring.empty ()) {
|
||||||
GST_WARNING_OBJECT (self, "Couldn't get default device id");
|
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);
|
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);
|
hr = GetActivationFactory (hstr_device_info.Get(), &device_info_static);
|
||||||
if (!gst_wasapi2_result (hr))
|
if (!gst_wasapi2_result (hr))
|
||||||
goto run_loop;
|
goto failed;
|
||||||
|
|
||||||
hr = device_info_static->FindAllAsyncDeviceClass (device_class, &async_op);
|
hr = device_info_static->FindAllAsyncDeviceClass (device_class, &async_op);
|
||||||
device_info_static.Reset ();
|
device_info_static.Reset ();
|
||||||
if (!gst_wasapi2_result (hr))
|
if (!gst_wasapi2_result (hr))
|
||||||
goto run_loop;
|
goto failed;
|
||||||
|
|
||||||
hr = SyncWait<DeviceInformationCollection*>(async_op.Get ());
|
hr = SyncWait<DeviceInformationCollection*>(async_op.Get ());
|
||||||
if (!gst_wasapi2_result (hr))
|
if (!gst_wasapi2_result (hr))
|
||||||
goto run_loop;
|
goto failed;
|
||||||
|
|
||||||
hr = async_op->GetResults (&device_list);
|
hr = async_op->GetResults (&device_list);
|
||||||
async_op.Reset ();
|
async_op.Reset ();
|
||||||
if (!gst_wasapi2_result (hr))
|
if (!gst_wasapi2_result (hr))
|
||||||
goto run_loop;
|
goto failed;
|
||||||
|
|
||||||
hr = device_list->get_Size (&count);
|
hr = device_list->get_Size (&count);
|
||||||
if (!gst_wasapi2_result (hr))
|
if (!gst_wasapi2_result (hr))
|
||||||
goto run_loop;
|
goto failed;
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
GST_WARNING_OBJECT (self, "No available device");
|
GST_WARNING_OBJECT (self, "No available device");
|
||||||
goto run_loop;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* device_index 0 will be assigned for default device
|
/* 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) {
|
if (self->device_index >= 0 && self->device_index > (gint) count) {
|
||||||
GST_WARNING_OBJECT (self, "Device index %d is unavailable",
|
GST_WARNING_OBJECT (self, "Device index %d is unavailable",
|
||||||
self->device_index);
|
self->device_index);
|
||||||
goto run_loop;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (self, "Available device count: %d", count);
|
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 ()) {
|
if (target_device_id_wstring.empty ()) {
|
||||||
GST_WARNING_OBJECT (self, "Couldn't find target device");
|
GST_WARNING_OBJECT (self, "Couldn't find target device");
|
||||||
goto run_loop;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
activate:
|
activate:
|
||||||
|
@ -783,18 +784,70 @@ activate:
|
||||||
hr = activator->ActivateDeviceAsync (target_device_id_wstring);
|
hr = activator->ActivateDeviceAsync (target_device_id_wstring);
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_WARNING_OBJECT (self, "Failed to activate device");
|
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;
|
goto run_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait ActivateCompleted event */
|
gst_wasapi2_client_activate_async (self, activator.Get ());
|
||||||
GST_DEBUG_OBJECT (self, "Wait device activation");
|
|
||||||
g_mutex_lock (&self->init_lock);
|
if (!self->dispatcher) {
|
||||||
while (!self->init_done)
|
/* In case that dispatcher is unavailable, wait activation synchroniously */
|
||||||
g_cond_wait (&self->init_cond, &self->init_lock);
|
GST_DEBUG_OBJECT (self, "Wait device activation");
|
||||||
g_mutex_unlock (&self->init_lock);
|
gst_wasapi2_client_ensure_activation (self);
|
||||||
GST_DEBUG_OBJECT (self, "Done device activation");
|
GST_DEBUG_OBJECT (self, "Device activation result %s",
|
||||||
|
activate_state_to_string (self->activate_state));
|
||||||
|
}
|
||||||
|
|
||||||
run_loop:
|
run_loop:
|
||||||
|
g_main_context_push_thread_default (self->context);
|
||||||
|
|
||||||
source = g_idle_source_new ();
|
source = g_idle_source_new ();
|
||||||
g_source_set_callback (source,
|
g_source_set_callback (source,
|
||||||
(GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, NULL);
|
(GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, NULL);
|
||||||
|
@ -829,20 +882,12 @@ run_loop:
|
||||||
self->audio_client = NULL;
|
self->audio_client = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reset explicitly to ensure that it happens before
|
||||||
|
* RoInitializeWrapper dtor is called */
|
||||||
|
activator.Reset ();
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (self, "Exit thread function");
|
GST_DEBUG_OBJECT (self, "Exit thread function");
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gpointer
|
|
||||||
gst_wasapi2_client_thread_func (GstWasapi2Client * self)
|
|
||||||
{
|
|
||||||
RoInitializeWrapper initialize (RO_INIT_MULTITHREADED);
|
|
||||||
|
|
||||||
/* Wrap thread function so that ensure everything happens inside of
|
|
||||||
* RoInitializeWrapper */
|
|
||||||
gst_wasapi2_client_thread_func_internal (self);
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1766,6 +1811,22 @@ gst_wasapi2_client_get_volume (GstWasapi2Client * client, gfloat * volume)
|
||||||
return TRUE;
|
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
|
static HRESULT
|
||||||
find_dispatcher (ICoreDispatcher ** dispatcher)
|
find_dispatcher (ICoreDispatcher ** dispatcher)
|
||||||
{
|
{
|
||||||
|
@ -1775,17 +1836,17 @@ find_dispatcher (ICoreDispatcher ** dispatcher)
|
||||||
|
|
||||||
ComPtr<ICoreApplication> core_app;
|
ComPtr<ICoreApplication> core_app;
|
||||||
hr = GetActivationFactory (hstr_core_app.Get(), &core_app);
|
hr = GetActivationFactory (hstr_core_app.Get(), &core_app);
|
||||||
if (!gst_wasapi2_result (hr))
|
if (FAILED (hr))
|
||||||
return hr;
|
return hr;
|
||||||
|
|
||||||
ComPtr<ICoreApplicationView> core_app_view;
|
ComPtr<ICoreApplicationView> core_app_view;
|
||||||
hr = core_app->GetCurrentView (&core_app_view);
|
hr = core_app->GetCurrentView (&core_app_view);
|
||||||
if (!gst_wasapi2_result (hr))
|
if (FAILED (hr))
|
||||||
return hr;
|
return hr;
|
||||||
|
|
||||||
ComPtr<ICoreWindow> core_window;
|
ComPtr<ICoreWindow> core_window;
|
||||||
hr = core_app_view->get_CoreWindow (&core_window);
|
hr = core_app_view->get_CoreWindow (&core_window);
|
||||||
if (!gst_wasapi2_result (hr))
|
if (FAILED (hr))
|
||||||
return hr;
|
return hr;
|
||||||
|
|
||||||
return core_window->get_Dispatcher (dispatcher);
|
return core_window->get_Dispatcher (dispatcher);
|
||||||
|
@ -1807,7 +1868,7 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
hr = find_dispatcher (&core_dispatcher);
|
hr = find_dispatcher (&core_dispatcher);
|
||||||
if (gst_wasapi2_result (hr)) {
|
if (SUCCEEDED (hr)) {
|
||||||
GST_DEBUG ("UI dispatcher is available");
|
GST_DEBUG ("UI dispatcher is available");
|
||||||
dispatcher = core_dispatcher.Get ();
|
dispatcher = core_dispatcher.Get ();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1826,7 +1887,7 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
* RoInitializeWrapper dtor is called */
|
* RoInitializeWrapper dtor is called */
|
||||||
core_dispatcher.Reset ();
|
core_dispatcher.Reset ();
|
||||||
|
|
||||||
if (!self->audio_client) {
|
if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_FAILED) {
|
||||||
gst_object_unref (self);
|
gst_object_unref (self);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ gboolean gst_wasapi2_client_set_volume (GstWasapi2Client * client,
|
||||||
gboolean gst_wasapi2_client_get_volume (GstWasapi2Client * client,
|
gboolean gst_wasapi2_client_get_volume (GstWasapi2Client * client,
|
||||||
gfloat * volume);
|
gfloat * volume);
|
||||||
|
|
||||||
|
gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client);
|
||||||
|
|
||||||
GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
gboolean low_latency,
|
gboolean low_latency,
|
||||||
gint device_index,
|
gint device_index,
|
||||||
|
|
|
@ -286,14 +286,21 @@ gst_wasapi2_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
||||||
GstWasapi2Sink *self = GST_WASAPI2_SINK (bsink);
|
GstWasapi2Sink *self = GST_WASAPI2_SINK (bsink);
|
||||||
GstCaps *caps = NULL;
|
GstCaps *caps = NULL;
|
||||||
|
|
||||||
/* store one caps here so that we can return device caps even if
|
/* In case of UWP, device activation might not be finished yet */
|
||||||
* audioclient was closed due to unprepare() */
|
if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) {
|
||||||
if (!self->cached_caps && self->client)
|
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
|
||||||
self->cached_caps = gst_wasapi2_client_get_caps (self->client);
|
("Failed to activate device"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (self->client)
|
if (self->client)
|
||||||
caps = gst_wasapi2_client_get_caps (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)
|
if (!caps && self->cached_caps)
|
||||||
caps = gst_caps_ref (self->cached_caps);
|
caps = gst_caps_ref (self->cached_caps);
|
||||||
|
|
||||||
|
@ -374,6 +381,11 @@ gst_wasapi2_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
|
||||||
goto done;
|
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)) {
|
if (!gst_wasapi2_client_open (self->client, spec, bsink->ringbuffer)) {
|
||||||
GST_ERROR_OBJECT (self, "Couldn't open audio client");
|
GST_ERROR_OBJECT (self, "Couldn't open audio client");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
@ -283,14 +283,21 @@ gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
|
||||||
GstWasapi2Src *self = GST_WASAPI2_SRC (bsrc);
|
GstWasapi2Src *self = GST_WASAPI2_SRC (bsrc);
|
||||||
GstCaps *caps = NULL;
|
GstCaps *caps = NULL;
|
||||||
|
|
||||||
/* store one caps here so that we can return device caps even if
|
/* In case of UWP, device activation might not be finished yet */
|
||||||
* audioclient was closed due to unprepare() */
|
if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) {
|
||||||
if (!self->cached_caps && self->client)
|
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
|
||||||
self->cached_caps = gst_wasapi2_client_get_caps (self->client);
|
("Failed to activate device"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (self->client)
|
if (self->client)
|
||||||
caps = gst_wasapi2_client_get_caps (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)
|
if (!caps && self->cached_caps)
|
||||||
caps = gst_caps_ref (self->cached_caps);
|
caps = gst_caps_ref (self->cached_caps);
|
||||||
|
|
||||||
|
@ -371,6 +378,11 @@ gst_wasapi2_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
|
||||||
goto done;
|
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)) {
|
if (!gst_wasapi2_client_open (self->client, spec, bsrc->ringbuffer)) {
|
||||||
GST_ERROR_OBJECT (self, "Couldn't open audio client");
|
GST_ERROR_OBJECT (self, "Couldn't open audio client");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
Loading…
Reference in a new issue