]> git.sesse.net Git - vlc/blobdiff - modules/stream_filter/httplive.c
stream_filter/httplive.c: do not crash on seeking
[vlc] / modules / stream_filter / httplive.c
index 9e61255d5b3e5a44616d13e9dcf04cc68a719c6f..ae4ece2a10bd2320a4a3047fb2e9bbfe3a74d5ce 100644 (file)
@@ -61,7 +61,7 @@ vlc_module_end()
 typedef struct segment_s
 {
     int         sequence;   /* unique sequence number */
-    int         length;     /* segment duration (ms) */
+    int         length;     /* segment duration (seconds) */
     uint64_t    size;       /* segment size in bytes */
 
     vlc_url_t   url;
@@ -75,11 +75,9 @@ typedef struct hls_stream_s
     int         version;    /* protocol version should be 1 */
     int         sequence;   /* media sequence number */
     int         duration;   /* maximum duration per segment (ms) */
-    uint64_t    bandwidth;  /* bandwidth usage of segments (kbps)*/
+    uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
 
     vlc_array_t *segments;  /* list of segments */
-    int         segment;    /* current segment downloading */
-
     vlc_url_t   url;        /* uri to m3u8 */
     vlc_mutex_t lock;
     bool        b_cache;    /* allow caching */
@@ -91,6 +89,7 @@ typedef struct
 
     /* */
     int         current;    /* current hls_stream  */
+    int         segment;    /* current segment for downloading */
     vlc_array_t *hls_stream;/* bandwidth adaptation */
 
     stream_t    *s;
@@ -182,7 +181,6 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, int id, uint64_t bw, char
     hls->duration = -1;/* unknown */
     hls->sequence = 0; /* default is 0 */
     hls->version = 1;  /* default protocol version */
-    hls->segment = 0;
     hls->b_cache = true;
     vlc_UrlParse(&hls->url, uri, 0);
     hls->segments = vlc_array_new();
@@ -234,6 +232,29 @@ static hls_stream_t *hls_GetLast(vlc_array_t *hls_stream)
     return (hls_stream_t *) hls_Get(hls_stream, count);
 }
 
+static int hls_LowestBandwidthStream(vlc_array_t *hls_stream)
+{
+    int count = vlc_array_count(hls_stream);
+    int i_lowest = 0;
+    uint64_t lowest = 0;
+
+    for (int i = 0; i < count; i++)
+    {
+        hls_stream_t *hls = (hls_stream_t *) vlc_array_item_at_index(hls_stream, i);
+        if (lowest == 0)
+        {
+            lowest = hls->bandwidth;
+            i_lowest = i;
+        }
+        else if (hls->bandwidth < lowest)
+        {
+            lowest = hls->bandwidth;
+            i_lowest = i;
+        }
+    }
+    return i_lowest;
+}
+
 /* Segment */
 static segment_t *segment_New(hls_stream_t* hls, int duration, char *uri)
 {
@@ -803,8 +824,6 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
     int candidate = -1;
     uint64_t bw = *bandwidth;
 
-    msg_Dbg(s, "bandwidth (bits/s) %"PRIu64, bw * 1000); /* bits / s */
-
     int count = vlc_array_count(p_sys->hls_stream);
     for (int n = 0; n < count; n++)
     {
@@ -815,8 +834,10 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
         /* only consider streams with the same PROGRAM-ID */
         if (hls->id == progid)
         {
-            if (bw <= hls->bandwidth)
+            if (bw >= hls->bandwidth)
             {
+                msg_Dbg(s, "candidate %d bandwidth (bits/s) %"PRIu64" >= %"PRIu64,
+                         n, bw, hls->bandwidth); /* bits / s */
                 *bandwidth = hls->bandwidth;
                 candidate = n; /* possible candidate */
             }
@@ -827,6 +848,8 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
 
 static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
 {
+    stream_sys_t *p_sys = s->p_sys;
+
     assert(hls);
     assert(segment);
 
@@ -848,14 +871,35 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
 
     vlc_mutex_unlock(&segment->lock);
 
-    uint64_t bw = (segment->size * 8) / (duration/1000);      /* bits / ms */
+    /* thread is not started yet */
+    if (p_sys->thread == NULL)
+    {
+        msg_Info(s, "downloaded segment %d from stream %d",
+                    p_sys->segment, p_sys->current);
+        return VLC_SUCCESS;
+    }
+
+    /* Do bandwidth calculations when there are at least 3 segments
+       downloaded */
+    msg_Info(s, "downloaded segment %d from stream %d",
+                p_sys->thread->segment, p_sys->thread->current);
+    /* FIXME: we need an average here */
+    if (p_sys->thread->segment - p_sys->segment < 3)
+        return VLC_SUCCESS;
+
+    /* 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 */
     if (hls->bandwidth != bw)
     {
         int newstream = BandwidthAdaptation(s, hls->id, &bw);
-        if ((newstream > 0) && (newstream != *cur_stream))
+        if ((newstream >= 0) && (newstream != *cur_stream))
         {
-            msg_Info(s, "switching to %s bandwidth (%"PRIu64") stream",
-                     (hls->bandwidth <= bw) ? "faster" : "lower", bw);
+            msg_Info(s, "detected %s bandwidth (%"PRIu64") stream",
+                     (bw >= hls->bandwidth) ? "faster" : "lower", bw);
             *cur_stream = newstream;
         }
     }
@@ -876,8 +920,7 @@ static void* hls_Thread(vlc_object_t *p_this)
         assert(hls);
 
         vlc_mutex_lock(&hls->lock);
-        segment_t *segment = segment_GetSegment(hls, hls->segment);
-        if (segment) hls->segment++;
+        segment_t *segment = segment_GetSegment(hls, client->segment);
         vlc_mutex_unlock(&hls->lock);
 
         /* Is there a new segment to process? */
@@ -891,6 +934,9 @@ static void* hls_Thread(vlc_object_t *p_this)
             if (!p_sys->b_live) break;
         }
 
+        /* download succeeded */
+        client->segment++;
+
         /* FIXME: Reread the m3u8 index file */
         if (p_sys->b_live)
         {
@@ -920,29 +966,21 @@ static void* hls_Thread(vlc_object_t *p_this)
     return NULL;
 }
 
-static int Prefetch(stream_t *s)
+static int Prefetch(stream_t *s, int *current)
 {
     stream_sys_t *p_sys = s->p_sys;
-    int current;
-
-again:
-    current = p_sys->current;
 
     hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
     if (hls == NULL)
         return VLC_EGENERIC;
 
-    segment_t *segment = segment_GetSegment(hls, hls->segment);
+    segment_t *segment = segment_GetSegment(hls, p_sys->segment);
     if (segment == NULL )
         return VLC_EGENERIC;
 
-    if (Download(s, hls, segment, &p_sys->current) != VLC_SUCCESS)
+    if (Download(s, hls, segment, current) != VLC_SUCCESS)
         return VLC_EGENERIC;
 
-    /* Bandwidth changed? */
-    if (current != p_sys->current)
-        goto again;
-
     return VLC_SUCCESS;
 }
 
@@ -1040,10 +1078,6 @@ static char *AccessReadLine(access_t *p_access, uint8_t *psz_tmp, size_t i_len)
         p++;
     }
 
-    /* EOL */
-    line = calloc(1, p - begin + 1);
-    if (line == NULL)
-        return NULL;
     /* copy line excluding \n */
     line = strndup(begin, p - begin);
 
@@ -1087,8 +1121,12 @@ static int AccessDownload(stream_t *s, segment_t *segment)
         {
             msg_Dbg(s, "size changed %"PRIu64, segment->size);
             segment->data = block_Realloc(segment->data, 0, p_sys->p_access->info.i_size);
-            if (segment->data)
-                segment->size = p_sys->p_access->info.i_size;
+            if (segment->data == NULL)
+            {
+                AccessClose(s);
+                return VLC_ENOMEM;
+            }
+            segment->size = p_sys->p_access->info.i_size;
             assert(segment->data->i_buffer == segment->size);
         }
         length = p_sys->p_access->pf_read(p_sys->p_access,
@@ -1143,10 +1181,10 @@ static int Open(vlc_object_t *p_this)
     }
 
     /* */
+    int current = p_sys->current = hls_LowestBandwidthStream(p_sys->hls_stream);
     p_sys->segment = 0;
-    p_sys->current = 0;
 
-    if (Prefetch(s) != VLC_SUCCESS)
+    if (Prefetch(s, &current) != VLC_SUCCESS)
     {
         msg_Err(s, "fetching first segment.");
         goto fail;
@@ -1160,7 +1198,8 @@ static int Open(vlc_object_t *p_this)
     }
 
     p_sys->thread->hls_stream = p_sys->hls_stream;
-    p_sys->thread->current = p_sys->current;
+    p_sys->thread->current = current;
+    p_sys->thread->segment = p_sys->segment + 1;
     p_sys->thread->s = s;
 
     if (vlc_thread_create(p_sys->thread, "HTTP Live Streaming client",
@@ -1215,12 +1254,13 @@ static void Close(vlc_object_t *p_this)
 static segment_t *NextSegment(stream_t *s)
 {
     stream_sys_t *p_sys = s->p_sys;
-    segment_t *segment;
+    segment_t *segment = NULL;
+    int i_stream = 0;
 
-    do
+    while(vlc_object_alive(s))
     {
         /* Is the next segment ready */
-        hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
+        hls_stream_t *hls = hls_Get(p_sys->hls_stream, i_stream);
         if (hls == NULL) return NULL;
 
         segment = segment_GetSegment(hls, p_sys->segment);
@@ -1230,26 +1270,18 @@ static segment_t *NextSegment(stream_t *s)
         if (segment->data != NULL)
             return segment;
 
-        /* Was the stream changed to another bitrate? */
         if (!p_sys->b_meta) return NULL;
 
-        if (p_sys->current != p_sys->thread->current)
-        {
-            /* YES it was */
-            p_sys->current = p_sys->thread->current;
-        }
-        else
-        {
+        /* Was the stream changed to another bitrate? */
+        i_stream++;
+        if (i_stream >= vlc_array_count(p_sys->hls_stream))
+            return NULL;
 #if 0
-            /* Not downloaded yet, do it here */
-            if (Download(s, hls, segment, &p_sys->current) != VLC_SUCCESS)
-                return segment;
-            else
+        msg_Info(s, "playback is switching from stream %d to %d",
+                 p_sys->current, i_stream);
 #endif
-                return NULL;
-        }
+        p_sys->current = i_stream;
     }
-    while(vlc_object_alive(s));
 
     return segment;
 }
@@ -1276,8 +1308,9 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
                 block_Release(segment->data);
                 segment->data = NULL;
             }
-            msg_Dbg(s, "switching to segment %d", p_sys->segment);
             p_sys->segment++;
+            msg_Info(s, "playing segment %d from stream %d",
+                        p_sys->segment, p_sys->current);
             vlc_mutex_unlock(&segment->lock);
             continue;
         }
@@ -1331,18 +1364,22 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek)
 {
     stream_sys_t *p_sys = s->p_sys;
     size_t curlen = 0;
+    segment_t *segment;
 
-    hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
-    if (hls == NULL)
-        return 0;
+again:
+    segment = NextSegment(s);
+    if (segment == NULL)
+    {
+        msg_Err(s, "segment should have been available");
+        return 0; /* eof? */
+    }
 
+    vlc_mutex_lock(&segment->lock);
+
+    /* remember segment to peek */
     int peek_segment = p_sys->segment;
     do
     {
-        segment_t *segment = segment_GetSegment(hls, peek_segment);
-        if (segment == NULL) return 0;
-
-        vlc_mutex_lock(&segment->lock);
         if (i_peek < segment->data->i_buffer)
         {
             *pp_peek = segment->data->p_buffer;
@@ -1350,11 +1387,17 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek)
         }
         else
         {
-             peek_segment++;
+            p_sys->segment++;
+            vlc_mutex_unlock(&segment->lock);
+            goto again;
         }
-        vlc_mutex_unlock(&segment->lock);
     } while ((curlen < i_peek) && vlc_object_alive(s));
 
+    /* restore segment to read */
+    p_sys->segment = peek_segment;
+
+    vlc_mutex_unlock(&segment->lock);
+
     return curlen;
 }
 
@@ -1370,9 +1413,10 @@ static bool hls_MaySeek(stream_t *s)
 
     if (p_sys->b_live)
     {
-       int count = vlc_array_count(hls->segments);
        vlc_mutex_lock(&hls->lock);
-       bool may_seek = (hls->segment < count - 2);
+       int count = vlc_array_count(hls->segments);
+       bool may_seek = (p_sys->thread == NULL) ? false :
+                            (p_sys->thread->segment < count - 2);
        vlc_mutex_unlock(&hls->lock);
        return may_seek;
     }
@@ -1420,7 +1464,7 @@ static int segment_Seek(stream_t *s, uint64_t pos)
     for (int n = 0; n < count; n++)
     {
         /* FIXME: Seeking in segments not dowloaded is not supported. */
-        if (n >= hls->segment)
+        if (n >= p_sys->thread->segment)
         {
             msg_Err(s, "seeking in segment not downloaded yet.");
             return VLC_EGENERIC;
@@ -1431,23 +1475,39 @@ static int segment_Seek(stream_t *s, uint64_t pos)
             return VLC_EGENERIC;
 
         vlc_mutex_lock(&segment->lock);
-        length += segment->size;
-        uint64_t size = segment->size -segment->data->i_buffer;
-        if (size > 0)
+        if (segment->data)
         {
-            segment->data->i_buffer += size;
-            segment->data->p_buffer -= size;
-        }
+            length += segment->size;
+            uint64_t size = segment->size -segment->data->i_buffer;
+            if (size > 0)
+            {
+                segment->data->i_buffer += size;
+                segment->data->p_buffer -= size;
+            }
+
+            if (!b_found && (pos <= length))
+            {
+                uint64_t used = length - pos;
+                segment->data->i_buffer -= used;
+                segment->data->p_buffer += used;
 
-        if (!b_found && (pos <= length))
+                count = p_sys->segment;
+                p_sys->segment = n;
+                b_found = true;
+            }
+        }
+        else
         {
-            uint64_t used = length - pos;
-            segment->data->i_buffer -= used;
-            segment->data->p_buffer += used;
+            /* FIXME: seeking is weird when seeking in segments
+               that have not been downloaded yet */
+            length += segment->length * hls->bandwidth;
 
-            count = p_sys->segment;
-            p_sys->segment = n;
-            b_found = true;
+            if (!b_found && (pos <= length))
+            {
+                count = p_sys->segment;
+                p_sys->segment = n;
+                b_found = true;
+            }
         }
         vlc_mutex_unlock(&segment->lock);
     }