X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fstream_filter%2Fhttplive.c;h=27839d2fba9b74ef6770dc17a0efb055f615dd2a;hb=6a24a618294ba84b569b32131eb3117b505ca4f0;hp=e2a8834e88c20cfd3ce530cfb572307f5ad9ad0e;hpb=c51ff47e321aa99f5f77033d091ce6140dd46d7e;p=vlc diff --git a/modules/stream_filter/httplive.c b/modules/stream_filter/httplive.c index e2a8834e88..27839d2fba 100644 --- a/modules/stream_filter/httplive.c +++ b/modules/stream_filter/httplive.c @@ -1,24 +1,24 @@ /***************************************************************************** * httplive.c: HTTP Live Streaming stream filter ***************************************************************************** - * Copyright (C) 2010-2011 M2X BV + * Copyright (C) 2010-2012 M2X BV * $Id$ * * Author: Jean-Paul Saman * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 program 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 General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** @@ -29,18 +29,17 @@ #endif #include -#include #include #include #include +#include #include #include #include #include -#include #include #include @@ -69,9 +68,9 @@ typedef struct segment_s uint64_t size; /* segment size in bytes */ uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/ - vlc_url_t url; + char *url; char *psz_key_path; /* url key path */ - uint8_t psz_AES_key[16]; /* AES-128 */ + uint8_t aes_key[16]; /* AES-128 */ bool b_key_loaded; vlc_mutex_t lock; @@ -89,7 +88,7 @@ typedef struct hls_stream_s foreach segment of (segment->duration * hls->bandwidth/8) */ vlc_array_t *segments; /* list of segments */ - vlc_url_t url; /* uri to m3u8 */ + char *url; /* uri to m3u8 */ vlc_mutex_t lock; bool b_cache; /* allow caching */ @@ -100,7 +99,7 @@ typedef struct hls_stream_s struct stream_sys_t { - vlc_url_t m3u8; /* M3U8 url */ + char *m3u8; /* M3U8 url */ vlc_thread_t reload; /* HLS m3u8 reload thread */ vlc_thread_t thread; /* HLS segment download thread */ @@ -152,7 +151,7 @@ static int Peek (stream_t *, const uint8_t **pp_peek, unsigned int i_peek); static int Control(stream_t *, int i_query, va_list); static ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer); -static ssize_t read_M3U8_from_url(stream_t *s, vlc_url_t *url, uint8_t **buffer); +static ssize_t read_M3U8_from_url(stream_t *s, const char *psz_url, uint8_t **buffer); static char *ReadLine(uint8_t *buffer, uint8_t **pos, size_t len); static int hls_Download(stream_t *s, segment_t *segment); @@ -163,50 +162,60 @@ static void* hls_Reload(void *); static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted); static void segment_Free(segment_t *segment); -static char *ConstructUrl(vlc_url_t *url); - /**************************************************************************** * ****************************************************************************/ -static const char *const ext[] = { - "#EXT-X-TARGETDURATION", - "#EXT-X-MEDIA-SEQUENCE", - "#EXT-X-KEY", - "#EXT-X-ALLOW-CACHE", - "#EXT-X-ENDLIST", - "#EXT-X-STREAM-INF", - "#EXT-X-DISCONTINUITY", - "#EXT-X-VERSION" -}; - static bool isHTTPLiveStreaming(stream_t *s) { - const uint8_t *peek, *peek_end; + const uint8_t *peek; - int64_t i_size = stream_Peek(s->p_source, &peek, 46); - if (i_size < 1) + int size = stream_Peek(s->p_source, &peek, 46); + if (size < 7) return false; - if (strncasecmp((const char*)peek, "#EXTM3U", 7) != 0) + if (memcmp(peek, "#EXTM3U", 7) != 0) return false; + peek += 7; + size -= 7; + /* Parse stream and search for * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */ - peek_end = peek + i_size; - while(peek <= peek_end) - { - if (*peek == '#') + while (size--) + { + static const char *const ext[] = { + "TARGETDURATION", + "MEDIA-SEQUENCE", + "KEY", + "ALLOW-CACHE", + "ENDLIST", + "STREAM-INF", + "DISCONTINUITY", + "VERSION" + }; + + if (*peek++ != '#') + continue; + + if (size < 6) + continue; + + if (memcmp(peek, "EXT-X-", 6)) + continue; + + peek += 6; + size -= 6; + + for (size_t i = 0; i < ARRAY_SIZE(ext); i++) { - for (unsigned int i = 0; i < ARRAY_SIZE(ext); i++) - { - char *p = strstr((const char*)peek, ext[i]); - if (p != NULL) - return true; - } + size_t len = strlen(ext[i]); + if (size < 0 || (size_t)size < len) + continue; + if (!memcmp(peek, ext[i], len)) + return true; } - peek++; - }; + } return false; } @@ -224,7 +233,12 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64 hls->sequence = 0; /* default is 0 */ hls->version = 1; /* default protocol version */ hls->b_cache = true; - vlc_UrlParse(&hls->url, uri, 0); + hls->url = strdup(uri); + if (hls->url == NULL) + { + free(hls); + return NULL; + } hls->psz_current_key_path = NULL; hls->segments = vlc_array_new(); vlc_array_append(hls_stream, hls); @@ -245,7 +259,7 @@ static void hls_Free(hls_stream_t *hls) } vlc_array_destroy(hls->segments); } - vlc_UrlClean(&hls->url); + free(hls->url); free(hls->psz_current_key_path); free(hls); } @@ -267,13 +281,12 @@ static hls_stream_t *hls_Copy(hls_stream_t *src, const bool b_cp_segments) dst->b_cache = src->b_cache; dst->psz_current_key_path = src->psz_current_key_path ? strdup( src->psz_current_key_path ) : NULL; - char *uri = ConstructUrl(&src->url); - if (uri == NULL) + dst->url = strdup(src->url); + if (dst->url == NULL) { free(dst); return NULL; } - vlc_UrlParse(&dst->url, uri, 0); if (!b_cp_segments) dst->segments = vlc_array_new(); vlc_mutex_init(&dst->lock); @@ -314,7 +327,7 @@ static hls_stream_t *hls_Find(vlc_array_t *hls_stream, hls_stream_t *hls_new) { /* compare */ if ((hls->id == hls_new->id) && - (hls->bandwidth == hls_new->bandwidth)) + ((hls->bandwidth == hls_new->bandwidth)||(hls_new->bandwidth==0))) return hls; } } @@ -357,7 +370,12 @@ static segment_t *segment_New(hls_stream_t* hls, const int duration, const char segment->size = 0; /* bytes */ segment->sequence = 0; segment->bandwidth = 0; - vlc_UrlParse(&segment->url, uri, 0); + segment->url = strdup(uri); + if (segment->url == NULL) + { + free(segment); + return NULL; + } segment->data = NULL; vlc_array_append(hls->segments, segment); vlc_mutex_init(&segment->lock); @@ -372,7 +390,7 @@ static void segment_Free(segment_t *segment) { vlc_mutex_destroy(&segment->lock); - vlc_UrlClean(&segment->url); + free(segment->url); free(segment->psz_key_path); if (segment->data) block_Release(segment->data); @@ -422,7 +440,7 @@ static int ChooseSegment(stream_t *s, const int current) int count = vlc_array_count(hls->segments); int i = p_sys->b_live ? count - 1 : 0; - while((i >= 0) && (i < count) && vlc_object_alive(s)) + while((i >= 0) && (i < count)) { segment_t *segment = segment_GetSegment(hls, i); assert(segment); @@ -445,7 +463,7 @@ static int ChooseSegment(stream_t *s, const int current) if (p_sys->b_live) i-- ; else - i++; + i++; } msg_Info(s, "Choose segment %d/%d (sequence=%d)", wanted, count, sequence); @@ -466,7 +484,8 @@ static char *parse_Attributes(const char *line, const char *attr) begin = p; do { - if (strncasecmp(begin, attr, strlen(attr)) == 0) + if (strncasecmp(begin, attr, strlen(attr)) == 0 + && begin[strlen(attr)] == '=') { /* =[,]* */ p = strchr(begin, ','); @@ -484,156 +503,59 @@ static char *parse_Attributes(const char *line, const char *attr) return NULL; } -static int hex2int(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return -1; -} - -static int string_to_IV(const char *string_hexa, uint8_t iv[AES_BLOCK_SIZE]) +static int string_to_IV(char *string_hexa, uint8_t iv[AES_BLOCK_SIZE]) { - const char *p = string_hexa; - uint8_t *d = iv; - unsigned int c; - - if (*p++ != '0') + unsigned long long iv_hi, iv_lo; + char *end = NULL; + if (*string_hexa++ != '0') return VLC_EGENERIC; - if (*p++ != 'x') + if (*string_hexa != 'x' && *string_hexa != 'X') return VLC_EGENERIC; - while (*p && *(p+1)) - { - c = hex2int(*p++) << 4; - c |= hex2int(*p++); - *d++ = c; - } - - return VLC_SUCCESS; -} - -static char *relative_URI(stream_t *s, const char *uri, const vlc_url_t *url) -{ - stream_sys_t *p_sys = s->p_sys; - char *psz_password = NULL; - char *psz_username = NULL; - char *psz_protocol = NULL; - char *psz_path = NULL; - char *psz_host = NULL; - char *psz_uri = NULL; - int i_port = -1; - - char *p = strchr(uri, ':'); - if (p != NULL) - return NULL; + string_hexa++; - /* Determine protocol to use */ - if (url && url->psz_protocol) - { - psz_protocol = strdup(url->psz_protocol); - i_port = url->i_port; - } - else if (p_sys->m3u8.psz_protocol) - { - psz_protocol = strdup(p_sys->m3u8.psz_protocol); - i_port = p_sys->m3u8.i_port; + size_t len = strlen(string_hexa); + if (len <= 16) { + iv_hi = 0; + iv_lo = strtoull(string_hexa, &end, 16); + if (end) + return VLC_EGENERIC; + } else { + iv_lo = strtoull(&string_hexa[len-16], NULL, 16); + if (end) + return VLC_EGENERIC; + string_hexa[len-16] = '\0'; + iv_hi = strtoull(string_hexa, NULL, 16); + if (end) + return VLC_EGENERIC; } - /* Determine host to use */ - if (url && url->psz_host) - psz_host = strdup(url->psz_host); - else if (p_sys->m3u8.psz_host) - psz_host = strdup(p_sys->m3u8.psz_host); - - /* Determine path to use */ - if (url && url->psz_path != NULL) - psz_path = strdup(url->psz_path); - else if (p_sys->m3u8.psz_path != NULL) - psz_path = strdup(p_sys->m3u8.psz_path); - - if ((psz_protocol == NULL) || - (psz_path == NULL) || - (psz_host == NULL)) - goto fail; - - p = strrchr(psz_path, '/'); - if (p) *p = '\0'; - - /* Determine credentials to use */ - if (url && url->psz_username) - psz_username = strdup(url->psz_username); - else if (p_sys->m3u8.psz_username) - psz_username = strdup(p_sys->m3u8.psz_username); - - if (url && url->psz_password) - psz_password = strdup(url->psz_password); - else if (p_sys->m3u8.psz_password) - psz_password = strdup(p_sys->m3u8.psz_password); - - /* */ - if (psz_password || psz_username) - { - if (asprintf(&psz_uri, "%s://%s:%s@%s:%d%s/%s", - psz_protocol, - psz_username ? psz_username : "", - psz_password ? psz_password : "", - psz_host, i_port, - psz_path, uri) < 0) - goto fail; - } - else - { - if (asprintf(&psz_uri, "%s://%s:%d%s/%s", - psz_protocol, psz_host, i_port, - psz_path, uri) < 0) - goto fail; + for (int i = 8; i ; --i) { + iv[ i] = iv_hi & 0xff; + iv[8+i] = iv_lo & 0xff; + iv_hi >>= 8; + iv_lo >>= 8; } -fail: - free(psz_password); - free(psz_username); - free(psz_protocol); - free(psz_path); - free(psz_host); - return psz_uri; + return VLC_SUCCESS; } -static char *ConstructUrl(vlc_url_t *url) +static char *relative_URI(const char *psz_url, const char *psz_path) { - if ((url->psz_protocol == NULL) || - (url->psz_path == NULL)) + assert(psz_url != NULL && psz_path != NULL); + //If the path is actually an absolute URL, don't do anything. + if (strncmp(psz_path, "http", 4) == 0) return NULL; - if (url->i_port <= 0) - { - if (strncmp(url->psz_protocol, "https", 5) == 0) - url->i_port = 443; - else - url->i_port = 80; - } - - char *psz_url = NULL; - if (url->psz_password || url->psz_username) - { - if (asprintf(&psz_url, "%s://%s:%s@%s:%d%s", - url->psz_protocol, - url->psz_username, url->psz_password, - url->psz_host, url->i_port, url->psz_path) < 0) - return NULL; - } - else - { - if (asprintf(&psz_url, "%s://%s:%d%s", - url->psz_protocol, - url->psz_host, url->i_port, url->psz_path) < 0) - return NULL; - } - - return psz_url; + char *path_end = strrchr(psz_url, '/'); + if (path_end == NULL) + return NULL; + unsigned int url_length = path_end - psz_url + 1; + char *psz_res = malloc(url_length + strlen(psz_path) + 1); + strncpy(psz_res, psz_url, url_length); + psz_res[url_length] = 0; + strcat(psz_res, psz_path); + return psz_res; } static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *duration) @@ -653,20 +575,23 @@ static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *durati return VLC_EGENERIC; int value; + char *endptr; if (hls->version < 3) { - value = strtol(token, NULL, 10); - if (errno == ERANGE) - { - *duration = -1; - return VLC_EGENERIC; - } - *duration = value; + errno = 0; + value = strtol(token, &endptr, 10); + if (token == endptr || errno == ERANGE) + { + *duration = -1; + return VLC_EGENERIC; + } + *duration = value; } else { - double d = strtod(token, (char **) NULL); - if (errno == ERANGE) + errno = 0; + double d = strtof(token, &endptr); + if (token == endptr || errno == ERANGE) { *duration = -1; return VLC_EGENERIC; @@ -675,14 +600,14 @@ static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *durati value = ((int)d) + 1; else value = ((int)d); + *duration = value; } /* Ignore the rest of the line */ - return VLC_SUCCESS; } -static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration, const char *uri) +static int parse_AddSegment(hls_stream_t *hls, const int duration, const char *uri) { assert(hls); assert(uri); @@ -690,7 +615,7 @@ static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration, /* Store segment information */ vlc_mutex_lock(&hls->lock); - char *psz_uri = relative_URI(s, uri, &hls->url); + char *psz_uri = relative_URI(hls->url, uri); segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri); if (segment) @@ -753,7 +678,7 @@ static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, msg_Info(s, "bandwidth adaptation detected (program-id=%d, bandwidth=%"PRIu64").", id, bw); - char *psz_uri = relative_URI(s, uri, NULL); + char *psz_uri = relative_URI(s->p_sys->m3u8, uri); *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri); @@ -875,7 +800,10 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read) */ if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC) + { + msg_Err(s, "IV invalid"); err = VLC_EGENERIC; + } else hls->b_iv_loaded = true; free(value); @@ -953,6 +881,13 @@ static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read) return VLC_SUCCESS; } +static int hls_CompareStreams( const void* a, const void* b ) +{ + hls_stream_t* stream_a = *(hls_stream_t**)a; + hls_stream_t* stream_b = *(hls_stream_t**)b; + return stream_a->bandwidth > stream_b->bandwidth; +} + /* The http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 * document defines the following new tags: EXT-X-TARGETDURATION, * EXT-X-MEDIA-SEQUENCE, EXT-X-KEY, EXT-X-PROGRAM-DATE-TIME, EXT-X- @@ -1047,7 +982,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const /* Download playlist file from server */ uint8_t *buf = NULL; - ssize_t len = read_M3U8_from_url(s, &hls->url, &buf); + ssize_t len = read_M3U8_from_url(s, hls->url, &buf); if (len < 0) err = VLC_EGENERIC; else @@ -1074,7 +1009,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const if (p_begin >= p_end) break; - } while ((err == VLC_SUCCESS) && vlc_object_alive(s)); + } while (err == VLC_SUCCESS); } else @@ -1087,7 +1022,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const else { /* No Meta playlist used */ - hls = hls_New(streams, 0, 0, NULL); + hls = hls_New(streams, 0, 0, p_sys->m3u8); if (hls) { /* Get TARGET-DURATION first */ @@ -1140,7 +1075,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const err = parse_EndList(s, hls); else if ((strncmp(line, "#", 1) != 0) && (*line != '\0') ) { - err = parse_AddSegment(s, hls, segment_duration, line); + err = parse_AddSegment(hls, segment_duration, line); segment_duration = -1; /* reset duration */ } @@ -1150,7 +1085,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const if (p_begin >= p_end) break; - } while ((err == VLC_SUCCESS) && vlc_object_alive(s)); + } while (err == VLC_SUCCESS); free(line); } @@ -1161,9 +1096,6 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const static int hls_DownloadSegmentKey(stream_t *s, segment_t *seg) { - uint8_t aeskey[32]; /* AES-512 can use up to 32 bytes */ - ssize_t len; - stream_t *p_m3u8 = stream_UrlNew(s, seg->psz_key_path); if (p_m3u8 == NULL) { @@ -1171,18 +1103,14 @@ static int hls_DownloadSegmentKey(stream_t *s, segment_t *seg) return VLC_EGENERIC; } - len = stream_Read(p_m3u8, aeskey, sizeof(aeskey)); + int len = stream_Read(p_m3u8, seg->aes_key, sizeof(seg->aes_key)); + stream_Delete(p_m3u8); if (len != AES_BLOCK_SIZE) { - msg_Err(s, "The AES key loaded doesn't have the right size (%zd)", len); - stream_Delete(p_m3u8); + msg_Err(s, "The AES key loaded doesn't have the right size (%d)", len); return VLC_EGENERIC; } - memcpy(seg->psz_AES_key, aeskey, AES_BLOCK_SIZE); - - stream_Delete(p_m3u8); - return VLC_SUCCESS; } @@ -1196,6 +1124,8 @@ static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls) { prev_seg = seg; seg = segment_GetSegment(hls, i); + if (seg == NULL ) + continue; if (seg->psz_key_path == NULL) continue; /* No key to load ? continue */ if (seg->b_key_loaded) @@ -1205,7 +1135,7 @@ static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls) * try to copy it, and don't load the key */ if (prev_seg && prev_seg->b_key_loaded && strcmp(seg->psz_key_path, prev_seg->psz_key_path) == 0) { - memcpy(seg->psz_AES_key, prev_seg->psz_AES_key, AES_BLOCK_SIZE); + memcpy(seg->aes_key, prev_seg->aes_key, AES_BLOCK_SIZE); seg->b_key_loaded = true; continue; } @@ -1244,8 +1174,8 @@ static int hls_DecodeSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segm } /* Set key */ - i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->psz_AES_key, - sizeof(segment->psz_AES_key)); + i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->aes_key, + sizeof(segment->aes_key)); if (i_gcrypt_err) { msg_Err(s, "gcry_cipher_setkey failed: %s", gpg_strerror(i_gcrypt_err)); @@ -1328,7 +1258,7 @@ static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams) /* Download playlist file from server */ uint8_t *buf = NULL; - ssize_t len = read_M3U8_from_url(s, &dst->url, &buf); + ssize_t len = read_M3U8_from_url(s, dst->url, &buf); if (len < 0) err = VLC_EGENERIC; else @@ -1360,35 +1290,35 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t * { vlc_mutex_lock(&segment->lock); - assert(p->url.psz_path); - assert(segment->url.psz_path); + assert(p->url); + assert(segment->url); /* they should be the same */ if ((p->sequence != segment->sequence) || (p->duration != segment->duration) || - (strcmp(p->url.psz_path, segment->url.psz_path) != 0)) + (strcmp(p->url, segment->url) != 0)) { msg_Warn(s, "existing segment found with different content - resetting"); msg_Warn(s, "- sequence: new=%d, old=%d", p->sequence, segment->sequence); msg_Warn(s, "- duration: new=%d, old=%d", p->duration, segment->duration); - msg_Warn(s, "- file: new=%s", p->url.psz_path); - msg_Warn(s, " old=%s", segment->url.psz_path); + msg_Warn(s, "- file: new=%s", p->url); + msg_Warn(s, " old=%s", segment->url); /* Resetting content */ - char *psz_url = ConstructUrl(&p->url); - if (psz_url == NULL) + segment->sequence = p->sequence; + segment->duration = p->duration; + free(segment->url); + segment->url = strdup(p->url); + if ( segment->url == NULL ) { msg_Err(s, "Failed updating segment %d - skipping it", p->sequence); segment_Free(p); vlc_mutex_unlock(&segment->lock); continue; } - segment->sequence = p->sequence; - segment->duration = p->duration; - vlc_UrlClean(&segment->url); - vlc_UrlParse(&segment->url, psz_url, 0); /* We must free the content, because if the key was not downloaded, content can't be decrypted */ - if (segment->data) + if ((p->psz_key_path || p->b_key_loaded) && + segment->data) { block_Release(segment->data); segment->data = NULL; @@ -1396,7 +1326,6 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t * free(segment->psz_key_path); segment->psz_key_path = p->psz_key_path ? strdup(p->psz_key_path) : NULL; segment_Free(p); - free(psz_url); } vlc_mutex_unlock(&segment->lock); } @@ -1548,10 +1477,10 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se return VLC_EGENERIC; } mtime_t duration = mdate() - start; - if (hls->bandwidth == 0) + if (hls->bandwidth == 0 && segment->duration > 0) { /* Try to estimate the bandwidth for this stream */ - hls->bandwidth = (uint64_t)((double)segment->size / ((double)duration / 1000000.0)); + hls->bandwidth = (uint64_t)(((double)segment->size * 8) / ((double)segment->duration)); } /* If the segment is encrypted, decode it */ @@ -1566,12 +1495,7 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se msg_Info(s, "downloaded segment %d from stream %d", segment->sequence, *cur_stream); - /* check for division by zero */ - double ms = (double)duration / 1000.0; /* ms */ - if (ms <= 0.0) - return VLC_SUCCESS; - - uint64_t bw = ((double)(segment->size * 8) / ms) * 1000; /* bits / s */ + uint64_t bw = segment->size * 8 * 1000000 / __MAX(1, duration); /* bits / s */ p_sys->bandwidth = bw; if (p_sys->b_meta && (hls->bandwidth != bw)) { @@ -1722,35 +1646,26 @@ static void* hls_Reload(void *p_this) static int Prefetch(stream_t *s, int *current) { stream_sys_t *p_sys = s->p_sys; - int stream; - - /* Try to pick best matching stream */; -again: - stream = *current; + int stream = *current; - hls_stream_t *hls = hls_Get(p_sys->hls_stream, *current); + hls_stream_t *hls = hls_Get(p_sys->hls_stream, stream); if (hls == NULL) return VLC_EGENERIC; - segment_t *segment = segment_GetSegment(hls, p_sys->download.segment); - if (segment == NULL ) - return VLC_EGENERIC; - - if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS) + if (vlc_array_count(hls->segments) == 0) return VLC_EGENERIC; + else if (vlc_array_count(hls->segments) == 1 && p_sys->b_live) + msg_Warn(s, "Only 1 segment available to prefetch in live stream; may stall"); - /* Found better bandwidth match, try again */ - if (*current != stream) - goto again; - - /* Download first 2 segments of this HLS stream */ - stream = *current; - for (int i = 0; i < 2; i++) + /* Download first 2 segments of this HLS stream if they exist */ + for (int i = 0; i < __MIN(vlc_array_count(hls->segments), 2); i++) { segment_t *segment = segment_GetSegment(hls, p_sys->download.segment); if (segment == NULL ) return VLC_EGENERIC; + /* It is useless to lock the segment here, as Prefetch is called before + download and playlit thread are started. */ if (segment->data) { p_sys->download.segment++; @@ -1783,13 +1698,7 @@ static int hls_Download(stream_t *s, segment_t *segment) { assert(segment); - /* Construct URL */ - char *psz_url = ConstructUrl(&segment->url); - if (psz_url == NULL) - return VLC_ENOMEM; - - stream_t *p_ts = stream_UrlNew(s, psz_url); - free(psz_url); + stream_t *p_ts = stream_UrlNew(s, segment->url); if (p_ts == NULL) return VLC_EGENERIC; @@ -1809,6 +1718,10 @@ static int hls_Download(stream_t *s, segment_t *segment) uint64_t size; do { + /* NOTE: Beware the size reported for a segment by the HLS server may not + * be correct, when downloading the segment data. Therefore check the size + * and enlarge the segment data block if necessary. + */ size = stream_Size(p_ts); if (size > segment->size) { @@ -1879,17 +1792,12 @@ static ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer) return total_bytes; } -static ssize_t read_M3U8_from_url(stream_t *s, vlc_url_t *url, uint8_t **buffer) +static ssize_t read_M3U8_from_url(stream_t *s, const char* psz_url, uint8_t **buffer) { assert(*buffer == NULL); /* Construct URL */ - char *psz_url = ConstructUrl(url); - if (psz_url == NULL) - return VLC_ENOMEM; - stream_t *p_m3u8 = stream_UrlNew(s, psz_url); - free(psz_url); if (p_m3u8 == NULL) return VLC_EGENERIC; @@ -1910,21 +1818,27 @@ static char *ReadLine(uint8_t *buffer, uint8_t **pos, const size_t len) while (p < end) { - if ((*p == '\n') || (*p == '\0')) + if ((*p == '\r') || (*p == '\n') || (*p == '\0')) break; p++; } - /* copy line excluding \n or \0 */ + /* copy line excluding \r \n or \0 */ line = strndup((char *)begin, p - begin); - if (*p == '\0') - *pos = end; - else + while ((*p == '\r') || (*p == '\n') || (*p == '\0')) { - /* next pass start after \n */ - p++; - *pos = p; + if (*p == '\0') + { + *pos = end; + break; + } + else + { + /* next pass start after \r and \n */ + p++; + *pos = p; + } } return line; @@ -1957,8 +1871,17 @@ static int Open(vlc_object_t *p_this) free(p_sys); return VLC_ENOMEM; } - vlc_UrlParse(&p_sys->m3u8, psz_uri, 0); - free(psz_uri); + p_sys->m3u8 = psz_uri; + + char *new_path; + if (asprintf(&new_path, "%s.ts", s->psz_path) < 0) + { + free(p_sys->m3u8); + free(p_sys); + return VLC_ENOMEM; + } + free(s->psz_path); + s->psz_path = new_path; p_sys->bandwidth = 0; p_sys->b_live = true; @@ -1968,7 +1891,7 @@ static int Open(vlc_object_t *p_this) p_sys->hls_stream = vlc_array_new(); if (p_sys->hls_stream == NULL) { - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); free(p_sys); return VLC_ENOMEM; } @@ -1989,6 +1912,10 @@ static int Open(vlc_object_t *p_this) goto fail; } free(buffer); + /* HLS standard doesn't provide any guaranty about streams + being sorted by bandwidth, so we sort them */ + qsort( p_sys->hls_stream->pp_elems, p_sys->hls_stream->i_count, + sizeof( hls_stream_t* ), &hls_CompareStreams ); /* Choose first HLS stream to start with */ int current = p_sys->playback.stream = 0; @@ -2008,7 +1935,6 @@ static int Open(vlc_object_t *p_this) goto fail; } - p_sys->download.stream = current; p_sys->playback.stream = current; p_sys->download.seek = -1; @@ -2053,7 +1979,7 @@ fail: vlc_array_destroy(p_sys->hls_stream); /* */ - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); free(p_sys); return VLC_EGENERIC; } @@ -2089,7 +2015,7 @@ static void Close(vlc_object_t *p_this) vlc_array_destroy(p_sys->hls_stream); /* */ - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); if (p_sys->peeked) block_Release (p_sys->peeked); free(p_sys); @@ -2111,21 +2037,23 @@ static segment_t *GetSegment(stream_t *s) segment = segment_GetSegment(hls, p_sys->playback.segment); if (segment != NULL) { + vlc_mutex_lock(&segment->lock); /* This segment is ready? */ if (segment->data != NULL) { + vlc_mutex_unlock(&segment->lock); p_sys->b_cache = hls->b_cache; vlc_mutex_unlock(&hls->lock); goto check; } + vlc_mutex_unlock(&segment->lock); } vlc_mutex_unlock(&hls->lock); } /* Was the HLS stream changed to another bitrate? */ - int i_stream = 0; segment = NULL; - while(vlc_object_alive(s)) + for (int i_stream = 0; i_stream < vlc_array_count(p_sys->hls_stream); i_stream++) { /* Is the next segment ready */ hls_stream_t *hls = hls_Get(p_sys->hls_stream, i_stream); @@ -2144,30 +2072,29 @@ static segment_t *GetSegment(stream_t *s) int i_segment = p_sys->download.segment; vlc_mutex_unlock(&p_sys->download.lock_wait); + vlc_mutex_lock(&segment->lock); /* This segment is ready? */ if ((segment->data != NULL) && (p_sys->playback.segment < i_segment)) { p_sys->playback.stream = i_stream; p_sys->b_cache = hls->b_cache; + vlc_mutex_unlock(&segment->lock); vlc_mutex_unlock(&hls->lock); goto check; } + vlc_mutex_unlock(&segment->lock); vlc_mutex_unlock(&hls->lock); if (!p_sys->b_meta) break; - - /* Was the stream changed to another bitrate? */ - i_stream++; - if (i_stream >= vlc_array_count(p_sys->hls_stream)) - break; } /* */ return NULL; check: /* sanity check */ + assert(segment->data); if (segment->data->i_buffer == 0) { vlc_mutex_lock(&hls->lock); @@ -2184,12 +2111,12 @@ check: return segment; } -static int segment_RestorePos( segment_t *segment ) +static int segment_RestorePos(segment_t *segment) { - if( segment->data ) + if (segment->data) { - uint64_t size = segment->size -segment->data->i_buffer; - if( size > 0 ) + uint64_t size = segment->size - segment->data->i_buffer; + if (size > 0) { segment->data->i_buffer += size; segment->data->p_buffer -= size; @@ -2198,10 +2125,11 @@ static int segment_RestorePos( segment_t *segment ) return VLC_SUCCESS; } +/* p_read might be NULL if caller wants to skip data */ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read) { stream_sys_t *p_sys = s->p_sys; - ssize_t copied = 0; + ssize_t used = 0; do { @@ -2221,7 +2149,7 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read) segment->data = NULL; } else - segment_RestorePos( segment ); + segment_RestorePos(segment); p_sys->playback.segment++; vlc_mutex_unlock(&segment->lock); @@ -2245,17 +2173,18 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read) if (len > 0) { - memcpy(p_read + copied, segment->data->p_buffer, len); + if (p_read) /* if NULL, then caller skips data */ + memcpy(p_read + used, segment->data->p_buffer, len); segment->data->i_buffer -= len; segment->data->p_buffer += len; - copied += len; + used += len; i_read -= len; } vlc_mutex_unlock(&segment->lock); - } while ((i_read > 0) && vlc_object_alive(s)); + } while (i_read > 0); - return copied; + return used; } static int Read(stream_t *s, void *buffer, unsigned int i_read) @@ -2268,15 +2197,7 @@ static int Read(stream_t *s, void *buffer, unsigned int i_read) if (p_sys->b_error) return 0; - if (buffer == NULL) - { - /* caller skips data, get big enough buffer */ - msg_Warn(s, "buffer is NULL (allocate %d)", i_read); - buffer = calloc(1, i_read); - if (buffer == NULL) - return 0; /* NO MEMORY left*/ - } - + /* NOTE: buffer might be NULL if caller wants to skip data */ length = hls_Read(s, (uint8_t*) buffer, i_read); if (length < 0) return 0; @@ -2326,6 +2247,7 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek) peeked = block_Realloc (peeked, 0, i_peek); if (peeked == NULL) return 0; + p_sys->peeked = peeked; memcpy(peeked->p_buffer, p_buff, i_buff); curlen = i_buff; @@ -2336,7 +2258,7 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek) p_buff = peeked->p_buffer; *pp_peek = p_buff; - while ((curlen < i_peek) && vlc_object_alive(s)) + while (curlen < i_peek) { nsegment = GetSegment(s); if (nsegment == NULL) @@ -2417,7 +2339,6 @@ static uint64_t GetStreamSize(stream_t *s) return size; } - static int segment_Seek(stream_t *s, const uint64_t pos) { stream_sys_t *p_sys = s->p_sys; @@ -2433,16 +2354,12 @@ static int segment_Seek(stream_t *s, const uint64_t pos) uint64_t size = hls->size; int count = vlc_array_count(hls->segments); - /* restore current segment to start position */ - segment_t *segment = segment_GetSegment(hls, p_sys->playback.segment); - if (segment == NULL) + segment_t *currentSegment = segment_GetSegment(hls, p_sys->playback.segment); + if (currentSegment == NULL) { vlc_mutex_unlock(&hls->lock); return VLC_EGENERIC; } - vlc_mutex_lock(&segment->lock); - segment_RestorePos( segment ); - vlc_mutex_unlock(&segment->lock); for (int n = 0; n < count; n++) { @@ -2457,7 +2374,7 @@ static int segment_Seek(stream_t *s, const uint64_t pos) length += segment->duration * (hls->bandwidth/8); vlc_mutex_unlock(&segment->lock); - if (!b_found && (pos <= length)) + if (pos <= length) { if (count - n >= 3) { @@ -2481,15 +2398,22 @@ static int segment_Seek(stream_t *s, const uint64_t pos) /* */ if (b_found) { - /* restore segment to start position */ + + /* restore current segment to start position */ + vlc_mutex_lock(¤tSegment->lock); + segment_RestorePos(currentSegment); + vlc_mutex_unlock(¤tSegment->lock); + + /* restore seeked segment to start position */ segment_t *segment = segment_GetSegment(hls, p_sys->playback.segment); if (segment == NULL) { vlc_mutex_unlock(&hls->lock); return VLC_EGENERIC; } + vlc_mutex_lock(&segment->lock); - segment_RestorePos( segment ); + segment_RestorePos(segment); vlc_mutex_unlock(&segment->lock); /* start download at current playback segment */ @@ -2499,14 +2423,12 @@ static int segment_Seek(stream_t *s, const uint64_t pos) vlc_mutex_lock(&p_sys->download.lock_wait); p_sys->download.seek = p_sys->playback.segment; vlc_cond_signal(&p_sys->download.wait); - vlc_mutex_unlock(&p_sys->download.lock_wait); /* Wait for download to be finished */ - vlc_mutex_lock(&p_sys->download.lock_wait); msg_Info(s, "seek to segment %d", p_sys->playback.segment); - while (((p_sys->download.seek != -1) || - (p_sys->download.segment - p_sys->playback.segment < 3)) && - (p_sys->download.segment < (count - 6))) + while ((p_sys->download.seek != -1) || + ((p_sys->download.segment - p_sys->playback.segment < 3) && + (p_sys->download.segment < count))) { vlc_cond_wait(&p_sys->download.wait, &p_sys->download.lock_wait); if (!vlc_object_alive(s) || s->b_error) break;