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;
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 */
/* */
int current; /* current hls_stream */
+ int segment; /* current segment for downloading */
vlc_array_t *hls_stream;/* bandwidth adaptation */
stream_t *s;
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();
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)
{
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++)
{
/* 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 */
}
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);
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;
}
}
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? */
if (!p_sys->b_live) break;
}
+ /* download succeeded */
+ client->segment++;
+
/* FIXME: Reread the m3u8 index file */
if (p_sys->b_live)
{
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;
}
p++;
}
- /* EOL */
- line = calloc(1, p - begin + 1);
- if (line == NULL)
- return NULL;
/* copy line excluding \n */
line = strndup(begin, p - begin);
{
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,
}
/* */
+ 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, ¤t) != VLC_SUCCESS)
{
msg_Err(s, "fetching first segment.");
goto fail;
}
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",
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);
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;
}
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;
}
{
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;
}
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;
}
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;
}
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;
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);
}