820 lines
25 KiB
C++
820 lines
25 KiB
C++
// https://tools.ietf.org/html/rfc8216
|
|
// https://developer.apple.com/documentation/http_live_streaming/about_the_ext-x-version_tag
|
|
//
|
|
// audio/mpegurl
|
|
// application/vnd.apple.mpegurl
|
|
|
|
#include "hls-parser.h"
|
|
#include "hls-string.h"
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
struct hls_parser_t
|
|
{
|
|
size_t media_capacity;
|
|
size_t variant_capacity;
|
|
size_t session_key_capacity;
|
|
size_t session_data_capacity;
|
|
struct hls_variant_t* variant;
|
|
struct hls_master_t* master;
|
|
|
|
size_t segment_capacity;
|
|
struct hls_segment_t* segment;
|
|
struct hls_segment_t* key_segment; // last segment has key
|
|
struct hls_playlist_t* playlist;
|
|
};
|
|
|
|
enum
|
|
{
|
|
ATTR_VALUE_TYPE_UINT32,
|
|
ATTR_VALUE_TYPE_UINT64,
|
|
ATTR_VALUE_TYPE_FLOAT32,
|
|
ATTR_VALUE_TYPE_FLOAT64,
|
|
ATTR_VALUE_TYPE_STRING,
|
|
ATTR_VALUE_TYPE_STRING_BOOL,
|
|
};
|
|
|
|
struct hls_tag_attr_t
|
|
{
|
|
int cls;
|
|
const char* name;
|
|
void* ptr;
|
|
};
|
|
|
|
#define HLS_TAG_ATTR_VALUE(a, cls0, name0, ptr0) {a.cls=cls0; a.name=name0; a.ptr=ptr0; }
|
|
|
|
static int hls_parser_realloc(void** ptr, size_t* capacity, size_t len, size_t incr, size_t size);
|
|
|
|
static int hls_attr_read(const char* value, size_t n, int cls, void* ptr)
|
|
{
|
|
switch (cls)
|
|
{
|
|
case ATTR_VALUE_TYPE_STRING_BOOL:
|
|
*(int*)ptr = (3 == n && 0 == strncasecmp(value, "YES", 3)) ? 1 : 0;
|
|
return 0;
|
|
|
|
case ATTR_VALUE_TYPE_UINT32:
|
|
*(uint32_t*)ptr = (uint32_t)strtoul(value, NULL, 10);
|
|
break;
|
|
|
|
case ATTR_VALUE_TYPE_UINT64:
|
|
*(uint64_t*)ptr = (uint64_t)strtoull(value, NULL, 10);
|
|
break;
|
|
|
|
case ATTR_VALUE_TYPE_FLOAT32:
|
|
*(float*)ptr = (float)strtod(value, NULL);
|
|
break;
|
|
|
|
case ATTR_VALUE_TYPE_FLOAT64:
|
|
*(double*)ptr = strtod(value, NULL);
|
|
break;
|
|
|
|
case ATTR_VALUE_TYPE_STRING:
|
|
*((char**)ptr) = (char*)value;
|
|
((char*)value)[n] = 0;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hls_parse_attrs(const char* data, size_t bytes, struct hls_tag_attr_t* attrs, size_t nattrs)
|
|
{
|
|
int r;
|
|
size_t i, n, nn, nv;
|
|
const char* ptr, *next;
|
|
const char* name, *value;
|
|
|
|
r = 0;
|
|
for (ptr = data; ptr && ptr < data + bytes && 0 == r; ptr = next)
|
|
{
|
|
n = hls_strsplit(ptr, data + bytes, ",", "\"", &next);
|
|
|
|
nn = hls_strsplit(ptr, ptr + n, "=", "", &value);
|
|
name = hls_strtrim(ptr, &nn, " \t", " \t"); // trim SP/HTAB
|
|
nv = ptr + n - value;
|
|
value = hls_strtrim(value, &nv, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
|
|
for (i = 0; i < nattrs; i++)
|
|
{
|
|
if (nn == strlen(attrs[i].name) && 0 == strncasecmp(attrs[i].name, name, nn))
|
|
{
|
|
r = hls_attr_read(value, nv, attrs[i].cls, attrs[i].ptr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int hls_ext_inf(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
size_t n;
|
|
const char* duration;
|
|
struct hls_segment_t* segment;
|
|
segment = parser->segment;
|
|
|
|
n = hls_strsplit(data, data + bytes, ",", "\"", (const char**)&segment->title);
|
|
duration = hls_strtrim(data, &n, " \t", " \t"); // trim SP/HTAB
|
|
|
|
// decimal-floating-point or decimal-integer number
|
|
segment->duration = strtod(duration, NULL);
|
|
n = data + bytes - segment->title;
|
|
segment->title = (char*)hls_strtrim(segment->title, &n, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
segment->title[n] = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_byterange(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
size_t n;
|
|
const char* ptr, *next;
|
|
struct hls_segment_t* segment;
|
|
segment = parser->segment;
|
|
|
|
n = hls_strsplit(data, data + bytes, "@", "", &next);
|
|
ptr = hls_strtrim(data, &n, " \t", " \t"); // trim SP/HTAB
|
|
segment->bytes = (int64_t)strtoull(ptr, NULL, 10); // decimal-integer
|
|
|
|
n = data + bytes - next;
|
|
next = hls_strtrim(next, &n, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
segment->offset = n > 0 ? (int64_t)strtoull(next, NULL, 10) : segment->offset;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_discontinuity(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
parser->segment->discontinuity = 1;
|
|
(void)data, (void)bytes;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_key(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int r;
|
|
struct hls_segment_t* segment;
|
|
struct hls_tag_attr_t attrs[5];
|
|
char* iv;
|
|
|
|
iv = "";
|
|
segment = parser->segment;
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "METHOD", &segment->key.method);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "URI", &segment->key.uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "IV", &iv);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "KEYFORMAT", &segment->key.keyformat);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_STRING, "KEYFORMATVERSIONS", &segment->key.keyformatversions);
|
|
|
|
r = hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
if (0 != r)
|
|
return r;
|
|
|
|
if (strlen(iv) == 34) // 0x + 32bits
|
|
hls_base16_decode(segment->key.iv, iv + 2, 32);
|
|
|
|
// It applies to every Media Segment and to every Media
|
|
// Initialization Section declared by an EXT-X-MAP tag that appears
|
|
// between it and the next EXT-X-KEY tag in the Playlist file with the
|
|
// same KEYFORMAT attribute (or the end of the Playlist file).
|
|
parser->key_segment = parser->segment; // save key_segment
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_map(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int r;
|
|
size_t n;
|
|
struct hls_segment_t* segment;
|
|
struct hls_tag_attr_t attrs[2];
|
|
char* byterange;
|
|
const char* ptr, *next;
|
|
|
|
byterange = "";
|
|
segment = parser->segment;
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "URI", &segment->map.uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "BYTERANGE", &byterange);
|
|
|
|
r = hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
if (0 != r)
|
|
return r;
|
|
|
|
n = strlen(byterange);
|
|
if (n > 2)
|
|
{
|
|
n = hls_strsplit(byterange, byterange + n, "@", "", &next);
|
|
ptr = hls_strtrim(byterange, &n, " \t", " \t"); // trim SP/HTAB
|
|
segment->map.bytes = (int64_t)strtoull(ptr, NULL, 10); // decimal-integer
|
|
|
|
n = byterange + n - next;
|
|
next = hls_strtrim(next, &n, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
segment->map.offset = n > 0 ? (int64_t)strtoull(next, NULL, 10) : segment->map.offset;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_program_date_time(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
struct hls_segment_t* segment;
|
|
segment = parser->segment;
|
|
segment->program_date_time = (char*)hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_daterange(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
struct hls_segment_t* segment;
|
|
struct hls_tag_attr_t attrs[7];
|
|
|
|
segment = parser->segment;
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "ID", &segment->daterange.id);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "CLASS", &segment->daterange.cls);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "START-DATE", &segment->daterange.start_date);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "END-DATE", &segment->daterange.end_date);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_FLOAT64, "DURATION", &segment->daterange.duration);
|
|
HLS_TAG_ATTR_VALUE(attrs[5], ATTR_VALUE_TYPE_FLOAT64, "PLANNED-DURATION", &segment->daterange.planned_duration);
|
|
HLS_TAG_ATTR_VALUE(attrs[6], ATTR_VALUE_TYPE_STRING_BOOL, "END-ON-NEXT", &segment->daterange.end_on_next);
|
|
|
|
return hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
}
|
|
|
|
static int hls_ext_x_targetduration(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
parser->playlist->target_duration = (uint64_t)strtoull(data, NULL, 10); // decimal-integer
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_media_sequence(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
parser->playlist->media_sequence = (uint64_t)strtoull(data, NULL, 10); // decimal-integer
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_discontinuity_sequence(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
parser->playlist->discontinuity_sequence = (uint64_t)strtoull(data, NULL, 10); // decimal-integer
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_endlist(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
parser->playlist->endlist = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_playlist_type(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
struct hls_playlist_t* playlist;
|
|
playlist = parser->playlist;
|
|
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
if (5 == bytes && 0 == strncasecmp("EVENT", data, 5))
|
|
playlist->type = HLS_PLAYLIST_TYPE_EVENT;
|
|
else if (3 == bytes && 0 == strncasecmp("VOD", data, 3))
|
|
playlist->type = HLS_PLAYLIST_TYPE_VOD;
|
|
else
|
|
playlist->type = HLS_PLAYLIST_TYPE_LIVE;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_i_frames_only(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
parser->playlist->i_frames_only = 1;
|
|
return 0;
|
|
}
|
|
|
|
// If the media type is VIDEO or AUDIO, a missing URI attribute
|
|
// indicates that the media data for this Rendition is included in the
|
|
// Media Playlist of any EXT-X-STREAM-INF tag referencing this EXT-X-MEDIA tag.
|
|
static int hls_ext_x_media(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
struct hls_media_t* media;
|
|
struct hls_master_t* master;
|
|
struct hls_tag_attr_t attrs[12];
|
|
|
|
master = parser->master;
|
|
if (0 != hls_parser_realloc((void**)&master->medias, &parser->media_capacity, master->media_count, 4, sizeof(struct hls_media_t)))
|
|
return -ENOMEM;
|
|
|
|
media = &master->medias[master->media_count++];
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "TYPE", &media->type);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "URI", &media->uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "GROUP-ID", &media->group_id);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "LANGUAGE", &media->language);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_STRING, "ASSOC-LANGUAGE", &media->assoc_language);
|
|
HLS_TAG_ATTR_VALUE(attrs[5], ATTR_VALUE_TYPE_STRING, "NAME", &media->name);
|
|
HLS_TAG_ATTR_VALUE(attrs[6], ATTR_VALUE_TYPE_STRING_BOOL, "DEFAULT", &media->is_default);
|
|
HLS_TAG_ATTR_VALUE(attrs[7], ATTR_VALUE_TYPE_STRING_BOOL, "AUTOSELECT", &media->autoselect);
|
|
HLS_TAG_ATTR_VALUE(attrs[8], ATTR_VALUE_TYPE_STRING_BOOL, "FORCED", &media->forced);
|
|
HLS_TAG_ATTR_VALUE(attrs[9], ATTR_VALUE_TYPE_STRING, "INSTREAM-ID", &media->instream_id);
|
|
HLS_TAG_ATTR_VALUE(attrs[10], ATTR_VALUE_TYPE_STRING, "CHARACTERISTICS", &media->characteristics);
|
|
HLS_TAG_ATTR_VALUE(attrs[11], ATTR_VALUE_TYPE_STRING, "CHANNELS", &media->channels);
|
|
|
|
return hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
}
|
|
|
|
static int hls_ext_x_stream_inf(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int r;
|
|
struct hls_variant_t* variant;
|
|
struct hls_tag_attr_t attrs[10];
|
|
char* resolution;
|
|
|
|
resolution = "";
|
|
variant = parser->variant;
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_UINT32, "BANDWIDTH", &variant->bandwidth);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_UINT32, "AVERAGE-BANDWIDTH", &variant->average_bandwidth);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "CODECS", &variant->codecs);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "RESOLUTION", &resolution);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_FLOAT64, "FRAME-RATE", &variant->fps);
|
|
HLS_TAG_ATTR_VALUE(attrs[5], ATTR_VALUE_TYPE_STRING, "HDCP-LEVEL", &variant->hdcp_level);
|
|
HLS_TAG_ATTR_VALUE(attrs[6], ATTR_VALUE_TYPE_STRING, "AUDIO", &variant->audio);
|
|
HLS_TAG_ATTR_VALUE(attrs[7], ATTR_VALUE_TYPE_STRING, "VIDEO", &variant->video);
|
|
HLS_TAG_ATTR_VALUE(attrs[8], ATTR_VALUE_TYPE_STRING, "SUBTITLES", &variant->subtitle);
|
|
HLS_TAG_ATTR_VALUE(attrs[9], ATTR_VALUE_TYPE_STRING, "CLOSED-CAPTIONS", &variant->closed_captions);
|
|
|
|
r = hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
if (0 != r)
|
|
return r;
|
|
|
|
if (2 != sscanf(resolution, "%dx%d", &variant->width, &variant->height))
|
|
return 0; // ignore
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_i_frame_stream_inf(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int r;
|
|
struct hls_variant_t* variant;
|
|
struct hls_variant_t* iframestream;
|
|
struct hls_tag_attr_t attrs[7];
|
|
char* resolution;
|
|
|
|
resolution = "";
|
|
variant = parser->variant;
|
|
if (!variant->i_frame_stream_inf)
|
|
{
|
|
variant->i_frame_stream_inf = calloc(1, sizeof(*variant->i_frame_stream_inf));
|
|
if (!variant->i_frame_stream_inf)
|
|
return -ENOMEM;
|
|
}
|
|
iframestream = variant->i_frame_stream_inf;
|
|
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "URI", &iframestream->uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_UINT32, "BANDWIDTH", &iframestream->bandwidth);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_UINT32, "AVERAGE-BANDWIDTH", &iframestream->average_bandwidth);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "CODECS", &iframestream->codecs);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_STRING, "RESOLUTION", &resolution);
|
|
HLS_TAG_ATTR_VALUE(attrs[5], ATTR_VALUE_TYPE_STRING, "HDCP-LEVEL", &iframestream->hdcp_level);
|
|
HLS_TAG_ATTR_VALUE(attrs[6], ATTR_VALUE_TYPE_STRING, "VIDEO", &iframestream->video);
|
|
|
|
r = hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
if (0 != r)
|
|
return r;
|
|
|
|
if (2 != sscanf(resolution, "%dx%d", &iframestream->width, &iframestream->height))
|
|
return 0; // ignore
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_session_data(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
struct hls_master_t* master;
|
|
struct hls_tag_attr_t attrs[4];
|
|
|
|
master = parser->master;
|
|
if (0 != hls_parser_realloc((void**)&master->session_data, &parser->session_data_capacity, master->session_data_count, 2, sizeof(master->session_data[0])))
|
|
return -ENOMEM;
|
|
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "DATA-ID", &master->session_data[master->session_data_count].data_id);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "VALUE", &master->session_data[master->session_data_count].value);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "URI", &master->session_data[master->session_data_count].uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "LANGUAGE", &master->session_data[master->session_data_count].language);
|
|
|
|
++master->session_data_count;
|
|
return hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
}
|
|
|
|
static int hls_ext_x_session_key(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int r;
|
|
struct hls_master_t* master;
|
|
struct hls_tag_attr_t attrs[5];
|
|
char* iv;
|
|
|
|
iv = "";
|
|
master = parser->master;
|
|
if (0 != hls_parser_realloc((void**)&master->session_key, &parser->session_key_capacity, master->session_key_count, 2, sizeof(master->session_key[0])))
|
|
return -ENOMEM;
|
|
|
|
HLS_TAG_ATTR_VALUE(attrs[0], ATTR_VALUE_TYPE_STRING, "METHOD", &master->session_key[master->session_key_count].method);
|
|
HLS_TAG_ATTR_VALUE(attrs[1], ATTR_VALUE_TYPE_STRING, "URI", &master->session_key[master->session_key_count].uri);
|
|
HLS_TAG_ATTR_VALUE(attrs[2], ATTR_VALUE_TYPE_STRING, "IV", &iv);
|
|
HLS_TAG_ATTR_VALUE(attrs[3], ATTR_VALUE_TYPE_STRING, "KEYFORMAT", &master->session_key[master->session_key_count].keyformat);
|
|
HLS_TAG_ATTR_VALUE(attrs[4], ATTR_VALUE_TYPE_STRING, "KEYFORMATVERSIONS", &master->session_key[master->session_key_count].keyformatversions);
|
|
|
|
r = hls_parse_attrs(data, bytes, attrs, sizeof(attrs) / sizeof(attrs[0]));
|
|
if (0 != r)
|
|
return r;
|
|
|
|
if (strlen(iv) == 34) // 0x + 32bits
|
|
hls_base16_decode(master->session_key[master->session_key_count].iv, iv + 2, 32);
|
|
|
|
++master->session_key_count;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_independent_segments(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
data = hls_strtrim(data, &bytes, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
if (parser->playlist)
|
|
parser->playlist->independent_segments = 1;
|
|
else
|
|
parser->master->independent_segments = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_start(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
size_t n;
|
|
int start_precise;
|
|
double start_time_offset;
|
|
const char* ptr, * next;
|
|
|
|
n = hls_strsplit(data, data + bytes, ",", "", &next);
|
|
ptr = hls_strtrim(data, &n, " \t", " \t"); // trim SP/HTAB
|
|
start_time_offset = strtod(ptr, NULL); // signed-decimal-floating-point
|
|
|
|
n = data + bytes - next;
|
|
next = hls_strtrim(next, &n, " \t'\"", " \t'\""); // trim SP/HTAB/'/"
|
|
start_precise = 3 == n && 0 == strncasecmp("YES", next, 3) ? 1 : 0;
|
|
|
|
if (parser->playlist)
|
|
{
|
|
parser->playlist->start_time_offset = start_time_offset;
|
|
parser->playlist->start_precise = start_precise;
|
|
}
|
|
else
|
|
{
|
|
parser->master->start_time_offset = start_time_offset;
|
|
parser->master->start_precise = start_precise;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hls_ext_x_version(struct hls_parser_t* parser, const char* data, size_t bytes)
|
|
{
|
|
int version;
|
|
data = hls_strtrim(data, &bytes, " \t", " \t"); // trim SP/HTAB
|
|
version = (int)strtoul(data, NULL, 10);
|
|
if (parser->playlist)
|
|
parser->playlist->version = version;
|
|
else
|
|
parser->master->version = version;
|
|
return 0;
|
|
}
|
|
|
|
static int hls_parser_onuri(struct hls_parser_t* parser, const char* uri, size_t len)
|
|
{
|
|
//printf("uri: %.*s\n", (int)len, uri);
|
|
|
|
uri = hls_strtrim(uri, &len, " \t", " \t"); // trim SP/HTAB
|
|
|
|
if (parser->playlist)
|
|
{
|
|
if (!parser->segment)
|
|
{
|
|
assert(0);
|
|
return -1;
|
|
}
|
|
parser->segment->uri = (char*)uri;
|
|
parser->segment->uri[len] = 0;
|
|
parser->segment = NULL;
|
|
parser->playlist->count++;
|
|
}
|
|
else
|
|
{
|
|
if (!parser->variant)
|
|
{
|
|
assert(0);
|
|
return -1;
|
|
}
|
|
parser->variant->uri = (char*)uri;
|
|
parser->variant->uri[len] = 0;
|
|
parser->variant = NULL;
|
|
parser->master->variant_count++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct
|
|
{
|
|
int cls; // 0-any, 1-playlist, 2-master
|
|
const char* name;
|
|
int (*parser)(struct hls_parser_t* parser, const char* attrs, size_t bytes);
|
|
} s_tags[] = {
|
|
// 4.3.2. Media Segment Tags
|
|
{ HLS_M3U8_PLAYLIST, "EXTINF", hls_ext_inf },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-BYTERANGE", hls_ext_x_byterange },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-DISCONTINUITY", hls_ext_x_discontinuity },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-KEY", hls_ext_x_key },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-MAP", hls_ext_x_map },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-PROGRAM-DATE-TIME", hls_ext_x_program_date_time },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-DATERANGE", hls_ext_x_daterange },
|
|
|
|
// 4.3.3. Media Playlist Tags
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-TARGETDURATION", hls_ext_x_targetduration },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-MEDIA-SEQUENCE", hls_ext_x_media_sequence },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-DISCONTINUITY-SEQUENCE",hls_ext_x_discontinuity_sequence },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-ENDLIST", hls_ext_x_endlist },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-PLAYLIST-TYPE", hls_ext_x_playlist_type },
|
|
{ HLS_M3U8_PLAYLIST, "EXT-X-I-FRAMES-ONLY", hls_ext_x_i_frames_only },
|
|
|
|
// 4.3.4. Master Playlist Tags
|
|
{ HLS_M3U8_MASTER, "EXT-X-MEDIA", hls_ext_x_media },
|
|
{ HLS_M3U8_MASTER, "EXT-X-STREAM-INF", hls_ext_x_stream_inf },
|
|
{ HLS_M3U8_MASTER, "EXT-X-I-FRAME-STREAM-INF", hls_ext_x_i_frame_stream_inf },
|
|
{ HLS_M3U8_MASTER, "EXT-X-SESSION-DATA", hls_ext_x_session_data },
|
|
{ HLS_M3U8_MASTER, "EXT-X-SESSION-KEY", hls_ext_x_session_key },
|
|
|
|
// 4.3.5. Media or Master Playlist Tags
|
|
{ HLS_M3U8_UNKNOWN, "EXT-X-INDEPENDENT-SEGMENTS", hls_ext_x_independent_segments },
|
|
{ HLS_M3U8_UNKNOWN, "EXT-X-START", hls_ext_x_start },
|
|
|
|
{ HLS_M3U8_UNKNOWN, "EXT-X-VERSION", hls_ext_x_version},
|
|
};
|
|
|
|
static int hls_parser_realloc(void** ptr, size_t* capacity, size_t len, size_t incr, size_t size)
|
|
{
|
|
size_t n;
|
|
void* ptr1;
|
|
if (len >= *capacity)
|
|
{
|
|
n = len / 4;
|
|
n = n > incr ? n : incr;
|
|
ptr1 = realloc(*ptr, (len + 1 + n) * size);
|
|
if (!ptr1)
|
|
return -ENOMEM;
|
|
|
|
memset((uint8_t*)ptr1 + len * size, 0, (1 + n) * size);
|
|
*capacity = len + 1 + n;
|
|
*ptr = ptr1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hls_parser_fetch_segment(struct hls_parser_t* parser)
|
|
{
|
|
if (parser->segment)
|
|
return 0;
|
|
|
|
if(0 != hls_parser_realloc((void**)&parser->playlist->segments, &parser->segment_capacity, parser->playlist->count, 8, sizeof(struct hls_segment_t)))
|
|
return -ENOMEM;
|
|
|
|
parser->segment = &parser->playlist->segments[parser->playlist->count];
|
|
//memset(parser->segment, 0, sizeof(*parser->segment));
|
|
parser->segment->bytes = -1;
|
|
parser->segment->offset = -1;
|
|
parser->segment->map.bytes = -1;
|
|
parser->segment->map.offset = -1;
|
|
|
|
// copy default key
|
|
if (parser->key_segment)
|
|
memcpy(&parser->segment->key, &parser->key_segment->key, sizeof(parser->segment->key));
|
|
return 0;
|
|
}
|
|
|
|
static int hls_parser_fetch_variant(struct hls_parser_t* parser)
|
|
{
|
|
if (parser->variant)
|
|
return 0;
|
|
|
|
if (0 != hls_parser_realloc((void**)&parser->master->variants, &parser->variant_capacity, parser->master->variant_count, 4, sizeof(struct hls_variant_t)))
|
|
return -ENOMEM;
|
|
|
|
parser->variant = &parser->master->variants[parser->master->variant_count];
|
|
//memset(parser->variant, 0, sizeof(*parser->variant));
|
|
return 0;
|
|
}
|
|
|
|
static int hls_parser_ontag(struct hls_parser_t* parser, const char* tag, size_t len, const char* attrs, size_t bytes)
|
|
{
|
|
int r;
|
|
size_t i;
|
|
|
|
//printf("%.*s: %.*s\n", (int)len, tag, (int)bytes, attrs);
|
|
r = parser->playlist ? hls_parser_fetch_segment(parser) : hls_parser_fetch_variant(parser);
|
|
if (0 != r)
|
|
return r;
|
|
|
|
for (i = 0; i < sizeof(s_tags) / sizeof(s_tags[0]); i++)
|
|
{
|
|
if (len == strlen(s_tags[i].name)+1 && 0 == strncasecmp(s_tags[i].name, tag+1, len-1))
|
|
{
|
|
if ((HLS_M3U8_PLAYLIST == s_tags[i].cls && !parser->playlist)
|
|
|| (HLS_M3U8_MASTER == s_tags[i].cls && !parser->master))
|
|
return -EINVAL;
|
|
|
|
r = s_tags[i].parser(parser, attrs, bytes);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int hls_parser_input(struct hls_parser_t* parser, const char* m3u8, size_t len)
|
|
{
|
|
int r;
|
|
size_t n, ntag;
|
|
const char* ptr, *next;
|
|
const char* attr;
|
|
|
|
r = 0;
|
|
for(ptr = m3u8; ptr && ptr < m3u8 + len; ptr = next)
|
|
{
|
|
n = hls_strsplit(ptr, m3u8 + len, "\r\n", "", &next);
|
|
ptr = hls_strtrim(ptr, &n, " \t", " \t"); // trim SP/HTAB
|
|
if (n < 1)
|
|
continue; // blank line
|
|
|
|
if ('#' == *ptr)
|
|
{
|
|
if (n <= 4 || strncmp("#EXT", ptr, 4))
|
|
{
|
|
// ignore comment
|
|
//assert(0);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// tags
|
|
attr = strpbrk(ptr, ": \t\r\n");
|
|
assert(attr <= ptr + n);
|
|
ntag = attr ? attr - ptr : n;
|
|
attr = attr ? attr + strspn(attr, ": \t") : ptr + n;
|
|
r = hls_parser_ontag(parser, ptr, ntag, attr, ptr + n - attr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// uri
|
|
r = hls_parser_onuri(parser, ptr, n);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int hls_master_parse(struct hls_master_t** master, const char* m3u8, size_t len)
|
|
{
|
|
int r;
|
|
char* ptr;
|
|
struct hls_parser_t parser;
|
|
|
|
memset(&parser, 0, sizeof(parser));
|
|
parser.master = (struct hls_master_t*)calloc(1, sizeof(struct hls_master_t) + len + 1);
|
|
if (!parser.master)
|
|
return -ENOMEM;
|
|
|
|
ptr = (char*)(parser.master + 1);
|
|
memcpy(ptr, m3u8, len);
|
|
|
|
r = hls_parser_input(&parser, ptr, len);
|
|
if (0 != r)
|
|
{
|
|
hls_master_free(&parser.master);
|
|
return r;
|
|
}
|
|
|
|
*master = parser.master;
|
|
return 0;
|
|
}
|
|
|
|
int hls_playlist_parse(struct hls_playlist_t** playlist, const char* m3u8, size_t len)
|
|
{
|
|
int r;
|
|
char* ptr;
|
|
struct hls_parser_t parser;
|
|
|
|
memset(&parser, 0, sizeof(parser));
|
|
parser.playlist = (struct hls_playlist_t*)calloc(1, sizeof(struct hls_playlist_t) + len + 1);
|
|
if (!parser.playlist)
|
|
return -ENOMEM;
|
|
|
|
ptr = (char*)(parser.playlist + 1);
|
|
memcpy(ptr, m3u8, len);
|
|
|
|
r = hls_parser_input(&parser, ptr, len);
|
|
if (0 != r)
|
|
{
|
|
hls_playlist_free(&parser.playlist);
|
|
return r;
|
|
}
|
|
|
|
*playlist = parser.playlist;
|
|
return 0;
|
|
}
|
|
|
|
int hls_master_free(struct hls_master_t** master)
|
|
{
|
|
size_t i;
|
|
struct hls_master_t* p;
|
|
if (master && *master)
|
|
{
|
|
p = *master;
|
|
if (p->medias)
|
|
free(p->medias);
|
|
if (p->session_data)
|
|
free(p->session_data);
|
|
if (p->session_key)
|
|
free(p->session_key);
|
|
if (p->variants)
|
|
{
|
|
for (i = 0; i < p->variant_count; i++)
|
|
{
|
|
if (p->variants[i].i_frame_stream_inf)
|
|
free(p->variants[i].i_frame_stream_inf);
|
|
}
|
|
free(p->variants);
|
|
}
|
|
free(p);
|
|
*master = NULL;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int hls_playlist_free(struct hls_playlist_t** playlist)
|
|
{
|
|
struct hls_playlist_t* p;
|
|
if (playlist && *playlist)
|
|
{
|
|
p = *playlist;
|
|
if (p->segments)
|
|
free(p->segments);
|
|
free(p);
|
|
*playlist = NULL;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int hls_parser_probe(const char* m3u8, size_t len)
|
|
{
|
|
size_t n;
|
|
const char* ptr, *next;
|
|
|
|
for (ptr = m3u8; ptr && ptr < m3u8 + len; ptr = next)
|
|
{
|
|
n = hls_strsplit(ptr, m3u8 + len, "\r\n", "", &next);
|
|
ptr = hls_strtrim(ptr, &n, " \t", " \t"); // trim SP/HTAB
|
|
if (n >= 7 && 0 == strncasecmp("#EXTINF", ptr, 7))
|
|
return HLS_M3U8_PLAYLIST;
|
|
else if (n >= 17 && 0 == strncasecmp("#EXT-X-STREAM-INF", ptr, 17))
|
|
return HLS_M3U8_MASTER;
|
|
}
|
|
|
|
return HLS_M3U8_UNKNOWN;
|
|
}
|
|
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
void hls_parser_test(const char* m3u8)
|
|
{
|
|
static char data[2 * 1024 * 1024];
|
|
FILE* fp = fopen(m3u8, "rb");
|
|
int n = (int)fread(data, 1, sizeof(data), fp);
|
|
fclose(fp);
|
|
|
|
int v = hls_parser_probe(data, n);
|
|
if (HLS_M3U8_MASTER == v)
|
|
{
|
|
struct hls_master_t* master;
|
|
assert(0 == hls_master_parse(&master, data, n));
|
|
hls_master_free(&master);
|
|
}
|
|
else if (HLS_M3U8_PLAYLIST == v)
|
|
{
|
|
struct hls_playlist_t* playlist;
|
|
assert(0 == hls_playlist_parse(&playlist, data, n));
|
|
hls_playlist_free(&playlist);
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
}
|
|
#endif
|