mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-18 06:16:36 +00:00
736 lines
22 KiB
C++
736 lines
22 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2011 David Schleef <ds@entropywave.com>
|
|
* Copyright (C) 2014 Sebastian Dröge <sebastian@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 Street, Suite 500,
|
|
* Boston, MA 02110-1335, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstdecklinkaudiosink.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_decklink_audio_sink_debug);
|
|
#define GST_CAT_DEFAULT gst_decklink_audio_sink_debug
|
|
|
|
// Ringbuffer implementation
|
|
|
|
#define GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER \
|
|
(gst_decklink_audio_sink_ringbuffer_get_type())
|
|
#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBuffer))
|
|
#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST(obj) \
|
|
((GstDecklinkAudioSinkRingBuffer*) obj)
|
|
#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBufferClass))
|
|
#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST_GET_CLASS(obj) \
|
|
(G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBufferClass))
|
|
#define GST_IS_DECKLINK_AUDIO_SINK_RING_BUFFER(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER))
|
|
#define GST_IS_DECKLINK_AUDIO_SINK_RING_BUFFER_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER))
|
|
|
|
typedef struct _GstDecklinkAudioSinkRingBuffer GstDecklinkAudioSinkRingBuffer;
|
|
typedef struct _GstDecklinkAudioSinkRingBufferClass
|
|
GstDecklinkAudioSinkRingBufferClass;
|
|
|
|
struct _GstDecklinkAudioSinkRingBuffer
|
|
{
|
|
GstAudioRingBuffer object;
|
|
|
|
GstDecklinkOutput *output;
|
|
GstDecklinkAudioSink *sink;
|
|
|
|
GMutex clock_id_lock;
|
|
GstClockID clock_id;
|
|
};
|
|
|
|
struct _GstDecklinkAudioSinkRingBufferClass
|
|
{
|
|
GstAudioRingBufferClass parent_class;
|
|
};
|
|
|
|
GType gst_decklink_audio_sink_ringbuffer_get_type (void);
|
|
|
|
static void gst_decklink_audio_sink_ringbuffer_finalize (GObject * object);
|
|
|
|
static void gst_decklink_audio_sink_ringbuffer_clear_all (GstAudioRingBuffer *
|
|
rb);
|
|
static guint gst_decklink_audio_sink_ringbuffer_delay (GstAudioRingBuffer * rb);
|
|
static gboolean gst_decklink_audio_sink_ringbuffer_start (GstAudioRingBuffer *
|
|
rb);
|
|
static gboolean gst_decklink_audio_sink_ringbuffer_pause (GstAudioRingBuffer *
|
|
rb);
|
|
static gboolean gst_decklink_audio_sink_ringbuffer_stop (GstAudioRingBuffer *
|
|
rb);
|
|
static gboolean gst_decklink_audio_sink_ringbuffer_acquire (GstAudioRingBuffer *
|
|
rb, GstAudioRingBufferSpec * spec);
|
|
static gboolean gst_decklink_audio_sink_ringbuffer_release (GstAudioRingBuffer *
|
|
rb);
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_open_device (GstAudioRingBuffer * rb);
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_close_device (GstAudioRingBuffer * rb);
|
|
|
|
#define ringbuffer_parent_class gst_decklink_audio_sink_ringbuffer_parent_class
|
|
G_DEFINE_TYPE (GstDecklinkAudioSinkRingBuffer,
|
|
gst_decklink_audio_sink_ringbuffer, GST_TYPE_AUDIO_RING_BUFFER);
|
|
|
|
static void
|
|
gst_decklink_audio_sink_ringbuffer_class_init
|
|
(GstDecklinkAudioSinkRingBufferClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstAudioRingBufferClass *gstringbuffer_class =
|
|
GST_AUDIO_RING_BUFFER_CLASS (klass);
|
|
|
|
gobject_class->finalize = gst_decklink_audio_sink_ringbuffer_finalize;
|
|
|
|
gstringbuffer_class->open_device =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_open_device);
|
|
gstringbuffer_class->close_device =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_close_device);
|
|
gstringbuffer_class->acquire =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_acquire);
|
|
gstringbuffer_class->release =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_release);
|
|
gstringbuffer_class->start =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_start);
|
|
gstringbuffer_class->pause =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_pause);
|
|
gstringbuffer_class->resume =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_start);
|
|
gstringbuffer_class->stop =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_stop);
|
|
gstringbuffer_class->delay =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_delay);
|
|
gstringbuffer_class->clear_all =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_clear_all);
|
|
}
|
|
|
|
static void
|
|
gst_decklink_audio_sink_ringbuffer_init (GstDecklinkAudioSinkRingBuffer * self)
|
|
{
|
|
g_mutex_init (&self->clock_id_lock);
|
|
}
|
|
|
|
static void
|
|
gst_decklink_audio_sink_ringbuffer_finalize (GObject * object)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (object);
|
|
|
|
gst_object_unref (self->sink);
|
|
self->sink = NULL;
|
|
g_mutex_clear (&self->clock_id_lock);
|
|
|
|
G_OBJECT_CLASS (ringbuffer_parent_class)->finalize (object);
|
|
}
|
|
|
|
class GStreamerAudioOutputCallback:public IDeckLinkAudioOutputCallback
|
|
{
|
|
public:
|
|
GStreamerAudioOutputCallback (GstDecklinkAudioSinkRingBuffer * ringbuffer)
|
|
:IDeckLinkAudioOutputCallback (), m_refcount (1)
|
|
{
|
|
m_ringbuffer =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (gst_object_ref (ringbuffer));
|
|
g_mutex_init (&m_mutex);
|
|
}
|
|
|
|
virtual HRESULT QueryInterface (REFIID, LPVOID *)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
virtual ULONG AddRef (void)
|
|
{
|
|
ULONG ret;
|
|
|
|
g_mutex_lock (&m_mutex);
|
|
m_refcount++;
|
|
ret = m_refcount;
|
|
g_mutex_unlock (&m_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
virtual ULONG Release (void)
|
|
{
|
|
ULONG ret;
|
|
|
|
g_mutex_lock (&m_mutex);
|
|
m_refcount--;
|
|
ret = m_refcount;
|
|
g_mutex_unlock (&m_mutex);
|
|
|
|
if (ret == 0) {
|
|
delete this;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
virtual ~ GStreamerAudioOutputCallback () {
|
|
gst_object_unref (m_ringbuffer);
|
|
g_mutex_clear (&m_mutex);
|
|
}
|
|
|
|
virtual HRESULT RenderAudioSamples (bool preroll)
|
|
{
|
|
guint8 *ptr;
|
|
gint seg;
|
|
gint len;
|
|
gint bpf;
|
|
guint written, written_sum;
|
|
HRESULT res;
|
|
const GstAudioRingBufferSpec *spec =
|
|
&GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)->spec;
|
|
guint delay, max_delay;
|
|
|
|
GST_LOG_OBJECT (m_ringbuffer->sink, "Writing audio samples (preroll: %d)",
|
|
preroll);
|
|
|
|
delay =
|
|
gst_audio_ring_buffer_delay (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer));
|
|
max_delay = MAX ((spec->segtotal * spec->segsize) / 2, spec->segsize);
|
|
max_delay /= GST_AUDIO_INFO_BPF (&spec->info);
|
|
if (delay > max_delay) {
|
|
GstClock *clock =
|
|
gst_element_get_clock (GST_ELEMENT_CAST (m_ringbuffer->sink));
|
|
GstClockTime wait_time;
|
|
GstClockID clock_id;
|
|
GstClockReturn clock_ret;
|
|
|
|
GST_DEBUG_OBJECT (m_ringbuffer->sink, "Delay %u > max delay %u", delay,
|
|
max_delay);
|
|
|
|
wait_time =
|
|
gst_util_uint64_scale (delay - max_delay, GST_SECOND,
|
|
GST_AUDIO_INFO_RATE (&spec->info));
|
|
GST_DEBUG_OBJECT (m_ringbuffer->sink, "Waiting for %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (wait_time));
|
|
wait_time += gst_clock_get_time (clock);
|
|
|
|
g_mutex_lock (&m_ringbuffer->clock_id_lock);
|
|
if (!GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)->acquired) {
|
|
GST_DEBUG_OBJECT (m_ringbuffer->sink,
|
|
"Ringbuffer not acquired anymore");
|
|
g_mutex_unlock (&m_ringbuffer->clock_id_lock);
|
|
gst_object_unref (clock);
|
|
return S_OK;
|
|
}
|
|
clock_id = gst_clock_new_single_shot_id (clock, wait_time);
|
|
m_ringbuffer->clock_id = clock_id;
|
|
g_mutex_unlock (&m_ringbuffer->clock_id_lock);
|
|
gst_object_unref (clock);
|
|
|
|
clock_ret = gst_clock_id_wait (clock_id, NULL);
|
|
|
|
g_mutex_lock (&m_ringbuffer->clock_id_lock);
|
|
gst_clock_id_unref (clock_id);
|
|
m_ringbuffer->clock_id = NULL;
|
|
g_mutex_unlock (&m_ringbuffer->clock_id_lock);
|
|
|
|
if (clock_ret == GST_CLOCK_UNSCHEDULED) {
|
|
GST_DEBUG_OBJECT (m_ringbuffer->sink, "Flushing");
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
if (!gst_audio_ring_buffer_prepare_read (GST_AUDIO_RING_BUFFER_CAST
|
|
(m_ringbuffer), &seg, &ptr, &len)) {
|
|
GST_WARNING_OBJECT (m_ringbuffer->sink, "No segment available");
|
|
return E_FAIL;
|
|
}
|
|
|
|
bpf =
|
|
GST_AUDIO_INFO_BPF (&GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)->
|
|
spec.info);
|
|
len /= bpf;
|
|
GST_LOG_OBJECT (m_ringbuffer->sink,
|
|
"Write audio samples: %p size %d segment: %d", ptr, len, seg);
|
|
|
|
written_sum = 0;
|
|
do {
|
|
res =
|
|
m_ringbuffer->output->output->ScheduleAudioSamples (ptr, len,
|
|
0, 0, &written);
|
|
len -= written;
|
|
ptr += written * bpf;
|
|
written_sum += written;
|
|
} while (len > 0 && res == S_OK);
|
|
|
|
GST_LOG_OBJECT (m_ringbuffer->sink, "Wrote %u samples: 0x%08x", written_sum,
|
|
res);
|
|
|
|
gst_audio_ring_buffer_clear (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer),
|
|
seg);
|
|
gst_audio_ring_buffer_advance (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer),
|
|
1);
|
|
|
|
return res;
|
|
}
|
|
|
|
private:
|
|
GstDecklinkAudioSinkRingBuffer * m_ringbuffer;
|
|
GMutex m_mutex;
|
|
gint m_refcount;
|
|
};
|
|
|
|
static void
|
|
gst_decklink_audio_sink_ringbuffer_clear_all (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Flushing");
|
|
|
|
if (self->output)
|
|
self->output->output->FlushBufferedAudioSamples ();
|
|
}
|
|
|
|
static guint
|
|
gst_decklink_audio_sink_ringbuffer_delay (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
guint ret = 0;
|
|
HRESULT res = S_OK;
|
|
|
|
if (self->output) {
|
|
if ((res =
|
|
self->output->output->GetBufferedAudioSampleFrameCount (&ret)) !=
|
|
S_OK)
|
|
ret = 0;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Delay: %u (0x%08x)", ret, res);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
in_same_pipeline (GstElement * a, GstElement * b)
|
|
{
|
|
GstObject *root = NULL, *tmp;
|
|
gboolean ret = FALSE;
|
|
|
|
tmp = gst_object_get_parent (GST_OBJECT_CAST (a));
|
|
while (tmp != NULL) {
|
|
if (root)
|
|
gst_object_unref (root);
|
|
root = tmp;
|
|
tmp = gst_object_get_parent (root);
|
|
}
|
|
|
|
ret = root && gst_object_has_ancestor (GST_OBJECT_CAST (b), root);
|
|
|
|
if (root)
|
|
gst_object_unref (root);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_start (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
GstElement *videosink = NULL;
|
|
gboolean ret = TRUE;
|
|
|
|
// Check if there is a video sink for this output too and if it
|
|
// is actually in the same pipeline
|
|
g_mutex_lock (&self->output->lock);
|
|
if (self->output->videosink)
|
|
videosink = GST_ELEMENT_CAST (gst_object_ref (self->output->videosink));
|
|
g_mutex_unlock (&self->output->lock);
|
|
|
|
if (!videosink) {
|
|
GST_ELEMENT_ERROR (self->sink, STREAM, FAILED,
|
|
(NULL), ("Audio sink needs a video sink for its operation"));
|
|
ret = FALSE;
|
|
}
|
|
// FIXME: This causes deadlocks sometimes
|
|
#if 0
|
|
else if (!in_same_pipeline (GST_ELEMENT_CAST (self->sink), videosink)) {
|
|
GST_ELEMENT_ERROR (self->sink, STREAM, FAILED,
|
|
(NULL), ("Audio sink and video sink need to be in the same pipeline"));
|
|
ret = FALSE;
|
|
}
|
|
#endif
|
|
|
|
if (videosink)
|
|
gst_object_unref (videosink);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_pause (GstAudioRingBuffer * rb)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_stop (GstAudioRingBuffer * rb)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_acquire (GstAudioRingBuffer * rb,
|
|
GstAudioRingBufferSpec * spec)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
HRESULT ret;
|
|
BMDAudioSampleType sample_depth;
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Acquire");
|
|
|
|
if (spec->info.finfo->format == GST_AUDIO_FORMAT_S16LE) {
|
|
sample_depth = bmdAudioSampleType16bitInteger;
|
|
} else {
|
|
sample_depth = bmdAudioSampleType32bitInteger;
|
|
}
|
|
|
|
ret = self->output->output->EnableAudioOutput (bmdAudioSampleRate48kHz,
|
|
sample_depth, spec->info.channels, bmdAudioOutputStreamContinuous);
|
|
if (ret != S_OK) {
|
|
GST_WARNING_OBJECT (self->sink, "Failed to enable audio output 0x%08x",
|
|
ret);
|
|
return FALSE;
|
|
}
|
|
|
|
ret =
|
|
self->output->
|
|
output->SetAudioCallback (new GStreamerAudioOutputCallback (self));
|
|
if (ret != S_OK) {
|
|
GST_WARNING_OBJECT (self->sink,
|
|
"Failed to set audio output callback 0x%08x", ret);
|
|
return FALSE;
|
|
}
|
|
|
|
spec->segsize =
|
|
(spec->latency_time * GST_AUDIO_INFO_RATE (&spec->info) /
|
|
G_USEC_PER_SEC) * GST_AUDIO_INFO_BPF (&spec->info);
|
|
spec->segtotal = spec->buffer_time / spec->latency_time;
|
|
// set latency to one more segment as we need some headroom
|
|
spec->seglatency = spec->segtotal + 1;
|
|
|
|
rb->size = spec->segtotal * spec->segsize;
|
|
rb->memory = (guint8 *) g_malloc0 (rb->size);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_release (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Release");
|
|
|
|
if (self->output) {
|
|
g_mutex_lock (&self->clock_id_lock);
|
|
if (self->clock_id)
|
|
gst_clock_id_unschedule (self->clock_id);
|
|
g_mutex_unlock (&self->clock_id_lock);
|
|
|
|
g_mutex_lock (&self->output->lock);
|
|
self->output->audio_enabled = FALSE;
|
|
if (self->output->start_scheduled_playback && self->output->videosink)
|
|
self->output->start_scheduled_playback (self->output->videosink);
|
|
g_mutex_unlock (&self->output->lock);
|
|
|
|
self->output->output->DisableAudioOutput ();
|
|
}
|
|
// free the buffer
|
|
g_free (rb->memory);
|
|
rb->memory = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_open_device (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Open device");
|
|
|
|
self->output =
|
|
gst_decklink_acquire_nth_output (self->sink->device_number,
|
|
GST_ELEMENT_CAST (self), TRUE);
|
|
if (!self->output) {
|
|
GST_ERROR_OBJECT (self, "Failed to acquire output");
|
|
return FALSE;
|
|
}
|
|
|
|
gst_decklink_output_set_audio_clock (self->output,
|
|
GST_AUDIO_BASE_SINK_CAST (self->sink)->provided_clock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_audio_sink_ringbuffer_close_device (GstAudioRingBuffer * rb)
|
|
{
|
|
GstDecklinkAudioSinkRingBuffer *self =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb);
|
|
|
|
GST_DEBUG_OBJECT (self->sink, "Close device");
|
|
|
|
if (self->output) {
|
|
gst_decklink_output_set_audio_clock (self->output, NULL);
|
|
gst_decklink_release_nth_output (self->sink->device_number,
|
|
GST_ELEMENT_CAST (self), TRUE);
|
|
self->output = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DEVICE_NUMBER
|
|
};
|
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS
|
|
("audio/x-raw, format={S16LE,S32LE}, channels={2, 8, 16}, rate=48000, "
|
|
"layout=interleaved")
|
|
);
|
|
|
|
static void gst_decklink_audio_sink_set_property (GObject * object,
|
|
guint property_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_decklink_audio_sink_get_property (GObject * object,
|
|
guint property_id, GValue * value, GParamSpec * pspec);
|
|
static void gst_decklink_audio_sink_finalize (GObject * object);
|
|
|
|
static GstStateChangeReturn gst_decklink_audio_sink_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
static GstCaps *gst_decklink_audio_sink_get_caps (GstBaseSink * bsink,
|
|
GstCaps * filter);
|
|
static GstAudioRingBuffer
|
|
* gst_decklink_audio_sink_create_ringbuffer (GstAudioBaseSink * absink);
|
|
|
|
#define parent_class gst_decklink_audio_sink_parent_class
|
|
G_DEFINE_TYPE (GstDecklinkAudioSink, gst_decklink_audio_sink,
|
|
GST_TYPE_AUDIO_BASE_SINK);
|
|
|
|
static void
|
|
gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
|
|
GstAudioBaseSinkClass *audiobasesink_class =
|
|
GST_AUDIO_BASE_SINK_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_decklink_audio_sink_set_property;
|
|
gobject_class->get_property = gst_decklink_audio_sink_get_property;
|
|
gobject_class->finalize = gst_decklink_audio_sink_finalize;
|
|
|
|
element_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_change_state);
|
|
|
|
basesink_class->get_caps =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_get_caps);
|
|
|
|
audiobasesink_class->create_ringbuffer =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_create_ringbuffer);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE_NUMBER,
|
|
g_param_spec_int ("device-number", "Device number",
|
|
"Output device instance to use", 0, G_MAXINT, 0,
|
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &sink_template);
|
|
|
|
gst_element_class_set_static_metadata (element_class, "Decklink Audio Sink",
|
|
"Audio/Sink", "Decklink Sink", "David Schleef <ds@entropywave.com>, "
|
|
"Sebastian Dröge <sebastian@centricular.com>");
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_decklink_audio_sink_debug, "decklinkaudiosink",
|
|
0, "debug category for decklinkaudiosink element");
|
|
}
|
|
|
|
static void
|
|
gst_decklink_audio_sink_init (GstDecklinkAudioSink * self)
|
|
{
|
|
self->device_number = 0;
|
|
|
|
// 25.000ms latency time seems to be needed at least,
|
|
// everything below can cause drop-outs
|
|
// TODO: This is probably related to the video mode that
|
|
// is selected, but not directly it seems. Choosing the
|
|
// duration of a frame does not work.
|
|
GST_AUDIO_BASE_SINK_CAST (self)->latency_time = 25000;
|
|
}
|
|
|
|
void
|
|
gst_decklink_audio_sink_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DEVICE_NUMBER:
|
|
self->device_number = g_value_get_int (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_decklink_audio_sink_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DEVICE_NUMBER:
|
|
g_value_set_int (value, self->device_number);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_decklink_audio_sink_finalize (GObject * object)
|
|
{
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_decklink_audio_sink_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (element);
|
|
GstDecklinkAudioSinkRingBuffer *buf =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (GST_AUDIO_BASE_SINK_CAST
|
|
(self)->ringbuffer);
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
return ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
g_mutex_lock (&buf->output->lock);
|
|
buf->output->audio_enabled = TRUE;
|
|
if (buf->output->start_scheduled_playback && buf->output->videosink)
|
|
buf->output->start_scheduled_playback (buf->output->videosink);
|
|
g_mutex_unlock (&buf->output->lock);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_decklink_audio_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
|
{
|
|
GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink);
|
|
GstDecklinkAudioSinkRingBuffer *buf =
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (GST_AUDIO_BASE_SINK_CAST
|
|
(self)->ringbuffer);
|
|
GstCaps *caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
|
|
|
if (buf) {
|
|
GST_OBJECT_LOCK (buf);
|
|
if (buf->output && buf->output->attributes) {
|
|
int64_t max_channels = 0;
|
|
HRESULT ret;
|
|
GstStructure *s;
|
|
GValue arr = G_VALUE_INIT;
|
|
GValue v = G_VALUE_INIT;
|
|
|
|
ret =
|
|
buf->output->attributes->GetInt (BMDDeckLinkMaximumAudioChannels,
|
|
&max_channels);
|
|
/* 2 should always be supported */
|
|
if (ret != S_OK) {
|
|
max_channels = 2;
|
|
}
|
|
|
|
caps = gst_caps_make_writable (caps);
|
|
s = gst_caps_get_structure (caps, 0);
|
|
|
|
g_value_init (&arr, GST_TYPE_LIST);
|
|
g_value_init (&v, G_TYPE_INT);
|
|
if (max_channels >= 16) {
|
|
g_value_set_int (&v, 16);
|
|
gst_value_list_append_value (&arr, &v);
|
|
}
|
|
if (max_channels >= 8) {
|
|
g_value_set_int (&v, 8);
|
|
gst_value_list_append_value (&arr, &v);
|
|
}
|
|
g_value_set_int (&v, 2);
|
|
gst_value_list_append_value (&arr, &v);
|
|
|
|
gst_structure_set_value (s, "channels", &arr);
|
|
g_value_unset (&v);
|
|
g_value_unset (&arr);
|
|
}
|
|
GST_OBJECT_UNLOCK (buf);
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *intersection =
|
|
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (caps);
|
|
caps = intersection;
|
|
}
|
|
|
|
return caps;
|
|
}
|
|
|
|
static GstAudioRingBuffer *
|
|
gst_decklink_audio_sink_create_ringbuffer (GstAudioBaseSink * absink)
|
|
{
|
|
GstAudioRingBuffer *ret;
|
|
|
|
GST_DEBUG_OBJECT (absink, "Creating ringbuffer");
|
|
|
|
ret =
|
|
GST_AUDIO_RING_BUFFER_CAST (g_object_new
|
|
(GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER, NULL));
|
|
|
|
GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (ret)->sink =
|
|
(GstDecklinkAudioSink *) gst_object_ref (absink);
|
|
|
|
return ret;
|
|
}
|