00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00027 #include <float.h>
00028
00029 #include "avformat.h"
00030 #include "internal.h"
00031
00032 #include "libavutil/avassert.h"
00033 #include "libavutil/log.h"
00034 #include "libavutil/opt.h"
00035 #include "libavutil/avstring.h"
00036 #include "libavutil/parseutils.h"
00037 #include "libavutil/mathematics.h"
00038
00039 typedef enum {
00040 LIST_TYPE_UNDEFINED = -1,
00041 LIST_TYPE_FLAT = 0,
00042 LIST_TYPE_CSV,
00043 LIST_TYPE_M3U8,
00044 LIST_TYPE_EXT,
00045 LIST_TYPE_NB,
00046 } ListType;
00047
00048
00049 #define SEGMENT_LIST_FLAG_CACHE 1
00050 #define SEGMENT_LIST_FLAG_LIVE 2
00051
00052 typedef struct {
00053 const AVClass *class;
00054 int segment_idx;
00055 int segment_idx_wrap;
00056 int segment_count;
00057 AVFormatContext *avf;
00058 char *format;
00059 char *list;
00060 int list_count;
00061 int list_flags;
00062 int list_size;
00063 double list_max_segment_time;
00064 ListType list_type;
00065 AVIOContext *list_pb;
00066 char *time_str;
00067 int64_t time;
00068 char *times_str;
00069 int64_t *times;
00070 int nb_times;
00071 char *time_delta_str;
00072 int64_t time_delta;
00073 int has_video;
00074 double start_time, end_time;
00075 } SegmentContext;
00076
00077 static void print_csv_escaped_str(AVIOContext *ctx, const char *str)
00078 {
00079 int needs_quoting = !!str[strcspn(str, "\",\n\r")];
00080
00081 if (needs_quoting)
00082 avio_w8(ctx, '"');
00083
00084 for (; *str; str++) {
00085 if (*str == '"')
00086 avio_w8(ctx, '"');
00087 avio_w8(ctx, *str);
00088 }
00089 if (needs_quoting)
00090 avio_w8(ctx, '"');
00091 }
00092
00093 static int segment_start(AVFormatContext *s)
00094 {
00095 SegmentContext *seg = s->priv_data;
00096 AVFormatContext *oc = seg->avf;
00097 int err = 0;
00098
00099 if (seg->segment_idx_wrap)
00100 seg->segment_idx %= seg->segment_idx_wrap;
00101
00102 if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
00103 s->filename, seg->segment_idx++) < 0) {
00104 av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename);
00105 return AVERROR(EINVAL);
00106 }
00107 seg->segment_count++;
00108
00109 if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
00110 &s->interrupt_callback, NULL)) < 0)
00111 return err;
00112
00113 if (!oc->priv_data && oc->oformat->priv_data_size > 0) {
00114 oc->priv_data = av_mallocz(oc->oformat->priv_data_size);
00115 if (!oc->priv_data) {
00116 avio_close(oc->pb);
00117 return AVERROR(ENOMEM);
00118 }
00119 if (oc->oformat->priv_class) {
00120 *(const AVClass**)oc->priv_data = oc->oformat->priv_class;
00121 av_opt_set_defaults(oc->priv_data);
00122 }
00123 }
00124
00125 if ((err = oc->oformat->write_header(oc)) < 0) {
00126 goto fail;
00127 }
00128
00129 return 0;
00130
00131 fail:
00132 av_log(oc, AV_LOG_ERROR, "Failure occurred when starting segment '%s'\n",
00133 oc->filename);
00134 avio_close(oc->pb);
00135 av_freep(&oc->priv_data);
00136
00137 return err;
00138 }
00139
00140 static int segment_list_open(AVFormatContext *s)
00141 {
00142 SegmentContext *seg = s->priv_data;
00143 int ret;
00144
00145 ret = avio_open2(&seg->list_pb, seg->list, AVIO_FLAG_WRITE,
00146 &s->interrupt_callback, NULL);
00147 if (ret < 0)
00148 return ret;
00149 seg->list_max_segment_time = 0;
00150
00151 if (seg->list_type == LIST_TYPE_M3U8) {
00152 avio_printf(seg->list_pb, "#EXTM3U\n");
00153 avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n");
00154 avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->list_count);
00155 avio_printf(seg->list_pb, "#EXT-X-ALLOWCACHE:%d\n",
00156 !!(seg->list_flags & SEGMENT_LIST_FLAG_CACHE));
00157 if (seg->list_flags & SEGMENT_LIST_FLAG_LIVE)
00158 avio_printf(seg->list_pb,
00159 "#EXT-X-TARGETDURATION:%"PRId64"\n", seg->time / 1000000);
00160 }
00161
00162 return ret;
00163 }
00164
00165 static void segment_list_close(AVFormatContext *s)
00166 {
00167 SegmentContext *seg = s->priv_data;
00168
00169 if (seg->list_type == LIST_TYPE_M3U8) {
00170 if (!(seg->list_flags & SEGMENT_LIST_FLAG_LIVE))
00171 avio_printf(seg->list_pb, "#EXT-X-TARGETDURATION:%d\n",
00172 (int)ceil(seg->list_max_segment_time));
00173 avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n");
00174 }
00175 seg->list_count++;
00176
00177 avio_close(seg->list_pb);
00178 }
00179
00180 static int segment_end(AVFormatContext *s)
00181 {
00182 SegmentContext *seg = s->priv_data;
00183 AVFormatContext *oc = seg->avf;
00184 int ret = 0;
00185
00186 if (oc->oformat->write_trailer)
00187 ret = oc->oformat->write_trailer(oc);
00188
00189 if (ret < 0)
00190 av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
00191 oc->filename);
00192
00193 if (seg->list) {
00194 if (seg->list_size && !(seg->segment_count % seg->list_size)) {
00195 segment_list_close(s);
00196 if ((ret = segment_list_open(s)) < 0)
00197 goto end;
00198 }
00199
00200 if (seg->list_type == LIST_TYPE_FLAT) {
00201 avio_printf(seg->list_pb, "%s\n", oc->filename);
00202 } else if (seg->list_type == LIST_TYPE_CSV || seg->list_type == LIST_TYPE_EXT) {
00203 print_csv_escaped_str(seg->list_pb, oc->filename);
00204 avio_printf(seg->list_pb, ",%f,%f\n", seg->start_time, seg->end_time);
00205 } else if (seg->list_type == LIST_TYPE_M3U8) {
00206 avio_printf(seg->list_pb, "#EXTINF:%f,\n%s\n",
00207 seg->end_time - seg->start_time, oc->filename);
00208 }
00209 seg->list_max_segment_time = FFMAX(seg->end_time - seg->start_time, seg->list_max_segment_time);
00210 avio_flush(seg->list_pb);
00211 }
00212
00213 end:
00214 avio_close(oc->pb);
00215 if (oc->oformat->priv_class)
00216 av_opt_free(oc->priv_data);
00217 av_freep(&oc->priv_data);
00218
00219 return ret;
00220 }
00221
00222 static int parse_times(void *log_ctx, int64_t **times, int *nb_times,
00223 const char *times_str)
00224 {
00225 char *p;
00226 int i, ret = 0;
00227 char *times_str1 = av_strdup(times_str);
00228 char *saveptr = NULL;
00229
00230 if (!times_str1)
00231 return AVERROR(ENOMEM);
00232
00233 #define FAIL(err) ret = err; goto end
00234
00235 *nb_times = 1;
00236 for (p = times_str1; *p; p++)
00237 if (*p == ',')
00238 (*nb_times)++;
00239
00240 *times = av_malloc(sizeof(**times) * *nb_times);
00241 if (!*times) {
00242 av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced times array\n");
00243 FAIL(AVERROR(ENOMEM));
00244 }
00245
00246 p = times_str1;
00247 for (i = 0; i < *nb_times; i++) {
00248 int64_t t;
00249 char *tstr = av_strtok(p, ",", &saveptr);
00250 av_assert0(tstr);
00251 p = NULL;
00252
00253 ret = av_parse_time(&t, tstr, 1);
00254 if (ret < 0) {
00255 av_log(log_ctx, AV_LOG_ERROR,
00256 "Invalid time duration specification in %s\n", p);
00257 FAIL(AVERROR(EINVAL));
00258 }
00259 (*times)[i] = t;
00260
00261
00262 if (i && (*times)[i-1] > (*times)[i]) {
00263 av_log(log_ctx, AV_LOG_ERROR,
00264 "Specified time %f is greater than the following time %f\n",
00265 (float)((*times)[i])/1000000, (float)((*times)[i-1])/1000000);
00266 FAIL(AVERROR(EINVAL));
00267 }
00268 }
00269
00270 end:
00271 av_free(times_str1);
00272 return ret;
00273 }
00274
00275 static int seg_write_header(AVFormatContext *s)
00276 {
00277 SegmentContext *seg = s->priv_data;
00278 AVFormatContext *oc;
00279 int ret, i;
00280
00281 seg->segment_count = 0;
00282
00283 if (seg->time_str && seg->times_str) {
00284 av_log(s, AV_LOG_ERROR,
00285 "segment_time and segment_times options are mutually exclusive, select just one of them\n");
00286 return AVERROR(EINVAL);
00287 }
00288
00289 if ((seg->list_flags & SEGMENT_LIST_FLAG_LIVE) && seg->times_str) {
00290 av_log(s, AV_LOG_ERROR,
00291 "segment_flags +live and segment_times options are mutually exclusive:"
00292 "specify -segment_time if you want a live-friendly list\n");
00293 return AVERROR(EINVAL);
00294 }
00295
00296 if (seg->times_str) {
00297 if ((ret = parse_times(s, &seg->times, &seg->nb_times, seg->times_str)) < 0)
00298 return ret;
00299 } else {
00300
00301 if (!seg->time_str)
00302 seg->time_str = av_strdup("2");
00303 if ((ret = av_parse_time(&seg->time, seg->time_str, 1)) < 0) {
00304 av_log(s, AV_LOG_ERROR,
00305 "Invalid time duration specification '%s' for segment_time option\n",
00306 seg->time_str);
00307 return ret;
00308 }
00309 }
00310
00311 if (seg->time_delta_str) {
00312 if ((ret = av_parse_time(&seg->time_delta, seg->time_delta_str, 1)) < 0) {
00313 av_log(s, AV_LOG_ERROR,
00314 "Invalid time duration specification '%s' for delta option\n",
00315 seg->time_delta_str);
00316 return ret;
00317 }
00318 }
00319
00320 oc = avformat_alloc_context();
00321
00322 if (!oc)
00323 return AVERROR(ENOMEM);
00324
00325 if (seg->list) {
00326 if (seg->list_type == LIST_TYPE_UNDEFINED) {
00327 if (av_match_ext(seg->list, "csv" )) seg->list_type = LIST_TYPE_CSV;
00328 else if (av_match_ext(seg->list, "ext" )) seg->list_type = LIST_TYPE_EXT;
00329 else if (av_match_ext(seg->list, "m3u8")) seg->list_type = LIST_TYPE_M3U8;
00330 else seg->list_type = LIST_TYPE_FLAT;
00331 }
00332 if ((ret = segment_list_open(s)) < 0)
00333 goto fail;
00334 }
00335 if (seg->list_type == LIST_TYPE_EXT)
00336 av_log(s, AV_LOG_WARNING, "'ext' list type option is deprecated in favor of 'csv'\n");
00337
00338 for (i = 0; i< s->nb_streams; i++)
00339 seg->has_video +=
00340 (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO);
00341
00342 if (seg->has_video > 1)
00343 av_log(s, AV_LOG_WARNING,
00344 "More than a single video stream present, "
00345 "expect issues decoding it.\n");
00346
00347 oc->oformat = av_guess_format(seg->format, s->filename, NULL);
00348
00349 if (!oc->oformat) {
00350 ret = AVERROR_MUXER_NOT_FOUND;
00351 goto fail;
00352 }
00353 if (oc->oformat->flags & AVFMT_NOFILE) {
00354 av_log(s, AV_LOG_ERROR, "format %s not supported.\n",
00355 oc->oformat->name);
00356 ret = AVERROR(EINVAL);
00357 goto fail;
00358 }
00359
00360 seg->avf = oc;
00361
00362 oc->streams = s->streams;
00363 oc->nb_streams = s->nb_streams;
00364
00365 if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
00366 s->filename, seg->segment_idx++) < 0) {
00367 ret = AVERROR(EINVAL);
00368 goto fail;
00369 }
00370 seg->segment_count++;
00371
00372 if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
00373 &s->interrupt_callback, NULL)) < 0)
00374 goto fail;
00375
00376 if ((ret = avformat_write_header(oc, NULL)) < 0) {
00377 avio_close(oc->pb);
00378 goto fail;
00379 }
00380
00381 fail:
00382 if (ret) {
00383 if (oc) {
00384 oc->streams = NULL;
00385 oc->nb_streams = 0;
00386 avformat_free_context(oc);
00387 }
00388 if (seg->list)
00389 segment_list_close(s);
00390 }
00391 return ret;
00392 }
00393
00394 static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
00395 {
00396 SegmentContext *seg = s->priv_data;
00397 AVFormatContext *oc = seg->avf;
00398 AVStream *st = oc->streams[pkt->stream_index];
00399 int64_t end_pts;
00400 int ret;
00401
00402 if (seg->times) {
00403 end_pts = seg->segment_count <= seg->nb_times ?
00404 seg->times[seg->segment_count-1] : INT64_MAX;
00405 } else {
00406 end_pts = seg->time * seg->segment_count;
00407 }
00408
00409
00410 if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO || !seg->has_video) &&
00411 av_compare_ts(pkt->pts, st->time_base,
00412 end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0 &&
00413 pkt->flags & AV_PKT_FLAG_KEY) {
00414
00415 av_log(s, AV_LOG_DEBUG, "Next segment starts with packet stream:%d pts:%"PRId64" pts_time:%f\n",
00416 pkt->stream_index, pkt->pts, pkt->pts * av_q2d(st->time_base));
00417
00418 if ((ret = segment_end(s)) < 0 || (ret = segment_start(s)) < 0)
00419 goto fail;
00420 seg->start_time = (double)pkt->pts * av_q2d(st->time_base);
00421 } else if (pkt->pts != AV_NOPTS_VALUE) {
00422 seg->end_time = FFMAX(seg->end_time,
00423 (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base));
00424 }
00425
00426 ret = oc->oformat->write_packet(oc, pkt);
00427
00428 fail:
00429 if (ret < 0) {
00430 oc->streams = NULL;
00431 oc->nb_streams = 0;
00432 if (seg->list)
00433 avio_close(seg->list_pb);
00434 avformat_free_context(oc);
00435 }
00436
00437 return ret;
00438 }
00439
00440 static int seg_write_trailer(struct AVFormatContext *s)
00441 {
00442 SegmentContext *seg = s->priv_data;
00443 AVFormatContext *oc = seg->avf;
00444 int ret = segment_end(s);
00445 if (seg->list)
00446 segment_list_close(s);
00447
00448 av_opt_free(seg);
00449 av_freep(&seg->times);
00450
00451 oc->streams = NULL;
00452 oc->nb_streams = 0;
00453 avformat_free_context(oc);
00454 return ret;
00455 }
00456
00457 #define OFFSET(x) offsetof(SegmentContext, x)
00458 #define E AV_OPT_FLAG_ENCODING_PARAM
00459 static const AVOption options[] = {
00460 { "segment_format", "set container format used for the segments", OFFSET(format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
00461 { "segment_list", "set the segment list filename", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
00462
00463 { "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"},
00464 { "cache", "allow list caching", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX, E, "list_flags"},
00465 { "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"},
00466
00467 { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
00468 { "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" },
00469 { "flat", "flat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FLAT }, INT_MIN, INT_MAX, 0, "list_type" },
00470 { "csv", "csv format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_CSV }, INT_MIN, INT_MAX, 0, "list_type" },
00471 { "ext", "extended format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_EXT }, INT_MIN, INT_MAX, 0, "list_type" },
00472 { "m3u8", "M3U8 format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, 0, "list_type" },
00473 { "segment_time", "set segment duration", OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
00474 { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta_str), AV_OPT_TYPE_STRING, {.str = "0"}, 0, 0, E },
00475 { "segment_times", "set segment split time points", OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E },
00476 { "segment_wrap", "set number after which the index wraps", OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
00477 { NULL },
00478 };
00479
00480 static const AVClass seg_class = {
00481 .class_name = "segment muxer",
00482 .item_name = av_default_item_name,
00483 .option = options,
00484 .version = LIBAVUTIL_VERSION_INT,
00485 };
00486
00487 AVOutputFormat ff_segment_muxer = {
00488 .name = "segment",
00489 .long_name = NULL_IF_CONFIG_SMALL("segment"),
00490 .priv_data_size = sizeof(SegmentContext),
00491 .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
00492 .write_header = seg_write_header,
00493 .write_packet = seg_write_packet,
00494 .write_trailer = seg_write_trailer,
00495 .priv_class = &seg_class,
00496 };
00497
00498 static const AVClass sseg_class = {
00499 .class_name = "stream_segment muxer",
00500 .item_name = av_default_item_name,
00501 .option = options,
00502 .version = LIBAVUTIL_VERSION_INT,
00503 };
00504
00505 AVOutputFormat ff_stream_segment_muxer = {
00506 .name = "stream_segment,ssegment",
00507 .long_name = NULL_IF_CONFIG_SMALL("streaming segment muxer"),
00508 .priv_data_size = sizeof(SegmentContext),
00509 .flags = AVFMT_NOFILE,
00510 .write_header = seg_write_header,
00511 .write_packet = seg_write_packet,
00512 .write_trailer = seg_write_trailer,
00513 .priv_class = &sseg_class,
00514 };