Main Page
Related Pages
Modules
Data Structures
Files
Examples
File List
Globals
libavformat
applehttpproto.c
Go to the documentation of this file.
1
/*
2
* Apple HTTP Live Streaming Protocol Handler
3
* Copyright (c) 2010 Martin Storsjo
4
*
5
* This file is part of Libav.
6
*
7
* Libav is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2.1 of the License, or (at your option) any later version.
11
*
12
* Libav is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
16
*
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with Libav; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
*/
21
28
#include "
libavutil/avstring.h
"
29
#include "
avformat.h
"
30
#include "
internal.h
"
31
#include "
url.h
"
32
#include <unistd.h>
33
34
/*
35
* An apple http stream consists of a playlist with media segment files,
36
* played sequentially. There may be several playlists with the same
37
* video content, in different bandwidth variants, that are played in
38
* parallel (preferrably only one bandwidth variant at a time). In this case,
39
* the user supplied the url to a main playlist that only lists the variant
40
* playlists.
41
*
42
* If the main playlist doesn't point at any variants, we still create
43
* one anonymous toplevel variant for this, to maintain the structure.
44
*/
45
46
struct
segment
{
47
int
duration
;
48
char
url
[
MAX_URL_SIZE
];
49
};
50
51
struct
variant
{
52
int
bandwidth
;
53
char
url
[
MAX_URL_SIZE
];
54
};
55
56
typedef
struct
AppleHTTPContext
{
57
char
playlisturl
[
MAX_URL_SIZE
];
58
int
target_duration
;
59
int
start_seq_no
;
60
int
finished
;
61
int
n_segments
;
62
struct
segment
**
segments
;
63
int
n_variants
;
64
struct
variant
**
variants
;
65
int
cur_seq_no
;
66
URLContext
*
seg_hd
;
67
int64_t
last_load_time
;
68
}
AppleHTTPContext
;
69
70
static
int
read_chomp_line
(
AVIOContext
*s,
char
*buf,
int
maxlen)
71
{
72
int
len
=
ff_get_line
(s, buf, maxlen);
73
while
(len > 0 && isspace(buf[len - 1]))
74
buf[--
len
] =
'\0'
;
75
return
len
;
76
}
77
78
static
void
free_segment_list
(
AppleHTTPContext
*s)
79
{
80
int
i;
81
for
(i = 0; i < s->
n_segments
; i++)
82
av_free
(s->
segments
[i]);
83
av_freep
(&s->
segments
);
84
s->
n_segments
= 0;
85
}
86
87
static
void
free_variant_list
(
AppleHTTPContext
*s)
88
{
89
int
i;
90
for
(i = 0; i < s->
n_variants
; i++)
91
av_free
(s->
variants
[i]);
92
av_freep
(&s->
variants
);
93
s->
n_variants
= 0;
94
}
95
96
struct
variant_info
{
97
char
bandwidth
[20];
98
};
99
100
static
void
handle_variant_args
(
struct
variant_info
*info,
const
char
*key,
101
int
key_len,
char
**dest,
int
*dest_len)
102
{
103
if
(!strncmp(key,
"BANDWIDTH="
, key_len)) {
104
*dest = info->
bandwidth
;
105
*dest_len =
sizeof
(info->
bandwidth
);
106
}
107
}
108
109
static
int
parse_playlist
(
URLContext
*h,
const
char
*url)
110
{
111
AppleHTTPContext
*s = h->
priv_data
;
112
AVIOContext
*in;
113
int
ret = 0,
duration
= 0, is_segment = 0, is_variant = 0, bandwidth = 0;
114
char
line
[1024];
115
const
char
*ptr;
116
117
if
((ret =
avio_open2
(&in, url,
AVIO_FLAG_READ
,
118
&h->
interrupt_callback
,
NULL
)) < 0)
119
return
ret;
120
121
read_chomp_line
(in, line,
sizeof
(line));
122
if
(strcmp(line,
"#EXTM3U"
))
123
return
AVERROR_INVALIDDATA
;
124
125
free_segment_list
(s);
126
s->
finished
= 0;
127
while
(!in->
eof_reached
) {
128
read_chomp_line
(in, line,
sizeof
(line));
129
if
(
av_strstart
(line,
"#EXT-X-STREAM-INF:"
, &ptr)) {
130
struct
variant_info
info = {{0}};
131
is_variant = 1;
132
ff_parse_key_value
(ptr, (
ff_parse_key_val_cb
)
handle_variant_args
,
133
&info);
134
bandwidth
= atoi(info.
bandwidth
);
135
}
else
if
(
av_strstart
(line,
"#EXT-X-TARGETDURATION:"
, &ptr)) {
136
s->
target_duration
= atoi(ptr);
137
}
else
if
(
av_strstart
(line,
"#EXT-X-MEDIA-SEQUENCE:"
, &ptr)) {
138
s->
start_seq_no
= atoi(ptr);
139
}
else
if
(
av_strstart
(line,
"#EXT-X-ENDLIST"
, &ptr)) {
140
s->
finished
= 1;
141
}
else
if
(
av_strstart
(line,
"#EXTINF:"
, &ptr)) {
142
is_segment = 1;
143
duration
= atoi(ptr);
144
}
else
if
(
av_strstart
(line,
"#"
,
NULL
)) {
145
continue
;
146
}
else
if
(line[0]) {
147
if
(is_segment) {
148
struct
segment
*seg =
av_malloc
(
sizeof
(
struct
segment
));
149
if
(!seg) {
150
ret =
AVERROR
(ENOMEM);
151
goto
fail;
152
}
153
seg->
duration
=
duration
;
154
ff_make_absolute_url
(seg->
url
,
sizeof
(seg->
url
), url, line);
155
dynarray_add
(&s->
segments
, &s->
n_segments
, seg);
156
is_segment = 0;
157
}
else
if
(is_variant) {
158
struct
variant
*var =
av_malloc
(
sizeof
(
struct
variant
));
159
if
(!var) {
160
ret =
AVERROR
(ENOMEM);
161
goto
fail;
162
}
163
var->
bandwidth
=
bandwidth
;
164
ff_make_absolute_url
(var->
url
,
sizeof
(var->
url
), url, line);
165
dynarray_add
(&s->
variants
, &s->
n_variants
, var);
166
is_variant = 0;
167
}
168
}
169
}
170
s->
last_load_time
=
av_gettime
();
171
172
fail:
173
avio_close
(in);
174
return
ret;
175
}
176
177
static
int
applehttp_close
(
URLContext
*h)
178
{
179
AppleHTTPContext
*s = h->
priv_data
;
180
181
free_segment_list
(s);
182
free_variant_list
(s);
183
ffurl_close
(s->
seg_hd
);
184
return
0;
185
}
186
187
static
int
applehttp_open
(
URLContext
*h,
const
char
*uri,
int
flags
)
188
{
189
AppleHTTPContext
*s = h->
priv_data
;
190
int
ret, i;
191
const
char
*nested_url;
192
193
if
(flags &
AVIO_FLAG_WRITE
)
194
return
AVERROR
(ENOSYS);
195
196
h->
is_streamed
= 1;
197
198
if
(
av_strstart
(uri,
"applehttp+"
, &nested_url)) {
199
av_strlcpy
(s->
playlisturl
, nested_url,
sizeof
(s->
playlisturl
));
200
}
else
if
(
av_strstart
(uri,
"applehttp://"
, &nested_url)) {
201
av_strlcpy
(s->
playlisturl
,
"http://"
,
sizeof
(s->
playlisturl
));
202
av_strlcat
(s->
playlisturl
, nested_url,
sizeof
(s->
playlisturl
));
203
}
else
{
204
av_log
(h,
AV_LOG_ERROR
,
"Unsupported url %s\n"
, uri);
205
ret =
AVERROR
(EINVAL);
206
goto
fail;
207
}
208
209
if
((ret =
parse_playlist
(h, s->
playlisturl
)) < 0)
210
goto
fail;
211
212
if
(s->
n_segments
== 0 && s->
n_variants
> 0) {
213
int
max_bandwidth
= 0, maxvar = -1;
214
for
(i = 0; i < s->
n_variants
; i++) {
215
if
(s->
variants
[i]->
bandwidth
> max_bandwidth || i == 0) {
216
max_bandwidth = s->
variants
[i]->
bandwidth
;
217
maxvar = i;
218
}
219
}
220
av_strlcpy
(s->
playlisturl
, s->
variants
[maxvar]->
url
,
221
sizeof
(s->
playlisturl
));
222
if
((ret =
parse_playlist
(h, s->
playlisturl
)) < 0)
223
goto
fail;
224
}
225
226
if
(s->
n_segments
== 0) {
227
av_log
(h,
AV_LOG_WARNING
,
"Empty playlist\n"
);
228
ret =
AVERROR
(EIO);
229
goto
fail;
230
}
231
s->
cur_seq_no
= s->
start_seq_no
;
232
if
(!s->
finished
&& s->
n_segments
>= 3)
233
s->
cur_seq_no
= s->
start_seq_no
+ s->
n_segments
- 3;
234
235
return
0;
236
237
fail:
238
applehttp_close
(h);
239
return
ret;
240
}
241
242
static
int
applehttp_read
(
URLContext
*h, uint8_t *buf,
int
size
)
243
{
244
AppleHTTPContext
*s = h->
priv_data
;
245
const
char
*
url
;
246
int
ret;
247
int64_t reload_interval;
248
249
start:
250
if
(s->
seg_hd
) {
251
ret =
ffurl_read
(s->
seg_hd
, buf, size);
252
if
(ret > 0)
253
return
ret;
254
}
255
if
(s->
seg_hd
) {
256
ffurl_close
(s->
seg_hd
);
257
s->
seg_hd
=
NULL
;
258
s->
cur_seq_no
++;
259
}
260
reload_interval = s->
n_segments
> 0 ?
261
s->
segments
[s->
n_segments
- 1]->
duration
:
262
s->
target_duration
;
263
reload_interval *= 1000000;
264
retry:
265
if
(!s->
finished
) {
266
int64_t now =
av_gettime
();
267
if
(now - s->
last_load_time
>= reload_interval) {
268
if
((ret =
parse_playlist
(h, s->
playlisturl
)) < 0)
269
return
ret;
270
/* If we need to reload the playlist again below (if
271
* there's still no more segments), switch to a reload
272
* interval of half the target duration. */
273
reload_interval = s->
target_duration
* 500000;
274
}
275
}
276
if
(s->
cur_seq_no
< s->
start_seq_no
) {
277
av_log
(h,
AV_LOG_WARNING
,
278
"skipping %d segments ahead, expired from playlist\n"
,
279
s->
start_seq_no
- s->
cur_seq_no
);
280
s->
cur_seq_no
= s->
start_seq_no
;
281
}
282
if
(s->
cur_seq_no
- s->
start_seq_no
>= s->
n_segments
) {
283
if
(s->
finished
)
284
return
AVERROR_EOF
;
285
while
(
av_gettime
() - s->
last_load_time
< reload_interval) {
286
if
(
ff_check_interrupt
(&h->
interrupt_callback
))
287
return
AVERROR_EXIT
;
288
usleep(100*1000);
289
}
290
goto
retry;
291
}
292
url = s->
segments
[s->
cur_seq_no
- s->
start_seq_no
]->
url
,
293
av_log
(h,
AV_LOG_DEBUG
,
"opening %s\n"
, url);
294
ret =
ffurl_open
(&s->
seg_hd
, url,
AVIO_FLAG_READ
,
295
&h->
interrupt_callback
,
NULL
);
296
if
(ret < 0) {
297
if
(
ff_check_interrupt
(&h->
interrupt_callback
))
298
return
AVERROR_EXIT
;
299
av_log
(h,
AV_LOG_WARNING
,
"Unable to open %s\n"
, url);
300
s->
cur_seq_no
++;
301
goto
retry;
302
}
303
goto
start;
304
}
305
306
URLProtocol
ff_applehttp_protocol
= {
307
.
name
=
"applehttp"
,
308
.url_open =
applehttp_open
,
309
.url_read =
applehttp_read
,
310
.url_close =
applehttp_close
,
311
.flags =
URL_PROTOCOL_FLAG_NESTED_SCHEME
,
312
.priv_data_size =
sizeof
(
AppleHTTPContext
),
313
};