/* GStreamer * Copyright (C) 2021 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstasioobject.h" #include #include #include #include #include #include #include GST_DEBUG_CATEGORY_STATIC (gst_asio_object_debug); #define GST_CAT_DEFAULT gst_asio_object_debug /* List of GstAsioObject */ static GList *asio_object_list = nullptr; /* *INDENT-OFF* */ /* Protect asio_object_list and other global values */ std::mutex global_lock; /* Protect callback slots */ std::mutex slot_lock; /* *INDENT-ON* */ static void gst_asio_object_buffer_switch (GstAsioObject * self, glong index, ASIOBool process_now); static void gst_asio_object_sample_rate_changed (GstAsioObject * self, ASIOSampleRate rate); static glong gst_asio_object_messages (GstAsioObject * self, glong selector, glong value, gpointer message, gdouble * opt); static ASIOTime *gst_asio_object_buffer_switch_time_info (GstAsioObject * self, ASIOTime * time_info, glong index, ASIOBool process_now); /* *INDENT-OFF* */ /* Object to delegate ASIO callbacks to dedicated GstAsioObject */ class GstAsioCallbacks { public: GstAsioCallbacks (GstAsioObject * object) { g_weak_ref_init (&object_, object); } virtual ~GstAsioCallbacks () { g_weak_ref_clear (&object_); } void BufferSwitch (glong index, ASIOBool process_now) { GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_); if (!obj) return; gst_asio_object_buffer_switch (obj, index, process_now); gst_object_unref (obj); } void SampleRateChanged (ASIOSampleRate rate) { GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_); if (!obj) return; gst_asio_object_sample_rate_changed (obj, rate); gst_object_unref (obj); } glong Messages (glong selector, glong value, gpointer message, gdouble *opt) { GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_); if (!obj) return 0; glong ret = gst_asio_object_messages (obj, selector, value, message, opt); gst_object_unref (obj); return ret; } ASIOTime * BufferSwitchTimeInfo (ASIOTime * time_info, glong index, ASIOBool process_now) { GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_); if (!obj) return nullptr; ASIOTime * ret = gst_asio_object_buffer_switch_time_info (obj, time_info, index, process_now); gst_object_unref (obj); return ret; } private: GWeakRef object_; }; template class GstAsioCallbacksSlot { public: static void BufferSwitchStatic(glong index, ASIOBool process_now) { buffer_switch(index, process_now); } static void SampleRateChangedStatic (ASIOSampleRate rate) { sample_rate_changed(rate); } static glong MessagesStatic(glong selector, glong value, gpointer message, gdouble *opt) { return messages(selector, value, message, opt); } static ASIOTime * BufferSwitchTimeInfoStatic(ASIOTime * time_info, glong index, ASIOBool process_now) { return buffer_switch_time_info(time_info, index, process_now); } static std::function buffer_switch; static std::function sample_rate_changed; static std::function messages; static std::function buffer_switch_time_info; static bool bound; static void Init () { buffer_switch = nullptr; sample_rate_changed = nullptr; messages = nullptr; buffer_switch_time_info = nullptr; bound = false; } static bool IsBound () { return bound; } static void Bind (GstAsioCallbacks * cb, ASIOCallbacks * driver_cb) { buffer_switch = std::bind(&GstAsioCallbacks::BufferSwitch, cb, std::placeholders::_1, std::placeholders::_2); sample_rate_changed = std::bind(&GstAsioCallbacks::SampleRateChanged, cb, std::placeholders::_1); messages = std::bind(&GstAsioCallbacks::Messages, cb, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); buffer_switch_time_info = std::bind(&GstAsioCallbacks::BufferSwitchTimeInfo, cb, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); driver_cb->bufferSwitch = BufferSwitchStatic; driver_cb->sampleRateDidChange = SampleRateChangedStatic; driver_cb->asioMessage = MessagesStatic; driver_cb->bufferSwitchTimeInfo = BufferSwitchTimeInfoStatic; bound = true; } }; template std::function GstAsioCallbacksSlot::buffer_switch; template std::function GstAsioCallbacksSlot::sample_rate_changed; template std::function GstAsioCallbacksSlot::messages; template std::function GstAsioCallbacksSlot::buffer_switch_time_info; template bool GstAsioCallbacksSlot::bound; /* XXX: Create global slot objects, * because ASIO callback doesn't support user data, hum.... */ GstAsioCallbacksSlot<0> cb_slot_0; GstAsioCallbacksSlot<1> cb_slot_1; GstAsioCallbacksSlot<2> cb_slot_2; GstAsioCallbacksSlot<3> cb_slot_3; GstAsioCallbacksSlot<4> cb_slot_4; GstAsioCallbacksSlot<5> cb_slot_5; GstAsioCallbacksSlot<6> cb_slot_6; GstAsioCallbacksSlot<7> cb_slot_7; /* *INDENT-ON* */ typedef struct { GstAsioObjectCallbacks callbacks; guint64 callback_id; } GstAsioObjectCallbacksPrivate; enum { PROP_0, PROP_DEVICE_INFO, }; typedef enum { GST_ASIO_OBJECT_STATE_LOADED, GST_ASIO_OBJECT_STATE_INITIALIZED, GST_ASIO_OBJECT_STATE_PREPARED, GST_ASIO_OBJECT_STATE_RUNNING, } GstAsioObjectState; /* Protect singletone object */ struct _GstAsioObject { GstObject parent; GstAsioDeviceInfo *device_info; GstAsioObjectState state; IASIO *asio_handle; GThread *thread; GMutex lock; GCond cond; GMainContext *context; GMainLoop *loop; GMutex thread_lock; GCond thread_cond; GMutex api_lock; /* called after init() done */ glong max_num_input_channels; glong max_num_output_channels; glong min_buffer_size; glong max_buffer_size; glong preferred_buffer_size; glong buffer_size_granularity; glong selected_buffer_size; /* List of supported sample rate */ GArray *supported_sample_rates; /* List of ASIOChannelInfo */ ASIOChannelInfo *input_channel_infos; ASIOChannelInfo *output_channel_infos; /* Selected sample rate */ ASIOSampleRate sample_rate; /* Input/Output buffer infors */ ASIOBufferInfo *buffer_infos; /* Store requested channel before createbuffer */ gboolean *input_channel_requested; gboolean *output_channel_requested; glong num_requested_input_channels; glong num_requested_output_channels; guint num_allocated_buffers; GList *src_client_callbacks; GList *sink_client_callbacks; GList *loopback_client_callbacks; guint64 next_callback_id; GstAsioCallbacks *callbacks; ASIOCallbacks driver_callbacks; int slot_id; gboolean occupy_all_channels; }; static void gst_asio_object_constructed (GObject * object); static void gst_asio_object_finalize (GObject * object); static void gst_asio_object_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static gpointer gst_asio_object_thread_func (GstAsioObject * self); #define gst_asio_object_parent_class parent_class G_DEFINE_TYPE (GstAsioObject, gst_asio_object, GST_TYPE_OBJECT); static void gst_asio_object_class_init (GstAsioObjectClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = gst_asio_object_constructed; gobject_class->finalize = gst_asio_object_finalize; gobject_class->set_property = gst_asio_object_set_property; g_object_class_install_property (gobject_class, PROP_DEVICE_INFO, g_param_spec_pointer ("device-info", "Device Info", "A pointer to GstAsioDeviceInfo struct", (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); GST_DEBUG_CATEGORY_INIT (gst_asio_object_debug, "asioobject", 0, "asioobject"); } static void gst_asio_object_init (GstAsioObject * self) { g_mutex_init (&self->lock); g_cond_init (&self->cond); g_mutex_init (&self->thread_lock); g_cond_init (&self->thread_cond); g_mutex_init (&self->api_lock); self->supported_sample_rates = g_array_new (FALSE, FALSE, sizeof (ASIOSampleRate)); self->slot_id = -1; } static void gst_asio_object_constructed (GObject * object) { GstAsioObject *self = GST_ASIO_OBJECT (object); if (!self->device_info) { GST_ERROR_OBJECT (self, "Device info was not configured"); return; } self->context = g_main_context_new (); self->loop = g_main_loop_new (self->context, FALSE); g_mutex_lock (&self->lock); self->thread = g_thread_new ("GstAsioObject", (GThreadFunc) gst_asio_object_thread_func, self); while (!g_main_loop_is_running (self->loop)) g_cond_wait (&self->cond, &self->lock); g_mutex_unlock (&self->lock); } static void gst_asio_object_finalize (GObject * object) { GstAsioObject *self = GST_ASIO_OBJECT (object); if (self->loop) { g_main_loop_quit (self->loop); g_thread_join (self->thread); g_main_loop_unref (self->loop); g_main_context_unref (self->context); } g_mutex_clear (&self->lock); g_cond_clear (&self->cond); g_mutex_clear (&self->thread_lock); g_cond_clear (&self->thread_cond); g_mutex_clear (&self->api_lock); g_array_unref (self->supported_sample_rates); gst_asio_device_info_free (self->device_info); g_free (self->input_channel_infos); g_free (self->output_channel_infos); g_free (self->input_channel_requested); g_free (self->output_channel_requested); if (self->src_client_callbacks) g_list_free_full (self->src_client_callbacks, (GDestroyNotify) g_free); if (self->sink_client_callbacks) g_list_free_full (self->sink_client_callbacks, (GDestroyNotify) g_free); if (self->loopback_client_callbacks) g_list_free_full (self->loopback_client_callbacks, (GDestroyNotify) g_free); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_asio_object_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAsioObject *self = GST_ASIO_OBJECT (object); switch (prop_id) { case PROP_DEVICE_INFO: g_clear_pointer (&self->device_info, gst_asio_device_info_free); self->device_info = gst_asio_device_info_copy ((GstAsioDeviceInfo *) g_value_get_pointer (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static HWND gst_asio_object_create_internal_hwnd (GstAsioObject * self) { WNDCLASSEXW wc; ATOM atom = 0; HINSTANCE hinstance = GetModuleHandle (NULL); atom = GetClassInfoExW (hinstance, L"GstAsioInternalWindow", &wc); if (atom == 0) { GST_LOG_OBJECT (self, "Register internal window class"); ZeroMemory (&wc, sizeof (WNDCLASSEX)); wc.cbSize = sizeof (WNDCLASSEX); wc.lpfnWndProc = DefWindowProc; wc.hInstance = GetModuleHandle (nullptr); wc.style = CS_OWNDC; wc.lpszClassName = L"GstAsioInternalWindow"; atom = RegisterClassExW (&wc); if (atom == 0) { GST_ERROR_OBJECT (self, "Failed to register window class 0x%x", (unsigned int) GetLastError ()); return nullptr; } } return CreateWindowExW (0, L"GstAsioInternalWindow", L"GstAsioInternal", WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle (nullptr), nullptr); } static gboolean hwnd_msg_cb (GIOChannel * source, GIOCondition condition, gpointer data) { MSG msg; if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) return G_SOURCE_CONTINUE; TranslateMessage (&msg); DispatchMessage (&msg); return G_SOURCE_CONTINUE; } static gboolean gst_asio_object_main_loop_running_cb (GstAsioObject * self) { GST_INFO_OBJECT (self, "Main loop running now"); g_mutex_lock (&self->lock); g_cond_signal (&self->cond); g_mutex_unlock (&self->lock); return G_SOURCE_REMOVE; } static gboolean gst_asio_object_bind_callbacks (GstAsioObject * self) { std::lock_guard < std::mutex > lk (slot_lock); gboolean ret = TRUE; if (!cb_slot_0.IsBound ()) { cb_slot_0.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 0; } else if (!cb_slot_1.IsBound ()) { cb_slot_1.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 1; } else if (!cb_slot_2.IsBound ()) { cb_slot_2.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 2; } else if (!cb_slot_3.IsBound ()) { cb_slot_3.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 3; } else if (!cb_slot_4.IsBound ()) { cb_slot_4.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 4; } else if (!cb_slot_5.IsBound ()) { cb_slot_5.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 5; } else if (!cb_slot_6.IsBound ()) { cb_slot_6.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 6; } else if (!cb_slot_7.IsBound ()) { cb_slot_7.Bind (self->callbacks, &self->driver_callbacks); self->slot_id = 7; } else { self->slot_id = -1; ret = FALSE; } return ret; } static void gst_asio_object_unbind_callbacks (GstAsioObject * self) { std::lock_guard < std::mutex > lk (slot_lock); if (!self->callbacks || self->slot_id < 0) return; switch (self->slot_id) { case 0: cb_slot_0.Init (); break; case 1: cb_slot_1.Init (); break; case 2: cb_slot_2.Init (); break; case 3: cb_slot_3.Init (); break; case 4: cb_slot_4.Init (); break; case 5: cb_slot_5.Init (); break; case 6: cb_slot_6.Init (); break; case 7: cb_slot_7.Init (); break; default: g_assert_not_reached (); break; } return; } static gpointer gst_asio_object_thread_func (GstAsioObject * self) { HANDLE avrt_handle = nullptr; static DWORD task_idx = 0; HWND hwnd; GSource *source = nullptr; GSource *hwnd_msg_source = nullptr; GIOChannel *msg_io_channel = nullptr; HRESULT hr; ASIOError asio_rst; IASIO *asio_handle = nullptr; GstAsioDeviceInfo *device_info = self->device_info; /* FIXME: check more sample rate */ static ASIOSampleRate sample_rate_to_check[] = { 48000.0, 44100.0, 192000.0, 96000.0, 88200.0, }; g_assert (device_info); GST_INFO_OBJECT (self, "Enter loop, ThreadingModel: %s, driver-name: %s, driver-desc: %s", device_info->sta_model ? "STA" : "MTA", GST_STR_NULL (device_info->driver_name), GST_STR_NULL (device_info->driver_desc)); if (device_info->sta_model) CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); else CoInitializeEx (NULL, COINIT_MULTITHREADED); /* Our thread is unlikely different from driver's working thread though, * let's do this. It should not cause any problem */ AvSetMmThreadCharacteristicsW (L"Pro Audio", &task_idx); g_main_context_push_thread_default (self->context); source = g_idle_source_new (); g_source_set_callback (source, (GSourceFunc) gst_asio_object_main_loop_running_cb, self, nullptr); g_source_attach (source, self->context); g_source_unref (source); /* XXX: not sure why ASIO API wants Windows handle for init(). * Possibly it might be used for STA COM threading * but it's undocummented... */ hwnd = gst_asio_object_create_internal_hwnd (self); if (!hwnd) goto run_loop; hr = CoCreateInstance (device_info->clsid, nullptr, CLSCTX_INPROC_SERVER, device_info->clsid, (gpointer *) & asio_handle); if (FAILED (hr)) { GST_WARNING_OBJECT (self, "Failed to create IASIO instance, hr: 0x%x", (guint) hr); goto run_loop; } if (!asio_handle->init (hwnd)) { GST_WARNING_OBJECT (self, "Failed to init IASIO instance"); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } /* Query device information */ asio_rst = asio_handle->getChannels (&self->max_num_input_channels, &self->max_num_output_channels); if (asio_rst != 0) { GST_WARNING_OBJECT (self, "Failed to query in/out channels, ret %ld", asio_rst); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } GST_INFO_OBJECT (self, "Input/Output channles: %ld/%ld", self->max_num_input_channels, self->max_num_output_channels); asio_rst = asio_handle->getBufferSize (&self->min_buffer_size, &self->max_buffer_size, &self->preferred_buffer_size, &self->buffer_size_granularity); if (asio_rst != 0) { GST_WARNING_OBJECT (self, "Failed to get buffer size, ret %ld", asio_rst); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } /* Use preferreed buffer size by default */ self->selected_buffer_size = self->preferred_buffer_size; GST_INFO_OBJECT (self, "min-buffer-size %ld, max-buffer-size %ld, " "preferred-buffer-size %ld, buffer-size-granularity %ld", self->min_buffer_size, self->max_buffer_size, self->preferred_buffer_size, self->buffer_size_granularity); for (guint i = 0; i < G_N_ELEMENTS (sample_rate_to_check); i++) { asio_rst = asio_handle->canSampleRate (sample_rate_to_check[i]); if (asio_rst != 0) continue; GST_INFO_OBJECT (self, "SampleRate %.1lf is supported", sample_rate_to_check[i]); g_array_append_val (self->supported_sample_rates, sample_rate_to_check[i]); } if (self->supported_sample_rates->len == 0) { GST_WARNING_OBJECT (self, "Failed to query supported sample rate"); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } /* Pick the first supported samplerate */ self->sample_rate = g_array_index (self->supported_sample_rates, ASIOSampleRate, 0); if (asio_handle->setSampleRate (self->sample_rate) != 0) { GST_WARNING_OBJECT (self, "Failed to set samplerate %.1lf", self->sample_rate); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } if (self->max_num_input_channels > 0) { self->input_channel_infos = g_new0 (ASIOChannelInfo, self->max_num_input_channels); for (glong i = 0; i < self->max_num_input_channels; i++) { ASIOChannelInfo *info = &self->input_channel_infos[i]; info->channel = i; info->isInput = TRUE; asio_rst = asio_handle->getChannelInfo (info); if (asio_rst != 0) { GST_WARNING_OBJECT (self, "Failed to %ld input channel info, ret %ld", i, asio_rst); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } GST_INFO_OBJECT (self, "InputChannelInfo %ld: isActive %s, channelGroup %ld, " "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false", info->channelGroup, info->type, GST_STR_NULL (info->name)); } self->input_channel_requested = g_new0 (gboolean, self->max_num_input_channels); } if (self->max_num_output_channels > 0) { self->output_channel_infos = g_new0 (ASIOChannelInfo, self->max_num_output_channels); for (glong i = 0; i < self->max_num_output_channels; i++) { ASIOChannelInfo *info = &self->output_channel_infos[i]; info->channel = i; info->isInput = FALSE; asio_rst = asio_handle->getChannelInfo (info); if (asio_rst != 0) { GST_WARNING_OBJECT (self, "Failed to %ld output channel info, ret %ld", i, asio_rst); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } GST_INFO_OBJECT (self, "OutputChannelInfo %ld: isActive %s, channelGroup %ld, " "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false", info->channelGroup, info->type, GST_STR_NULL (info->name)); } self->output_channel_requested = g_new0 (gboolean, self->max_num_input_channels); } asio_rst = asio_handle->getSampleRate (&self->sample_rate); if (asio_rst != 0) { GST_WARNING_OBJECT (self, "Failed to get current samplerate, ret %ld", asio_rst); asio_handle->Release (); asio_handle = nullptr; goto run_loop; } GST_INFO_OBJECT (self, "Current samplerate %.1lf", self->sample_rate); self->callbacks = new GstAsioCallbacks (self); if (!gst_asio_object_bind_callbacks (self)) { GST_ERROR_OBJECT (self, "Failed to bind callback to slot"); delete self->callbacks; self->callbacks = nullptr; asio_handle->Release (); asio_handle = nullptr; goto run_loop; } msg_io_channel = g_io_channel_win32_new_messages ((guintptr) hwnd); hwnd_msg_source = g_io_create_watch (msg_io_channel, G_IO_IN); g_source_set_callback (hwnd_msg_source, (GSourceFunc) hwnd_msg_cb, self->context, nullptr); g_source_attach (hwnd_msg_source, self->context); self->state = GST_ASIO_OBJECT_STATE_INITIALIZED; self->asio_handle = asio_handle; run_loop: g_main_loop_run (self->loop); if (self->asio_handle) { if (self->state > GST_ASIO_OBJECT_STATE_PREPARED) self->asio_handle->stop (); if (self->state > GST_ASIO_OBJECT_STATE_INITIALIZED) self->asio_handle->disposeBuffers (); } gst_asio_object_unbind_callbacks (self); if (self->callbacks) { delete self->callbacks; self->callbacks = nullptr; } if (hwnd_msg_source) { g_source_destroy (hwnd_msg_source); g_source_unref (hwnd_msg_source); } if (msg_io_channel) g_io_channel_unref (msg_io_channel); if (hwnd) DestroyWindow (hwnd); g_main_context_pop_thread_default (self->context); if (avrt_handle) AvRevertMmThreadCharacteristics (avrt_handle); if (asio_handle) { asio_handle->Release (); asio_handle = nullptr; } CoUninitialize (); GST_INFO_OBJECT (self, "Exit loop"); return nullptr; } static void gst_asio_object_weak_ref_notify (gpointer data, GstAsioObject * object) { std::lock_guard < std::mutex > lk (global_lock); asio_object_list = g_list_remove (asio_object_list, object); } GstAsioObject * gst_asio_object_new (const GstAsioDeviceInfo * info, gboolean occupy_all_channels) { GstAsioObject *self = nullptr; GList *iter; std::lock_guard < std::mutex > lk (global_lock); g_return_val_if_fail (info != nullptr, nullptr); /* Check if we have object corresponding to CLSID, and if so return * already existing object instead of allocating new one */ for (iter = asio_object_list; iter; iter = g_list_next (iter)) { GstAsioObject *object = (GstAsioObject *) iter->data; if (object->device_info->clsid == info->clsid) { GST_DEBUG_OBJECT (object, "Found configured ASIO object"); self = (GstAsioObject *) gst_object_ref (object); break; } } if (self) return self; self = (GstAsioObject *) g_object_new (GST_TYPE_ASIO_OBJECT, "device-info", info, nullptr); if (!self->asio_handle) { GST_WARNING_OBJECT (self, "ASIO handle is not available"); gst_object_unref (self); return nullptr; } self->occupy_all_channels = occupy_all_channels; gst_object_ref_sink (self); g_object_weak_ref (G_OBJECT (self), (GWeakNotify) gst_asio_object_weak_ref_notify, nullptr); asio_object_list = g_list_append (asio_object_list, self); return self; } static GstCaps * gst_asio_object_create_caps_from_channel_info (GstAsioObject * self, ASIOChannelInfo * info, guint min_num_channels, guint max_num_channels) { GstCaps *caps; std::string caps_str; GstAudioFormat fmt; const gchar *fmt_str; g_assert (info); g_assert (max_num_channels >= min_num_channels); fmt = gst_asio_sample_type_to_gst (info->type); if (fmt == GST_AUDIO_FORMAT_UNKNOWN) { GST_ERROR_OBJECT (self, "Unknown format"); return nullptr; } fmt_str = gst_audio_format_to_string (fmt); /* Actually we are non-interleaved, but element will interlave data */ caps_str = "audio/x-raw, layout = (string) interleaved, "; caps_str += "format = (string) " + std::string (fmt_str) + ", "; /* use fixated sample rate, otherwise get_caps/set_sample_rate() might * be racy in case that multiple sink/src are used */ caps_str += "rate = (int) " + std::to_string ((gint) self->sample_rate) + ", "; if (max_num_channels == min_num_channels) caps_str += "channels = (int) " + std::to_string (max_num_channels); else caps_str += "channels = (int) [ " + std::to_string (min_num_channels) + ", " + std::to_string (max_num_channels) + " ]"; caps = gst_caps_from_string (caps_str.c_str ()); if (!caps) { GST_ERROR_OBJECT (self, "Failed to create caps"); return nullptr; } GST_DEBUG_OBJECT (self, "Create caps %" GST_PTR_FORMAT, caps); return caps; } /* FIXME: assuming all channels has the same format but it might not be true? */ GstCaps * gst_asio_object_get_caps (GstAsioObject * obj, GstAsioDeviceClassType type, guint min_num_channels, guint max_num_channels) { ASIOChannelInfo *infos; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), nullptr); if (type == GST_ASIO_DEVICE_CLASS_CAPTURE) { if (obj->max_num_input_channels == 0) { GST_WARNING_OBJECT (obj, "Device doesn't support input"); return nullptr; } /* max_num_channels == 0 means [1, max-allowed-channles] */ if (max_num_channels > 0) { if (max_num_channels > obj->max_num_input_channels) { GST_WARNING_OBJECT (obj, "Too many max channels"); return nullptr; } } else { max_num_channels = obj->max_num_input_channels; } if (min_num_channels > 0) { if (min_num_channels > obj->max_num_input_channels) { GST_WARNING_OBJECT (obj, "Too many min channels"); return nullptr; } } else { min_num_channels = 1; } infos = obj->input_channel_infos; } else { if (obj->max_num_output_channels == 0) { GST_WARNING_OBJECT (obj, "Device doesn't support output"); return nullptr; } /* max_num_channels == 0 means [1, max-allowed-channles] */ if (max_num_channels > 0) { if (max_num_channels > obj->max_num_output_channels) { GST_WARNING_OBJECT (obj, "Too many max channels"); return nullptr; } } else { max_num_channels = obj->max_num_output_channels; } if (min_num_channels > 0) { if (min_num_channels > obj->max_num_output_channels) { GST_WARNING_OBJECT (obj, "Too many min channels"); return nullptr; } } else { min_num_channels = 1; } infos = obj->output_channel_infos; } return gst_asio_object_create_caps_from_channel_info (obj, infos, min_num_channels, max_num_channels); } gboolean gst_asio_object_get_max_num_channels (GstAsioObject * obj, glong * num_input_ch, glong * num_output_ch) { g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); if (num_input_ch) *num_input_ch = obj->max_num_input_channels; if (num_output_ch) *num_output_ch = obj->max_num_output_channels; return TRUE; } gboolean gst_asio_object_get_buffer_size (GstAsioObject * obj, glong * min_size, glong * max_size, glong * preferred_size, glong * granularity) { g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); if (min_size) *min_size = obj->min_buffer_size; if (max_size) *max_size = obj->max_buffer_size; if (preferred_size) *preferred_size = obj->preferred_buffer_size; if (granularity) *granularity = obj->buffer_size_granularity; return TRUE; } typedef void (*GstAsioObjectThreadFunc) (GstAsioObject * obj, gpointer data); typedef struct { GstAsioObject *self; GstAsioObjectThreadFunc func; gpointer data; gboolean fired; } GstAsioObjectThreadRunData; static gboolean gst_asio_object_thread_run_func (GstAsioObjectThreadRunData * data) { GstAsioObject *self = data->self; if (data->func) data->func (self, data->data); g_mutex_lock (&self->thread_lock); data->fired = TRUE; g_cond_broadcast (&self->thread_cond); g_mutex_unlock (&self->thread_lock); return G_SOURCE_REMOVE; } static void gst_asio_object_thread_add (GstAsioObject * self, GstAsioObjectThreadFunc func, gpointer data) { GstAsioObjectThreadRunData thread_data; g_return_if_fail (GST_IS_ASIO_OBJECT (self)); thread_data.self = self; thread_data.func = func; thread_data.data = data; thread_data.fired = FALSE; g_main_context_invoke (self->context, (GSourceFunc) gst_asio_object_thread_run_func, &thread_data); g_mutex_lock (&self->thread_lock); while (!thread_data.fired) g_cond_wait (&self->thread_cond, &self->thread_lock); g_mutex_unlock (&self->thread_lock); } static gboolean gst_asio_object_validate_channels (GstAsioObject * self, gboolean is_input, guint * channel_indices, guint num_channels) { if (is_input) { if (self->max_num_input_channels < num_channels) { GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld", num_channels, self->max_num_input_channels); return FALSE; } for (guint i = 0; i < num_channels; i++) { guint ch = channel_indices[i]; if (self->max_num_input_channels <= ch) { GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld", ch, self->max_num_input_channels); return FALSE; } } } else { if (self->max_num_output_channels < num_channels) { GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld", num_channels, self->max_num_output_channels); return FALSE; } for (guint i = 0; i < num_channels; i++) { guint ch = channel_indices[i]; if (self->max_num_output_channels <= ch) { GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld", ch, self->max_num_output_channels); return FALSE; } } } return TRUE; } static gboolean gst_asio_object_check_buffer_reuse (GstAsioObject * self, ASIOBool is_input, guint * channel_indices, guint num_channels) { guint num_found = 0; g_assert (self->buffer_infos); g_assert (self->num_allocated_buffers > 0); for (guint i = 0; i < self->num_allocated_buffers; i++) { ASIOBufferInfo *info = &self->buffer_infos[i]; if (info->isInput != is_input) continue; for (guint j = 0; j < num_channels; j++) { if (info->channelNum == channel_indices[j]) { num_found++; break; } } } return num_found == num_channels; } static void gst_asio_object_dispose_buffers_async (GstAsioObject * self, ASIOError * rst) { g_assert (self->asio_handle); g_assert (rst); *rst = self->asio_handle->disposeBuffers (); } static gboolean gst_asio_object_dispose_buffers (GstAsioObject * self) { ASIOError rst; g_assert (self->asio_handle); if (!self->buffer_infos) return TRUE; if (!self->device_info->sta_model) { rst = self->asio_handle->disposeBuffers (); } else { gst_asio_object_thread_add (self, (GstAsioObjectThreadFunc) gst_asio_object_dispose_buffers_async, &rst); } g_clear_pointer (&self->buffer_infos, g_free); self->num_allocated_buffers = 0; return rst == 0; } static ASIOError gst_asio_object_create_buffers_real (GstAsioObject * self, glong * buffer_size) { ASIOError err; g_assert (buffer_size); err = self->asio_handle->createBuffers (self->buffer_infos, self->num_requested_input_channels + self->num_requested_output_channels, *buffer_size, &self->driver_callbacks); /* It failed and buffer size is not equal to preferred size, * try again with preferred size */ if (err != 0 && *buffer_size != self->preferred_buffer_size) { GST_WARNING_OBJECT (self, "Failed to create buffer with buffer size %ld, try again with %ld", *buffer_size, self->preferred_buffer_size); err = self->asio_handle->createBuffers (self->buffer_infos, self->num_requested_input_channels + self->num_requested_output_channels, self->preferred_buffer_size, &self->driver_callbacks); if (!err) { *buffer_size = self->preferred_buffer_size; } } return err; } typedef struct { glong buffer_size; ASIOError err; } CreateBuffersAsyncData; static void gst_asio_object_create_buffers_async (GstAsioObject * self, CreateBuffersAsyncData * data) { data->err = gst_asio_object_create_buffers_real (self, &data->buffer_size); } static gboolean gst_asio_object_create_buffers_internal (GstAsioObject * self, glong * buffer_size) { ASIOError err; g_assert (self->asio_handle); if (!self->device_info->sta_model) { err = gst_asio_object_create_buffers_real (self, buffer_size); } else { CreateBuffersAsyncData data; data.buffer_size = *buffer_size; gst_asio_object_thread_add (self, (GstAsioObjectThreadFunc) gst_asio_object_create_buffers_async, &data); err = data.err; *buffer_size = data.buffer_size; } return !err; } gboolean gst_asio_object_create_buffers (GstAsioObject * obj, GstAsioDeviceClassType type, guint * channel_indices, guint num_channels, guint * buffer_size) { gboolean can_reuse = FALSE; guint i, j; glong buf_size; glong prev_buf_size = 0; gboolean is_src; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_return_val_if_fail (channel_indices != nullptr, FALSE); g_return_val_if_fail (num_channels > 0, FALSE); GST_DEBUG_OBJECT (obj, "Create buffers"); if (type == GST_ASIO_DEVICE_CLASS_CAPTURE) is_src = TRUE; else is_src = FALSE; g_mutex_lock (&obj->api_lock); if (!gst_asio_object_validate_channels (obj, is_src, channel_indices, num_channels)) { GST_ERROR_OBJECT (obj, "Invalid request"); g_mutex_unlock (&obj->api_lock); return FALSE; } if (obj->buffer_infos) { GST_DEBUG_OBJECT (obj, "Have configured buffer infors, checking whether we can reuse it"); can_reuse = gst_asio_object_check_buffer_reuse (obj, is_src ? TRUE : FALSE, channel_indices, num_channels); } if (can_reuse) { GST_DEBUG_OBJECT (obj, "We can reuse already allocated buffers"); if (buffer_size) *buffer_size = obj->selected_buffer_size; g_mutex_unlock (&obj->api_lock); return TRUE; } /* Cannot re-allocated buffers once started... */ if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) { GST_WARNING_OBJECT (obj, "We are running already"); g_mutex_unlock (&obj->api_lock); return FALSE; } /* Use already configured buffer size */ if (obj->buffer_infos) prev_buf_size = obj->selected_buffer_size; /* If we have configured buffers, dispose and re-allocate */ if (!gst_asio_object_dispose_buffers (obj)) { GST_ERROR_OBJECT (obj, "Failed to dispose buffers"); obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED; g_mutex_unlock (&obj->api_lock); return FALSE; } if (obj->occupy_all_channels) { GST_INFO_OBJECT (obj, "occupy-all-channels mode, will allocate buffers for all channels"); /* In this case, we will allocate buffer for all available input/output * channles, regardless of what requested here */ for (guint i = 0; i < (guint) obj->max_num_input_channels; i++) obj->input_channel_requested[i] = TRUE; for (guint i = 0; i < (guint) obj->max_num_output_channels; i++) obj->output_channel_requested[i] = TRUE; obj->num_requested_input_channels = obj->max_num_input_channels; obj->num_requested_output_channels = obj->max_num_output_channels; } else { if (is_src) { for (guint i = 0; i < num_channels; i++) { guint ch = channel_indices[i]; obj->input_channel_requested[ch] = TRUE; } obj->num_requested_input_channels = 0; for (guint i = 0; i < obj->max_num_input_channels; i++) { if (obj->input_channel_requested[i]) obj->num_requested_input_channels++; } } else { for (guint i = 0; i < num_channels; i++) { guint ch = channel_indices[i]; obj->output_channel_requested[ch] = TRUE; } obj->num_requested_output_channels = 0; for (guint i = 0; i < obj->max_num_output_channels; i++) { if (obj->output_channel_requested[i]) obj->num_requested_output_channels++; } } } obj->num_allocated_buffers = obj->num_requested_input_channels + obj->num_requested_output_channels; obj->buffer_infos = g_new0 (ASIOBufferInfo, obj->num_allocated_buffers); for (i = 0, j = 0; i < obj->num_requested_input_channels; i++) { ASIOBufferInfo *info = &obj->buffer_infos[i]; info->isInput = TRUE; while (!obj->input_channel_requested[j]) j++; info->channelNum = j; j++; } for (i = obj->num_requested_input_channels, j = 0; i < obj->num_requested_input_channels + obj->num_requested_output_channels; i++) { ASIOBufferInfo *info = &obj->buffer_infos[i]; info->isInput = FALSE; while (!obj->output_channel_requested[j]) j++; info->channelNum = j; j++; } if (prev_buf_size > 0) { buf_size = prev_buf_size; } else if (buffer_size && *buffer_size > 0) { buf_size = *buffer_size; } else { buf_size = obj->preferred_buffer_size; } GST_INFO_OBJECT (obj, "Creating buffer with size %ld", buf_size); if (!gst_asio_object_create_buffers_internal (obj, &buf_size)) { GST_ERROR_OBJECT (obj, "Failed to create buffers"); g_clear_pointer (&obj->buffer_infos, g_free); obj->num_allocated_buffers = 0; obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED; g_mutex_unlock (&obj->api_lock); return FALSE; } GST_INFO_OBJECT (obj, "Selected buffer size %ld", buf_size); obj->selected_buffer_size = buf_size; if (buffer_size) *buffer_size = buf_size; obj->state = GST_ASIO_OBJECT_STATE_PREPARED; g_mutex_unlock (&obj->api_lock); return TRUE; } typedef struct { glong arg[4]; ASIOError ret; } RunAsyncData; static void gst_asio_object_get_latencies_async (GstAsioObject * self, RunAsyncData * data) { data->ret = self->asio_handle->getLatencies (&data->arg[0], &data->arg[1]); } gboolean gst_asio_object_get_latencies (GstAsioObject * obj, glong * input_latency, glong * output_latency) { RunAsyncData data = { 0 }; ASIOError err; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_assert (obj->asio_handle); if (!obj->device_info->sta_model) { err = obj->asio_handle->getLatencies (input_latency, output_latency); } else { gst_asio_object_thread_add (obj, (GstAsioObjectThreadFunc) gst_asio_object_get_latencies_async, &data); *input_latency = data.arg[0]; *output_latency = data.arg[1]; err = data.ret; } return !err; } typedef struct { ASIOSampleRate sample_rate; ASIOError err; } SampleRateAsyncData; static void gst_asio_object_can_sample_rate_async (GstAsioObject * self, SampleRateAsyncData * data) { data->err = self->asio_handle->canSampleRate (data->sample_rate); } gboolean gst_asio_object_can_sample_rate (GstAsioObject * obj, ASIOSampleRate sample_rate) { SampleRateAsyncData data = { 0 }; ASIOError err = 0; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_assert (obj->asio_handle); g_mutex_lock (&obj->api_lock); for (guint i = 0; i < obj->supported_sample_rates->len; i++) { ASIOSampleRate val = g_array_index (obj->supported_sample_rates, ASIOSampleRate, i); if (val == sample_rate) { g_mutex_unlock (&obj->api_lock); return TRUE; } } if (!obj->device_info->sta_model) { err = obj->asio_handle->canSampleRate (sample_rate); if (!err) g_array_append_val (obj->supported_sample_rates, sample_rate); g_mutex_unlock (&obj->api_lock); return !err; } data.sample_rate = sample_rate; gst_asio_object_thread_add (obj, (GstAsioObjectThreadFunc) gst_asio_object_can_sample_rate_async, &data); if (!data.err) g_array_append_val (obj->supported_sample_rates, sample_rate); g_mutex_unlock (&obj->api_lock); return !data.err; } gboolean gst_asio_object_get_sample_rate (GstAsioObject * obj, ASIOSampleRate * sample_rate) { g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); *sample_rate = obj->sample_rate; return 0; } static void gst_asio_object_set_sample_rate_async (GstAsioObject * self, SampleRateAsyncData * data) { data->err = self->asio_handle->setSampleRate (data->sample_rate); if (!data->err) self->sample_rate = data->sample_rate; } gboolean gst_asio_object_set_sample_rate (GstAsioObject * obj, ASIOSampleRate sample_rate) { SampleRateAsyncData data = { 0 }; ASIOError err = 0; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_assert (obj->asio_handle); g_mutex_lock (&obj->api_lock); if (sample_rate == obj->sample_rate) { g_mutex_unlock (&obj->api_lock); return TRUE; } if (!obj->device_info->sta_model) { err = obj->asio_handle->setSampleRate (sample_rate); if (!err) obj->sample_rate = sample_rate; g_mutex_unlock (&obj->api_lock); return !err; } data.sample_rate = sample_rate; gst_asio_object_thread_add (obj, (GstAsioObjectThreadFunc) gst_asio_object_set_sample_rate_async, &data); g_mutex_unlock (&obj->api_lock); return !data.err; } static void gst_asio_object_buffer_switch (GstAsioObject * self, glong index, ASIOBool process_now) { ASIOTime time_info; ASIOTime *our_time_info = nullptr; ASIOError err = 0; memset (&time_info, 0, sizeof (ASIOTime)); err = self->asio_handle->getSamplePosition (&time_info.timeInfo.samplePosition, &time_info.timeInfo.systemTime); if (!err) our_time_info = &time_info; gst_asio_object_buffer_switch_time_info (self, our_time_info, index, process_now); } static void gst_asio_object_sample_rate_changed (GstAsioObject * self, ASIOSampleRate rate) { GST_INFO_OBJECT (self, "SampleRate changed to %lf", rate); } static glong gst_asio_object_messages (GstAsioObject * self, glong selector, glong value, gpointer message, gdouble * opt) { GST_DEBUG_OBJECT (self, "ASIO message: %ld, %ld", selector, value); switch (selector) { case kAsioSelectorSupported: if (value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged || value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor) return 0; else if (value == kAsioSupportsTimeInfo) return 1; GST_WARNING_OBJECT (self, "Unsupported ASIO selector: %li", value); break; case kAsioBufferSizeChange: GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioBufferSizeChange"); break; case kAsioResetRequest: GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResetRequest"); break; case kAsioResyncRequest: GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResyncRequest"); break; case kAsioLatenciesChanged: GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioLatenciesChanged"); break; case kAsioEngineVersion: /* We target the ASIO v2 API, which includes ASIOOutputReady() */ return 2; case kAsioSupportsTimeInfo: /* We use the new time info buffer switch callback */ return 1; case kAsioSupportsTimeCode: /* We don't use the time code info right now */ return 0; default: GST_WARNING_OBJECT (self, "Unsupported ASIO message: %li, %li", selector, value); break; } return 0; } #define PACK_ASIO_64(v) ((v).lo | ((guint64)((v).hi) << 32)) static ASIOTime * gst_asio_object_buffer_switch_time_info (GstAsioObject * self, ASIOTime * time_info, glong index, ASIOBool process_now) { GList *iter; if (time_info) { guint64 pos; guint64 system_time; pos = PACK_ASIO_64 (time_info->timeInfo.samplePosition); system_time = PACK_ASIO_64 (time_info->timeInfo.systemTime); GST_TRACE_OBJECT (self, "Sample Position: %" G_GUINT64_FORMAT ", System Time: %" GST_TIME_FORMAT, pos, GST_TIME_ARGS (system_time)); } g_mutex_lock (&self->api_lock); if (!self->src_client_callbacks && !self->sink_client_callbacks && !self->loopback_client_callbacks) { GST_WARNING_OBJECT (self, "No installed client callback"); goto out; } for (iter = self->src_client_callbacks; iter;) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; gboolean ret; ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos, self->num_allocated_buffers, self->input_channel_infos, self->output_channel_infos, self->sample_rate, self->selected_buffer_size, time_info, cb->callbacks.user_data); if (!ret) { GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT, cb->callback_id); GList *to_remove = iter; iter = g_list_next (iter); g_free (to_remove->data); g_list_free (to_remove); } iter = g_list_next (iter); } for (iter = self->sink_client_callbacks; iter;) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; gboolean ret; ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos, self->num_allocated_buffers, self->input_channel_infos, self->output_channel_infos, self->sample_rate, self->selected_buffer_size, time_info, cb->callbacks.user_data); if (!ret) { GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT, cb->callback_id); GList *to_remove = iter; iter = g_list_next (iter); g_free (to_remove->data); g_list_free (to_remove); } iter = g_list_next (iter); } for (iter = self->loopback_client_callbacks; iter;) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; gboolean ret; ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos, self->num_allocated_buffers, self->input_channel_infos, self->output_channel_infos, self->sample_rate, self->selected_buffer_size, time_info, cb->callbacks.user_data); if (!ret) { GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT, cb->callback_id); GList *to_remove = iter; iter = g_list_next (iter); g_free (to_remove->data); g_list_free (to_remove); } iter = g_list_next (iter); } self->asio_handle->outputReady (); out: g_mutex_unlock (&self->api_lock); return nullptr; } static void gst_asio_object_start_async (GstAsioObject * self, ASIOError * rst) { *rst = self->asio_handle->start (); } gboolean gst_asio_object_start (GstAsioObject * obj) { ASIOError ret; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_mutex_lock (&obj->api_lock); if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) { GST_DEBUG_OBJECT (obj, "We are running already"); g_mutex_unlock (&obj->api_lock); return TRUE; } else if (obj->state < GST_ASIO_OBJECT_STATE_PREPARED) { GST_ERROR_OBJECT (obj, "We are not prepared"); g_mutex_unlock (&obj->api_lock); return FALSE; } /* Then start */ if (!obj->device_info->sta_model) { ret = obj->asio_handle->start (); } else { gst_asio_object_thread_add (obj, (GstAsioObjectThreadFunc) gst_asio_object_start_async, &ret); } if (ret != 0) { GST_ERROR_OBJECT (obj, "Failed to start object"); g_mutex_unlock (&obj->api_lock); return FALSE; } obj->state = GST_ASIO_OBJECT_STATE_RUNNING; g_mutex_unlock (&obj->api_lock); return TRUE; } gboolean gst_asio_object_install_callback (GstAsioObject * obj, GstAsioDeviceClassType type, GstAsioObjectCallbacks * callbacks, guint64 * callback_id) { GstAsioObjectCallbacksPrivate *cb; g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE); g_return_val_if_fail (callbacks != nullptr, FALSE); g_return_val_if_fail (callback_id != nullptr, FALSE); g_mutex_lock (&obj->api_lock); cb = g_new0 (GstAsioObjectCallbacksPrivate, 1); cb->callbacks = *callbacks; cb->callback_id = obj->next_callback_id; switch (type) { case GST_ASIO_DEVICE_CLASS_CAPTURE: obj->src_client_callbacks = g_list_append (obj->src_client_callbacks, cb); break; case GST_ASIO_DEVICE_CLASS_RENDER: obj->sink_client_callbacks = g_list_append (obj->sink_client_callbacks, cb); break; case GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE: obj->loopback_client_callbacks = g_list_append (obj->loopback_client_callbacks, cb); break; default: g_assert_not_reached (); g_free (cb); return FALSE; } *callback_id = cb->callback_id; g_mutex_unlock (&obj->api_lock); return TRUE; } void gst_asio_object_uninstall_callback (GstAsioObject * obj, guint64 callback_id) { GList *iter; g_return_if_fail (GST_IS_ASIO_OBJECT (obj)); g_mutex_lock (&obj->api_lock); GST_DEBUG_OBJECT (obj, "Removing callback id %" G_GUINT64_FORMAT, callback_id); for (iter = obj->src_client_callbacks; iter; iter = g_list_next (iter)) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; if (cb->callback_id != callback_id) continue; GST_DEBUG_OBJECT (obj, "Found src callback for id %" G_GUINT64_FORMAT, callback_id); obj->src_client_callbacks = g_list_remove_link (obj->src_client_callbacks, iter); g_free (iter->data); g_list_free (iter); g_mutex_unlock (&obj->api_lock); return; } for (iter = obj->sink_client_callbacks; iter; iter = g_list_next (iter)) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; if (cb->callback_id != callback_id) continue; GST_DEBUG_OBJECT (obj, "Found sink callback for id %" G_GUINT64_FORMAT, callback_id); obj->sink_client_callbacks = g_list_remove_link (obj->sink_client_callbacks, iter); g_free (iter->data); g_list_free (iter); g_mutex_unlock (&obj->api_lock); return; } for (iter = obj->loopback_client_callbacks; iter; iter = g_list_next (iter)) { GstAsioObjectCallbacksPrivate *cb = (GstAsioObjectCallbacksPrivate *) iter->data; if (cb->callback_id != callback_id) continue; GST_DEBUG_OBJECT (obj, "Found loopback callback for id %" G_GUINT64_FORMAT, callback_id); obj->loopback_client_callbacks = g_list_remove_link (obj->loopback_client_callbacks, iter); g_free (iter->data); g_list_free (iter); break; } g_mutex_unlock (&obj->api_lock); }