|
|
@@ -22,10 +22,20 @@ |
|
|
|
#include <float.h> |
|
|
|
#include <stdint.h> |
|
|
|
|
|
|
|
#include <config.h> |
|
|
|
|
|
|
|
#if CONFIG_GCRYPT |
|
|
|
#include <gcrypt.h> |
|
|
|
#elif CONFIG_OPENSSL |
|
|
|
#include <openssl/rand.h> |
|
|
|
#endif |
|
|
|
|
|
|
|
#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 +70,112 @@ typedef struct HLSContext { |
|
|
|
ListEntry *end_list; |
|
|
|
char *basename; |
|
|
|
char *baseurl; |
|
|
|
|
|
|
|
int encrypt; // Set by a private option. |
|
|
|
char *key; // Set by a private option. |
|
|
|
int key_len; |
|
|
|
char *key_url; // Set by a private option. |
|
|
|
char *iv; // Set by a private option. |
|
|
|
int iv_len; |
|
|
|
|
|
|
|
char *key_basename; |
|
|
|
|
|
|
|
AVDictionary *enc_opts; |
|
|
|
} HLSContext; |
|
|
|
|
|
|
|
|
|
|
|
static int randomize(uint8_t *buf, int len) |
|
|
|
{ |
|
|
|
#if CONFIG_GCRYPT |
|
|
|
gcry_randomize(buf, len, GCRY_VERY_STRONG_RANDOM); |
|
|
|
return 0; |
|
|
|
#elif CONFIG_OPENSSL |
|
|
|
if (RAND_bytes(buf, len)) |
|
|
|
return 0; |
|
|
|
#else |
|
|
|
return AVERROR(ENOSYS); |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
static void free_encryption(AVFormatContext *s) |
|
|
|
{ |
|
|
|
HLSContext *hls = s->priv_data; |
|
|
|
|
|
|
|
av_dict_free(&hls->enc_opts); |
|
|
|
|
|
|
|
av_freep(&hls->key_basename); |
|
|
|
} |
|
|
|
|
|
|
|
static int dict_set_bin(AVDictionary **dict, const char *key, uint8_t *buf) |
|
|
|
{ |
|
|
|
char hex[33]; |
|
|
|
|
|
|
|
ff_data_to_hex(hex, buf, sizeof(buf), 0); |
|
|
|
hex[32] = '\0'; |
|
|
|
|
|
|
|
return av_dict_set(dict, key, hex, 0); |
|
|
|
} |
|
|
|
|
|
|
|
static int setup_encryption(AVFormatContext *s) |
|
|
|
{ |
|
|
|
HLSContext *hls = s->priv_data; |
|
|
|
AVIOContext *out = NULL; |
|
|
|
int len, ret; |
|
|
|
uint8_t buf[16]; |
|
|
|
uint8_t *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 (hls->key_len != 16) { |
|
|
|
av_log(s, AV_LOG_ERROR, |
|
|
|
"Invalid key size %d, expected 16-bytes hex-coded key\n", |
|
|
|
hls->key_len); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
|
|
|
|
if ((ret = dict_set_bin(&hls->enc_opts, "key", hls->key)) < 0) |
|
|
|
return ret; |
|
|
|
k = hls->key; |
|
|
|
} else { |
|
|
|
if ((ret = randomize(buf, sizeof(buf))) < 0) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Cannot generate a strong random key\n"); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
if ((ret = dict_set_bin(&hls->enc_opts, "key", buf)) < 0) |
|
|
|
return ret; |
|
|
|
k = buf; |
|
|
|
} |
|
|
|
|
|
|
|
if (hls->iv) { |
|
|
|
if (hls->iv_len != 16) { |
|
|
|
av_log(s, AV_LOG_ERROR, |
|
|
|
"Invalid key size %d, expected 16-bytes hex-coded initialization vector\n", |
|
|
|
hls->iv_len); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
|
|
|
|
if ((ret = dict_set_bin(&hls->enc_opts, "iv", hls->iv)) < 0) |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
if ((ret = s->io_open(s, &out, hls->key_basename, AVIO_FLAG_WRITE, NULL)) < 0) |
|
|
|
return ret; |
|
|
|
|
|
|
|
avio_write(out, k, 16); |
|
|
|
|
|
|
|
avio_close(out); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int hls_mux_init(AVFormatContext *s) |
|
|
|
{ |
|
|
|
HLSContext *hls = s->priv_data; |
|
|
@@ -165,6 +279,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 +323,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 +399,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 +420,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 +438,8 @@ fail: |
|
|
|
av_free(hls->basename); |
|
|
|
if (hls->avf) |
|
|
|
avformat_free_context(hls->avf); |
|
|
|
|
|
|
|
free_encryption(s); |
|
|
|
} |
|
|
|
return ret; |
|
|
|
} |
|
|
@@ -332,6 +507,7 @@ static int hls_write_trailer(struct AVFormatContext *s) |
|
|
|
hls_window(s, 1); |
|
|
|
|
|
|
|
free_entries(hls); |
|
|
|
free_encryption(s); |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
@@ -345,6 +521,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_BINARY, .flags = 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_BINARY, .flags = E}, |
|
|
|
{ NULL }, |
|
|
|
}; |
|
|
|
|
|
|
|