diff options
| author | Khem Raj <raj.khem@gmail.com> | 2016-12-22 00:14:14 -0800 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-01-19 22:47:20 +0000 |
| commit | 72ee691056ee9c9488774350b15d2e7602ac4683 (patch) | |
| tree | 8a18aa5ef721a5b4c01e15547c9821b2048ba477 | |
| parent | fdf4a0d3cf25498bc38d5882f2b5e97d171e3d1d (diff) | |
| download | poky-72ee691056ee9c9488774350b15d2e7602ac4683.tar.gz | |
gstreamer1.0-plugins-bad: Backport patches for improving live playback
(From OE-Core rev: 23c37ffb25a41cd8b30a3fb56731fd6753478092)
Signed-off-by: Khem Raj <raj.khem@gmail.com>
Signed-off-by: Ross Burton <ross.burton@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
4 files changed, 1177 insertions, 0 deletions
diff --git a/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-mssdemux-improved-live-playback-support.patch b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-mssdemux-improved-live-playback-support.patch new file mode 100644 index 0000000000..4832c18e78 --- /dev/null +++ b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-mssdemux-improved-live-playback-support.patch | |||
| @@ -0,0 +1,929 @@ | |||
| 1 | From 73721ad4e9e2d32e1c8b6a3b4aaa98401530e58a Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Philippe Normand <philn@igalia.com> | ||
| 3 | Date: Tue, 29 Nov 2016 14:43:41 +0100 | ||
| 4 | Subject: [PATCH] mssdemux: improved live playback support | ||
| 5 | |||
| 6 | When a MSS server hosts a live stream the fragments listed in the | ||
| 7 | manifest usually don't have accurate timestamps and duration, except | ||
| 8 | for the first fragment, which additionally stores timing information | ||
| 9 | for the few upcoming fragments. In this scenario it is useless to | ||
| 10 | periodically fetch and update the manifest and the fragments list can | ||
| 11 | be incrementally built by parsing the first/current fragment. | ||
| 12 | |||
| 13 | https://bugzilla.gnome.org/show_bug.cgi?id=755036 | ||
| 14 | --- | ||
| 15 | Upstream-Status: Backport | ||
| 16 | Signed-off-by: Khem Raj <raj.khem@gmail.com> | ||
| 17 | |||
| 18 | ext/smoothstreaming/Makefile.am | 2 + | ||
| 19 | ext/smoothstreaming/gstmssdemux.c | 60 ++++++ | ||
| 20 | ext/smoothstreaming/gstmssfragmentparser.c | 266 ++++++++++++++++++++++++++ | ||
| 21 | ext/smoothstreaming/gstmssfragmentparser.h | 84 ++++++++ | ||
| 22 | ext/smoothstreaming/gstmssmanifest.c | 158 ++++++++++++++- | ||
| 23 | ext/smoothstreaming/gstmssmanifest.h | 7 + | ||
| 24 | gst-libs/gst/adaptivedemux/gstadaptivedemux.c | 27 ++- | ||
| 25 | gst-libs/gst/adaptivedemux/gstadaptivedemux.h | 14 ++ | ||
| 26 | 8 files changed, 606 insertions(+), 12 deletions(-) | ||
| 27 | create mode 100644 ext/smoothstreaming/gstmssfragmentparser.c | ||
| 28 | create mode 100644 ext/smoothstreaming/gstmssfragmentparser.h | ||
| 29 | |||
| 30 | diff --git a/ext/smoothstreaming/Makefile.am b/ext/smoothstreaming/Makefile.am | ||
| 31 | index 4faf9df9f..a5e1ad6ae 100644 | ||
| 32 | --- a/ext/smoothstreaming/Makefile.am | ||
| 33 | +++ b/ext/smoothstreaming/Makefile.am | ||
| 34 | @@ -13,8 +13,10 @@ libgstsmoothstreaming_la_LIBADD = \ | ||
| 35 | libgstsmoothstreaming_la_LDFLAGS = ${GST_PLUGIN_LDFLAGS} | ||
| 36 | libgstsmoothstreaming_la_SOURCES = gstsmoothstreaming-plugin.c \ | ||
| 37 | gstmssdemux.c \ | ||
| 38 | + gstmssfragmentparser.c \ | ||
| 39 | gstmssmanifest.c | ||
| 40 | libgstsmoothstreaming_la_LIBTOOLFLAGS = --tag=disable-static | ||
| 41 | |||
| 42 | noinst_HEADERS = gstmssdemux.h \ | ||
| 43 | + gstmssfragmentparser.h \ | ||
| 44 | gstmssmanifest.h | ||
| 45 | diff --git a/ext/smoothstreaming/gstmssdemux.c b/ext/smoothstreaming/gstmssdemux.c | ||
| 46 | index 12fb40497..120d9c22b 100644 | ||
| 47 | --- a/ext/smoothstreaming/gstmssdemux.c | ||
| 48 | +++ b/ext/smoothstreaming/gstmssdemux.c | ||
| 49 | @@ -135,11 +135,18 @@ gst_mss_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream); | ||
| 50 | static gboolean gst_mss_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek); | ||
| 51 | static gint64 | ||
| 52 | gst_mss_demux_get_manifest_update_interval (GstAdaptiveDemux * demux); | ||
| 53 | +static gint64 | ||
| 54 | +gst_mss_demux_stream_get_fragment_waiting_time (GstAdaptiveDemuxStream * | ||
| 55 | + stream); | ||
| 56 | static GstFlowReturn | ||
| 57 | gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux, | ||
| 58 | GstBuffer * buffer); | ||
| 59 | static gboolean gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux, | ||
| 60 | gint64 * start, gint64 * stop); | ||
| 61 | +static GstFlowReturn gst_mss_demux_data_received (GstAdaptiveDemux * demux, | ||
| 62 | + GstAdaptiveDemuxStream * stream, GstBuffer * buffer); | ||
| 63 | +static gboolean | ||
| 64 | +gst_mss_demux_requires_periodical_playlist_update (GstAdaptiveDemux * demux); | ||
| 65 | |||
| 66 | static void | ||
| 67 | gst_mss_demux_class_init (GstMssDemuxClass * klass) | ||
| 68 | @@ -192,10 +199,15 @@ gst_mss_demux_class_init (GstMssDemuxClass * klass) | ||
| 69 | gst_mss_demux_stream_select_bitrate; | ||
| 70 | gstadaptivedemux_class->stream_update_fragment_info = | ||
| 71 | gst_mss_demux_stream_update_fragment_info; | ||
| 72 | + gstadaptivedemux_class->stream_get_fragment_waiting_time = | ||
| 73 | + gst_mss_demux_stream_get_fragment_waiting_time; | ||
| 74 | gstadaptivedemux_class->update_manifest_data = | ||
| 75 | gst_mss_demux_update_manifest_data; | ||
| 76 | gstadaptivedemux_class->get_live_seek_range = | ||
| 77 | gst_mss_demux_get_live_seek_range; | ||
| 78 | + gstadaptivedemux_class->data_received = gst_mss_demux_data_received; | ||
| 79 | + gstadaptivedemux_class->requires_periodical_playlist_update = | ||
| 80 | + gst_mss_demux_requires_periodical_playlist_update; | ||
| 81 | |||
| 82 | GST_DEBUG_CATEGORY_INIT (mssdemux_debug, "mssdemux", 0, "mssdemux plugin"); | ||
| 83 | } | ||
| 84 | @@ -650,6 +662,13 @@ gst_mss_demux_get_manifest_update_interval (GstAdaptiveDemux * demux) | ||
| 85 | return interval; | ||
| 86 | } | ||
| 87 | |||
| 88 | +static gint64 | ||
| 89 | +gst_mss_demux_stream_get_fragment_waiting_time (GstAdaptiveDemuxStream * stream) | ||
| 90 | +{ | ||
| 91 | + /* Wait a second for live streams so we don't try premature fragments downloading */ | ||
| 92 | + return GST_SECOND; | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | static GstFlowReturn | ||
| 96 | gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux, | ||
| 97 | GstBuffer * buffer) | ||
| 98 | @@ -670,3 +689,44 @@ gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, | ||
| 99 | |||
| 100 | return gst_mss_manifest_get_live_seek_range (mssdemux->manifest, start, stop); | ||
| 101 | } | ||
| 102 | + | ||
| 103 | +static GstFlowReturn | ||
| 104 | +gst_mss_demux_data_received (GstAdaptiveDemux * demux, | ||
| 105 | + GstAdaptiveDemuxStream * stream, GstBuffer * buffer) | ||
| 106 | +{ | ||
| 107 | + GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux); | ||
| 108 | + GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream; | ||
| 109 | + gsize available; | ||
| 110 | + | ||
| 111 | + if (!gst_mss_manifest_is_live (mssdemux->manifest)) { | ||
| 112 | + return GST_ADAPTIVE_DEMUX_CLASS (parent_class)->data_received (demux, | ||
| 113 | + stream, buffer); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + if (gst_mss_stream_fragment_parsing_needed (mssstream->manifest_stream)) { | ||
| 117 | + gst_mss_manifest_live_adapter_push (mssstream->manifest_stream, buffer); | ||
| 118 | + available = | ||
| 119 | + gst_mss_manifest_live_adapter_available (mssstream->manifest_stream); | ||
| 120 | + // FIXME: try to reduce this minimal size. | ||
| 121 | + if (available < 4096) { | ||
| 122 | + return GST_FLOW_OK; | ||
| 123 | + } else { | ||
| 124 | + GST_LOG_OBJECT (stream->pad, "enough data, parsing fragment."); | ||
| 125 | + buffer = | ||
| 126 | + gst_mss_manifest_live_adapter_take_buffer (mssstream->manifest_stream, | ||
| 127 | + available); | ||
| 128 | + gst_mss_stream_parse_fragment (mssstream->manifest_stream, buffer); | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + return GST_ADAPTIVE_DEMUX_CLASS (parent_class)->data_received (demux, stream, | ||
| 133 | + buffer); | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +static gboolean | ||
| 137 | +gst_mss_demux_requires_periodical_playlist_update (GstAdaptiveDemux * demux) | ||
| 138 | +{ | ||
| 139 | + GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux); | ||
| 140 | + | ||
| 141 | + return (!gst_mss_manifest_is_live (mssdemux->manifest)); | ||
| 142 | +} | ||
| 143 | diff --git a/ext/smoothstreaming/gstmssfragmentparser.c b/ext/smoothstreaming/gstmssfragmentparser.c | ||
| 144 | new file mode 100644 | ||
| 145 | index 000000000..b554d4f31 | ||
| 146 | --- /dev/null | ||
| 147 | +++ b/ext/smoothstreaming/gstmssfragmentparser.c | ||
| 148 | @@ -0,0 +1,266 @@ | ||
| 149 | +/* | ||
| 150 | + * Microsoft Smooth-Streaming fragment parsing library | ||
| 151 | + * | ||
| 152 | + * gstmssfragmentparser.h | ||
| 153 | + * | ||
| 154 | + * Copyright (C) 2016 Igalia S.L | ||
| 155 | + * Copyright (C) 2016 Metrological | ||
| 156 | + * Author: Philippe Normand <philn@igalia.com> | ||
| 157 | + * | ||
| 158 | + * This library is free software; you can redistribute it and/or | ||
| 159 | + * modify it under the terms of the GNU Library General Public | ||
| 160 | + * License as published by the Free Software Foundation; either | ||
| 161 | + * version 2.1 of the License, or (at your option) any later version. | ||
| 162 | + * | ||
| 163 | + * This library is distributed in the hope that it will be useful, | ||
| 164 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 165 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 166 | + * Library General Public License for more details. | ||
| 167 | + * | ||
| 168 | + * You should have received a copy of the GNU Library General Public | ||
| 169 | + * License along with this library (COPYING); if not, write to the | ||
| 170 | + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 171 | + * Boston, MA 02111-1307, USA. | ||
| 172 | + */ | ||
| 173 | + | ||
| 174 | +#include "gstmssfragmentparser.h" | ||
| 175 | +#include <gst/base/gstbytereader.h> | ||
| 176 | +#include <string.h> | ||
| 177 | + | ||
| 178 | +GST_DEBUG_CATEGORY_EXTERN (mssdemux_debug); | ||
| 179 | +#define GST_CAT_DEFAULT mssdemux_debug | ||
| 180 | + | ||
| 181 | +void | ||
| 182 | +gst_mss_fragment_parser_init (GstMssFragmentParser * parser) | ||
| 183 | +{ | ||
| 184 | + parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_INIT; | ||
| 185 | + parser->tfrf.entries_count = 0; | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +void | ||
| 189 | +gst_mss_fragment_parser_clear (GstMssFragmentParser * parser) | ||
| 190 | +{ | ||
| 191 | + parser->tfrf.entries_count = 0; | ||
| 192 | + if (parser->tfrf.entries) { | ||
| 193 | + g_free (parser->tfrf.entries); | ||
| 194 | + parser->tfrf.entries = 0; | ||
| 195 | + } | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +static gboolean | ||
| 199 | +_parse_tfrf_box (GstMssFragmentParser * parser, GstByteReader * reader) | ||
| 200 | +{ | ||
| 201 | + guint8 version; | ||
| 202 | + guint32 flags = 0; | ||
| 203 | + guint8 fragment_count = 0; | ||
| 204 | + guint8 index = 0; | ||
| 205 | + | ||
| 206 | + if (!gst_byte_reader_get_uint8 (reader, &version)) { | ||
| 207 | + GST_ERROR ("Error getting box's version field"); | ||
| 208 | + return FALSE; | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + if (!gst_byte_reader_get_uint24_be (reader, &flags)) { | ||
| 212 | + GST_ERROR ("Error getting box's flags field"); | ||
| 213 | + return FALSE; | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + gst_byte_reader_get_uint8 (reader, &fragment_count); | ||
| 217 | + parser->tfrf.entries_count = fragment_count; | ||
| 218 | + parser->tfrf.entries = | ||
| 219 | + g_malloc (sizeof (GstTfrfBoxEntry) * parser->tfrf.entries_count); | ||
| 220 | + for (index = 0; index < fragment_count; index++) { | ||
| 221 | + guint64 absolute_time = 0; | ||
| 222 | + guint64 absolute_duration = 0; | ||
| 223 | + if (version & 0x01) { | ||
| 224 | + gst_byte_reader_get_uint64_be (reader, &absolute_time); | ||
| 225 | + gst_byte_reader_get_uint64_be (reader, &absolute_duration); | ||
| 226 | + } else { | ||
| 227 | + guint32 time = 0; | ||
| 228 | + guint32 duration = 0; | ||
| 229 | + gst_byte_reader_get_uint32_be (reader, &time); | ||
| 230 | + gst_byte_reader_get_uint32_be (reader, &duration); | ||
| 231 | + time = ~time; | ||
| 232 | + duration = ~duration; | ||
| 233 | + absolute_time = ~time; | ||
| 234 | + absolute_duration = ~duration; | ||
| 235 | + } | ||
| 236 | + parser->tfrf.entries[index].time = absolute_time; | ||
| 237 | + parser->tfrf.entries[index].duration = absolute_duration; | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + GST_LOG ("tfrf box parsed"); | ||
| 241 | + return TRUE; | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +static gboolean | ||
| 245 | +_parse_tfxd_box (GstMssFragmentParser * parser, GstByteReader * reader) | ||
| 246 | +{ | ||
| 247 | + guint8 version; | ||
| 248 | + guint32 flags = 0; | ||
| 249 | + guint64 absolute_time = 0; | ||
| 250 | + guint64 absolute_duration = 0; | ||
| 251 | + | ||
| 252 | + if (!gst_byte_reader_get_uint8 (reader, &version)) { | ||
| 253 | + GST_ERROR ("Error getting box's version field"); | ||
| 254 | + return FALSE; | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + if (!gst_byte_reader_get_uint24_be (reader, &flags)) { | ||
| 258 | + GST_ERROR ("Error getting box's flags field"); | ||
| 259 | + return FALSE; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + if (version & 0x01) { | ||
| 263 | + gst_byte_reader_get_uint64_be (reader, &absolute_time); | ||
| 264 | + gst_byte_reader_get_uint64_be (reader, &absolute_duration); | ||
| 265 | + } else { | ||
| 266 | + guint32 time = 0; | ||
| 267 | + guint32 duration = 0; | ||
| 268 | + gst_byte_reader_get_uint32_be (reader, &time); | ||
| 269 | + gst_byte_reader_get_uint32_be (reader, &duration); | ||
| 270 | + time = ~time; | ||
| 271 | + duration = ~duration; | ||
| 272 | + absolute_time = ~time; | ||
| 273 | + absolute_duration = ~duration; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + parser->tfxd.time = absolute_time; | ||
| 277 | + parser->tfxd.duration = absolute_duration; | ||
| 278 | + GST_LOG ("tfxd box parsed"); | ||
| 279 | + return TRUE; | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +gboolean | ||
| 283 | +gst_mss_fragment_parser_add_buffer (GstMssFragmentParser * parser, | ||
| 284 | + GstBuffer * buffer) | ||
| 285 | +{ | ||
| 286 | + GstByteReader reader; | ||
| 287 | + GstMapInfo info; | ||
| 288 | + guint32 size; | ||
| 289 | + guint32 fourcc; | ||
| 290 | + const guint8 *uuid; | ||
| 291 | + gboolean error = FALSE; | ||
| 292 | + gboolean mdat_box_found = FALSE; | ||
| 293 | + | ||
| 294 | + static const guint8 tfrf_uuid[] = { | ||
| 295 | + 0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95, | ||
| 296 | + 0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f | ||
| 297 | + }; | ||
| 298 | + | ||
| 299 | + static const guint8 tfxd_uuid[] = { | ||
| 300 | + 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, | ||
| 301 | + 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 | ||
| 302 | + }; | ||
| 303 | + | ||
| 304 | + static const guint8 piff_uuid[] = { | ||
| 305 | + 0xa2, 0x39, 0x4f, 0x52, 0x5a, 0x9b, 0x4f, 0x14, | ||
| 306 | + 0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4 | ||
| 307 | + }; | ||
| 308 | + | ||
| 309 | + if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { | ||
| 310 | + return FALSE; | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + gst_byte_reader_init (&reader, info.data, info.size); | ||
| 314 | + GST_TRACE ("Total buffer size: %u", gst_byte_reader_get_size (&reader)); | ||
| 315 | + | ||
| 316 | + size = gst_byte_reader_get_uint32_be_unchecked (&reader); | ||
| 317 | + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); | ||
| 318 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_MOOF) { | ||
| 319 | + GST_TRACE ("moof box found"); | ||
| 320 | + size = gst_byte_reader_get_uint32_be_unchecked (&reader); | ||
| 321 | + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); | ||
| 322 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_MFHD) { | ||
| 323 | + gst_byte_reader_skip_unchecked (&reader, size - 8); | ||
| 324 | + | ||
| 325 | + size = gst_byte_reader_get_uint32_be_unchecked (&reader); | ||
| 326 | + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); | ||
| 327 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_TRAF) { | ||
| 328 | + size = gst_byte_reader_get_uint32_be_unchecked (&reader); | ||
| 329 | + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); | ||
| 330 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_TFHD) { | ||
| 331 | + gst_byte_reader_skip_unchecked (&reader, size - 8); | ||
| 332 | + | ||
| 333 | + size = gst_byte_reader_get_uint32_be_unchecked (&reader); | ||
| 334 | + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); | ||
| 335 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_TRUN) { | ||
| 336 | + GST_TRACE ("trun box found, size: %" G_GUINT32_FORMAT, size); | ||
| 337 | + if (!gst_byte_reader_skip (&reader, size - 8)) { | ||
| 338 | + GST_WARNING ("Failed to skip trun box, enough data?"); | ||
| 339 | + error = TRUE; | ||
| 340 | + goto beach; | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + } | ||
| 344 | + } | ||
| 345 | + } | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + while (!mdat_box_found) { | ||
| 349 | + GST_TRACE ("remaining data: %u", gst_byte_reader_get_remaining (&reader)); | ||
| 350 | + if (!gst_byte_reader_get_uint32_be (&reader, &size)) { | ||
| 351 | + GST_WARNING ("Failed to get box size, enough data?"); | ||
| 352 | + error = TRUE; | ||
| 353 | + break; | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + GST_TRACE ("box size: %" G_GUINT32_FORMAT, size); | ||
| 357 | + if (!gst_byte_reader_get_uint32_le (&reader, &fourcc)) { | ||
| 358 | + GST_WARNING ("Failed to get fourcc, enough data?"); | ||
| 359 | + error = TRUE; | ||
| 360 | + break; | ||
| 361 | + } | ||
| 362 | + | ||
| 363 | + if (fourcc == GST_MSS_FRAGMENT_FOURCC_MDAT) { | ||
| 364 | + GST_LOG ("mdat box found"); | ||
| 365 | + mdat_box_found = TRUE; | ||
| 366 | + break; | ||
| 367 | + } | ||
| 368 | + | ||
| 369 | + if (fourcc != GST_MSS_FRAGMENT_FOURCC_UUID) { | ||
| 370 | + GST_ERROR ("invalid UUID fourcc: %" GST_FOURCC_FORMAT, | ||
| 371 | + GST_FOURCC_ARGS (fourcc)); | ||
| 372 | + error = TRUE; | ||
| 373 | + break; | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + if (!gst_byte_reader_peek_data (&reader, 16, &uuid)) { | ||
| 377 | + GST_ERROR ("not enough data in UUID box"); | ||
| 378 | + error = TRUE; | ||
| 379 | + break; | ||
| 380 | + } | ||
| 381 | + | ||
| 382 | + if (memcmp (uuid, piff_uuid, 16) == 0) { | ||
| 383 | + gst_byte_reader_skip_unchecked (&reader, size - 8); | ||
| 384 | + GST_LOG ("piff box detected"); | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + if (memcmp (uuid, tfrf_uuid, 16) == 0) { | ||
| 388 | + gst_byte_reader_get_data (&reader, 16, &uuid); | ||
| 389 | + if (!_parse_tfrf_box (parser, &reader)) { | ||
| 390 | + GST_ERROR ("txrf box parsing error"); | ||
| 391 | + error = TRUE; | ||
| 392 | + break; | ||
| 393 | + } | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + if (memcmp (uuid, tfxd_uuid, 16) == 0) { | ||
| 397 | + gst_byte_reader_get_data (&reader, 16, &uuid); | ||
| 398 | + if (!_parse_tfxd_box (parser, &reader)) { | ||
| 399 | + GST_ERROR ("tfrf box parsing error"); | ||
| 400 | + error = TRUE; | ||
| 401 | + break; | ||
| 402 | + } | ||
| 403 | + } | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | +beach: | ||
| 407 | + | ||
| 408 | + if (!error) | ||
| 409 | + parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_FINISHED; | ||
| 410 | + | ||
| 411 | + GST_LOG ("Fragment parsing successful: %s", error ? "no" : "yes"); | ||
| 412 | + gst_buffer_unmap (buffer, &info); | ||
| 413 | + return !error; | ||
| 414 | +} | ||
| 415 | diff --git a/ext/smoothstreaming/gstmssfragmentparser.h b/ext/smoothstreaming/gstmssfragmentparser.h | ||
| 416 | new file mode 100644 | ||
| 417 | index 000000000..cf4711865 | ||
| 418 | --- /dev/null | ||
| 419 | +++ b/ext/smoothstreaming/gstmssfragmentparser.h | ||
| 420 | @@ -0,0 +1,84 @@ | ||
| 421 | +/* | ||
| 422 | + * Microsoft Smooth-Streaming fragment parsing library | ||
| 423 | + * | ||
| 424 | + * gstmssfragmentparser.h | ||
| 425 | + * | ||
| 426 | + * Copyright (C) 2016 Igalia S.L | ||
| 427 | + * Copyright (C) 2016 Metrological | ||
| 428 | + * Author: Philippe Normand <philn@igalia.com> | ||
| 429 | + * | ||
| 430 | + * This library is free software; you can redistribute it and/or | ||
| 431 | + * modify it under the terms of the GNU Library General Public | ||
| 432 | + * License as published by the Free Software Foundation; either | ||
| 433 | + * version 2.1 of the License, or (at your option) any later version. | ||
| 434 | + * | ||
| 435 | + * This library is distributed in the hope that it will be useful, | ||
| 436 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 437 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 438 | + * Library General Public License for more details. | ||
| 439 | + * | ||
| 440 | + * You should have received a copy of the GNU Library General Public | ||
| 441 | + * License along with this library (COPYING); if not, write to the | ||
| 442 | + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 443 | + * Boston, MA 02111-1307, USA. | ||
| 444 | + */ | ||
| 445 | + | ||
| 446 | +#ifndef __GST_MSS_FRAGMENT_PARSER_H__ | ||
| 447 | +#define __GST_MSS_FRAGMENT_PARSER_H__ | ||
| 448 | + | ||
| 449 | +#include <gst/gst.h> | ||
| 450 | + | ||
| 451 | +G_BEGIN_DECLS | ||
| 452 | + | ||
| 453 | +#define GST_MSS_FRAGMENT_FOURCC_MOOF GST_MAKE_FOURCC('m','o','o','f') | ||
| 454 | +#define GST_MSS_FRAGMENT_FOURCC_MFHD GST_MAKE_FOURCC('m','f','h','d') | ||
| 455 | +#define GST_MSS_FRAGMENT_FOURCC_TRAF GST_MAKE_FOURCC('t','r','a','f') | ||
| 456 | +#define GST_MSS_FRAGMENT_FOURCC_TFHD GST_MAKE_FOURCC('t','f','h','d') | ||
| 457 | +#define GST_MSS_FRAGMENT_FOURCC_TRUN GST_MAKE_FOURCC('t','r','u','n') | ||
| 458 | +#define GST_MSS_FRAGMENT_FOURCC_UUID GST_MAKE_FOURCC('u','u','i','d') | ||
| 459 | +#define GST_MSS_FRAGMENT_FOURCC_MDAT GST_MAKE_FOURCC('m','d','a','t') | ||
| 460 | + | ||
| 461 | +typedef struct _GstTfxdBox | ||
| 462 | +{ | ||
| 463 | + guint8 version; | ||
| 464 | + guint32 flags; | ||
| 465 | + | ||
| 466 | + guint64 time; | ||
| 467 | + guint64 duration; | ||
| 468 | +} GstTfxdBox; | ||
| 469 | + | ||
| 470 | +typedef struct _GstTfrfBoxEntry | ||
| 471 | +{ | ||
| 472 | + guint64 time; | ||
| 473 | + guint64 duration; | ||
| 474 | +} GstTfrfBoxEntry; | ||
| 475 | + | ||
| 476 | +typedef struct _GstTfrfBox | ||
| 477 | +{ | ||
| 478 | + guint8 version; | ||
| 479 | + guint32 flags; | ||
| 480 | + | ||
| 481 | + gint entries_count; | ||
| 482 | + GstTfrfBoxEntry *entries; | ||
| 483 | +} GstTfrfBox; | ||
| 484 | + | ||
| 485 | +typedef enum _GstFragmentHeaderParserStatus | ||
| 486 | +{ | ||
| 487 | + GST_MSS_FRAGMENT_HEADER_PARSER_INIT, | ||
| 488 | + GST_MSS_FRAGMENT_HEADER_PARSER_FINISHED | ||
| 489 | +} GstFragmentHeaderParserStatus; | ||
| 490 | + | ||
| 491 | +typedef struct _GstMssFragmentParser | ||
| 492 | +{ | ||
| 493 | + GstFragmentHeaderParserStatus status; | ||
| 494 | + GstTfxdBox tfxd; | ||
| 495 | + GstTfrfBox tfrf; | ||
| 496 | +} GstMssFragmentParser; | ||
| 497 | + | ||
| 498 | +void gst_mss_fragment_parser_init (GstMssFragmentParser * parser); | ||
| 499 | +void gst_mss_fragment_parser_clear (GstMssFragmentParser * parser); | ||
| 500 | +gboolean gst_mss_fragment_parser_add_buffer (GstMssFragmentParser * parser, GstBuffer * buf); | ||
| 501 | + | ||
| 502 | +G_END_DECLS | ||
| 503 | + | ||
| 504 | +#endif /* __GST_MSS_FRAGMENT_PARSER_H__ */ | ||
| 505 | diff --git a/ext/smoothstreaming/gstmssmanifest.c b/ext/smoothstreaming/gstmssmanifest.c | ||
| 506 | index 144bbb42d..e1031ba55 100644 | ||
| 507 | --- a/ext/smoothstreaming/gstmssmanifest.c | ||
| 508 | +++ b/ext/smoothstreaming/gstmssmanifest.c | ||
| 509 | @@ -1,5 +1,7 @@ | ||
| 510 | /* GStreamer | ||
| 511 | * Copyright (C) 2012 Smart TV Alliance | ||
| 512 | + * Copyright (C) 2016 Igalia S.L | ||
| 513 | + * Copyright (C) 2016 Metrological | ||
| 514 | * Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd. | ||
| 515 | * | ||
| 516 | * gstmssmanifest.c: | ||
| 517 | @@ -31,6 +33,7 @@ | ||
| 518 | #include <gst/codecparsers/gsth264parser.h> | ||
| 519 | |||
| 520 | #include "gstmssmanifest.h" | ||
| 521 | +#include "gstmssfragmentparser.h" | ||
| 522 | |||
| 523 | GST_DEBUG_CATEGORY_EXTERN (mssdemux_debug); | ||
| 524 | #define GST_CAT_DEFAULT mssdemux_debug | ||
| 525 | @@ -74,12 +77,17 @@ struct _GstMssStream | ||
| 526 | gboolean active; /* if the stream is currently being used */ | ||
| 527 | gint selectedQualityIndex; | ||
| 528 | |||
| 529 | + gboolean has_live_fragments; | ||
| 530 | + GstAdapter *live_adapter; | ||
| 531 | + | ||
| 532 | GList *fragments; | ||
| 533 | GList *qualities; | ||
| 534 | |||
| 535 | gchar *url; | ||
| 536 | gchar *lang; | ||
| 537 | |||
| 538 | + GstMssFragmentParser fragment_parser; | ||
| 539 | + | ||
| 540 | guint fragment_repetition_index; | ||
| 541 | GList *current_fragment; | ||
| 542 | GList *current_quality; | ||
| 543 | @@ -96,6 +104,7 @@ struct _GstMssManifest | ||
| 544 | |||
| 545 | gboolean is_live; | ||
| 546 | gint64 dvr_window; | ||
| 547 | + guint64 look_ahead_fragment_count; | ||
| 548 | |||
| 549 | GString *protection_system_id; | ||
| 550 | gchar *protection_data; | ||
| 551 | @@ -235,7 +244,8 @@ compare_bitrate (GstMssStreamQuality * a, GstMssStreamQuality * b) | ||
| 552 | } | ||
| 553 | |||
| 554 | static void | ||
| 555 | -_gst_mss_stream_init (GstMssStream * stream, xmlNodePtr node) | ||
| 556 | +_gst_mss_stream_init (GstMssManifest * manifest, GstMssStream * stream, | ||
| 557 | + xmlNodePtr node) | ||
| 558 | { | ||
| 559 | xmlNodePtr iter; | ||
| 560 | GstMssFragmentListBuilder builder; | ||
| 561 | @@ -248,9 +258,21 @@ _gst_mss_stream_init (GstMssStream * stream, xmlNodePtr node) | ||
| 562 | stream->url = (gchar *) xmlGetProp (node, (xmlChar *) MSS_PROP_URL); | ||
| 563 | stream->lang = (gchar *) xmlGetProp (node, (xmlChar *) MSS_PROP_LANGUAGE); | ||
| 564 | |||
| 565 | + /* for live playback each fragment usually has timing | ||
| 566 | + * information for the few next look-ahead fragments so the | ||
| 567 | + * playlist can be built incrementally from the first fragment | ||
| 568 | + * of the manifest. | ||
| 569 | + */ | ||
| 570 | + | ||
| 571 | + GST_DEBUG ("Live stream: %s, look-ahead fragments: %" G_GUINT64_FORMAT, | ||
| 572 | + manifest->is_live ? "yes" : "no", manifest->look_ahead_fragment_count); | ||
| 573 | + stream->has_live_fragments = manifest->is_live | ||
| 574 | + && manifest->look_ahead_fragment_count; | ||
| 575 | + | ||
| 576 | for (iter = node->children; iter; iter = iter->next) { | ||
| 577 | if (node_has_type (iter, MSS_NODE_STREAM_FRAGMENT)) { | ||
| 578 | - gst_mss_fragment_list_builder_add (&builder, iter); | ||
| 579 | + if (!stream->has_live_fragments || !builder.fragments) | ||
| 580 | + gst_mss_fragment_list_builder_add (&builder, iter); | ||
| 581 | } else if (node_has_type (iter, MSS_NODE_STREAM_QUALITY)) { | ||
| 582 | GstMssStreamQuality *quality = gst_mss_stream_quality_new (iter); | ||
| 583 | stream->qualities = g_list_prepend (stream->qualities, quality); | ||
| 584 | @@ -259,17 +281,24 @@ _gst_mss_stream_init (GstMssStream * stream, xmlNodePtr node) | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | - stream->fragments = g_list_reverse (builder.fragments); | ||
| 589 | + if (stream->has_live_fragments) { | ||
| 590 | + stream->live_adapter = gst_adapter_new (); | ||
| 591 | + } | ||
| 592 | + | ||
| 593 | + if (builder.fragments) { | ||
| 594 | + stream->fragments = g_list_reverse (builder.fragments); | ||
| 595 | + stream->current_fragment = stream->fragments; | ||
| 596 | + } | ||
| 597 | |||
| 598 | /* order them from smaller to bigger based on bitrates */ | ||
| 599 | stream->qualities = | ||
| 600 | g_list_sort (stream->qualities, (GCompareFunc) compare_bitrate); | ||
| 601 | - | ||
| 602 | - stream->current_fragment = stream->fragments; | ||
| 603 | stream->current_quality = stream->qualities; | ||
| 604 | |||
| 605 | stream->regex_bitrate = g_regex_new ("\\{[Bb]itrate\\}", 0, 0, NULL); | ||
| 606 | stream->regex_position = g_regex_new ("\\{start[ _]time\\}", 0, 0, NULL); | ||
| 607 | + | ||
| 608 | + gst_mss_fragment_parser_init (&stream->fragment_parser); | ||
| 609 | } | ||
| 610 | |||
| 611 | |||
| 612 | @@ -315,6 +344,7 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 613 | xmlNodePtr nodeiter; | ||
| 614 | gchar *live_str; | ||
| 615 | GstMapInfo mapinfo; | ||
| 616 | + gchar *look_ahead_fragment_count_str; | ||
| 617 | |||
| 618 | if (!gst_buffer_map (data, &mapinfo, GST_MAP_READ)) { | ||
| 619 | return NULL; | ||
| 620 | @@ -335,6 +365,7 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 621 | /* the entire file is always available for non-live streams */ | ||
| 622 | if (!manifest->is_live) { | ||
| 623 | manifest->dvr_window = 0; | ||
| 624 | + manifest->look_ahead_fragment_count = 0; | ||
| 625 | } else { | ||
| 626 | /* if 0, or non-existent, the length is infinite */ | ||
| 627 | gchar *dvr_window_str = (gchar *) xmlGetProp (root, | ||
| 628 | @@ -346,6 +377,17 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 629 | manifest->dvr_window = 0; | ||
| 630 | } | ||
| 631 | } | ||
| 632 | + | ||
| 633 | + look_ahead_fragment_count_str = | ||
| 634 | + (gchar *) xmlGetProp (root, (xmlChar *) "LookAheadFragmentCount"); | ||
| 635 | + if (look_ahead_fragment_count_str) { | ||
| 636 | + manifest->look_ahead_fragment_count = | ||
| 637 | + g_ascii_strtoull (look_ahead_fragment_count_str, NULL, 10); | ||
| 638 | + xmlFree (look_ahead_fragment_count_str); | ||
| 639 | + if (manifest->look_ahead_fragment_count <= 0) { | ||
| 640 | + manifest->look_ahead_fragment_count = 0; | ||
| 641 | + } | ||
| 642 | + } | ||
| 643 | } | ||
| 644 | |||
| 645 | for (nodeiter = root->children; nodeiter; nodeiter = nodeiter->next) { | ||
| 646 | @@ -354,7 +396,7 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 647 | GstMssStream *stream = g_new0 (GstMssStream, 1); | ||
| 648 | |||
| 649 | manifest->streams = g_slist_append (manifest->streams, stream); | ||
| 650 | - _gst_mss_stream_init (stream, nodeiter); | ||
| 651 | + _gst_mss_stream_init (manifest, stream, nodeiter); | ||
| 652 | } | ||
| 653 | |||
| 654 | if (nodeiter->type == XML_ELEMENT_NODE | ||
| 655 | @@ -371,6 +413,11 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 656 | static void | ||
| 657 | gst_mss_stream_free (GstMssStream * stream) | ||
| 658 | { | ||
| 659 | + if (stream->live_adapter) { | ||
| 660 | + gst_adapter_clear (stream->live_adapter); | ||
| 661 | + g_object_unref (stream->live_adapter); | ||
| 662 | + } | ||
| 663 | + | ||
| 664 | g_list_free_full (stream->fragments, g_free); | ||
| 665 | g_list_free_full (stream->qualities, | ||
| 666 | (GDestroyNotify) gst_mss_stream_quality_free); | ||
| 667 | @@ -379,6 +426,7 @@ gst_mss_stream_free (GstMssStream * stream) | ||
| 668 | g_regex_unref (stream->regex_position); | ||
| 669 | g_regex_unref (stream->regex_bitrate); | ||
| 670 | g_free (stream); | ||
| 671 | + gst_mss_fragment_parser_clear (&stream->fragment_parser); | ||
| 672 | } | ||
| 673 | |||
| 674 | void | ||
| 675 | @@ -1079,6 +1127,9 @@ GstFlowReturn | ||
| 676 | gst_mss_stream_advance_fragment (GstMssStream * stream) | ||
| 677 | { | ||
| 678 | GstMssStreamFragment *fragment; | ||
| 679 | + const gchar *stream_type_name = | ||
| 680 | + gst_mss_stream_type_name (gst_mss_stream_get_type (stream)); | ||
| 681 | + | ||
| 682 | g_return_val_if_fail (stream->active, GST_FLOW_ERROR); | ||
| 683 | |||
| 684 | if (stream->current_fragment == NULL) | ||
| 685 | @@ -1086,14 +1137,20 @@ gst_mss_stream_advance_fragment (GstMssStream * stream) | ||
| 686 | |||
| 687 | fragment = stream->current_fragment->data; | ||
| 688 | stream->fragment_repetition_index++; | ||
| 689 | - if (stream->fragment_repetition_index < fragment->repetitions) { | ||
| 690 | - return GST_FLOW_OK; | ||
| 691 | - } | ||
| 692 | + if (stream->fragment_repetition_index < fragment->repetitions) | ||
| 693 | + goto beach; | ||
| 694 | |||
| 695 | stream->fragment_repetition_index = 0; | ||
| 696 | stream->current_fragment = g_list_next (stream->current_fragment); | ||
| 697 | + | ||
| 698 | + GST_DEBUG ("Advanced to fragment #%d on %s stream", fragment->number, | ||
| 699 | + stream_type_name); | ||
| 700 | if (stream->current_fragment == NULL) | ||
| 701 | return GST_FLOW_EOS; | ||
| 702 | + | ||
| 703 | +beach: | ||
| 704 | + gst_mss_fragment_parser_clear (&stream->fragment_parser); | ||
| 705 | + gst_mss_fragment_parser_init (&stream->fragment_parser); | ||
| 706 | return GST_FLOW_OK; | ||
| 707 | } | ||
| 708 | |||
| 709 | @@ -1173,6 +1230,11 @@ gst_mss_stream_seek (GstMssStream * stream, gboolean forward, | ||
| 710 | GST_DEBUG ("Stream %s seeking to %" G_GUINT64_FORMAT, stream->url, time); | ||
| 711 | for (iter = stream->fragments; iter; iter = g_list_next (iter)) { | ||
| 712 | fragment = iter->data; | ||
| 713 | + if (stream->has_live_fragments) { | ||
| 714 | + if (fragment->time + fragment->repetitions * fragment->duration > time) | ||
| 715 | + stream->current_fragment = iter; | ||
| 716 | + break; | ||
| 717 | + } | ||
| 718 | if (fragment->time + fragment->repetitions * fragment->duration > time) { | ||
| 719 | stream->current_fragment = iter; | ||
| 720 | stream->fragment_repetition_index = | ||
| 721 | @@ -1256,9 +1318,14 @@ static void | ||
| 722 | gst_mss_stream_reload_fragments (GstMssStream * stream, xmlNodePtr streamIndex) | ||
| 723 | { | ||
| 724 | xmlNodePtr iter; | ||
| 725 | - guint64 current_gst_time = gst_mss_stream_get_fragment_gst_timestamp (stream); | ||
| 726 | + guint64 current_gst_time; | ||
| 727 | GstMssFragmentListBuilder builder; | ||
| 728 | |||
| 729 | + if (stream->has_live_fragments) | ||
| 730 | + return; | ||
| 731 | + | ||
| 732 | + current_gst_time = gst_mss_stream_get_fragment_gst_timestamp (stream); | ||
| 733 | + | ||
| 734 | gst_mss_fragment_list_builder_init (&builder); | ||
| 735 | |||
| 736 | GST_DEBUG ("Current position: %" GST_TIME_FORMAT, | ||
| 737 | @@ -1514,3 +1581,74 @@ gst_mss_manifest_get_live_seek_range (GstMssManifest * manifest, gint64 * start, | ||
| 738 | |||
| 739 | return ret; | ||
| 740 | } | ||
| 741 | + | ||
| 742 | +void | ||
| 743 | +gst_mss_manifest_live_adapter_push (GstMssStream * stream, GstBuffer * buffer) | ||
| 744 | +{ | ||
| 745 | + gst_adapter_push (stream->live_adapter, buffer); | ||
| 746 | +} | ||
| 747 | + | ||
| 748 | +gsize | ||
| 749 | +gst_mss_manifest_live_adapter_available (GstMssStream * stream) | ||
| 750 | +{ | ||
| 751 | + return gst_adapter_available (stream->live_adapter); | ||
| 752 | +} | ||
| 753 | + | ||
| 754 | +GstBuffer * | ||
| 755 | +gst_mss_manifest_live_adapter_take_buffer (GstMssStream * stream, gsize nbytes) | ||
| 756 | +{ | ||
| 757 | + return gst_adapter_take_buffer (stream->live_adapter, nbytes); | ||
| 758 | +} | ||
| 759 | + | ||
| 760 | +gboolean | ||
| 761 | +gst_mss_stream_fragment_parsing_needed (GstMssStream * stream) | ||
| 762 | +{ | ||
| 763 | + return stream->fragment_parser.status == GST_MSS_FRAGMENT_HEADER_PARSER_INIT; | ||
| 764 | +} | ||
| 765 | + | ||
| 766 | +void | ||
| 767 | +gst_mss_stream_parse_fragment (GstMssStream * stream, GstBuffer * buffer) | ||
| 768 | +{ | ||
| 769 | + GstMssStreamFragment *current_fragment = NULL; | ||
| 770 | + const gchar *stream_type_name; | ||
| 771 | + guint8 index; | ||
| 772 | + | ||
| 773 | + if (!stream->has_live_fragments) | ||
| 774 | + return; | ||
| 775 | + | ||
| 776 | + if (!gst_mss_fragment_parser_add_buffer (&stream->fragment_parser, buffer)) | ||
| 777 | + return; | ||
| 778 | + | ||
| 779 | + current_fragment = stream->current_fragment->data; | ||
| 780 | + current_fragment->time = stream->fragment_parser.tfxd.time; | ||
| 781 | + current_fragment->duration = stream->fragment_parser.tfxd.duration; | ||
| 782 | + | ||
| 783 | + stream_type_name = | ||
| 784 | + gst_mss_stream_type_name (gst_mss_stream_get_type (stream)); | ||
| 785 | + | ||
| 786 | + for (index = 0; index < stream->fragment_parser.tfrf.entries_count; index++) { | ||
| 787 | + GList *l = g_list_last (stream->fragments); | ||
| 788 | + GstMssStreamFragment *last; | ||
| 789 | + GstMssStreamFragment *fragment; | ||
| 790 | + | ||
| 791 | + if (l == NULL) | ||
| 792 | + break; | ||
| 793 | + | ||
| 794 | + last = (GstMssStreamFragment *) l->data; | ||
| 795 | + | ||
| 796 | + if (last->time == stream->fragment_parser.tfrf.entries[index].time) | ||
| 797 | + continue; | ||
| 798 | + | ||
| 799 | + fragment = g_new (GstMssStreamFragment, 1); | ||
| 800 | + fragment->number = last->number + 1; | ||
| 801 | + fragment->repetitions = 1; | ||
| 802 | + fragment->time = stream->fragment_parser.tfrf.entries[index].time; | ||
| 803 | + fragment->duration = stream->fragment_parser.tfrf.entries[index].duration; | ||
| 804 | + | ||
| 805 | + stream->fragments = g_list_append (stream->fragments, fragment); | ||
| 806 | + GST_LOG ("Adding fragment number: %u to %s stream, time: %" G_GUINT64_FORMAT | ||
| 807 | + ", duration: %" G_GUINT64_FORMAT ", repetitions: %u", | ||
| 808 | + fragment->number, stream_type_name, | ||
| 809 | + fragment->time, fragment->duration, fragment->repetitions); | ||
| 810 | + } | ||
| 811 | +} | ||
| 812 | diff --git a/ext/smoothstreaming/gstmssmanifest.h b/ext/smoothstreaming/gstmssmanifest.h | ||
| 813 | index 6b7b1f971..03b066ae5 100644 | ||
| 814 | --- a/ext/smoothstreaming/gstmssmanifest.h | ||
| 815 | +++ b/ext/smoothstreaming/gstmssmanifest.h | ||
| 816 | @@ -26,6 +26,7 @@ | ||
| 817 | #include <glib.h> | ||
| 818 | #include <gio/gio.h> | ||
| 819 | #include <gst/gst.h> | ||
| 820 | +#include <gst/base/gstadapter.h> | ||
| 821 | |||
| 822 | G_BEGIN_DECLS | ||
| 823 | |||
| 824 | @@ -73,5 +74,11 @@ const gchar * gst_mss_stream_get_lang (GstMssStream * stream); | ||
| 825 | |||
| 826 | const gchar * gst_mss_stream_type_name (GstMssStreamType streamtype); | ||
| 827 | |||
| 828 | +void gst_mss_manifest_live_adapter_push(GstMssStream * stream, GstBuffer * buffer); | ||
| 829 | +gsize gst_mss_manifest_live_adapter_available(GstMssStream * stream); | ||
| 830 | +GstBuffer * gst_mss_manifest_live_adapter_take_buffer(GstMssStream * stream, gsize nbytes); | ||
| 831 | +gboolean gst_mss_stream_fragment_parsing_needed(GstMssStream * stream); | ||
| 832 | +void gst_mss_stream_parse_fragment(GstMssStream * stream, GstBuffer * buffer); | ||
| 833 | + | ||
| 834 | G_END_DECLS | ||
| 835 | #endif /* __GST_MSS_MANIFEST_H__ */ | ||
| 836 | diff --git a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c | ||
| 837 | index 634e4f388..ddca726b6 100644 | ||
| 838 | --- a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c | ||
| 839 | +++ b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c | ||
| 840 | @@ -291,6 +291,9 @@ gst_adaptive_demux_wait_until (GstClock * clock, GCond * cond, GMutex * mutex, | ||
| 841 | GstClockTime end_time); | ||
| 842 | static gboolean gst_adaptive_demux_clock_callback (GstClock * clock, | ||
| 843 | GstClockTime time, GstClockID id, gpointer user_data); | ||
| 844 | +static gboolean | ||
| 845 | +gst_adaptive_demux_requires_periodical_playlist_update_default (GstAdaptiveDemux | ||
| 846 | + * demux); | ||
| 847 | |||
| 848 | /* we can't use G_DEFINE_ABSTRACT_TYPE because we need the klass in the _init | ||
| 849 | * method to get to the padtemplates */ | ||
| 850 | @@ -412,6 +415,9 @@ gst_adaptive_demux_class_init (GstAdaptiveDemuxClass * klass) | ||
| 851 | klass->data_received = gst_adaptive_demux_stream_data_received_default; | ||
| 852 | klass->finish_fragment = gst_adaptive_demux_stream_finish_fragment_default; | ||
| 853 | klass->update_manifest = gst_adaptive_demux_update_manifest_default; | ||
| 854 | + klass->requires_periodical_playlist_update = | ||
| 855 | + gst_adaptive_demux_requires_periodical_playlist_update_default; | ||
| 856 | + | ||
| 857 | } | ||
| 858 | |||
| 859 | static void | ||
| 860 | @@ -686,7 +692,9 @@ gst_adaptive_demux_sink_event (GstPad * pad, GstObject * parent, | ||
| 861 | demux->priv->stop_updates_task = FALSE; | ||
| 862 | g_mutex_unlock (&demux->priv->updates_timed_lock); | ||
| 863 | /* Task to periodically update the manifest */ | ||
| 864 | - gst_task_start (demux->priv->updates_task); | ||
| 865 | + if (demux_class->requires_periodical_playlist_update (demux)) { | ||
| 866 | + gst_task_start (demux->priv->updates_task); | ||
| 867 | + } | ||
| 868 | } | ||
| 869 | } else { | ||
| 870 | /* no streams */ | ||
| 871 | @@ -2125,6 +2133,13 @@ gst_adaptive_demux_stream_data_received_default (GstAdaptiveDemux * demux, | ||
| 872 | return gst_adaptive_demux_stream_push_buffer (stream, buffer); | ||
| 873 | } | ||
| 874 | |||
| 875 | +static gboolean | ||
| 876 | +gst_adaptive_demux_requires_periodical_playlist_update_default (GstAdaptiveDemux | ||
| 877 | + * demux) | ||
| 878 | +{ | ||
| 879 | + return TRUE; | ||
| 880 | +} | ||
| 881 | + | ||
| 882 | static GstFlowReturn | ||
| 883 | _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) | ||
| 884 | { | ||
| 885 | @@ -3338,7 +3353,15 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream) | ||
| 886 | GST_DEBUG_OBJECT (stream->pad, "EOS, checking to stop download loop"); | ||
| 887 | /* we push the EOS after releasing the object lock */ | ||
| 888 | if (gst_adaptive_demux_is_live (demux)) { | ||
| 889 | - if (gst_adaptive_demux_stream_wait_manifest_update (demux, stream)) { | ||
| 890 | + GstAdaptiveDemuxClass *demux_class = | ||
| 891 | + GST_ADAPTIVE_DEMUX_GET_CLASS (demux); | ||
| 892 | + | ||
| 893 | + /* this might be a fragment download error, refresh the manifest, just in case */ | ||
| 894 | + if (!demux_class->requires_periodical_playlist_update (demux)) { | ||
| 895 | + ret = gst_adaptive_demux_update_manifest (demux); | ||
| 896 | + break; | ||
| 897 | + } else if (gst_adaptive_demux_stream_wait_manifest_update (demux, | ||
| 898 | + stream)) { | ||
| 899 | goto end; | ||
| 900 | } | ||
| 901 | gst_task_stop (stream->download_task); | ||
| 902 | diff --git a/gst-libs/gst/adaptivedemux/gstadaptivedemux.h b/gst-libs/gst/adaptivedemux/gstadaptivedemux.h | ||
| 903 | index 780f4d93f..9a1a1b7d1 100644 | ||
| 904 | --- a/gst-libs/gst/adaptivedemux/gstadaptivedemux.h | ||
| 905 | +++ b/gst-libs/gst/adaptivedemux/gstadaptivedemux.h | ||
| 906 | @@ -459,6 +459,20 @@ struct _GstAdaptiveDemuxClass | ||
| 907 | * selected period. | ||
| 908 | */ | ||
| 909 | GstClockTime (*get_period_start_time) (GstAdaptiveDemux *demux); | ||
| 910 | + | ||
| 911 | + /** | ||
| 912 | + * requires_periodical_playlist_update: | ||
| 913 | + * @demux: #GstAdaptiveDemux | ||
| 914 | + * | ||
| 915 | + * Some adaptive streaming protocols allow the client to download | ||
| 916 | + * the playlist once and build up the fragment list based on the | ||
| 917 | + * current fragment metadata. For those protocols the demuxer | ||
| 918 | + * doesn't need to periodically refresh the playlist. This vfunc | ||
| 919 | + * is relevant only for live playback scenarios. | ||
| 920 | + * | ||
| 921 | + * Return: %TRUE if the playlist needs to be refreshed periodically by the demuxer. | ||
| 922 | + */ | ||
| 923 | + gboolean (*requires_periodical_playlist_update) (GstAdaptiveDemux * demux); | ||
| 924 | }; | ||
| 925 | |||
| 926 | GType gst_adaptive_demux_get_type (void); | ||
| 927 | -- | ||
| 928 | 2.11.0 | ||
| 929 | |||
diff --git a/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-implement-adaptivedemux-s-get_live_s.patch b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-implement-adaptivedemux-s-get_live_s.patch new file mode 100644 index 0000000000..76d29e151b --- /dev/null +++ b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-implement-adaptivedemux-s-get_live_s.patch | |||
| @@ -0,0 +1,183 @@ | |||
| 1 | From e9178fa082116d4bf733b184a8b6951112c17900 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Matthew Waters <matthew@centricular.com> | ||
| 3 | Date: Thu, 10 Nov 2016 17:18:36 +1100 | ||
| 4 | Subject: [PATCH] smoothstreaming: implement adaptivedemux's | ||
| 5 | get_live_seek_range() | ||
| 6 | |||
| 7 | Allows seeking through the available fragments that are still available | ||
| 8 | on the server as specified by the DVRWindowLength attribute in the | ||
| 9 | manifest. | ||
| 10 | |||
| 11 | https://bugzilla.gnome.org/show_bug.cgi?id=774178 | ||
| 12 | --- | ||
| 13 | Upstream-Status: Backport | ||
| 14 | Signed-off-by: Khem Raj <raj.khem@gmail.com> | ||
| 15 | |||
| 16 | ext/smoothstreaming/gstmssdemux.c | 13 ++++++ | ||
| 17 | ext/smoothstreaming/gstmssmanifest.c | 84 ++++++++++++++++++++++++++++++++++++ | ||
| 18 | ext/smoothstreaming/gstmssmanifest.h | 1 + | ||
| 19 | 3 files changed, 98 insertions(+) | ||
| 20 | |||
| 21 | diff --git a/ext/smoothstreaming/gstmssdemux.c b/ext/smoothstreaming/gstmssdemux.c | ||
| 22 | index 9d0aece2b..b66e19514 100644 | ||
| 23 | --- a/ext/smoothstreaming/gstmssdemux.c | ||
| 24 | +++ b/ext/smoothstreaming/gstmssdemux.c | ||
| 25 | @@ -138,6 +138,8 @@ gst_mss_demux_get_manifest_update_interval (GstAdaptiveDemux * demux); | ||
| 26 | static GstFlowReturn | ||
| 27 | gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux, | ||
| 28 | GstBuffer * buffer); | ||
| 29 | +static gboolean gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux, | ||
| 30 | + gint64 * start, gint64 * stop); | ||
| 31 | |||
| 32 | static void | ||
| 33 | gst_mss_demux_class_init (GstMssDemuxClass * klass) | ||
| 34 | @@ -192,6 +194,8 @@ gst_mss_demux_class_init (GstMssDemuxClass * klass) | ||
| 35 | gst_mss_demux_stream_update_fragment_info; | ||
| 36 | gstadaptivedemux_class->update_manifest_data = | ||
| 37 | gst_mss_demux_update_manifest_data; | ||
| 38 | + gstadaptivedemux_class->get_live_seek_range = | ||
| 39 | + gst_mss_demux_get_live_seek_range; | ||
| 40 | |||
| 41 | GST_DEBUG_CATEGORY_INIT (mssdemux_debug, "mssdemux", 0, "mssdemux plugin"); | ||
| 42 | } | ||
| 43 | @@ -659,3 +663,12 @@ gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux, | ||
| 44 | gst_mss_manifest_reload_fragments (mssdemux->manifest, buffer); | ||
| 45 | return GST_FLOW_OK; | ||
| 46 | } | ||
| 47 | + | ||
| 48 | +static gboolean | ||
| 49 | +gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, | ||
| 50 | + gint64 * stop) | ||
| 51 | +{ | ||
| 52 | + GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux); | ||
| 53 | + | ||
| 54 | + return gst_mss_manifest_get_live_seek_range (mssdemux->manifest, start, stop); | ||
| 55 | +} | ||
| 56 | diff --git a/ext/smoothstreaming/gstmssmanifest.c b/ext/smoothstreaming/gstmssmanifest.c | ||
| 57 | index 1b72e8de1..317b3cef9 100644 | ||
| 58 | --- a/ext/smoothstreaming/gstmssmanifest.c | ||
| 59 | +++ b/ext/smoothstreaming/gstmssmanifest.c | ||
| 60 | @@ -42,6 +42,7 @@ GST_DEBUG_CATEGORY_EXTERN (mssdemux_debug); | ||
| 61 | |||
| 62 | #define MSS_PROP_BITRATE "Bitrate" | ||
| 63 | #define MSS_PROP_DURATION "d" | ||
| 64 | +#define MSS_PROP_DVR_WINDOW_LENGTH "DVRWindowLength" | ||
| 65 | #define MSS_PROP_LANGUAGE "Language" | ||
| 66 | #define MSS_PROP_NUMBER "n" | ||
| 67 | #define MSS_PROP_REPETITIONS "r" | ||
| 68 | @@ -94,6 +95,7 @@ struct _GstMssManifest | ||
| 69 | xmlNodePtr xmlrootnode; | ||
| 70 | |||
| 71 | gboolean is_live; | ||
| 72 | + gint64 dvr_window; | ||
| 73 | |||
| 74 | GString *protection_system_id; | ||
| 75 | gchar *protection_data; | ||
| 76 | @@ -330,6 +332,22 @@ gst_mss_manifest_new (GstBuffer * data) | ||
| 77 | xmlFree (live_str); | ||
| 78 | } | ||
| 79 | |||
| 80 | + /* the entire file is always available for non-live streams */ | ||
| 81 | + if (!manifest->is_live) { | ||
| 82 | + manifest->dvr_window = 0; | ||
| 83 | + } else { | ||
| 84 | + /* if 0, or non-existent, the length is infinite */ | ||
| 85 | + gchar *dvr_window_str = (gchar *) xmlGetProp (root, | ||
| 86 | + (xmlChar *) MSS_PROP_DVR_WINDOW_LENGTH); | ||
| 87 | + if (dvr_window_str) { | ||
| 88 | + manifest->dvr_window = g_ascii_strtoull (dvr_window_str, NULL, 10); | ||
| 89 | + xmlFree (dvr_window_str); | ||
| 90 | + if (manifest->dvr_window <= 0) { | ||
| 91 | + manifest->dvr_window = 0; | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | for (nodeiter = root->children; nodeiter; nodeiter = nodeiter->next) { | ||
| 97 | if (nodeiter->type == XML_ELEMENT_NODE | ||
| 98 | && (strcmp ((const char *) nodeiter->name, "StreamIndex") == 0)) { | ||
| 99 | @@ -1406,3 +1424,69 @@ gst_mss_stream_get_lang (GstMssStream * stream) | ||
| 100 | { | ||
| 101 | return stream->lang; | ||
| 102 | } | ||
| 103 | + | ||
| 104 | +static GstClockTime | ||
| 105 | +gst_mss_manifest_get_dvr_window_length_clock_time (GstMssManifest * manifest) | ||
| 106 | +{ | ||
| 107 | + gint64 timescale; | ||
| 108 | + | ||
| 109 | + /* the entire file is always available for non-live streams */ | ||
| 110 | + if (manifest->dvr_window == 0) | ||
| 111 | + return GST_CLOCK_TIME_NONE; | ||
| 112 | + | ||
| 113 | + timescale = gst_mss_manifest_get_timescale (manifest); | ||
| 114 | + return (GstClockTime) gst_util_uint64_scale_round (manifest->dvr_window, | ||
| 115 | + GST_SECOND, timescale); | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +static gboolean | ||
| 119 | +gst_mss_stream_get_live_seek_range (GstMssStream * stream, gint64 * start, | ||
| 120 | + gint64 * stop) | ||
| 121 | +{ | ||
| 122 | + GList *l; | ||
| 123 | + GstMssStreamFragment *fragment; | ||
| 124 | + guint64 timescale = gst_mss_stream_get_timescale (stream); | ||
| 125 | + | ||
| 126 | + g_return_val_if_fail (stream->active, FALSE); | ||
| 127 | + | ||
| 128 | + /* XXX: assumes all the data in the stream is still available */ | ||
| 129 | + l = g_list_first (stream->fragments); | ||
| 130 | + fragment = (GstMssStreamFragment *) l->data; | ||
| 131 | + *start = gst_util_uint64_scale_round (fragment->time, GST_SECOND, timescale); | ||
| 132 | + | ||
| 133 | + l = g_list_last (stream->fragments); | ||
| 134 | + fragment = (GstMssStreamFragment *) l->data; | ||
| 135 | + *stop = gst_util_uint64_scale_round (fragment->time + fragment->duration * | ||
| 136 | + fragment->repetitions, GST_SECOND, timescale); | ||
| 137 | + | ||
| 138 | + return TRUE; | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +gboolean | ||
| 142 | +gst_mss_manifest_get_live_seek_range (GstMssManifest * manifest, gint64 * start, | ||
| 143 | + gint64 * stop) | ||
| 144 | +{ | ||
| 145 | + GSList *iter; | ||
| 146 | + gboolean ret = FALSE; | ||
| 147 | + | ||
| 148 | + for (iter = manifest->streams; iter; iter = g_slist_next (iter)) { | ||
| 149 | + GstMssStream *stream = iter->data; | ||
| 150 | + | ||
| 151 | + if (stream->active) { | ||
| 152 | + /* FIXME: bound this correctly for multiple streams */ | ||
| 153 | + if (!(ret = gst_mss_stream_get_live_seek_range (stream, start, stop))) | ||
| 154 | + break; | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + if (ret && gst_mss_manifest_is_live (manifest)) { | ||
| 159 | + GstClockTime dvr_window = | ||
| 160 | + gst_mss_manifest_get_dvr_window_length_clock_time (manifest); | ||
| 161 | + | ||
| 162 | + if (GST_CLOCK_TIME_IS_VALID (dvr_window) && *stop - *start > dvr_window) { | ||
| 163 | + *start = *stop - dvr_window; | ||
| 164 | + } | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + return ret; | ||
| 168 | +} | ||
| 169 | diff --git a/ext/smoothstreaming/gstmssmanifest.h b/ext/smoothstreaming/gstmssmanifest.h | ||
| 170 | index af7419c23..6b7b1f971 100644 | ||
| 171 | --- a/ext/smoothstreaming/gstmssmanifest.h | ||
| 172 | +++ b/ext/smoothstreaming/gstmssmanifest.h | ||
| 173 | @@ -54,6 +54,7 @@ void gst_mss_manifest_reload_fragments (GstMssManifest * manifest, GstBuffer * d | ||
| 174 | GstClockTime gst_mss_manifest_get_min_fragment_duration (GstMssManifest * manifest); | ||
| 175 | const gchar * gst_mss_manifest_get_protection_system_id (GstMssManifest * manifest); | ||
| 176 | const gchar * gst_mss_manifest_get_protection_data (GstMssManifest * manifest); | ||
| 177 | +gboolean gst_mss_manifest_get_live_seek_range (GstMssManifest * manifest, gint64 * start, gint64 * stop); | ||
| 178 | |||
| 179 | GstMssStreamType gst_mss_stream_get_type (GstMssStream *stream); | ||
| 180 | GstCaps * gst_mss_stream_get_caps (GstMssStream * stream); | ||
| 181 | -- | ||
| 182 | 2.11.0 | ||
| 183 | |||
diff --git a/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-use-the-duration-from-the-list-of-fr.patch b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-use-the-duration-from-the-list-of-fr.patch new file mode 100644 index 0000000000..4e51040863 --- /dev/null +++ b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0001-smoothstreaming-use-the-duration-from-the-list-of-fr.patch | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | From 0fbee8f37427b88339194b22ba9aa210772a8613 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Matthew Waters <matthew@centricular.com> | ||
| 3 | Date: Thu, 10 Nov 2016 17:20:27 +1100 | ||
| 4 | Subject: [PATCH] smoothstreaming: use the duration from the list of fragments | ||
| 5 | if not present in the manifest | ||
| 6 | |||
| 7 | Provides a more accurate duration for live streams that may be minutes | ||
| 8 | or hours in front of the earliest fragment. | ||
| 9 | |||
| 10 | https://bugzilla.gnome.org/show_bug.cgi?id=774178 | ||
| 11 | --- | ||
| 12 | Upstream-Status: Backport | ||
| 13 | Signed-off-by: Khem Raj <raj.khem@gmail.com> | ||
| 14 | |||
| 15 | ext/smoothstreaming/gstmssmanifest.c | 24 ++++++++++++++++++++++++ | ||
| 16 | 1 file changed, 24 insertions(+) | ||
| 17 | |||
| 18 | diff --git a/ext/smoothstreaming/gstmssmanifest.c b/ext/smoothstreaming/gstmssmanifest.c | ||
| 19 | index 317b3cef9..144bbb42d 100644 | ||
| 20 | --- a/ext/smoothstreaming/gstmssmanifest.c | ||
| 21 | +++ b/ext/smoothstreaming/gstmssmanifest.c | ||
| 22 | @@ -888,6 +888,7 @@ gst_mss_manifest_get_duration (GstMssManifest * manifest) | ||
| 23 | gchar *duration; | ||
| 24 | guint64 dur = -1; | ||
| 25 | |||
| 26 | + /* try the property */ | ||
| 27 | duration = | ||
| 28 | (gchar *) xmlGetProp (manifest->xmlrootnode, | ||
| 29 | (xmlChar *) MSS_PROP_STREAM_DURATION); | ||
| 30 | @@ -895,6 +896,29 @@ gst_mss_manifest_get_duration (GstMssManifest * manifest) | ||
| 31 | dur = g_ascii_strtoull (duration, NULL, 10); | ||
| 32 | xmlFree (duration); | ||
| 33 | } | ||
| 34 | + /* else use the fragment list */ | ||
| 35 | + if (dur <= 0) { | ||
| 36 | + guint64 max_dur = 0; | ||
| 37 | + GSList *iter; | ||
| 38 | + | ||
| 39 | + for (iter = manifest->streams; iter; iter = g_slist_next (iter)) { | ||
| 40 | + GstMssStream *stream = iter->data; | ||
| 41 | + | ||
| 42 | + if (stream->active) { | ||
| 43 | + if (stream->fragments) { | ||
| 44 | + GList *l = g_list_last (stream->fragments); | ||
| 45 | + GstMssStreamFragment *fragment = (GstMssStreamFragment *) l->data; | ||
| 46 | + guint64 frag_dur = | ||
| 47 | + fragment->time + fragment->duration * fragment->repetitions; | ||
| 48 | + max_dur = MAX (frag_dur, max_dur); | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + if (max_dur != 0) | ||
| 54 | + dur = max_dur; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | return dur; | ||
| 58 | } | ||
| 59 | |||
| 60 | -- | ||
| 61 | 2.11.0 | ||
| 62 | |||
diff --git a/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad_1.10.2.bb b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad_1.10.2.bb index 73cf591f98..f487b09cb2 100644 --- a/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad_1.10.2.bb +++ b/meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad_1.10.2.bb | |||
| @@ -15,6 +15,9 @@ SRC_URI = " \ | |||
| 15 | file://0009-glimagesink-Downrank-to-marginal.patch \ | 15 | file://0009-glimagesink-Downrank-to-marginal.patch \ |
| 16 | file://0001-introspection.m4-prefix-pkgconfig-paths-with-PKG_CON.patch \ | 16 | file://0001-introspection.m4-prefix-pkgconfig-paths-with-PKG_CON.patch \ |
| 17 | file://0001-Prepend-PKG_CONFIG_SYSROOT_DIR-to-pkg-config-output.patch \ | 17 | file://0001-Prepend-PKG_CONFIG_SYSROOT_DIR-to-pkg-config-output.patch \ |
| 18 | file://0001-smoothstreaming-implement-adaptivedemux-s-get_live_s.patch \ | ||
| 19 | file://0001-smoothstreaming-use-the-duration-from-the-list-of-fr.patch \ | ||
| 20 | file://0001-mssdemux-improved-live-playback-support.patch \ | ||
| 18 | " | 21 | " |
| 19 | SRC_URI[md5sum] = "823f4c33fe27c61332c0122273217988" | 22 | SRC_URI[md5sum] = "823f4c33fe27c61332c0122273217988" |
| 20 | SRC_URI[sha256sum] = "0795ca9303a99cc7e44dda0e6e18524de02b39892e4b68eaba488f7b9db53a3a" | 23 | SRC_URI[sha256sum] = "0795ca9303a99cc7e44dda0e6e18524de02b39892e4b68eaba488f7b9db53a3a" |
