gstreamer/subprojects/gst-plugins-bad/sys/asio/gstasioobject.cpp

1907 lines
52 KiB
C++
Raw Normal View History

/* GStreamer
* Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
*
* 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 <string.h>
#include <avrt.h>
#include <string>
#include <functional>
#include <vector>
#include <mutex>
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* */
/* ASIO COM interface */
struct IASIO : public IUnknown
{
virtual ASIOBool init (gpointer sysHandle) = 0;
virtual void getDriverName (gchar *name) = 0;
virtual glong getDriverVersion () = 0;
virtual void getErrorMessage (gchar * string) = 0;
virtual ASIOError start () = 0;
virtual ASIOError stop () = 0;
virtual ASIOError getChannels (glong * numInputChannels,
glong * numOutputChannels) = 0;
virtual ASIOError getLatencies (glong * inputLatency,
glong * outputLatency) = 0;
virtual ASIOError getBufferSize (glong *minSize, glong *maxSize,
glong * preferredSize, glong *granularity) = 0;
virtual ASIOError canSampleRate (ASIOSampleRate sampleRate) = 0;
virtual ASIOError getSampleRate (ASIOSampleRate * sampleRate) = 0;
virtual ASIOError setSampleRate (ASIOSampleRate sampleRate) = 0;
virtual ASIOError getClockSources (ASIOClockSource *clocks,
glong *numSources) = 0;
virtual ASIOError setClockSource (glong reference) = 0;
virtual ASIOError getSamplePosition (ASIOSamples * sPos,
ASIOTimeStamp *tStamp) = 0;
virtual ASIOError getChannelInfo (ASIOChannelInfo * info) = 0;
virtual ASIOError createBuffers (ASIOBufferInfo * bufferInfos,
glong numChannels, glong bufferSize, ASIOCallbacks * callbacks) = 0;
virtual ASIOError disposeBuffers () = 0;
virtual ASIOError controlPanel () = 0;
virtual ASIOError future (glong selector,gpointer opt) = 0;
virtual ASIOError outputReady () = 0;
};
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 <int instance_id>
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<void(glong, ASIOBool)> buffer_switch;
static std::function<void(ASIOSampleRate)> sample_rate_changed;
static std::function<glong(glong, glong, gpointer, gdouble *)> messages;
static std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> 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 <int instance_id>
std::function<void(glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch;
template <int instance_id>
std::function<void(ASIOSampleRate)> GstAsioCallbacksSlot<instance_id>::sample_rate_changed;
template <int instance_id>
std::function<glong(glong, glong, gpointer, gdouble *)> GstAsioCallbacksSlot<instance_id>::messages;
template <int instance_id>
std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch_time_info;
template <int instance_id>
bool GstAsioCallbacksSlot<instance_id>::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_output_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 > (guint) 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 > (guint) 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 > (guint) 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 > (guint) 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 ((guint) 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 ((guint) 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 ((guint) 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 ((guint) 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 ((guint) 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 < (guint) 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 < (guint) 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 < (guint) 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 < (guint)
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);
}