/*
* 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-avtpaafdepay
* @see_also: avtpaafpay
*
* Extract raw audio from AVTPDUs according to IEEE 1722-2016. For detailed
* information see https://standards.ieee.org/standard/1722-2016.html.
*
*
* Example pipeline
* |[
* gst-launch-1.0 avtpsrc ! avtpaafdepay ! autoaudiosink
* ]| This example pipeline will depayload AVTPDUs. Refer to the avtpaafpay
* example to create the AVTP stream.
*
*/
#include
#include
#include
#include "gstavtpaafdepay.h"
GST_DEBUG_CATEGORY_STATIC (avtpaafdepay_debug);
#define GST_CAT_DEFAULT (avtpaafdepay_debug)
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
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")
);
G_DEFINE_TYPE (GstAvtpAafDepay, gst_avtp_aaf_depay,
GST_TYPE_AVTP_BASE_DEPAYLOAD);
static GstFlowReturn gst_avtp_aaf_depay_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer);
static void
gst_avtp_aaf_depay_class_init (GstAvtpAafDepayClass * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstAvtpBaseDepayloadClass *avtpbasedepayload_class =
GST_AVTP_BASE_DEPAYLOAD_CLASS (klass);
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_set_static_metadata (element_class,
"AVTP Audio Format (AAF) depayloader",
"Codec/Depayloader/Network/AVTP",
"Extracts raw audio from AAF AVTPDUs",
"Andre Guedes ");
avtpbasedepayload_class->chain = GST_DEBUG_FUNCPTR (gst_avtp_aaf_depay_chain);
GST_DEBUG_CATEGORY_INIT (avtpaafdepay_debug, "avtpaafdepay", 0,
"AAF AVTP Depayloader");
}
static void
gst_avtp_aaf_depay_init (GstAvtpAafDepay * avtpaafdepay)
{
avtpaafdepay->channels = 0;
avtpaafdepay->depth = 0;
avtpaafdepay->rate = 0;
avtpaafdepay->format = 0;
}
static const gchar *
avtp_to_gst_format (int avtp_format)
{
GstAudioFormat gst_format;
switch (avtp_format) {
case AVTP_AAF_FORMAT_INT_16BIT:
gst_format = GST_AUDIO_FORMAT_S16BE;
break;
case AVTP_AAF_FORMAT_INT_24BIT:
gst_format = GST_AUDIO_FORMAT_S24BE;
break;
case AVTP_AAF_FORMAT_INT_32BIT:
gst_format = GST_AUDIO_FORMAT_S32BE;
break;
case AVTP_AAF_FORMAT_FLOAT_32BIT:
gst_format = GST_AUDIO_FORMAT_F32BE;
break;
default:
gst_format = GST_AUDIO_FORMAT_UNKNOWN;
break;
}
return gst_audio_format_to_string (gst_format);
}
static gint
avtp_to_gst_rate (int rate)
{
switch (rate) {
case AVTP_AAF_PCM_NSR_8KHZ:
return 8000;
case AVTP_AAF_PCM_NSR_16KHZ:
return 16000;
case AVTP_AAF_PCM_NSR_24KHZ:
return 24000;
case AVTP_AAF_PCM_NSR_32KHZ:
return 32000;
case AVTP_AAF_PCM_NSR_44_1KHZ:
return 44100;
case AVTP_AAF_PCM_NSR_48KHZ:
return 48000;
case AVTP_AAF_PCM_NSR_88_2KHZ:
return 88200;
case AVTP_AAF_PCM_NSR_96KHZ:
return 96000;
case AVTP_AAF_PCM_NSR_176_4KHZ:
return 176400;
case AVTP_AAF_PCM_NSR_192KHZ:
return 192000;
default:
return 0;
}
}
static gboolean
gst_avtp_aaf_depay_push_caps_event (GstAvtpAafDepay * avtpaafdepay,
guint64 rate, guint64 depth, guint64 format, guint64 channels)
{
GstCaps *caps;
GstEvent *event;
GstAvtpBaseDepayload *avtpbasedepayload =
GST_AVTP_BASE_DEPAYLOAD (avtpaafdepay);
caps = gst_caps_new_simple ("audio/x-raw",
"format", G_TYPE_STRING, avtp_to_gst_format (format),
"rate", G_TYPE_INT, avtp_to_gst_rate (rate),
"channels", G_TYPE_INT, channels,
"layout", G_TYPE_STRING, "interleaved", NULL);
event = gst_event_new_caps (caps);
if (!gst_pad_push_event (avtpbasedepayload->srcpad, event)) {
GST_ERROR_OBJECT (avtpaafdepay, "Failed to push CAPS event");
gst_caps_unref (caps);
return FALSE;
}
GST_DEBUG_OBJECT (avtpaafdepay, "CAPS event pushed %" GST_PTR_FORMAT, caps);
avtpaafdepay->rate = rate;
avtpaafdepay->depth = depth;
avtpaafdepay->format = format;
avtpaafdepay->channels = channels;
gst_caps_unref (caps);
return TRUE;
}
static gboolean
gst_avtp_aaf_depay_are_audio_features_valid (GstAvtpAafDepay * avtpaafdepay,
guint64 rate, guint64 depth, guint64 format, guint64 channels)
{
if (G_UNLIKELY (rate != avtpaafdepay->rate)) {
GST_INFO_OBJECT (avtpaafdepay, "Rate doesn't match, disarding buffer");
return FALSE;
}
if (G_UNLIKELY (depth != avtpaafdepay->depth)) {
GST_INFO_OBJECT (avtpaafdepay, "Bit depth doesn't match, disarding buffer");
return FALSE;
}
if (G_UNLIKELY (format != avtpaafdepay->format)) {
GST_INFO_OBJECT (avtpaafdepay,
"Sample format doesn't match, disarding buffer");
return FALSE;
}
if (G_UNLIKELY (channels != avtpaafdepay->channels)) {
GST_INFO_OBJECT (avtpaafdepay,
"Number of channels doesn't match, disarding buffer");
return FALSE;
}
return TRUE;
}
static GstFlowReturn
gst_avtp_aaf_depay_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
int res;
GstMapInfo info;
guint32 subtype, version;
GstClockTime ptime;
GstBuffer *subbuffer;
struct avtp_stream_pdu *pdu;
guint64 channels, depth, rate, format, tstamp, seqnum, streamid,
streamid_valid, data_len;
GstAvtpBaseDepayload *avtpbasedepayload = GST_AVTP_BASE_DEPAYLOAD (parent);
GstAvtpAafDepay *avtpaafdepay = GST_AVTP_AAF_DEPAY (avtpbasedepayload);
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
GST_ELEMENT_ERROR (avtpaafdepay, RESOURCE, READ, ("Failed to map memory"),
(NULL));
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
if (info.size < sizeof (struct avtp_stream_pdu)) {
GST_DEBUG_OBJECT (avtpaafdepay, "Malformed AVTPDU, discarding it");
gst_buffer_unmap (buffer, &info);
goto discard;
}
pdu = (struct avtp_stream_pdu *) info.data;
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_NSR, &rate);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_FORMAT, &format);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_SEQ_NUM, &seqnum);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_BIT_DEPTH, &depth);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_TIMESTAMP, &tstamp);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_SV, &streamid_valid);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_STREAM_ID, &streamid);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &channels);
g_assert (res == 0);
res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &data_len);
g_assert (res == 0);
res = avtp_pdu_get ((struct avtp_common_pdu *) pdu, AVTP_FIELD_SUBTYPE,
&subtype);
g_assert (res == 0);
res = avtp_pdu_get ((struct avtp_common_pdu *) pdu, AVTP_FIELD_VERSION,
&version);
g_assert (res == 0);
gst_buffer_unmap (buffer, &info);
if (subtype != AVTP_SUBTYPE_AAF) {
GST_DEBUG_OBJECT (avtpaafdepay, "Subtype doesn't match, discarding buffer");
goto discard;
}
if (version != 0) {
GST_DEBUG_OBJECT (avtpaafdepay, "Version doesn't match, discarding buffer");
goto discard;
}
if (streamid_valid != 1 || streamid != avtpbasedepayload->streamid) {
GST_DEBUG_OBJECT (avtpaafdepay, "Invalid StreamID, discarding buffer");
goto discard;
}
if (gst_buffer_get_size (buffer) < sizeof (*pdu) + data_len) {
GST_DEBUG_OBJECT (avtpaafdepay, "Incomplete AVTPDU, discarding buffer");
goto discard;
}
if (G_UNLIKELY (!gst_pad_has_current_caps (avtpbasedepayload->srcpad))) {
if (!gst_avtp_aaf_depay_push_caps_event (avtpaafdepay, rate, depth, format,
channels)) {
gst_buffer_unref (buffer);
return GST_FLOW_NOT_NEGOTIATED;
}
if (!gst_avtp_base_depayload_push_segment_event (avtpbasedepayload, tstamp)) {
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
avtpbasedepayload->seqnum = seqnum;
}
if (G_UNLIKELY (!gst_avtp_aaf_depay_are_audio_features_valid (avtpaafdepay,
rate, depth, format, channels)))
goto discard;
if (seqnum != avtpbasedepayload->seqnum) {
GST_INFO_OBJECT (avtpaafdepay, "Sequence number mismatch: expected %"
G_GUINT16_FORMAT " received %" G_GUINT64_FORMAT,
avtpbasedepayload->seqnum, seqnum);
avtpbasedepayload->seqnum = seqnum;
}
avtpbasedepayload->seqnum++;
ptime = gst_avtp_base_depayload_tstamp_to_ptime (avtpbasedepayload, tstamp,
avtpbasedepayload->prev_ptime);
subbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
sizeof (struct avtp_stream_pdu), data_len);
GST_BUFFER_PTS (subbuffer) = ptime;
GST_BUFFER_DTS (subbuffer) = ptime;
avtpbasedepayload->prev_ptime = ptime;
gst_buffer_unref (buffer);
return gst_pad_push (avtpbasedepayload->srcpad, subbuffer);
discard:
gst_buffer_unref (buffer);
return GST_FLOW_OK;
}
gboolean
gst_avtp_aaf_depay_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "avtpaafdepay", GST_RANK_NONE,
GST_TYPE_AVTP_AAF_DEPAY);
}