/* * GStreamer AVTP Plugin * Copyright (C) 2019 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ /** * SECTION:element-avtpaafpay * @see_also: avtpaafdepay * * Payload raw audio into AVTPDUs according to IEEE 1722-2016. For detailed * information see https://standards.ieee.org/standard/1722-2016.html. * * <refsect2> * <title>Example pipeline</title> * |[ * gst-launch-1.0 audiotestsrc ! audioconvert ! avtpaafpay ! avtpsink * ]| This example pipeline will payload raw audio. Refer to the avtpaafdepay * example to depayload and play the AVTP stream. * </refsect2> */ #include <avtp.h> #include <avtp_aaf.h> #include <gst/audio/audio-format.h> #include "gstavtpaafpay.h" GST_DEBUG_CATEGORY_STATIC (avtpaafpay_debug); #define GST_CAT_DEFAULT (avtpaafpay_debug) #define DEFAULT_TSTAMP_MODE GST_AVTP_AAF_TSTAMP_MODE_NORMAL enum { PROP_0, PROP_TSTAMP_MODE, }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw, " "format = (string) { S16BE, S24BE, S32BE, F32BE }, " "rate = (int) { 8000, 16000, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }, " "channels = " GST_AUDIO_CHANNELS_RANGE ", " "layout = (string) interleaved") ); #define GST_TYPE_AVTP_AAF_TSTAMP_MODE (gst_avtp_aaf_tstamp_mode_get_type()) static GType gst_avtp_aaf_tstamp_mode_get_type (void) { static GType tstamp_mode_type = 0; static const GEnumValue tstamp_mode_types[] = { {GST_AVTP_AAF_TSTAMP_MODE_NORMAL, "Normal timestamping mode", "normal"}, {GST_AVTP_AAF_TSTAMP_MODE_SPARSE, "Sparse timestamping mode", "sparse"}, {0, NULL, NULL}, }; tstamp_mode_type = g_enum_register_static ("GstAvtpAafTstampMode", tstamp_mode_types); return tstamp_mode_type; } #define gst_avtp_aaf_pay_parent_class parent_class G_DEFINE_TYPE (GstAvtpAafPay, gst_avtp_aaf_pay, GST_TYPE_AVTP_BASE_PAYLOAD); static void gst_avtp_aaf_pay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_avtp_aaf_pay_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstStateChangeReturn gst_avtp_aaf_pay_change_state (GstElement * element, GstStateChange transition); static GstFlowReturn gst_avtp_aaf_pay_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer); static gboolean gst_avtp_aaf_pay_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static void gst_avtp_aaf_pay_class_init (GstAvtpAafPayClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstAvtpBasePayloadClass *avtpbasepayload_class = GST_AVTP_BASE_PAYLOAD_CLASS (klass); object_class->set_property = gst_avtp_aaf_pay_set_property; object_class->get_property = gst_avtp_aaf_pay_get_property; g_object_class_install_property (object_class, PROP_TSTAMP_MODE, g_param_spec_enum ("tstamp-mode", "Timestamping Mode", "AAF timestamping mode", GST_TYPE_AVTP_AAF_TSTAMP_MODE, DEFAULT_TSTAMP_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PAUSED)); element_class->change_state = GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_change_state); gst_element_class_add_static_pad_template (element_class, &sink_template); gst_element_class_set_static_metadata (element_class, "AVTP Audio Format (AAF) payloader", "Codec/Payloader/Network/AVTP", "Payload-encode Raw audio into AAF AVTPDU (IEEE 1722)", "Andre Guedes <andre.guedes@intel.com>"); avtpbasepayload_class->chain = GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_chain); avtpbasepayload_class->sink_event = GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_sink_event); GST_DEBUG_CATEGORY_INIT (avtpaafpay_debug, "avtpaafpay", 0, "AAF AVTP Payloader"); } static void gst_avtp_aaf_pay_init (GstAvtpAafPay * avtpaafpay) { avtpaafpay->tstamp_mode = DEFAULT_TSTAMP_MODE; avtpaafpay->header = NULL; avtpaafpay->channels = 0; avtpaafpay->depth = 0; avtpaafpay->rate = 0; avtpaafpay->format = 0; } static void gst_avtp_aaf_pay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (object); GST_DEBUG_OBJECT (avtpaafpay, "prop_id %u", prop_id); switch (prop_id) { case PROP_TSTAMP_MODE: avtpaafpay->tstamp_mode = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_avtp_aaf_pay_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (object); GST_DEBUG_OBJECT (avtpaafpay, "prop_id %u", prop_id); switch (prop_id) { case PROP_TSTAMP_MODE: g_value_set_enum (value, avtpaafpay->tstamp_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstStateChangeReturn gst_avtp_aaf_pay_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (element); GST_DEBUG_OBJECT (avtpaafpay, "transition %d", transition); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY:{ GstMemory *mem; mem = gst_allocator_alloc (NULL, sizeof (struct avtp_stream_pdu), NULL); if (!mem) { GST_ERROR_OBJECT (avtpaafpay, "Failed to allocate GstMemory"); return GST_STATE_CHANGE_FAILURE; } avtpaafpay->header = mem; break; } case GST_STATE_CHANGE_READY_TO_PAUSED:{ int res; GstMapInfo info; struct avtp_stream_pdu *pdu; GstMemory *mem = avtpaafpay->header; GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (element); if (!gst_memory_map (mem, &info, GST_MAP_WRITE)) { GST_ERROR_OBJECT (avtpaafpay, "Failed to map GstMemory"); return GST_STATE_CHANGE_FAILURE; } pdu = (struct avtp_stream_pdu *) info.data; res = avtp_aaf_pdu_init (pdu); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_MR, 0); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TV, 1); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TU, 0); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_STREAM_ID, avtpbasepayload->streamid); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_SP, avtpaafpay->tstamp_mode); g_assert (res == 0); gst_memory_unmap (mem, &info); break; } default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) { GST_ERROR_OBJECT (avtpaafpay, "Parent failed to handle state transition"); return ret; } switch (transition) { case GST_STATE_CHANGE_READY_TO_NULL: gst_memory_unref (avtpaafpay->header); break; default: break; } return ret; } static GstFlowReturn gst_avtp_aaf_pay_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { int res; GstMemory *mem; GstMapInfo info; gsize data_len; GstClockTime ptime; struct avtp_stream_pdu *pdu; GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (parent); GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (parent); ptime = gst_avtp_base_payload_calc_ptime (avtpbasepayload, buffer); data_len = gst_buffer_get_size (buffer); mem = gst_memory_copy (avtpaafpay->header, 0, -1); if (!gst_memory_map (mem, &info, GST_MAP_WRITE)) { GST_ELEMENT_ERROR (avtpaafpay, RESOURCE, WRITE, ("Failed to map memory"), (NULL)); gst_buffer_unref (buffer); return GST_FLOW_ERROR; } pdu = (struct avtp_stream_pdu *) info.data; res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TIMESTAMP, ptime); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_NSR, avtpaafpay->rate); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_FORMAT, avtpaafpay->format); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_BIT_DEPTH, avtpaafpay->depth); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, data_len); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, avtpaafpay->channels); g_assert (res == 0); res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_SEQ_NUM, avtpbasepayload->seqnum++); g_assert (res == 0); gst_memory_unmap (mem, &info); gst_buffer_prepend_memory (buffer, mem); return gst_pad_push (avtpbasepayload->srcpad, buffer); } static int gst_to_avtp_rate (gint rate) { switch (rate) { case 8000: return AVTP_AAF_PCM_NSR_8KHZ; case 16000: return AVTP_AAF_PCM_NSR_16KHZ; case 24000: return AVTP_AAF_PCM_NSR_24KHZ; case 32000: return AVTP_AAF_PCM_NSR_32KHZ; case 44100: return AVTP_AAF_PCM_NSR_44_1KHZ; case 48000: return AVTP_AAF_PCM_NSR_48KHZ; case 88200: return AVTP_AAF_PCM_NSR_88_2KHZ; case 96000: return AVTP_AAF_PCM_NSR_96KHZ; case 176400: return AVTP_AAF_PCM_NSR_176_4KHZ; case 192000: return AVTP_AAF_PCM_NSR_192KHZ; default: return AVTP_AAF_PCM_NSR_USER; } } static int gst_to_avtp_format (GstAudioFormat format) { switch (format) { case GST_AUDIO_FORMAT_S16BE: return AVTP_AAF_FORMAT_INT_16BIT; case GST_AUDIO_FORMAT_S24BE: return AVTP_AAF_FORMAT_INT_24BIT; case GST_AUDIO_FORMAT_S32BE: return AVTP_AAF_FORMAT_INT_32BIT; case GST_AUDIO_FORMAT_F32BE: return AVTP_AAF_FORMAT_FLOAT_32BIT; default: return AVTP_AAF_FORMAT_USER; } } static gboolean gst_avtp_aaf_pay_new_caps (GstAvtpAafPay * avtpaafpay, GstCaps * caps) { GstAudioInfo info; gst_audio_info_init (&info); if (!gst_audio_info_from_caps (&info, caps)) { GST_ERROR_OBJECT (avtpaafpay, "Failed to get info from caps"); return FALSE; } avtpaafpay->channels = info.channels; avtpaafpay->depth = info.finfo->depth; avtpaafpay->rate = gst_to_avtp_rate (info.rate); avtpaafpay->format = gst_to_avtp_format (info.finfo->format); GST_DEBUG_OBJECT (avtpaafpay, "channels %d, depth %d, rate %d, format %s", info.channels, info.finfo->depth, info.rate, gst_audio_format_to_string (info.finfo->format)); return TRUE; } static gboolean gst_avtp_aaf_pay_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstCaps *caps; GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (parent); gboolean ret; GST_DEBUG_OBJECT (avtpaafpay, "event %s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_CAPS: gst_event_parse_caps (event, &caps); ret = gst_avtp_aaf_pay_new_caps (avtpaafpay, caps); gst_event_unref (event); return ret; default: return GST_AVTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (pad, parent, event); } } gboolean gst_avtp_aaf_pay_plugin_init (GstPlugin * plugin) { return gst_element_register (plugin, "avtpaafpay", GST_RANK_NONE, GST_TYPE_AVTP_AAF_PAY); }