Make it work a little more like RTP. Have the source interact with the clock and set the capture time on each packet. Then the other elements can use that to do adjustments. Since AVTP is always very low latency, it can be assumed that the gPTP clock at the packet reception is very close to the sending time, never more than 2 seconds off, so the timestamps can be compared directly. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9412>
318 lines
9.7 KiB
C
318 lines
9.7 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);
|
|
GST_ELEMENT_REGISTER_DEFINE (avtpaafdepay, "avtpaafdepay", GST_RANK_NONE,
|
|
GST_TYPE_AVTP_AAF_DEPAY);
|
|
|
|
static GstFlowReturn gst_avtp_aaf_depay_process (GstAvtpBaseDepayload *
|
|
basedepay, 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->process = gst_avtp_aaf_depay_process;
|
|
|
|
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,
|
|
gint rate, gint depth, gint format, gint 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_process (GstAvtpBaseDepayload * avtpbasedepayload,
|
|
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;
|
|
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;
|
|
}
|
|
|
|
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->last_dts);
|
|
|
|
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;
|
|
|
|
gst_buffer_unref (buffer);
|
|
|
|
return gst_avtp_base_depayload_push (avtpbasedepayload, subbuffer);
|
|
|
|
discard:
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_OK;
|
|
}
|