Partially based on Christian Suloway <csulo...@globaleagleent.com> work.
---
 libavformat/hlsenc.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++-----
 libavutil/opt.c      |   1 +
 2 files changed, 172 insertions(+), 18 deletions(-)

diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 96b780f..5990adb 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -25,7 +25,9 @@
 #include "libavutil/mathematics.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/avstring.h"
+#include "libavutil/intreadwrite.h"
 #include "libavutil/opt.h"
+#include "libavutil/random_seed.h"
 #include "libavutil/log.h"
 
 #include "avformat.h"
@@ -60,8 +62,93 @@ typedef struct HLSContext {
     ListEntry *end_list;
     char *basename;
     char *baseurl;
+
+    int encrypt;           // Set by a private option.
+    char *key;             // Set by a private option.
+    char *key_url;         // Set by a private option.
+    char *iv;              // Set by a private option.
+
+    char *key_basename;
+
+    AVDictionary *enc_opts;
 } HLSContext;
 
+static void fill_random(char hex[33])
+{
+    uint8_t buf[16];
+    int i;
+
+    for (i = 0; i < sizeof(buf); i++)
+        buf[i] = av_get_random_seed();
+
+    ff_data_to_hex(hex, buf, sizeof(buf), 0);
+    hex[32] = '\0';
+}
+
+static void free_encryption(AVFormatContext *s)
+{
+    HLSContext *hls = s->priv_data;
+
+    av_dict_free(&hls->enc_opts);
+
+    av_freep(&hls->key_basename);
+}
+
+static int setup_encryption(AVFormatContext *s)
+{
+    HLSContext *hls = s->priv_data;
+    AVIOContext *out = NULL;
+    int len, ret;
+    uint8_t buf[16];
+    char key[33], *k;
+
+    len = strlen(hls->basename) + 4 + 1;
+    hls->key_basename = av_mallocz(len);
+    if (!hls->key_basename)
+        return AVERROR(ENOMEM);
+
+    av_strlcpy(hls->key_basename, hls->basename + 7, len);
+    av_strlcat(hls->key_basename, ".key", len);
+
+    if (hls->key) {
+        if ((len = ff_hex_to_data(NULL, hls->key)) != 16) {
+            av_log(s, AV_LOG_ERROR, "Invalid key size %d, expected 16-bytes 
hex-coded key\n", len);
+            return AVERROR(EINVAL);
+        }
+        if ((ret = av_dict_set(&hls->enc_opts, "key", hls->key, 0)) < 0)
+            return ret;
+        k = hls->key;
+    } else {
+        fill_random(key);
+
+        av_log(s, AV_LOG_VERBOSE, "Using key: %s\n", key);
+
+        if ((ret = av_dict_set(&hls->enc_opts, "key", key, 0)) < 0)
+            return ret;
+        k = key;
+    }
+
+    if (hls->iv) {
+        if ((len = ff_hex_to_data(NULL, hls->iv)) != 16) {
+            av_log(s, AV_LOG_ERROR, "Invalid key size %d, expected 16-bytes 
hex-coded initialization vector\n", len);
+            return AVERROR(EINVAL);
+        }
+        if ((ret = av_dict_set(&hls->enc_opts, "iv", hls->iv, 0)) < 0)
+            return ret;
+    }
+
+    if ((ret = s->io_open(s, &out, hls->key_basename, AVIO_FLAG_WRITE, NULL)) 
< 0)
+        return ret;
+
+    ff_hex_to_data(buf, k);
+
+    avio_write(out, buf, 16);
+
+    avio_close(out);
+
+    return 0;
+}
+
 static int hls_mux_init(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
@@ -165,6 +252,24 @@ static int hls_window(AVFormatContext *s, int last)
            sequence);
 
     for (en = hls->list; en; en = en->next) {
+        if (hls->encrypt) {
+            char *key_url;
+
+            if (hls->key_url)
+                key_url = hls->key_url;
+            else
+                key_url = hls->baseurl;
+
+            avio_printf(out, "#EXT-X-KEY:METHOD=AES-128");
+            avio_printf(out, ",URI=\"");
+            if (key_url)
+                avio_printf(out, "%s", key_url);
+            avio_printf(out, "%s\"", av_basename(hls->key_basename));
+            if (hls->iv)
+                avio_printf(out, ",IV=\"0x%s\"", hls->iv);
+            avio_printf(out, "\n");
+        }
+
         if (hls->version > 2)
             avio_printf(out, "#EXTINF:%f\n",
                         (double)en->duration / AV_TIME_BASE);
@@ -191,18 +296,75 @@ static int hls_start(AVFormatContext *s)
     HLSContext *c = s->priv_data;
     AVFormatContext *oc = c->avf;
     int err = 0;
+    AVDictionary *opts = NULL;
+
 
     if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
                               c->basename, c->wrap ? c->sequence % c->wrap : 
c->sequence) < 0)
         return AVERROR(EINVAL);
     c->number++;
 
-    if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 
0)
+    if (c->encrypt) {
+        if ((err = av_dict_copy(&opts, c->enc_opts, 0)) < 0)
+            return err;
+        if (!c->iv) {
+            uint8_t iv[16] = { 0 };
+            char buf[33];
+
+            AV_WB64(iv + 8, c->sequence);
+            ff_data_to_hex(buf, iv, sizeof(iv), 0);
+            buf[32] = '\0';
+
+            if ((err = av_dict_set(&opts, "iv", buf, 0)) < 0)
+                goto fail;
+        }
+    }
+
+    if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, &opts)) < 
0)
         return err;
 
     if (oc->oformat->priv_class && oc->priv_data)
         av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
 
+fail:
+    av_dict_free(&opts);
+
+    return err;
+}
+
+static int hls_setup(AVFormatContext *s)
+{
+    HLSContext *hls = s->priv_data;
+    const char *pattern = "%d.ts";
+    int basename_size = strlen(s->filename) + strlen(pattern) + 1;
+    char *p;
+
+    if (hls->encrypt)
+        basename_size += 7;
+
+    hls->basename = av_mallocz(basename_size);
+    if (!hls->basename)
+        return AVERROR(ENOMEM);
+
+    // TODO: support protocol nesting?
+    if (hls->encrypt)
+        strcpy(hls->basename, "crypto:");
+
+    av_strlcat(hls->basename, s->filename, basename_size);
+
+    p = strrchr(hls->basename, '.');
+
+    if (p)
+        *p = '\0';
+
+    if (hls->encrypt) {
+        int ret = setup_encryption(s);
+        if (ret < 0)
+            return ret;
+    }
+
+    av_strlcat(hls->basename, pattern, basename_size);
+
     return 0;
 }
 
@@ -210,9 +372,6 @@ static int hls_write_header(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
     int ret, i;
-    char *p;
-    const char *pattern = "%d.ts";
-    int basename_size = strlen(s->filename) + strlen(pattern) + 1;
 
     hls->sequence       = hls->start_sequence;
     hls->recording_time = hls->time * AV_TIME_BASE;
@@ -234,21 +393,8 @@ static int hls_write_header(AVFormatContext *s)
         goto fail;
     }
 
-    hls->basename = av_malloc(basename_size);
-
-    if (!hls->basename) {
-        ret = AVERROR(ENOMEM);
+    if ((ret = hls_setup(s)) < 0)
         goto fail;
-    }
-
-    strcpy(hls->basename, s->filename);
-
-    p = strrchr(hls->basename, '.');
-
-    if (p)
-        *p = '\0';
-
-    av_strlcat(hls->basename, pattern, basename_size);
 
     if ((ret = hls_mux_init(s)) < 0)
         goto fail;
@@ -265,6 +411,8 @@ fail:
         av_free(hls->basename);
         if (hls->avf)
             avformat_free_context(hls->avf);
+
+        free_encryption(s);
     }
     return ret;
 }
@@ -332,6 +480,7 @@ static int hls_write_trailer(struct AVFormatContext *s)
     hls_window(s, 1);
 
     free_entries(hls);
+    free_encryption(s);
     return 0;
 }
 
@@ -345,6 +494,10 @@ static const AVOption options[] = {
     {"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_version",   "protocol version",                        
OFFSET(version), AV_OPT_TYPE_INT,    {.i64 = 3},     2, 3, E},
+    {"hls_enc",       "AES128 encryption support",               
OFFSET(encrypt), AV_OPT_TYPE_INT,    {.i64 = 0},     0, 1, E},
+    {"hls_enc_key",   "use the specified hex-coded 16byte key to encrypt the 
segments",  OFFSET(key), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
+    {"hls_enc_key_url", "url to access the key to decrypt the segments",    
OFFSET(key_url), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
+    {"hls_enc_iv",     "use the specified hex-coded 16byte initialization 
vector",  OFFSET(iv), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
     { NULL },
 };
 
diff --git a/libavutil/opt.c b/libavutil/opt.c
index 7cb3d66..ff13f73 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -744,6 +744,7 @@ int av_opt_set_dict(void *obj, AVDictionary **options)
             av_dict_set(&tmp, t->key, t->value, 0);
         else if (ret < 0) {
             av_log(obj, AV_LOG_ERROR, "Error setting option %s to value 
%s.\n", t->key, t->value);
+            abort();
             break;
         }
         ret = 0;
-- 
2.9.2

_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to