Added HLS encryption with -hls_key_info_file <key_info_file> option. The first line of key_info_file specifies the key URI for the playlist, and the second line specifies the path to the file containing the encryption key. Changes to key_info_file will be reflected in segment encryption along with an entry in the playlist for the new key URI.
Also added -hls_flags random_iv option flag to use a random IV for encryption instead of the segment number. Signed-off-by: Christian Suloway <csulo...@globaleagleent.com> --- doc/muxers.texi | 11 +++ libavformat/hlsenc.c | 257 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 262 insertions(+), 6 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index a1264d2..29a5de3 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -263,6 +263,13 @@ ffmpeg in.nut -hls_segment_filename 'file%03d.ts' out.m3u8 This example will produce the playlist, @file{out.m3u8}, and segment files: @file{file000.ts}, @file{file001.ts}, @file{file002.ts}, etc. +@item hls_key_info_file @var{file} +Use in the information in @var{file} for segment encryption. The first line of +@var{file} specifies the key URI for the playlist. The second line specifies +the path to the file containing the encryption key as a single packed array of +16 octets in binary format. Changes to @var{file} will result in segment +encryption with the new key and an entry in the playlist for the new key URI. + @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 @@ -277,6 +284,10 @@ Will produce the playlist, @file{out.m3u8}, and a single segment file, @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. + +@item hls_flags random_iv +Segment file encryption will use a random initialization vector (IV) instead of +the segment number. @end table @anchor{ico} diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 79f3a23..5bde70e 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -32,17 +32,24 @@ #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" #include "os_support.h" +#define BLOCKSIZE 16 + typedef struct HLSSegment { char filename[1024]; double duration; /* in seconds */ int64_t pos; int64_t size; + char *key_uri; + char *iv_string; + struct HLSSegment *next; } HLSSegment; @@ -50,6 +57,7 @@ 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), + HLS_RANDOM_IV = (1 << 2), } HLSFlags; typedef struct HLSContext { @@ -86,9 +94,23 @@ typedef struct HLSContext { char *format_options_str; AVDictionary *format_options; + char *key_info_file; + char *key_file; + char *key_uri; + char *key_string; + char *iv_string; + AVLFG *lfg; + AVIOContext *pb; } HLSContext; +static void hls_free_segment(HLSSegment *en) +{ + av_freep(&en->key_uri); + av_freep(&en->iv_string); + av_freep(&en); +} + static int hls_delete_old_segments(HLSContext *hls) { HLSSegment *segment, *previous_segment = NULL; @@ -145,7 +167,7 @@ static int hls_delete_old_segments(HLSContext *hls) { av_free(path); previous_segment = segment; segment = previous_segment->next; - av_free(previous_segment); + hls_free_segment(previous_segment); } fail: @@ -154,6 +176,157 @@ fail: return ret; } +static int hls_encryption_init(AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + + if (hls->flags & HLS_RANDOM_IV) { + hls->lfg = av_malloc(sizeof(AVLFG)); + if (!hls->lfg) + return AVERROR(ENOMEM); + av_lfg_init(hls->lfg, av_get_random_seed()); + } + + return 0; +} + +static int hls_encryption_start(HLSContext *hls) +{ + + int ret = 0, i, j, rotate_iv = 0; + AVIOContext *pb = NULL; + AVIOContext *dyn_buf = NULL; + uint8_t buf[1024], *tmp = NULL, *key = NULL, *iv = NULL; + char *p, *tstr, *saveptr = NULL, *key_string = NULL; + unsigned int u; + + 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); + goto fail; + } + + ret = avio_open_dyn_buf(&dyn_buf); + if (ret < 0) { + avio_closep(&pb); + goto fail; + } + + while ((ret = avio_read(pb, buf, sizeof(buf))) > 0) + avio_write(dyn_buf, buf, ret); + avio_closep(&pb); + if (ret != AVERROR_EOF && ret < 0) { + avio_close_dyn_buf(dyn_buf, &tmp); + goto fail; + } + + avio_w8(dyn_buf, 0); + if ((ret = avio_close_dyn_buf(dyn_buf, &tmp)) < 0) + goto fail; + + p = tmp; + if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) { + av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file %s\n", + hls->key_info_file); + ret = AVERROR(EINVAL); + goto fail; + } + p = NULL; + av_free(hls->key_uri); + hls->key_uri = av_strdup(tstr); + if (!hls->key_uri) { + ret = AVERROR(ENOMEM); + goto fail; + } + 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); + ret = AVERROR(EINVAL); + goto fail; + } + av_free(hls->key_file); + hls->key_file = av_strdup(tstr); + if (!hls->key_file) { + ret = AVERROR(ENOMEM); + goto fail; + } + + 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); + goto fail; + } + + key = av_malloc(BLOCKSIZE); + if (!key) { + ret = AVERROR(ENOMEM); + goto fail; + } + ret = avio_read(pb, key, BLOCKSIZE); + avio_closep(&pb); + if (ret != BLOCKSIZE) { + av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", + hls->key_file); + if (ret >= 0 || ret == AVERROR_EOF) + ret = AVERROR(EINVAL); + goto fail; + } + + key_string = av_mallocz(BLOCKSIZE*2 + 1); + if (!key_string) { + ret = AVERROR(ENOMEM); + goto fail; + } + ff_data_to_hex(key_string, key, BLOCKSIZE, 0); + if (!hls->key_string || strncmp(key_string, hls->key_string, BLOCKSIZE*2)) { + av_free(hls->key_string); + hls->key_string = av_strdup(key_string); + if (!hls->key_string) { + ret = AVERROR(ENOMEM); + goto fail; + } + rotate_iv = 1; + } + + if (!(hls->flags & HLS_RANDOM_IV)) { + iv = av_mallocz(BLOCKSIZE); + if (!iv) { + ret = AVERROR(ENOMEM); + goto fail; + } + for (i = 0; i < 8; i++) + iv[BLOCKSIZE - 1 - i ] = (hls->sequence >> i*8) & 0xff; + } else if (!hls->iv_string || rotate_iv) { + iv = av_malloc(BLOCKSIZE); + if (!iv) { + ret = AVERROR(ENOMEM); + goto fail; + } + for (i = 0; i < BLOCKSIZE >> 2; i++) { + u = av_lfg_get(hls->lfg); + for (j = 0; j < 4; j++) + iv[i*4 + j] = (u >> j*8) & 0xff; + } + } + if (iv) { + av_free(hls->iv_string); + hls->iv_string = av_mallocz(BLOCKSIZE*2 + 1); + if (!hls->iv_string) { + ret = AVERROR(ENOMEM); + goto fail; + } + ff_data_to_hex(hls->iv_string, iv, BLOCKSIZE, 0); + } + +fail: + av_free(tmp); + av_free(key_string); + av_free(key); + av_free(iv); + + return ret; +} + static int hls_mux_init(AVFormatContext *s) { HLSContext *hls = s->priv_data; @@ -200,6 +373,18 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, en->size = size; en->next = NULL; + if (hls->key_info_file) { + 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 @@ -217,7 +402,7 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, if ((ret = hls_delete_old_segments(hls)) < 0) return ret; } else - av_free(en); + hls_free_segment(en); } else hls->nb_entries++; @@ -233,10 +418,18 @@ static void hls_free_segments(HLSSegment *p) 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->flags & 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; @@ -245,6 +438,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) @@ -267,6 +462,16 @@ static int hls_window(AVFormatContext *s, int last) sequence); for (en = hls->segments; en; en = en->next) { + if (hls->key_info_file) { + if (!key_uri || av_strcasecmp(en->key_uri, key_uri) || + hls->flags & 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", @@ -288,6 +493,10 @@ static int hls_start(AVFormatContext *s) { HLSContext *c = s->priv_data; AVFormatContext *oc = c->avf; + AVDictionary *options = NULL; + const char *prefix = "crypto:"; + int filename_size; + char *filename; int err = 0; if (c->flags & HLS_SINGLE_FILE) @@ -301,9 +510,34 @@ static int hls_start(AVFormatContext *s) } c->number++; - if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - return err; + if (c->key_info_file) { + if ((err = hls_encryption_start(c)) < 0) + return err; + if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0)) + < 0) + return err; + if ((err = av_dict_set(&options, "encryption_iv", c->iv_string, 0)) + < 0) + return err; + + filename_size = strlen(prefix) + strlen(oc->filename) + 1; + filename = av_malloc(filename_size); + if (!filename) { + av_dict_free(&options); + return AVERROR(ENOMEM); + } + av_strlcpy(filename, prefix, 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; + } else + if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + return err; if (oc->oformat->priv_class && oc->priv_data) av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); @@ -373,6 +607,9 @@ static int hls_write_header(AVFormatContext *s) av_strlcat(hls->basename, pattern, basename_size); } + if ((ret = hls_encryption_init(s)) < 0) + goto fail; + if ((ret = hls_mux_init(s)) < 0) goto fail; @@ -482,6 +719,12 @@ static int hls_write_trailer(struct AVFormatContext *s) hls->avf = NULL; hls_window(s, 1); + 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); @@ -499,9 +742,11 @@ 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_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 URI and key file path", OFFSET(key_info_file), 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"}, + {"random_iv", "randomize initialization vector", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_RANDOM_IV }, 0, UINT_MAX, E, "flags"}, { NULL }, }; -- 1.9.3 (Apple Git-50) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel