avformat/hlsenc: added segment file deletion
authorChristian Suloway <csuloway@row44.com>
Mon, 8 Dec 2014 16:25:05 +0000 (10:25 -0600)
committerMichael Niedermayer <michaelni@gmx.at>
Wed, 10 Dec 2014 22:49:25 +0000 (23:49 +0100)
This option flag deletes segment files removed from the playlist after a
period of time equal to the duration of the segment plus the duration of
the playlist.

Signed-off-by: Christian Suloway <csuloway@globaleagleent.com>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
doc/muxers.texi
libavformat/hlsenc.c

index 34e827c..a1264d2 100644 (file)
@@ -273,6 +273,10 @@ ffmpeg -i in.nut -hls_flags single_file out.m3u8
 @end example
 Will produce the playlist, @file{out.m3u8}, and a single segment file,
 @file{out.ts}.
+
+@item hls_flags delete_segments
+Segment files removed from the playlist are deleted after a period of time
+equal to the duration of the segment plus the duration of the playlist.
 @end table
 
 @anchor{ico}
index d5ea990..79f3a23 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "config.h"
 #include <float.h>
 #include <stdint.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
 
 #include "libavutil/avassert.h"
 #include "libavutil/mathematics.h"
@@ -31,6 +35,7 @@
 
 #include "avformat.h"
 #include "internal.h"
+#include "os_support.h"
 
 typedef struct HLSSegment {
     char filename[1024];
@@ -44,6 +49,7 @@ typedef struct HLSSegment {
 typedef enum HLSFlags {
     // Generate a single media file and use byte ranges in the playlist.
     HLS_SINGLE_FILE = (1 << 0),
+    HLS_DELETE_SEGMENTS = (1 << 1),
 } HLSFlags;
 
 typedef struct HLSContext {
@@ -73,6 +79,7 @@ typedef struct HLSContext {
 
     HLSSegment *segments;
     HLSSegment *last_segment;
+    HLSSegment *old_segments;
 
     char *basename;
     char *baseurl;
@@ -82,6 +89,71 @@ typedef struct HLSContext {
     AVIOContext *pb;
 } HLSContext;
 
+static int hls_delete_old_segments(HLSContext *hls) {
+
+    HLSSegment *segment, *previous_segment = NULL;
+    float playlist_duration = 0.0f;
+    int ret = 0, path_size;
+    char *dirname = NULL, *p, *path;
+
+    segment = hls->segments;
+    while (segment) {
+        playlist_duration += segment->duration;
+        segment = segment->next;
+    }
+
+    segment = hls->old_segments;
+    while (segment) {
+        playlist_duration -= segment->duration;
+        previous_segment = segment;
+        segment = previous_segment->next;
+        if (playlist_duration <= -previous_segment->duration) {
+            previous_segment->next = NULL;
+            break;
+        }
+    }
+
+    if (segment) {
+        if (hls->segment_filename) {
+            dirname = av_strdup(hls->segment_filename);
+        } else {
+            dirname = av_strdup(hls->avf->filename);
+        }
+        if (!dirname) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        p = (char *)av_basename(dirname);
+        *p = '\0';
+    }
+
+    while (segment) {
+        av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
+                                  segment->filename);
+        path_size = strlen(dirname) + strlen(segment->filename) + 1;
+        path = av_malloc(path_size);
+        if (!path) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        av_strlcpy(path, dirname, path_size);
+        av_strlcat(path, segment->filename, path_size);
+        if (unlink(path) < 0) {
+            av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
+                                     path, strerror(errno));
+        }
+        av_free(path);
+        previous_segment = segment;
+        segment = previous_segment->next;
+        av_free(previous_segment);
+    }
+
+fail:
+    av_free(dirname);
+
+    return ret;
+}
+
 static int hls_mux_init(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
@@ -116,6 +188,7 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
                               int64_t size)
 {
     HLSSegment *en = av_malloc(sizeof(*en));
+    int ret;
 
     if (!en)
         return AVERROR(ENOMEM);
@@ -137,7 +210,14 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
     if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) {
         en = hls->segments;
         hls->segments = en->next;
-        av_free(en);
+        if (en && hls->flags & HLS_DELETE_SEGMENTS &&
+                !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) {
+            en->next = hls->old_segments;
+            hls->old_segments = en;
+            if ((ret = hls_delete_old_segments(hls)) < 0)
+                return ret;
+        } else
+            av_free(en);
     } else
         hls->nb_entries++;
 
@@ -146,9 +226,9 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
     return 0;
 }
 
-static void hls_free_segments(HLSContext *hls)
+static void hls_free_segments(HLSSegment *p)
 {
-    HLSSegment *p = hls->segments, *en;
+    HLSSegment *en;
 
     while(p) {
         en = p;
@@ -402,7 +482,8 @@ static int hls_write_trailer(struct AVFormatContext *s)
     hls->avf = NULL;
     hls_window(s, 1);
 
-    hls_free_segments(hls);
+    hls_free_segments(hls->segments);
+    hls_free_segments(hls->old_segments);
     avio_close(hls->pb);
     return 0;
 }
@@ -420,6 +501,7 @@ static const AVOption options[] = {
     {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename),   AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
     {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
     {"single_file",   "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX,   E, "flags"},
+    {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX,   E, "flags"},
 
     { NULL },
 };