Merge commit '5c08ae4f37281441188447cd04dcaf7cd7ce031f'
authorMichael Niedermayer <michaelni@gmx.at>
Tue, 29 Apr 2014 13:42:04 +0000 (15:42 +0200)
committerMichael Niedermayer <michaelni@gmx.at>
Tue, 29 Apr 2014 13:57:43 +0000 (15:57 +0200)
* commit '5c08ae4f37281441188447cd04dcaf7cd7ce031f':
  segment: Add an option to prepend a string to the list entries

Conflicts:
doc/muxers.texi
libavformat/segment.c

See: 5e278c19c752d65fdc1da1ceb599b091f71a4b4b
Merged-by: Michael Niedermayer <michaelni@gmx.at>
1  2 
doc/muxers.texi
libavformat/segment.c

diff --cc doc/muxers.texi
@@@ -766,271 -474,20 +766,271 @@@ reference stream. The default value is 
  @item segment_format @var{format}
  Override the inner container format, by default it is guessed by the filename
  extension.
 -@item segment_time @var{t}
 -Set segment duration to @var{t} seconds.
 +
  @item segment_list @var{name}
 -Generate also a listfile named @var{name}.
 +Generate also a listfile named @var{name}. If not specified no
 +listfile is generated.
 +
 +@item segment_list_flags @var{flags}
 +Set flags affecting the segment list generation.
 +
 +It currently supports the following flags:
 +@table @samp
 +@item cache
 +Allow caching (only affects M3U8 list files).
 +
 +@item live
 +Allow live-friendly file generation.
 +@end table
 +
  @item segment_list_size @var{size}
 -Overwrite the listfile once it reaches @var{size} entries.
 +Update the list file so that it contains at most the last @var{size}
 +segments. If 0 the list file will contain all the segments. Default
 +value is 0.
 +
  @item segment_list_entry_prefix @var{prefix}
- Set @var{prefix} to prepend to the name of each entry filename. By
- default no prefix is applied.
+ Prepend @var{prefix} to each entry. Useful to generate absolute paths.
++By default no prefix is applied.
 +
 +@item segment_list_type @var{type}
 +Specify the format for the segment list file.
 +
 +The following values are recognized:
 +@table @samp
 +@item flat
 +Generate a flat list for the created segments, one segment per line.
 +
 +@item csv, ext
 +Generate a list for the created segments, one segment per line,
 +each line matching the format (comma-separated values):
 +@example
 +@var{segment_filename},@var{segment_start_time},@var{segment_end_time}
 +@end example
 +
 +@var{segment_filename} is the name of the output file generated by the
 +muxer according to the provided pattern. CSV escaping (according to
 +RFC4180) is applied if required.
 +
 +@var{segment_start_time} and @var{segment_end_time} specify
 +the segment start and end time expressed in seconds.
 +
 +A list file with the suffix @code{".csv"} or @code{".ext"} will
 +auto-select this format.
 +
 +@samp{ext} is deprecated in favor or @samp{csv}.
 +
 +@item ffconcat
 +Generate an ffconcat file for the created segments. The resulting file
 +can be read using the FFmpeg @ref{concat} demuxer.
 +
 +A list file with the suffix @code{".ffcat"} or @code{".ffconcat"} will
 +auto-select this format.
 +
 +@item m3u8
 +Generate an extended M3U8 file, version 3, compliant with
 +@url{http://tools.ietf.org/id/draft-pantos-http-live-streaming}.
 +
 +A list file with the suffix @code{".m3u8"} will auto-select this format.
 +@end table
 +
 +If not specified the type is guessed from the list file name suffix.
 +
 +@item segment_time @var{time}
 +Set segment duration to @var{time}, the value must be a duration
 +specification. Default value is "2". See also the
 +@option{segment_times} option.
 +
 +Note that splitting may not be accurate, unless you force the
 +reference stream key-frames at the given time. See the introductory
 +notice and the examples below.
 +
 +@item segment_time_delta @var{delta}
 +Specify the accuracy time when selecting the start time for a
 +segment, expressed as a duration specification. Default value is "0".
 +
 +When delta is specified a key-frame will start a new segment if its
 +PTS satisfies the relation:
 +@example
 +PTS >= start_time - time_delta
 +@end example
 +
 +This option is useful when splitting video content, which is always
 +split at GOP boundaries, in case a key frame is found just before the
 +specified split time.
 +
 +In particular may be used in combination with the @file{ffmpeg} option
 +@var{force_key_frames}. The key frame times specified by
 +@var{force_key_frames} may not be set accurately because of rounding
 +issues, with the consequence that a key frame time may result set just
 +before the specified time. For constant frame rate videos a value of
 +1/(2*@var{frame_rate}) should address the worst case mismatch between
 +the specified time and the time set by @var{force_key_frames}.
 +
 +@item segment_times @var{times}
 +Specify a list of split points. @var{times} contains a list of comma
 +separated duration specifications, in increasing order. See also
 +the @option{segment_time} option.
 +
 +@item segment_frames @var{frames}
 +Specify a list of split video frame numbers. @var{frames} contains a
 +list of comma separated integer numbers, in increasing order.
 +
 +This option specifies to start a new segment whenever a reference
 +stream key frame is found and the sequential number (starting from 0)
 +of the frame is greater or equal to the next value in the list.
 +
  @item segment_wrap @var{limit}
  Wrap around segment index once it reaches @var{limit}.
 +
 +@item segment_start_number @var{number}
 +Set the sequence number of the first segment. Defaults to @code{0}.
 +
 +@item reset_timestamps @var{1|0}
 +Reset timestamps at the begin of each segment, so that each segment
 +will start with near-zero timestamps. It is meant to ease the playback
 +of the generated segments. May not work with some combinations of
 +muxers/codecs. It is set to @code{0} by default.
 +
 +@item initial_offset @var{offset}
 +Specify timestamp offset to apply to the output packet timestamps. The
 +argument must be a time duration specification, and defaults to 0.
  @end table
  
 +@subsection Examples
 +
 +@itemize
 +@item
 +To remux the content of file @file{in.mkv} to a list of segments
 +@file{out-000.nut}, @file{out-001.nut}, etc., and write the list of
 +generated segments to @file{out.list}:
 +@example
 +ffmpeg -i in.mkv -codec copy -map 0 -f segment -segment_list out.list out%03d.nut
 +@end example
 +
 +@item
 +As the example above, but segment the input file according to the split
 +points specified by the @var{segment_times} option:
 +@example
 +ffmpeg -i in.mkv -codec copy -map 0 -f segment -segment_list out.csv -segment_times 1,2,3,5,8,13,21 out%03d.nut
 +@end example
 +
 +@item
 +As the example above, but use the @command{ffmpeg} @option{force_key_frames}
 +option to force key frames in the input at the specified location, together
 +with the segment option @option{segment_time_delta} to account for
 +possible roundings operated when setting key frame times.
 +@example
 +ffmpeg -i in.mkv -force_key_frames 1,2,3,5,8,13,21 -codec:v mpeg4 -codec:a pcm_s16le -map 0 \
 +-f segment -segment_list out.csv -segment_times 1,2,3,5,8,13,21 -segment_time_delta 0.05 out%03d.nut
 +@end example
 +In order to force key frames on the input file, transcoding is
 +required.
 +
 +@item
 +Segment the input file by splitting the input file according to the
 +frame numbers sequence specified with the @option{segment_frames} option:
  @example
 -avconv -i in.mkv -c copy -map 0 -f segment -list out.list out%03d.nut
 +ffmpeg -i in.mkv -codec copy -map 0 -f segment -segment_list out.csv -segment_frames 100,200,300,500,800 out%03d.nut
  @end example
  
 +@item
 +To convert the @file{in.mkv} to TS segments using the @code{libx264}
 +and @code{libfaac} encoders:
 +@example
 +ffmpeg -i in.mkv -map 0 -codec:v libx264 -codec:a libfaac -f ssegment -segment_list out.list out%03d.ts
 +@end example
 +
 +@item
 +Segment the input file, and create an M3U8 live playlist (can be used
 +as live HLS source):
 +@example
 +ffmpeg -re -i in.mkv -codec copy -map 0 -f segment -segment_list playlist.m3u8 \
 +-segment_list_flags +live -segment_time 10 out%03d.mkv
 +@end example
 +@end itemize
 +
 +@section tee
 +
 +The tee muxer can be used to write the same data to several files or any
 +other kind of muxer. It can be used, for example, to both stream a video to
 +the network and save it to disk at the same time.
 +
 +It is different from specifying several outputs to the @command{ffmpeg}
 +command-line tool because the audio and video data will be encoded only once
 +with the tee muxer; encoding can be a very expensive process. It is not
 +useful when using the libavformat API directly because it is then possible
 +to feed the same packets to several muxers directly.
 +
 +The slave outputs are specified in the file name given to the muxer,
 +separated by '|'. If any of the slave name contains the '|' separator,
 +leading or trailing spaces or any special character, it must be
 +escaped (see @ref{quoting_and_escaping,,the "Quoting and escaping"
 +section in the ffmpeg-utils(1) manual,ffmpeg-utils}).
 +
 +Muxer options can be specified for each slave by prepending them as a list of
 +@var{key}=@var{value} pairs separated by ':', between square brackets. If
 +the options values contain a special character or the ':' separator, they
 +must be escaped; note that this is a second level escaping.
 +
 +The following special options are also recognized:
 +@table @option
 +@item f
 +Specify the format name. Useful if it cannot be guessed from the
 +output name suffix.
 +
 +@item bsfs[/@var{spec}]
 +Specify a list of bitstream filters to apply to the specified
 +output.
 +
 +It is possible to specify to which streams a given bitstream filter
 +applies, by appending a stream specifier to the option separated by
 +@code{/}. @var{spec} must be a stream specifier (see @ref{Format
 +stream specifiers}).  If the stream specifier is not specified, the
 +bitstream filters will be applied to all streams in the output.
 +
 +Several bitstream filters can be specified, separated by ",".
 +
 +@item select
 +Select the streams that should be mapped to the slave output,
 +specified by a stream specifier. If not specified, this defaults to
 +all the input streams.
 +@end table
 +
 +@subsection Examples
 +
 +@itemize
 +@item
 +Encode something and both archive it in a WebM file and stream it
 +as MPEG-TS over UDP (the streams need to be explicitly mapped):
 +@example
 +ffmpeg -i ... -c:v libx264 -c:a mp2 -f tee -map 0:v -map 0:a
 +  "archive-20121107.mkv|[f=mpegts]udp://10.0.1.255:1234/"
 +@end example
 +
 +@item
 +Use @command{ffmpeg} to encode the input, and send the output
 +to three different destinations. The @code{dump_extra} bitstream
 +filter is used to add extradata information to all the output video
 +keyframes packets, as requested by the MPEG-TS format. The select
 +option is applied to @file{out.aac} in order to make it contain only
 +audio packets.
 +@example
 +ffmpeg -i ... -map 0 -flags +global_header -c:v libx264 -c:a aac -strict experimental
 +       -f tee "[bsfs/v=dump_extra]out.ts|[movflags=+faststart]out.mp4|[select=a]out.aac"
 +@end example
 +
 +@item
 +As below, but select only stream @code{a:1} for the audio output. Note
 +that a second level escaping must be performed, as ":" is a special
 +character used to separate options.
 +@example
 +ffmpeg -i ... -map 0 -flags +global_header -c:v libx264 -c:a aac -strict experimental
 +       -f tee "[bsfs/v=dump_extra]out.ts|[movflags=+faststart]out.mp4|[select=\'a:1\']out.aac"
 +@end example
 +@end itemize
 +
 +Note: some codecs may need different options depending on the output format;
 +the auto-detection of this can not work with the tee muxer. The main example
 +is the @option{global_header} flag.
 +
  @c man end MUXERS
@@@ -63,62 -32,28 +63,62 @@@ typedef enum 
  
  typedef struct {
      const AVClass *class;  /**< Class for private options. */
 -    int number;
 +    int segment_idx;       ///< index of the segment file to write, starting from 0
 +    int segment_idx_wrap;  ///< number after which the index wraps
 +    int segment_idx_wrap_nb;  ///< number of time the index has wraped
 +    int segment_count;     ///< number of segment files already written
      AVOutputFormat *oformat;
      AVFormatContext *avf;
 -    char *format;          /**< Set by a private option. */
 -    char *list;            /**< Set by a private option. */
 -    char *entry_prefix;    /**< Set by a private option. */
 -    int  list_type;        /**< Set by a private option. */
 -    float time;            /**< Set by a private option. */
 -    int  size;             /**< Set by a private option. */
 -    int  wrap;             /**< Set by a private option. */
 +    char *format;          ///< format to use for output segment files
 +    char *list;            ///< filename for the segment list file
 +    int   list_flags;      ///< flags affecting list generation
 +    int   list_size;       ///< number of entries for the segment list file
-     char *list_entry_prefix; ///< prefix to add to list entry filenames
++    char *entry_prefix;    ///< prefix to add to list entry filenames
 +    ListType list_type;    ///< set the list type
 +    AVIOContext *list_pb;  ///< list file put-byte context
 +    char *time_str;        ///< segment duration specification string
 +    int64_t time;          ///< segment duration
 +
 +    char *times_str;       ///< segment times specification string
 +    int64_t *times;        ///< list of segment interval specification
 +    int nb_times;          ///< number of elments in the times array
 +
 +    char *frames_str;      ///< segment frame numbers specification string
 +    int *frames;           ///< list of frame number specification
 +    int nb_frames;         ///< number of elments in the frames array
 +    int frame_count;
 +
 +    int64_t time_delta;
      int  individual_header_trailer; /**< Set by a private option. */
      int  write_header_trailer; /**< Set by a private option. */
 -    int64_t offset_time;
 -    int64_t recording_time;
 -    int has_video;
 -    AVIOContext *pb;
 +
 +    int reset_timestamps;  ///< reset timestamps at the begin of each segment
 +    int64_t initial_offset;    ///< initial timestamps offset, expressed in microseconds
 +    char *reference_stream_specifier; ///< reference stream specifier
 +    int   reference_stream_index;
 +
 +    SegmentListEntry cur_entry;
 +    SegmentListEntry *segment_list_entries;
 +    SegmentListEntry *segment_list_entries_end;
 +
 +    int is_first_pkt;      ///< tells if it is the first packet in the segment
  } SegmentContext;
  
 -enum {
 -    LIST_FLAT,
 -    LIST_HLS
 -};
 +static void print_csv_escaped_str(AVIOContext *ctx, const char *str)
 +{
 +    int needs_quoting = !!str[strcspn(str, "\",\n\r")];
 +
 +    if (needs_quoting)
 +        avio_w8(ctx, '"');
 +
 +    for (; *str; str++) {
 +        if (*str == '"')
 +            avio_w8(ctx, '"');
 +        avio_w8(ctx, *str);
 +    }
 +    if (needs_quoting)
 +        avio_w8(ctx, '"');
 +}
  
  static int segment_mux_init(AVFormatContext *s)
  {
      return 0;
  }
  
 -static int segment_hls_window(AVFormatContext *s, int last)
 +static int set_segment_filename(AVFormatContext *s)
  {
      SegmentContext *seg = s->priv_data;
 -    int i, ret = 0;
 -    char buf[1024];
 -
 -    if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE,
 -                              &s->interrupt_callback, NULL)) < 0)
 -        goto fail;
 +    AVFormatContext *oc = seg->avf;
 +    size_t size;
  
 -    avio_printf(seg->pb, "#EXTM3U\n");
 -    avio_printf(seg->pb, "#EXT-X-VERSION:3\n");
 -    avio_printf(seg->pb, "#EXT-X-TARGETDURATION:%d\n", (int)seg->time);
 -    avio_printf(seg->pb, "#EXT-X-MEDIA-SEQUENCE:%d\n",
 -                FFMAX(0, seg->number - seg->size));
 -
 -    for (i = FFMAX(0, seg->number - seg->size);
 -         i < seg->number; i++) {
 -        avio_printf(seg->pb, "#EXTINF:%d,\n", (int)seg->time);
 -        if (seg->entry_prefix != NULL) {
 -            avio_printf(seg->pb, "%s", seg->entry_prefix);
 -        }
 -        av_get_frame_filename(buf, sizeof(buf), s->filename, i);
 -        avio_printf(seg->pb, "%s\n", buf);
 +    if (seg->segment_idx_wrap)
 +        seg->segment_idx %= seg->segment_idx_wrap;
 +    if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
 +                              s->filename, seg->segment_idx) < 0) {
 +        av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename);
 +        return AVERROR(EINVAL);
      }
  
 -    if (last)
 -        avio_printf(seg->pb, "#EXT-X-ENDLIST\n");
 -fail:
 -    avio_closep(&seg->pb);
 -    return ret;
 +    /* copy modified name in list entry */
 +    size = strlen(av_basename(oc->filename)) + 1;
-     if (seg->list_entry_prefix)
-         size += strlen(seg->list_entry_prefix);
++    if (seg->entry_prefix)
++        size += strlen(seg->entry_prefix);
 +
 +    seg->cur_entry.filename = av_mallocz(size);
 +    if (!seg->cur_entry.filename)
 +        return AVERROR(ENOMEM);
 +    snprintf(seg->cur_entry.filename, size, "%s%s",
-              seg->list_entry_prefix ? seg->list_entry_prefix : "",
++             seg->entry_prefix ? seg->entry_prefix : "",
 +             av_basename(oc->filename));
 +
 +    return 0;
  }
  
  static int segment_start(AVFormatContext *s, int write_header)
@@@ -774,37 -359,17 +774,37 @@@ fail
  #define OFFSET(x) offsetof(SegmentContext, x)
  #define E AV_OPT_FLAG_ENCODING_PARAM
  static const AVOption options[] = {
 -    { "segment_format",    "container format used for the segments",  OFFSET(format),  AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 -    { "segment_time",      "segment length in seconds",               OFFSET(time),    AV_OPT_TYPE_FLOAT,  {.dbl = 2},     0, FLT_MAX, E },
 -    { "segment_list",      "output the segment list",                 OFFSET(list),    AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 -    { "segment_list_size", "maximum number of playlist entries",      OFFSET(size),    AV_OPT_TYPE_INT,    {.i64 = 5},     0, INT_MAX, E },
 -    { "segment_list_type", "segment list format",                     OFFSET(list_type),    AV_OPT_TYPE_INT,    {.i64 = LIST_FLAT},     0, 2, E, "list_type" },
 -    {   "flat",            "plain list (default)",                    0,               AV_OPT_TYPE_CONST,  {.i64 = LIST_FLAT}, 0, 0, E, "list_type" },
 -    {   "hls",             "Apple HTTP Live Streaming compatible",    0,               AV_OPT_TYPE_CONST,  {.i64 = LIST_HLS},  0, 0, E, "list_type" },
 -    { "segment_wrap",      "number after which the index wraps",      OFFSET(wrap),    AV_OPT_TYPE_INT,    {.i64 = 0},     0, INT_MAX, E },
 -    { "segment_list_entry_prefix",  "base url prefix for segments",   OFFSET(entry_prefix), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 +    { "reference_stream",  "set reference stream", OFFSET(reference_stream_specifier), AV_OPT_TYPE_STRING, {.str = "auto"}, CHAR_MIN, CHAR_MAX, E },
 +    { "segment_format",    "set container format used for the segments", OFFSET(format),  AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 +    { "segment_list",      "set the segment list filename",              OFFSET(list),    AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 +
 +    { "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"},
 +    { "cache",             "allow list caching",                                    0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX,   E, "list_flags"},
 +    { "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"},
 +
 +    { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT,  {.i64 = 0},     0, INT_MAX, E },
-     { "segment_list_entry_prefix", "set prefix to prepend to each list entry filename", OFFSET(list_entry_prefix), AV_OPT_TYPE_STRING,  {.str = NULL}, 0, 0, E },
 +
 +    { "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" },
 +    { "flat", "flat format",     0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FLAT }, INT_MIN, INT_MAX, E, "list_type" },
 +    { "csv",  "csv format",      0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_CSV  }, INT_MIN, INT_MAX, E, "list_type" },
 +    { "ext",  "extended format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_EXT  }, INT_MIN, INT_MAX, E, "list_type" },
 +    { "ffconcat", "ffconcat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FFCONCAT }, INT_MIN, INT_MAX, E, "list_type" },
 +    { "m3u8", "M3U8 format",     0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" },
 +    { "hls", "Apple HTTP Live Streaming compatible", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" },
 +
 +    { "segment_time",      "set segment duration",                       OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 +    { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, 0, E },
 +    { "segment_times",     "set segment split time points",              OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL},  0, 0,       E },
 +    { "segment_frames",    "set segment split frame numbers",            OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL},  0, 0,       E },
 +    { "segment_wrap",      "set number after which the index wraps",     OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
++    { "segment_list_entry_prefix", "set base url prefix for segments", OFFSET(entry_prefix), AV_OPT_TYPE_STRING,  {.str = NULL}, 0, 0, E },
 +    { "segment_start_number", "set the sequence number of the first segment", OFFSET(segment_idx), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
 +    { "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 },
 +
      { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E },
      { "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 },
 +    { "reset_timestamps", "reset timestamps at the begin of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
 +    { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E },
      { NULL },
  };