// 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 #include #include #include #include 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