summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/muxers.texi56
-rw-r--r--libavformat/hlsenc.c118
2 files changed, 172 insertions, 2 deletions
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 95cdb8faea..1dd7d06761 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -263,6 +263,62 @@ 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{key_info_file}
+Use the information in @var{key_info_file} for segment encryption. The first
+line of @var{key_info_file} specifies the key URI written to the playlist. The
+key URL is used to access the encryption key during playback. The second line
+specifies the path to the key file used to obtain the key during the encryption
+process. The key file is read as a single packed array of 16 octets in binary
+format. The optional third line specifies the initialization vector (IV) as a
+hexadecimal string to be used instead of the segment sequence number (default)
+for encryption. Changes to @var{key_info_file} will result in segment
+encryption with the new key/IV and an entry in the playlist for the new key
+URI/IV.
+
+Key info file format:
+@example
+@var{key URI}
+@var{key file path}
+@var{IV} (optional)
+@end example
+
+Example key URIs:
+@example
+http://server/file.key
+/path/to/file.key
+file.key
+@end example
+
+Example key file paths:
+@example
+file.key
+/path/to/file.key
+@end example
+
+Example IV:
+@example
+0123456789ABCDEF0123456789ABCDEF
+@end example
+
+Key info file example:
+@example
+http://server/file.key
+/path/to/file.key
+0123456789ABCDEF0123456789ABCDEF
+@end example
+
+Example shell script:
+@example
+#!/bin/sh
+BASE_URL=${1:-'.'}
+openssl rand 16 > file.key
+echo $BASE_URL/file.key > file.keyinfo
+echo file.key >> file.keyinfo
+echo $(openssl rand -hex 16) >> file.keyinfo
+ffmpeg -f lavfi -re -i testsrc -c:v h264 -hls_flags delete_segments \
+ -hls_key_info_file file.keyinfo out.m3u8
+@end example
+
@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 4d9466c91f..68c6479fe3 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -37,12 +37,18 @@
#include "internal.h"
#include "os_support.h"
+#define KEYSIZE 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[KEYSIZE*2 + 1];
+
struct HLSSegment *next;
} HLSSegment;
@@ -89,6 +95,12 @@ typedef struct HLSContext {
char *baseurl;
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[KEYSIZE*2 + 1];
+ char iv_string[KEYSIZE*2 + 1];
} HLSContext;
static int hls_delete_old_segments(HLSContext *hls) {
@@ -156,6 +168,60 @@ fail:
return ret;
}
+static int hls_encryption_start(AVFormatContext *s)
+{
+ HLSContext *hls = s->priv_data;
+ int ret;
+ AVIOContext *pb;
+ uint8_t key[KEYSIZE];
+
+ 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';
+
+ 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);
+ }
+
+ if (!*hls->key_file) {
+ av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
+ return AVERROR(EINVAL);
+ }
+
+ 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;
@@ -202,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
@@ -239,6 +310,10 @@ static void hls_free_segments(HLSSegment *p)
}
}
+static void print_encryption_tag(HLSContext *hls, HLSSegment *en)
+{
+}
+
static int hls_window(AVFormatContext *s, int last)
{
HLSContext *hls = s->priv_data;
@@ -252,6 +327,8 @@ static int hls_window(AVFormatContext *s, int last)
const char *proto = avio_find_protocol_name(s->filename);
int use_rename = proto && !strcmp(proto, "file");
static unsigned warned_non_file;
+ char *key_uri = NULL;
+ char *iv_string = NULL;
if (!use_rename && !warned_non_file++)
av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporarly partial files\n");
@@ -282,6 +359,16 @@ static int hls_window(AVFormatContext *s, int last)
hls->discontinuity_set = 1;
}
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))) {
+ avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
+ if (*en->iv_string)
+ avio_printf(out, ",IV=0x%s", en->iv_string);
+ avio_printf(out, "\n");
+ key_uri = en->key_uri;
+ iv_string = en->iv_string;
+ }
+
if (hls->flags & HLS_ROUND_DURATIONS)
avio_printf(out, "#EXTINF:%d,\n", (int)round(en->duration));
else
@@ -308,6 +395,8 @@ static int hls_start(AVFormatContext *s)
{
HLSContext *c = s->priv_data;
AVFormatContext *oc = c->avf;
+ AVDictionary *options = NULL;
+ char *filename, iv_string[KEYSIZE*2 + 1];
int err = 0;
if (c->flags & HLS_SINGLE_FILE)
@@ -321,9 +410,33 @@ static int hls_start(AVFormatContext *s)
}
c->number++;
- if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
+ if (c->key_info_file) {
+ if ((err = hls_encryption_start(s)) < 0)
+ return err;
+ 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), "%032"PRIx64, c->sequence);
+ if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0)
+ return err;
+
+ filename = av_asprintf("crypto:%s", oc->filename);
+ if (!filename) {
+ av_dict_free(&options);
+ return AVERROR(ENOMEM);
+ }
+ 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;
+ return err;
if (oc->oformat->priv_class && oc->priv_data)
av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
@@ -520,6 +633,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"},