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 <gst/gst.h>
27 #include <gst/tag/tag.h>
28 #include <string.h>
29
30 #include "gsthlsdemux.h"
31
32 GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux_debug);
33 #define GST_CAT_DEFAULT gst_hls_demux_debug
34
35 /* Check for sync byte, error_indicator == 0 and packet has payload.
36 * Adaptation control field (data[3] & 0x30) may be zero for TS packets with
37 * null PIDs. Still, these streams are valid TS streams (for null packets,
38 * AFC is supposed to be 0x1, but the spec also says decoders should just
39 * discard any packets with AFC = 0x00) */
40 #define IS_MPEGTS_HEADER(data) (data[0] == 0x47 && \
41 (data[1] & 0x80) == 0x00 && \
42 ((data[3] & 0x30) != 0x00 || \
43 ((data[3] & 0x30) == 0x00 && (data[1] & 0x1f) == 0x1f && (data[2] & 0xff) == 0xff)))
44
45 #define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27)
46 #define MPEGTIME_TO_GSTTIME(t) (((t) * (guint64)100000) / 9)
47
48 static gboolean
have_ts_sync(const guint8 * data,guint size,guint packet_size,guint num)49 have_ts_sync (const guint8 * data, guint size, guint packet_size, guint num)
50 {
51 while (num-- > 0) {
52 if (size < packet_size)
53 return FALSE;
54 if (!IS_MPEGTS_HEADER (data))
55 return FALSE;
56 data += packet_size;
57 size -= packet_size;
58 }
59 return TRUE;
60 }
61
62 static gint
find_offset(GstHLSTSReader * r,const guint8 * data,guint size)63 find_offset (GstHLSTSReader * r, const guint8 * data, guint size)
64 {
65 guint sync_points = CLAMP (size / 188, 25, 100);
66 guint off;
67 const gint packet_size = 188;
68
69 /* FIXME: check 192 as well, and maybe also 204, 208 */
70 for (off = 0; off < MIN (size, packet_size); ++off) {
71 if (have_ts_sync (data + off, size - off, packet_size, sync_points)) {
72 r->packet_size = packet_size;
73 return off;
74 }
75 }
76 return -1;
77 }
78
79 static gboolean
handle_pcr(GstHLSTSReader * r,const guint8 * data,guint size)80 handle_pcr (GstHLSTSReader * r, const guint8 * data, guint size)
81 {
82 const guint8 *p = data;
83 guint32 hdr = GST_READ_UINT32_BE (p);
84 guint af_len, flags;
85
86 guint64 pcr_base, pcr_ext, pcr, ts;
87
88 data = p + 4;
89 if ((hdr & 0x00000020) == 0) /* has_adaptation_field */
90 return FALSE;
91 af_len = p[4]; /* adaptation_field_len */
92 ++data;
93 if (af_len < (1 + 6) || af_len > r->packet_size - (4 + 1))
94 return FALSE;
95 flags = data[0];
96 /* Does the packet have a PCR? */
97 if ((flags & 0x10) == 0)
98 return FALSE;
99 ++data;
100 --af_len;
101 pcr_base = (GST_READ_UINT64_BE (data) >> 16) >> (6 + 9);
102 pcr_ext = (GST_READ_UINT64_BE (data) >> 16) & 0x1ff;
103 pcr = pcr_base * 300 + pcr_ext;
104 ts = PCRTIME_TO_GSTTIME (pcr);
105 GST_LOG ("have PCR! %" G_GUINT64_FORMAT "\t%" GST_TIME_FORMAT,
106 pcr, GST_TIME_ARGS (ts));
107 if (r->first_pcr == GST_CLOCK_TIME_NONE)
108 r->first_pcr = ts;
109 r->last_pcr = ts;
110
111 return TRUE;
112 }
113
114 static gboolean
handle_pmt(GstHLSTSReader * r,const guint8 * data,guint size)115 handle_pmt (GstHLSTSReader * r, const guint8 * data, guint size)
116 {
117 const guint8 *p = data;
118 guint32 hdr = GST_READ_UINT32_BE (p);
119 guint slen, pcr_pid;
120
121 data = p + 4;
122 if ((hdr & 0x00000020) != 0) /* has_adaptation_field */
123 data += 1 + p[4]; /* adaptation_field_len */
124 data += 1 + data[0]; /* pointer_field */
125 if (data[0] != 0x02) /* table_id */
126 return FALSE;
127 //gst_util_dump_mem (data, 8);
128 /* we assume the entire PMT fits into a single packet and this is it */
129 if (data[6] != 0 || data[6] != data[7])
130 return FALSE;
131 slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
132 if (slen > (gsize) (p + r->packet_size - (data + 1 + 2)) || slen < 5 + 2 + 4)
133 return FALSE;
134 data += 3 + 5;
135 slen -= 5; /* bytes after section_length field itself */
136 slen -= 4; /* crc at end */
137 pcr_pid = GST_READ_UINT16_BE (data) & 0x1fff;
138 if (pcr_pid != 0x1fff) {
139 GST_DEBUG ("pcr_pid now: %04x", pcr_pid);
140 r->pcr_pid = pcr_pid;
141 return TRUE;
142 }
143
144 return FALSE;
145 }
146
147 static gboolean
handle_pat(GstHLSTSReader * r,const guint8 * data,guint size)148 handle_pat (GstHLSTSReader * r, const guint8 * data, guint size)
149 {
150 const guint8 *p = data;
151 guint32 hdr = GST_READ_UINT32_BE (p);
152 guint slen;
153
154 data = p + 4;
155 if ((hdr & 0x00000020) != 0) /* has_adaptation_field */
156 data += 1 + p[4]; /* adaptation_field_len */
157 data += 1 + data[0]; /* pointer_field */
158 if (data[0] != 0) /* table_id */
159 return FALSE;
160 /* we assume the entire PAT fits into a single packet and this is it */
161 if (data[6] != 0 || data[6] != data[7])
162 return FALSE;
163 slen = GST_READ_UINT16_BE (data + 1) & 0x0FFF;
164 if (slen > (gsize) (p + r->packet_size - (data + 1 + 2)) || slen < 5 + 4 + 4)
165 return FALSE;
166 data += 3 + 5;
167 slen -= 5; /* bytes after section_length field itself */
168 slen -= 4; /* crc at end */
169 while (slen >= 4) {
170 guint program_num = GST_READ_UINT16_BE (data);
171 guint val = GST_READ_UINT16_BE (data + 2) & 0x1fff;
172 if (program_num != 0) {
173 GST_DEBUG (" program %04x: pmt_pid : %04x", program_num, val);
174 r->pmt_pid = val;
175 return TRUE;
176 }
177 data += 4;
178 slen -= 4;
179 }
180
181 return FALSE;
182 }
183
184 void
gst_hlsdemux_tsreader_init(GstHLSTSReader * r)185 gst_hlsdemux_tsreader_init (GstHLSTSReader * r)
186 {
187 r->rtype = GST_HLS_TSREADER_NONE;
188 r->packet_size = 188;
189 r->pmt_pid = r->pcr_pid = -1;
190 r->first_pcr = GST_CLOCK_TIME_NONE;
191 r->last_pcr = GST_CLOCK_TIME_NONE;
192 }
193
194 void
gst_hlsdemux_tsreader_set_type(GstHLSTSReader * r,GstHLSTSReaderType rtype)195 gst_hlsdemux_tsreader_set_type (GstHLSTSReader * r, GstHLSTSReaderType rtype)
196 {
197 r->rtype = rtype;
198 r->have_id3 = FALSE;
199 }
200
201 static gboolean
gst_hlsdemux_tsreader_find_pcrs_mpegts(GstHLSTSReader * r,GstBuffer * buffer,GstClockTime * first_pcr,GstClockTime * last_pcr)202 gst_hlsdemux_tsreader_find_pcrs_mpegts (GstHLSTSReader * r,
203 GstBuffer * buffer, GstClockTime * first_pcr, GstClockTime * last_pcr)
204 {
205 GstMapInfo info;
206 gint offset;
207 const guint8 *p;
208 const guint8 *data;
209 gsize size;
210
211 if (!gst_buffer_map (buffer, &info, GST_MAP_READ))
212 return FALSE;
213
214 data = info.data;
215 size = info.size;
216
217 *first_pcr = *last_pcr = GST_CLOCK_TIME_NONE;
218
219 offset = find_offset (r, data, size);
220 if (offset < 0) {
221 gst_buffer_unmap (buffer, &info);
222 return FALSE;
223 }
224
225 GST_LOG ("TS packet start offset: %d", offset);
226
227 /* We don't store a partial packet at the end,
228 * and just assume that the final PCR is
229 * going to be completely inside the last data
230 * segment passed to us */
231 data += offset;
232 size -= offset;
233
234 for (p = data; size >= r->packet_size;
235 p += r->packet_size, size -= r->packet_size) {
236 guint32 hdr = GST_READ_UINT32_BE (p);
237
238 /* sync byte (0x47), error indicator (TEI) not set, PID 0, has_payload */
239 if ((hdr & 0xFF9FFF10) == 0x47000010) {
240 GST_LOG ("Found packet for PID 0000 (PAT)");
241 handle_pat (r, p, size);
242 }
243 /* sync byte (0x47), error indicator (TEI) not set, has_payload, PID = PMT_pid */
244 else if ((hdr & 0xFF800010) == 0x47000010
245 && ((hdr >> 8) & 0x1fff) == r->pmt_pid) {
246 GST_LOG ("Found packet for PID %04x (PMT)", r->pmt_pid);
247 handle_pmt (r, p, size);
248 }
249 /* sync byte (0x47), error indicator (TEI) not set, has_adaptation_field */
250 else if ((hdr & 0xFF800020) == 0x47000020
251 && ((hdr >> 8) & 0x1fff) == r->pcr_pid) {
252 GST_LOG ("Found packet for PID %04x (PCR)", r->pcr_pid);
253 handle_pcr (r, p, size);
254 }
255 }
256
257 gst_buffer_unmap (buffer, &info);
258
259 *first_pcr = r->first_pcr;
260 *last_pcr = r->last_pcr;
261
262 /* Return TRUE if this piece was big enough to get a PCR from */
263 return (r->first_pcr != GST_CLOCK_TIME_NONE);
264 }
265
266 static gboolean
gst_hlsdemux_tsreader_find_pcrs_id3(GstHLSTSReader * r,GstBuffer ** buffer_out,GstClockTime * first_pcr,GstClockTime * last_pcr,GstTagList ** tags)267 gst_hlsdemux_tsreader_find_pcrs_id3 (GstHLSTSReader * r,
268 GstBuffer ** buffer_out, GstClockTime * first_pcr, GstClockTime * last_pcr,
269 GstTagList ** tags)
270 {
271 GstMapInfo info;
272 guint32 tag_size;
273 gsize size;
274 GstTagList *taglist;
275 GstSample *priv_data = NULL;
276 GstBuffer *buffer = *buffer_out;
277 GstBuffer *tag_buf;
278 guint64 pts;
279
280 *first_pcr = r->first_pcr;
281 *last_pcr = r->last_pcr;
282
283 if (r->have_id3)
284 return TRUE;
285
286 /* We need at least 10 bytes, starting with "ID3" for the header */
287 size = gst_buffer_get_size (buffer);
288 if (size < 10)
289 return FALSE;
290
291 /* Read the tag size */
292 tag_size = gst_tag_get_id3v2_tag_size (buffer);
293
294 /* Check we've collected that much */
295 if (size < tag_size)
296 return FALSE;
297
298 /* From here, whether the tag is valid or not we'll
299 * not try and read again */
300 r->have_id3 = TRUE;
301
302 *buffer_out =
303 gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, tag_size, -1);
304
305 /* Parse the tag */
306 taglist = gst_tag_list_from_id3v2_tag (buffer);
307 if (taglist == NULL) {
308 gst_buffer_unref (buffer);
309 return TRUE; /* Invalid tag, stop trying */
310 }
311
312 *tags = taglist;
313
314 /* Extract the timestamps */
315 if (!gst_tag_list_get_sample (taglist, GST_TAG_PRIVATE_DATA, &priv_data))
316 goto out;
317
318 if (!g_str_equal ("com.apple.streaming.transportStreamTimestamp",
319 gst_structure_get_string (gst_sample_get_info (priv_data), "owner")))
320 goto out;
321
322 /* OK, now as per section 3, the tag contains a 33-bit PCR inside a 64-bit
323 * BE-word */
324 tag_buf = gst_sample_get_buffer (priv_data);
325 if (tag_buf == NULL)
326 goto out;
327
328 if (!gst_buffer_map (tag_buf, &info, GST_MAP_READ))
329 goto out;
330
331 pts = GST_READ_UINT64_BE (info.data);
332 *first_pcr = r->first_pcr = MPEGTIME_TO_GSTTIME (pts);
333
334 GST_LOG ("Got AAC TS PTS %" G_GUINT64_FORMAT " (%" G_GUINT64_FORMAT ")",
335 pts, r->first_pcr);
336
337 gst_buffer_unmap (tag_buf, &info);
338
339 out:
340 if (priv_data)
341 gst_sample_unref (priv_data);
342 gst_buffer_unref (buffer);
343
344 return TRUE;
345 }
346
347 gboolean
gst_hlsdemux_tsreader_find_pcrs(GstHLSTSReader * r,GstBuffer ** buffer,GstClockTime * first_pcr,GstClockTime * last_pcr,GstTagList ** tags)348 gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r,
349 GstBuffer ** buffer, GstClockTime * first_pcr, GstClockTime * last_pcr,
350 GstTagList ** tags)
351 {
352 *tags = NULL;
353
354 if (r->rtype == GST_HLS_TSREADER_MPEGTS)
355 return gst_hlsdemux_tsreader_find_pcrs_mpegts (r, *buffer, first_pcr,
356 last_pcr);
357
358 return gst_hlsdemux_tsreader_find_pcrs_id3 (r, buffer, first_pcr, last_pcr,
359 tags);
360 }
361