2 * Copyright (C) <2005> Wim Taymans <wim.taymans@gmail.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
26 #include <gst/rtp/gstrtpbuffer.h>
27 #include <gst/video/video.h>
29 #include "gstrtpmp4vpay.h"
30 #include "gstrtputils.h"
32 GST_DEBUG_CATEGORY_STATIC (rtpmp4vpay_debug);
33 #define GST_CAT_DEFAULT (rtpmp4vpay_debug)
35 static GstStaticPadTemplate gst_rtp_mp4v_pay_sink_template =
36 GST_STATIC_PAD_TEMPLATE ("sink",
39 GST_STATIC_CAPS ("video/mpeg,"
40 "mpegversion=(int) 4, systemstream=(boolean)false;" "video/x-divx")
43 static GstStaticPadTemplate gst_rtp_mp4v_pay_src_template =
44 GST_STATIC_PAD_TEMPLATE ("src",
47 GST_STATIC_CAPS ("application/x-rtp, "
48 "media = (string) \"video\", "
49 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
50 "clock-rate = (int) [1, MAX ], " "encoding-name = (string) \"MP4V-ES\""
53 "profile-level-id = (string) [1,MAX]"
54 "config = (string) [1,MAX]"
59 #define DEFAULT_CONFIG_INTERVAL 0
68 static void gst_rtp_mp4v_pay_finalize (GObject * object);
70 static void gst_rtp_mp4v_pay_set_property (GObject * object, guint prop_id,
71 const GValue * value, GParamSpec * pspec);
72 static void gst_rtp_mp4v_pay_get_property (GObject * object, guint prop_id,
73 GValue * value, GParamSpec * pspec);
75 static gboolean gst_rtp_mp4v_pay_setcaps (GstRTPBasePayload * payload,
77 static GstFlowReturn gst_rtp_mp4v_pay_handle_buffer (GstRTPBasePayload *
78 payload, GstBuffer * buffer);
79 static gboolean gst_rtp_mp4v_pay_sink_event (GstRTPBasePayload * pay,
82 #define gst_rtp_mp4v_pay_parent_class parent_class
83 G_DEFINE_TYPE (GstRtpMP4VPay, gst_rtp_mp4v_pay, GST_TYPE_RTP_BASE_PAYLOAD);
86 gst_rtp_mp4v_pay_class_init (GstRtpMP4VPayClass * klass)
88 GObjectClass *gobject_class;
89 GstElementClass *gstelement_class;
90 GstRTPBasePayloadClass *gstrtpbasepayload_class;
92 gobject_class = (GObjectClass *) klass;
93 gstelement_class = (GstElementClass *) klass;
94 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
96 gobject_class->set_property = gst_rtp_mp4v_pay_set_property;
97 gobject_class->get_property = gst_rtp_mp4v_pay_get_property;
99 gst_element_class_add_static_pad_template (gstelement_class,
100 &gst_rtp_mp4v_pay_src_template);
101 gst_element_class_add_static_pad_template (gstelement_class,
102 &gst_rtp_mp4v_pay_sink_template);
104 gst_element_class_set_static_metadata (gstelement_class,
105 "RTP MPEG4 Video payloader", "Codec/Payloader/Network/RTP",
106 "Payload MPEG-4 video as RTP packets (RFC 3016)",
107 "Wim Taymans <wim.taymans@gmail.com>");
109 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONFIG_INTERVAL,
110 g_param_spec_int ("config-interval", "Config Send Interval",
111 "Send Config Insertion Interval in seconds (configuration headers "
112 "will be multiplexed in the data stream when detected.) "
113 "(0 = disabled, -1 = send with every IDR frame)",
114 -1, 3600, DEFAULT_CONFIG_INTERVAL,
115 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
118 gobject_class->finalize = gst_rtp_mp4v_pay_finalize;
120 gstrtpbasepayload_class->set_caps = gst_rtp_mp4v_pay_setcaps;
121 gstrtpbasepayload_class->handle_buffer = gst_rtp_mp4v_pay_handle_buffer;
122 gstrtpbasepayload_class->sink_event = gst_rtp_mp4v_pay_sink_event;
124 GST_DEBUG_CATEGORY_INIT (rtpmp4vpay_debug, "rtpmp4vpay", 0,
125 "MP4 video RTP Payloader");
129 gst_rtp_mp4v_pay_init (GstRtpMP4VPay * rtpmp4vpay)
131 rtpmp4vpay->adapter = gst_adapter_new ();
132 rtpmp4vpay->rate = 90000;
133 rtpmp4vpay->profile = 1;
134 rtpmp4vpay->need_config = TRUE;
135 rtpmp4vpay->config_interval = DEFAULT_CONFIG_INTERVAL;
136 rtpmp4vpay->last_config = -1;
138 rtpmp4vpay->config = NULL;
142 gst_rtp_mp4v_pay_finalize (GObject * object)
144 GstRtpMP4VPay *rtpmp4vpay;
146 rtpmp4vpay = GST_RTP_MP4V_PAY (object);
148 if (rtpmp4vpay->config) {
149 gst_buffer_unref (rtpmp4vpay->config);
150 rtpmp4vpay->config = NULL;
152 g_object_unref (rtpmp4vpay->adapter);
153 rtpmp4vpay->adapter = NULL;
155 G_OBJECT_CLASS (parent_class)->finalize (object);
159 gst_rtp_mp4v_pay_new_caps (GstRtpMP4VPay * rtpmp4vpay)
161 gchar *profile, *config;
165 profile = g_strdup_printf ("%d", rtpmp4vpay->profile);
166 g_value_init (&v, GST_TYPE_BUFFER);
167 gst_value_set_buffer (&v, rtpmp4vpay->config);
168 config = gst_value_serialize (&v);
170 res = gst_rtp_base_payload_set_outcaps (GST_RTP_BASE_PAYLOAD (rtpmp4vpay),
171 "profile-level-id", G_TYPE_STRING, profile,
172 "config", G_TYPE_STRING, config, NULL);
183 gst_rtp_mp4v_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
185 GstRtpMP4VPay *rtpmp4vpay;
186 GstStructure *structure;
187 const GValue *codec_data;
190 rtpmp4vpay = GST_RTP_MP4V_PAY (payload);
192 gst_rtp_base_payload_set_options (payload, "video", TRUE, "MP4V-ES",
197 structure = gst_caps_get_structure (caps, 0);
198 codec_data = gst_structure_get_value (structure, "codec_data");
200 GST_LOG_OBJECT (rtpmp4vpay, "got codec_data");
201 if (G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) {
204 buffer = gst_value_get_buffer (codec_data);
206 if (gst_buffer_get_size (buffer) < 5)
209 gst_buffer_extract (buffer, 4, &rtpmp4vpay->profile, 1);
210 GST_LOG_OBJECT (rtpmp4vpay, "configuring codec_data, profile %d",
211 rtpmp4vpay->profile);
213 if (rtpmp4vpay->config)
214 gst_buffer_unref (rtpmp4vpay->config);
215 rtpmp4vpay->config = gst_buffer_copy (buffer);
216 res = gst_rtp_mp4v_pay_new_caps (rtpmp4vpay);
225 gst_rtp_mp4v_pay_empty (GstRtpMP4VPay * rtpmp4vpay)
227 gst_adapter_clear (rtpmp4vpay->adapter);
230 #define RTP_HEADER_LEN 12
233 gst_rtp_mp4v_pay_flush (GstRtpMP4VPay * rtpmp4vpay)
237 GstBuffer *outbuf_data = NULL;
239 GstBufferList *list = NULL;
241 /* the data available in the adapter is either smaller
242 * than the MTU or bigger. In the case it is smaller, the complete
243 * adapter contents can be put in one packet. In the case the
244 * adapter has more than one MTU, we need to split the MP4V data
245 * over multiple packets. */
246 avail = gst_adapter_available (rtpmp4vpay->adapter);
248 if (rtpmp4vpay->config == NULL && rtpmp4vpay->need_config) {
249 /* when we don't have a config yet, flush things out */
250 gst_adapter_flush (rtpmp4vpay->adapter, avail);
257 mtu = GST_RTP_BASE_PAYLOAD_MTU (rtpmp4vpay);
259 /* Use buffer lists. Each frame will be put into a list
260 * of buffers and the whole list will be pushed downstream
262 list = gst_buffer_list_new_sized ((avail / (mtu - RTP_HEADER_LEN)) + 1);
268 GstRTPBuffer rtp = { NULL };
270 /* this will be the total length of the packet */
271 packet_len = gst_rtp_buffer_calc_packet_len (avail, 0, 0);
273 /* fill one MTU or all available bytes */
274 towrite = MIN (packet_len, mtu);
276 /* this is the payload length */
277 payload_len = gst_rtp_buffer_calc_payload_len (towrite, 0, 0);
279 /* create buffer without payload. The payload will be put
280 * in next buffer instead. Both buffers will be merged */
281 outbuf = gst_rtp_buffer_new_allocate (0, 0, 0);
283 /* Take buffer with the payload from the adapter */
284 outbuf_data = gst_adapter_take_buffer_fast (rtpmp4vpay->adapter,
287 avail -= payload_len;
289 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
290 gst_rtp_buffer_set_marker (&rtp, avail == 0);
291 gst_rtp_buffer_unmap (&rtp);
292 gst_rtp_copy_video_meta (rtpmp4vpay, outbuf, outbuf_data);
293 outbuf = gst_buffer_append (outbuf, outbuf_data);
295 GST_BUFFER_PTS (outbuf) = rtpmp4vpay->first_timestamp;
298 gst_buffer_list_insert (list, -1, outbuf);
301 /* push the whole buffer list at once */
303 gst_rtp_base_payload_push_list (GST_RTP_BASE_PAYLOAD (rtpmp4vpay), list);
308 #define VOS_STARTCODE 0x000001B0
309 #define VOS_ENDCODE 0x000001B1
310 #define USER_DATA_STARTCODE 0x000001B2
311 #define GOP_STARTCODE 0x000001B3
312 #define VISUAL_OBJECT_STARTCODE 0x000001B5
313 #define VOP_STARTCODE 0x000001B6
316 gst_rtp_mp4v_pay_depay_data (GstRtpMP4VPay * enc, guint8 * data, guint size,
317 gint * strip, gboolean * vopi)
328 code = GST_READ_UINT32_BE (data);
329 GST_DEBUG_OBJECT (enc, "start code 0x%08x", code);
337 gboolean newprofile = FALSE;
340 if (code == VOS_STARTCODE) {
341 /* profile_and_level_indication */
344 GST_DEBUG_OBJECT (enc, "VOS profile 0x%08x", profile);
346 if (profile != enc->profile) {
348 enc->profile = profile;
352 /* up to the next GOP_STARTCODE or VOP_STARTCODE is
353 * the config information */
355 for (i = 5; i < size - 4; i++) {
356 code = (code << 8) | data[i];
357 if (code == GOP_STARTCODE || code == VOP_STARTCODE)
361 /* see if config changed */
364 if (gst_buffer_get_size (enc->config) == i) {
365 equal = gst_buffer_memcmp (enc->config, 0, data, i) == 0;
368 /* if config string changed or new profile, make new caps */
369 if (!equal || newprofile) {
371 gst_buffer_unref (enc->config);
372 enc->config = gst_buffer_new_and_alloc (i);
374 gst_buffer_fill (enc->config, 0, data, i);
376 gst_rtp_mp4v_pay_new_caps (enc);
379 /* we need to flush out the current packet. */
384 GST_DEBUG_OBJECT (enc, "VOP");
385 /* VOP startcode, we don't have to flush the packet */
387 /* vop-coding-type == I-frame */
388 if (size > 4 && (data[4] >> 6 == 0)) {
389 GST_DEBUG_OBJECT (enc, "VOP-I");
394 GST_DEBUG_OBJECT (enc, "GOP");
399 enc->need_config = FALSE;
403 if (code >= 0x20 && code <= 0x2f) {
404 GST_DEBUG_OBJECT (enc, "short header");
407 GST_DEBUG_OBJECT (enc, "other startcode");
408 /* all other startcodes need a flush */
416 /* we expect buffers starting on startcodes.
419 gst_rtp_mp4v_pay_handle_buffer (GstRTPBasePayload * basepayload,
422 GstRtpMP4VPay *rtpmp4vpay;
430 GstClockTime timestamp, duration;
432 gboolean send_config;
433 GstClockTime running_time = GST_CLOCK_TIME_NONE;
438 rtpmp4vpay = GST_RTP_MP4V_PAY (basepayload);
440 gst_buffer_map (buffer, &map, GST_MAP_READ);
442 timestamp = GST_BUFFER_PTS (buffer);
443 duration = GST_BUFFER_DURATION (buffer);
444 avail = gst_adapter_available (rtpmp4vpay->adapter);
449 /* empty buffer, take timestamp */
451 rtpmp4vpay->first_timestamp = timestamp;
452 rtpmp4vpay->duration = 0;
455 /* depay incoming data and see if we need to start a new RTP
458 gst_rtp_mp4v_pay_depay_data (rtpmp4vpay, map.data, size, &strip, &vopi);
459 gst_buffer_unmap (buffer, &map);
462 /* strip off config if requested, do not strip off if the
463 * config_interval is set to -1 */
464 if (!(rtpmp4vpay->config_interval > 0)
465 && !(rtpmp4vpay->config_interval == -1)) {
468 GST_LOG_OBJECT (rtpmp4vpay, "stripping config at %d, size %d", strip,
469 (gint) size - strip);
471 /* strip off header */
472 subbuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, strip,
474 GST_BUFFER_PTS (subbuf) = timestamp;
475 gst_buffer_unref (buffer);
478 size = gst_buffer_get_size (buffer);
481 gst_segment_to_running_time (&basepayload->segment, GST_FORMAT_TIME,
484 GST_LOG_OBJECT (rtpmp4vpay, "found config in stream");
485 rtpmp4vpay->last_config = running_time;
489 /* there is a config request, see if we need to insert it */
490 if (vopi && (rtpmp4vpay->config_interval > 0) && rtpmp4vpay->config) {
492 gst_segment_to_running_time (&basepayload->segment, GST_FORMAT_TIME,
495 if (rtpmp4vpay->last_config != -1) {
498 GST_LOG_OBJECT (rtpmp4vpay,
499 "now %" GST_TIME_FORMAT ", last VOP-I %" GST_TIME_FORMAT,
500 GST_TIME_ARGS (running_time),
501 GST_TIME_ARGS (rtpmp4vpay->last_config));
503 /* calculate diff between last config in milliseconds */
504 if (running_time > rtpmp4vpay->last_config) {
505 diff = running_time - rtpmp4vpay->last_config;
510 GST_DEBUG_OBJECT (rtpmp4vpay,
511 "interval since last config %" GST_TIME_FORMAT, GST_TIME_ARGS (diff));
513 /* bigger than interval, queue config */
514 if (GST_TIME_AS_SECONDS (diff) >= rtpmp4vpay->config_interval) {
515 GST_DEBUG_OBJECT (rtpmp4vpay, "time to send config");
519 /* no known previous config time, send now */
520 GST_DEBUG_OBJECT (rtpmp4vpay, "no previous config time, send now");
525 if (vopi && (rtpmp4vpay->config_interval == -1)) {
526 GST_DEBUG_OBJECT (rtpmp4vpay, "sending config before current IDR frame");
527 /* send config before every IDR frame */
532 /* we need to send config now first */
533 GST_LOG_OBJECT (rtpmp4vpay, "inserting config in stream");
536 buffer = gst_buffer_append (gst_buffer_ref (rtpmp4vpay->config), buffer);
538 GST_BUFFER_PTS (buffer) = timestamp;
539 size = gst_buffer_get_size (buffer);
541 if (running_time != -1) {
542 rtpmp4vpay->last_config = running_time;
546 /* if we need to flush, do so now */
548 ret = gst_rtp_mp4v_pay_flush (rtpmp4vpay);
549 rtpmp4vpay->first_timestamp = timestamp;
550 rtpmp4vpay->duration = 0;
554 /* get packet length of data and see if we exceeded MTU. */
555 packet_len = gst_rtp_buffer_calc_packet_len (avail + size, 0, 0);
557 if (gst_rtp_base_payload_is_filled (basepayload,
558 packet_len, rtpmp4vpay->duration + duration)) {
559 ret = gst_rtp_mp4v_pay_flush (rtpmp4vpay);
560 rtpmp4vpay->first_timestamp = timestamp;
561 rtpmp4vpay->duration = 0;
565 gst_adapter_push (rtpmp4vpay->adapter, buffer);
567 rtpmp4vpay->duration += duration;
573 gst_rtp_mp4v_pay_sink_event (GstRTPBasePayload * pay, GstEvent * event)
575 GstRtpMP4VPay *rtpmp4vpay;
577 rtpmp4vpay = GST_RTP_MP4V_PAY (pay);
579 GST_DEBUG ("Got event: %s", GST_EVENT_TYPE_NAME (event));
581 switch (GST_EVENT_TYPE (event)) {
582 case GST_EVENT_SEGMENT:
584 /* This flush call makes sure that the last buffer is always pushed
585 * to the base payloader */
586 gst_rtp_mp4v_pay_flush (rtpmp4vpay);
588 case GST_EVENT_FLUSH_STOP:
589 gst_rtp_mp4v_pay_empty (rtpmp4vpay);
595 /* let parent handle event too */
596 return GST_RTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (pay, event);
600 gst_rtp_mp4v_pay_set_property (GObject * object, guint prop_id,
601 const GValue * value, GParamSpec * pspec)
603 GstRtpMP4VPay *rtpmp4vpay;
605 rtpmp4vpay = GST_RTP_MP4V_PAY (object);
608 case PROP_CONFIG_INTERVAL:
609 rtpmp4vpay->config_interval = g_value_get_int (value);
617 gst_rtp_mp4v_pay_get_property (GObject * object, guint prop_id,
618 GValue * value, GParamSpec * pspec)
620 GstRtpMP4VPay *rtpmp4vpay;
622 rtpmp4vpay = GST_RTP_MP4V_PAY (object);
625 case PROP_CONFIG_INTERVAL:
626 g_value_set_int (value, rtpmp4vpay->config_interval);
634 gst_rtp_mp4v_pay_plugin_init (GstPlugin * plugin)
636 /* Note: This element is marked at a "+1" rank to make sure that
637 * auto-plugging of payloaders for MPEG4 elementary streams don't
638 * end up using the 'rtpmp4gpay' element (generic mpeg4) which isn't
639 * as well supported as this RFC */
640 return gst_element_register (plugin, "rtpmp4vpay",
641 GST_RANK_SECONDARY + 1, GST_TYPE_RTP_MP4V_PAY);