Commit 2d9e01b4 authored by Jean-Paul Saman's avatar Jean-Paul Saman
Browse files

stream_filter/httplive.c: rewrite parsing of HLS m3u8 files

The parsing of .m3u8 HLS files was too position dependend and did not work well
with some implementations of HLS playlist files. Parsing and reading the .m3u8 file
are now separated.
parent 9ad9fe48
......@@ -136,13 +136,16 @@ static int Read (stream_t *, void *p_read, unsigned int i_read);
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 access_ReadM3U8(stream_t *s, vlc_url_t *url, uint8_t **buffer);
static ssize_t ReadM3U8(stream_t *s, uint8_t **buffer);
static char *ReadLine(uint8_t *buffer, uint8_t **remain, size_t len);
static int AccessOpen(stream_t *s, vlc_url_t *url);
static void AccessClose(stream_t *s);
static char *AccessReadLine(access_t *p_access, uint8_t *psz_tmp, size_t i_len);
static int AccessDownload(stream_t *s, segment_t *segment);
static void* hls_Thread(vlc_object_t *);
static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls);
static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted);
static void segment_Free(segment_t *segment);
......@@ -349,7 +352,7 @@ static segment_t *segment_Find(hls_stream_t *hls, int sequence)
return NULL;
}
static int live_ChooseSegment(stream_t *s, int current)
static int ChooseSegment(stream_t *s, int current)
{
stream_sys_t *p_sys = (stream_sys_t *)s->p_sys;
hls_stream_t *hls = hls_Get(p_sys->hls_stream, current);
......@@ -361,21 +364,34 @@ static int live_ChooseSegment(stream_t *s, int current)
int wanted = -1;
int duration = 0;
int count = vlc_array_count(hls->segments);
for (int i = count; i >= 0; i--)
int i = p_sys->b_live ? count - 1 : 0;
while((i >= 0) && (i < count) && vlc_object_alive(s))
{
segment_t *segment = segment_GetSegment(hls, i);
if (segment)
assert(segment);
if (segment->duration > hls->duration)
{
duration += segment->duration;
if (duration >= 3 * hls->duration)
{
/* Start point found */
wanted = i;
break;
}
msg_Err(s, "EXTINF:%d duration is larger then EXT-X-TARGETDURATION:%d",
segment->duration, hls->duration);
}
duration += segment->duration;
if (duration >= 3 * hls->duration)
{
/* Start point found */
wanted = p_sys->b_live ? i : 0;
break;
}
if (p_sys->b_live)
i-- ;
else
i++;
}
msg_Info(s, "Choose segment %d/%d", wanted, count);
return wanted;
}
......@@ -451,30 +467,32 @@ fail:
return NULL;
}
static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri)
static int parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri)
{
stream_sys_t *p_sys = s->p_sys;
assert(hls);
assert(p_read);
int duration;
int ret = sscanf(p_read, "#EXTINF:%d,", &duration);
if (ret != 1)
{
msg_Err(s, "expected #EXTINF:<s>,");
p_sys->b_error = true;
return;
}
/* strip of #EXTINF: */
char *p_next = NULL;
char *token = strtok_r(p_read, ":", &p_next);
if (token == NULL)
return VLC_EGENERIC;
/* read duration */
token = strtok_r(NULL, ",", &p_next);
if (token == NULL)
return VLC_EGENERIC;
int duration = atoi(token);
/* Ignore the rest of the line */
/* Store segment information */
char *psz_path = NULL;
if (hls->url.psz_path != NULL)
{
psz_path = strdup(hls->url.psz_path);
if (psz_path == NULL)
{
p_sys->b_error = true;
return;
}
return VLC_ENOMEM;
char *p = strrchr(psz_path, '/');
if (p) *p = '\0';
}
......@@ -485,14 +503,10 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea
segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
if (segment)
segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1;
if (duration > hls->duration)
{
msg_Err(s, "EXTINF:%d duration is larger then EXT-X-TARGETDURATION:%d",
duration, hls->duration);
}
vlc_mutex_unlock(&hls->lock);
free(psz_uri);
return segment ? VLC_SUCCESS : VLC_ENOMEM;
}
static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
......@@ -511,21 +525,20 @@ static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
return VLC_SUCCESS;
}
static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
char *p_read, char *uri)
static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
hls_stream_t **hls, char *p_read, char *uri)
{
stream_sys_t *p_sys = s->p_sys;
int id;
uint64_t bw;
char *attr;
assert(*hls == NULL);
attr = parse_Attributes(p_read, "PROGRAM-ID");
if (attr == NULL)
{
msg_Err(s, "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>");
p_sys->b_error = true;
return;
return VLC_EGENERIC;
}
id = atol(attr);
free(attr);
......@@ -534,8 +547,7 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
if (attr == NULL)
{
msg_Err(s, "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
p_sys->b_error = true;
return;
return VLC_EGENERIC;
}
bw = atoll(attr);
free(attr);
......@@ -543,19 +555,18 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
if (bw == 0)
{
msg_Err(s, "#EXT-X-STREAM-INF: bandwidth cannot be 0");
p_sys->b_error = true;
return;
return VLC_EGENERIC;
}
msg_Info(s, "bandwidth adaption detected (program-id=%d, bandwidth=%"PRIu64").", id, bw);
char *psz_uri = relative_URI(s, uri, NULL);
hls_stream_t *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri);
if (hls == NULL)
p_sys->b_error = true;
*hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri);
free(psz_uri);
return (*hls == NULL) ? VLC_ENOMEM : VLC_SUCCESS;
}
static int parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
......@@ -684,328 +695,207 @@ static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read)
return VLC_SUCCESS;
}
static void parse_M3U8ExtLine(stream_t *s, hls_stream_t *hls, char *line)
{
if (*line == '#')
{
int err = VLC_SUCCESS;
if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
err = parse_TargetDuration(s, hls, line);
else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0)
err = parse_MediaSequence(s, hls, line);
else if (strncmp(line, "#EXT-X-KEY", 10) == 0)
err = parse_Key(s, hls, line);
else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 24) == 0)
err = parse_ProgramDateTime(s, hls, line);
else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 18) == 0)
err = parse_AllowCache(s, hls, line);
else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
err = parse_Discontinuity(s, hls, line);
else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
err = parse_Version(s, hls, line);
else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
err = parse_EndList(s, hls);
if (err != VLC_SUCCESS)
s->p_sys->b_error = true;
}
}
#define HTTPLIVE_MAX_LINE 4096
static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls)
/* 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-
* ALLOW-CACHE, EXT-X-STREAM-INF, EXT-X-ENDLIST, EXT-X-DISCONTINUITY,
* and EXT-X-VERSION.
*/
static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, ssize_t len)
{
stream_sys_t *p_sys = s->p_sys;
uint8_t *p_read, *p_begin, *p_end;
/* Download new playlist file from server */
if (AccessOpen(s, &hls->url) != VLC_SUCCESS)
return VLC_EGENERIC;
assert(streams);
assert(buffer);
/* Parse the rest of the reply */
uint8_t *tmp = calloc(1, HTTPLIVE_MAX_LINE);
if (tmp == NULL)
{
AccessClose(s);
msg_Dbg(s, "parse_M3U8\n%s", buffer);
p_begin = buffer;
p_end = p_begin + len;
char *line = ReadLine(p_begin, &p_read, p_end - p_begin);
if (line == NULL)
return VLC_ENOMEM;
}
char *line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (strncmp(line, "#EXTM3U", 7) != 0)
{
msg_Err(s, "missing #EXTM3U tag");
goto error;
msg_Err(s, "missing #EXTM3U tag .. aborting");
free(line);
return VLC_EGENERIC;
}
free(line);
line = NULL;
for( ; ; )
/* What is the version ? */
int version = 1;
uint8_t *p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-VERSION:");
if (p != NULL)
{
line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (line == NULL)
uint8_t *tmp = NULL;
char *psz_version = ReadLine(p, &tmp, p_end - p);
if (psz_version == NULL)
return VLC_ENOMEM;
int ret = sscanf((const char*)psz_version, "#EXT-X-VERSION:%d", &version);
if (ret != 1)
{
msg_Dbg(s, "end of data");
break;
}
if (!vlc_object_alive(s))
goto error;
/* some more checks for actual data */
if (strncmp(line, "#EXTINF", 7) == 0)
{
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (uri == NULL)
p_sys->b_error = true;
else
{
parse_SegmentInformation(s, hls, line, uri);
free(uri);
}
msg_Warn(s, "#EXT-X-VERSION: no protocol version found, assuming version 1.");
version = 1;
}
else
{
parse_M3U8ExtLine(s, hls, line);
}
/* Error during m3u8 parsing abort */
if (p_sys->b_error)
goto error;
free(line);
free(psz_version);
p = NULL;
}
free(line);
free(tmp);
AccessClose(s);
return VLC_SUCCESS;
error:
free(line);
free(tmp);
AccessClose(s);
return VLC_EGENERIC;
}
static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
{
stream_sys_t *p_sys = s->p_sys;
assert(*streams);
/* Download new playlist file from server */
if (AccessOpen(s, &p_sys->m3u8) != VLC_SUCCESS)
return VLC_EGENERIC;
/* Is it a live stream ? */
p_sys->b_live = (strstr((const char *)buffer, "#EXT-X-ENDLIST") == NULL) ? true : false;
/* Parse the rest of the reply */
uint8_t *tmp = calloc(1, HTTPLIVE_MAX_LINE);
if (tmp == NULL)
{
AccessClose(s);
return VLC_ENOMEM;
}
/* Is it a meta index file ? */
bool b_meta = (strstr((const char *)buffer, "#EXT-X-STREAM-INF") == NULL) ? false : true;
char *line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (strncmp(line, "#EXTM3U", 7) != 0)
{
msg_Err(s, "missing #EXTM3U tag");
goto error;
}
free(line);
line = NULL;
if (b_meta)
msg_Info(s, "Meta playlist");
else
msg_Info(s, "%s Playlist HLS protocol version: %d", p_sys->b_live ? "Live": "VOD", version);
for( ; ; )
/* */
int err = VLC_SUCCESS;
do
{
line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
/* Next line */
p_begin = p_read;
line = ReadLine(p_begin, &p_read, p_end - p_begin);
if (line == NULL)
{
msg_Dbg(s, "end of data");
break;
}
if (!vlc_object_alive(s))
goto error;
/* */
p_begin = p_read;
/* some more checks for actual data */
if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0)
/* M3U8 Meta Index file */
if (b_meta)
{
p_sys->b_meta = true;
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (uri == NULL)
p_sys->b_error = true;
else
if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0)
{
parse_StreamInformation(s, streams, line, uri);
free(uri);
}
}
else if (strncmp(line, "#EXTINF", 7) == 0)
{
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (uri == NULL)
p_sys->b_error = true;
else
{
hls_stream_t *hls = hls_GetLast(*streams);
if (hls)
parse_SegmentInformation(s, hls, line, uri);
p_sys->b_meta = true;
char *uri = ReadLine(p_begin, &p_read, p_end - p_begin);
if (uri == NULL)
err = VLC_ENOMEM;
else
p_sys->b_error = true;
free(uri);
{
hls_stream_t *hls = NULL;
err = parse_StreamInformation(s, &streams, &hls, line, uri);
free(uri);
/* Download playlist file from server */
uint8_t *buf = NULL;
ssize_t len = access_ReadM3U8(s, &hls->url, &buf);
if (len < 0)
err = VLC_EGENERIC;
else
{
/* Parse HLS m3u8 content. */
err = parse_M3U8(s, streams, buf, len);
free(buf);
}
if (hls)
{
hls->version = version;
if (!p_sys->b_live)
hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
}
}
}
}
else
{
hls_stream_t *hls = hls_GetLast(*streams);
if ((hls == NULL) && (!p_sys->b_meta))
hls_stream_t *hls = hls_GetLast(streams);
if (hls == NULL)
{
hls = hls_New(*streams, -1, -1, NULL);
/* No Meta playlist used */
hls = hls_New(streams, 0, -1, NULL);
if (hls == NULL)
{
p_sys->b_error = true;
return VLC_ENOMEM;
msg_Err(s, "No HLS structure created");
err = VLC_ENOMEM;
break;
}
/* Get TARGET-DURATION first */
p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-TARGETDURATION:");
if (p)
{
uint8_t *p_rest = NULL;
char *psz_duration = ReadLine(p, &p_rest, p_end - p);
if (psz_duration)
{
err = parse_TargetDuration(s, hls, psz_duration);
free(psz_duration);
p = NULL;
}
}
hls->version = version;
}
parse_M3U8ExtLine(s, hls, line);
}
/* Error during m3u8 parsing abort */
if (p_sys->b_error)
goto error;
if (strncmp(line, "#EXTINF", 7) == 0)
{
char *uri = ReadLine(p_begin, &p_read, p_end - p_begin);
if (uri == NULL)
err = VLC_EGENERIC;
else
{
err = parse_SegmentInformation(s, hls, line, uri);
free(uri);
}
p_begin = p_read;
}
else if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
err = parse_TargetDuration(s, hls, line);
else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0)
err = parse_MediaSequence(s, hls, line);
else if (strncmp(line, "#EXT-X-KEY", 10) == 0)
err = parse_Key(s, hls, line);
else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 24) == 0)
err = parse_ProgramDateTime(s, hls, line);
else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 18) == 0)
err = parse_AllowCache(s, hls, line);
else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
err = parse_Discontinuity(s, hls, line);
else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
err = parse_Version(s, hls, line);
else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
err = parse_EndList(s, hls);
}
free(line);
}
line = NULL;
free(line);
free(tmp);
AccessClose(s);
return VLC_SUCCESS;
if (p_begin >= p_end)
break;
} while ((err == VLC_SUCCESS) && vlc_object_alive(s));
error:
free(line);
free(tmp);
AccessClose(s);
return VLC_EGENERIC;
return err;
}
#undef HTTPLIVE_MAX_LINE
/* 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-
* ALLOW-CACHE, EXT-X-STREAM-INF, EXT-X-ENDLIST, EXT-X-DISCONTINUITY,
* and EXT-X-VERSION.
*/
static int parse_HTTPLiveStreaming(stream_t *s)
static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
{
stream_sys_t *p_sys = s->p_sys;
char *p_read, *p_begin, *p_end;
assert(p_sys->hls_stream);
p_begin = p_read = stream_ReadLine(s->p_source);
if (!p_begin)
return VLC_ENOMEM;
/* */
int i_len = strlen(p_begin);
p_end = p_read + i_len;
assert(*streams);
if (strncmp(p_read, "#EXTM3U", 7) != 0)
{
msg_Err(s, "missing #EXTM3U tag .. aborting");
free(p_begin);
/* Download new playlist file from server */
uint8_t *buffer = NULL;
ssize_t len = access_ReadM3U8(s, &p_sys->m3u8, &buffer);
if (len < 0)
return VLC_EGENERIC;
}
do {
free(p_begin);
if (p_sys->b_error)
return VLC_EGENERIC;
/* Next line */
p_begin = stream_ReadLine(s->p_source);
if (p_begin == NULL)
break;
i_len = strlen(p_begin);
p_read = p_begin;
p_end = p_read + i_len;
if (strncmp(p_read, "#EXT-X-STREAM-INF", 17) == 0)
{
p_sys->b_meta = true;
char *uri = stream_ReadLine(s->p_source);
if (uri == NULL)
p_sys->b_error = true;
else
{
parse_StreamInformation(s, &p_sys->hls_stream, p_read, uri);
free(uri);
}
}
else if (strncmp(p_read, "#EXTINF", 7) == 0)