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 };