[FFmpeg-devel] [PATCH] Add Apple HTTP Live Streaming protocol handler
Ronald S. Bultje
rsbultje
Wed Aug 4 23:16:24 CEST 2010
Hi,
On Tue, Jul 27, 2010 at 5:45 AM, Martin Storsj? <martin at martin.st> wrote:
[..]
> +struct segment {
> + int duration;
> + char *url;
> +};
> +
> +struct variant {
> + int bandwidth;
> + char *url;
> + ByteIOContext *pb;
> + AVFormatContext *ctx;
> + AVPacket pkt;
> + int stream_offset;
> +
> + int start_seq_no;
> + int n_segments;
> + struct segment **segments;
> + int needed;
> +};
Each of these (and below) could use at least a few doxy words to
explain what they are. What is a segment? What is a variant?
Is it possible to make url in both segment and vriant fixed-length, so
that we don't have to alloc so may individual elements? Not critical,
but would be nice.
> +typedef struct AppleHTTPContext {
> + char playlisturl[2048];
Unused (and kinda big).
> + int64_t last_load_time;
You're assigning this with av_gettime(), which seems wrong if
clockrate server/client are off. I think it's better if you use PTS
here instead of clocktime.
> +static int read_chomp_line(ByteIOContext *s, char *buf, int maxlen)
> +{
> + int len = ff_get_line(s, buf, maxlen);
> + if (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
> + buf[--len] = '\0';
> + if (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
> + buf[--len] = '\0';
> + return len;
> +}
This could be simplified (if ...buf[len-1]=='\n' { if
(buf[len-2]=='n') .. else .. }), but that's not too critical.
You can presumably also chop off all space-like characters, assuming
that's valid.
> +static void reset_packet(AVPacket *pkt)
> +{
> + av_init_packet(pkt);
> + pkt->data = NULL;
> +}
This seems overused. When is the data=NULL necessary, and what does it
mean? What happens if we don't?
> +static struct variant *new_variant(AppleHTTPContext *c, int bandwidth,
> + const char *url)
> +{
> + struct variant *var = av_mallocz(sizeof(struct variant));
> + if (!var)
> + return NULL;
> + reset_packet(&var->pkt);
> + var->bandwidth = bandwidth;
> + var->url = av_strdup(url);
Vertical align (also in other places). I'm wondering if we can just
av_strlcpy() into a fixed size buffer here (as said above).
[..]
> +static int parse_playlist(AppleHTTPContext *c, const char *url,
> + struct variant *var)
> +{
> + ByteIOContext *in;
[..]
> + if ((ret = url_fopen(&in, url, URL_RDONLY)) < 0)
> + return ret;
This should already be open in s->pb. Why do you want to reopen it?
> +static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap)
[..]
> + if (c->n_variants > 1 || c->variants[0]->n_segments == 0) {
> + char urlbuf[2048];
> + for (i = 0; i < c->n_variants; i++) {
> + make_absolute_url(urlbuf, sizeof(urlbuf), s->filename,
> + c->variants[i]->url);
> + av_freep(&c->variants[i]->url);
> + c->variants[i]->url = av_strdup(urlbuf);
> + if ((ret = parse_playlist(c, c->variants[i]->url,
> + c->variants[i])) < 0)
> + goto fail;
> + }
> + }
The idea is that this should be transparent to the user. If the file
contains a playlist, it should be handled as just any other file, and
that should _work_, just like that. If it doesn't, it's a bug and
should be fixed.
Therefore, this code shouldn't be necessary.
> + if (c->variants[0]->n_segments == 0) {
> + av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
> + ret = AVERROR_EOF;
> + goto fail;
> + }
This would then change to if (contained-data-length == 0) ... fail.
> + if (c->finished) {
> + int duration = 0;
> + for (i = 0; i < c->variants[0]->n_segments; i++)
> + duration += c->variants[0]->segments[i]->duration;
> + s->duration = duration * AV_TIME_BASE;
> + }
Why if (finished)? Isn't it an error if it's not? (And otherwise,
what's duration?)
> +static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt)
This function could use some documentation / comments.
> + for (i = 0; i < c->n_variants; i++)
> + c->variants[i]->needed = 0;
> + for (i = 0; i < s->nb_streams; i++) {
> + AVStream *st = s->streams[i];
> + struct variant *var = c->variants[s->streams[i]->id];
> + if (st->discard < AVDISCARD_ALL) {
> + var->needed = 1;
> + needed++;
> + }
> + var->ctx->streams[i - var->stream_offset]->discard = st->discard;
> + }
Why are these context variables. Why not a array of size MAXSTREAMS or
so? They don't need to be saved across runs.
> + if (!c->finished) {
> + int64_t now = av_gettime();
> + if (now - c->last_load_time >= c->target_duration*1000000) {
> + c->max_start_seq = 0;
> + c->min_end_seq = INT_MAX;
> + for (i = 0; i < c->n_variants; i++) {
> + struct variant *var = c->variants[i];
> + if (var->needed) {
> + if ((ret = parse_playlist(c, var->url, var)) < 0)
> + return ret;
> + c->max_start_seq = FFMAX(c->max_start_seq,
> + var->start_seq_no);
> + c->min_end_seq = FFMIN(c->min_end_seq,
> + var->start_seq_no + var->n_segments);
Eh... So this is a playlist-in-a-playlist? I'm again confused, this
should be transparent. And shouldn't this be a media file at this
point? again, some documentation / comments would help me understand
what the code is doing.
> + if (c->cur_seq_no < c->max_start_seq) {
> + av_log(NULL, AV_LOG_WARNING,
> + "skipping %d segments ahead, expired from playlists\n",
> + c->max_start_seq - c->cur_seq_no);
> + c->cur_seq_no = c->max_start_seq;
> + }
> + if (c->cur_seq_no >= c->min_end_seq) {
> + if (c->finished)
> + return AVERROR_EOF;
> + while (av_gettime() - c->last_load_time < c->target_duration*1000000) {
> + if (url_interrupt_cb())
> + return AVERROR(EINTR);
> + usleep(100*1000);
> + }
> + goto retry;
> + }
> + goto start;
> +}
I personally object to the use of av_gettime() here...
(Skipped seeking code for this cycle.)
> + .extensions = "m3u8",
?? Really?
Is a probe function possible based on the expected EXT-attributes in
it that are relatively Apple-specific?
Ronald
More information about the ffmpeg-devel
mailing list