Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Apple HTTP Live Streaming segmenter | |
3 | * Copyright (c) 2012, Luca Barbato | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg 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 | * FFmpeg 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 FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include <float.h> | |
23 | #include <stdint.h> | |
24 | ||
25 | #include "libavutil/avassert.h" | |
26 | #include "libavutil/mathematics.h" | |
27 | #include "libavutil/parseutils.h" | |
28 | #include "libavutil/avstring.h" | |
29 | #include "libavutil/opt.h" | |
30 | #include "libavutil/log.h" | |
31 | ||
32 | #include "avformat.h" | |
33 | #include "internal.h" | |
34 | ||
35 | typedef struct HLSSegment { | |
36 | char filename[1024]; | |
37 | double duration; /* in seconds */ | |
38 | ||
39 | struct HLSSegment *next; | |
40 | } HLSSegment; | |
41 | ||
42 | typedef struct HLSContext { | |
43 | const AVClass *class; // Class for private options. | |
44 | unsigned number; | |
45 | int64_t sequence; | |
46 | int64_t start_sequence; | |
47 | AVOutputFormat *oformat; | |
48 | ||
49 | AVFormatContext *avf; | |
50 | ||
51 | float time; // Set by a private option. | |
52 | int max_nb_segments; // Set by a private option. | |
53 | int wrap; // Set by a private option. | |
54 | ||
55 | int64_t recording_time; | |
56 | int has_video; | |
57 | int64_t start_pts; | |
58 | int64_t end_pts; | |
59 | double duration; // last segment duration computed so far, in seconds | |
60 | int nb_entries; | |
61 | ||
62 | HLSSegment *segments; | |
63 | HLSSegment *last_segment; | |
64 | ||
65 | char *basename; | |
66 | char *baseurl; | |
67 | ||
68 | AVIOContext *pb; | |
69 | } HLSContext; | |
70 | ||
71 | static int hls_mux_init(AVFormatContext *s) | |
72 | { | |
73 | HLSContext *hls = s->priv_data; | |
74 | AVFormatContext *oc; | |
75 | int i; | |
76 | ||
77 | hls->avf = oc = avformat_alloc_context(); | |
78 | if (!oc) | |
79 | return AVERROR(ENOMEM); | |
80 | ||
81 | oc->oformat = hls->oformat; | |
82 | oc->interrupt_callback = s->interrupt_callback; | |
83 | av_dict_copy(&oc->metadata, s->metadata, 0); | |
84 | ||
85 | for (i = 0; i < s->nb_streams; i++) { | |
86 | AVStream *st; | |
87 | if (!(st = avformat_new_stream(oc, NULL))) | |
88 | return AVERROR(ENOMEM); | |
89 | avcodec_copy_context(st->codec, s->streams[i]->codec); | |
90 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; | |
91 | } | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | /* Create a new segment and append it to the segment list */ | |
97 | static int hls_append_segment(HLSContext *hls, double duration) | |
98 | { | |
99 | HLSSegment *en = av_malloc(sizeof(*en)); | |
100 | ||
101 | if (!en) | |
102 | return AVERROR(ENOMEM); | |
103 | ||
104 | av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); | |
105 | ||
106 | en->duration = duration; | |
107 | en->next = NULL; | |
108 | ||
109 | if (!hls->segments) | |
110 | hls->segments = en; | |
111 | else | |
112 | hls->last_segment->next = en; | |
113 | ||
114 | hls->last_segment = en; | |
115 | ||
116 | if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { | |
117 | en = hls->segments; | |
118 | hls->segments = en->next; | |
119 | av_free(en); | |
120 | } else | |
121 | hls->nb_entries++; | |
122 | ||
123 | hls->sequence++; | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | static void hls_free_segments(HLSContext *hls) | |
129 | { | |
130 | HLSSegment *p = hls->segments, *en; | |
131 | ||
132 | while(p) { | |
133 | en = p; | |
134 | p = p->next; | |
135 | av_free(en); | |
136 | } | |
137 | } | |
138 | ||
139 | static int hls_window(AVFormatContext *s, int last) | |
140 | { | |
141 | HLSContext *hls = s->priv_data; | |
142 | HLSSegment *en; | |
143 | int target_duration = 0; | |
144 | int ret = 0; | |
145 | int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); | |
146 | ||
147 | if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE, | |
148 | &s->interrupt_callback, NULL)) < 0) | |
149 | goto fail; | |
150 | ||
151 | for (en = hls->segments; en; en = en->next) { | |
152 | if (target_duration < en->duration) | |
153 | target_duration = ceil(en->duration); | |
154 | } | |
155 | ||
156 | avio_printf(hls->pb, "#EXTM3U\n"); | |
157 | avio_printf(hls->pb, "#EXT-X-VERSION:3\n"); | |
158 | avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration); | |
159 | avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); | |
160 | ||
161 | av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", | |
162 | sequence); | |
163 | ||
164 | for (en = hls->segments; en; en = en->next) { | |
165 | avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration); | |
166 | if (hls->baseurl) | |
167 | avio_printf(hls->pb, "%s", hls->baseurl); | |
168 | avio_printf(hls->pb, "%s\n", en->filename); | |
169 | } | |
170 | ||
171 | if (last) | |
172 | avio_printf(hls->pb, "#EXT-X-ENDLIST\n"); | |
173 | ||
174 | fail: | |
175 | avio_closep(&hls->pb); | |
176 | return ret; | |
177 | } | |
178 | ||
179 | static int hls_start(AVFormatContext *s) | |
180 | { | |
181 | HLSContext *c = s->priv_data; | |
182 | AVFormatContext *oc = c->avf; | |
183 | int err = 0; | |
184 | ||
185 | if (av_get_frame_filename(oc->filename, sizeof(oc->filename), | |
186 | c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { | |
187 | av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename); | |
188 | return AVERROR(EINVAL); | |
189 | } | |
190 | c->number++; | |
191 | ||
192 | if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, | |
193 | &s->interrupt_callback, NULL)) < 0) | |
194 | return err; | |
195 | ||
196 | if (oc->oformat->priv_class && oc->priv_data) | |
197 | av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static int hls_write_header(AVFormatContext *s) | |
203 | { | |
204 | HLSContext *hls = s->priv_data; | |
205 | int ret, i; | |
206 | char *p; | |
207 | const char *pattern = "%d.ts"; | |
208 | int basename_size = strlen(s->filename) + strlen(pattern) + 1; | |
209 | ||
210 | hls->sequence = hls->start_sequence; | |
211 | hls->recording_time = hls->time * AV_TIME_BASE; | |
212 | hls->start_pts = AV_NOPTS_VALUE; | |
213 | ||
214 | for (i = 0; i < s->nb_streams; i++) | |
215 | hls->has_video += | |
216 | s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; | |
217 | ||
218 | if (hls->has_video > 1) | |
219 | av_log(s, AV_LOG_WARNING, | |
220 | "More than a single video stream present, " | |
221 | "expect issues decoding it.\n"); | |
222 | ||
223 | hls->oformat = av_guess_format("mpegts", NULL, NULL); | |
224 | ||
225 | if (!hls->oformat) { | |
226 | ret = AVERROR_MUXER_NOT_FOUND; | |
227 | goto fail; | |
228 | } | |
229 | ||
230 | hls->basename = av_malloc(basename_size); | |
231 | ||
232 | if (!hls->basename) { | |
233 | ret = AVERROR(ENOMEM); | |
234 | goto fail; | |
235 | } | |
236 | ||
237 | strcpy(hls->basename, s->filename); | |
238 | ||
239 | p = strrchr(hls->basename, '.'); | |
240 | ||
241 | if (p) | |
242 | *p = '\0'; | |
243 | ||
244 | av_strlcat(hls->basename, pattern, basename_size); | |
245 | ||
246 | if ((ret = hls_mux_init(s)) < 0) | |
247 | goto fail; | |
248 | ||
249 | if ((ret = hls_start(s)) < 0) | |
250 | goto fail; | |
251 | ||
252 | if ((ret = avformat_write_header(hls->avf, NULL)) < 0) | |
253 | goto fail; | |
254 | ||
255 | av_assert0(s->nb_streams == hls->avf->nb_streams); | |
256 | for (i = 0; i < s->nb_streams; i++) { | |
257 | AVStream *inner_st = hls->avf->streams[i]; | |
258 | AVStream *outter_st = s->streams[i]; | |
259 | avpriv_set_pts_info(outter_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); | |
260 | } | |
261 | ||
262 | fail: | |
263 | if (ret) { | |
264 | av_free(hls->basename); | |
265 | if (hls->avf) | |
266 | avformat_free_context(hls->avf); | |
267 | } | |
268 | return ret; | |
269 | } | |
270 | ||
271 | static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) | |
272 | { | |
273 | HLSContext *hls = s->priv_data; | |
274 | AVFormatContext *oc = hls->avf; | |
275 | AVStream *st = s->streams[pkt->stream_index]; | |
276 | int64_t end_pts = hls->recording_time * hls->number; | |
277 | int is_ref_pkt = 1; | |
278 | int ret, can_split = 1; | |
279 | ||
280 | if (hls->start_pts == AV_NOPTS_VALUE) { | |
281 | hls->start_pts = pkt->pts; | |
282 | hls->end_pts = pkt->pts; | |
283 | } | |
284 | ||
285 | if (hls->has_video) { | |
286 | can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && | |
287 | pkt->flags & AV_PKT_FLAG_KEY; | |
288 | is_ref_pkt = st->codec->codec_type == AVMEDIA_TYPE_VIDEO; | |
289 | } | |
290 | if (pkt->pts == AV_NOPTS_VALUE) | |
291 | is_ref_pkt = can_split = 0; | |
292 | ||
293 | if (is_ref_pkt) | |
294 | hls->duration = (double)(pkt->pts - hls->end_pts) | |
295 | * st->time_base.num / st->time_base.den; | |
296 | ||
297 | if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, | |
298 | end_pts, AV_TIME_BASE_Q) >= 0) { | |
299 | ret = hls_append_segment(hls, hls->duration); | |
300 | if (ret) | |
301 | return ret; | |
302 | ||
303 | hls->end_pts = pkt->pts; | |
304 | hls->duration = 0; | |
305 | ||
306 | av_write_frame(oc, NULL); /* Flush any buffered data */ | |
307 | avio_close(oc->pb); | |
308 | ||
309 | ret = hls_start(s); | |
310 | ||
311 | if (ret) | |
312 | return ret; | |
313 | ||
314 | oc = hls->avf; | |
315 | ||
316 | if ((ret = hls_window(s, 0)) < 0) | |
317 | return ret; | |
318 | } | |
319 | ||
320 | ret = ff_write_chained(oc, pkt->stream_index, pkt, s, 0); | |
321 | ||
322 | return ret; | |
323 | } | |
324 | ||
325 | static int hls_write_trailer(struct AVFormatContext *s) | |
326 | { | |
327 | HLSContext *hls = s->priv_data; | |
328 | AVFormatContext *oc = hls->avf; | |
329 | ||
330 | av_write_trailer(oc); | |
331 | avio_closep(&oc->pb); | |
332 | avformat_free_context(oc); | |
333 | av_free(hls->basename); | |
334 | hls_append_segment(hls, hls->duration); | |
335 | hls_window(s, 1); | |
336 | ||
337 | hls_free_segments(hls); | |
338 | avio_close(hls->pb); | |
339 | return 0; | |
340 | } | |
341 | ||
342 | #define OFFSET(x) offsetof(HLSContext, x) | |
343 | #define E AV_OPT_FLAG_ENCODING_PARAM | |
344 | static const AVOption options[] = { | |
345 | {"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, | |
346 | {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, | |
347 | {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, | |
348 | {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, | |
349 | {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, | |
350 | { NULL }, | |
351 | }; | |
352 | ||
353 | static const AVClass hls_class = { | |
354 | .class_name = "hls muxer", | |
355 | .item_name = av_default_item_name, | |
356 | .option = options, | |
357 | .version = LIBAVUTIL_VERSION_INT, | |
358 | }; | |
359 | ||
360 | ||
361 | AVOutputFormat ff_hls_muxer = { | |
362 | .name = "hls", | |
363 | .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"), | |
364 | .extensions = "m3u8", | |
365 | .priv_data_size = sizeof(HLSContext), | |
366 | .audio_codec = AV_CODEC_ID_AAC, | |
367 | .video_codec = AV_CODEC_ID_H264, | |
368 | .flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH, | |
369 | .write_header = hls_write_header, | |
370 | .write_packet = hls_write_packet, | |
371 | .write_trailer = hls_write_trailer, | |
372 | .priv_class = &hls_class, | |
373 | }; |