2 * Copyright (C) 2016 Jan Schmidt <jan@centricular.com>
3 * Copyright (C) 2016 Tim-Philipp Müller <tim@centricular.com>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
31 #include <gst/tag/tag.h>
34 #include "gsthlsdemux.h"
36 GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux2_debug);
37 #define GST_CAT_DEFAULT gst_hls_demux2_debug
41 #define TS_PACKET_SYNC_BYTE 0x47
43 #define TS_PACKET_TRANSPORT_ERROR_INDICATOR(packet) \
45 #define TS_PACKET_PAYLOAD_UNIT_START(packet) \
48 #define TS_PACKET_PID(packet) \
49 ((guint16) ((packet)[1] & 0x1f) << 8 | (packet)[2])
51 #define TS_PACKET_TRANSPORT_SCRAMBLING_CONTROL(packet) \
53 #define TS_PACKET_HAS_ADAPTATION_FIELD(packet) \
55 #define TS_PACKET_HAS_PAYLOAD(packet) \
57 #define TS_PACKET_CONTINUITY_COUNTER(pacet) \
60 #define TS_PACKET_ADAPTATION_FIELD(packet) \
61 (TS_PACKET_HAS_ADAPTATION_FIELD(packet) ? \
64 /* Adaptation field size. Note: can be 0 */
65 #define TS_PACKET_ADAPTATION_FIELD_SIZE(packet) \
69 #define TS_PACKET_PAYLOAD_OFFSET(packet) \
70 (TS_PACKET_HAS_ADAPTATION_FIELD (packet) ? \
71 4 + TS_PACKET_ADAPTATION_FIELD_SIZE (packet) + 1 : \
74 #define TS_PACKET_PAYLOAD(packet) \
75 (TS_PACKET_HAS_PAYLOAD (packet) ? \
76 (packet) + TS_PACKET_PAYLOAD_OFFSET(packet) : \
81 #define PES_IS_VALID(pes) ((pes)[0] == 0x00 && \
85 #define PES_STREAM_ID(pes) ((pes)[3])
87 #define PES_PACKET_LENGTH(pes) \
88 ((guint16) (((pes)[4] << 8) | (pes)[5]))
90 #define PES_STREAM_TYPE_HAS_HEADER(stream_type) \
93 #define PES_HEADER_DATA_LENGTH(pes) ((pes)[8])
94 #define PES_PAYLOAD_DATA_OFFSET(pes) \
95 (9 + PES_HEADER_DATA_LENGTH (pes))
97 #define PES_HAS_PTS(pes) ((pes)[7] & 0x80)
98 #define PES_HAS_DTS(pes) ((pes)[7] & 0x40)
102 #define TS_SECTION_POINTER(payload) ((payload)[0])
103 #define TS_PACKET_GET_SECTION(payload) ((payload) + TS_SECTION_POINTER(payload))
106 #define PAT_PROGRAM_OFFSET(pat, idx) \
108 #define PAT_PROGRAM_PID_OFFSET(pat, idx) \
109 (PAT_PROGRAM_PID_OFFSET(pat,idx) + 2)
110 #define PAT_GET_PROGRAM_PID(pat, idx) \
111 (pat[ + PAT_PROGRAM_OFFSET(pat, idx) + 2)
113 static inline gboolean
114 read_ts (const guint8 * data, guint64 * target)
116 /* sync:4 == 00xx ! pts:3 ! 1 ! pts:15 ! 1 | pts:15 ! 1 */
117 if ((*data & 0x01) != 0x01)
119 *target = ((guint64) (*data++ & 0x0E)) << 29;
120 *target |= ((guint64) (*data++)) << 22;
121 if ((*data & 0x01) != 0x01)
123 *target |= ((guint64) (*data++ & 0xFE)) << 14;
124 *target |= ((guint64) (*data++)) << 7;
125 if ((*data & 0x01) != 0x01)
127 *target |= ((guint64) (*data++ & 0xFE)) >> 1;
132 #define PES_PTS_OFFSET(pes) (9)
133 #define PES_PTS(pes, dest) (read_ts ((pes) + PES_PTS_OFFSET(pes), dest))
135 #define PES_DTS_OFFSET(pes) (PES_HAS_PTS(pes) ? 9 + 5 : 9)
136 #define PES_DTS(pes, dest) (read_ts ((pes) + PES_DTS_OFFSET(pes), dest))
139 /* Check for sync byte, error_indicator == 0 and packet has payload.
140 * Adaptation control field (data[3] & 0x30) may be zero for TS packets with
141 * null PIDs. Still, these streams are valid TS streams (for null packets,
142 * AFC is supposed to be 0x1, but the spec also says decoders should just
143 * discard any packets with AFC = 0x00) */
144 #define IS_MPEGTS_HEADER(data) (data[0] == 0x47 && \
145 (data[1] & 0x80) == 0x00 && \
146 ((data[3] & 0x30) != 0x00 || \
147 ((data[3] & 0x30) == 0x00 && (data[1] & 0x1f) == 0x1f && (data[2] & 0xff) == 0xff)))
149 #define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27)
150 #define MPEGTIME_TO_GSTTIME(t) (((t) * (guint64)100000) / 9)
153 have_ts_sync (const guint8 * data, guint size, guint packet_size, guint num)
156 if (size < packet_size)
158 if (!IS_MPEGTS_HEADER (data))
166 #define GST_MPEGTS_TYPEFIND_MIN_HEADERS 4
169 find_offset (const guint8 * data, guint size, guint * out_packet_size)
171 guint sync_points = CLAMP (size / 188, GST_MPEGTS_TYPEFIND_MIN_HEADERS, 100);
173 const gint packet_size = 188;
175 /* FIXME: check 192 as well, and maybe also 204, 208 */
176 for (off = 0; off < MIN (size, 1024); ++off) {
177 if (have_ts_sync (data + off, size - off, packet_size, sync_points)) {
178 *out_packet_size = packet_size;
186 handle_pmt (const guint8 * data, guint size, guint packet_size)
188 const guint8 *p = data;
189 guint32 hdr = GST_READ_UINT32_BE (p);
190 guint slen, pcr_pid, pilen;
192 GST_MEMDUMP ("PMT", data, size);
194 if ((hdr & 0x00000020) != 0) /* has_adaptation_field */
195 data += 1 + p[4]; /* adaptation_field_len */
196 data += 1 + data[0]; /* pointer_field */
197 if (data[0] != 0x02) /* table_id */
199 //gst_util_dump_mem (data, 8);
200 /* we assume the entire PMT fits into a single packet and this is it */
201 if (data[6] != 0 || data[6] != data[7])
203 slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
204 if (slen > (gsize) (p + packet_size - (data + 1 + 2)) || slen < 5 + 2 + 4)
207 slen -= 5; /* bytes after section_length field itself */
208 slen -= 4; /* crc at end */
209 pcr_pid = GST_READ_UINT16_BE (data) & 0x1fff;
210 if (pcr_pid != 0x1fff) {
211 GST_DEBUG ("pcr_pid: %04x", pcr_pid);
214 /* Skip global descriptors */
215 pilen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
223 pat_get_pmt_pid (const guint8 * data, guint size, guint packet_size,
226 const guint8 *p = data;
227 guint32 hdr = GST_READ_UINT32_BE (p);
231 if ((hdr & 0x00000020) != 0) /* has_adaptation_field */
232 data += 1 + p[4]; /* adaptation_field_len */
233 data += 1 + data[0]; /* pointer_field */
234 if (data[0] != 0) /* table_id */
236 /* we assume the entire PAT fits into a single packet and this is it */
237 if (data[6] != 0 || data[6] != data[7])
239 slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
240 if (slen > (gsize) (p + packet_size - (data + 1 + 2)) || slen < 5 + 4 + 4)
243 slen -= 5; /* bytes after section_length field itself */
244 slen -= 4; /* crc at end */
246 guint program_num = GST_READ_UINT16_BE (data);
247 guint val = GST_READ_UINT16_BE (data + 2) & 0x1fff;
248 if (program_num != 0) {
249 GST_DEBUG (" program %04x: pmt_pid : %04x", program_num, val);
261 get_first_mpegts_time (const guint8 * data, gsize size, guint packet_size)
263 GstClockTime internal_time = GST_CLOCK_TIME_NONE;
267 for (p = data; size >= packet_size; p += packet_size, size -= packet_size) {
268 if (p[0] != TS_PACKET_SYNC_BYTE) {
269 GST_WARNING ("Lost sync");
273 /* We only care about start packets which have some form of payload (pes or
275 if (TS_PACKET_PAYLOAD_UNIT_START (p) && TS_PACKET_HAS_PAYLOAD (p)) {
277 const guint8 *payload;
280 /* Skip packets which have error indicator set or are scrambled */
281 if (G_UNLIKELY (TS_PACKET_TRANSPORT_ERROR_INDICATOR (p) ||
282 TS_PACKET_TRANSPORT_SCRAMBLING_CONTROL (p)))
285 pid = TS_PACKET_PID (p);
286 payload = TS_PACKET_PAYLOAD (p);
287 afc = TS_PACKET_ADAPTATION_FIELD (p);
289 GST_LOG ("PID 0x%04x", pid);
291 GST_MEMDUMP ("afc", afc, afc[0]);
292 GST_MEMDUMP ("payload", payload, 32);
293 if (pmt_pid != -1 && PES_IS_VALID (payload)) {
295 GstClockTime pts, dts;
297 pts = dts = GST_CLOCK_TIME_NONE;
299 GST_DEBUG ("PID 0x%04x stream_id 0x%02x PES start", pid,
300 PES_STREAM_ID (payload));
301 GST_MEMDUMP ("PES data", payload + PES_PAYLOAD_DATA_OFFSET (payload),
305 if (PES_HAS_PTS (payload) && PES_PTS (payload, &ts)) {
306 pts = MPEGTIME_TO_GSTTIME (ts);
307 GST_LOG ("PID 0x%04x PTS %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT,
308 pid, ts, GST_TIME_ARGS (pts));
310 if (PES_HAS_DTS (payload) && PES_DTS (payload, &ts)) {
311 dts = MPEGTIME_TO_GSTTIME (ts);
312 GST_LOG ("PID 0x%04x DTS %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT,
313 pid, ts, GST_TIME_ARGS (dts));
316 /* Pick the lowest value */
317 if (GST_CLOCK_TIME_IS_VALID (dts)) {
318 if (GST_CLOCK_TIME_IS_VALID (pts)) {
319 /* Only take the PTS if it's lower than the dts and does not differ
320 * by more than a second (which would indicate bogus values) */
321 if (pts < dts && ABS (pts - dts) < GST_SECOND)
329 } else if (GST_CLOCK_TIME_IS_VALID (pts)) {
333 } else if (pid == 0x00) {
335 if (!pat_get_pmt_pid (p, packet_size, packet_size, &pmt_pid)) {
336 GST_WARNING ("Invalid PAT");
339 } else if (pmt_pid != -1 && pid == pmt_pid) {
341 /* FIXME : Grab the list of *actual* elementary stream PID to make sure
342 * we have checked the first PTS of each stream (and not just the first
343 * one we saw, which might not be the smallest */
344 handle_pmt (p, packet_size, packet_size);
350 return internal_time;
354 gst_hlsdemux_handle_content_mpegts (GstHLSDemux * demux,
355 GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
360 GstClockTime internal_time = GST_CLOCK_TIME_NONE;
364 if (!gst_buffer_map (*buffer, &info, GST_MAP_READ))
365 return GST_HLS_PARSER_RESULT_ERROR;
370 offset = find_offset (data, size, &packet_size);
372 gst_buffer_unmap (*buffer, &info);
373 return GST_HLS_PARSER_RESULT_ERROR;
376 GST_LOG ("TS packet start offset: %d", offset);
381 internal_time = get_first_mpegts_time (data, size, packet_size);
383 GST_DEBUG_OBJECT (hls_stream, "Using internal time %" GST_TIME_FORMAT,
384 GST_TIME_ARGS (internal_time));
386 gst_buffer_unmap (*buffer, &info);
388 if (!GST_CLOCK_TIME_IS_VALID (internal_time))
389 return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
391 /* We have the first internal time, figure out if we are in sync or not */
392 return gst_hlsdemux_handle_internal_time (demux, hls_stream, internal_time);
396 gst_hlsdemux_handle_content_isobmff (GstHLSDemux * demux,
397 GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
400 GstByteReader br, sub;
404 GstHLSParserResult ret = GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
405 GstClockTime smallest_ts = GST_CLOCK_TIME_NONE;
407 if (!gst_buffer_map (*buffer, &info, GST_MAP_READ))
408 return GST_HLS_PARSER_RESULT_ERROR;
410 gst_byte_reader_init (&br, info.data, info.size);
412 while (gst_byte_reader_get_remaining (&br) &&
413 gst_isoff_parse_box_header (&br, &box_type, NULL, &header_size,
415 GST_DEBUG ("box %" GST_FOURCC_FORMAT " size:%" G_GUINT64_FORMAT,
416 GST_FOURCC_ARGS (box_type), box_size);
418 GST_MEMDUMP ("box content", br.data + br.byte, MIN (256,
419 box_size - header_size));
422 case GST_ISOFF_FOURCC_MOOV:
425 gst_byte_reader_get_sub_reader (&br, &sub, box_size - header_size);
426 moov = gst_isoff_moov_box_parse (&sub);
429 GST_DEBUG ("Got moov box");
430 if (hls_stream->moov)
431 gst_isoff_moov_box_free (hls_stream->moov);
432 hls_stream->moov = moov;
436 case GST_ISOFF_FOURCC_MOOF:
440 if (hls_stream->moov == NULL) {
441 GST_WARNING ("Received moof with moov in iso-ff stream");
445 gst_byte_reader_get_sub_reader (&br, &sub, box_size - header_size);
447 moof = gst_isoff_moof_box_parse (&sub);
451 GST_DEBUG ("Got moof box");
452 /* Use the track information from stream->moov */
453 for (i = 0; i < hls_stream->moov->trak->len; i++) {
455 &g_array_index (hls_stream->moov->trak, GstTrakBox, i);
456 GST_LOG ("trak #%d %p", i, trak);
457 for (j = 0; j < moof->traf->len; j++) {
458 GstTrafBox *traf = &g_array_index (moof->traf, GstTrafBox, j);
459 if (traf->tfhd.track_id == trak->tkhd.track_id) {
461 guint64 decode_time = traf->tfdt.decode_time;
463 if (decode_time != GST_CLOCK_TIME_NONE)
464 ts = gst_util_uint64_scale (decode_time, GST_SECOND,
465 trak->mdia.mdhd.timescale);
467 GST_LOG ("Found decode_time %" GST_TIME_FORMAT " for trak %d",
468 GST_TIME_ARGS (ts), traf->tfhd.track_id);
469 if (smallest_ts == GST_CLOCK_TIME_NONE || ts < smallest_ts)
474 gst_isoff_moof_box_free (moof);
476 GST_WARNING ("Failed to parse moof");
478 if (smallest_ts != GST_CLOCK_TIME_NONE)
482 case GST_ISOFF_FOURCC_MDAT:
483 GST_DEBUG ("Reached `mdat`, returning");
487 GST_LOG ("Skipping unhandled box %" GST_FOURCC_FORMAT,
488 GST_FOURCC_ARGS (box_type));
489 gst_byte_reader_skip (&br, box_size - header_size);
496 gst_buffer_unmap (*buffer, &info);
498 if (smallest_ts != GST_CLOCK_TIME_NONE) {
499 ret = gst_hlsdemux_handle_internal_time (demux, hls_stream, smallest_ts);
507 gst_hlsdemux_handle_content_id3 (GstHLSDemux * demux,
508 GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
514 GstSample *priv_data = NULL;
517 GstHLSParserResult ret = GST_HLS_PARSER_RESULT_DONE;
518 GstClockTime internal;
520 /* We need at least 10 bytes, starting with "ID3" for the header */
521 size = gst_buffer_get_size (*buffer);
523 return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
525 /* Read the tag size */
526 tag_size = gst_tag_get_id3v2_tag_size (*buffer);
528 /* Check we've collected that much */
530 return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
533 taglist = gst_tag_list_from_id3v2_tag (*buffer);
534 if (taglist == NULL) {
535 return GST_HLS_PARSER_RESULT_ERROR; /* Invalid tag, stop trying */
538 /* Extract the timestamps */
539 if (!gst_tag_list_get_sample (taglist, GST_TAG_PRIVATE_DATA, &priv_data))
542 if (!g_str_equal ("com.apple.streaming.transportStreamTimestamp",
543 gst_structure_get_string (gst_sample_get_info (priv_data), "owner")))
546 /* OK, now as per section 3, the tag contains a 33-bit PCR inside a 64-bit
548 tag_buf = gst_sample_get_buffer (priv_data);
552 if (!gst_buffer_map (tag_buf, &info, GST_MAP_READ))
554 GST_MEMDUMP ("id3 tag", info.data, info.size);
556 pts = GST_READ_UINT64_BE (info.data);
557 internal = MPEGTIME_TO_GSTTIME (pts);
559 GST_LOG ("Got internal PTS from ID3: %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT
560 ")", pts, GST_TIME_ARGS (internal));
562 gst_buffer_unmap (tag_buf, &info);
564 ret = gst_hlsdemux_handle_internal_time (demux, hls_stream, internal);
568 gst_sample_unref (priv_data);
570 gst_tag_list_unref (taglist);
575 /* Grabs the next numerical value from the bytereader, skipping any spaces.
577 * It will stop/return at the next non-digit/non-space position */
579 byte_reader_get_next_uint_string (GstByteReader * br, guint * out)
582 gboolean res = FALSE;
584 while (gst_byte_reader_get_remaining (br)) {
585 guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
587 if (g_ascii_isdigit (d)) {
588 value = value * 10 + (d - '0');
590 } else if (d != ' ' && d != '\t') {
591 /* we're done and not advancing */
594 gst_byte_reader_skip_unchecked (br, 1);
603 /* Grabs the next numerical value from the bytereader, skipping any spaces.
605 * It will stop/return at the next non-digit/non-space position */
607 byte_reader_get_next_uint64_string (GstByteReader * br, guint64 * out)
610 gboolean res = FALSE;
612 while (gst_byte_reader_get_remaining (br)) {
613 guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
615 if (g_ascii_isdigit (d)) {
616 value = value * 10 + (d - '0');
618 } else if (d != ' ' && d != '\t') {
619 /* we're done and not advancing */
622 gst_byte_reader_skip_unchecked (br, 1);
632 parse_webvtt_time (GstByteReader * br, GstClockTime * t,
633 const gchar ** remainder)
635 GstClockTime val = 0;
636 gboolean res = FALSE;
638 while (!res && gst_byte_reader_get_remaining (br)) {
640 if (byte_reader_get_next_uint_string (br, &numval)) {
641 guint8 next = gst_byte_reader_peek_uint8_unchecked (br);
643 if (next == ':' || next == '.') {
644 /* value was hours, minutes or seconds */
645 val = val * 60 + numval;
646 gst_byte_reader_skip (br, 1);
648 /* Reached the milliseconds, convert to GstClockTime */
649 val = val * GST_SECOND + numval * GST_MSECOND;
658 if (gst_byte_reader_get_remaining (br))
659 *remainder = (const gchar *) gst_byte_reader_peek_data_unchecked (br);
669 br_skipwhitespace (GstByteReader * br)
671 while (gst_byte_reader_get_remaining (br)) {
672 guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
673 if (d != ' ' && d != '\t')
675 gst_byte_reader_skip_unchecked (br, 1);
679 /* Returns TRUE if br starts with str
681 * Skips any spaces/tabs before and after str */
683 br_startswith (GstByteReader * br, const gchar * str, gboolean skip_ws)
685 guint len = strlen (str);
689 br_skipwhitespace (br);
690 if (!gst_byte_reader_peek_data (br, len, &data))
692 if (strncmp ((gchar *) data, str, len))
694 gst_byte_reader_skip_unchecked (br, len);
696 br_skipwhitespace (br);
702 gst_hls_demux_webvtt_read_x_timestamp_map (gchar * data, GstClockTime * local,
703 GstClockTime * mpegts)
707 gst_byte_reader_init (&br, (guint8 *) data, strlen (data));
709 if (!br_startswith (&br, "X-TIMESTAMP-MAP=", FALSE))
712 if (br_startswith (&br, "MPEGTS:", TRUE)) {
713 if (!byte_reader_get_next_uint64_string (&br, mpegts))
715 /* Convert to GstClockTime */
716 *mpegts = MPEGTIME_TO_GSTTIME (*mpegts);
717 if (!br_startswith (&br, ",", TRUE))
719 if (!br_startswith (&br, "LOCAL:", TRUE))
721 if (!parse_webvtt_time (&br, local, NULL))
723 } else if (br_startswith (&br, "LOCAL:", TRUE)) {
724 if (!parse_webvtt_time (&br, local, NULL))
726 if (!br_startswith (&br, ",", TRUE))
728 if (!br_startswith (&br, "MPEGTS:", TRUE))
730 if (!byte_reader_get_next_uint64_string (&br, mpegts))
732 /* Convert to GstClockTime */
733 *mpegts = MPEGTIME_TO_GSTTIME (*mpegts);
738 GST_DEBUG ("local time:%" GST_TIME_FORMAT ", mpegts time:%" GST_TIME_FORMAT,
739 GST_TIME_ARGS (*local), GST_TIME_ARGS (*mpegts));
745 utf8_string_contains_alnum (gchar * string)
749 while ((c = g_utf8_get_char (string))) {
750 if (g_unichar_isalnum (c))
752 string = g_utf8_next_char (string);
758 #define T_H(t) ((t) / (GST_SECOND * 60 * 60))
759 #define T_M(t) ((t) / (GST_SECOND * 60) % 60)
760 #define T_S(t) ((t) / GST_SECOND % 60)
761 #define WEBVTT_TIME_FORMAT "02u:%02u:%02u.%03u"
762 #define WEBVTT_TIME_ARGS(t) \
763 (guint) ((t) / (GST_SECOND * 60 * 60)) , \
764 (guint) ((t) / (GST_SECOND * 60) % 60), \
765 (guint) ((t) / GST_SECOND % 60), \
766 (guint) ((t) / GST_MSECOND % 1000)
768 process_webvtt_cue_timing_setting_line (const gchar * input,
769 GstClockTime * start, GstClockTime * stop, const gchar ** cue_settings)
773 gst_byte_reader_init (&br, (guint8 *) input, strlen (input));
775 /* Handle cue timing start */
776 if (!parse_webvtt_time (&br, start, NULL))
780 if (gst_byte_reader_get_remaining (&br) < 12 ||
781 g_ascii_strncasecmp ((const gchar *)
782 gst_byte_reader_peek_data_unchecked (&br), "-->", 3))
785 gst_byte_reader_skip (&br, 4);
787 /* Handle cue timing stop */
788 if (!parse_webvtt_time (&br, stop, cue_settings))
794 static GstClockTimeDiff
795 convert_webvtt_to_stream_time (GstHLSTimeMap * map, GstClockTime localtime,
796 GstClockTime mpegtime, GstClockTime vtt_value)
798 GstClockTimeDiff res;
800 if (localtime == GST_CLOCK_TIME_NONE || mpegtime == GST_CLOCK_TIME_NONE) {
801 GST_DEBUG ("No X-TIMESTAMP-MAP, assuming values are MPEG-TS values");
802 res = gst_hls_internal_to_stream_time (map, vtt_value);
804 /* VTT only uses positive values */
808 GST_DEBUG ("Converting %" GST_TIME_FORMAT,
809 GST_TIME_ARGS (vtt_value + mpegtime - localtime));
811 gst_hls_internal_to_stream_time (map, vtt_value + mpegtime - localtime);
812 if (res == GST_CLOCK_STIME_NONE) {
813 GST_WARNING ("Couldn't convert value, using original value %"
814 GST_TIME_FORMAT, GST_TIME_ARGS (vtt_value));
816 } else if (res < 0) {
825 gst_hlsdemux_handle_content_webvtt (GstHLSDemux * demux,
826 GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
828 GstHLSParserResult ret = GST_HLS_PARSER_RESULT_DONE;
829 gchar *original_content;
831 gchar **original_lines;
832 GstClockTime localtime = GST_CLOCK_TIME_NONE;
833 GstClockTime mpegtime = GST_CLOCK_TIME_NONE;
834 GstClockTime low_stream_time = GST_CLOCK_STIME_NONE;
835 GstClockTime high_stream_time = GST_CLOCK_STIME_NONE;
836 gboolean found_timing = FALSE;
837 gboolean found_text = FALSE;
839 GstM3U8MediaSegment *current_segment = hls_stream->current_segment;
840 GstClockTimeDiff segment_start, segment_end;
841 GstClockTimeDiff tolerance;
842 gboolean out_of_bounds = FALSE;
845 /* We only process full webvtt fragments */
847 return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
849 original_content = gst_hls_buf_to_utf8_text (*buffer);
851 if (!original_content)
852 return GST_HLS_PARSER_RESULT_ERROR;
854 segment_start = current_segment->stream_time;
855 segment_end = segment_start + current_segment->duration;
856 tolerance = MAX (current_segment->duration / 2, 500 * GST_MSECOND);
858 map = gst_hls_find_time_map (demux, current_segment->discont_sequence);
860 builder = g_ptr_array_new_with_free_func (g_free);
862 original_lines = g_strsplit_set (original_content, "\n\r", 0);
863 nb = g_strv_length (original_lines);
865 for (i = 0; i < nb; i++) {
866 gchar *line = original_lines[i];
868 GST_LOG ("Line: %s", line);
870 if (g_str_has_prefix (line, "X-TIMESTAMP-MAP=")) {
871 if (!gst_hls_demux_webvtt_read_x_timestamp_map (line, &localtime,
873 GST_WARNING ("webvtt timestamp map isn't valid");
874 ret = GST_HLS_PARSER_RESULT_ERROR;
877 g_ptr_array_add (builder, g_strdup (line));
878 } else if (strstr (line, " --> ")) {
879 GstClockTime start, stop;
880 const gchar *leftover;
881 if (process_webvtt_cue_timing_setting_line (line, &start, &stop,
883 GstClockTimeDiff start_stream, stop_stream;
886 GST_LOG ("Found time line %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT,
887 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
890 convert_webvtt_to_stream_time (map, localtime, mpegtime, start);
892 convert_webvtt_to_stream_time (map, localtime, mpegtime, stop);
894 GST_LOG ("Stream time %" GST_STIME_FORMAT " --> %" GST_STIME_FORMAT,
895 GST_STIME_ARGS (start_stream), GST_STIME_ARGS (stop_stream));
897 if (stop_stream < (segment_start - tolerance) ||
898 start_stream > (segment_end + tolerance)) {
899 GST_WARNING ("Out of bounds");
900 out_of_bounds = TRUE;
902 if (low_stream_time == GST_CLOCK_STIME_NONE
903 || stop_stream < low_stream_time)
904 low_stream_time = stop_stream;
905 if (high_stream_time == GST_CLOCK_STIME_NONE
906 || start_stream > high_stream_time)
907 high_stream_time = start_stream;
909 /* Apply the stream presentation offset */
910 start_stream += hls_stream->presentation_offset;
911 stop_stream += hls_stream->presentation_offset;
913 /* Create the time-shifted WebVTT cue line */
916 g_strdup_printf ("%" WEBVTT_TIME_FORMAT " --> %"
917 WEBVTT_TIME_FORMAT " %s", WEBVTT_TIME_ARGS (start_stream),
918 WEBVTT_TIME_ARGS (stop_stream), leftover);
921 g_strdup_printf ("%" WEBVTT_TIME_FORMAT " --> %"
922 WEBVTT_TIME_FORMAT, WEBVTT_TIME_ARGS (start_stream),
923 WEBVTT_TIME_ARGS (stop_stream));
925 GST_LOG ("Generated line '%s'", newline);
926 g_ptr_array_add (builder, newline);
929 GST_WARNING ("Failed to parse time line '%s'", line);
932 } else if (found_timing && !found_text) {
933 gchar *linecopy = g_strdup (line);
934 g_ptr_array_add (builder, linecopy);
935 if (utf8_string_contains_alnum (linecopy)) {
936 GST_DEBUG ("Non-empty line '%s'", line);
940 g_ptr_array_add (builder, g_strdup (line));
948 /* Ensure file always ends with an empty newline by adding an empty
949 * line. This helps downstream parsers properly detect entries */
950 g_ptr_array_add (builder, g_strdup ("\n"));
951 /* Add NULL-terminator to string list */
952 g_ptr_array_add (builder, NULL);
953 newfile = g_strjoinv ("\n", (gchar **) builder->pdata);
954 GST_DEBUG ("newfile:\n%s", newfile);
955 gst_buffer_unref (*buffer);
956 *buffer = gst_buffer_new_wrapped (newfile, strlen (newfile));
959 GST_DEBUG_OBJECT (hls_stream,
960 "Stream time %" GST_STIME_FORMAT " -> %" GST_STIME_FORMAT,
961 GST_STIME_ARGS (low_stream_time), GST_STIME_ARGS (high_stream_time));
963 g_ptr_array_unref (builder);
965 g_strfreev (original_lines);
966 g_free (original_content);
969 GstM3U8MediaSegment *candidate_segment;
971 /* The computed stream time falls outside of the guesstimated stream time,
972 * reassess which segment we really are in */
973 GST_WARNING ("Cue %" GST_STIME_FORMAT " -> %" GST_STIME_FORMAT
974 " is outside of segment %" GST_STIME_FORMAT " -> %"
975 GST_STIME_FORMAT, GST_STIME_ARGS (low_stream_time),
976 GST_STIME_ARGS (high_stream_time),
977 GST_STIME_ARGS (current_segment->stream_time),
978 GST_STIME_ARGS (current_segment->stream_time +
979 current_segment->duration));
982 gst_hls_media_playlist_seek (hls_stream->playlist, TRUE,
983 GST_SEEK_FLAG_SNAP_NEAREST, low_stream_time);
984 if (candidate_segment) {
985 g_assert (candidate_segment != current_segment);
986 GST_DEBUG_OBJECT (hls_stream,
987 "Stream time corresponds to segment %" GST_STIME_FORMAT
988 " duration %" GST_TIME_FORMAT,
989 GST_STIME_ARGS (candidate_segment->stream_time),
990 GST_TIME_ARGS (candidate_segment->duration));
991 /* Recalculate everything and ask parent class to restart */
992 hls_stream->current_segment->stream_time = candidate_segment->stream_time;
993 gst_hls_media_playlist_recalculate_stream_time (hls_stream->playlist,
994 hls_stream->current_segment);
995 gst_m3u8_media_segment_unref (candidate_segment);
1000 GST_DEBUG_OBJECT (hls_stream, "Replacing buffer with droppable buffer");
1002 GST_BUFFER_PTS (*buffer) =
1003 current_segment->stream_time + hls_stream->presentation_offset;
1004 GST_BUFFER_DURATION (*buffer) = current_segment->duration;
1006 gst_buffer_set_flags (*buffer, GST_BUFFER_FLAG_DROPPABLE);
1012 /* Get a utf8-validated string of the contents of the buffer */
1014 gst_hls_buf_to_utf8_text (GstBuffer * buf)
1019 if (!gst_buffer_map (buf, &info, GST_MAP_READ))
1022 if (!g_utf8_validate ((gchar *) info.data, info.size, NULL))
1023 goto validate_error;
1025 /* alloc size + 1 to end with a null character */
1026 playlist = g_malloc0 (info.size + 1);
1027 memcpy (playlist, info.data, info.size);
1029 gst_buffer_unmap (buf, &info);
1033 gst_buffer_unmap (buf, &info);