mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-09 05:38:58 +00:00
326 lines
10 KiB
C
326 lines
10 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipeline</title>
|
|
* |[
|
|
* gst-launch-1.0 avtpsrc ! avtpaafdepay ! autoaudiosink
|
|
* ]| This example pipeline will depayload AVTPDUs. Refer to the avtpaafpay
|
|
* example to create the AVTP stream.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#include <avtp.h>
|
|
#include <avtp_aaf.h>
|
|
#include <gst/audio/audio-format.h>
|
|
|
|
#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 <andre.guedes@intel.com>");
|
|
|
|
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 %u"
|
|
" 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);
|
|
}
|