gstreamer/ext/smoothstreaming/gstmssfragmentparser.c
Philippe Normand 73721ad4e9 mssdemux: improved live playback support
When a MSS server hosts a live stream the fragments listed in the
manifest usually don't have accurate timestamps and duration, except
for the first fragment, which additionally stores timing information
for the few upcoming fragments. In this scenario it is useless to
periodically fetch and update the manifest and the fragments list can
be incrementally built by parsing the first/current fragment.

https://bugzilla.gnome.org/show_bug.cgi?id=755036
2016-11-29 14:43:41 +01:00

266 lines
7.8 KiB
C

/*
* Microsoft Smooth-Streaming fragment parsing library
*
* gstmssfragmentparser.h
*
* Copyright (C) 2016 Igalia S.L
* Copyright (C) 2016 Metrological
* Author: Philippe Normand <philn@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gstmssfragmentparser.h"
#include <gst/base/gstbytereader.h>
#include <string.h>
GST_DEBUG_CATEGORY_EXTERN (mssdemux_debug);
#define GST_CAT_DEFAULT mssdemux_debug
void
gst_mss_fragment_parser_init (GstMssFragmentParser * parser)
{
parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_INIT;
parser->tfrf.entries_count = 0;
}
void
gst_mss_fragment_parser_clear (GstMssFragmentParser * parser)
{
parser->tfrf.entries_count = 0;
if (parser->tfrf.entries) {
g_free (parser->tfrf.entries);
parser->tfrf.entries = 0;
}
}
static gboolean
_parse_tfrf_box (GstMssFragmentParser * parser, GstByteReader * reader)
{
guint8 version;
guint32 flags = 0;
guint8 fragment_count = 0;
guint8 index = 0;
if (!gst_byte_reader_get_uint8 (reader, &version)) {
GST_ERROR ("Error getting box's version field");
return FALSE;
}
if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
GST_ERROR ("Error getting box's flags field");
return FALSE;
}
gst_byte_reader_get_uint8 (reader, &fragment_count);
parser->tfrf.entries_count = fragment_count;
parser->tfrf.entries =
g_malloc (sizeof (GstTfrfBoxEntry) * parser->tfrf.entries_count);
for (index = 0; index < fragment_count; index++) {
guint64 absolute_time = 0;
guint64 absolute_duration = 0;
if (version & 0x01) {
gst_byte_reader_get_uint64_be (reader, &absolute_time);
gst_byte_reader_get_uint64_be (reader, &absolute_duration);
} else {
guint32 time = 0;
guint32 duration = 0;
gst_byte_reader_get_uint32_be (reader, &time);
gst_byte_reader_get_uint32_be (reader, &duration);
time = ~time;
duration = ~duration;
absolute_time = ~time;
absolute_duration = ~duration;
}
parser->tfrf.entries[index].time = absolute_time;
parser->tfrf.entries[index].duration = absolute_duration;
}
GST_LOG ("tfrf box parsed");
return TRUE;
}
static gboolean
_parse_tfxd_box (GstMssFragmentParser * parser, GstByteReader * reader)
{
guint8 version;
guint32 flags = 0;
guint64 absolute_time = 0;
guint64 absolute_duration = 0;
if (!gst_byte_reader_get_uint8 (reader, &version)) {
GST_ERROR ("Error getting box's version field");
return FALSE;
}
if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
GST_ERROR ("Error getting box's flags field");
return FALSE;
}
if (version & 0x01) {
gst_byte_reader_get_uint64_be (reader, &absolute_time);
gst_byte_reader_get_uint64_be (reader, &absolute_duration);
} else {
guint32 time = 0;
guint32 duration = 0;
gst_byte_reader_get_uint32_be (reader, &time);
gst_byte_reader_get_uint32_be (reader, &duration);
time = ~time;
duration = ~duration;
absolute_time = ~time;
absolute_duration = ~duration;
}
parser->tfxd.time = absolute_time;
parser->tfxd.duration = absolute_duration;
GST_LOG ("tfxd box parsed");
return TRUE;
}
gboolean
gst_mss_fragment_parser_add_buffer (GstMssFragmentParser * parser,
GstBuffer * buffer)
{
GstByteReader reader;
GstMapInfo info;
guint32 size;
guint32 fourcc;
const guint8 *uuid;
gboolean error = FALSE;
gboolean mdat_box_found = FALSE;
static const guint8 tfrf_uuid[] = {
0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95,
0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f
};
static const guint8 tfxd_uuid[] = {
0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
};
static const guint8 piff_uuid[] = {
0xa2, 0x39, 0x4f, 0x52, 0x5a, 0x9b, 0x4f, 0x14,
0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4
};
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
return FALSE;
}
gst_byte_reader_init (&reader, info.data, info.size);
GST_TRACE ("Total buffer size: %u", gst_byte_reader_get_size (&reader));
size = gst_byte_reader_get_uint32_be_unchecked (&reader);
fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
if (fourcc == GST_MSS_FRAGMENT_FOURCC_MOOF) {
GST_TRACE ("moof box found");
size = gst_byte_reader_get_uint32_be_unchecked (&reader);
fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
if (fourcc == GST_MSS_FRAGMENT_FOURCC_MFHD) {
gst_byte_reader_skip_unchecked (&reader, size - 8);
size = gst_byte_reader_get_uint32_be_unchecked (&reader);
fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
if (fourcc == GST_MSS_FRAGMENT_FOURCC_TRAF) {
size = gst_byte_reader_get_uint32_be_unchecked (&reader);
fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
if (fourcc == GST_MSS_FRAGMENT_FOURCC_TFHD) {
gst_byte_reader_skip_unchecked (&reader, size - 8);
size = gst_byte_reader_get_uint32_be_unchecked (&reader);
fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
if (fourcc == GST_MSS_FRAGMENT_FOURCC_TRUN) {
GST_TRACE ("trun box found, size: %" G_GUINT32_FORMAT, size);
if (!gst_byte_reader_skip (&reader, size - 8)) {
GST_WARNING ("Failed to skip trun box, enough data?");
error = TRUE;
goto beach;
}
}
}
}
}
}
while (!mdat_box_found) {
GST_TRACE ("remaining data: %u", gst_byte_reader_get_remaining (&reader));
if (!gst_byte_reader_get_uint32_be (&reader, &size)) {
GST_WARNING ("Failed to get box size, enough data?");
error = TRUE;
break;
}
GST_TRACE ("box size: %" G_GUINT32_FORMAT, size);
if (!gst_byte_reader_get_uint32_le (&reader, &fourcc)) {
GST_WARNING ("Failed to get fourcc, enough data?");
error = TRUE;
break;
}
if (fourcc == GST_MSS_FRAGMENT_FOURCC_MDAT) {
GST_LOG ("mdat box found");
mdat_box_found = TRUE;
break;
}
if (fourcc != GST_MSS_FRAGMENT_FOURCC_UUID) {
GST_ERROR ("invalid UUID fourcc: %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (fourcc));
error = TRUE;
break;
}
if (!gst_byte_reader_peek_data (&reader, 16, &uuid)) {
GST_ERROR ("not enough data in UUID box");
error = TRUE;
break;
}
if (memcmp (uuid, piff_uuid, 16) == 0) {
gst_byte_reader_skip_unchecked (&reader, size - 8);
GST_LOG ("piff box detected");
}
if (memcmp (uuid, tfrf_uuid, 16) == 0) {
gst_byte_reader_get_data (&reader, 16, &uuid);
if (!_parse_tfrf_box (parser, &reader)) {
GST_ERROR ("txrf box parsing error");
error = TRUE;
break;
}
}
if (memcmp (uuid, tfxd_uuid, 16) == 0) {
gst_byte_reader_get_data (&reader, 16, &uuid);
if (!_parse_tfxd_box (parser, &reader)) {
GST_ERROR ("tfrf box parsing error");
error = TRUE;
break;
}
}
}
beach:
if (!error)
parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_FINISHED;
GST_LOG ("Fragment parsing successful: %s", error ? "no" : "yes");
gst_buffer_unmap (buffer, &info);
return !error;
}