Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2011, Luca Barbato | |
3 | * | |
4 | * This file is part of FFmpeg. | |
5 | * | |
6 | * FFmpeg is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2.1 of the License, or (at your option) any later version. | |
10 | * | |
11 | * FFmpeg is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with FFmpeg; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | */ | |
20 | ||
21 | /** | |
22 | * @file generic segmenter | |
23 | * M3U8 specification can be find here: | |
24 | * @url{http://tools.ietf.org/id/draft-pantos-http-live-streaming} | |
25 | */ | |
26 | ||
27 | /* #define DEBUG */ | |
28 | ||
29 | #include <float.h> | |
30 | #include <time.h> | |
31 | ||
32 | #include "avformat.h" | |
33 | #include "internal.h" | |
34 | ||
35 | #include "libavutil/avassert.h" | |
36 | #include "libavutil/log.h" | |
37 | #include "libavutil/opt.h" | |
38 | #include "libavutil/avstring.h" | |
39 | #include "libavutil/parseutils.h" | |
40 | #include "libavutil/mathematics.h" | |
41 | #include "libavutil/time.h" | |
f6fa7814 | 42 | #include "libavutil/time_internal.h" |
2ba45a60 DM |
43 | #include "libavutil/timestamp.h" |
44 | ||
45 | typedef struct SegmentListEntry { | |
46 | int index; | |
47 | double start_time, end_time; | |
48 | int64_t start_pts; | |
49 | int64_t offset_pts; | |
50 | char *filename; | |
51 | struct SegmentListEntry *next; | |
52 | int64_t last_duration; | |
53 | } SegmentListEntry; | |
54 | ||
55 | typedef enum { | |
56 | LIST_TYPE_UNDEFINED = -1, | |
57 | LIST_TYPE_FLAT = 0, | |
58 | LIST_TYPE_CSV, | |
59 | LIST_TYPE_M3U8, | |
60 | LIST_TYPE_EXT, ///< deprecated | |
61 | LIST_TYPE_FFCONCAT, | |
62 | LIST_TYPE_NB, | |
63 | } ListType; | |
64 | ||
65 | #define SEGMENT_LIST_FLAG_CACHE 1 | |
66 | #define SEGMENT_LIST_FLAG_LIVE 2 | |
67 | ||
68 | typedef struct { | |
69 | const AVClass *class; /**< Class for private options. */ | |
70 | int segment_idx; ///< index of the segment file to write, starting from 0 | |
71 | int segment_idx_wrap; ///< number after which the index wraps | |
72 | int segment_idx_wrap_nb; ///< number of time the index has wraped | |
73 | int segment_count; ///< number of segment files already written | |
74 | AVOutputFormat *oformat; | |
75 | AVFormatContext *avf; | |
76 | char *format; ///< format to use for output segment files | |
77 | char *format_options_str; ///< format options to use for output segment files | |
78 | AVDictionary *format_options; | |
79 | char *list; ///< filename for the segment list file | |
80 | int list_flags; ///< flags affecting list generation | |
81 | int list_size; ///< number of entries for the segment list file | |
82 | ||
83 | int use_clocktime; ///< flag to cut segments at regular clock time | |
84 | int64_t last_val; ///< remember last time for wrap around detection | |
85 | int64_t last_cut; ///< remember last cut | |
86 | int cut_pending; | |
87 | ||
88 | char *entry_prefix; ///< prefix to add to list entry filenames | |
89 | ListType list_type; ///< set the list type | |
90 | AVIOContext *list_pb; ///< list file put-byte context | |
91 | char *time_str; ///< segment duration specification string | |
92 | int64_t time; ///< segment duration | |
93 | ||
94 | char *times_str; ///< segment times specification string | |
95 | int64_t *times; ///< list of segment interval specification | |
96 | int nb_times; ///< number of elments in the times array | |
97 | ||
98 | char *frames_str; ///< segment frame numbers specification string | |
99 | int *frames; ///< list of frame number specification | |
100 | int nb_frames; ///< number of elments in the frames array | |
101 | int frame_count; ///< total number of reference frames | |
102 | int segment_frame_count; ///< number of reference frames in the segment | |
103 | ||
104 | int64_t time_delta; | |
105 | int individual_header_trailer; /**< Set by a private option. */ | |
106 | int write_header_trailer; /**< Set by a private option. */ | |
107 | ||
108 | int reset_timestamps; ///< reset timestamps at the begin of each segment | |
109 | int64_t initial_offset; ///< initial timestamps offset, expressed in microseconds | |
110 | char *reference_stream_specifier; ///< reference stream specifier | |
111 | int reference_stream_index; | |
112 | ||
113 | SegmentListEntry cur_entry; | |
114 | SegmentListEntry *segment_list_entries; | |
115 | SegmentListEntry *segment_list_entries_end; | |
116 | } SegmentContext; | |
117 | ||
118 | static void print_csv_escaped_str(AVIOContext *ctx, const char *str) | |
119 | { | |
120 | int needs_quoting = !!str[strcspn(str, "\",\n\r")]; | |
121 | ||
122 | if (needs_quoting) | |
123 | avio_w8(ctx, '"'); | |
124 | ||
125 | for (; *str; str++) { | |
126 | if (*str == '"') | |
127 | avio_w8(ctx, '"'); | |
128 | avio_w8(ctx, *str); | |
129 | } | |
130 | if (needs_quoting) | |
131 | avio_w8(ctx, '"'); | |
132 | } | |
133 | ||
134 | static int segment_mux_init(AVFormatContext *s) | |
135 | { | |
136 | SegmentContext *seg = s->priv_data; | |
137 | AVFormatContext *oc; | |
138 | int i; | |
139 | int ret; | |
140 | ||
141 | ret = avformat_alloc_output_context2(&seg->avf, seg->oformat, NULL, NULL); | |
142 | if (ret < 0) | |
143 | return ret; | |
144 | oc = seg->avf; | |
145 | ||
146 | oc->interrupt_callback = s->interrupt_callback; | |
f6fa7814 | 147 | oc->max_delay = s->max_delay; |
2ba45a60 DM |
148 | av_dict_copy(&oc->metadata, s->metadata, 0); |
149 | ||
150 | for (i = 0; i < s->nb_streams; i++) { | |
151 | AVStream *st; | |
152 | AVCodecContext *icodec, *ocodec; | |
153 | ||
154 | if (!(st = avformat_new_stream(oc, NULL))) | |
155 | return AVERROR(ENOMEM); | |
156 | icodec = s->streams[i]->codec; | |
157 | ocodec = st->codec; | |
158 | avcodec_copy_context(ocodec, icodec); | |
159 | if (!oc->oformat->codec_tag || | |
160 | av_codec_get_id (oc->oformat->codec_tag, icodec->codec_tag) == ocodec->codec_id || | |
161 | av_codec_get_tag(oc->oformat->codec_tag, icodec->codec_id) <= 0) { | |
162 | ocodec->codec_tag = icodec->codec_tag; | |
163 | } else { | |
164 | ocodec->codec_tag = 0; | |
165 | } | |
166 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; | |
f6fa7814 | 167 | st->time_base = s->streams[i]->time_base; |
2ba45a60 DM |
168 | av_dict_copy(&st->metadata, s->streams[i]->metadata, 0); |
169 | } | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
174 | static int set_segment_filename(AVFormatContext *s) | |
175 | { | |
176 | SegmentContext *seg = s->priv_data; | |
177 | AVFormatContext *oc = seg->avf; | |
178 | size_t size; | |
179 | ||
180 | if (seg->segment_idx_wrap) | |
181 | seg->segment_idx %= seg->segment_idx_wrap; | |
182 | if (av_get_frame_filename(oc->filename, sizeof(oc->filename), | |
183 | s->filename, seg->segment_idx) < 0) { | |
184 | av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename); | |
185 | return AVERROR(EINVAL); | |
186 | } | |
187 | ||
188 | /* copy modified name in list entry */ | |
189 | size = strlen(av_basename(oc->filename)) + 1; | |
190 | if (seg->entry_prefix) | |
191 | size += strlen(seg->entry_prefix); | |
192 | ||
193 | seg->cur_entry.filename = av_mallocz(size); | |
194 | if (!seg->cur_entry.filename) | |
195 | return AVERROR(ENOMEM); | |
196 | snprintf(seg->cur_entry.filename, size, "%s%s", | |
197 | seg->entry_prefix ? seg->entry_prefix : "", | |
198 | av_basename(oc->filename)); | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | static int segment_start(AVFormatContext *s, int write_header) | |
204 | { | |
205 | SegmentContext *seg = s->priv_data; | |
206 | AVFormatContext *oc = seg->avf; | |
207 | int err = 0; | |
208 | ||
209 | if (write_header) { | |
210 | avformat_free_context(oc); | |
211 | seg->avf = NULL; | |
212 | if ((err = segment_mux_init(s)) < 0) | |
213 | return err; | |
214 | oc = seg->avf; | |
215 | } | |
216 | ||
217 | seg->segment_idx++; | |
218 | if ((seg->segment_idx_wrap) && (seg->segment_idx%seg->segment_idx_wrap == 0)) | |
219 | seg->segment_idx_wrap_nb++; | |
220 | ||
221 | if ((err = set_segment_filename(s)) < 0) | |
222 | return err; | |
223 | ||
224 | if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, | |
225 | &s->interrupt_callback, NULL)) < 0) { | |
226 | av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); | |
227 | return err; | |
228 | } | |
229 | ||
230 | if (oc->oformat->priv_class && oc->priv_data) | |
231 | av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0); | |
232 | ||
233 | if (write_header) { | |
234 | if ((err = avformat_write_header(oc, NULL)) < 0) | |
235 | return err; | |
236 | } | |
237 | ||
238 | seg->segment_frame_count = 0; | |
239 | return 0; | |
240 | } | |
241 | ||
242 | static int segment_list_open(AVFormatContext *s) | |
243 | { | |
244 | SegmentContext *seg = s->priv_data; | |
245 | int ret; | |
246 | ||
247 | ret = avio_open2(&seg->list_pb, seg->list, AVIO_FLAG_WRITE, | |
248 | &s->interrupt_callback, NULL); | |
249 | if (ret < 0) { | |
250 | av_log(s, AV_LOG_ERROR, "Failed to open segment list '%s'\n", seg->list); | |
251 | return ret; | |
252 | } | |
253 | ||
254 | if (seg->list_type == LIST_TYPE_M3U8 && seg->segment_list_entries) { | |
255 | SegmentListEntry *entry; | |
256 | double max_duration = 0; | |
257 | ||
258 | avio_printf(seg->list_pb, "#EXTM3U\n"); | |
259 | avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n"); | |
260 | avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->segment_list_entries->index); | |
261 | avio_printf(seg->list_pb, "#EXT-X-ALLOW-CACHE:%s\n", | |
262 | seg->list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO"); | |
263 | ||
264 | av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%d\n", | |
265 | seg->segment_list_entries->index); | |
266 | ||
267 | for (entry = seg->segment_list_entries; entry; entry = entry->next) | |
268 | max_duration = FFMAX(max_duration, entry->end_time - entry->start_time); | |
269 | avio_printf(seg->list_pb, "#EXT-X-TARGETDURATION:%"PRId64"\n", (int64_t)ceil(max_duration)); | |
270 | } else if (seg->list_type == LIST_TYPE_FFCONCAT) { | |
271 | avio_printf(seg->list_pb, "ffconcat version 1.0\n"); | |
272 | } | |
273 | ||
274 | return ret; | |
275 | } | |
276 | ||
277 | static void segment_list_print_entry(AVIOContext *list_ioctx, | |
278 | ListType list_type, | |
279 | const SegmentListEntry *list_entry, | |
280 | void *log_ctx) | |
281 | { | |
282 | switch (list_type) { | |
283 | case LIST_TYPE_FLAT: | |
284 | avio_printf(list_ioctx, "%s\n", list_entry->filename); | |
285 | break; | |
286 | case LIST_TYPE_CSV: | |
287 | case LIST_TYPE_EXT: | |
288 | print_csv_escaped_str(list_ioctx, list_entry->filename); | |
289 | avio_printf(list_ioctx, ",%f,%f\n", list_entry->start_time, list_entry->end_time); | |
290 | break; | |
291 | case LIST_TYPE_M3U8: | |
292 | avio_printf(list_ioctx, "#EXTINF:%f,\n%s\n", | |
293 | list_entry->end_time - list_entry->start_time, list_entry->filename); | |
294 | break; | |
295 | case LIST_TYPE_FFCONCAT: | |
296 | { | |
297 | char *buf; | |
298 | if (av_escape(&buf, list_entry->filename, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_WHITESPACE) < 0) { | |
299 | av_log(log_ctx, AV_LOG_WARNING, | |
300 | "Error writing list entry '%s' in list file\n", list_entry->filename); | |
301 | return; | |
302 | } | |
303 | avio_printf(list_ioctx, "file %s\n", buf); | |
304 | av_free(buf); | |
305 | break; | |
306 | } | |
307 | default: | |
308 | av_assert0(!"Invalid list type"); | |
309 | } | |
310 | } | |
311 | ||
312 | static int segment_end(AVFormatContext *s, int write_trailer, int is_last) | |
313 | { | |
314 | SegmentContext *seg = s->priv_data; | |
315 | AVFormatContext *oc = seg->avf; | |
316 | int ret = 0; | |
317 | ||
318 | av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */ | |
319 | if (write_trailer) | |
320 | ret = av_write_trailer(oc); | |
321 | ||
322 | if (ret < 0) | |
323 | av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n", | |
324 | oc->filename); | |
325 | ||
326 | if (seg->list) { | |
327 | if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) { | |
328 | SegmentListEntry *entry = av_mallocz(sizeof(*entry)); | |
329 | if (!entry) { | |
330 | ret = AVERROR(ENOMEM); | |
331 | goto end; | |
332 | } | |
333 | ||
334 | /* append new element */ | |
335 | memcpy(entry, &seg->cur_entry, sizeof(*entry)); | |
336 | if (!seg->segment_list_entries) | |
337 | seg->segment_list_entries = seg->segment_list_entries_end = entry; | |
338 | else | |
339 | seg->segment_list_entries_end->next = entry; | |
340 | seg->segment_list_entries_end = entry; | |
341 | ||
342 | /* drop first item */ | |
343 | if (seg->list_size && seg->segment_count >= seg->list_size) { | |
344 | entry = seg->segment_list_entries; | |
345 | seg->segment_list_entries = seg->segment_list_entries->next; | |
346 | av_free(entry->filename); | |
347 | av_freep(&entry); | |
348 | } | |
349 | ||
350 | avio_close(seg->list_pb); | |
351 | if ((ret = segment_list_open(s)) < 0) | |
352 | goto end; | |
353 | for (entry = seg->segment_list_entries; entry; entry = entry->next) | |
354 | segment_list_print_entry(seg->list_pb, seg->list_type, entry, s); | |
355 | if (seg->list_type == LIST_TYPE_M3U8 && is_last) | |
356 | avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n"); | |
357 | } else { | |
358 | segment_list_print_entry(seg->list_pb, seg->list_type, &seg->cur_entry, s); | |
359 | } | |
360 | avio_flush(seg->list_pb); | |
361 | } | |
362 | ||
363 | av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n", | |
364 | seg->avf->filename, seg->segment_count); | |
365 | seg->segment_count++; | |
366 | ||
367 | end: | |
368 | avio_close(oc->pb); | |
369 | ||
370 | return ret; | |
371 | } | |
372 | ||
373 | static int parse_times(void *log_ctx, int64_t **times, int *nb_times, | |
374 | const char *times_str) | |
375 | { | |
376 | char *p; | |
377 | int i, ret = 0; | |
378 | char *times_str1 = av_strdup(times_str); | |
379 | char *saveptr = NULL; | |
380 | ||
381 | if (!times_str1) | |
382 | return AVERROR(ENOMEM); | |
383 | ||
384 | #define FAIL(err) ret = err; goto end | |
385 | ||
386 | *nb_times = 1; | |
387 | for (p = times_str1; *p; p++) | |
388 | if (*p == ',') | |
389 | (*nb_times)++; | |
390 | ||
391 | *times = av_malloc_array(*nb_times, sizeof(**times)); | |
392 | if (!*times) { | |
393 | av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced times array\n"); | |
394 | FAIL(AVERROR(ENOMEM)); | |
395 | } | |
396 | ||
397 | p = times_str1; | |
398 | for (i = 0; i < *nb_times; i++) { | |
399 | int64_t t; | |
400 | char *tstr = av_strtok(p, ",", &saveptr); | |
401 | p = NULL; | |
402 | ||
403 | if (!tstr || !tstr[0]) { | |
404 | av_log(log_ctx, AV_LOG_ERROR, "Empty time specification in times list %s\n", | |
405 | times_str); | |
406 | FAIL(AVERROR(EINVAL)); | |
407 | } | |
408 | ||
409 | ret = av_parse_time(&t, tstr, 1); | |
410 | if (ret < 0) { | |
411 | av_log(log_ctx, AV_LOG_ERROR, | |
412 | "Invalid time duration specification '%s' in times list %s\n", tstr, times_str); | |
413 | FAIL(AVERROR(EINVAL)); | |
414 | } | |
415 | (*times)[i] = t; | |
416 | ||
417 | /* check on monotonicity */ | |
418 | if (i && (*times)[i-1] > (*times)[i]) { | |
419 | av_log(log_ctx, AV_LOG_ERROR, | |
420 | "Specified time %f is greater than the following time %f\n", | |
421 | (float)((*times)[i])/1000000, (float)((*times)[i-1])/1000000); | |
422 | FAIL(AVERROR(EINVAL)); | |
423 | } | |
424 | } | |
425 | ||
426 | end: | |
427 | av_free(times_str1); | |
428 | return ret; | |
429 | } | |
430 | ||
431 | static int parse_frames(void *log_ctx, int **frames, int *nb_frames, | |
432 | const char *frames_str) | |
433 | { | |
434 | char *p; | |
435 | int i, ret = 0; | |
436 | char *frames_str1 = av_strdup(frames_str); | |
437 | char *saveptr = NULL; | |
438 | ||
439 | if (!frames_str1) | |
440 | return AVERROR(ENOMEM); | |
441 | ||
442 | #define FAIL(err) ret = err; goto end | |
443 | ||
444 | *nb_frames = 1; | |
445 | for (p = frames_str1; *p; p++) | |
446 | if (*p == ',') | |
447 | (*nb_frames)++; | |
448 | ||
449 | *frames = av_malloc_array(*nb_frames, sizeof(**frames)); | |
450 | if (!*frames) { | |
451 | av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced frames array\n"); | |
452 | FAIL(AVERROR(ENOMEM)); | |
453 | } | |
454 | ||
455 | p = frames_str1; | |
456 | for (i = 0; i < *nb_frames; i++) { | |
457 | long int f; | |
458 | char *tailptr; | |
459 | char *fstr = av_strtok(p, ",", &saveptr); | |
460 | ||
461 | p = NULL; | |
462 | if (!fstr) { | |
463 | av_log(log_ctx, AV_LOG_ERROR, "Empty frame specification in frame list %s\n", | |
464 | frames_str); | |
465 | FAIL(AVERROR(EINVAL)); | |
466 | } | |
467 | f = strtol(fstr, &tailptr, 10); | |
468 | if (*tailptr || f <= 0 || f >= INT_MAX) { | |
469 | av_log(log_ctx, AV_LOG_ERROR, | |
470 | "Invalid argument '%s', must be a positive integer <= INT64_MAX\n", | |
471 | fstr); | |
472 | FAIL(AVERROR(EINVAL)); | |
473 | } | |
474 | (*frames)[i] = f; | |
475 | ||
476 | /* check on monotonicity */ | |
477 | if (i && (*frames)[i-1] > (*frames)[i]) { | |
478 | av_log(log_ctx, AV_LOG_ERROR, | |
479 | "Specified frame %d is greater than the following frame %d\n", | |
480 | (*frames)[i], (*frames)[i-1]); | |
481 | FAIL(AVERROR(EINVAL)); | |
482 | } | |
483 | } | |
484 | ||
485 | end: | |
486 | av_free(frames_str1); | |
487 | return ret; | |
488 | } | |
489 | ||
490 | static int open_null_ctx(AVIOContext **ctx) | |
491 | { | |
492 | int buf_size = 32768; | |
493 | uint8_t *buf = av_malloc(buf_size); | |
494 | if (!buf) | |
495 | return AVERROR(ENOMEM); | |
496 | *ctx = avio_alloc_context(buf, buf_size, AVIO_FLAG_WRITE, NULL, NULL, NULL, NULL); | |
497 | if (!*ctx) { | |
498 | av_free(buf); | |
499 | return AVERROR(ENOMEM); | |
500 | } | |
501 | return 0; | |
502 | } | |
503 | ||
504 | static void close_null_ctx(AVIOContext *pb) | |
505 | { | |
506 | av_free(pb->buffer); | |
507 | av_free(pb); | |
508 | } | |
509 | ||
510 | static int select_reference_stream(AVFormatContext *s) | |
511 | { | |
512 | SegmentContext *seg = s->priv_data; | |
513 | int ret, i; | |
514 | ||
515 | seg->reference_stream_index = -1; | |
516 | if (!strcmp(seg->reference_stream_specifier, "auto")) { | |
517 | /* select first index of type with highest priority */ | |
518 | int type_index_map[AVMEDIA_TYPE_NB]; | |
519 | static const enum AVMediaType type_priority_list[] = { | |
520 | AVMEDIA_TYPE_VIDEO, | |
521 | AVMEDIA_TYPE_AUDIO, | |
522 | AVMEDIA_TYPE_SUBTITLE, | |
523 | AVMEDIA_TYPE_DATA, | |
524 | AVMEDIA_TYPE_ATTACHMENT | |
525 | }; | |
526 | enum AVMediaType type; | |
527 | ||
528 | for (i = 0; i < AVMEDIA_TYPE_NB; i++) | |
529 | type_index_map[i] = -1; | |
530 | ||
531 | /* select first index for each type */ | |
532 | for (i = 0; i < s->nb_streams; i++) { | |
533 | type = s->streams[i]->codec->codec_type; | |
534 | if ((unsigned)type < AVMEDIA_TYPE_NB && type_index_map[type] == -1 | |
535 | /* ignore attached pictures/cover art streams */ | |
536 | && !(s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)) | |
537 | type_index_map[type] = i; | |
538 | } | |
539 | ||
540 | for (i = 0; i < FF_ARRAY_ELEMS(type_priority_list); i++) { | |
541 | type = type_priority_list[i]; | |
542 | if ((seg->reference_stream_index = type_index_map[type]) >= 0) | |
543 | break; | |
544 | } | |
545 | } else { | |
546 | for (i = 0; i < s->nb_streams; i++) { | |
547 | ret = avformat_match_stream_specifier(s, s->streams[i], | |
548 | seg->reference_stream_specifier); | |
549 | if (ret < 0) | |
550 | return ret; | |
551 | if (ret > 0) { | |
552 | seg->reference_stream_index = i; | |
553 | break; | |
554 | } | |
555 | } | |
556 | } | |
557 | ||
558 | if (seg->reference_stream_index < 0) { | |
559 | av_log(s, AV_LOG_ERROR, "Could not select stream matching identifier '%s'\n", | |
560 | seg->reference_stream_specifier); | |
561 | return AVERROR(EINVAL); | |
562 | } | |
563 | ||
564 | return 0; | |
565 | } | |
566 | ||
567 | static int seg_write_header(AVFormatContext *s) | |
568 | { | |
569 | SegmentContext *seg = s->priv_data; | |
570 | AVFormatContext *oc = NULL; | |
571 | AVDictionary *options = NULL; | |
572 | int ret; | |
f6fa7814 | 573 | int i; |
2ba45a60 DM |
574 | |
575 | seg->segment_count = 0; | |
576 | if (!seg->write_header_trailer) | |
577 | seg->individual_header_trailer = 0; | |
578 | ||
579 | if (!!seg->time_str + !!seg->times_str + !!seg->frames_str > 1) { | |
580 | av_log(s, AV_LOG_ERROR, | |
581 | "segment_time, segment_times, and segment_frames options " | |
582 | "are mutually exclusive, select just one of them\n"); | |
583 | return AVERROR(EINVAL); | |
584 | } | |
585 | ||
586 | if (seg->times_str) { | |
587 | if ((ret = parse_times(s, &seg->times, &seg->nb_times, seg->times_str)) < 0) | |
588 | return ret; | |
589 | } else if (seg->frames_str) { | |
590 | if ((ret = parse_frames(s, &seg->frames, &seg->nb_frames, seg->frames_str)) < 0) | |
591 | return ret; | |
592 | } else { | |
593 | /* set default value if not specified */ | |
594 | if (!seg->time_str) | |
595 | seg->time_str = av_strdup("2"); | |
596 | if ((ret = av_parse_time(&seg->time, seg->time_str, 1)) < 0) { | |
597 | av_log(s, AV_LOG_ERROR, | |
598 | "Invalid time duration specification '%s' for segment_time option\n", | |
599 | seg->time_str); | |
600 | return ret; | |
601 | } | |
602 | } | |
603 | ||
604 | if (seg->format_options_str) { | |
605 | ret = av_dict_parse_string(&seg->format_options, seg->format_options_str, "=", ":", 0); | |
606 | if (ret < 0) { | |
607 | av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", | |
608 | seg->format_options_str); | |
609 | goto fail; | |
610 | } | |
611 | } | |
612 | ||
613 | if (seg->list) { | |
614 | if (seg->list_type == LIST_TYPE_UNDEFINED) { | |
615 | if (av_match_ext(seg->list, "csv" )) seg->list_type = LIST_TYPE_CSV; | |
616 | else if (av_match_ext(seg->list, "ext" )) seg->list_type = LIST_TYPE_EXT; | |
617 | else if (av_match_ext(seg->list, "m3u8")) seg->list_type = LIST_TYPE_M3U8; | |
618 | else if (av_match_ext(seg->list, "ffcat,ffconcat")) seg->list_type = LIST_TYPE_FFCONCAT; | |
619 | else seg->list_type = LIST_TYPE_FLAT; | |
620 | } | |
621 | if ((ret = segment_list_open(s)) < 0) | |
622 | goto fail; | |
623 | } | |
624 | if (seg->list_type == LIST_TYPE_EXT) | |
625 | av_log(s, AV_LOG_WARNING, "'ext' list type option is deprecated in favor of 'csv'\n"); | |
626 | ||
627 | if ((ret = select_reference_stream(s)) < 0) | |
628 | goto fail; | |
629 | av_log(s, AV_LOG_VERBOSE, "Selected stream id:%d type:%s\n", | |
630 | seg->reference_stream_index, | |
631 | av_get_media_type_string(s->streams[seg->reference_stream_index]->codec->codec_type)); | |
632 | ||
633 | seg->oformat = av_guess_format(seg->format, s->filename, NULL); | |
634 | ||
635 | if (!seg->oformat) { | |
636 | ret = AVERROR_MUXER_NOT_FOUND; | |
637 | goto fail; | |
638 | } | |
639 | if (seg->oformat->flags & AVFMT_NOFILE) { | |
640 | av_log(s, AV_LOG_ERROR, "format %s not supported.\n", | |
641 | seg->oformat->name); | |
642 | ret = AVERROR(EINVAL); | |
643 | goto fail; | |
644 | } | |
645 | ||
646 | if ((ret = segment_mux_init(s)) < 0) | |
647 | goto fail; | |
648 | oc = seg->avf; | |
649 | ||
650 | if ((ret = set_segment_filename(s)) < 0) | |
651 | goto fail; | |
652 | ||
653 | if (seg->write_header_trailer) { | |
654 | if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, | |
655 | &s->interrupt_callback, NULL)) < 0) { | |
656 | av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); | |
657 | goto fail; | |
658 | } | |
659 | } else { | |
660 | if ((ret = open_null_ctx(&oc->pb)) < 0) | |
661 | goto fail; | |
662 | } | |
663 | ||
664 | av_dict_copy(&options, seg->format_options, 0); | |
665 | ret = avformat_write_header(oc, &options); | |
666 | if (av_dict_count(options)) { | |
667 | av_log(s, AV_LOG_ERROR, | |
668 | "Some of the provided format options in '%s' are not recognized\n", seg->format_options_str); | |
669 | ret = AVERROR(EINVAL); | |
670 | goto fail; | |
671 | } | |
672 | ||
673 | if (ret < 0) { | |
674 | avio_close(oc->pb); | |
675 | goto fail; | |
676 | } | |
677 | seg->segment_frame_count = 0; | |
678 | ||
f6fa7814 DM |
679 | av_assert0(s->nb_streams == oc->nb_streams); |
680 | for (i = 0; i < s->nb_streams; i++) { | |
681 | AVStream *inner_st = oc->streams[i]; | |
682 | AVStream *outer_st = s->streams[i]; | |
683 | avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); | |
684 | } | |
685 | ||
2ba45a60 DM |
686 | if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0) |
687 | s->avoid_negative_ts = 1; | |
688 | ||
689 | if (!seg->write_header_trailer) { | |
690 | close_null_ctx(oc->pb); | |
691 | if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, | |
692 | &s->interrupt_callback, NULL)) < 0) | |
693 | goto fail; | |
694 | } | |
695 | ||
696 | fail: | |
697 | av_dict_free(&options); | |
698 | if (ret) { | |
699 | if (seg->list) | |
700 | avio_close(seg->list_pb); | |
701 | if (seg->avf) | |
702 | avformat_free_context(seg->avf); | |
703 | } | |
704 | return ret; | |
705 | } | |
706 | ||
707 | static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) | |
708 | { | |
709 | SegmentContext *seg = s->priv_data; | |
710 | AVStream *st = s->streams[pkt->stream_index]; | |
711 | int64_t end_pts = INT64_MAX, offset; | |
712 | int start_frame = INT_MAX; | |
713 | int ret; | |
714 | struct tm ti; | |
715 | int64_t usecs; | |
716 | int64_t wrapped_val; | |
717 | ||
718 | if (seg->times) { | |
719 | end_pts = seg->segment_count < seg->nb_times ? | |
720 | seg->times[seg->segment_count] : INT64_MAX; | |
721 | } else if (seg->frames) { | |
722 | start_frame = seg->segment_count < seg->nb_frames ? | |
723 | seg->frames[seg->segment_count] : INT_MAX; | |
724 | } else { | |
725 | if (seg->use_clocktime) { | |
726 | int64_t avgt = av_gettime(); | |
727 | time_t sec = avgt / 1000000; | |
2ba45a60 | 728 | localtime_r(&sec, &ti); |
2ba45a60 DM |
729 | usecs = (int64_t)(ti.tm_hour*3600 + ti.tm_min*60 + ti.tm_sec) * 1000000 + (avgt % 1000000); |
730 | wrapped_val = usecs % seg->time; | |
731 | if (seg->last_cut != usecs && wrapped_val < seg->last_val) { | |
732 | seg->cut_pending = 1; | |
733 | seg->last_cut = usecs; | |
734 | } | |
735 | seg->last_val = wrapped_val; | |
736 | } else { | |
737 | end_pts = seg->time * (seg->segment_count+1); | |
738 | } | |
739 | } | |
740 | ||
741 | av_dlog(s, "packet stream:%d pts:%s pts_time:%s duration_time:%s is_key:%d frame:%d\n", | |
742 | pkt->stream_index, av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), | |
743 | av_ts2timestr(pkt->duration, &st->time_base), | |
744 | pkt->flags & AV_PKT_FLAG_KEY, | |
745 | pkt->stream_index == seg->reference_stream_index ? seg->frame_count : -1); | |
746 | ||
747 | if (pkt->stream_index == seg->reference_stream_index && | |
748 | pkt->flags & AV_PKT_FLAG_KEY && | |
749 | seg->segment_frame_count > 0 && | |
750 | (seg->cut_pending || seg->frame_count >= start_frame || | |
751 | (pkt->pts != AV_NOPTS_VALUE && | |
752 | av_compare_ts(pkt->pts, st->time_base, | |
753 | end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0))) { | |
754 | /* sanitize end time in case last packet didn't have a defined duration */ | |
755 | if (seg->cur_entry.last_duration == 0) | |
756 | seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base); | |
757 | ||
758 | if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0) | |
759 | goto fail; | |
760 | ||
761 | if ((ret = segment_start(s, seg->individual_header_trailer)) < 0) | |
762 | goto fail; | |
763 | ||
764 | seg->cut_pending = 0; | |
765 | seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap*seg->segment_idx_wrap_nb; | |
766 | seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base); | |
767 | seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); | |
768 | seg->cur_entry.end_time = seg->cur_entry.start_time + | |
769 | pkt->pts != AV_NOPTS_VALUE ? (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base) : 0; | |
770 | } else if (pkt->pts != AV_NOPTS_VALUE && pkt->stream_index == seg->reference_stream_index) { | |
771 | seg->cur_entry.end_time = | |
772 | FFMAX(seg->cur_entry.end_time, (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base)); | |
773 | seg->cur_entry.last_duration = pkt->duration; | |
774 | } | |
775 | ||
776 | if (seg->segment_frame_count == 0) { | |
777 | av_log(s, AV_LOG_VERBOSE, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d\n", | |
778 | seg->avf->filename, pkt->stream_index, | |
779 | av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), seg->frame_count); | |
780 | } | |
781 | ||
782 | av_log(s, AV_LOG_DEBUG, "stream:%d start_pts_time:%s pts:%s pts_time:%s dts:%s dts_time:%s", | |
783 | pkt->stream_index, | |
784 | av_ts2timestr(seg->cur_entry.start_pts, &AV_TIME_BASE_Q), | |
785 | av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), | |
786 | av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); | |
787 | ||
788 | /* compute new timestamps */ | |
789 | offset = av_rescale_q(seg->initial_offset - (seg->reset_timestamps ? seg->cur_entry.start_pts : 0), | |
790 | AV_TIME_BASE_Q, st->time_base); | |
791 | if (pkt->pts != AV_NOPTS_VALUE) | |
792 | pkt->pts += offset; | |
793 | if (pkt->dts != AV_NOPTS_VALUE) | |
794 | pkt->dts += offset; | |
795 | ||
796 | av_log(s, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n", | |
797 | av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), | |
798 | av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); | |
799 | ||
800 | ret = ff_write_chained(seg->avf, pkt->stream_index, pkt, s, seg->initial_offset || seg->reset_timestamps); | |
801 | ||
802 | fail: | |
803 | if (pkt->stream_index == seg->reference_stream_index) { | |
804 | seg->frame_count++; | |
805 | seg->segment_frame_count++; | |
806 | } | |
807 | ||
808 | return ret; | |
809 | } | |
810 | ||
811 | static int seg_write_trailer(struct AVFormatContext *s) | |
812 | { | |
813 | SegmentContext *seg = s->priv_data; | |
814 | AVFormatContext *oc = seg->avf; | |
815 | SegmentListEntry *cur, *next; | |
816 | ||
817 | int ret; | |
818 | if (!seg->write_header_trailer) { | |
819 | if ((ret = segment_end(s, 0, 1)) < 0) | |
820 | goto fail; | |
821 | open_null_ctx(&oc->pb); | |
822 | ret = av_write_trailer(oc); | |
823 | close_null_ctx(oc->pb); | |
824 | } else { | |
825 | ret = segment_end(s, 1, 1); | |
826 | } | |
827 | fail: | |
828 | if (seg->list) | |
829 | avio_close(seg->list_pb); | |
830 | ||
831 | av_dict_free(&seg->format_options); | |
832 | av_opt_free(seg); | |
833 | av_freep(&seg->times); | |
834 | av_freep(&seg->frames); | |
835 | ||
836 | cur = seg->segment_list_entries; | |
837 | while (cur) { | |
838 | next = cur->next; | |
839 | av_free(cur->filename); | |
840 | av_free(cur); | |
841 | cur = next; | |
842 | } | |
843 | ||
844 | avformat_free_context(oc); | |
845 | return ret; | |
846 | } | |
847 | ||
848 | #define OFFSET(x) offsetof(SegmentContext, x) | |
849 | #define E AV_OPT_FLAG_ENCODING_PARAM | |
850 | static const AVOption options[] = { | |
851 | { "reference_stream", "set reference stream", OFFSET(reference_stream_specifier), AV_OPT_TYPE_STRING, {.str = "auto"}, CHAR_MIN, CHAR_MAX, E }, | |
852 | { "segment_format", "set container format used for the segments", OFFSET(format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | |
853 | { "segment_format_options", "set list of options for the container format used for the segments", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | |
854 | { "segment_list", "set the segment list filename", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | |
855 | ||
856 | { "segment_list_flags","set flags affecting segment list generation", OFFSET(list_flags), AV_OPT_TYPE_FLAGS, {.i64 = SEGMENT_LIST_FLAG_CACHE }, 0, UINT_MAX, E, "list_flags"}, | |
857 | { "cache", "allow list caching", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX, E, "list_flags"}, | |
858 | { "live", "enable live-friendly list generation (useful for HLS)", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_LIVE }, INT_MIN, INT_MAX, E, "list_flags"}, | |
859 | ||
860 | { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |
861 | ||
862 | { "segment_list_type", "set the segment list type", OFFSET(list_type), AV_OPT_TYPE_INT, {.i64 = LIST_TYPE_UNDEFINED}, -1, LIST_TYPE_NB-1, E, "list_type" }, | |
863 | { "flat", "flat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FLAT }, INT_MIN, INT_MAX, E, "list_type" }, | |
864 | { "csv", "csv format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_CSV }, INT_MIN, INT_MAX, E, "list_type" }, | |
865 | { "ext", "extended format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_EXT }, INT_MIN, INT_MAX, E, "list_type" }, | |
866 | { "ffconcat", "ffconcat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FFCONCAT }, INT_MIN, INT_MAX, E, "list_type" }, | |
867 | { "m3u8", "M3U8 format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" }, | |
868 | { "hls", "Apple HTTP Live Streaming compatible", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" }, | |
869 | ||
870 | { "segment_atclocktime", "set segment to be cut at clocktime", OFFSET(use_clocktime), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E}, | |
871 | { "segment_time", "set segment duration", OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | |
872 | { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, 0, E }, | |
873 | { "segment_times", "set segment split time points", OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, | |
874 | { "segment_frames", "set segment split frame numbers", OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, | |
875 | { "segment_wrap", "set number after which the index wraps", OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |
876 | { "segment_list_entry_prefix", "set base url prefix for segments", OFFSET(entry_prefix), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | |
877 | { "segment_start_number", "set the sequence number of the first segment", OFFSET(segment_idx), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |
878 | { "segment_wrap_number", "set the number of wrap before the first segment", OFFSET(segment_idx_wrap_nb), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |
879 | ||
880 | { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, | |
881 | { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, | |
882 | { "reset_timestamps", "reset timestamps at the begin of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, | |
883 | { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E }, | |
884 | { NULL }, | |
885 | }; | |
886 | ||
887 | static const AVClass seg_class = { | |
888 | .class_name = "segment muxer", | |
889 | .item_name = av_default_item_name, | |
890 | .option = options, | |
891 | .version = LIBAVUTIL_VERSION_INT, | |
892 | }; | |
893 | ||
894 | AVOutputFormat ff_segment_muxer = { | |
895 | .name = "segment", | |
896 | .long_name = NULL_IF_CONFIG_SMALL("segment"), | |
897 | .priv_data_size = sizeof(SegmentContext), | |
898 | .flags = AVFMT_NOFILE|AVFMT_GLOBALHEADER, | |
899 | .write_header = seg_write_header, | |
900 | .write_packet = seg_write_packet, | |
901 | .write_trailer = seg_write_trailer, | |
902 | .priv_class = &seg_class, | |
903 | }; | |
904 | ||
905 | static const AVClass sseg_class = { | |
906 | .class_name = "stream_segment muxer", | |
907 | .item_name = av_default_item_name, | |
908 | .option = options, | |
909 | .version = LIBAVUTIL_VERSION_INT, | |
910 | }; | |
911 | ||
912 | AVOutputFormat ff_stream_segment_muxer = { | |
913 | .name = "stream_segment,ssegment", | |
914 | .long_name = NULL_IF_CONFIG_SMALL("streaming segment muxer"), | |
915 | .priv_data_size = sizeof(SegmentContext), | |
916 | .flags = AVFMT_NOFILE, | |
917 | .write_header = seg_write_header, | |
918 | .write_packet = seg_write_packet, | |
919 | .write_trailer = seg_write_trailer, | |
920 | .priv_class = &sseg_class, | |
921 | }; |