[FFmpeg-devel] [PATCH] avformat/hlsenc: added HLS encryption
Michael Niedermayer
michaelni at gmx.at
Wed Jan 7 21:17:15 CET 2015
On Wed, Jan 07, 2015 at 08:59:11AM -0600, Christian Suloway wrote:
> 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. The
> second line specifies the path to the file containing the encryption
> key. An optional third line specifies an IV to use instead of the
> segment number. Changes to key_info_file will be reflected in segment
> encryption along with an entry in the playlist for the new key URI and
> IV.
>
> Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>
Please add a testcase/example to either the documentation or
commit message
> ---
> doc/muxers.texi | 9 ++++
> libavformat/hlsenc.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 136 insertions(+), 3 deletions(-)
>
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index a1264d2..f2ecf8b 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -263,6 +263,15 @@ 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.
>
> + at item hls_key_info_file @var{file}
> +Use in the information in @var{file} for segment encryption. The first line of
> + at 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. The optional third line specifies a hexidecimal
> +string for the initialization vector (IV) to be used instead of the segment
> +number. Changes to @var{file} will result in segment encryption with the new
> +key/IV and an entry in the playlist for the new key URI/IV.
> +
> @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
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index f46e8d4..7f2bc96 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -37,12 +37,18 @@
> #include "internal.h"
> #include "os_support.h"
>
> +#define BLOCKSIZE 16
> +#define LINE_BUFFER_SIZE 1024
> +
> typedef struct HLSSegment {
> char filename[1024];
> double duration; /* in seconds */
> int64_t pos;
> int64_t size;
>
> + char key_uri[LINE_BUFFER_SIZE + 1];
> + char iv_string[BLOCKSIZE*2 + 1];
> +
> struct HLSSegment *next;
> } HLSSegment;
>
> @@ -86,6 +92,12 @@ typedef struct HLSContext {
> char *format_options_str;
> AVDictionary *format_options;
>
> + char *key_info_file;
> + char key_file[LINE_BUFFER_SIZE + 1];
> + char key_uri[LINE_BUFFER_SIZE + 1];
> + char key_string[BLOCKSIZE*2 + 1];
> + char iv_string[BLOCKSIZE*2 + 1];
> +
> AVIOContext *pb;
> } HLSContext;
>
> @@ -154,6 +166,62 @@ fail:
> return ret;
> }
>
> +static int hls_encryption_start(AVFormatContext *s)
> +{
> + HLSContext *hls = s->priv_data;
> + int ret;
> + AVIOContext *pb;
> + uint8_t key[BLOCKSIZE];
> +
> + if ((ret = avio_open2(&pb, hls->key_info_file, AVIO_FLAG_READ,
> + &s->interrupt_callback, NULL)) < 0) {
> + av_log(hls, AV_LOG_ERROR,
> + "error opening key info file %s\n", hls->key_info_file);
> + return ret;
> + }
> +
> + ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri));
> + hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0';
> +
> + ff_get_line(pb, hls->key_file, sizeof(hls->key_file));
> + hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0';
> +
> + ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string));
> + hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0';
in what case are the strcspn() needed ?
> +
> + avio_close(pb);
> +
> + if (!*hls->key_uri) {
> + av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
> + return AVERROR(EINVAL);
> + }
> + av_log(hls, AV_LOG_DEBUG, "key URI = %s\n", hls->key_uri);
> +
> + if (!*hls->key_file) {
> + av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
> + return AVERROR(EINVAL);
> + }
> + av_log(hls, AV_LOG_DEBUG, "key file = %s\n", hls->key_file);
> +
> + if ((ret = avio_open2(&pb, hls->key_file, AVIO_FLAG_READ,
> + &s->interrupt_callback, NULL)) < 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_close(pb);
> + if (ret != sizeof(key)) {
> + av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file);
> + if (ret >= 0 || ret == AVERROR_EOF)
> + ret = AVERROR(EINVAL);
> + return ret;
> + }
> + ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
> +
> + return 0;
> +}
> +
> static int hls_mux_init(AVFormatContext *s)
> {
> HLSContext *hls = s->priv_data;
> @@ -200,6 +268,11 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
> en->size = size;
> en->next = NULL;
>
> + if (hls->key_info_file) {
> + av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
> + av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
> + }
> +
> if (!hls->segments)
> hls->segments = en;
> else
> @@ -237,6 +310,14 @@ static void hls_free_segments(HLSSegment *p)
> }
> }
>
> +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 (*en->iv_string)
> + 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 +326,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 +350,13 @@ static int hls_window(AVFormatContext *s, int last)
> sequence);
>
> for (en = hls->segments; en; en = en->next) {
> + if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) ||
> + 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 +378,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, iv_string[BLOCKSIZE*2 + 1];
> int err = 0;
>
> if (c->flags & HLS_SINGLE_FILE)
> @@ -301,9 +395,38 @@ 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(s)) < 0)
> + return err;
> + av_log(c, AV_LOG_DEBUG, "key = 0x%s\n", c->key_string);
> + if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
> + < 0)
> + return err;
> + err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string));
> + if (!err)
> + snprintf(iv_string, sizeof(iv_string), "%032llx", c->sequence);
libavformat/hlsenc.c:407:13: warning: format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 4 has type ‘int64_t’ [-Wformat]
libavformat/hlsenc.c:407:13: warning: format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 4 has type ‘int64_t’ [-Wformat]
> + av_log(c, AV_LOG_DEBUG, "IV = 0x%s\n", iv_string);
> + if ((err = av_dict_set(&options, "encryption_iv", 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);
this looks like it can be simplified with av_asprintf()
> + 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);
> @@ -501,6 +624,7 @@ 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"},
> --
> 1.9.3 (Apple Git-50)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
--
Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
Everything should be made as simple as possible, but not simpler.
-- Albert Einstein
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20150107/56bf31cc/attachment.asc>
More information about the ffmpeg-devel
mailing list