[FFmpeg-devel] [PATCH] hlsenc: single_file, support HLS ver 4 byteranges

Raento Mika mika.raento at elisa.fi
Mon Sep 15 08:34:44 CEST 2014


On 15/09/14 01:50, "Michael Niedermayer" <michaelni at gmx.at> wrote:

>On Sun, Sep 14, 2014 at 03:37:00PM +0300, Mika Raento wrote:
>> This adds a new option -hls_flags single_file that creates one .ts file
>> for HLS and adds byteranges to the .m3u8 file, instead of creating one
>> .ts file for each segment.
>> 
>> This is helpful at least for storing large number of videos, as the
>> number of files per video is drastically reduced and copying and storing
>> those files takes less requests and inodes.
>> 
>> This is based on work by Nicolas Martyanoff, discussed on ffmpeg-devel
>> in July 2014. That patch seems abandoned by the author, and contained
>> unrelated changes. This patch tries to add the minimum amount of code to
>> support the byterange playlists.
>> ---
>>  doc/muxers.texi      | 23 +++++++++++++++----
>>  libavformat/hlsenc.c | 65
>>++++++++++++++++++++++++++++++++++++++++++----------
>>  2 files changed, 72 insertions(+), 16 deletions(-)
>> 
>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>> index 57e81f4..40ae857 100644
>> --- a/doc/muxers.texi
>> +++ b/doc/muxers.texi
>> @@ -194,15 +194,19 @@ can not be smaller than one centi second.
>>  Apple HTTP Live Streaming muxer that segments MPEG-TS according to
>>  the HTTP Live Streaming (HLS) specification.
>>  
>> -It creates a playlist file and numbered segment files. The output
>> -filename specifies the playlist filename; the segment filenames
>> -receive the same basename as the playlist, a sequential number and
>> -a .ts extension.
>> +It creates a playlist file, and one or more segment files. The output
>>filename
>> +specifies the playlist filename.
>> +
>> +By default, the muxer creates a file for each segment produced. These
>>files
>> +have the same name as the playlist, followed by a sequential number
>>and a
>> +.ts extension.
>>  
>>  For example, to convert an input file with @command{ffmpeg}:
>>  @example
>>  ffmpeg -i in.nut out.m3u8
>>  @end example
>> +This example will produce the playlist, @file{out.m3u8}, and segment
>>files:
>> + at file{out0.ts}, @file{out1.ts}, @file{out2.ts}, etc.
>>  
>>  See also the @ref{segment} muxer, which provides a more generic and
>>  flexible implementation of a segmenter, and can be used to perform HLS
>> @@ -241,6 +245,17 @@ Note that the playlist sequence number must be
>>unique for each segment
>>  and it is not to be confused with the segment filename sequence number
>>  which can be cyclic, for example if the @option{wrap} option is
>>  specified.
>> +
>> + at item hls_flags single_file
>> +If this flag is set, the muxer will store all segments in a single
>>MPEG-TS
>> +file, and will use byte ranges in the playlist. HLS playlists
>>generated with
>> +this way will have the version number 4.
>> +For example:
>> + at example
>> +ffmpeg -i in.nut -hls_flags single_file out.m3u8
>> + at end example
>> +Will produce the playlist, @file{out.m3u8}, and a single segment file,
>> + at file{out.ts}.
>>  @end table
>>  
>>  @anchor{ico}
>> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
>> index 11f1e5b..9fcb999 100644
>> --- a/libavformat/hlsenc.c
>> +++ b/libavformat/hlsenc.c
>> @@ -34,10 +34,17 @@
>>  typedef struct HLSSegment {
>>      char filename[1024];
>>      double duration; /* in seconds */
>> +    int64_t pos;
>> +    int64_t size;
>>  
>>      struct HLSSegment *next;
>>  } HLSSegment;
>>  
>> +typedef enum HLSFlags {
>> +    // Generate a single media file and use byte ranges in the
>>playlist.
>> +    HLS_SINGLE_FILE = (1 << 0),
>> +} HLSFlags;
>> +
>>  typedef struct HLSContext {
>>      const AVClass *class;  // Class for private options.
>>      unsigned number;
>> @@ -50,12 +57,15 @@ typedef struct HLSContext {
>>      float time;            // Set by a private option.
>>      int max_nb_segments;   // Set by a private option.
>>      int  wrap;             // Set by a private option.
>> +    uint32_t flags;        // enum HLSFlags
>>  
>>      int64_t recording_time;
>>      int has_video;
>>      int64_t start_pts;
>>      int64_t end_pts;
>>      double duration;      // last segment duration computed so far, in
>>seconds
>> +    int64_t start_pos;    // last segment starting position
>> +    int64_t size;         // last segment size
>>      int nb_entries;
>>  
>>      HLSSegment *segments;
>> @@ -88,12 +98,14 @@ static int hls_mux_init(AVFormatContext *s)
>>          avcodec_copy_context(st->codec, s->streams[i]->codec);
>>          st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
>>      }
>> +    hls->start_pos = 0;
>>  
>>      return 0;
>>  }
>>  
>>  /* Create a new segment and append it to the segment list */
>> -static int hls_append_segment(HLSContext *hls, double duration)
>> +static int hls_append_segment(HLSContext *hls, double duration,
>>int64_t pos,
>> +                              int64_t size)
>>  {
>>      HLSSegment *en = av_malloc(sizeof(*en));
>>  
>> @@ -103,6 +115,8 @@ static int hls_append_segment(HLSContext *hls,
>>double duration)
>>      av_strlcpy(en->filename, av_basename(hls->avf->filename),
>>sizeof(en->filename));
>>  
>>      en->duration = duration;
>> +    en->pos      = pos;
>> +    en->size     = size;
>>      en->next     = NULL;
>>  
>>      if (!hls->segments)
>> @@ -142,6 +156,7 @@ static int hls_window(AVFormatContext *s, int last)
>>      int target_duration = 0;
>>      int ret = 0;
>>      int64_t sequence = FFMAX(hls->start_sequence, hls->sequence -
>>hls->nb_entries);
>> +    int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
>>  
>>      if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
>>                            &s->interrupt_callback, NULL)) < 0)
>> @@ -153,7 +168,7 @@ static int hls_window(AVFormatContext *s, int last)
>>      }
>>  
>>      avio_printf(hls->pb, "#EXTM3U\n");
>> -    avio_printf(hls->pb, "#EXT-X-VERSION:3\n");
>> +    avio_printf(hls->pb, "#EXT-X-VERSION:%d\n", version);
>>      avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n",
>>target_duration);
>>      avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n",
>>sequence);
>>  
>> @@ -162,6 +177,9 @@ static int hls_window(AVFormatContext *s, int last)
>>  
>>      for (en = hls->segments; en; en = en->next) {
>>          avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration);
>> +        if (hls->flags & HLS_SINGLE_FILE)
>> +             avio_printf(hls->pb,
>>"#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
>> +                         en->size, en->pos);
>>          if (hls->baseurl)
>>              avio_printf(hls->pb, "%s", hls->baseurl);
>>          avio_printf(hls->pb, "%s\n", en->filename);
>> @@ -181,11 +199,15 @@ static int hls_start(AVFormatContext *s)
>>      AVFormatContext *oc = c->avf;
>>      int err = 0;
>>  
>> -    if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
>> -                              c->basename, c->wrap ? c->sequence %
>>c->wrap : c->sequence) < 0) {
>> -        av_log(oc, AV_LOG_ERROR, "Invalid segment filename template
>>'%s'\n", c->basename);
>> -        return AVERROR(EINVAL);
>> -    }
>> +    if (c->flags & HLS_SINGLE_FILE)
>> +        av_strlcpy(oc->filename, c->basename,
>> +                   sizeof(oc->filename));
>> +    else
>> +        if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
>> +                                  c->basename, c->wrap ? c->sequence %
>>c->wrap : c->sequence) < 0) {
>> +            av_log(oc, AV_LOG_ERROR, "Invalid segment filename
>>template '%s'\n", c->basename);
>> +            return AVERROR(EINVAL);
>> +        }
>>      c->number++;
>>  
>>      if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
>> @@ -210,6 +232,9 @@ static int hls_write_header(AVFormatContext *s)
>>      hls->recording_time = hls->time * AV_TIME_BASE;
>>      hls->start_pts      = AV_NOPTS_VALUE;
>>  
>> +    if (hls->flags & HLS_SINGLE_FILE)
>> +        pattern = ".ts";
>> +
>>      for (i = 0; i < s->nb_streams; i++)
>>          hls->has_video +=
>>              s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO;
>
>> @@ -289,17 +314,27 @@ static int hls_write_packet(AVFormatContext *s,
>>AVPacket *pkt)
>>  
>>      if (can_split && av_compare_ts(pkt->pts - hls->start_pts,
>>st->time_base,
>>                                     end_pts, AV_TIME_BASE_Q) >= 0) {
>> -        ret = hls_append_segment(hls, hls->duration);
>> +        av_write_frame(oc, NULL); /* Flush any buffered data */
>> +
>> +        hls->size = hls->avf->pb->pos - hls->start_pos;
>
>is it intended to access pos directly instead of using avio_tell() ?
>
>
>> +        int64_t start_pos = hls->avf->pb->pos;
>
>this mixes declarations and statements, some compilers dislike that
>
>
>> +        ret = hls_append_segment(hls, hls->duration, hls->start_pos,
>>hls->size);
>> +        hls->start_pos = start_pos;
>>          if (ret)
>>              return ret;
>>  
>>          hls->end_pts = pkt->pts;
>>          hls->duration = 0;
>>  
>> -        av_write_frame(oc, NULL); /* Flush any buffered data */
>> -        avio_close(oc->pb);
>> +        if (hls->flags & HLS_SINGLE_FILE) {
>> +            if (hls->avf->oformat->priv_class && hls->avf->priv_data)
>> +                av_opt_set(hls->avf->priv_data, "mpegts_flags",
>>"resend_headers", 0);
>> +            hls->number++;
>> +        } else {
>> +            avio_close(oc->pb);
>>  
>> -        ret = hls_start(s);
>> +            ret = hls_start(s);
>> +        }
>>  
>>          if (ret)
>>              return ret;
>> @@ -321,10 +356,13 @@ static int hls_write_trailer(struct
>>AVFormatContext *s)
>>      AVFormatContext *oc = hls->avf;
>>  
>>      av_write_trailer(oc);
>> +    hls->size = hls->avf->pb->pos - hls->start_pos;
>>      avio_closep(&oc->pb);
>>      avformat_free_context(oc);
>>      av_free(hls->basename);
>
>> -    hls_append_segment(hls, hls->duration);
>> +    if (hls->duration > 0.0) {
>> +        hls_append_segment(hls, hls->duration, hls->start_pos,
>>hls->size);
>> +    }
>
>is this change related to the rest of the patch or a unrelated
>bugfix ? if the later please split it in a seperate patch

Unrelated, will re-submit without it. Thanks.

>
>
>[...]
>
>-- 
>Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
>Avoid a single point of failure, be that a person or equipment.
>




More information about the ffmpeg-devel mailing list