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