[FFmpeg-devel] [PATCH] avformat/hlsenc: added HLS encryption

Christian Suloway csuloway at row44.com
Wed Nov 26 23:37:51 CET 2014


Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>
---
 libavformat/crypto.c | 233 ++++++++++++++++++++++++++++---
 libavformat/hlsenc.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 567 insertions(+), 53 deletions(-)

diff --git a/libavformat/crypto.c b/libavformat/crypto.c
index a9b6e47..dc3dc88 100644
--- a/libavformat/crypto.c
+++ b/libavformat/crypto.c
@@ -38,17 +38,35 @@ typedef struct {
     int indata, indata_used, outdata;
     int eof;
     uint8_t *key;
-    int keylen;
+    int key_len;
     uint8_t *iv;
-    int ivlen;
-    struct AVAES *aes;
+    int iv_len;
+    uint8_t *decrypt_key;
+    int decrypt_key_len;
+    uint8_t *decrypt_iv;
+    int decrypt_iv_len;
+    uint8_t *encrypt_key;
+    int encrypt_key_len;
+    uint8_t *encrypt_iv;
+    int encrypt_iv_len;
+    struct AVAES *aes_decrypt;
+    struct AVAES *aes_encrypt;
+
+    uint8_t pad[BLOCKSIZE];
+    int pad_len;
+
 } CryptoContext;
 
 #define OFFSET(x) offsetof(CryptoContext, x)
 #define D AV_OPT_FLAG_DECODING_PARAM
+#define E AV_OPT_FLAG_ENCODING_PARAM
 static const AVOption options[] = {
-    {"key", "AES decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D },
-    {"iv",  "AES decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D },
+    {"key", "AES encryption/decryption key",                   OFFSET(key),         AV_OPT_TYPE_BINARY, .flags = D|E },
+    {"iv",  "AES encryption/decryption initialization vector", OFFSET(iv),          AV_OPT_TYPE_BINARY, .flags = D|E },
+    {"decryption_key", "AES decryption key",                   OFFSET(decrypt_key), AV_OPT_TYPE_BINARY, .flags = D },
+    {"decryption_iv",  "AES decryption initialization vector", OFFSET(decrypt_iv),  AV_OPT_TYPE_BINARY, .flags = D },
+    {"encryption_key", "AES encryption key",                   OFFSET(encrypt_key), AV_OPT_TYPE_BINARY, .flags = E },
+    {"encryption_iv",  "AES encryption initialization vector", OFFSET(encrypt_iv),  AV_OPT_TYPE_BINARY, .flags = E },
     { NULL }
 };
 
@@ -72,28 +90,145 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
         goto err;
     }
 
-    if (c->keylen < BLOCKSIZE || c->ivlen < BLOCKSIZE) {
-        av_log(h, AV_LOG_ERROR, "Key or IV not set\n");
-        ret = AVERROR(EINVAL);
-        goto err;
+    if (flags & AVIO_FLAG_READ) {
+        if (!c->decrypt_key_len) {
+            if (!c->key_len) {
+                av_log(h, AV_LOG_ERROR, "decryption key not set\n");
+                ret = AVERROR(EINVAL);
+                goto err;
+            } else if (c->key_len != BLOCKSIZE) {
+                av_log(h, AV_LOG_ERROR, "invalid key size "
+                                        "(%d bytes, block size is %d)\n",
+                                        c->key_len, BLOCKSIZE);
+                ret = AVERROR(EINVAL);
+                goto err;
+            }
+            c->decrypt_key = av_malloc(c->key_len);
+            if (!c->decrypt_key) {
+                ret = AVERROR(ENOMEM);
+                goto err;
+            }
+            memcpy(c->decrypt_key, c->key, c->key_len);
+            c->decrypt_key_len = c->key_len;
+        } else if (c->decrypt_key_len != BLOCKSIZE) {
+            av_log(h, AV_LOG_ERROR, "invalid decryption key size "
+                                    "(%d bytes, block size is %d)\n",
+                                    c->decrypt_key_len, BLOCKSIZE);
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+        if (!c->decrypt_iv_len) {
+            if (!c->iv_len) {
+                av_log(h, AV_LOG_ERROR, "decryption IV not set\n");
+                ret = AVERROR(EINVAL);
+                goto err;
+            } else if (c->iv_len != BLOCKSIZE) {
+                av_log(h, AV_LOG_ERROR, "invalid IV size "
+                                        "(%d bytes, block size is %d)\n",
+                                        c->iv_len, BLOCKSIZE);
+                ret = AVERROR(EINVAL);
+                goto err;
+            }
+            c->decrypt_iv = av_malloc(c->iv_len);
+            if (!c->decrypt_iv) {
+                ret = AVERROR(ENOMEM);
+                goto err;
+            }
+            memcpy(c->decrypt_iv, c->iv, c->iv_len);
+            c->decrypt_iv_len = c->iv_len;
+        } else if (c->decrypt_iv_len != BLOCKSIZE) {
+            av_log(h, AV_LOG_ERROR, "invalid decryption IV size "
+                                    "(%d bytes, block size is %d)\n",
+                                    c->decrypt_iv_len, BLOCKSIZE);
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
     }
+
     if (flags & AVIO_FLAG_WRITE) {
-        av_log(h, AV_LOG_ERROR, "Only decryption is supported currently\n");
-        ret = AVERROR(ENOSYS);
-        goto err;
+        if (!c->encrypt_key_len) {
+            if (!c->key_len) {
+                av_log(h, AV_LOG_ERROR, "encryption key not set\n");
+                ret = AVERROR(EINVAL);
+                goto err;
+            } else if (c->key_len != BLOCKSIZE) {
+                av_log(h, AV_LOG_ERROR, "invalid key size "
+                                        "(%d bytes, block size is %d)\n",
+                                        c->key_len, BLOCKSIZE);
+                ret = AVERROR(EINVAL);
+                goto err;
+            }
+            c->encrypt_key = av_malloc(c->key_len);
+            if (!c->encrypt_key) {
+                ret = AVERROR(ENOMEM);
+                goto err;
+            }
+            memcpy(c->encrypt_key, c->key, c->key_len);
+            c->encrypt_key_len = c->key_len;
+        } else if (c->encrypt_key_len != BLOCKSIZE) {
+            av_log(h, AV_LOG_ERROR, "invalid encryption key size "
+                                    "(%d bytes, block size is %d)\n",
+                                    c->encrypt_key_len, BLOCKSIZE);
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+        if (!c->encrypt_iv_len) {
+            if (!c->iv_len) {
+                av_log(h, AV_LOG_ERROR, "encryption IV not set\n");
+                ret = AVERROR(EINVAL);
+                goto err;
+            } else if (c->iv_len != BLOCKSIZE) {
+                av_log(h, AV_LOG_ERROR, "invalid IV size "
+                                        "(%d bytes, block size is %d)\n",
+                                        c->iv_len, BLOCKSIZE);
+                ret = AVERROR(EINVAL);
+                goto err;
+            }
+            c->encrypt_iv = av_malloc(c->iv_len);
+            if (!c->encrypt_iv) {
+                ret = AVERROR(ENOMEM);
+                goto err;
+            }
+            memcpy(c->encrypt_iv, c->iv, c->iv_len);
+            c->encrypt_iv_len = c->iv_len;
+        } else if (c->encrypt_iv_len != BLOCKSIZE) {
+            av_log(h, AV_LOG_ERROR, "invalid encryption IV size "
+                                    "(%d bytes, block size is %d)\n",
+                                    c->encrypt_iv_len, BLOCKSIZE);
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
     }
-    if ((ret = ffurl_open(&c->hd, nested_url, AVIO_FLAG_READ,
+
+    if ((ret = ffurl_open(&c->hd, nested_url, flags,
                           &h->interrupt_callback, options)) < 0) {
-        av_log(h, AV_LOG_ERROR, "Unable to open input\n");
+        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url);
         goto err;
     }
-    c->aes = av_aes_alloc();
-    if (!c->aes) {
-        ret = AVERROR(ENOMEM);
-        goto err;
+
+    if (flags & AVIO_FLAG_READ) {
+        c->aes_decrypt = av_aes_alloc();
+        if (!c->aes_decrypt) {
+            ret = AVERROR(ENOMEM);
+            goto err;
+        }
+        ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE*8, 1);
+        if (ret < 0)
+            goto err;
+    }
+
+    if (flags & AVIO_FLAG_WRITE) {
+        c->aes_encrypt = av_aes_alloc();
+        if (!c->aes_encrypt) {
+            ret = AVERROR(ENOMEM);
+            goto err;
+        }
+        ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE*8, 0);
+        if (ret < 0)
+            goto err;
     }
 
-    av_aes_init(c->aes, c->key, 128, 1);
+    c->pad_len = 0;
 
     h->is_streamed = 1;
 
@@ -131,8 +266,8 @@ retry:
         return AVERROR_EOF;
     if (!c->eof)
         blocks--;
-    av_aes_crypt(c->aes, c->outbuffer, c->inbuffer + c->indata_used, blocks,
-                 c->iv, 1);
+    av_aes_crypt(c->aes_decrypt, c->outbuffer, c->inbuffer + c->indata_used,
+                 blocks, c->decrypt_iv, 1);
     c->outdata      = BLOCKSIZE * blocks;
     c->outptr       = c->outbuffer;
     c->indata_used += BLOCKSIZE * blocks;
@@ -150,12 +285,65 @@ retry:
     goto retry;
 }
 
+static int crypto_write(URLContext *h, const unsigned char *buf, int size)
+{
+    CryptoContext *c = h->priv_data;
+    int total_size, blocks, pad_len, out_size;
+    uint8_t *out_buf;
+    int ret = 0;
+
+    total_size = size + c->pad_len;
+    pad_len = total_size % BLOCKSIZE;
+    out_size = total_size - pad_len;
+    blocks = out_size / BLOCKSIZE;
+
+    if (out_size) {
+        out_buf = av_malloc(out_size);
+        if (!out_buf)
+            return AVERROR(ENOMEM);
+
+        if (c->pad_len) {
+            memcpy(&c->pad[c->pad_len], buf, BLOCKSIZE - c->pad_len);
+            av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);
+            blocks--;
+        }
+
+        av_aes_crypt(c->aes_encrypt, &out_buf[c->pad_len ? BLOCKSIZE : 0],
+                             &buf[c->pad_len ? BLOCKSIZE - c->pad_len: 0],
+                             blocks, c->encrypt_iv, 0);
+
+        ret = ffurl_write(c->hd, out_buf, out_size);
+        av_free(out_buf);
+        if (ret < 0)
+            return ret;
+
+        memcpy(c->pad, &buf[size - pad_len], pad_len);
+    } else
+        memcpy(&c->pad[c->pad_len], buf, size);
+
+    c->pad_len = pad_len;
+
+    return size;
+}
+
 static int crypto_close(URLContext *h)
 {
     CryptoContext *c = h->priv_data;
+    uint8_t out_buf[BLOCKSIZE];
+    int ret, pad;
+
+    if (c->aes_encrypt) {
+        pad = BLOCKSIZE - c->pad_len;
+        memset(&c->pad[c->pad_len], pad, pad);
+        av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);
+        if ((ret =  ffurl_write(c->hd, out_buf, BLOCKSIZE)) < 0)
+            return ret;
+    }
+
     if (c->hd)
         ffurl_close(c->hd);
-    av_freep(&c->aes);
+    av_freep(&c->aes_decrypt);
+    av_freep(&c->aes_encrypt);
     return 0;
 }
 
@@ -163,6 +351,7 @@ URLProtocol ff_crypto_protocol = {
     .name            = "crypto",
     .url_open2       = crypto_open2,
     .url_read        = crypto_read,
+    .url_write       = crypto_write,
     .url_close       = crypto_close,
     .priv_data_size  = sizeof(CryptoContext),
     .priv_data_class = &crypto_class,
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index e13f438..516eac6 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -19,8 +19,13 @@
  * 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 <stdio.h>
 
 #include "libavutil/avassert.h"
 #include "libavutil/mathematics.h"
@@ -28,16 +33,21 @@
 #include "libavutil/avstring.h"
 #include "libavutil/opt.h"
 #include "libavutil/log.h"
+#include "libavutil/lfg.h"
+#include "libavutil/random_seed.h"
 
 #include "avformat.h"
 #include "internal.h"
 
 typedef struct HLSSegment {
-    char filename[1024];
+    char *filename;
     double duration; /* in seconds */
     int64_t pos;
     int64_t size;
 
+    char *key_uri;
+    char *iv_string;
+
     struct HLSSegment *next;
 } HLSSegment;
 
@@ -58,6 +68,7 @@ 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.
+    int delete_segments;
     uint32_t flags;        // enum HLSFlags
 
     int allowcache;
@@ -72,15 +83,219 @@ typedef struct HLSContext {
 
     HLSSegment *segments;
     HLSSegment *last_segment;
+    HLSSegment *old_segments;
 
+    char *dirname;
     char *basename;
     char *baseurl;
     char *format_options_str;
     AVDictionary *format_options;
 
+    char *segment_filename;
+
+    char *key_info_file;
+    int random_iv;
+    int encrypt;
+
+    char *key_file;
+    char *key_uri;
+    char *key_string;
+    char *iv_string;
+
+    AVLFG *lfg;
+
     AVIOContext *pb;
 } HLSContext;
 
+static int hex_string(char **string, uint8_t *buf, int size)
+{
+    int i;
+
+    *string = av_mallocz(size*2 + 1);
+    if (!*string)
+        return AVERROR(ENOMEM);
+    for (i = 0; i < size; i++)
+        snprintf(&(*string)[i*2], 3, "%02X", buf[i]);
+
+    return 0;
+}
+
+
+static void hls_free_segment(HLSSegment *en)
+{
+    av_free(en->filename);
+    av_free(en->key_uri);
+    av_free(en->iv_string);
+    av_free(en);
+}
+
+static int hls_delete_old_segments(HLSContext *hls) {
+
+    HLSSegment *segment, *previous_segment = NULL;
+    float playlist_duration = 0.0f;
+    int path_size;
+    char *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 = segment->next;
+        if (playlist_duration <= 0.0f) {
+            previous_segment->next = NULL;
+            break;
+        }
+    }
+
+    while (segment) {
+        av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
+                                  segment->filename);
+        path_size = strlen(hls->dirname) + strlen(segment->filename) + 1;
+        path = av_malloc(path_size);
+        if (!path)
+            return AVERROR(ENOMEM);
+        av_strlcpy(path, hls->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 = segment->next;
+        hls_free_segment(previous_segment);
+    }
+
+    return 0;
+}
+
+static int hls_encryption_init(AVFormatContext *s)
+{
+    HLSContext *hls = s->priv_data;
+
+    hls->key_file = NULL;
+    hls->key_uri = NULL;
+    hls->key_string = NULL;
+    hls->iv_string = NULL;
+
+    if (hls->key_info_file) {
+        hls->encrypt = 1;
+    } else {
+        hls->encrypt = 0;
+        return 0;
+    }
+
+    if (hls->random_iv) {
+        hls->lfg = av_malloc(sizeof(AVLFG));
+        av_lfg_init(hls->lfg, av_get_random_seed());
+    } else
+        hls->lfg = NULL;
+
+    return 0;
+}
+
+static int hls_encryption_start(HLSContext *hls) {
+
+    uint8_t iv[16];
+    int ret, i, j, rotate_iv = 0;
+    unsigned int u;
+    AVIOContext *pb = NULL;
+    AVIOContext *dyn_buf = NULL;
+    uint8_t buf[1024], *tmp;
+    char *p, *tstr, *saveptr = NULL;
+    char key[16], *key_string;
+
+    if ((ret = avio_open(&pb, hls->key_info_file, AVIO_FLAG_READ)) < 0) {
+        av_log(hls, AV_LOG_ERROR, "error opening key info file %s\n",
+                                  hls->key_info_file);
+        return ret;
+    }
+    ret = avio_open_dyn_buf(&dyn_buf);
+    if (ret < 0) {
+        avio_closep(&pb);
+        return ret;
+    }
+
+    while ((ret = avio_read(pb, buf, sizeof(buf))) > 0)
+        avio_write(dyn_buf, buf, ret);
+    avio_w8(dyn_buf, 0);
+    avio_closep(&pb);
+
+    if ((ret = avio_close_dyn_buf(dyn_buf, &tmp)) < 0)
+        return ret;
+
+    p = tmp;
+    if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
+        av_log(hls, AV_LOG_ERROR, "no key URL specified in key info file %s\n",
+                                  hls->key_info_file);
+        return AVERROR(EINVAL);
+    }
+    p = NULL;
+    av_free(hls->key_uri);
+    hls->key_uri = av_strdup(tstr);
+    if (!hls->key_uri)
+        return AVERROR(ENOMEM);
+    if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
+        av_log(hls, AV_LOG_ERROR, "no key file specified in key info file %s\n",
+                                  hls->key_info_file);
+        return AVERROR(EINVAL);
+    }
+    av_free(hls->key_file);
+    hls->key_file = av_strdup(tstr);
+    if (!hls->key_file)
+        return AVERROR(ENOMEM);
+    av_free(tmp);
+
+    if ((ret = avio_open(&pb, hls->key_file, AVIO_FLAG_READ)) < 0) {
+        av_log(hls, AV_LOG_ERROR, "error opening key file %s\n",
+                                  hls->key_file);
+        return ret;
+    }
+
+    ret = avio_read(pb, key, sizeof(key));
+    avio_closep(&pb);
+    if (ret < sizeof(key)) {
+        av_log(hls, AV_LOG_ERROR, "error reading key file %s\n",
+                                  hls->key_file);
+        return ret;
+    }
+
+    if ((ret = hex_string(&key_string, key, 16)) < 0)
+        return ret;
+    if (!hls->key_string || strncmp(key_string, hls->key_string, 32)) {
+        av_free(hls->key_string);
+        hls->key_string = key_string;
+        rotate_iv = 1;
+    } else {
+        av_free(key_string);
+    }
+
+    if (!hls->random_iv) {
+        for (i = 0; i < 8; i++)
+            iv[15 - i] = (hls->sequence >> i*8) & 0xff;
+        memset(iv, 0, 8);
+        if ((ret = hex_string(&hls->iv_string, iv, 16)) < 0)
+            return ret;
+    } else if (!hls->iv_string || rotate_iv) {
+        for (i = 0; i < 4; i++) {
+            u = av_lfg_get(hls->lfg);
+            for (j = 0; j < 4; j++)
+                iv[i*4 + j] = (u >> j*8) & 0xff;
+        }
+        av_free(hls->iv_string);
+        if ((ret = hex_string(&hls->iv_string, iv, 16)) < 0)
+            return ret;
+    }
+
+    return 0;
+}
+
 static int hls_mux_init(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
@@ -115,17 +330,32 @@ 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);
 
-    av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename));
+    en->filename = av_strdup(hls->avf->filename);
+    if (!en->filename)
+        return AVERROR(ENOMEM);
 
     en->duration = duration;
     en->pos      = pos;
     en->size     = size;
     en->next     = NULL;
 
+    if (hls->encrypt) {
+        en->key_uri = av_strdup(hls->key_uri);
+        if (!en->key_uri)
+            return AVERROR(ENOMEM);
+        en->iv_string = av_strdup(hls->iv_string);
+        if (!en->iv_string)
+            return AVERROR(ENOMEM);
+    } else {
+        en->key_uri = NULL;
+        en->iv_string = NULL;
+    }
+
     if (!hls->segments)
         hls->segments = en;
     else
@@ -136,7 +366,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->delete_segments && !hls->wrap) {
+            en->next = hls->old_segments;
+            hls->old_segments = en;
+            if ((ret = hls_delete_old_segments(hls)) < 0)
+                return ret;
+        } else
+            hls_free_segment(en);
     } else
         hls->nb_entries++;
 
@@ -145,17 +382,25 @@ 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;
         p = p->next;
-        av_free(en);
+        hls_free_segment(en);
     }
 }
 
+static void print_encryption_tag(HLSContext *hls, HLSSegment *en)
+{
+    avio_printf(hls->pb, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
+    if (hls->random_iv)
+        avio_printf(hls->pb, ",IV=0x%s", en->iv_string);
+    avio_printf(hls->pb, "\n");
+}
+
 static int hls_window(AVFormatContext *s, int last)
 {
     HLSContext *hls = s->priv_data;
@@ -164,6 +409,8 @@ static int hls_window(AVFormatContext *s, int last)
     int ret = 0;
     int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
     int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
+    char *key_uri = NULL;
+    char *iv_string = NULL;
 
     if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
                           &s->interrupt_callback, NULL)) < 0)
@@ -186,6 +433,16 @@ static int hls_window(AVFormatContext *s, int last)
            sequence);
 
     for (en = hls->segments; en; en = en->next) {
+        if (hls->encrypt) {
+            if (!key_uri || av_strcasecmp(en->key_uri, key_uri) ||
+                hls->random_iv &&
+                (!iv_string || av_strcasecmp(en->iv_string, iv_string))) {
+                print_encryption_tag(hls, en);
+                key_uri = en->key_uri;
+                iv_string = en->iv_string;
+            }
+        }
+
         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",
@@ -208,6 +465,10 @@ static int hls_start(AVFormatContext *s)
     HLSContext *c = s->priv_data;
     AVFormatContext *oc = c->avf;
     int err = 0;
+    AVDictionary *options = NULL;
+    const char *prefix = "crypto:";
+    int filename_size;
+    char *filename;
 
     if (c->flags & HLS_SINGLE_FILE)
         av_strlcpy(oc->filename, c->basename,
@@ -220,8 +481,33 @@ static int hls_start(AVFormatContext *s)
         }
     c->number++;
 
-    if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
-                          &s->interrupt_callback, NULL)) < 0)
+    if (c->encrypt) {
+        if ((err = hls_encryption_start(c)) < 0)
+            return err;
+        av_dict_set(&options, "encryption_key", c->key_string, 0);
+        av_dict_set(&options, "encryption_iv", c->iv_string, 0);
+
+        filename_size = strlen(prefix) + strlen(c->dirname) \
+                                       + strlen(oc->filename) + 1;
+        filename = av_malloc(filename_size);
+        if (!filename)
+            return AVERROR(ENOMEM);
+        av_strlcpy(filename, prefix, filename_size);
+    } else {
+        filename_size = strlen(c->dirname) + strlen(oc->filename) + 1;
+        filename = av_malloc(filename_size);
+        if (!filename)
+            return AVERROR(ENOMEM);
+        *filename = '\0';
+    }
+    av_strlcat(filename, c->dirname, filename_size);
+    av_strlcat(filename, oc->filename, filename_size);
+
+    err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,
+                     &s->interrupt_callback, &options);
+    av_free(filename);
+    av_dict_free(&options);
+    if (err < 0)
         return err;
 
     if (oc->oformat->priv_class && oc->priv_data)
@@ -237,15 +523,13 @@ static int hls_write_header(AVFormatContext *s)
     char *p;
     const char *pattern = "%d.ts";
     AVDictionary *options = NULL;
-    int basename_size = strlen(s->filename) + strlen(pattern) + 1;
+    int basename_size;
+    char *basename;
 
     hls->sequence       = hls->start_sequence;
     hls->recording_time = hls->time * AV_TIME_BASE;
     hls->start_pts      = AV_NOPTS_VALUE;
 
-    if (hls->flags & HLS_SINGLE_FILE)
-        pattern = ".ts";
-
     if (hls->format_options_str) {
         ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0);
         if (ret < 0) {
@@ -270,21 +554,48 @@ static int hls_write_header(AVFormatContext *s)
         goto fail;
     }
 
-    hls->basename = av_malloc(basename_size);
-
-    if (!hls->basename) {
+    hls->dirname = av_strdup(s->filename);
+    if (!hls->dirname) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    strcpy(hls->basename, s->filename);
+    basename = (char *)av_basename(hls->dirname);
 
-    p = strrchr(hls->basename, '.');
+    if (hls->segment_filename) {
+        hls->basename = av_strdup(av_basename(hls->segment_filename));
+        if (!hls->basename) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        if (strlen(hls->basename) != strlen(hls->segment_filename)) {
+            av_log(hls, AV_LOG_ERROR, "invalid segment filename %s\n",
+                                      hls->segment_filename);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+    } else {
+        if (hls->flags & HLS_SINGLE_FILE)
+            pattern = ".ts";
 
-    if (p)
-        *p = '\0';
+        basename_size = strlen(basename) + strlen(pattern) + 1;
+        hls->basename = av_malloc(basename_size);
+        if (!hls->basename) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        av_strlcpy(hls->basename, basename, basename_size);
+        p = strrchr(hls->basename, '.');
+        if (p)
+            *p = '\0';
+        av_strlcat(hls->basename, pattern, basename_size);
+    }
 
-    av_strlcat(hls->basename, pattern, basename_size);
+    *basename = '\0';
+
+    if ((ret = hls_encryption_init(s) < 0))
+        goto fail;
 
     if ((ret = hls_mux_init(s)) < 0)
         goto fail;
@@ -309,6 +620,7 @@ fail:
 
     av_dict_free(&options);
     if (ret) {
+        av_free(hls->dirname);
         av_free(hls->basename);
         if (hls->avf)
             avformat_free_context(hls->avf);
@@ -395,24 +707,37 @@ static int hls_write_trailer(struct AVFormatContext *s)
     hls->avf = NULL;
     hls_window(s, 1);
 
-    hls_free_segments(hls);
+    av_free(hls->dirname);
+    av_free(hls->key_file);
+    av_free(hls->key_uri);
+    av_free(hls->key_string);
+    av_free(hls->iv_string);
+    av_free(hls->lfg);
+
+    hls_free_segments(hls->segments);
+    hls_free_segments(hls->old_segments);
+
     avio_close(hls->pb);
+
     return 0;
 }
 
 #define OFFSET(x) offsetof(HLSContext, x)
 #define E AV_OPT_FLAG_ENCODING_PARAM
 static const AVOption options[] = {
-    {"start_number",  "set first number in the sequence",        OFFSET(start_sequence),AV_OPT_TYPE_INT64,  {.i64 = 0},     0, INT64_MAX, E},
-    {"hls_time",      "set segment length in seconds",           OFFSET(time),    AV_OPT_TYPE_FLOAT,  {.dbl = 2},     0, FLT_MAX, E},
-    {"hls_list_size", "set maximum number of playlist entries",  OFFSET(max_nb_segments),    AV_OPT_TYPE_INT,    {.i64 = 5},     0, INT_MAX, E},
-    {"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,    E},
-    {"hls_wrap",      "set number after which the index wraps",  OFFSET(wrap),    AV_OPT_TYPE_INT,    {.i64 = 0},     0, INT_MAX, E},
-    {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E},
-    {"hls_base_url",  "url to prepend to each playlist entry",   OFFSET(baseurl), 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"},
-
+    {"start_number",         "set first number in the sequence",                                               OFFSET(start_sequence),     AV_OPT_TYPE_INT64,  {.i64 = 0},               0,       INT64_MAX, E},
+    {"hls_time",             "set segment length in seconds",                                                  OFFSET(time),               AV_OPT_TYPE_FLOAT,  {.dbl = 2},               0,       FLT_MAX,   E},
+    {"hls_list_size",        "set maximum number of playlist entries",                                         OFFSET(max_nb_segments),    AV_OPT_TYPE_INT,    {.i64 = 5},               0,       INT_MAX,   E},
+    {"hls_ts_options",       "set hls mpegts list of options for the container format used for hls",           OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
+    {"hls_wrap",             "set number after which the index wraps",                                         OFFSET(wrap),               AV_OPT_TYPE_INT,    {.i64 = 0},               0,       INT_MAX,   E},
+    {"hls_delete",           "delete segment files that are no longer part of the playlist",                   OFFSET(delete_segments),    AV_OPT_TYPE_INT,    {.i64 = 0},               0,       1,         E},
+    {"hls_allow_cache",      "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache),         AV_OPT_TYPE_INT,    {.i64 = -1},              INT_MIN, INT_MAX,   E},
+    {"hls_base_url",         "url to prepend to each playlist entry",                                          OFFSET(baseurl),            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"},
+    {"hls_segment_filename", "filename template for segment files",                                            OFFSET(segment_filename),   AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
+    {"hls_key_info_file",    "file with key URL and key file path",                                            OFFSET(key_info_file),      AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
+    {"hls_random_iv",        "randomize initialization vector",                                                OFFSET(random_iv),          AV_OPT_TYPE_INT,    {.i64 = 0},               0,       1,         E},
     { NULL },
 };
 
-- 
1.9.3 (Apple Git-50)



More information about the ffmpeg-devel mailing list