/* Farsight * Copyright (C) 2006 Marcel Moreaux * (C) 2008 Wim Taymans * * 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 St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * RTP DV depayloader. * * Important note for NTSC-users: * * Because the author uses PAL video, and he does not have proper DV * documentation (the DV format specification is not freely available), * this code may very well contain PAL-specific assumptions. */ #include #include #include #include "gstrtpdvdepay.h" #include "gstrtpelements.h" #include "gstrtputils.h" GST_DEBUG_CATEGORY (rtpdvdepay_debug); #define GST_CAT_DEFAULT (rtpdvdepay_debug) /* Filter signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; enum { PROP_0, }; static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-dv") ); static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-rtp, " "media = (string) { \"video\", \"audio\" }," "encoding-name = (string) \"DV\", " "clock-rate = (int) 90000," "encode = (string) { \"SD-VCR/525-60\", \"SD-VCR/625-50\", \"HD-VCR/1125-60\"," "\"HD-VCR/1250-50\", \"SDL-VCR/525-60\", \"SDL-VCR/625-50\"," "\"306M/525-60\", \"306M/625-50\", \"314M-25/525-60\"," "\"314M-25/625-50\", \"314M-50/525-60\", \"314M-50/625-50\" }" /* optional parameters can't go in the template * "audio = (string) { \"bundled\", \"none\" }" */ ) ); static GstStateChangeReturn gst_rtp_dv_depay_change_state (GstElement * element, GstStateChange transition); static GstBuffer *gst_rtp_dv_depay_process (GstRTPBaseDepayload * base, GstRTPBuffer * rtp); static gboolean gst_rtp_dv_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps); #define gst_rtp_dv_depay_parent_class parent_class G_DEFINE_TYPE (GstRTPDVDepay, gst_rtp_dv_depay, GST_TYPE_RTP_BASE_DEPAYLOAD); GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpdvdepay, "rtpdvdepay", GST_RANK_SECONDARY, GST_TYPE_RTP_DV_DEPAY, rtp_element_init (plugin)); static void gst_rtp_dv_depay_class_init (GstRTPDVDepayClass * klass) { GstElementClass *gstelement_class = (GstElementClass *) klass; GstRTPBaseDepayloadClass *gstrtpbasedepayload_class = (GstRTPBaseDepayloadClass *) klass; GST_DEBUG_CATEGORY_INIT (rtpdvdepay_debug, "rtpdvdepay", 0, "DV RTP Depayloader"); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_dv_depay_change_state); gst_element_class_add_static_pad_template (gstelement_class, &src_factory); gst_element_class_add_static_pad_template (gstelement_class, &sink_factory); gst_element_class_set_static_metadata (gstelement_class, "RTP DV Depayloader", "Codec/Depayloader/Network/RTP", "Depayloads DV from RTP packets (RFC 3189)", "Marcel Moreaux , Wim Taymans "); gstrtpbasedepayload_class->process_rtp_packet = GST_DEBUG_FUNCPTR (gst_rtp_dv_depay_process); gstrtpbasedepayload_class->set_caps = GST_DEBUG_FUNCPTR (gst_rtp_dv_depay_setcaps); } /* initialize the new element * instantiate pads and add them to element * set functions * initialize structure */ static void gst_rtp_dv_depay_init (GstRTPDVDepay * filter) { } static gboolean parse_encode (GstRTPDVDepay * rtpdvdepay, const gchar * encode) { rtpdvdepay->width = 720; if (!strcmp (encode, "314M-25/525-60")) { rtpdvdepay->frame_size = 240000; rtpdvdepay->height = 480; rtpdvdepay->rate_num = 30000; rtpdvdepay->rate_denom = 1001; } else if (!strcmp (encode, "SD-VCR/525-60")) { rtpdvdepay->frame_size = 120000; rtpdvdepay->height = 480; rtpdvdepay->rate_num = 30000; rtpdvdepay->rate_denom = 1001; } else if (!strcmp (encode, "314M-50/625-50")) { rtpdvdepay->frame_size = 288000; rtpdvdepay->height = 576; rtpdvdepay->rate_num = 25; rtpdvdepay->rate_denom = 1; } else if (!strcmp (encode, "SD-VCR/625-50")) { rtpdvdepay->frame_size = 144000; rtpdvdepay->height = 576; rtpdvdepay->rate_num = 25; rtpdvdepay->rate_denom = 1; } else if (!strcmp (encode, "314M-25/625-50")) { rtpdvdepay->frame_size = 144000; rtpdvdepay->height = 576; rtpdvdepay->rate_num = 25; rtpdvdepay->rate_denom = 1; } else rtpdvdepay->frame_size = -1; return rtpdvdepay->frame_size != -1; } static gboolean gst_rtp_dv_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps) { GstStructure *structure; GstRTPDVDepay *rtpdvdepay; GstCaps *srccaps; gint clock_rate; gboolean systemstream, ret; const gchar *encode, *media; rtpdvdepay = GST_RTP_DV_DEPAY (depayload); structure = gst_caps_get_structure (caps, 0); if (!gst_structure_get_int (structure, "clock-rate", &clock_rate)) clock_rate = 90000; /* default */ depayload->clock_rate = clock_rate; /* we really need the encode property to figure out the frame size, it's also * required by the spec */ if (!(encode = gst_structure_get_string (structure, "encode"))) goto no_encode; /* figure out the size of one frame */ if (!parse_encode (rtpdvdepay, encode)) goto unknown_encode; /* check the media, this tells us that the stream has video or not */ if (!(media = gst_structure_get_string (structure, "media"))) goto no_media; systemstream = FALSE; if (!strcmp (media, "audio")) { /* we need a demuxer for audio only */ systemstream = TRUE; } else if (!strcmp (media, "video")) { const gchar *audio; /* check the optional audio field, if it's present and set to bundled, we * are dealing with a system stream. */ if ((audio = gst_structure_get_string (structure, "audio"))) { if (!strcmp (audio, "bundled")) systemstream = TRUE; } } /* allocate accumulator */ rtpdvdepay->acc = gst_buffer_new_and_alloc (rtpdvdepay->frame_size); /* Initialize the new accumulator frame. * If the previous frame exists, copy that into the accumulator frame. * This way, missing packets in the stream won't show up badly. */ gst_buffer_memset (rtpdvdepay->acc, 0, 0, rtpdvdepay->frame_size); srccaps = gst_caps_new_simple ("video/x-dv", "systemstream", G_TYPE_BOOLEAN, systemstream, "width", G_TYPE_INT, rtpdvdepay->width, "height", G_TYPE_INT, rtpdvdepay->height, "framerate", GST_TYPE_FRACTION, rtpdvdepay->rate_num, rtpdvdepay->rate_denom, NULL); ret = gst_pad_set_caps (depayload->srcpad, srccaps); gst_caps_unref (srccaps); return ret; /* ERRORS */ no_encode: { GST_ERROR_OBJECT (rtpdvdepay, "required encode property not found in caps"); return FALSE; } unknown_encode: { GST_ERROR_OBJECT (rtpdvdepay, "unknown encode property %s found", encode); return FALSE; } no_media: { GST_ERROR_OBJECT (rtpdvdepay, "required media property not found in caps"); return FALSE; } } /* A DV frame consists of a bunch of 80-byte DIF blocks. * Each DIF block contains a 3-byte header telling where in the DV frame the * DIF block should go. We use this information to calculate its position. */ static guint calculate_difblock_location (guint8 * block) { gint block_type, dif_sequence, dif_block; guint location; block_type = block[0] >> 5; dif_sequence = block[1] >> 4; dif_block = block[2]; location = dif_sequence * 150; switch (block_type) { case 0: /* Header block, no offset */ break; case 1: /* Subcode block */ location += (1 + dif_block); break; case 2: /* VAUX block */ location += (3 + dif_block); break; case 3: /* Audio block */ location += (6 + dif_block * 16); break; case 4: /* Video block */ location += (7 + (dif_block / 15) + dif_block); break; default: /* Something bogus */ GST_DEBUG ("UNKNOWN BLOCK"); location = -1; break; } return location; } static gboolean foreach_metadata_drop (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data) { *meta = NULL; return TRUE; } /* Process one RTP packet. Accumulate RTP payload in the proper place in a DV * frame, and return that frame if we detect a new frame, or NULL otherwise. * We assume a DV frame is 144000 bytes. That should accommodate PAL as well as * NTSC. */ static GstBuffer * gst_rtp_dv_depay_process (GstRTPBaseDepayload * base, GstRTPBuffer * rtp) { GstBuffer *out = NULL; guint8 *payload; guint32 rtp_ts; guint payload_len, location; GstRTPDVDepay *dvdepay = GST_RTP_DV_DEPAY (base); gboolean marker; GstMapInfo map; marker = gst_rtp_buffer_get_marker (rtp); /* Check if the received packet contains (the start of) a new frame, we do * this by checking the RTP timestamp. */ rtp_ts = gst_rtp_buffer_get_timestamp (rtp); /* we cannot copy the packet yet if the marker is set, we will do that below * after taking out the data */ if (dvdepay->prev_ts != -1 && rtp_ts != dvdepay->prev_ts && !marker) { /* the timestamp changed */ GST_DEBUG_OBJECT (dvdepay, "new frame with ts %u, old ts %u", rtp_ts, dvdepay->prev_ts); /* return copy of accumulator. */ out = gst_buffer_copy (dvdepay->acc); gst_buffer_foreach_meta (dvdepay->acc, foreach_metadata_drop, NULL); } /* Extract the payload */ payload_len = gst_rtp_buffer_get_payload_len (rtp); payload = gst_rtp_buffer_get_payload (rtp); /* copy all DIF chunks in their place. */ gst_buffer_map (dvdepay->acc, &map, GST_MAP_READWRITE); while (payload_len >= 80) { guint offset; /* Calculate where in the frame the payload should go */ location = calculate_difblock_location (payload); if (location < 6) { /* part of a header, set the flag to mark that we have the header. */ dvdepay->header_mask |= (1 << location); GST_LOG_OBJECT (dvdepay, "got header at location %d, now %02x", location, dvdepay->header_mask); } else { GST_LOG_OBJECT (dvdepay, "got block at location %d", location); } if (location != -1) { /* get the byte offset of the dif block */ offset = location * 80; /* And copy it in, provided the location is sane. */ if (offset <= dvdepay->frame_size - 80) { memcpy (map.data + offset, payload, 80); gst_rtp_copy_meta (GST_ELEMENT_CAST (dvdepay), dvdepay->acc, rtp->buffer, 0); } } payload += 80; payload_len -= 80; } gst_buffer_unmap (dvdepay->acc, &map); if (marker) { GST_DEBUG_OBJECT (dvdepay, "marker bit complete frame %u", rtp_ts); /* only copy the frame when we have a complete header */ if (dvdepay->header_mask == 0x3f) { /* The marker marks the end of a frame that we need to push. The next frame * will change the timestamp but we won't copy the accumulator again because * we set the prev_ts to -1. */ out = gst_buffer_copy (dvdepay->acc); gst_buffer_foreach_meta (dvdepay->acc, foreach_metadata_drop, NULL); } else { GST_WARNING_OBJECT (dvdepay, "waiting for frame headers %02x", dvdepay->header_mask); } dvdepay->prev_ts = -1; } else { /* save last timestamp */ dvdepay->prev_ts = rtp_ts; } return out; } static void gst_rtp_dv_depay_reset (GstRTPDVDepay * depay) { if (depay->acc) gst_buffer_unref (depay->acc); depay->acc = NULL; depay->prev_ts = -1; depay->header_mask = 0; } static GstStateChangeReturn gst_rtp_dv_depay_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstRTPDVDepay *depay = GST_RTP_DV_DEPAY (element); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: gst_rtp_dv_depay_reset (depay); break; default: break; } ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state, (element, transition), GST_STATE_CHANGE_FAILURE); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_rtp_dv_depay_reset (depay); break; default: break; } return ret; }