[FFmpeg-devel] [PATCH] avformat/hlsenc: added HLS encryption
Christian Suloway
csuloway at row44.com
Wed Nov 26 23:37:51 CET 2014
Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>
---
libavformat/crypto.c | 233 ++++++++++++++++++++++++++++---
libavformat/hlsenc.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 567 insertions(+), 53 deletions(-)
diff --git a/libavformat/crypto.c b/libavformat/crypto.c
index a9b6e47..dc3dc88 100644
--- a/libavformat/crypto.c
+++ b/libavformat/crypto.c
@@ -38,17 +38,35 @@ typedef struct {
int indata, indata_used, outdata;
int eof;
uint8_t *key;
- int keylen;
+ int key_len;
uint8_t *iv;
- int ivlen;
- struct AVAES *aes;
+ int iv_len;
+ uint8_t *decrypt_key;
+ int decrypt_key_len;
+ uint8_t *decrypt_iv;
+ int decrypt_iv_len;
+ uint8_t *encrypt_key;
+ int encrypt_key_len;
+ uint8_t *encrypt_iv;
+ int encrypt_iv_len;
+ struct AVAES *aes_decrypt;
+ struct AVAES *aes_encrypt;
+
+ uint8_t pad[BLOCKSIZE];
+ int pad_len;
+
} CryptoContext;
#define OFFSET(x) offsetof(CryptoContext, x)
#define D AV_OPT_FLAG_DECODING_PARAM
+#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
- {"key", "AES decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D },
- {"iv", "AES decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D },
+ {"key", "AES encryption/decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D|E },
+ {"iv", "AES encryption/decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D|E },
+ {"decryption_key", "AES decryption key", OFFSET(decrypt_key), AV_OPT_TYPE_BINARY, .flags = D },
+ {"decryption_iv", "AES decryption initialization vector", OFFSET(decrypt_iv), AV_OPT_TYPE_BINARY, .flags = D },
+ {"encryption_key", "AES encryption key", OFFSET(encrypt_key), AV_OPT_TYPE_BINARY, .flags = E },
+ {"encryption_iv", "AES encryption initialization vector", OFFSET(encrypt_iv), AV_OPT_TYPE_BINARY, .flags = E },
{ NULL }
};
@@ -72,28 +90,145 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
goto err;
}
- if (c->keylen < BLOCKSIZE || c->ivlen < BLOCKSIZE) {
- av_log(h, AV_LOG_ERROR, "Key or IV not set\n");
- ret = AVERROR(EINVAL);
- goto err;
+ if (flags & AVIO_FLAG_READ) {
+ if (!c->decrypt_key_len) {
+ if (!c->key_len) {
+ av_log(h, AV_LOG_ERROR, "decryption key not set\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ } else if (c->key_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid key size "
+ "(%d bytes, block size is %d)\n",
+ c->key_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ c->decrypt_key = av_malloc(c->key_len);
+ if (!c->decrypt_key) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ memcpy(c->decrypt_key, c->key, c->key_len);
+ c->decrypt_key_len = c->key_len;
+ } else if (c->decrypt_key_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid decryption key size "
+ "(%d bytes, block size is %d)\n",
+ c->decrypt_key_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ if (!c->decrypt_iv_len) {
+ if (!c->iv_len) {
+ av_log(h, AV_LOG_ERROR, "decryption IV not set\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ } else if (c->iv_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid IV size "
+ "(%d bytes, block size is %d)\n",
+ c->iv_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ c->decrypt_iv = av_malloc(c->iv_len);
+ if (!c->decrypt_iv) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ memcpy(c->decrypt_iv, c->iv, c->iv_len);
+ c->decrypt_iv_len = c->iv_len;
+ } else if (c->decrypt_iv_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid decryption IV size "
+ "(%d bytes, block size is %d)\n",
+ c->decrypt_iv_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
}
+
if (flags & AVIO_FLAG_WRITE) {
- av_log(h, AV_LOG_ERROR, "Only decryption is supported currently\n");
- ret = AVERROR(ENOSYS);
- goto err;
+ if (!c->encrypt_key_len) {
+ if (!c->key_len) {
+ av_log(h, AV_LOG_ERROR, "encryption key not set\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ } else if (c->key_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid key size "
+ "(%d bytes, block size is %d)\n",
+ c->key_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ c->encrypt_key = av_malloc(c->key_len);
+ if (!c->encrypt_key) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ memcpy(c->encrypt_key, c->key, c->key_len);
+ c->encrypt_key_len = c->key_len;
+ } else if (c->encrypt_key_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid encryption key size "
+ "(%d bytes, block size is %d)\n",
+ c->encrypt_key_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ if (!c->encrypt_iv_len) {
+ if (!c->iv_len) {
+ av_log(h, AV_LOG_ERROR, "encryption IV not set\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ } else if (c->iv_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid IV size "
+ "(%d bytes, block size is %d)\n",
+ c->iv_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+ c->encrypt_iv = av_malloc(c->iv_len);
+ if (!c->encrypt_iv) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ memcpy(c->encrypt_iv, c->iv, c->iv_len);
+ c->encrypt_iv_len = c->iv_len;
+ } else if (c->encrypt_iv_len != BLOCKSIZE) {
+ av_log(h, AV_LOG_ERROR, "invalid encryption IV size "
+ "(%d bytes, block size is %d)\n",
+ c->encrypt_iv_len, BLOCKSIZE);
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
}
- if ((ret = ffurl_open(&c->hd, nested_url, AVIO_FLAG_READ,
+
+ if ((ret = ffurl_open(&c->hd, nested_url, flags,
&h->interrupt_callback, options)) < 0) {
- av_log(h, AV_LOG_ERROR, "Unable to open input\n");
+ av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url);
goto err;
}
- c->aes = av_aes_alloc();
- if (!c->aes) {
- ret = AVERROR(ENOMEM);
- goto err;
+
+ if (flags & AVIO_FLAG_READ) {
+ c->aes_decrypt = av_aes_alloc();
+ if (!c->aes_decrypt) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE*8, 1);
+ if (ret < 0)
+ goto err;
+ }
+
+ if (flags & AVIO_FLAG_WRITE) {
+ c->aes_encrypt = av_aes_alloc();
+ if (!c->aes_encrypt) {
+ ret = AVERROR(ENOMEM);
+ goto err;
+ }
+ ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE*8, 0);
+ if (ret < 0)
+ goto err;
}
- av_aes_init(c->aes, c->key, 128, 1);
+ c->pad_len = 0;
h->is_streamed = 1;
@@ -131,8 +266,8 @@ retry:
return AVERROR_EOF;
if (!c->eof)
blocks--;
- av_aes_crypt(c->aes, c->outbuffer, c->inbuffer + c->indata_used, blocks,
- c->iv, 1);
+ av_aes_crypt(c->aes_decrypt, c->outbuffer, c->inbuffer + c->indata_used,
+ blocks, c->decrypt_iv, 1);
c->outdata = BLOCKSIZE * blocks;
c->outptr = c->outbuffer;
c->indata_used += BLOCKSIZE * blocks;
@@ -150,12 +285,65 @@ retry:
goto retry;
}
+static int crypto_write(URLContext *h, const unsigned char *buf, int size)
+{
+ CryptoContext *c = h->priv_data;
+ int total_size, blocks, pad_len, out_size;
+ uint8_t *out_buf;
+ int ret = 0;
+
+ total_size = size + c->pad_len;
+ pad_len = total_size % BLOCKSIZE;
+ out_size = total_size - pad_len;
+ blocks = out_size / BLOCKSIZE;
+
+ if (out_size) {
+ out_buf = av_malloc(out_size);
+ if (!out_buf)
+ return AVERROR(ENOMEM);
+
+ if (c->pad_len) {
+ memcpy(&c->pad[c->pad_len], buf, BLOCKSIZE - c->pad_len);
+ av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);
+ blocks--;
+ }
+
+ av_aes_crypt(c->aes_encrypt, &out_buf[c->pad_len ? BLOCKSIZE : 0],
+ &buf[c->pad_len ? BLOCKSIZE - c->pad_len: 0],
+ blocks, c->encrypt_iv, 0);
+
+ ret = ffurl_write(c->hd, out_buf, out_size);
+ av_free(out_buf);
+ if (ret < 0)
+ return ret;
+
+ memcpy(c->pad, &buf[size - pad_len], pad_len);
+ } else
+ memcpy(&c->pad[c->pad_len], buf, size);
+
+ c->pad_len = pad_len;
+
+ return size;
+}
+
static int crypto_close(URLContext *h)
{
CryptoContext *c = h->priv_data;
+ uint8_t out_buf[BLOCKSIZE];
+ int ret, pad;
+
+ if (c->aes_encrypt) {
+ pad = BLOCKSIZE - c->pad_len;
+ memset(&c->pad[c->pad_len], pad, pad);
+ av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);
+ if ((ret = ffurl_write(c->hd, out_buf, BLOCKSIZE)) < 0)
+ return ret;
+ }
+
if (c->hd)
ffurl_close(c->hd);
- av_freep(&c->aes);
+ av_freep(&c->aes_decrypt);
+ av_freep(&c->aes_encrypt);
return 0;
}
@@ -163,6 +351,7 @@ URLProtocol ff_crypto_protocol = {
.name = "crypto",
.url_open2 = crypto_open2,
.url_read = crypto_read,
+ .url_write = crypto_write,
.url_close = crypto_close,
.priv_data_size = sizeof(CryptoContext),
.priv_data_class = &crypto_class,
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index e13f438..516eac6 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -19,8 +19,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include "config.h"
#include <float.h>
#include <stdint.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
#include "libavutil/avassert.h"
#include "libavutil/mathematics.h"
@@ -28,16 +33,21 @@
#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"
typedef struct HLSSegment {
- char filename[1024];
+ char *filename;
double duration; /* in seconds */
int64_t pos;
int64_t size;
+ char *key_uri;
+ char *iv_string;
+
struct HLSSegment *next;
} HLSSegment;
@@ -58,6 +68,7 @@ typedef struct HLSContext {
float time; // Set by a private option.
int max_nb_segments; // Set by a private option.
int wrap; // Set by a private option.
+ int delete_segments;
uint32_t flags; // enum HLSFlags
int allowcache;
@@ -72,15 +83,219 @@ typedef struct HLSContext {
HLSSegment *segments;
HLSSegment *last_segment;
+ HLSSegment *old_segments;
+ char *dirname;
char *basename;
char *baseurl;
char *format_options_str;
AVDictionary *format_options;
+ char *segment_filename;
+
+ char *key_info_file;
+ int random_iv;
+ int encrypt;
+
+ char *key_file;
+ char *key_uri;
+ char *key_string;
+ char *iv_string;
+
+ AVLFG *lfg;
+
AVIOContext *pb;
} HLSContext;
+static int hex_string(char **string, uint8_t *buf, int size)
+{
+ int i;
+
+ *string = av_mallocz(size*2 + 1);
+ if (!*string)
+ return AVERROR(ENOMEM);
+ for (i = 0; i < size; i++)
+ snprintf(&(*string)[i*2], 3, "%02X", buf[i]);
+
+ return 0;
+}
+
+
+static void hls_free_segment(HLSSegment *en)
+{
+ av_free(en->filename);
+ av_free(en->key_uri);
+ av_free(en->iv_string);
+ av_free(en);
+}
+
+static int hls_delete_old_segments(HLSContext *hls) {
+
+ HLSSegment *segment, *previous_segment = NULL;
+ float playlist_duration = 0.0f;
+ int path_size;
+ char *path;
+
+ segment = hls->segments;
+ while (segment) {
+ playlist_duration += segment->duration;
+ segment = segment->next;
+ }
+
+ segment = hls->old_segments;
+ while (segment) {
+ playlist_duration -= segment->duration;
+ previous_segment = segment;
+ segment = segment->next;
+ if (playlist_duration <= 0.0f) {
+ previous_segment->next = NULL;
+ break;
+ }
+ }
+
+ while (segment) {
+ av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
+ segment->filename);
+ path_size = strlen(hls->dirname) + strlen(segment->filename) + 1;
+ path = av_malloc(path_size);
+ if (!path)
+ return AVERROR(ENOMEM);
+ av_strlcpy(path, hls->dirname, path_size);
+ av_strlcat(path, segment->filename, path_size);
+ if (unlink(path) < 0) {
+ av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
+ path, strerror(errno));
+ }
+ av_free(path);
+ previous_segment = segment;
+ segment = segment->next;
+ hls_free_segment(previous_segment);
+ }
+
+ return 0;
+}
+
+static int hls_encryption_init(AVFormatContext *s)
+{
+ HLSContext *hls = s->priv_data;
+
+ hls->key_file = NULL;
+ hls->key_uri = NULL;
+ hls->key_string = NULL;
+ hls->iv_string = NULL;
+
+ if (hls->key_info_file) {
+ hls->encrypt = 1;
+ } else {
+ hls->encrypt = 0;
+ return 0;
+ }
+
+ if (hls->random_iv) {
+ hls->lfg = av_malloc(sizeof(AVLFG));
+ av_lfg_init(hls->lfg, av_get_random_seed());
+ } else
+ hls->lfg = NULL;
+
+ return 0;
+}
+
+static int hls_encryption_start(HLSContext *hls) {
+
+ uint8_t iv[16];
+ int ret, i, j, rotate_iv = 0;
+ unsigned int u;
+ AVIOContext *pb = NULL;
+ AVIOContext *dyn_buf = NULL;
+ uint8_t buf[1024], *tmp;
+ char *p, *tstr, *saveptr = NULL;
+ char key[16], *key_string;
+
+ 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);
+ return ret;
+ }
+ ret = avio_open_dyn_buf(&dyn_buf);
+ if (ret < 0) {
+ avio_closep(&pb);
+ return ret;
+ }
+
+ while ((ret = avio_read(pb, buf, sizeof(buf))) > 0)
+ avio_write(dyn_buf, buf, ret);
+ avio_w8(dyn_buf, 0);
+ avio_closep(&pb);
+
+ if ((ret = avio_close_dyn_buf(dyn_buf, &tmp)) < 0)
+ return ret;
+
+ p = tmp;
+ if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
+ av_log(hls, AV_LOG_ERROR, "no key URL specified in key info file %s\n",
+ hls->key_info_file);
+ return AVERROR(EINVAL);
+ }
+ p = NULL;
+ av_free(hls->key_uri);
+ hls->key_uri = av_strdup(tstr);
+ if (!hls->key_uri)
+ return AVERROR(ENOMEM);
+ 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);
+ return AVERROR(EINVAL);
+ }
+ av_free(hls->key_file);
+ hls->key_file = av_strdup(tstr);
+ if (!hls->key_file)
+ return AVERROR(ENOMEM);
+ av_free(tmp);
+
+ 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);
+ return ret;
+ }
+
+ ret = avio_read(pb, key, sizeof(key));
+ avio_closep(&pb);
+ if (ret < sizeof(key)) {
+ av_log(hls, AV_LOG_ERROR, "error reading key file %s\n",
+ hls->key_file);
+ return ret;
+ }
+
+ if ((ret = hex_string(&key_string, key, 16)) < 0)
+ return ret;
+ if (!hls->key_string || strncmp(key_string, hls->key_string, 32)) {
+ av_free(hls->key_string);
+ hls->key_string = key_string;
+ rotate_iv = 1;
+ } else {
+ av_free(key_string);
+ }
+
+ if (!hls->random_iv) {
+ for (i = 0; i < 8; i++)
+ iv[15 - i] = (hls->sequence >> i*8) & 0xff;
+ memset(iv, 0, 8);
+ if ((ret = hex_string(&hls->iv_string, iv, 16)) < 0)
+ return ret;
+ } else if (!hls->iv_string || rotate_iv) {
+ for (i = 0; i < 4; i++) {
+ u = av_lfg_get(hls->lfg);
+ for (j = 0; j < 4; j++)
+ iv[i*4 + j] = (u >> j*8) & 0xff;
+ }
+ av_free(hls->iv_string);
+ if ((ret = hex_string(&hls->iv_string, iv, 16)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int hls_mux_init(AVFormatContext *s)
{
HLSContext *hls = s->priv_data;
@@ -115,17 +330,32 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
int64_t size)
{
HLSSegment *en = av_malloc(sizeof(*en));
+ int ret;
if (!en)
return AVERROR(ENOMEM);
- av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename));
+ en->filename = av_strdup(hls->avf->filename);
+ if (!en->filename)
+ return AVERROR(ENOMEM);
en->duration = duration;
en->pos = pos;
en->size = size;
en->next = NULL;
+ if (hls->encrypt) {
+ 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
@@ -136,7 +366,14 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) {
en = hls->segments;
hls->segments = en->next;
- av_free(en);
+
+ if (en && hls->delete_segments && !hls->wrap) {
+ en->next = hls->old_segments;
+ hls->old_segments = en;
+ if ((ret = hls_delete_old_segments(hls)) < 0)
+ return ret;
+ } else
+ hls_free_segment(en);
} else
hls->nb_entries++;
@@ -145,17 +382,25 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
return 0;
}
-static void hls_free_segments(HLSContext *hls)
+static void hls_free_segments(HLSSegment *p)
{
- HLSSegment *p = hls->segments, *en;
+ HLSSegment *en;
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->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;
@@ -164,6 +409,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)
@@ -186,6 +433,16 @@ static int hls_window(AVFormatContext *s, int last)
sequence);
for (en = hls->segments; en; en = en->next) {
+ if (hls->encrypt) {
+ if (!key_uri || av_strcasecmp(en->key_uri, key_uri) ||
+ 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",
@@ -208,6 +465,10 @@ static int hls_start(AVFormatContext *s)
HLSContext *c = s->priv_data;
AVFormatContext *oc = c->avf;
int err = 0;
+ AVDictionary *options = NULL;
+ const char *prefix = "crypto:";
+ int filename_size;
+ char *filename;
if (c->flags & HLS_SINGLE_FILE)
av_strlcpy(oc->filename, c->basename,
@@ -220,8 +481,33 @@ static int hls_start(AVFormatContext *s)
}
c->number++;
- if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL)) < 0)
+ if (c->encrypt) {
+ if ((err = hls_encryption_start(c)) < 0)
+ return err;
+ av_dict_set(&options, "encryption_key", c->key_string, 0);
+ av_dict_set(&options, "encryption_iv", c->iv_string, 0);
+
+ filename_size = strlen(prefix) + strlen(c->dirname) \
+ + strlen(oc->filename) + 1;
+ filename = av_malloc(filename_size);
+ if (!filename)
+ return AVERROR(ENOMEM);
+ av_strlcpy(filename, prefix, filename_size);
+ } else {
+ filename_size = strlen(c->dirname) + strlen(oc->filename) + 1;
+ filename = av_malloc(filename_size);
+ if (!filename)
+ return AVERROR(ENOMEM);
+ *filename = '\0';
+ }
+ av_strlcat(filename, c->dirname, 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;
if (oc->oformat->priv_class && oc->priv_data)
@@ -237,15 +523,13 @@ static int hls_write_header(AVFormatContext *s)
char *p;
const char *pattern = "%d.ts";
AVDictionary *options = NULL;
- int basename_size = strlen(s->filename) + strlen(pattern) + 1;
+ int basename_size;
+ char *basename;
hls->sequence = hls->start_sequence;
hls->recording_time = hls->time * AV_TIME_BASE;
hls->start_pts = AV_NOPTS_VALUE;
- if (hls->flags & HLS_SINGLE_FILE)
- pattern = ".ts";
-
if (hls->format_options_str) {
ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0);
if (ret < 0) {
@@ -270,21 +554,48 @@ static int hls_write_header(AVFormatContext *s)
goto fail;
}
- hls->basename = av_malloc(basename_size);
-
- if (!hls->basename) {
+ hls->dirname = av_strdup(s->filename);
+ if (!hls->dirname) {
ret = AVERROR(ENOMEM);
goto fail;
}
- strcpy(hls->basename, s->filename);
+ basename = (char *)av_basename(hls->dirname);
- p = strrchr(hls->basename, '.');
+ if (hls->segment_filename) {
+ hls->basename = av_strdup(av_basename(hls->segment_filename));
+ if (!hls->basename) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ if (strlen(hls->basename) != strlen(hls->segment_filename)) {
+ av_log(hls, AV_LOG_ERROR, "invalid segment filename %s\n",
+ hls->segment_filename);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ } else {
+ if (hls->flags & HLS_SINGLE_FILE)
+ pattern = ".ts";
- if (p)
- *p = '\0';
+ basename_size = strlen(basename) + strlen(pattern) + 1;
+ hls->basename = av_malloc(basename_size);
+ if (!hls->basename) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ av_strlcpy(hls->basename, basename, basename_size);
+ p = strrchr(hls->basename, '.');
+ if (p)
+ *p = '\0';
+ av_strlcat(hls->basename, pattern, basename_size);
+ }
- av_strlcat(hls->basename, pattern, basename_size);
+ *basename = '\0';
+
+ if ((ret = hls_encryption_init(s) < 0))
+ goto fail;
if ((ret = hls_mux_init(s)) < 0)
goto fail;
@@ -309,6 +620,7 @@ fail:
av_dict_free(&options);
if (ret) {
+ av_free(hls->dirname);
av_free(hls->basename);
if (hls->avf)
avformat_free_context(hls->avf);
@@ -395,24 +707,37 @@ static int hls_write_trailer(struct AVFormatContext *s)
hls->avf = NULL;
hls_window(s, 1);
- hls_free_segments(hls);
+ av_free(hls->dirname);
+ 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);
+
return 0;
}
#define OFFSET(x) offsetof(HLSContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
- {"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E},
- {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
- {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E},
- {"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
- {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
- {"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_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"},
-
+ {"start_number", "set first number in the sequence", OFFSET(start_sequence), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E},
+ {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
+ {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E},
+ {"hls_ts_options", "set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
+ {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
+ {"hls_delete", "delete segment files that are no longer part of the playlist", OFFSET(delete_segments), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E},
+ {"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_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"},
+ {"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 URL and key file path", OFFSET(key_info_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
+ {"hls_random_iv", "randomize initialization vector", OFFSET(random_iv), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E},
{ NULL },
};
--
1.9.3 (Apple Git-50)
More information about the ffmpeg-devel
mailing list