hlsdemux2: Ensure processed webvtt ends with empty new line
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / ext / adaptivedemux2 / hls / gsthlsdemux-util.c
1 /* GStreamer
2  * Copyright (C) 2016 Jan Schmidt <jan@centricular.com>
3  * Copyright (C) 2016 Tim-Philipp Müller <tim@centricular.com>
4  *
5  * gsthlsdemux-util.c:
6  *
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.
11  *
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.
16  *
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.
21  */
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <stdio.h>
27
28 #include <gmodule.h>
29
30 #include <gst/gst.h>
31 #include <gst/tag/tag.h>
32 #include <string.h>
33
34 #include "gsthlsdemux.h"
35
36 GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux2_debug);
37 #define GST_CAT_DEFAULT gst_hls_demux2_debug
38
39
40 /* Mpeg-TS Packet */
41 #define TS_PACKET_SYNC_BYTE 0x47
42
43 #define TS_PACKET_TRANSPORT_ERROR_INDICATOR(packet) \
44   ((packet)[1] & 0x80)
45 #define TS_PACKET_PAYLOAD_UNIT_START(packet) \
46   ((packet)[1] & 0x40)
47
48 #define TS_PACKET_PID(packet)                           \
49   ((guint16) ((packet)[1] & 0x1f) << 8 | (packet)[2])
50
51 #define TS_PACKET_TRANSPORT_SCRAMBLING_CONTROL(packet)  \
52   ((packet)[3] & 0xc0)
53 #define TS_PACKET_HAS_ADAPTATION_FIELD(packet) \
54   ((packet)[3] & 0x20)
55 #define TS_PACKET_HAS_PAYLOAD(packet)           \
56   ((packet)[3] & 0x10)
57 #define TS_PACKET_CONTINUITY_COUNTER(pacet)     \
58   ((packet)[3] & 0x0f)
59
60 #define TS_PACKET_ADAPTATION_FIELD(packet)     \
61   (TS_PACKET_HAS_ADAPTATION_FIELD(packet) ?    \
62    (packet) + 4 : NULL)
63
64 /* Adaptation field size. Note: can be 0 */
65 #define TS_PACKET_ADAPTATION_FIELD_SIZE(packet) \
66   (packet)[4]
67
68
69 #define TS_PACKET_PAYLOAD_OFFSET(packet)                \
70   (TS_PACKET_HAS_ADAPTATION_FIELD (packet) ?            \
71    4 + TS_PACKET_ADAPTATION_FIELD_SIZE (packet) + 1 :   \
72    4)
73
74 #define TS_PACKET_PAYLOAD(packet)                       \
75   (TS_PACKET_HAS_PAYLOAD (packet) ?                     \
76    (packet) + TS_PACKET_PAYLOAD_OFFSET(packet) :        \
77    NULL)
78
79 /* PES Packet */
80
81 #define PES_IS_VALID(pes) ((pes)[0] == 0x00 &&  \
82                            (pes)[1] == 0x00 &&  \
83                            (pes)[2] == 0x01)
84
85 #define PES_STREAM_ID(pes) ((pes)[3])
86
87 #define PES_PACKET_LENGTH(pes)                  \
88   ((guint16) (((pes)[4] << 8) | (pes)[5]))
89
90 #define PES_STREAM_TYPE_HAS_HEADER(stream_type) \
91   (stream_type != 0xac)
92
93 #define PES_HEADER_DATA_LENGTH(pes) ((pes)[8])
94 #define PES_PAYLOAD_DATA_OFFSET(pes) \
95   (9 + PES_HEADER_DATA_LENGTH (pes))
96
97 #define PES_HAS_PTS(pes) ((pes)[7] & 0x80)
98 #define PES_HAS_DTS(pes) ((pes)[7] & 0x40)
99
100 /* SI/PSI Packet */
101
102 #define TS_SECTION_POINTER(payload) ((payload)[0])
103 #define TS_PACKET_GET_SECTION(payload) ((payload) + TS_SECTION_POINTER(payload))
104
105 /* PAT section */
106 #define PAT_PROGRAM_OFFSET(pat, idx) \
107   (7 + (idx) * 4)
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)
112
113 static inline gboolean
114 read_ts (const guint8 * data, guint64 * target)
115 {
116 /* sync:4 == 00xx ! pts:3 ! 1 ! pts:15 ! 1 | pts:15 ! 1 */
117   if ((*data & 0x01) != 0x01)
118     return FALSE;
119   *target = ((guint64) (*data++ & 0x0E)) << 29;
120   *target |= ((guint64) (*data++)) << 22;
121   if ((*data & 0x01) != 0x01)
122     return FALSE;
123   *target |= ((guint64) (*data++ & 0xFE)) << 14;
124   *target |= ((guint64) (*data++)) << 7;
125   if ((*data & 0x01) != 0x01)
126     return FALSE;
127   *target |= ((guint64) (*data++ & 0xFE)) >> 1;
128
129   return TRUE;
130 }
131
132 #define PES_PTS_OFFSET(pes) (9)
133 #define PES_PTS(pes, dest) (read_ts ((pes) + PES_PTS_OFFSET(pes), dest))
134
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))
137
138
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)))
148
149 #define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27)
150 #define MPEGTIME_TO_GSTTIME(t) (((t) * (guint64)100000) / 9)
151
152 static gboolean
153 have_ts_sync (const guint8 * data, guint size, guint packet_size, guint num)
154 {
155   while (num-- > 0) {
156     if (size < packet_size)
157       return FALSE;
158     if (!IS_MPEGTS_HEADER (data))
159       return FALSE;
160     data += packet_size;
161     size -= packet_size;
162   }
163   return TRUE;
164 }
165
166 #define GST_MPEGTS_TYPEFIND_MIN_HEADERS 4
167
168 static gint
169 find_offset (const guint8 * data, guint size, guint * out_packet_size)
170 {
171   guint sync_points = CLAMP (size / 188, GST_MPEGTS_TYPEFIND_MIN_HEADERS, 100);
172   guint off;
173   const gint packet_size = 188;
174
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;
179       return off;
180     }
181   }
182   return -1;
183 }
184
185 static gboolean
186 handle_pmt (const guint8 * data, guint size, guint packet_size)
187 {
188   const guint8 *p = data;
189   guint32 hdr = GST_READ_UINT32_BE (p);
190   guint slen, pcr_pid, pilen;
191
192   GST_MEMDUMP ("PMT", data, size);
193   data = p + 4;
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 */
198     return FALSE;
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])
202     return FALSE;
203   slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
204   if (slen > (gsize) (p + packet_size - (data + 1 + 2)) || slen < 5 + 2 + 4)
205     return FALSE;
206   data += 3 + 5;
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);
212   }
213   data += 2;
214   /* Skip global descriptors */
215   pilen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
216   data += 2 + pilen;
217
218
219   return FALSE;
220 }
221
222 static gboolean
223 pat_get_pmt_pid (const guint8 * data, guint size, guint packet_size,
224     gint * pmt_pid)
225 {
226   const guint8 *p = data;
227   guint32 hdr = GST_READ_UINT32_BE (p);
228   guint slen;
229
230   data = p + 4;
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 */
235     return FALSE;
236   /* we assume the entire PAT fits into a single packet and this is it */
237   if (data[6] != 0 || data[6] != data[7])
238     return FALSE;
239   slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
240   if (slen > (gsize) (p + packet_size - (data + 1 + 2)) || slen < 5 + 4 + 4)
241     return FALSE;
242   data += 3 + 5;
243   slen -= 5;                    /* bytes after section_length field itself */
244   slen -= 4;                    /* crc at end */
245   while (slen >= 4) {
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);
250       *pmt_pid = val;
251       return TRUE;
252     }
253     data += 4;
254     slen -= 4;
255   }
256
257   return FALSE;
258 }
259
260 static GstClockTime
261 get_first_mpegts_time (const guint8 * data, gsize size, guint packet_size)
262 {
263   GstClockTime internal_time = GST_CLOCK_TIME_NONE;
264   const guint8 *p;
265   gint pmt_pid = -1;
266
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");
270       break;
271     }
272
273     /* We only care about start packets which have some form of payload (pes or
274        section) */
275     if (TS_PACKET_PAYLOAD_UNIT_START (p) && TS_PACKET_HAS_PAYLOAD (p)) {
276       guint16 pid;
277       const guint8 *payload;
278       const guint8 *afc;
279
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)))
283         continue;
284
285       pid = TS_PACKET_PID (p);
286       payload = TS_PACKET_PAYLOAD (p);
287       afc = TS_PACKET_ADAPTATION_FIELD (p);
288
289       GST_LOG ("PID 0x%04x", pid);
290       if (afc && afc[0])
291         GST_MEMDUMP ("afc", afc, afc[0]);
292       GST_MEMDUMP ("payload", payload, 32);
293       if (pmt_pid != -1 && PES_IS_VALID (payload)) {
294         guint64 ts;
295         GstClockTime pts, dts;
296
297         pts = dts = GST_CLOCK_TIME_NONE;
298
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),
302             32);
303
304         /* Grab PTS/DTS */
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));
309         }
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));
314         }
315
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)
322               internal_time = pts;
323             else
324               internal_time = dts;
325           } else {
326             internal_time = dts;
327           }
328           goto out;
329         } else if (GST_CLOCK_TIME_IS_VALID (pts)) {
330           internal_time = pts;
331           goto out;
332         }
333       } else if (pid == 0x00) {
334         GST_DEBUG ("PAT !");
335         if (!pat_get_pmt_pid (p, packet_size, packet_size, &pmt_pid)) {
336           GST_WARNING ("Invalid PAT");
337           goto out;
338         }
339       } else if (pmt_pid != -1 && pid == pmt_pid) {
340         GST_DEBUG ("PMT !");
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);
345       }
346     }
347   }
348
349 out:
350   return internal_time;
351 }
352
353 GstHLSParserResult
354 gst_hlsdemux_handle_content_mpegts (GstHLSDemux * demux,
355     GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
356 {
357   GstMapInfo info;
358   gint offset;
359   const guint8 *data;
360   GstClockTime internal_time = GST_CLOCK_TIME_NONE;
361   guint packet_size;
362   gsize size;
363
364   if (!gst_buffer_map (*buffer, &info, GST_MAP_READ))
365     return GST_HLS_PARSER_RESULT_ERROR;
366
367   data = info.data;
368   size = info.size;
369
370   offset = find_offset (data, size, &packet_size);
371   if (offset < 0) {
372     gst_buffer_unmap (*buffer, &info);
373     return GST_HLS_PARSER_RESULT_ERROR;
374   }
375
376   GST_LOG ("TS packet start offset: %d", offset);
377
378   data += offset;
379   size -= offset;
380
381   internal_time = get_first_mpegts_time (data, size, packet_size);
382
383   GST_DEBUG_OBJECT (hls_stream, "Using internal time %" GST_TIME_FORMAT,
384       GST_TIME_ARGS (internal_time));
385
386   gst_buffer_unmap (*buffer, &info);
387
388   if (!GST_CLOCK_TIME_IS_VALID (internal_time))
389     return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
390
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);
393 }
394
395 GstHLSParserResult
396 gst_hlsdemux_handle_content_isobmff (GstHLSDemux * demux,
397     GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
398 {
399   GstMapInfo info;
400   GstByteReader br, sub;
401   guint32 box_type;
402   guint header_size;
403   guint64 box_size;
404   GstHLSParserResult ret = GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
405   GstClockTime smallest_ts = GST_CLOCK_TIME_NONE;
406
407   if (!gst_buffer_map (*buffer, &info, GST_MAP_READ))
408     return GST_HLS_PARSER_RESULT_ERROR;
409
410   gst_byte_reader_init (&br, info.data, info.size);
411
412   while (gst_byte_reader_get_remaining (&br) &&
413       gst_isoff_parse_box_header (&br, &box_type, NULL, &header_size,
414           &box_size)) {
415     GST_DEBUG ("box %" GST_FOURCC_FORMAT " size:%" G_GUINT64_FORMAT,
416         GST_FOURCC_ARGS (box_type), box_size);
417
418     GST_MEMDUMP ("box content", br.data + br.byte, MIN (256,
419             box_size - header_size));
420
421     switch (box_type) {
422       case GST_ISOFF_FOURCC_MOOV:
423       {
424         GstMoovBox *moov;
425         gst_byte_reader_get_sub_reader (&br, &sub, box_size - header_size);
426         moov = gst_isoff_moov_box_parse (&sub);
427
428         if (moov) {
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;
433         }
434         break;
435       }
436       case GST_ISOFF_FOURCC_MOOF:
437       {
438         GstMoofBox *moof;
439
440         if (hls_stream->moov == NULL) {
441           GST_WARNING ("Received moof with moov in iso-ff stream");
442           break;
443         }
444
445         gst_byte_reader_get_sub_reader (&br, &sub, box_size - header_size);
446
447         moof = gst_isoff_moof_box_parse (&sub);
448
449         if (moof) {
450           guint i, j;
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++) {
454             GstTrakBox *trak =
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) {
460                 GstClockTime ts = 0;
461                 guint64 decode_time = traf->tfdt.decode_time;
462
463                 if (decode_time != GST_CLOCK_TIME_NONE)
464                   ts = gst_util_uint64_scale (decode_time, GST_SECOND,
465                       trak->mdia.mdhd.timescale);
466
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)
470                   smallest_ts = ts;
471               }
472             }
473           }
474           gst_isoff_moof_box_free (moof);
475         } else {
476           GST_WARNING ("Failed to parse moof");
477         }
478         if (smallest_ts != GST_CLOCK_TIME_NONE)
479           goto out;
480         break;
481       }
482       case GST_ISOFF_FOURCC_MDAT:
483         GST_DEBUG ("Reached `mdat`, returning");
484         goto out;
485         break;
486       default:
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);
490         break;
491     }
492
493   }
494
495 out:
496   gst_buffer_unmap (*buffer, &info);
497
498   if (smallest_ts != GST_CLOCK_TIME_NONE) {
499     ret = gst_hlsdemux_handle_internal_time (demux, hls_stream, smallest_ts);
500   }
501
502   return ret;
503 }
504
505
506 GstHLSParserResult
507 gst_hlsdemux_handle_content_id3 (GstHLSDemux * demux,
508     GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
509 {
510   GstMapInfo info;
511   guint32 tag_size;
512   gsize size;
513   GstTagList *taglist;
514   GstSample *priv_data = NULL;
515   GstBuffer *tag_buf;
516   guint64 pts;
517   GstHLSParserResult ret = GST_HLS_PARSER_RESULT_DONE;
518   GstClockTime internal;
519
520   /* We need at least 10 bytes, starting with "ID3" for the header */
521   size = gst_buffer_get_size (*buffer);
522   if (size < 10)
523     return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
524
525   /* Read the tag size */
526   tag_size = gst_tag_get_id3v2_tag_size (*buffer);
527
528   /* Check we've collected that much */
529   if (size < tag_size)
530     return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
531
532   /* Parse the tag */
533   taglist = gst_tag_list_from_id3v2_tag (*buffer);
534   if (taglist == NULL) {
535     return GST_HLS_PARSER_RESULT_ERROR; /* Invalid tag, stop trying */
536   }
537
538   /* Extract the timestamps */
539   if (!gst_tag_list_get_sample (taglist, GST_TAG_PRIVATE_DATA, &priv_data))
540     goto out;
541
542   if (!g_str_equal ("com.apple.streaming.transportStreamTimestamp",
543           gst_structure_get_string (gst_sample_get_info (priv_data), "owner")))
544     goto out;
545
546   /* OK, now as per section 3, the tag contains a 33-bit PCR inside a 64-bit
547    * BE-word */
548   tag_buf = gst_sample_get_buffer (priv_data);
549   if (tag_buf == NULL)
550     goto out;
551
552   if (!gst_buffer_map (tag_buf, &info, GST_MAP_READ))
553     goto out;
554   GST_MEMDUMP ("id3 tag", info.data, info.size);
555
556   pts = GST_READ_UINT64_BE (info.data);
557   internal = MPEGTIME_TO_GSTTIME (pts);
558
559   GST_LOG ("Got internal PTS from ID3: %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT
560       ")", pts, GST_TIME_ARGS (internal));
561
562   gst_buffer_unmap (tag_buf, &info);
563
564   ret = gst_hlsdemux_handle_internal_time (demux, hls_stream, internal);
565
566 out:
567   if (priv_data)
568     gst_sample_unref (priv_data);
569   if (taglist)
570     gst_tag_list_unref (taglist);
571
572   return ret;
573 }
574
575 /* Grabs the next numerical value from the bytereader, skipping any spaces.
576  *
577  * It will stop/return at the next non-digit/non-space position */
578 static gboolean
579 byte_reader_get_next_uint_string (GstByteReader * br, guint * out)
580 {
581   guint value = 0;
582   gboolean res = FALSE;
583
584   while (gst_byte_reader_get_remaining (br)) {
585     guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
586
587     if (g_ascii_isdigit (d)) {
588       value = value * 10 + (d - '0');
589       res = TRUE;
590     } else if (d != ' ' && d != '\t') {
591       /* we're done and not advancing */
592       break;
593     }
594     gst_byte_reader_skip_unchecked (br, 1);
595   }
596
597   if (res)
598     *out = value;
599
600   return res;
601 }
602
603 /* Grabs the next numerical value from the bytereader, skipping any spaces.
604  *
605  * It will stop/return at the next non-digit/non-space position */
606 static gboolean
607 byte_reader_get_next_uint64_string (GstByteReader * br, guint64 * out)
608 {
609   guint64 value = 0;
610   gboolean res = FALSE;
611
612   while (gst_byte_reader_get_remaining (br)) {
613     guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
614
615     if (g_ascii_isdigit (d)) {
616       value = value * 10 + (d - '0');
617       res = TRUE;
618     } else if (d != ' ' && d != '\t') {
619       /* we're done and not advancing */
620       break;
621     }
622     gst_byte_reader_skip_unchecked (br, 1);
623   }
624
625   if (res)
626     *out = value;
627
628   return res;
629 }
630
631 static gboolean
632 parse_webvtt_time (GstByteReader * br, GstClockTime * t,
633     const gchar ** remainder)
634 {
635   GstClockTime val = 0;
636   gboolean res = FALSE;
637
638   while (!res && gst_byte_reader_get_remaining (br)) {
639     guint numval;
640     if (byte_reader_get_next_uint_string (br, &numval)) {
641       guint8 next = gst_byte_reader_peek_uint8_unchecked (br);
642
643       if (next == ':' || next == '.') {
644         /* value was hours, minutes or seconds */
645         val = val * 60 + numval;
646         gst_byte_reader_skip (br, 1);
647       } else {
648         /* Reached the milliseconds, convert to GstClockTime */
649         val = val * GST_SECOND + numval * GST_MSECOND;
650         res = TRUE;
651       }
652     }
653   }
654
655   if (res) {
656     *t = val;
657     if (remainder) {
658       if (gst_byte_reader_get_remaining (br))
659         *remainder = (const gchar *) gst_byte_reader_peek_data_unchecked (br);
660       else
661         *remainder = NULL;
662     }
663   }
664
665   return res;
666 }
667
668 static inline void
669 br_skipwhitespace (GstByteReader * br)
670 {
671   while (gst_byte_reader_get_remaining (br)) {
672     guint8 d = gst_byte_reader_peek_uint8_unchecked (br);
673     if (d != ' ' && d != '\t')
674       return;
675     gst_byte_reader_skip_unchecked (br, 1);
676   }
677 }
678
679 /* Returns TRUE if br starts with str
680  *
681  * Skips any spaces/tabs before and after str */
682 static gboolean
683 br_startswith (GstByteReader * br, const gchar * str, gboolean skip_ws)
684 {
685   guint len = strlen (str);
686   const guint8 *data;
687
688   if (skip_ws)
689     br_skipwhitespace (br);
690   if (!gst_byte_reader_peek_data (br, len, &data))
691     return FALSE;
692   if (strncmp ((gchar *) data, str, len))
693     return FALSE;
694   gst_byte_reader_skip_unchecked (br, len);
695   if (skip_ws)
696     br_skipwhitespace (br);
697
698   return TRUE;
699 }
700
701 static gboolean
702 gst_hls_demux_webvtt_read_x_timestamp_map (gchar * data, GstClockTime * local,
703     GstClockTime * mpegts)
704 {
705   GstByteReader br;
706
707   gst_byte_reader_init (&br, (guint8 *) data, strlen (data));
708
709   if (!br_startswith (&br, "X-TIMESTAMP-MAP=", FALSE))
710     return FALSE;
711
712   if (br_startswith (&br, "MPEGTS:", TRUE)) {
713     if (!byte_reader_get_next_uint64_string (&br, mpegts))
714       return FALSE;
715     /* Convert to GstClockTime */
716     *mpegts = MPEGTIME_TO_GSTTIME (*mpegts);
717     if (!br_startswith (&br, ",", TRUE))
718       return FALSE;
719     if (!br_startswith (&br, "LOCAL:", TRUE))
720       return FALSE;
721     if (!parse_webvtt_time (&br, local, NULL))
722       return FALSE;
723   } else if (br_startswith (&br, "LOCAL:", TRUE)) {
724     if (!parse_webvtt_time (&br, local, NULL))
725       return FALSE;
726     if (!br_startswith (&br, ",", TRUE))
727       return FALSE;
728     if (!br_startswith (&br, "MPEGTS:", TRUE))
729       return FALSE;
730     if (!byte_reader_get_next_uint64_string (&br, mpegts))
731       return FALSE;
732     /* Convert to GstClockTime */
733     *mpegts = MPEGTIME_TO_GSTTIME (*mpegts);
734   } else {
735     return FALSE;
736   }
737
738   GST_DEBUG ("local time:%" GST_TIME_FORMAT ", mpegts time:%" GST_TIME_FORMAT,
739       GST_TIME_ARGS (*local), GST_TIME_ARGS (*mpegts));
740
741   return TRUE;
742 }
743
744 static gboolean
745 utf8_string_contains_alnum (gchar * string)
746 {
747   gunichar c;
748
749   while ((c = g_utf8_get_char (string))) {
750     if (g_unichar_isalnum (c))
751       return TRUE;
752     string = g_utf8_next_char (string);
753   }
754
755   return FALSE;
756 }
757
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)
767 static gboolean
768 process_webvtt_cue_timing_setting_line (const gchar * input,
769     GstClockTime * start, GstClockTime * stop, const gchar ** cue_settings)
770 {
771   GstByteReader br;
772
773   gst_byte_reader_init (&br, (guint8 *) input, strlen (input));
774
775   /* Handle cue timing start */
776   if (!parse_webvtt_time (&br, start, NULL))
777     return FALSE;
778
779   /* --> */
780   if (gst_byte_reader_get_remaining (&br) < 12 ||
781       g_ascii_strncasecmp ((const gchar *)
782           gst_byte_reader_peek_data_unchecked (&br), "-->", 3))
783     return FALSE;
784
785   gst_byte_reader_skip (&br, 4);
786
787   /* Handle cue timing stop */
788   if (!parse_webvtt_time (&br, stop, cue_settings))
789     return FALSE;
790
791   return TRUE;
792 }
793
794 static GstClockTimeDiff
795 convert_webvtt_to_stream_time (GstHLSTimeMap * map, GstClockTime localtime,
796     GstClockTime mpegtime, GstClockTime vtt_value)
797 {
798   GstClockTimeDiff res;
799
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);
803
804     /* VTT only uses positive values */
805     if (res < 0)
806       res = 0;
807   } else {
808     GST_DEBUG ("Converting %" GST_TIME_FORMAT,
809         GST_TIME_ARGS (vtt_value + mpegtime - localtime));
810     res =
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));
815       res = vtt_value;
816     } else if (res < 0) {
817       res = 0;
818     }
819   }
820
821   return res;
822 }
823
824 GstHLSParserResult
825 gst_hlsdemux_handle_content_webvtt (GstHLSDemux * demux,
826     GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
827 {
828   GstHLSParserResult ret = GST_HLS_PARSER_RESULT_DONE;
829   gchar *original_content;
830   guint i, nb;
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;
838   GPtrArray *builder;
839   GstM3U8MediaSegment *current_segment = hls_stream->current_segment;
840   GstClockTimeDiff segment_start, segment_end;
841   GstClockTimeDiff tolerance;
842   gboolean out_of_bounds = FALSE;
843   GstHLSTimeMap *map;
844
845   /* We only process full webvtt fragments */
846   if (!draining)
847     return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
848
849   original_content = gst_hls_buf_to_utf8_text (*buffer);
850
851   if (!original_content)
852     return GST_HLS_PARSER_RESULT_ERROR;
853
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);
857
858   map = gst_hls_find_time_map (demux, current_segment->discont_sequence);
859
860   builder = g_ptr_array_new_with_free_func (g_free);
861
862   original_lines = g_strsplit_set (original_content, "\n\r", 0);
863   nb = g_strv_length (original_lines);
864
865   for (i = 0; i < nb; i++) {
866     gchar *line = original_lines[i];
867
868     GST_LOG ("Line: %s", line);
869
870     if (g_str_has_prefix (line, "X-TIMESTAMP-MAP=")) {
871       if (!gst_hls_demux_webvtt_read_x_timestamp_map (line, &localtime,
872               &mpegtime)) {
873         GST_WARNING ("webvtt timestamp map isn't valid");
874         ret = GST_HLS_PARSER_RESULT_ERROR;
875         goto out;
876       }
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,
882               &leftover)) {
883         GstClockTimeDiff start_stream, stop_stream;
884         gchar *newline;
885
886         GST_LOG ("Found time line %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT,
887             GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
888
889         start_stream =
890             convert_webvtt_to_stream_time (map, localtime, mpegtime, start);
891         stop_stream =
892             convert_webvtt_to_stream_time (map, localtime, mpegtime, stop);
893
894         GST_LOG ("Stream time %" GST_STIME_FORMAT " --> %" GST_STIME_FORMAT,
895             GST_STIME_ARGS (start_stream), GST_STIME_ARGS (stop_stream));
896
897         if (stop_stream < (segment_start - tolerance) ||
898             start_stream > (segment_end + tolerance)) {
899           GST_WARNING ("Out of bounds");
900           out_of_bounds = TRUE;
901         }
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;
908
909         /* Apply the stream presentation offset */
910         start_stream += hls_stream->presentation_offset;
911         stop_stream += hls_stream->presentation_offset;
912
913         /* Create the time-shifted WebVTT cue line */
914         if (leftover) {
915           newline =
916               g_strdup_printf ("%" WEBVTT_TIME_FORMAT " --> %"
917               WEBVTT_TIME_FORMAT " %s", WEBVTT_TIME_ARGS (start_stream),
918               WEBVTT_TIME_ARGS (stop_stream), leftover);
919         } else {
920           newline =
921               g_strdup_printf ("%" WEBVTT_TIME_FORMAT " --> %"
922               WEBVTT_TIME_FORMAT, WEBVTT_TIME_ARGS (start_stream),
923               WEBVTT_TIME_ARGS (stop_stream));
924         }
925         GST_LOG ("Generated line '%s'", newline);
926         g_ptr_array_add (builder, newline);
927         found_timing = TRUE;
928       } else {
929         GST_WARNING ("Failed to parse time line '%s'", line);
930         /* Abort ? */
931       }
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);
937         found_text = TRUE;
938       }
939     } else {
940       g_ptr_array_add (builder, g_strdup (line));
941     }
942   }
943
944 out:
945   if (ret) {
946     gchar *newfile;
947
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));
957   }
958
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));
962
963   g_ptr_array_unref (builder);
964
965   g_strfreev (original_lines);
966   g_free (original_content);
967
968   if (out_of_bounds) {
969     GstM3U8MediaSegment *candidate_segment;
970
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));
980
981     candidate_segment =
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);
996     }
997   }
998
999   if (!found_text) {
1000     GST_DEBUG_OBJECT (hls_stream, "Replacing buffer with droppable buffer");
1001
1002     GST_BUFFER_PTS (*buffer) =
1003         current_segment->stream_time + hls_stream->presentation_offset;
1004     GST_BUFFER_DURATION (*buffer) = current_segment->duration;
1005
1006     gst_buffer_set_flags (*buffer, GST_BUFFER_FLAG_DROPPABLE);
1007   }
1008
1009   return ret;
1010 }
1011
1012 /* Get a utf8-validated string of the contents of the buffer */
1013 gchar *
1014 gst_hls_buf_to_utf8_text (GstBuffer * buf)
1015 {
1016   GstMapInfo info;
1017   gchar *playlist;
1018
1019   if (!gst_buffer_map (buf, &info, GST_MAP_READ))
1020     goto map_error;
1021
1022   if (!g_utf8_validate ((gchar *) info.data, info.size, NULL))
1023     goto validate_error;
1024
1025   /* alloc size + 1 to end with a null character */
1026   playlist = g_malloc0 (info.size + 1);
1027   memcpy (playlist, info.data, info.size);
1028
1029   gst_buffer_unmap (buf, &info);
1030   return playlist;
1031
1032 validate_error:
1033   gst_buffer_unmap (buf, &info);
1034 map_error:
1035   return NULL;
1036 }