/* 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 "gstasioringbuffer.h" #include <string.h> #include "gstasioutils.h" #include "gstasioobject.h" GST_DEBUG_CATEGORY_STATIC (gst_asio_ring_buffer_debug); #define GST_CAT_DEFAULT gst_asio_ring_buffer_debug struct _GstAsioRingBuffer { GstAudioRingBuffer parent; GstAsioDeviceClassType type; GstAsioObject *asio_object; guint *channel_indices; guint num_channels; ASIOBufferInfo **infos; guint64 callback_id; gboolean callback_installed; gboolean running; guint buffer_size; /* Used to detect sample gap */ gboolean is_first; guint64 expected_sample_position; gboolean trace_sample_position; }; enum { PROP_0, PROP_DEVICE_INFO, }; static void gst_asio_ring_buffer_dispose (GObject * object); static gboolean gst_asio_ring_buffer_open_device (GstAudioRingBuffer * buf); static gboolean gst_asio_ring_buffer_close_device (GstAudioRingBuffer * buf); static gboolean gst_asio_ring_buffer_acquire (GstAudioRingBuffer * buf, GstAudioRingBufferSpec * spec); static gboolean gst_asio_ring_buffer_release (GstAudioRingBuffer * buf); static gboolean gst_asio_ring_buffer_start (GstAudioRingBuffer * buf); static gboolean gst_asio_ring_buffer_stop (GstAudioRingBuffer * buf); static guint gst_asio_ring_buffer_delay (GstAudioRingBuffer * buf); static gboolean gst_asio_buffer_switch_cb (GstAsioObject * obj, glong index, ASIOBufferInfo * infos, guint num_infos, ASIOChannelInfo * input_channel_infos, ASIOChannelInfo * output_channel_infos, ASIOSampleRate sample_rate, glong buffer_size, gpointer user_data); #define gst_asio_ring_buffer_parent_class parent_class G_DEFINE_TYPE (GstAsioRingBuffer, gst_asio_ring_buffer, GST_TYPE_AUDIO_RING_BUFFER); static void gst_asio_ring_buffer_class_init (GstAsioRingBufferClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstAudioRingBufferClass *ring_buffer_class = GST_AUDIO_RING_BUFFER_CLASS (klass); gobject_class->dispose = gst_asio_ring_buffer_dispose; ring_buffer_class->open_device = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_open_device); ring_buffer_class->close_device = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_close_device); ring_buffer_class->acquire = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_acquire); ring_buffer_class->release = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_release); ring_buffer_class->start = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_start); ring_buffer_class->resume = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_start); ring_buffer_class->stop = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_stop); ring_buffer_class->delay = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_delay); GST_DEBUG_CATEGORY_INIT (gst_asio_ring_buffer_debug, "asioringbuffer", 0, "asioringbuffer"); } static void gst_asio_ring_buffer_init (GstAsioRingBuffer * self) { } static void gst_asio_ring_buffer_dispose (GObject * object) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (object); gst_clear_object (&self->asio_object); g_clear_pointer (&self->channel_indices, g_free); G_OBJECT_CLASS (parent_class)->dispose (object); } static gboolean gst_asio_ring_buffer_open_device (GstAudioRingBuffer * buf) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf); GST_DEBUG_OBJECT (self, "Open"); return TRUE; } static gboolean gst_asio_ring_buffer_close_device (GstAudioRingBuffer * buf) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf); GST_DEBUG_OBJECT (self, "Close"); return TRUE; } #define PACK_ASIO_64(v) ((v).lo | ((guint64)((v).hi) << 32)) static gboolean gst_asio_buffer_switch_cb (GstAsioObject * obj, glong index, ASIOBufferInfo * infos, guint num_infos, ASIOChannelInfo * input_channel_infos, ASIOChannelInfo * output_channel_infos, ASIOSampleRate sample_rate, glong buffer_size, ASIOTime * time_info, gpointer user_data) { GstAsioRingBuffer *self = (GstAsioRingBuffer *) user_data; GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self); gint segment; guint8 *readptr; gint len; guint i, j; guint num_channels = 0; guint bps = GST_AUDIO_INFO_WIDTH (&ringbuffer->spec.info) >> 3; g_assert (index == 0 || index == 1); g_assert (num_infos >= self->num_channels); GST_TRACE_OBJECT (self, "Buffer Switch callback, index %ld", index); if (!gst_audio_ring_buffer_prepare_read (ringbuffer, &segment, &readptr, &len)) { GST_WARNING_OBJECT (self, "No segment available"); return TRUE; } GST_TRACE_OBJECT (self, "segment %d, length %d", segment, len); /* Check missing frames */ if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE) { if (self->is_first) { if (time_info) { self->expected_sample_position = PACK_ASIO_64 (time_info->timeInfo.samplePosition) + buffer_size; self->trace_sample_position = TRUE; } else { GST_WARNING_OBJECT (self, "ASIOTime is not available"); self->trace_sample_position = FALSE; } self->is_first = FALSE; } else if (self->trace_sample_position) { if (!time_info) { GST_WARNING_OBJECT (self, "ASIOTime is not available"); self->trace_sample_position = FALSE; } else { guint64 sample_position = PACK_ASIO_64 (time_info->timeInfo.samplePosition); if (self->expected_sample_position < sample_position) { guint64 gap_frames = sample_position - self->expected_sample_position; gint gap_size = gap_frames * bps; GST_WARNING_OBJECT (self, "%" G_GUINT64_FORMAT " frames are missing"); while (gap_size >= len) { gst_audio_format_info_fill_silence (ringbuffer->spec.info.finfo, readptr, len); gst_audio_ring_buffer_advance (ringbuffer, 1); gst_audio_ring_buffer_prepare_read (ringbuffer, &segment, &readptr, &len); gap_size -= len; } } self->expected_sample_position = sample_position + buffer_size; GST_TRACE_OBJECT (self, "Sample Position %" G_GUINT64_FORMAT ", next: %" G_GUINT64_FORMAT, sample_position, self->expected_sample_position); } } } /* Given @infos might contain more channel data, pick channels what we want to * read */ for (i = 0; i < num_infos; i++) { ASIOBufferInfo *info = &infos[i]; if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE) { if (!info->isInput) continue; } else { if (info->isInput) continue; } for (j = 0; j < self->num_channels; j++) { if (self->channel_indices[j] != info->channelNum) continue; g_assert (num_channels < self->num_channels); self->infos[num_channels++] = info; break; } } if (num_channels < self->num_channels) { GST_ERROR_OBJECT (self, "Too small number of channel %d (expected %d)", num_channels, self->num_channels); } else { if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE || self->type == GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE) { if (num_channels == 1) { memcpy (readptr, self->infos[0]->buffers[index], len); } else { guint gst_offset = 0, asio_offset = 0; /* Interleaves audio */ while (gst_offset < len) { for (i = 0; i < num_channels; i++) { ASIOBufferInfo *info = self->infos[i]; memcpy (readptr + gst_offset, ((guint8 *) info->buffers[index]) + asio_offset, bps); gst_offset += bps; } asio_offset += bps; } } } else { if (num_channels == 1) { memcpy (self->infos[0]->buffers[index], readptr, len); } else { guint gst_offset = 0, asio_offset = 0; /* Interleaves audio */ while (gst_offset < len) { for (i = 0; i < num_channels; i++) { ASIOBufferInfo *info = self->infos[i]; memcpy (((guint8 *) info->buffers[index]) + asio_offset, readptr + gst_offset, bps); gst_offset += bps; } asio_offset += bps; } } } } if (self->type == GST_ASIO_DEVICE_CLASS_RENDER) gst_audio_ring_buffer_clear (ringbuffer, segment); gst_audio_ring_buffer_advance (ringbuffer, 1); return TRUE; } static gboolean gst_asio_ring_buffer_acquire (GstAudioRingBuffer * buf, GstAudioRingBufferSpec * spec) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf); if (!self->asio_object) { GST_ERROR_OBJECT (self, "No configured ASIO object"); return FALSE; } if (!self->channel_indices || self->num_channels == 0) { GST_ERROR_OBJECT (self, "No configured channels"); return FALSE; } if (!gst_asio_object_set_sample_rate (self->asio_object, GST_AUDIO_INFO_RATE (&spec->info))) { GST_ERROR_OBJECT (self, "Failed to set sample rate"); return FALSE; } spec->segsize = self->buffer_size * (GST_AUDIO_INFO_WIDTH (&spec->info) >> 3) * GST_AUDIO_INFO_CHANNELS (&spec->info); spec->segtotal = 2; buf->size = spec->segtotal * spec->segsize; buf->memory = (guint8 *) g_malloc (buf->size); gst_audio_format_info_fill_silence (buf->spec.info.finfo, buf->memory, buf->size); return TRUE; } static gboolean gst_asio_ring_buffer_release (GstAudioRingBuffer * buf) { GST_DEBUG_OBJECT (buf, "Release"); g_clear_pointer (&buf->memory, g_free); return TRUE; } static gboolean gst_asio_ring_buffer_start (GstAudioRingBuffer * buf) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf); GstAsioObjectCallbacks callbacks; GST_DEBUG_OBJECT (buf, "Start"); callbacks.buffer_switch = gst_asio_buffer_switch_cb; callbacks.user_data = self; self->is_first = TRUE; self->expected_sample_position = 0; if (!gst_asio_object_install_callback (self->asio_object, self->type, &callbacks, &self->callback_id)) { GST_ERROR_OBJECT (self, "Failed to install callback"); return FALSE; } self->callback_installed = TRUE; if (!gst_asio_object_start (self->asio_object)) { GST_ERROR_OBJECT (self, "Failed to start"); gst_asio_ring_buffer_stop (buf); return FALSE; } self->running = TRUE; return TRUE; } static gboolean gst_asio_ring_buffer_stop (GstAudioRingBuffer * buf) { GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf); GST_DEBUG_OBJECT (buf, "Stop"); self->running = FALSE; if (!self->asio_object) return TRUE; if (self->callback_installed) gst_asio_object_uninstall_callback (self->asio_object, self->callback_id); self->callback_installed = FALSE; self->callback_id = 0; self->is_first = TRUE; self->expected_sample_position = 0; return TRUE; } static guint gst_asio_ring_buffer_delay (GstAudioRingBuffer * buf) { /* FIXME: impl. */ return 0; } GstAsioRingBuffer * gst_asio_ring_buffer_new (GstAsioObject * object, GstAsioDeviceClassType type, const gchar * name) { GstAsioRingBuffer *self; g_return_val_if_fail (GST_IS_ASIO_OBJECT (object), nullptr); self = (GstAsioRingBuffer *) g_object_new (GST_TYPE_ASIO_RING_BUFFER, "name", name, nullptr); g_assert (self); self->type = type; self->asio_object = (GstAsioObject *) gst_object_ref (object); return self; } gboolean gst_asio_ring_buffer_configure (GstAsioRingBuffer * buf, guint * channel_indices, guint num_channles, guint preferred_buffer_size) { g_return_val_if_fail (GST_IS_ASIO_RING_BUFFER (buf), FALSE); g_return_val_if_fail (buf->asio_object != nullptr, FALSE); g_return_val_if_fail (num_channles > 0, FALSE); GST_DEBUG_OBJECT (buf, "Configure"); buf->buffer_size = preferred_buffer_size; if (!gst_asio_object_create_buffers (buf->asio_object, buf->type, channel_indices, num_channles, &buf->buffer_size)) { GST_ERROR_OBJECT (buf, "Failed to configure"); g_clear_pointer (&buf->channel_indices, g_free); buf->num_channels = 0; return FALSE; } GST_DEBUG_OBJECT (buf, "configured buffer size: %d", buf->buffer_size); g_free (buf->channel_indices); buf->channel_indices = g_new0 (guint, num_channles); for (guint i = 0; i < num_channles; i++) buf->channel_indices[i] = channel_indices[i]; buf->num_channels = num_channles; g_clear_pointer (&buf->infos, g_free); buf->infos = g_new0 (ASIOBufferInfo *, num_channles); return TRUE; } GstCaps * gst_asio_ring_buffer_get_caps (GstAsioRingBuffer * buf) { g_return_val_if_fail (GST_IS_ASIO_RING_BUFFER (buf), nullptr); g_assert (buf->asio_object != nullptr); return gst_asio_object_get_caps (buf->asio_object, buf->type, buf->num_channels, buf->num_channels); }