1 /* GStreamer RTMP Library
2 * Copyright (C) 2017 Make.TV, Inc. <info@make.tv>
3 * Contact: Jan Alexander Steffens (heftig) <jsteffens@make.tv>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
18 * Boston, MA 02110-1335, USA.
25 #include "rtmpchunkstream.h"
26 #include "rtmputils.h"
28 GST_DEBUG_CATEGORY_STATIC (gst_rtmp_chunk_stream_debug_category);
29 #define GST_CAT_DEFAULT gst_rtmp_chunk_stream_debug_category
34 static volatile gsize done = 0;
35 if (g_once_init_enter (&done)) {
36 GST_DEBUG_CATEGORY_INIT (gst_rtmp_chunk_stream_debug_category,
37 "rtmpchunkstream", 0, "debug category for rtmp chunk streams");
38 g_once_init_leave (&done, 1);
44 CHUNK_BYTE_TWOBYTE = 0,
45 CHUNK_BYTE_THREEBYTE = 1,
46 CHUNK_BYTE_MASK = 0x3f,
47 CHUNK_STREAM_MIN_TWOBYTE = 0x40,
48 CHUNK_STREAM_MIN_THREEBYTE = 0x140,
49 CHUNK_STREAM_MAX_THREEBYTE = 0x1003f,
60 static const gsize chunk_header_sizes[4] = { 11, 7, 3, 0 };
62 struct _GstRtmpChunkStream
66 GstMapInfo map; /* Only used for parsing */
72 struct _GstRtmpChunkStreams
77 static inline gboolean
78 chunk_stream_is_open (GstRtmpChunkStream * cstream)
80 return cstream->map.data != NULL;
84 chunk_stream_take_buffer (GstRtmpChunkStream * cstream, GstBuffer * buffer)
86 GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
88 g_assert (cstream->buffer == NULL);
89 cstream->buffer = buffer;
94 chunk_stream_clear (GstRtmpChunkStream * cstream)
96 if (chunk_stream_is_open (cstream)) {
97 gst_buffer_unmap (cstream->buffer, &cstream->map);
98 cstream->map.data = NULL;
101 gst_buffer_replace (&cstream->buffer, NULL);
102 cstream->meta = NULL;
107 chunk_stream_next_size (GstRtmpChunkStream * cstream, guint32 chunk_size)
109 guint32 size, offset;
111 size = cstream->meta->size;
112 offset = cstream->offset;
114 g_return_val_if_fail (offset <= size, 0);
115 return MIN (size - offset, chunk_size);
118 static inline gboolean
119 needs_ext_ts (GstRtmpMeta * meta)
121 return meta->ts_delta >= 0xffffff;
126 dts_to_abs_ts (GstBuffer * buffer)
128 GstClockTime dts = GST_BUFFER_DTS (buffer);
131 if (GST_CLOCK_TIME_IS_VALID (dts)) {
132 ret = gst_util_uint64_scale_round (dts, 1, GST_MSECOND);
135 GST_TRACE ("Converted DTS %" GST_TIME_FORMAT " into abs TS %"
136 G_GUINT32_FORMAT " ms", GST_TIME_ARGS (dts), ret);
141 dts_diff_to_delta_ts (GstBuffer * old_buffer, GstBuffer * buffer,
144 GstClockTime dts = GST_BUFFER_DTS (buffer),
145 old_dts = GST_BUFFER_DTS (old_buffer);
146 guint32 abs_ts, old_abs_ts, delta_32 = 0;
148 if (!GST_CLOCK_TIME_IS_VALID (dts) || !GST_CLOCK_TIME_IS_VALID (old_dts)) {
149 GST_LOG ("Timestamps not valid; using delta TS 0");
153 if (ABS (GST_CLOCK_DIFF (old_dts, dts)) > GST_MSECOND * G_MAXINT32) {
154 GST_WARNING ("Timestamp delta too large: %" GST_TIME_FORMAT " -> %"
155 GST_TIME_FORMAT, GST_TIME_ARGS (old_dts), GST_TIME_ARGS (dts));
159 abs_ts = gst_util_uint64_scale_round (dts, 1, GST_MSECOND);
160 old_abs_ts = gst_util_uint64_scale_round (old_dts, 1, GST_MSECOND);
162 /* underflow wraps around */
163 delta_32 = abs_ts - old_abs_ts;
165 GST_TRACE ("Converted DTS %" GST_TIME_FORMAT " (%" G_GUINT32_FORMAT
166 " ms) -> %" GST_TIME_FORMAT " (%" G_GUINT32_FORMAT " ms) into delta TS %"
167 G_GUINT32_FORMAT " ms", GST_TIME_ARGS (old_dts), old_abs_ts,
168 GST_TIME_ARGS (dts), abs_ts, delta_32);
176 select_chunk_type (GstRtmpChunkStream * cstream, GstBuffer * buffer)
178 GstBuffer *old_buffer = cstream->buffer;
179 GstRtmpMeta *meta, *old_meta;
181 g_return_val_if_fail (buffer, -1);
183 meta = gst_buffer_get_rtmp_meta (buffer);
185 g_return_val_if_fail (meta, -1);
186 g_return_val_if_fail (gst_rtmp_message_type_is_valid (meta->type), -1);
188 meta->size = gst_buffer_get_size (buffer);
189 meta->cstream = cstream->id;
191 g_return_val_if_fail (meta->size <= GST_RTMP_MAXIMUM_MESSAGE_SIZE, -1);
194 GST_TRACE ("Picking header 0: no previous header");
195 meta->ts_delta = dts_to_abs_ts (buffer);
199 old_meta = gst_buffer_get_rtmp_meta (old_buffer);
200 g_return_val_if_fail (old_meta, -1);
202 if (old_meta->mstream != meta->mstream) {
203 GST_TRACE ("Picking header 0: stream mismatch; "
204 "want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
205 old_meta->mstream, meta->mstream);
206 meta->ts_delta = dts_to_abs_ts (buffer);
210 if (!dts_diff_to_delta_ts (old_buffer, buffer, &meta->ts_delta)) {
211 GST_TRACE ("Picking header 0: timestamp delta overflow");
212 meta->ts_delta = dts_to_abs_ts (buffer);
216 /* now at least type 1 */
218 if (old_meta->type != meta->type) {
219 GST_TRACE ("Picking header 1: type mismatch; want %d got %d",
220 old_meta->type, meta->type);
224 if (old_meta->size != meta->size) {
225 GST_TRACE ("Picking header 1: size mismatch; "
226 "want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
227 old_meta->size, meta->size);
231 /* now at least type 2 */
233 if (old_meta->ts_delta != meta->ts_delta) {
234 GST_TRACE ("Picking header 2: timestamp delta mismatch; "
235 "want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
236 old_meta->ts_delta, meta->ts_delta);
240 /* now at least type 3 */
242 GST_TRACE ("Picking header 3");
247 serialize_next (GstRtmpChunkStream * cstream, guint32 chunk_size,
250 GstRtmpMeta *meta = cstream->meta;
251 guint8 small_stream_id;
252 gsize header_size = chunk_header_sizes[type], offset;
257 GST_TRACE ("Serializing a chunk of type %d, offset %" G_GUINT32_FORMAT,
258 type, cstream->offset);
260 if (cstream->id < CHUNK_STREAM_MIN_TWOBYTE) {
261 small_stream_id = cstream->id;
263 } else if (cstream->id < CHUNK_STREAM_MIN_THREEBYTE) {
264 small_stream_id = CHUNK_BYTE_TWOBYTE;
267 small_stream_id = CHUNK_BYTE_THREEBYTE;
271 ext_ts = needs_ext_ts (meta);
276 GST_TRACE ("Allocating buffer, header size %" G_GSIZE_FORMAT, header_size);
278 ret = gst_buffer_new_allocate (NULL, header_size, NULL);
280 GST_ERROR ("Failed to allocate chunk buffer");
284 if (!gst_buffer_map (ret, &map, GST_MAP_WRITE)) {
285 GST_ERROR ("Failed to map %" GST_PTR_FORMAT, ret);
286 gst_buffer_unref (ret);
290 /* Chunk Basic Header */
291 GST_WRITE_UINT8 (map.data, (type << 6) | small_stream_id);
294 switch (small_stream_id) {
295 case CHUNK_BYTE_TWOBYTE:
296 GST_WRITE_UINT8 (map.data + 1, cstream->id - CHUNK_STREAM_MIN_TWOBYTE);
300 case CHUNK_BYTE_THREEBYTE:
301 GST_WRITE_UINT16_LE (map.data + 1,
302 cstream->id - CHUNK_STREAM_MIN_TWOBYTE);
309 /* SRSLY: "Message stream ID is stored in little-endian format." */
310 GST_WRITE_UINT32_LE (map.data + offset + 7, meta->mstream);
313 GST_WRITE_UINT24_BE (map.data + offset + 3, meta->size);
314 GST_WRITE_UINT8 (map.data + offset + 6, meta->type);
317 GST_WRITE_UINT24_BE (map.data + offset,
318 ext_ts ? 0xffffff : meta->ts_delta);
321 offset += chunk_header_sizes[type];
324 GST_WRITE_UINT32_BE (map.data + offset, meta->ts_delta);
329 g_assert (offset == header_size);
330 GST_MEMDUMP (">>> chunk header", map.data, offset);
332 gst_buffer_unmap (ret, &map);
334 GST_BUFFER_OFFSET (ret) = GST_BUFFER_OFFSET_IS_VALID (cstream->buffer) ?
335 GST_BUFFER_OFFSET (cstream->buffer) + cstream->offset : cstream->bytes;
336 GST_BUFFER_OFFSET_END (ret) = GST_BUFFER_OFFSET (ret);
338 if (meta->size > 0) {
339 guint32 payload_size = chunk_stream_next_size (cstream, chunk_size);
341 GST_TRACE ("Appending %" G_GUINT32_FORMAT " bytes of payload",
344 gst_buffer_copy_into (ret, cstream->buffer, GST_BUFFER_COPY_MEMORY,
345 cstream->offset, payload_size);
347 GST_BUFFER_OFFSET_END (ret) += payload_size;
348 cstream->offset += payload_size;
349 cstream->bytes += payload_size;
351 GST_TRACE ("Chunk has no payload");
354 gst_rtmp_buffer_dump (ret, ">>> chunk");
360 gst_rtmp_chunk_stream_clear (GstRtmpChunkStream * cstream)
362 g_return_if_fail (cstream);
363 GST_LOG ("Clearing chunk stream %" G_GUINT32_FORMAT, cstream->id);
364 chunk_stream_clear (cstream);
368 gst_rtmp_chunk_stream_parse_id (const guint8 * data, gsize size)
373 GST_TRACE ("Not enough bytes to read ID");
377 ret = GST_READ_UINT8 (data) & CHUNK_BYTE_MASK;
380 case CHUNK_BYTE_TWOBYTE:
382 GST_TRACE ("Not enough bytes to read two-byte ID");
386 ret = GST_READ_UINT8 (data + 1) + CHUNK_STREAM_MIN_TWOBYTE;
389 case CHUNK_BYTE_THREEBYTE:
391 GST_TRACE ("Not enough bytes to read three-byte ID");
395 ret = GST_READ_UINT16_LE (data + 1) + CHUNK_STREAM_MIN_TWOBYTE;
399 GST_TRACE ("Parsed chunk stream ID %" G_GUINT32_FORMAT, ret);
404 gst_rtmp_chunk_stream_parse_header (GstRtmpChunkStream * cstream,
405 const guint8 * data, gsize size)
409 const guint8 *message_header;
412 gboolean has_abs_timestamp = FALSE;
414 g_return_val_if_fail (cstream, 0);
415 g_return_val_if_fail (cstream->id == gst_rtmp_chunk_stream_parse_id (data,
418 type = GST_READ_UINT8 (data) >> 6;
419 GST_TRACE ("Parsing chunk stream %" G_GUINT32_FORMAT " header type %d",
422 switch (GST_READ_UINT8 (data) & CHUNK_BYTE_MASK) {
423 case CHUNK_BYTE_TWOBYTE:
426 case CHUNK_BYTE_THREEBYTE:
434 message_header = data + header_size;
435 header_size += chunk_header_sizes[type];
437 if (cstream->buffer) {
438 buffer = cstream->buffer;
439 meta = cstream->meta;
440 g_assert (meta->cstream == cstream->id);
442 buffer = gst_buffer_new ();
443 GST_BUFFER_DTS (buffer) = 0;
444 GST_BUFFER_OFFSET (buffer) = cstream->bytes;
445 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
447 meta = gst_buffer_add_rtmp_meta (buffer);
448 meta->cstream = cstream->id;
450 chunk_stream_take_buffer (cstream, buffer);
451 GST_DEBUG ("Starting parse with new %" GST_PTR_FORMAT, buffer);
454 if (size < header_size) {
455 GST_TRACE ("not enough bytes to read header");
461 has_abs_timestamp = TRUE;
462 /* SRSLY: "Message stream ID is stored in little-endian format." */
463 meta->mstream = GST_READ_UINT32_LE (message_header + 7);
466 meta->type = GST_READ_UINT8 (message_header + 6);
467 meta->size = GST_READ_UINT24_BE (message_header + 3);
470 meta->ts_delta = GST_READ_UINT24_BE (message_header);
473 if (needs_ext_ts (meta)) {
476 if (size < header_size + 4) {
477 GST_TRACE ("not enough bytes to read extended timestamp");
478 return header_size + 4;
481 GST_TRACE ("Reading extended timestamp");
482 timestamp = GST_READ_UINT32_BE (data + header_size);
484 if (type == 3 && meta->ts_delta != timestamp) {
485 GST_WARNING ("Type 3 extended timestamp does not match expected"
486 " timestamp (want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT
487 "); assuming it's not present", meta->ts_delta, timestamp);
489 meta->ts_delta = timestamp;
495 GST_MEMDUMP ("<<< chunk header", data, header_size);
497 if (!chunk_stream_is_open (cstream)) {
498 GstClockTime dts = GST_BUFFER_DTS (buffer);
499 guint32 delta_32, abs_32;
502 if (has_abs_timestamp) {
503 abs_32 = meta->ts_delta;
504 delta_32 = abs_32 - dts / GST_MSECOND;
506 delta_32 = meta->ts_delta;
507 abs_32 = delta_32 + dts / GST_MSECOND;
510 GST_TRACE ("Timestamp delta is %" G_GUINT32_FORMAT " (absolute %"
511 G_GUINT32_FORMAT ")", delta_32, abs_32);
513 /* emulate signed overflow */
515 if (delta_64 > G_MAXINT32) {
516 delta_64 -= G_MAXUINT32;
520 delta_64 *= GST_MSECOND;
522 if (G_LIKELY (delta_64 >= 0)) {
523 /* Normal advancement */
524 } else if (G_LIKELY ((guint64) (-delta_64) <= dts)) {
525 /* In-bounds regression */
526 GST_WARNING ("Timestamp regression: %" GST_STIME_FORMAT,
527 GST_STIME_ARGS (delta_64));
529 /* Out-of-bounds regression */
530 GST_WARNING ("Timestamp regression: %" GST_STIME_FORMAT ", offsetting",
531 GST_STIME_ARGS (delta_64));
532 delta_64 = delta_32 * GST_MSECOND;
535 GST_BUFFER_DTS (buffer) += delta_64;
537 GST_TRACE ("Adjusted buffer DTS (%" GST_TIME_FORMAT ") by %"
538 GST_STIME_FORMAT " to %" GST_TIME_FORMAT, GST_TIME_ARGS (dts),
539 GST_STIME_ARGS (delta_64), GST_TIME_ARGS (GST_BUFFER_DTS (buffer)));
541 GST_TRACE ("Message payload already started; not touching timestamp");
548 gst_rtmp_chunk_stream_parse_payload (GstRtmpChunkStream * cstream,
549 guint32 chunk_size, guint8 ** data)
553 g_return_val_if_fail (cstream, 0);
554 g_return_val_if_fail (cstream->buffer, 0);
556 if (!chunk_stream_is_open (cstream)) {
557 guint32 size = cstream->meta->size;
559 GST_TRACE ("Allocating buffer, payload size %" G_GUINT32_FORMAT, size);
561 mem = gst_allocator_alloc (NULL, size, 0);
563 GST_ERROR ("Failed to allocate buffer for payload size %"
564 G_GUINT32_FORMAT, size);
568 gst_buffer_append_memory (cstream->buffer, mem);
569 gst_buffer_map (cstream->buffer, &cstream->map, GST_MAP_WRITE);
572 g_return_val_if_fail (cstream->map.size == cstream->meta->size, 0);
575 *data = cstream->map.data + cstream->offset;
578 return chunk_stream_next_size (cstream, chunk_size);
582 gst_rtmp_chunk_stream_wrote_payload (GstRtmpChunkStream * cstream,
587 g_return_val_if_fail (cstream, FALSE);
588 g_return_val_if_fail (chunk_stream_is_open (cstream), FALSE);
590 size = chunk_stream_next_size (cstream, chunk_size);
591 cstream->offset += size;
592 cstream->bytes += size;
594 return chunk_stream_next_size (cstream, chunk_size);
598 gst_rtmp_chunk_stream_parse_finish (GstRtmpChunkStream * cstream)
600 GstBuffer *buffer, *empty;
602 g_return_val_if_fail (cstream, NULL);
603 g_return_val_if_fail (cstream->buffer, NULL);
605 buffer = gst_buffer_ref (cstream->buffer);
606 GST_BUFFER_OFFSET_END (buffer) = cstream->bytes;
608 gst_rtmp_buffer_dump (buffer, "<<< message");
610 chunk_stream_clear (cstream);
612 empty = gst_buffer_new ();
614 if (!gst_buffer_copy_into (empty, buffer, GST_BUFFER_COPY_META, 0, 0)) {
615 GST_ERROR ("copy_into failed");
619 GST_BUFFER_DTS (empty) = GST_BUFFER_DTS (buffer);
620 GST_BUFFER_OFFSET (empty) = GST_BUFFER_OFFSET_END (buffer);
622 chunk_stream_take_buffer (cstream, empty);
628 gst_rtmp_chunk_stream_serialize_start (GstRtmpChunkStream * cstream,
629 GstBuffer * buffer, guint32 chunk_size)
633 g_return_val_if_fail (cstream, NULL);
634 g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
636 type = select_chunk_type (cstream, buffer);
637 g_return_val_if_fail (type >= 0, NULL);
639 GST_TRACE ("Starting serialization of message %" GST_PTR_FORMAT
640 " into stream %" G_GUINT32_FORMAT, buffer, cstream->id);
642 gst_rtmp_buffer_dump (buffer, ">>> message");
644 chunk_stream_clear (cstream);
645 chunk_stream_take_buffer (cstream, buffer);
647 return serialize_next (cstream, chunk_size, type);
651 gst_rtmp_chunk_stream_serialize_next (GstRtmpChunkStream * cstream,
654 g_return_val_if_fail (cstream, NULL);
655 g_return_val_if_fail (cstream->buffer, NULL);
657 if (chunk_stream_next_size (cstream, chunk_size) == 0) {
658 GST_TRACE ("Message serialization finished");
662 GST_TRACE ("Continuing serialization of message %" GST_PTR_FORMAT
663 " into stream %" G_GUINT32_FORMAT, cstream->buffer, cstream->id);
665 return serialize_next (cstream, chunk_size, CHUNK_TYPE_3);
668 GstRtmpChunkStreams *
669 gst_rtmp_chunk_streams_new (void)
671 GstRtmpChunkStreams *cstreams;
675 cstreams = g_slice_new (GstRtmpChunkStreams);
676 cstreams->array = g_array_new (FALSE, TRUE, sizeof (GstRtmpChunkStream));
677 g_array_set_clear_func (cstreams->array,
678 (GDestroyNotify) gst_rtmp_chunk_stream_clear);
683 gst_rtmp_chunk_streams_free (gpointer ptr)
685 GstRtmpChunkStreams *cstreams = ptr;
686 g_clear_pointer (&cstreams->array, g_array_unref);
687 g_slice_free (GstRtmpChunkStreams, cstreams);
691 gst_rtmp_chunk_streams_get (GstRtmpChunkStreams * cstreams, guint32 id)
694 GstRtmpChunkStream *entry;
697 g_return_val_if_fail (cstreams, NULL);
698 g_return_val_if_fail (id > CHUNK_BYTE_THREEBYTE, NULL);
699 g_return_val_if_fail (id <= CHUNK_STREAM_MAX_THREEBYTE, NULL);
701 array = cstreams->array;
703 for (i = 0; i < array->len; i++) {
704 entry = &g_array_index (array, GstRtmpChunkStream, i);
705 if (entry->id == id) {
706 GST_TRACE ("Obtaining chunk stream %" G_GUINT32_FORMAT, id);
711 GST_DEBUG ("Allocating chunk stream %" G_GUINT32_FORMAT, id);
713 g_array_set_size (array, i + 1);
714 entry = &g_array_index (array, GstRtmpChunkStream, i);