/* GStreamer * Copyright (C) 2021 Sebastian Dröge * * 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 #include "gstajacommon.h" #include "gstajasrcdemux.h" GST_DEBUG_CATEGORY_STATIC(gst_aja_src_demux_debug); #define GST_CAT_DEFAULT gst_aja_src_demux_debug static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE( "video", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-raw")); static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE( "audio", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("audio/x-raw")); static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE( "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-raw")); static GstFlowReturn gst_aja_src_demux_sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer); static gboolean gst_aja_src_demux_sink_event(GstPad *pad, GstObject *parent, GstEvent *event); static gboolean gst_aja_src_demux_audio_src_query(GstPad *pad, GstObject *parent, GstQuery *query); static gboolean gst_aja_src_demux_video_src_query(GstPad *pad, GstObject *parent, GstQuery *query); #define parent_class gst_aja_src_demux_parent_class G_DEFINE_TYPE(GstAjaSrcDemux, gst_aja_src_demux, GST_TYPE_ELEMENT); static void gst_aja_src_demux_class_init(GstAjaSrcDemuxClass *klass) { GstElementClass *element_class = GST_ELEMENT_CLASS(klass); gst_element_class_add_static_pad_template(element_class, &sink_template); gst_element_class_add_static_pad_template(element_class, &video_src_template); gst_element_class_add_static_pad_template(element_class, &audio_src_template); gst_element_class_set_static_metadata( element_class, "AJA audio/video source demuxer", "Audio/Video/Demux", "Demuxes audio/video from video buffers", "Sebastian Dröge "); GST_DEBUG_CATEGORY_INIT(gst_aja_src_demux_debug, "ajasrcdemux", 0, "AJA source demuxer"); } static void gst_aja_src_demux_init(GstAjaSrcDemux *self) { self->sink = gst_pad_new_from_static_template(&sink_template, "sink"); gst_pad_set_chain_function(self->sink, GST_DEBUG_FUNCPTR(gst_aja_src_demux_sink_chain)); gst_pad_set_event_function(self->sink, GST_DEBUG_FUNCPTR(gst_aja_src_demux_sink_event)); gst_element_add_pad(GST_ELEMENT(self), self->sink); self->audio_src = gst_pad_new_from_static_template(&audio_src_template, "audio"); gst_pad_set_query_function( self->audio_src, GST_DEBUG_FUNCPTR(gst_aja_src_demux_audio_src_query)); gst_element_add_pad(GST_ELEMENT(self), self->audio_src); self->video_src = gst_pad_new_from_static_template(&video_src_template, "video"); gst_pad_set_query_function( self->video_src, GST_DEBUG_FUNCPTR(gst_aja_src_demux_video_src_query)); gst_element_add_pad(GST_ELEMENT(self), self->video_src); } static GstFlowReturn gst_aja_src_demux_sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) { GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); GstAjaAudioMeta *meta = gst_buffer_get_aja_audio_meta(buffer); GstFlowReturn audio_flow_ret = GST_FLOW_OK; GstFlowReturn video_flow_ret = GST_FLOW_OK; if (meta) { GstBuffer *audio_buffer; buffer = gst_buffer_make_writable(buffer); meta = gst_buffer_get_aja_audio_meta(buffer); audio_buffer = gst_buffer_ref(meta->buffer); gst_buffer_remove_meta(buffer, GST_META_CAST(meta)); audio_flow_ret = gst_pad_push(self->audio_src, audio_buffer); } else { GstEvent *event = gst_event_new_gap(GST_BUFFER_PTS(buffer), GST_BUFFER_DURATION(buffer)); gst_pad_push_event(self->audio_src, event); } video_flow_ret = gst_pad_push(self->video_src, buffer); // Combine flows the way it makes sense if (video_flow_ret == GST_FLOW_NOT_LINKED && audio_flow_ret == GST_FLOW_NOT_LINKED) return GST_FLOW_NOT_LINKED; if (video_flow_ret == GST_FLOW_EOS && audio_flow_ret == GST_FLOW_EOS) return GST_FLOW_EOS; if (video_flow_ret == GST_FLOW_FLUSHING || video_flow_ret <= GST_FLOW_NOT_NEGOTIATED) return video_flow_ret; if (audio_flow_ret == GST_FLOW_FLUSHING || audio_flow_ret <= GST_FLOW_NOT_NEGOTIATED) return audio_flow_ret; return GST_FLOW_OK; } static gboolean gst_aja_src_demux_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) { GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); switch (GST_EVENT_TYPE(event)) { case GST_EVENT_CAPS: { GstCaps *caps; GstStructure *s; GstAudioInfo audio_info; gint audio_channels = 0; gst_event_parse_caps(event, &caps); s = gst_caps_get_structure(caps, 0); gst_structure_get_int(s, "audio-channels", &audio_channels); GstCaps *audio_caps, *video_caps; gst_audio_info_init(&audio_info); gst_audio_info_set_format(&audio_info, GST_AUDIO_FORMAT_S32LE, 48000, audio_channels ? audio_channels : 1, NULL); audio_caps = gst_audio_info_to_caps(&audio_info); gst_pad_set_caps(self->audio_src, audio_caps); gst_caps_unref(audio_caps); video_caps = gst_caps_ref(caps); gst_event_unref(event); video_caps = gst_caps_make_writable(video_caps); s = gst_caps_get_structure(video_caps, 0); gst_structure_remove_field(s, "audio-channels"); gst_pad_set_caps(self->video_src, video_caps); gst_caps_unref(video_caps); return TRUE; } default: return gst_pad_event_default(pad, parent, event); } } static gboolean gst_aja_src_demux_audio_src_query(GstPad *pad, GstObject *parent, GstQuery *query) { GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); switch (GST_QUERY_TYPE(query)) { case GST_QUERY_CAPS: { GstCaps *filter, *caps; gst_query_parse_caps(query, &filter); if ((caps = gst_pad_get_current_caps(pad))) { GST_DEBUG_OBJECT( pad, "Returning currently negotiated caps %" GST_PTR_FORMAT, caps); } else if ((caps = gst_pad_peer_query_caps(self->sink, NULL))) { guint n; GstAudioInfo audio_info; gint audio_channels = 0; GstCaps *tmp; GST_DEBUG_OBJECT(pad, "Got upstream caps %" GST_PTR_FORMAT, caps); n = gst_caps_get_size(caps); for (guint i = 0; i < n; i++) { GstStructure *s = gst_caps_get_structure(caps, i); gint tmp; if (!gst_structure_get_int(s, "audio-channels", &tmp)) { tmp = 0; } // No audio channels in all caps if (tmp == 0 || (audio_channels != 0 && audio_channels != tmp)) { audio_channels = 0; break; } audio_channels = tmp; } gst_audio_info_init(&audio_info); gst_audio_info_set_format(&audio_info, GST_AUDIO_FORMAT_S32LE, 48000, audio_channels ? audio_channels : 1, NULL); tmp = gst_audio_info_to_caps(&audio_info); gst_caps_unref(caps); caps = tmp; if (!audio_channels) { gst_caps_set_simple(caps, "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); } GST_DEBUG_OBJECT(pad, "Returning caps %" GST_PTR_FORMAT, caps); } else { caps = gst_pad_get_pad_template_caps(pad); GST_DEBUG_OBJECT(pad, "Returning template caps %" GST_PTR_FORMAT, caps); } if (filter) { GstCaps *tmp = gst_caps_intersect_full(filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref(caps); caps = tmp; } gst_query_set_caps_result(query, caps); gst_caps_unref(caps); return TRUE; } default: return gst_pad_query_default(pad, parent, query); } } static gboolean gst_aja_src_demux_video_src_query(GstPad *pad, GstObject *parent, GstQuery *query) { GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); switch (GST_QUERY_TYPE(query)) { case GST_QUERY_CAPS: { GstCaps *filter, *caps; gst_query_parse_caps(query, &filter); if ((caps = gst_pad_get_current_caps(pad))) { GST_DEBUG_OBJECT( pad, "Returning currently negotiated caps %" GST_PTR_FORMAT, caps); } else if ((caps = gst_pad_peer_query_caps(self->sink, NULL))) { guint n; GST_DEBUG_OBJECT(pad, "Returning upstream caps %" GST_PTR_FORMAT, caps); caps = gst_caps_make_writable(caps); n = gst_caps_get_size(caps); for (guint i = 0; i < n; i++) { GstStructure *s = gst_caps_get_structure(caps, i); gst_structure_remove_field(s, "audio-channels"); } } else { caps = gst_pad_get_pad_template_caps(pad); GST_DEBUG_OBJECT(pad, "Returning template caps %" GST_PTR_FORMAT, caps); } if (filter) { GstCaps *tmp = gst_caps_intersect_full(filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref(caps); caps = tmp; } gst_query_set_caps_result(query, caps); gst_caps_unref(caps); return TRUE; } default: return gst_pad_query_default(pad, parent, query); } }