Message ID | 20210224111321.9704-1-nachiket.programmer@gmail.com |
---|---|
State | New |
Headers | show |
Series | [FFmpeg-devel,1/3] libavcodec/adts_header: add frame_length field and avpriv function to parse AAC ADTS header | expand |
Context | Check | Description |
---|---|---|
andriy/x86_make | success | Make finished |
andriy/x86_make_fate | success | Make fate finished |
andriy/PPC64_make | success | Make finished |
andriy/PPC64_make_fate | success | Make fate finished |
@Steven Liu <lq@chinaffmpeg.org> Can we merge this patch ? Best Regards, Nachiket Tarate On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < nachiket.programmer@gmail.com> wrote: > Apple HTTP Live Streaming Sample Encryption: > > > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> > --- > libavformat/Makefile | 2 +- > libavformat/hls.c | 105 ++++++++-- > libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ > libavformat/hls_sample_aes.h | 66 ++++++ > libavformat/mpegts.c | 12 ++ > 5 files changed, 562 insertions(+), 14 deletions(-) > create mode 100644 libavformat/hls_sample_aes.c > create mode 100644 libavformat/hls_sample_aes.h > > diff --git a/libavformat/Makefile b/libavformat/Makefile > index fcb39ce133..62627d50a6 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o > pcm.o > OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o > OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o > OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o > -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o > +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o > OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o > OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o > OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o > diff --git a/libavformat/hls.c b/libavformat/hls.c > index af2468ad9b..3cb3853c79 100644 > --- a/libavformat/hls.c > +++ b/libavformat/hls.c > @@ -2,6 +2,7 @@ > * Apple HTTP Live Streaming demuxer > * Copyright (c) 2010 Martin Storsjo > * Copyright (c) 2013 Anssi Hannula > + * Copyright (c) 2021 Nachiket Tarate > * > * This file is part of FFmpeg. > * > @@ -39,6 +40,8 @@ > #include "avio_internal.h" > #include "id3v2.h" > > +#include "hls_sample_aes.h" > + > #define INITIAL_BUFFER_SIZE 32768 > > #define MAX_FIELD_LEN 64 > @@ -145,6 +148,10 @@ struct playlist { > int id3_changed; /* ID3 tag data has changed at some point */ > ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer > is opened */ > > + /* Used in case of SAMPLE-AES encryption method */ > + HLSAudioSetupInfo audio_setup_info; > + HLSCryptoContext crypto_ctx; > + > int64_t seek_timestamp; > int seek_flags; > int seek_stream_index; /* into subdemuxer stream array */ > @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) > pls->ctx->pb = NULL; > avformat_close_input(&pls->ctx); > } > + if (pls->crypto_ctx.aes_ctx) > + av_free(pls->crypto_ctx.aes_ctx); > av_free(pls); > } > av_freep(&c->playlists); > @@ -994,7 +1003,10 @@ fail: > > static struct segment *current_segment(struct playlist *pls) > { > - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; > + int64_t n = pls->cur_seq_no - pls->start_seq_no; > + if (n >= pls->n_segments) > + return NULL; > + return pls->segments[n]; > } > > static struct segment *next_segment(struct playlist *pls) > @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, > struct segment *seg, > > /* Parse the raw ID3 data and pass contents to caller */ > static void parse_id3(AVFormatContext *s, AVIOContext *pb, > - AVDictionary **metadata, int64_t *dts, > + AVDictionary **metadata, int64_t *dts, > HLSAudioSetupInfo *audio_setup_info, > ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta > **extra_meta) > { > static const char id3_priv_owner_ts[] = > "com.apple.streaming.transportStreamTimestamp"; > + static const char id3_priv_owner_audio_setup[] = > "com.apple.streaming.audioDescription"; > ID3v2ExtraMeta *meta; > > ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); > @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, > AVIOContext *pb, > *dts = ts; > else > av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio > timestamp %"PRId64"\n", ts); > + } else if (priv->datasize >= 8 && !strcmp(priv->owner, > id3_priv_owner_audio_setup)) { > + ff_hls_read_audio_setup_info(audio_setup_info, > priv->data, priv->datasize); > } > } else if (!strcmp(meta->tag, "APIC") && apic) > *apic = &meta->data.apic; > @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct > playlist *pls) > ID3v2ExtraMeta *extra_meta = NULL; > int64_t timestamp = AV_NOPTS_VALUE; > > - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); > + parse_id3(pls->ctx, pb, &metadata, ×tamp, > &pls->audio_setup_info, &apic, &extra_meta); > > if (timestamp != AV_NOPTS_VALUE) { > pls->id3_mpegts_timestamp = timestamp; > @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct > playlist *pls, struct segment *seg, > av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset > %"PRId64", playlist %d\n", > seg->url, seg->url_offset, pls->index); > > - if (seg->key_type == KEY_NONE) { > - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > &is_http); > - } else if (seg->key_type == KEY_AES_128) { > - char iv[33], key[33], url[MAX_URL_SIZE]; > + if (seg->key_type == KEY_AES_128 || seg->key_type == KEY_SAMPLE_AES) { > if (strcmp(seg->key, pls->key_url)) { > AVIOContext *pb = NULL; > if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, opts, > NULL) == 0) { > @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct > playlist *pls, struct segment *seg, > } > av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); > } > + } > + > + if (seg->key_type == KEY_AES_128) { > + char iv[33], key[33], url[MAX_URL_SIZE]; > ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); > ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); > iv[32] = key[32] = '\0'; > @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct > playlist *pls, struct segment *seg, > goto cleanup; > } > ret = 0; > - } else if (seg->key_type == KEY_SAMPLE_AES) { > - av_log(pls->parent, AV_LOG_ERROR, > - "SAMPLE-AES encryption is not supported yet\n"); > - ret = AVERROR_PATCHWELCOME; > + } else { > + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > &is_http); > } > - else > - ret = AVERROR(ENOSYS); > > /* Seek to the requested position. If this was a HTTP request, the > offset > * should already be where want it to, but this allows e.g. local > testing > @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) > struct playlist *pls = c->playlists[i]; > char *url; > ff_const59 AVInputFormat *in_fmt = NULL; > + struct segment *seg = NULL; > > if (!(pls->ctx = avformat_alloc_context())) { > ret = AVERROR(ENOMEM); > @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) > pls->ctx = NULL; > goto fail; > } > + > ffio_init_context(&pls->pb, pls->read_buffer, > INITIAL_BUFFER_SIZE, 0, pls, > read_data, NULL, NULL); > + > + /* > + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of > + * external audio track that contains audio setup information > + */ > + seg = current_segment(pls); > + if (seg && seg->key_type == KEY_SAMPLE_AES && pls->n_renditions > > 0 && > + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { > + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; > + if ((ret = avio_read(&pls->pb, buf, > HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { > + /* Fail if error was not end of file */ > + if (ret != AVERROR_EOF) { > + avformat_free_context(pls->ctx); > + pls->ctx = NULL; > + goto fail; > + } > + } > + ret = 0; > + } > + > + /* > + * If encryption scheme is SAMPLE-AES and audio setup information > is present in external audio track, > + * use that information to find the media format, otherwise probe > input data > + */ > + if (seg && seg->key_type == KEY_SAMPLE_AES && > pls->is_id3_timestamped && > + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { > + void *iter = NULL; > + while ((in_fmt = (ff_const59 AVInputFormat > *)av_demuxer_iterate(&iter))) > + if (in_fmt->raw_codec_id == > pls->audio_setup_info.codec_id) { > + break; > + } > + } else { > pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * 4; > pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? > s->max_analyze_duration : 4 * AV_TIME_BASE; > pls->ctx->interrupt_callback = s->interrupt_callback; > @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) > goto fail; > } > av_free(url); > + } > + > + if (seg && seg->key_type == KEY_SAMPLE_AES) { > + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && > pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && > + strcmp(in_fmt->name, "mpegts")) { > + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not > supported for fragmented MP4 format yet\n"); > + ret = AVERROR_PATCHWELCOME; > + } else { > + pls->crypto_ctx.aes_ctx = av_aes_alloc(); > + if (!pls->crypto_ctx.aes_ctx) > + ret = AVERROR(ENOMEM); > + } > + if (ret != 0) { > + avformat_free_context(pls->ctx); > + pls->ctx = NULL; > + goto fail; > + } > + } > + > pls->ctx->pb = &pls->pb; > pls->ctx->io_open = nested_io_open; > pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; > @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) > * on us if they want to. > */ > if (pls->is_id3_timestamped || (pls->n_renditions > 0 && > pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { > + if (seg && seg->key_type == KEY_SAMPLE_AES && > pls->audio_setup_info.setup_data_length > 0 && > + pls->ctx->nb_streams == 1) > + ret = ff_hls_parse_audio_setup_info(pls->ctx->streams[0], > &pls->audio_setup_info); > + else > ret = avformat_find_stream_info(pls->ctx, NULL); > + > if (ret < 0) > goto fail; > } > @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, > AVPacket *pkt) > while (1) { > int64_t ts_diff; > AVRational tb; > + struct segment *seg = NULL; > ret = av_read_frame(pls->ctx, &pls->pkt); > if (ret < 0) { > if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) > @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, > AVPacket *pkt) > get_timebase(pls), AV_TIME_BASE_Q); > } > > + seg = current_segment(pls); > + if (seg && seg->key_type == KEY_SAMPLE_AES) { > + enum AVCodecID codec_id = > pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; > + memcpy(pls->crypto_ctx.iv, seg->iv, sizeof(seg->iv)); > + memcpy(pls->crypto_ctx.key, pls->key, > sizeof(pls->key)); > + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, > &pls->pkt); > + } > + > if (pls->seek_timestamp == AV_NOPTS_VALUE) > break; > > diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c > new file mode 100644 > index 0000000000..0407a15b0f > --- /dev/null > +++ b/libavformat/hls_sample_aes.c > @@ -0,0 +1,391 @@ > +/* > + * Apple HTTP Live Streaming Sample Encryption/Decryption > + * > + * Copyright (c) 2021 Nachiket Tarate > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA > + */ > + > +/** > + * @file > + * Apple HTTP Live Streaming Sample Encryption > + * > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > + */ > + > +#include "hls_sample_aes.h" > + > +#include "libavcodec/adts_header.h" > +#include "libavcodec/adts_parser.h" > +#include "libavcodec/ac3_parser_internal.h" > + > + > +typedef struct NALUnit { > + uint8_t *data; > + int type; > + int length; > + int start_code_length; > +} NALUnit; > + > +typedef struct AudioFrame { > + uint8_t *data; > + int length; > + int header_length; > +} AudioFrame; > + > +typedef struct CodecParserContext { > + const uint8_t *buf_ptr; > + const uint8_t *buf_end; > +} CodecParserContext; > + > +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; > + > +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t > *buf, size_t size) > +{ > + if (size < 8) > + return; > + > + info->codec_tag = AV_RL32(buf); > + > + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) > + info->codec_id = AV_CODEC_ID_AAC; > + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) > + info->codec_id = AV_CODEC_ID_AC3; > + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) > + info->codec_id = AV_CODEC_ID_EAC3; > + else > + info->codec_id = AV_CODEC_ID_NONE; > + > + buf += 4; > + info->priming = AV_RL16(buf); > + buf += 2; > + info->version = *buf++; > + info->setup_data_length = *buf++; > + > + if (info->setup_data_length > size - 8) > + info->setup_data_length = size - 8; > + > + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) > + return; > + > + memcpy(info->setup_data, buf, info->setup_data_length); > +} > + > +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info) > +{ > + int ret = 0; > + > + st->codecpar->codec_tag = info->codec_tag; > + > + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) > + return 0; > + > + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && > st->codecpar->codec_id != AV_CODEC_ID_EAC3) > + return AVERROR_INVALIDDATA; > + > + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { > + > + AC3HeaderInfo *ac3hdr = NULL; > + > + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, > info->setup_data_length); > + if (ret < 0) { > + if (ret != AVERROR(ENOMEM)) > + av_free(ac3hdr); > + return ret; > + } > + > + st->codecpar->sample_rate = ac3hdr->sample_rate; > + st->codecpar->channels = ac3hdr->channels; > + st->codecpar->channel_layout = ac3hdr->channel_layout; > + st->codecpar->bit_rate = ac3hdr->bit_rate; > + > + av_free(ac3hdr); > + } else { /* Parse 'dec3' EC3SpecificBox */ > + > + GetBitContext gb; > + int data_rate, fscod, acmod, lfeon; > + > + ret = init_get_bits8(&gb, info->setup_data, > info->setup_data_length); > + if (ret < 0) > + return AVERROR_INVALIDDATA; > + > + data_rate = get_bits(&gb, 13); > + skip_bits(&gb, 3); > + fscod = get_bits(&gb, 2); > + skip_bits(&gb, 10); > + acmod = get_bits(&gb, 3); > + lfeon = get_bits(&gb, 1); > + > + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; > + > + st->codecpar->channel_layout = > avpriv_ac3_channel_layout_tab[acmod]; > + if (lfeon) > + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; > + > + st->codecpar->channels = > av_get_channel_layout_nb_channels(st->codecpar->channel_layout); > + > + st->codecpar->bit_rate = data_rate*1000; > + } > + > + return 0; > +} > + > +/* > + * Remove start code emulation prevention 0x03 bytes > + */ > +static void remove_scep_3_bytes(NALUnit *nalu) > +{ > + int i = 0; > + int j = 0; > + > + uint8_t *data = nalu->data; > + > + while (i < nalu->length) { > + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { > + data[j++] = data[i++]; > + data[j++] = data[i++]; > + i++; > + } else { > + data[j++] = data[i++]; > + } > + } > + > + nalu->length = j; > +} > + > +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) > +{ > + const uint8_t *nalu_start = ctx->buf_ptr; > + > + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > 0x00000001) > + nalu->start_code_length = 4; > + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) == > 0x000001) > + nalu->start_code_length = 3; > + else /* No start code at the beginning of the NAL unit */ > + return -1; > + > + ctx->buf_ptr += nalu->start_code_length; > + > + while (ctx->buf_ptr < ctx->buf_end) { > + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > 0x00000001) > + break; > + else if (ctx->buf_end - ctx->buf_ptr >= 3 && > AV_RB24(ctx->buf_ptr) == 0x000001) > + break; > + ctx->buf_ptr++; > + } > + > + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; > + nalu->length = ctx->buf_ptr - nalu->data; > + nalu->type = *nalu->data & 0x1F; > + > + return 0; > +} > + > +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit *nalu) > +{ > + int ret = 0; > + int rem_bytes; > + uint8_t *data; > + uint8_t iv[16]; > + > + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > + if (ret < 0) > + return ret; > + > + /* Remove start code emulation prevention 0x03 bytes */ > + remove_scep_3_bytes(nalu); > + > + data = nalu->data + 32; > + rem_bytes = nalu->length - 32; > + > + memcpy(iv, crypto_ctx->iv, 16); > + > + while (rem_bytes > 0) { > + if (rem_bytes > 16) { > + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); > + data += 16; > + rem_bytes -= 16; > + } > + data += FFMIN(144, rem_bytes); > + rem_bytes -= FFMIN(144, rem_bytes); > + } > + > + return 0; > +} > + > +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket > *pkt) > +{ > + int ret = 0; > + CodecParserContext ctx; > + NALUnit nalu; > + uint8_t *data_ptr; > + int move_nalu = 0; > + > + memset(&ctx, 0, sizeof(ctx)); > + ctx.buf_ptr = pkt->data; > + ctx.buf_end = pkt->data + pkt->size; > + > + data_ptr = pkt->data; > + > + while (ctx.buf_ptr < ctx.buf_end) { > + memset(&nalu, 0, sizeof(nalu)); > + ret = get_next_nal_unit(&ctx, &nalu); > + if (ret < 0) > + return ret; > + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > 48) > { > + int encrypted_nalu_length = nalu.length; > + ret = decrypt_nal_unit(crypto_ctx, &nalu); > + if (ret < 0) > + return ret; > + move_nalu = nalu.length != encrypted_nalu_length; > + } > + if (move_nalu) > + memmove(data_ptr, nalu.data - nalu.start_code_length, > nalu.start_code_length + nalu.length); > + data_ptr += nalu.start_code_length + nalu.length; > + } > + > + av_shrink_packet(pkt, data_ptr - pkt->data); > + > + return 0; > +} > + > +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame *frame) > +{ > + int ret = 0; > + > + AACADTSHeaderInfo *adts_hdr = NULL; > + > + /* Find next sync word 0xFFF */ > + while (ctx->buf_ptr < ctx->buf_end - 1) { > + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == 0xF0) > + break; > + ctx->buf_ptr++; > + } > + > + if (ctx->buf_ptr >= ctx->buf_end - 1) > + return -1; > + > + frame->data = (uint8_t*)ctx->buf_ptr; > + > + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, ctx->buf_end > - frame->data); > + if (ret < 0) > + return ret; > + > + frame->header_length = adts_hdr->crc_absent ? AV_AAC_ADTS_HEADER_SIZE > : AV_AAC_ADTS_HEADER_SIZE + 2; > + frame->length = adts_hdr->frame_length; > + > + av_free(adts_hdr); > + > + return 0; > +} > + > +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, > AudioFrame *frame) > +{ > + int ret = 0; > + > + AC3HeaderInfo *hdr = NULL; > + > + /* Find next sync word 0x0B77 */ > + while (ctx->buf_ptr < ctx->buf_end - 1) { > + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) > + break; > + ctx->buf_ptr++; > + } > + > + if (ctx->buf_ptr >= ctx->buf_end - 1) > + return -1; > + > + frame->data = (uint8_t*)ctx->buf_ptr; > + frame->header_length = 0; > + > + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - > frame->data); > + if (ret < 0) { > + if (ret != AVERROR(ENOMEM)) > + av_free(hdr); > + return ret; > + } > + > + frame->length = hdr->frame_size; > + > + av_free(hdr); > + > + return 0; > +} > + > +static int get_next_sync_frame(enum AVCodecID codec_id, > CodecParserContext *ctx, AudioFrame *frame) > +{ > + if (codec_id == AV_CODEC_ID_AAC) > + return get_next_adts_frame(ctx, frame); > + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == AV_CODEC_ID_EAC3) > + return get_next_ac3_eac3_sync_frame(ctx, frame); > + else > + return AVERROR_INVALIDDATA; > +} > + > +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext > *crypto_ctx, AudioFrame *frame) > +{ > + int ret = 0; > + uint8_t *data; > + int num_of_encrypted_blocks; > + > + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > + if (ret < 0) > + return ret; > + > + data = frame->data + frame->header_length + 16; > + > + num_of_encrypted_blocks = (frame->length - frame->header_length - > 16)/16; > + > + av_aes_crypt(crypto_ctx->aes_ctx, data, data, > num_of_encrypted_blocks, crypto_ctx->iv, 1); > + > + return 0; > +} > + > +static int decrypt_audio_frame(enum AVCodecID codec_id, HLSCryptoContext > *crypto_ctx, AVPacket *pkt) > +{ > + int ret = 0; > + CodecParserContext ctx; > + AudioFrame frame; > + > + memset(&ctx, 0, sizeof(ctx)); > + ctx.buf_ptr = pkt->data; > + ctx.buf_end = pkt->data + pkt->size; > + > + while (ctx.buf_ptr < ctx.buf_end) { > + memset(&frame, 0, sizeof(frame)); > + ret = get_next_sync_frame(codec_id, &ctx, &frame); > + if (ret < 0) > + return ret; > + if (frame.length - frame.header_length > 31) { > + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); > + if (ret < 0) > + return ret; > + } > + ctx.buf_ptr += frame.length; > + } > + > + return 0; > +} > + > +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > *crypto_ctx, AVPacket *pkt) > +{ > + if (codec_id == AV_CODEC_ID_H264) > + return decrypt_video_frame(crypto_ctx, pkt); > + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 > || codec_id == AV_CODEC_ID_EAC3) > + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); > + > + return AVERROR_INVALIDDATA; > +} > diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h > new file mode 100644 > index 0000000000..cf80e41cb0 > --- /dev/null > +++ b/libavformat/hls_sample_aes.h > @@ -0,0 +1,66 @@ > +/* > + * Apple HTTP Live Streaming Sample Encryption/Decryption > + * > + * Copyright (c) 2021 Nachiket Tarate > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA > + */ > + > +/** > + * @file > + * Apple HTTP Live Streaming Sample Encryption > + * > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > + */ > + > +#ifndef AVFORMAT_HLS_SAMPLE_AES_H > +#define AVFORMAT_HLS_SAMPLE_AES_H > + > +#include <stdint.h> > + > +#include "avformat.h" > + > +#include "libavcodec/avcodec.h" > +#include "libavutil/aes.h" > + > +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 > +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 > + > + > +typedef struct HLSCryptoContext { > + struct AVAES *aes_ctx; > + uint8_t key[16]; > + uint8_t iv[16]; > +} HLSCryptoContext; > + > +typedef struct HLSAudioSetupInfo { > + enum AVCodecID codec_id; > + uint32_t codec_tag; > + uint16_t priming; > + uint8_t version; > + uint8_t setup_data_length; > + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; > +} HLSAudioSetupInfo; > + > + > +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t > *buf, size_t size); > + > +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info); > + > +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > *crypto_ctx, AVPacket *pkt); > + > +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ > + > diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c > index e283ec09d7..dc611ae788 100644 > --- a/libavformat/mpegts.c > +++ b/libavformat/mpegts.c > @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { > { 0 }, > }; > > +/* HLS Sample Encryption Types */ > +static const StreamType HLS_SAMPLE_ENC_types[] = { > + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, > + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, > + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, > + { 0 }, > +}; > + > + > static const StreamType REGD_types[] = { > { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC }, > { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, > PESContext *pes, > } > if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > mpegts_find_stream_type(st, pes->stream_type, MISC_types); > + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > + mpegts_find_stream_type(st, pes->stream_type, > HLS_SAMPLE_ENC_types); > if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { > st->codecpar->codec_id = old_codec_id; > st->codecpar->codec_type = old_codec_type; > -- > 2.17.1 > >
> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > @Steven Liu <lq@chinaffmpeg.org> > > Can we merge this patch ? I’m waiting update patch for fragment mp4 encryption. After new version of the patchset I will test and review. > > Best Regards, > Nachiket Tarate > > On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < > nachiket.programmer@gmail.com> wrote: > >> Apple HTTP Live Streaming Sample Encryption: >> >> >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >> >> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> >> --- >> libavformat/Makefile | 2 +- >> libavformat/hls.c | 105 ++++++++-- >> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ >> libavformat/hls_sample_aes.h | 66 ++++++ >> libavformat/mpegts.c | 12 ++ >> 5 files changed, 562 insertions(+), 14 deletions(-) >> create mode 100644 libavformat/hls_sample_aes.c >> create mode 100644 libavformat/hls_sample_aes.h >> >> diff --git a/libavformat/Makefile b/libavformat/Makefile >> index fcb39ce133..62627d50a6 100644 >> --- a/libavformat/Makefile >> +++ b/libavformat/Makefile >> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o >> pcm.o >> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o >> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o >> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o >> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o >> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o >> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o >> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o >> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o >> diff --git a/libavformat/hls.c b/libavformat/hls.c >> index af2468ad9b..3cb3853c79 100644 >> --- a/libavformat/hls.c >> +++ b/libavformat/hls.c >> @@ -2,6 +2,7 @@ >> * Apple HTTP Live Streaming demuxer >> * Copyright (c) 2010 Martin Storsjo >> * Copyright (c) 2013 Anssi Hannula >> + * Copyright (c) 2021 Nachiket Tarate >> * >> * This file is part of FFmpeg. >> * >> @@ -39,6 +40,8 @@ >> #include "avio_internal.h" >> #include "id3v2.h" >> >> +#include "hls_sample_aes.h" >> + >> #define INITIAL_BUFFER_SIZE 32768 >> >> #define MAX_FIELD_LEN 64 >> @@ -145,6 +148,10 @@ struct playlist { >> int id3_changed; /* ID3 tag data has changed at some point */ >> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer >> is opened */ >> >> + /* Used in case of SAMPLE-AES encryption method */ >> + HLSAudioSetupInfo audio_setup_info; >> + HLSCryptoContext crypto_ctx; >> + >> int64_t seek_timestamp; >> int seek_flags; >> int seek_stream_index; /* into subdemuxer stream array */ >> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) >> pls->ctx->pb = NULL; >> avformat_close_input(&pls->ctx); >> } >> + if (pls->crypto_ctx.aes_ctx) >> + av_free(pls->crypto_ctx.aes_ctx); >> av_free(pls); >> } >> av_freep(&c->playlists); >> @@ -994,7 +1003,10 @@ fail: >> >> static struct segment *current_segment(struct playlist *pls) >> { >> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; >> + int64_t n = pls->cur_seq_no - pls->start_seq_no; >> + if (n >= pls->n_segments) >> + return NULL; >> + return pls->segments[n]; >> } >> >> static struct segment *next_segment(struct playlist *pls) >> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, >> struct segment *seg, >> >> /* Parse the raw ID3 data and pass contents to caller */ >> static void parse_id3(AVFormatContext *s, AVIOContext *pb, >> - AVDictionary **metadata, int64_t *dts, >> + AVDictionary **metadata, int64_t *dts, >> HLSAudioSetupInfo *audio_setup_info, >> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta >> **extra_meta) >> { >> static const char id3_priv_owner_ts[] = >> "com.apple.streaming.transportStreamTimestamp"; >> + static const char id3_priv_owner_audio_setup[] = >> "com.apple.streaming.audioDescription"; >> ID3v2ExtraMeta *meta; >> >> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); >> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, >> AVIOContext *pb, >> *dts = ts; >> else >> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio >> timestamp %"PRId64"\n", ts); >> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, >> id3_priv_owner_audio_setup)) { >> + ff_hls_read_audio_setup_info(audio_setup_info, >> priv->data, priv->datasize); >> } >> } else if (!strcmp(meta->tag, "APIC") && apic) >> *apic = &meta->data.apic; >> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct >> playlist *pls) >> ID3v2ExtraMeta *extra_meta = NULL; >> int64_t timestamp = AV_NOPTS_VALUE; >> >> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); >> + parse_id3(pls->ctx, pb, &metadata, ×tamp, >> &pls->audio_setup_info, &apic, &extra_meta); >> >> if (timestamp != AV_NOPTS_VALUE) { >> pls->id3_mpegts_timestamp = timestamp; >> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct >> playlist *pls, struct segment *seg, >> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset >> %"PRId64", playlist %d\n", >> seg->url, seg->url_offset, pls->index); >> >> - if (seg->key_type == KEY_NONE) { >> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >> &is_http); >> - } else if (seg->key_type == KEY_AES_128) { >> - char iv[33], key[33], url[MAX_URL_SIZE]; >> + if (seg->key_type == KEY_AES_128 || seg->key_type == KEY_SAMPLE_AES) { >> if (strcmp(seg->key, pls->key_url)) { >> AVIOContext *pb = NULL; >> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, opts, >> NULL) == 0) { >> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct >> playlist *pls, struct segment *seg, >> } >> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); >> } >> + } >> + >> + if (seg->key_type == KEY_AES_128) { >> + char iv[33], key[33], url[MAX_URL_SIZE]; >> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); >> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); >> iv[32] = key[32] = '\0'; >> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct >> playlist *pls, struct segment *seg, >> goto cleanup; >> } >> ret = 0; >> - } else if (seg->key_type == KEY_SAMPLE_AES) { >> - av_log(pls->parent, AV_LOG_ERROR, >> - "SAMPLE-AES encryption is not supported yet\n"); >> - ret = AVERROR_PATCHWELCOME; >> + } else { >> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >> &is_http); >> } >> - else >> - ret = AVERROR(ENOSYS); >> >> /* Seek to the requested position. If this was a HTTP request, the >> offset >> * should already be where want it to, but this allows e.g. local >> testing >> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) >> struct playlist *pls = c->playlists[i]; >> char *url; >> ff_const59 AVInputFormat *in_fmt = NULL; >> + struct segment *seg = NULL; >> >> if (!(pls->ctx = avformat_alloc_context())) { >> ret = AVERROR(ENOMEM); >> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) >> pls->ctx = NULL; >> goto fail; >> } >> + >> ffio_init_context(&pls->pb, pls->read_buffer, >> INITIAL_BUFFER_SIZE, 0, pls, >> read_data, NULL, NULL); >> + >> + /* >> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of >> + * external audio track that contains audio setup information >> + */ >> + seg = current_segment(pls); >> + if (seg && seg->key_type == KEY_SAMPLE_AES && pls->n_renditions > >> 0 && >> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { >> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; >> + if ((ret = avio_read(&pls->pb, buf, >> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { >> + /* Fail if error was not end of file */ >> + if (ret != AVERROR_EOF) { >> + avformat_free_context(pls->ctx); >> + pls->ctx = NULL; >> + goto fail; >> + } >> + } >> + ret = 0; >> + } >> + >> + /* >> + * If encryption scheme is SAMPLE-AES and audio setup information >> is present in external audio track, >> + * use that information to find the media format, otherwise probe >> input data >> + */ >> + if (seg && seg->key_type == KEY_SAMPLE_AES && >> pls->is_id3_timestamped && >> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { >> + void *iter = NULL; >> + while ((in_fmt = (ff_const59 AVInputFormat >> *)av_demuxer_iterate(&iter))) >> + if (in_fmt->raw_codec_id == >> pls->audio_setup_info.codec_id) { >> + break; >> + } >> + } else { >> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * 4; >> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? >> s->max_analyze_duration : 4 * AV_TIME_BASE; >> pls->ctx->interrupt_callback = s->interrupt_callback; >> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) >> goto fail; >> } >> av_free(url); >> + } >> + >> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && >> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && >> + strcmp(in_fmt->name, "mpegts")) { >> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not >> supported for fragmented MP4 format yet\n"); >> + ret = AVERROR_PATCHWELCOME; >> + } else { >> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); >> + if (!pls->crypto_ctx.aes_ctx) >> + ret = AVERROR(ENOMEM); >> + } >> + if (ret != 0) { >> + avformat_free_context(pls->ctx); >> + pls->ctx = NULL; >> + goto fail; >> + } >> + } >> + >> pls->ctx->pb = &pls->pb; >> pls->ctx->io_open = nested_io_open; >> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; >> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) >> * on us if they want to. >> */ >> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && >> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { >> + if (seg && seg->key_type == KEY_SAMPLE_AES && >> pls->audio_setup_info.setup_data_length > 0 && >> + pls->ctx->nb_streams == 1) >> + ret = ff_hls_parse_audio_setup_info(pls->ctx->streams[0], >> &pls->audio_setup_info); >> + else >> ret = avformat_find_stream_info(pls->ctx, NULL); >> + >> if (ret < 0) >> goto fail; >> } >> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, >> AVPacket *pkt) >> while (1) { >> int64_t ts_diff; >> AVRational tb; >> + struct segment *seg = NULL; >> ret = av_read_frame(pls->ctx, &pls->pkt); >> if (ret < 0) { >> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) >> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, >> AVPacket *pkt) >> get_timebase(pls), AV_TIME_BASE_Q); >> } >> >> + seg = current_segment(pls); >> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >> + enum AVCodecID codec_id = >> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; >> + memcpy(pls->crypto_ctx.iv, seg->iv, sizeof(seg->iv)); >> + memcpy(pls->crypto_ctx.key, pls->key, >> sizeof(pls->key)); >> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, >> &pls->pkt); >> + } >> + >> if (pls->seek_timestamp == AV_NOPTS_VALUE) >> break; >> >> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c >> new file mode 100644 >> index 0000000000..0407a15b0f >> --- /dev/null >> +++ b/libavformat/hls_sample_aes.c >> @@ -0,0 +1,391 @@ >> +/* >> + * Apple HTTP Live Streaming Sample Encryption/Decryption >> + * >> + * Copyright (c) 2021 Nachiket Tarate >> + * >> + * This file is part of FFmpeg. >> + * >> + * FFmpeg is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU Lesser General Public >> + * License as published by the Free Software Foundation; either >> + * version 2.1 of the License, or (at your option) any later version. >> + * >> + * FFmpeg is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * Lesser General Public License for more details. >> + * >> + * You should have received a copy of the GNU Lesser General Public >> + * License along with FFmpeg; if not, write to the Free Software >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >> 02110-1301 USA >> + */ >> + >> +/** >> + * @file >> + * Apple HTTP Live Streaming Sample Encryption >> + * >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >> + */ >> + >> +#include "hls_sample_aes.h" >> + >> +#include "libavcodec/adts_header.h" >> +#include "libavcodec/adts_parser.h" >> +#include "libavcodec/ac3_parser_internal.h" >> + >> + >> +typedef struct NALUnit { >> + uint8_t *data; >> + int type; >> + int length; >> + int start_code_length; >> +} NALUnit; >> + >> +typedef struct AudioFrame { >> + uint8_t *data; >> + int length; >> + int header_length; >> +} AudioFrame; >> + >> +typedef struct CodecParserContext { >> + const uint8_t *buf_ptr; >> + const uint8_t *buf_end; >> +} CodecParserContext; >> + >> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; >> + >> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t >> *buf, size_t size) >> +{ >> + if (size < 8) >> + return; >> + >> + info->codec_tag = AV_RL32(buf); >> + >> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) >> + info->codec_id = AV_CODEC_ID_AAC; >> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) >> + info->codec_id = AV_CODEC_ID_AC3; >> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) >> + info->codec_id = AV_CODEC_ID_EAC3; >> + else >> + info->codec_id = AV_CODEC_ID_NONE; >> + >> + buf += 4; >> + info->priming = AV_RL16(buf); >> + buf += 2; >> + info->version = *buf++; >> + info->setup_data_length = *buf++; >> + >> + if (info->setup_data_length > size - 8) >> + info->setup_data_length = size - 8; >> + >> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) >> + return; >> + >> + memcpy(info->setup_data, buf, info->setup_data_length); >> +} >> + >> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info) >> +{ >> + int ret = 0; >> + >> + st->codecpar->codec_tag = info->codec_tag; >> + >> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) >> + return 0; >> + >> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && >> st->codecpar->codec_id != AV_CODEC_ID_EAC3) >> + return AVERROR_INVALIDDATA; >> + >> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { >> + >> + AC3HeaderInfo *ac3hdr = NULL; >> + >> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, >> info->setup_data_length); >> + if (ret < 0) { >> + if (ret != AVERROR(ENOMEM)) >> + av_free(ac3hdr); >> + return ret; >> + } >> + >> + st->codecpar->sample_rate = ac3hdr->sample_rate; >> + st->codecpar->channels = ac3hdr->channels; >> + st->codecpar->channel_layout = ac3hdr->channel_layout; >> + st->codecpar->bit_rate = ac3hdr->bit_rate; >> + >> + av_free(ac3hdr); >> + } else { /* Parse 'dec3' EC3SpecificBox */ >> + >> + GetBitContext gb; >> + int data_rate, fscod, acmod, lfeon; >> + >> + ret = init_get_bits8(&gb, info->setup_data, >> info->setup_data_length); >> + if (ret < 0) >> + return AVERROR_INVALIDDATA; >> + >> + data_rate = get_bits(&gb, 13); >> + skip_bits(&gb, 3); >> + fscod = get_bits(&gb, 2); >> + skip_bits(&gb, 10); >> + acmod = get_bits(&gb, 3); >> + lfeon = get_bits(&gb, 1); >> + >> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; >> + >> + st->codecpar->channel_layout = >> avpriv_ac3_channel_layout_tab[acmod]; >> + if (lfeon) >> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; >> + >> + st->codecpar->channels = >> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); >> + >> + st->codecpar->bit_rate = data_rate*1000; >> + } >> + >> + return 0; >> +} >> + >> +/* >> + * Remove start code emulation prevention 0x03 bytes >> + */ >> +static void remove_scep_3_bytes(NALUnit *nalu) >> +{ >> + int i = 0; >> + int j = 0; >> + >> + uint8_t *data = nalu->data; >> + >> + while (i < nalu->length) { >> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { >> + data[j++] = data[i++]; >> + data[j++] = data[i++]; >> + i++; >> + } else { >> + data[j++] = data[i++]; >> + } >> + } >> + >> + nalu->length = j; >> +} >> + >> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) >> +{ >> + const uint8_t *nalu_start = ctx->buf_ptr; >> + >> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == >> 0x00000001) >> + nalu->start_code_length = 4; >> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) == >> 0x000001) >> + nalu->start_code_length = 3; >> + else /* No start code at the beginning of the NAL unit */ >> + return -1; >> + >> + ctx->buf_ptr += nalu->start_code_length; >> + >> + while (ctx->buf_ptr < ctx->buf_end) { >> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == >> 0x00000001) >> + break; >> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && >> AV_RB24(ctx->buf_ptr) == 0x000001) >> + break; >> + ctx->buf_ptr++; >> + } >> + >> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; >> + nalu->length = ctx->buf_ptr - nalu->data; >> + nalu->type = *nalu->data & 0x1F; >> + >> + return 0; >> +} >> + >> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit *nalu) >> +{ >> + int ret = 0; >> + int rem_bytes; >> + uint8_t *data; >> + uint8_t iv[16]; >> + >> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >> + if (ret < 0) >> + return ret; >> + >> + /* Remove start code emulation prevention 0x03 bytes */ >> + remove_scep_3_bytes(nalu); >> + >> + data = nalu->data + 32; >> + rem_bytes = nalu->length - 32; >> + >> + memcpy(iv, crypto_ctx->iv, 16); >> + >> + while (rem_bytes > 0) { >> + if (rem_bytes > 16) { >> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); >> + data += 16; >> + rem_bytes -= 16; >> + } >> + data += FFMIN(144, rem_bytes); >> + rem_bytes -= FFMIN(144, rem_bytes); >> + } >> + >> + return 0; >> +} >> + >> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket >> *pkt) >> +{ >> + int ret = 0; >> + CodecParserContext ctx; >> + NALUnit nalu; >> + uint8_t *data_ptr; >> + int move_nalu = 0; >> + >> + memset(&ctx, 0, sizeof(ctx)); >> + ctx.buf_ptr = pkt->data; >> + ctx.buf_end = pkt->data + pkt->size; >> + >> + data_ptr = pkt->data; >> + >> + while (ctx.buf_ptr < ctx.buf_end) { >> + memset(&nalu, 0, sizeof(nalu)); >> + ret = get_next_nal_unit(&ctx, &nalu); >> + if (ret < 0) >> + return ret; >> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > 48) >> { >> + int encrypted_nalu_length = nalu.length; >> + ret = decrypt_nal_unit(crypto_ctx, &nalu); >> + if (ret < 0) >> + return ret; >> + move_nalu = nalu.length != encrypted_nalu_length; >> + } >> + if (move_nalu) >> + memmove(data_ptr, nalu.data - nalu.start_code_length, >> nalu.start_code_length + nalu.length); >> + data_ptr += nalu.start_code_length + nalu.length; >> + } >> + >> + av_shrink_packet(pkt, data_ptr - pkt->data); >> + >> + return 0; >> +} >> + >> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame *frame) >> +{ >> + int ret = 0; >> + >> + AACADTSHeaderInfo *adts_hdr = NULL; >> + >> + /* Find next sync word 0xFFF */ >> + while (ctx->buf_ptr < ctx->buf_end - 1) { >> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == 0xF0) >> + break; >> + ctx->buf_ptr++; >> + } >> + >> + if (ctx->buf_ptr >= ctx->buf_end - 1) >> + return -1; >> + >> + frame->data = (uint8_t*)ctx->buf_ptr; >> + >> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, ctx->buf_end >> - frame->data); >> + if (ret < 0) >> + return ret; >> + >> + frame->header_length = adts_hdr->crc_absent ? AV_AAC_ADTS_HEADER_SIZE >> : AV_AAC_ADTS_HEADER_SIZE + 2; >> + frame->length = adts_hdr->frame_length; >> + >> + av_free(adts_hdr); >> + >> + return 0; >> +} >> + >> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, >> AudioFrame *frame) >> +{ >> + int ret = 0; >> + >> + AC3HeaderInfo *hdr = NULL; >> + >> + /* Find next sync word 0x0B77 */ >> + while (ctx->buf_ptr < ctx->buf_end - 1) { >> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) >> + break; >> + ctx->buf_ptr++; >> + } >> + >> + if (ctx->buf_ptr >= ctx->buf_end - 1) >> + return -1; >> + >> + frame->data = (uint8_t*)ctx->buf_ptr; >> + frame->header_length = 0; >> + >> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - >> frame->data); >> + if (ret < 0) { >> + if (ret != AVERROR(ENOMEM)) >> + av_free(hdr); >> + return ret; >> + } >> + >> + frame->length = hdr->frame_size; >> + >> + av_free(hdr); >> + >> + return 0; >> +} >> + >> +static int get_next_sync_frame(enum AVCodecID codec_id, >> CodecParserContext *ctx, AudioFrame *frame) >> +{ >> + if (codec_id == AV_CODEC_ID_AAC) >> + return get_next_adts_frame(ctx, frame); >> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == AV_CODEC_ID_EAC3) >> + return get_next_ac3_eac3_sync_frame(ctx, frame); >> + else >> + return AVERROR_INVALIDDATA; >> +} >> + >> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext >> *crypto_ctx, AudioFrame *frame) >> +{ >> + int ret = 0; >> + uint8_t *data; >> + int num_of_encrypted_blocks; >> + >> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >> + if (ret < 0) >> + return ret; >> + >> + data = frame->data + frame->header_length + 16; >> + >> + num_of_encrypted_blocks = (frame->length - frame->header_length - >> 16)/16; >> + >> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, >> num_of_encrypted_blocks, crypto_ctx->iv, 1); >> + >> + return 0; >> +} >> + >> +static int decrypt_audio_frame(enum AVCodecID codec_id, HLSCryptoContext >> *crypto_ctx, AVPacket *pkt) >> +{ >> + int ret = 0; >> + CodecParserContext ctx; >> + AudioFrame frame; >> + >> + memset(&ctx, 0, sizeof(ctx)); >> + ctx.buf_ptr = pkt->data; >> + ctx.buf_end = pkt->data + pkt->size; >> + >> + while (ctx.buf_ptr < ctx.buf_end) { >> + memset(&frame, 0, sizeof(frame)); >> + ret = get_next_sync_frame(codec_id, &ctx, &frame); >> + if (ret < 0) >> + return ret; >> + if (frame.length - frame.header_length > 31) { >> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); >> + if (ret < 0) >> + return ret; >> + } >> + ctx.buf_ptr += frame.length; >> + } >> + >> + return 0; >> +} >> + >> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >> *crypto_ctx, AVPacket *pkt) >> +{ >> + if (codec_id == AV_CODEC_ID_H264) >> + return decrypt_video_frame(crypto_ctx, pkt); >> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 >> || codec_id == AV_CODEC_ID_EAC3) >> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); >> + >> + return AVERROR_INVALIDDATA; >> +} >> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h >> new file mode 100644 >> index 0000000000..cf80e41cb0 >> --- /dev/null >> +++ b/libavformat/hls_sample_aes.h >> @@ -0,0 +1,66 @@ >> +/* >> + * Apple HTTP Live Streaming Sample Encryption/Decryption >> + * >> + * Copyright (c) 2021 Nachiket Tarate >> + * >> + * This file is part of FFmpeg. >> + * >> + * FFmpeg is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU Lesser General Public >> + * License as published by the Free Software Foundation; either >> + * version 2.1 of the License, or (at your option) any later version. >> + * >> + * FFmpeg is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * Lesser General Public License for more details. >> + * >> + * You should have received a copy of the GNU Lesser General Public >> + * License along with FFmpeg; if not, write to the Free Software >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >> 02110-1301 USA >> + */ >> + >> +/** >> + * @file >> + * Apple HTTP Live Streaming Sample Encryption >> + * >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >> + */ >> + >> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H >> +#define AVFORMAT_HLS_SAMPLE_AES_H >> + >> +#include <stdint.h> >> + >> +#include "avformat.h" >> + >> +#include "libavcodec/avcodec.h" >> +#include "libavutil/aes.h" >> + >> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 >> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 >> + >> + >> +typedef struct HLSCryptoContext { >> + struct AVAES *aes_ctx; >> + uint8_t key[16]; >> + uint8_t iv[16]; >> +} HLSCryptoContext; >> + >> +typedef struct HLSAudioSetupInfo { >> + enum AVCodecID codec_id; >> + uint32_t codec_tag; >> + uint16_t priming; >> + uint8_t version; >> + uint8_t setup_data_length; >> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; >> +} HLSAudioSetupInfo; >> + >> + >> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t >> *buf, size_t size); >> + >> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info); >> + >> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >> *crypto_ctx, AVPacket *pkt); >> + >> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ >> + >> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c >> index e283ec09d7..dc611ae788 100644 >> --- a/libavformat/mpegts.c >> +++ b/libavformat/mpegts.c >> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { >> { 0 }, >> }; >> >> +/* HLS Sample Encryption Types */ >> +static const StreamType HLS_SAMPLE_ENC_types[] = { >> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, >> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, >> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, >> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, >> + { 0 }, >> +}; >> + >> + >> static const StreamType REGD_types[] = { >> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC }, >> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, >> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, >> PESContext *pes, >> } >> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >> mpegts_find_stream_type(st, pes->stream_type, MISC_types); >> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >> + mpegts_find_stream_type(st, pes->stream_type, >> HLS_SAMPLE_ENC_types); >> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { >> st->codecpar->codec_id = old_codec_id; >> st->codecpar->codec_type = old_codec_type; >> -- >> 2.17.1 >> >> > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". Thanks Steven Liu
This is an updated version of the patch in which I have added the check. If the segments are in Fragmented MP4 format, HLS demuxer quits by giving an error message: "SAMPLE-AES encryption is not supported for fragmented MP4 format yet" Best Regards, Nachiket Tarate On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: > > > > 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > > > @Steven Liu <lq@chinaffmpeg.org> > > > > Can we merge this patch ? > I’m waiting update patch for fragment mp4 encryption. > After new version of the patchset I will test and review. > > > > Best Regards, > > Nachiket Tarate > > > > On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < > > nachiket.programmer@gmail.com> wrote: > > > >> Apple HTTP Live Streaming Sample Encryption: > >> > >> > >> > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >> > >> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> > >> --- > >> libavformat/Makefile | 2 +- > >> libavformat/hls.c | 105 ++++++++-- > >> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ > >> libavformat/hls_sample_aes.h | 66 ++++++ > >> libavformat/mpegts.c | 12 ++ > >> 5 files changed, 562 insertions(+), 14 deletions(-) > >> create mode 100644 libavformat/hls_sample_aes.c > >> create mode 100644 libavformat/hls_sample_aes.h > >> > >> diff --git a/libavformat/Makefile b/libavformat/Makefile > >> index fcb39ce133..62627d50a6 100644 > >> --- a/libavformat/Makefile > >> +++ b/libavformat/Makefile > >> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o > >> pcm.o > >> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o > >> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o > >> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o > >> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o > >> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o > >> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o > >> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o > >> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o > >> diff --git a/libavformat/hls.c b/libavformat/hls.c > >> index af2468ad9b..3cb3853c79 100644 > >> --- a/libavformat/hls.c > >> +++ b/libavformat/hls.c > >> @@ -2,6 +2,7 @@ > >> * Apple HTTP Live Streaming demuxer > >> * Copyright (c) 2010 Martin Storsjo > >> * Copyright (c) 2013 Anssi Hannula > >> + * Copyright (c) 2021 Nachiket Tarate > >> * > >> * This file is part of FFmpeg. > >> * > >> @@ -39,6 +40,8 @@ > >> #include "avio_internal.h" > >> #include "id3v2.h" > >> > >> +#include "hls_sample_aes.h" > >> + > >> #define INITIAL_BUFFER_SIZE 32768 > >> > >> #define MAX_FIELD_LEN 64 > >> @@ -145,6 +148,10 @@ struct playlist { > >> int id3_changed; /* ID3 tag data has changed at some point */ > >> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer > >> is opened */ > >> > >> + /* Used in case of SAMPLE-AES encryption method */ > >> + HLSAudioSetupInfo audio_setup_info; > >> + HLSCryptoContext crypto_ctx; > >> + > >> int64_t seek_timestamp; > >> int seek_flags; > >> int seek_stream_index; /* into subdemuxer stream array */ > >> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) > >> pls->ctx->pb = NULL; > >> avformat_close_input(&pls->ctx); > >> } > >> + if (pls->crypto_ctx.aes_ctx) > >> + av_free(pls->crypto_ctx.aes_ctx); > >> av_free(pls); > >> } > >> av_freep(&c->playlists); > >> @@ -994,7 +1003,10 @@ fail: > >> > >> static struct segment *current_segment(struct playlist *pls) > >> { > >> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; > >> + int64_t n = pls->cur_seq_no - pls->start_seq_no; > >> + if (n >= pls->n_segments) > >> + return NULL; > >> + return pls->segments[n]; > >> } > >> > >> static struct segment *next_segment(struct playlist *pls) > >> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, > >> struct segment *seg, > >> > >> /* Parse the raw ID3 data and pass contents to caller */ > >> static void parse_id3(AVFormatContext *s, AVIOContext *pb, > >> - AVDictionary **metadata, int64_t *dts, > >> + AVDictionary **metadata, int64_t *dts, > >> HLSAudioSetupInfo *audio_setup_info, > >> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta > >> **extra_meta) > >> { > >> static const char id3_priv_owner_ts[] = > >> "com.apple.streaming.transportStreamTimestamp"; > >> + static const char id3_priv_owner_audio_setup[] = > >> "com.apple.streaming.audioDescription"; > >> ID3v2ExtraMeta *meta; > >> > >> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); > >> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, > >> AVIOContext *pb, > >> *dts = ts; > >> else > >> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio > >> timestamp %"PRId64"\n", ts); > >> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, > >> id3_priv_owner_audio_setup)) { > >> + ff_hls_read_audio_setup_info(audio_setup_info, > >> priv->data, priv->datasize); > >> } > >> } else if (!strcmp(meta->tag, "APIC") && apic) > >> *apic = &meta->data.apic; > >> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct > >> playlist *pls) > >> ID3v2ExtraMeta *extra_meta = NULL; > >> int64_t timestamp = AV_NOPTS_VALUE; > >> > >> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); > >> + parse_id3(pls->ctx, pb, &metadata, ×tamp, > >> &pls->audio_setup_info, &apic, &extra_meta); > >> > >> if (timestamp != AV_NOPTS_VALUE) { > >> pls->id3_mpegts_timestamp = timestamp; > >> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct > >> playlist *pls, struct segment *seg, > >> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', > offset > >> %"PRId64", playlist %d\n", > >> seg->url, seg->url_offset, pls->index); > >> > >> - if (seg->key_type == KEY_NONE) { > >> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >> &is_http); > >> - } else if (seg->key_type == KEY_AES_128) { > >> - char iv[33], key[33], url[MAX_URL_SIZE]; > >> + if (seg->key_type == KEY_AES_128 || seg->key_type == > KEY_SAMPLE_AES) { > >> if (strcmp(seg->key, pls->key_url)) { > >> AVIOContext *pb = NULL; > >> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, > opts, > >> NULL) == 0) { > >> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct > >> playlist *pls, struct segment *seg, > >> } > >> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); > >> } > >> + } > >> + > >> + if (seg->key_type == KEY_AES_128) { > >> + char iv[33], key[33], url[MAX_URL_SIZE]; > >> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); > >> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); > >> iv[32] = key[32] = '\0'; > >> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct > >> playlist *pls, struct segment *seg, > >> goto cleanup; > >> } > >> ret = 0; > >> - } else if (seg->key_type == KEY_SAMPLE_AES) { > >> - av_log(pls->parent, AV_LOG_ERROR, > >> - "SAMPLE-AES encryption is not supported yet\n"); > >> - ret = AVERROR_PATCHWELCOME; > >> + } else { > >> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >> &is_http); > >> } > >> - else > >> - ret = AVERROR(ENOSYS); > >> > >> /* Seek to the requested position. If this was a HTTP request, the > >> offset > >> * should already be where want it to, but this allows e.g. local > >> testing > >> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) > >> struct playlist *pls = c->playlists[i]; > >> char *url; > >> ff_const59 AVInputFormat *in_fmt = NULL; > >> + struct segment *seg = NULL; > >> > >> if (!(pls->ctx = avformat_alloc_context())) { > >> ret = AVERROR(ENOMEM); > >> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) > >> pls->ctx = NULL; > >> goto fail; > >> } > >> + > >> ffio_init_context(&pls->pb, pls->read_buffer, > >> INITIAL_BUFFER_SIZE, 0, pls, > >> read_data, NULL, NULL); > >> + > >> + /* > >> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of > >> + * external audio track that contains audio setup information > >> + */ > >> + seg = current_segment(pls); > >> + if (seg && seg->key_type == KEY_SAMPLE_AES && > pls->n_renditions > > >> 0 && > >> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { > >> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; > >> + if ((ret = avio_read(&pls->pb, buf, > >> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { > >> + /* Fail if error was not end of file */ > >> + if (ret != AVERROR_EOF) { > >> + avformat_free_context(pls->ctx); > >> + pls->ctx = NULL; > >> + goto fail; > >> + } > >> + } > >> + ret = 0; > >> + } > >> + > >> + /* > >> + * If encryption scheme is SAMPLE-AES and audio setup > information > >> is present in external audio track, > >> + * use that information to find the media format, otherwise > probe > >> input data > >> + */ > >> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >> pls->is_id3_timestamped && > >> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { > >> + void *iter = NULL; > >> + while ((in_fmt = (ff_const59 AVInputFormat > >> *)av_demuxer_iterate(&iter))) > >> + if (in_fmt->raw_codec_id == > >> pls->audio_setup_info.codec_id) { > >> + break; > >> + } > >> + } else { > >> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * > 4; > >> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? > >> s->max_analyze_duration : 4 * AV_TIME_BASE; > >> pls->ctx->interrupt_callback = s->interrupt_callback; > >> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) > >> goto fail; > >> } > >> av_free(url); > >> + } > >> + > >> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && > >> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && > >> + strcmp(in_fmt->name, "mpegts")) { > >> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not > >> supported for fragmented MP4 format yet\n"); > >> + ret = AVERROR_PATCHWELCOME; > >> + } else { > >> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); > >> + if (!pls->crypto_ctx.aes_ctx) > >> + ret = AVERROR(ENOMEM); > >> + } > >> + if (ret != 0) { > >> + avformat_free_context(pls->ctx); > >> + pls->ctx = NULL; > >> + goto fail; > >> + } > >> + } > >> + > >> pls->ctx->pb = &pls->pb; > >> pls->ctx->io_open = nested_io_open; > >> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; > >> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) > >> * on us if they want to. > >> */ > >> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && > >> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { > >> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >> pls->audio_setup_info.setup_data_length > 0 && > >> + pls->ctx->nb_streams == 1) > >> + ret = > ff_hls_parse_audio_setup_info(pls->ctx->streams[0], > >> &pls->audio_setup_info); > >> + else > >> ret = avformat_find_stream_info(pls->ctx, NULL); > >> + > >> if (ret < 0) > >> goto fail; > >> } > >> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, > >> AVPacket *pkt) > >> while (1) { > >> int64_t ts_diff; > >> AVRational tb; > >> + struct segment *seg = NULL; > >> ret = av_read_frame(pls->ctx, &pls->pkt); > >> if (ret < 0) { > >> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) > >> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, > >> AVPacket *pkt) > >> get_timebase(pls), AV_TIME_BASE_Q); > >> } > >> > >> + seg = current_segment(pls); > >> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >> + enum AVCodecID codec_id = > >> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; > >> + memcpy(pls->crypto_ctx.iv, seg->iv, > sizeof(seg->iv)); > >> + memcpy(pls->crypto_ctx.key, pls->key, > >> sizeof(pls->key)); > >> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, > >> &pls->pkt); > >> + } > >> + > >> if (pls->seek_timestamp == AV_NOPTS_VALUE) > >> break; > >> > >> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c > >> new file mode 100644 > >> index 0000000000..0407a15b0f > >> --- /dev/null > >> +++ b/libavformat/hls_sample_aes.c > >> @@ -0,0 +1,391 @@ > >> +/* > >> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >> + * > >> + * Copyright (c) 2021 Nachiket Tarate > >> + * > >> + * This file is part of FFmpeg. > >> + * > >> + * FFmpeg is free software; you can redistribute it and/or > >> + * modify it under the terms of the GNU Lesser General Public > >> + * License as published by the Free Software Foundation; either > >> + * version 2.1 of the License, or (at your option) any later version. > >> + * > >> + * FFmpeg is distributed in the hope that it will be useful, > >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >> + * Lesser General Public License for more details. > >> + * > >> + * You should have received a copy of the GNU Lesser General Public > >> + * License along with FFmpeg; if not, write to the Free Software > >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >> 02110-1301 USA > >> + */ > >> + > >> +/** > >> + * @file > >> + * Apple HTTP Live Streaming Sample Encryption > >> + * > >> > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >> + */ > >> + > >> +#include "hls_sample_aes.h" > >> + > >> +#include "libavcodec/adts_header.h" > >> +#include "libavcodec/adts_parser.h" > >> +#include "libavcodec/ac3_parser_internal.h" > >> + > >> + > >> +typedef struct NALUnit { > >> + uint8_t *data; > >> + int type; > >> + int length; > >> + int start_code_length; > >> +} NALUnit; > >> + > >> +typedef struct AudioFrame { > >> + uint8_t *data; > >> + int length; > >> + int header_length; > >> +} AudioFrame; > >> + > >> +typedef struct CodecParserContext { > >> + const uint8_t *buf_ptr; > >> + const uint8_t *buf_end; > >> +} CodecParserContext; > >> + > >> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; > >> + > >> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > uint8_t > >> *buf, size_t size) > >> +{ > >> + if (size < 8) > >> + return; > >> + > >> + info->codec_tag = AV_RL32(buf); > >> + > >> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) > >> + info->codec_id = AV_CODEC_ID_AAC; > >> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) > >> + info->codec_id = AV_CODEC_ID_AC3; > >> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) > >> + info->codec_id = AV_CODEC_ID_EAC3; > >> + else > >> + info->codec_id = AV_CODEC_ID_NONE; > >> + > >> + buf += 4; > >> + info->priming = AV_RL16(buf); > >> + buf += 2; > >> + info->version = *buf++; > >> + info->setup_data_length = *buf++; > >> + > >> + if (info->setup_data_length > size - 8) > >> + info->setup_data_length = size - 8; > >> + > >> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) > >> + return; > >> + > >> + memcpy(info->setup_data, buf, info->setup_data_length); > >> +} > >> + > >> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > *info) > >> +{ > >> + int ret = 0; > >> + > >> + st->codecpar->codec_tag = info->codec_tag; > >> + > >> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) > >> + return 0; > >> + > >> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && > >> st->codecpar->codec_id != AV_CODEC_ID_EAC3) > >> + return AVERROR_INVALIDDATA; > >> + > >> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { > >> + > >> + AC3HeaderInfo *ac3hdr = NULL; > >> + > >> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, > >> info->setup_data_length); > >> + if (ret < 0) { > >> + if (ret != AVERROR(ENOMEM)) > >> + av_free(ac3hdr); > >> + return ret; > >> + } > >> + > >> + st->codecpar->sample_rate = ac3hdr->sample_rate; > >> + st->codecpar->channels = ac3hdr->channels; > >> + st->codecpar->channel_layout = ac3hdr->channel_layout; > >> + st->codecpar->bit_rate = ac3hdr->bit_rate; > >> + > >> + av_free(ac3hdr); > >> + } else { /* Parse 'dec3' EC3SpecificBox */ > >> + > >> + GetBitContext gb; > >> + int data_rate, fscod, acmod, lfeon; > >> + > >> + ret = init_get_bits8(&gb, info->setup_data, > >> info->setup_data_length); > >> + if (ret < 0) > >> + return AVERROR_INVALIDDATA; > >> + > >> + data_rate = get_bits(&gb, 13); > >> + skip_bits(&gb, 3); > >> + fscod = get_bits(&gb, 2); > >> + skip_bits(&gb, 10); > >> + acmod = get_bits(&gb, 3); > >> + lfeon = get_bits(&gb, 1); > >> + > >> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; > >> + > >> + st->codecpar->channel_layout = > >> avpriv_ac3_channel_layout_tab[acmod]; > >> + if (lfeon) > >> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; > >> + > >> + st->codecpar->channels = > >> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); > >> + > >> + st->codecpar->bit_rate = data_rate*1000; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +/* > >> + * Remove start code emulation prevention 0x03 bytes > >> + */ > >> +static void remove_scep_3_bytes(NALUnit *nalu) > >> +{ > >> + int i = 0; > >> + int j = 0; > >> + > >> + uint8_t *data = nalu->data; > >> + > >> + while (i < nalu->length) { > >> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { > >> + data[j++] = data[i++]; > >> + data[j++] = data[i++]; > >> + i++; > >> + } else { > >> + data[j++] = data[i++]; > >> + } > >> + } > >> + > >> + nalu->length = j; > >> +} > >> + > >> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) > >> +{ > >> + const uint8_t *nalu_start = ctx->buf_ptr; > >> + > >> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > >> 0x00000001) > >> + nalu->start_code_length = 4; > >> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) > == > >> 0x000001) > >> + nalu->start_code_length = 3; > >> + else /* No start code at the beginning of the NAL unit */ > >> + return -1; > >> + > >> + ctx->buf_ptr += nalu->start_code_length; > >> + > >> + while (ctx->buf_ptr < ctx->buf_end) { > >> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) > == > >> 0x00000001) > >> + break; > >> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && > >> AV_RB24(ctx->buf_ptr) == 0x000001) > >> + break; > >> + ctx->buf_ptr++; > >> + } > >> + > >> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; > >> + nalu->length = ctx->buf_ptr - nalu->data; > >> + nalu->type = *nalu->data & 0x1F; > >> + > >> + return 0; > >> +} > >> + > >> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit > *nalu) > >> +{ > >> + int ret = 0; > >> + int rem_bytes; > >> + uint8_t *data; > >> + uint8_t iv[16]; > >> + > >> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >> + if (ret < 0) > >> + return ret; > >> + > >> + /* Remove start code emulation prevention 0x03 bytes */ > >> + remove_scep_3_bytes(nalu); > >> + > >> + data = nalu->data + 32; > >> + rem_bytes = nalu->length - 32; > >> + > >> + memcpy(iv, crypto_ctx->iv, 16); > >> + > >> + while (rem_bytes > 0) { > >> + if (rem_bytes > 16) { > >> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); > >> + data += 16; > >> + rem_bytes -= 16; > >> + } > >> + data += FFMIN(144, rem_bytes); > >> + rem_bytes -= FFMIN(144, rem_bytes); > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket > >> *pkt) > >> +{ > >> + int ret = 0; > >> + CodecParserContext ctx; > >> + NALUnit nalu; > >> + uint8_t *data_ptr; > >> + int move_nalu = 0; > >> + > >> + memset(&ctx, 0, sizeof(ctx)); > >> + ctx.buf_ptr = pkt->data; > >> + ctx.buf_end = pkt->data + pkt->size; > >> + > >> + data_ptr = pkt->data; > >> + > >> + while (ctx.buf_ptr < ctx.buf_end) { > >> + memset(&nalu, 0, sizeof(nalu)); > >> + ret = get_next_nal_unit(&ctx, &nalu); > >> + if (ret < 0) > >> + return ret; > >> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > > 48) > >> { > >> + int encrypted_nalu_length = nalu.length; > >> + ret = decrypt_nal_unit(crypto_ctx, &nalu); > >> + if (ret < 0) > >> + return ret; > >> + move_nalu = nalu.length != encrypted_nalu_length; > >> + } > >> + if (move_nalu) > >> + memmove(data_ptr, nalu.data - nalu.start_code_length, > >> nalu.start_code_length + nalu.length); > >> + data_ptr += nalu.start_code_length + nalu.length; > >> + } > >> + > >> + av_shrink_packet(pkt, data_ptr - pkt->data); > >> + > >> + return 0; > >> +} > >> + > >> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame > *frame) > >> +{ > >> + int ret = 0; > >> + > >> + AACADTSHeaderInfo *adts_hdr = NULL; > >> + > >> + /* Find next sync word 0xFFF */ > >> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == > 0xF0) > >> + break; > >> + ctx->buf_ptr++; > >> + } > >> + > >> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >> + return -1; > >> + > >> + frame->data = (uint8_t*)ctx->buf_ptr; > >> + > >> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, > ctx->buf_end > >> - frame->data); > >> + if (ret < 0) > >> + return ret; > >> + > >> + frame->header_length = adts_hdr->crc_absent ? > AV_AAC_ADTS_HEADER_SIZE > >> : AV_AAC_ADTS_HEADER_SIZE + 2; > >> + frame->length = adts_hdr->frame_length; > >> + > >> + av_free(adts_hdr); > >> + > >> + return 0; > >> +} > >> + > >> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, > >> AudioFrame *frame) > >> +{ > >> + int ret = 0; > >> + > >> + AC3HeaderInfo *hdr = NULL; > >> + > >> + /* Find next sync word 0x0B77 */ > >> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) > >> + break; > >> + ctx->buf_ptr++; > >> + } > >> + > >> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >> + return -1; > >> + > >> + frame->data = (uint8_t*)ctx->buf_ptr; > >> + frame->header_length = 0; > >> + > >> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - > >> frame->data); > >> + if (ret < 0) { > >> + if (ret != AVERROR(ENOMEM)) > >> + av_free(hdr); > >> + return ret; > >> + } > >> + > >> + frame->length = hdr->frame_size; > >> + > >> + av_free(hdr); > >> + > >> + return 0; > >> +} > >> + > >> +static int get_next_sync_frame(enum AVCodecID codec_id, > >> CodecParserContext *ctx, AudioFrame *frame) > >> +{ > >> + if (codec_id == AV_CODEC_ID_AAC) > >> + return get_next_adts_frame(ctx, frame); > >> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == > AV_CODEC_ID_EAC3) > >> + return get_next_ac3_eac3_sync_frame(ctx, frame); > >> + else > >> + return AVERROR_INVALIDDATA; > >> +} > >> + > >> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext > >> *crypto_ctx, AudioFrame *frame) > >> +{ > >> + int ret = 0; > >> + uint8_t *data; > >> + int num_of_encrypted_blocks; > >> + > >> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >> + if (ret < 0) > >> + return ret; > >> + > >> + data = frame->data + frame->header_length + 16; > >> + > >> + num_of_encrypted_blocks = (frame->length - frame->header_length - > >> 16)/16; > >> + > >> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, > >> num_of_encrypted_blocks, crypto_ctx->iv, 1); > >> + > >> + return 0; > >> +} > >> + > >> +static int decrypt_audio_frame(enum AVCodecID codec_id, > HLSCryptoContext > >> *crypto_ctx, AVPacket *pkt) > >> +{ > >> + int ret = 0; > >> + CodecParserContext ctx; > >> + AudioFrame frame; > >> + > >> + memset(&ctx, 0, sizeof(ctx)); > >> + ctx.buf_ptr = pkt->data; > >> + ctx.buf_end = pkt->data + pkt->size; > >> + > >> + while (ctx.buf_ptr < ctx.buf_end) { > >> + memset(&frame, 0, sizeof(frame)); > >> + ret = get_next_sync_frame(codec_id, &ctx, &frame); > >> + if (ret < 0) > >> + return ret; > >> + if (frame.length - frame.header_length > 31) { > >> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); > >> + if (ret < 0) > >> + return ret; > >> + } > >> + ctx.buf_ptr += frame.length; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >> *crypto_ctx, AVPacket *pkt) > >> +{ > >> + if (codec_id == AV_CODEC_ID_H264) > >> + return decrypt_video_frame(crypto_ctx, pkt); > >> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 > >> || codec_id == AV_CODEC_ID_EAC3) > >> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); > >> + > >> + return AVERROR_INVALIDDATA; > >> +} > >> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h > >> new file mode 100644 > >> index 0000000000..cf80e41cb0 > >> --- /dev/null > >> +++ b/libavformat/hls_sample_aes.h > >> @@ -0,0 +1,66 @@ > >> +/* > >> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >> + * > >> + * Copyright (c) 2021 Nachiket Tarate > >> + * > >> + * This file is part of FFmpeg. > >> + * > >> + * FFmpeg is free software; you can redistribute it and/or > >> + * modify it under the terms of the GNU Lesser General Public > >> + * License as published by the Free Software Foundation; either > >> + * version 2.1 of the License, or (at your option) any later version. > >> + * > >> + * FFmpeg is distributed in the hope that it will be useful, > >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >> + * Lesser General Public License for more details. > >> + * > >> + * You should have received a copy of the GNU Lesser General Public > >> + * License along with FFmpeg; if not, write to the Free Software > >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >> 02110-1301 USA > >> + */ > >> + > >> +/** > >> + * @file > >> + * Apple HTTP Live Streaming Sample Encryption > >> + * > >> > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >> + */ > >> + > >> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H > >> +#define AVFORMAT_HLS_SAMPLE_AES_H > >> + > >> +#include <stdint.h> > >> + > >> +#include "avformat.h" > >> + > >> +#include "libavcodec/avcodec.h" > >> +#include "libavutil/aes.h" > >> + > >> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 > >> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 > >> + > >> + > >> +typedef struct HLSCryptoContext { > >> + struct AVAES *aes_ctx; > >> + uint8_t key[16]; > >> + uint8_t iv[16]; > >> +} HLSCryptoContext; > >> + > >> +typedef struct HLSAudioSetupInfo { > >> + enum AVCodecID codec_id; > >> + uint32_t codec_tag; > >> + uint16_t priming; > >> + uint8_t version; > >> + uint8_t setup_data_length; > >> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; > >> +} HLSAudioSetupInfo; > >> + > >> + > >> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > uint8_t > >> *buf, size_t size); > >> + > >> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > *info); > >> + > >> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >> *crypto_ctx, AVPacket *pkt); > >> + > >> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ > >> + > >> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c > >> index e283ec09d7..dc611ae788 100644 > >> --- a/libavformat/mpegts.c > >> +++ b/libavformat/mpegts.c > >> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { > >> { 0 }, > >> }; > >> > >> +/* HLS Sample Encryption Types */ > >> +static const StreamType HLS_SAMPLE_ENC_types[] = { > >> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, > >> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, > >> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > >> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, > >> + { 0 }, > >> +}; > >> + > >> + > >> static const StreamType REGD_types[] = { > >> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC > }, > >> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 > }, > >> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, > >> PESContext *pes, > >> } > >> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >> mpegts_find_stream_type(st, pes->stream_type, MISC_types); > >> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >> + mpegts_find_stream_type(st, pes->stream_type, > >> HLS_SAMPLE_ENC_types); > >> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { > >> st->codecpar->codec_id = old_codec_id; > >> st->codecpar->codec_type = old_codec_type; > >> -- > >> 2.17.1 > >> > >> > > _______________________________________________ > > ffmpeg-devel mailing list > > ffmpeg-devel@ffmpeg.org > > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > > > To unsubscribe, visit link above, or email > > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > Thanks > > Steven Liu > > > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > This is an updated version of the patch in which I have added the check. If > the segments are in Fragmented MP4 format, HLS demuxer quits by giving an > error message: > > "SAMPLE-AES encryption is not supported for fragmented MP4 format yet” I don’t think is a good resolution for SAMPLE-AES encryption and decryption. You should support that if you want support SAMPLE-AES in hls, because SAMPLE-AES not only support in MPEG-TS, but also support fragment mp4. Whatever, if you only support mpegts en[de]cryption, it should be a half part patch. > > Best Regards, > Nachiket Tarate > > On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: > >> >> >>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: >>> >>> @Steven Liu <lq@chinaffmpeg.org> >>> >>> Can we merge this patch ? >> I’m waiting update patch for fragment mp4 encryption. >> After new version of the patchset I will test and review. >>> >>> Best Regards, >>> Nachiket Tarate >>> >>> On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < >>> nachiket.programmer@gmail.com> wrote: >>> >>>> Apple HTTP Live Streaming Sample Encryption: >>>> >>>> >>>> >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>> >>>> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> >>>> --- >>>> libavformat/Makefile | 2 +- >>>> libavformat/hls.c | 105 ++++++++-- >>>> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ >>>> libavformat/hls_sample_aes.h | 66 ++++++ >>>> libavformat/mpegts.c | 12 ++ >>>> 5 files changed, 562 insertions(+), 14 deletions(-) >>>> create mode 100644 libavformat/hls_sample_aes.c >>>> create mode 100644 libavformat/hls_sample_aes.h >>>> >>>> diff --git a/libavformat/Makefile b/libavformat/Makefile >>>> index fcb39ce133..62627d50a6 100644 >>>> --- a/libavformat/Makefile >>>> +++ b/libavformat/Makefile >>>> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o >>>> pcm.o >>>> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o >>>> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o >>>> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o >>>> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o >>>> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o >>>> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o >>>> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o >>>> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o >>>> diff --git a/libavformat/hls.c b/libavformat/hls.c >>>> index af2468ad9b..3cb3853c79 100644 >>>> --- a/libavformat/hls.c >>>> +++ b/libavformat/hls.c >>>> @@ -2,6 +2,7 @@ >>>> * Apple HTTP Live Streaming demuxer >>>> * Copyright (c) 2010 Martin Storsjo >>>> * Copyright (c) 2013 Anssi Hannula >>>> + * Copyright (c) 2021 Nachiket Tarate >>>> * >>>> * This file is part of FFmpeg. >>>> * >>>> @@ -39,6 +40,8 @@ >>>> #include "avio_internal.h" >>>> #include "id3v2.h" >>>> >>>> +#include "hls_sample_aes.h" >>>> + >>>> #define INITIAL_BUFFER_SIZE 32768 >>>> >>>> #define MAX_FIELD_LEN 64 >>>> @@ -145,6 +148,10 @@ struct playlist { >>>> int id3_changed; /* ID3 tag data has changed at some point */ >>>> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer >>>> is opened */ >>>> >>>> + /* Used in case of SAMPLE-AES encryption method */ >>>> + HLSAudioSetupInfo audio_setup_info; >>>> + HLSCryptoContext crypto_ctx; >>>> + >>>> int64_t seek_timestamp; >>>> int seek_flags; >>>> int seek_stream_index; /* into subdemuxer stream array */ >>>> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) >>>> pls->ctx->pb = NULL; >>>> avformat_close_input(&pls->ctx); >>>> } >>>> + if (pls->crypto_ctx.aes_ctx) >>>> + av_free(pls->crypto_ctx.aes_ctx); >>>> av_free(pls); >>>> } >>>> av_freep(&c->playlists); >>>> @@ -994,7 +1003,10 @@ fail: >>>> >>>> static struct segment *current_segment(struct playlist *pls) >>>> { >>>> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; >>>> + int64_t n = pls->cur_seq_no - pls->start_seq_no; >>>> + if (n >= pls->n_segments) >>>> + return NULL; >>>> + return pls->segments[n]; >>>> } >>>> >>>> static struct segment *next_segment(struct playlist *pls) >>>> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, >>>> struct segment *seg, >>>> >>>> /* Parse the raw ID3 data and pass contents to caller */ >>>> static void parse_id3(AVFormatContext *s, AVIOContext *pb, >>>> - AVDictionary **metadata, int64_t *dts, >>>> + AVDictionary **metadata, int64_t *dts, >>>> HLSAudioSetupInfo *audio_setup_info, >>>> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta >>>> **extra_meta) >>>> { >>>> static const char id3_priv_owner_ts[] = >>>> "com.apple.streaming.transportStreamTimestamp"; >>>> + static const char id3_priv_owner_audio_setup[] = >>>> "com.apple.streaming.audioDescription"; >>>> ID3v2ExtraMeta *meta; >>>> >>>> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); >>>> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, >>>> AVIOContext *pb, >>>> *dts = ts; >>>> else >>>> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio >>>> timestamp %"PRId64"\n", ts); >>>> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, >>>> id3_priv_owner_audio_setup)) { >>>> + ff_hls_read_audio_setup_info(audio_setup_info, >>>> priv->data, priv->datasize); >>>> } >>>> } else if (!strcmp(meta->tag, "APIC") && apic) >>>> *apic = &meta->data.apic; >>>> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct >>>> playlist *pls) >>>> ID3v2ExtraMeta *extra_meta = NULL; >>>> int64_t timestamp = AV_NOPTS_VALUE; >>>> >>>> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); >>>> + parse_id3(pls->ctx, pb, &metadata, ×tamp, >>>> &pls->audio_setup_info, &apic, &extra_meta); >>>> >>>> if (timestamp != AV_NOPTS_VALUE) { >>>> pls->id3_mpegts_timestamp = timestamp; >>>> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct >>>> playlist *pls, struct segment *seg, >>>> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', >> offset >>>> %"PRId64", playlist %d\n", >>>> seg->url, seg->url_offset, pls->index); >>>> >>>> - if (seg->key_type == KEY_NONE) { >>>> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >>>> &is_http); >>>> - } else if (seg->key_type == KEY_AES_128) { >>>> - char iv[33], key[33], url[MAX_URL_SIZE]; >>>> + if (seg->key_type == KEY_AES_128 || seg->key_type == >> KEY_SAMPLE_AES) { >>>> if (strcmp(seg->key, pls->key_url)) { >>>> AVIOContext *pb = NULL; >>>> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, >> opts, >>>> NULL) == 0) { >>>> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct >>>> playlist *pls, struct segment *seg, >>>> } >>>> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); >>>> } >>>> + } >>>> + >>>> + if (seg->key_type == KEY_AES_128) { >>>> + char iv[33], key[33], url[MAX_URL_SIZE]; >>>> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); >>>> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); >>>> iv[32] = key[32] = '\0'; >>>> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct >>>> playlist *pls, struct segment *seg, >>>> goto cleanup; >>>> } >>>> ret = 0; >>>> - } else if (seg->key_type == KEY_SAMPLE_AES) { >>>> - av_log(pls->parent, AV_LOG_ERROR, >>>> - "SAMPLE-AES encryption is not supported yet\n"); >>>> - ret = AVERROR_PATCHWELCOME; >>>> + } else { >>>> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >>>> &is_http); >>>> } >>>> - else >>>> - ret = AVERROR(ENOSYS); >>>> >>>> /* Seek to the requested position. If this was a HTTP request, the >>>> offset >>>> * should already be where want it to, but this allows e.g. local >>>> testing >>>> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) >>>> struct playlist *pls = c->playlists[i]; >>>> char *url; >>>> ff_const59 AVInputFormat *in_fmt = NULL; >>>> + struct segment *seg = NULL; >>>> >>>> if (!(pls->ctx = avformat_alloc_context())) { >>>> ret = AVERROR(ENOMEM); >>>> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) >>>> pls->ctx = NULL; >>>> goto fail; >>>> } >>>> + >>>> ffio_init_context(&pls->pb, pls->read_buffer, >>>> INITIAL_BUFFER_SIZE, 0, pls, >>>> read_data, NULL, NULL); >>>> + >>>> + /* >>>> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of >>>> + * external audio track that contains audio setup information >>>> + */ >>>> + seg = current_segment(pls); >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >> pls->n_renditions > >>>> 0 && >>>> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { >>>> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; >>>> + if ((ret = avio_read(&pls->pb, buf, >>>> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { >>>> + /* Fail if error was not end of file */ >>>> + if (ret != AVERROR_EOF) { >>>> + avformat_free_context(pls->ctx); >>>> + pls->ctx = NULL; >>>> + goto fail; >>>> + } >>>> + } >>>> + ret = 0; >>>> + } >>>> + >>>> + /* >>>> + * If encryption scheme is SAMPLE-AES and audio setup >> information >>>> is present in external audio track, >>>> + * use that information to find the media format, otherwise >> probe >>>> input data >>>> + */ >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >>>> pls->is_id3_timestamped && >>>> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { >>>> + void *iter = NULL; >>>> + while ((in_fmt = (ff_const59 AVInputFormat >>>> *)av_demuxer_iterate(&iter))) >>>> + if (in_fmt->raw_codec_id == >>>> pls->audio_setup_info.codec_id) { >>>> + break; >>>> + } >>>> + } else { >>>> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * >> 4; >>>> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? >>>> s->max_analyze_duration : 4 * AV_TIME_BASE; >>>> pls->ctx->interrupt_callback = s->interrupt_callback; >>>> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) >>>> goto fail; >>>> } >>>> av_free(url); >>>> + } >>>> + >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >>>> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && >>>> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && >>>> + strcmp(in_fmt->name, "mpegts")) { >>>> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not >>>> supported for fragmented MP4 format yet\n"); >>>> + ret = AVERROR_PATCHWELCOME; >>>> + } else { >>>> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); >>>> + if (!pls->crypto_ctx.aes_ctx) >>>> + ret = AVERROR(ENOMEM); >>>> + } >>>> + if (ret != 0) { >>>> + avformat_free_context(pls->ctx); >>>> + pls->ctx = NULL; >>>> + goto fail; >>>> + } >>>> + } >>>> + >>>> pls->ctx->pb = &pls->pb; >>>> pls->ctx->io_open = nested_io_open; >>>> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; >>>> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) >>>> * on us if they want to. >>>> */ >>>> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && >>>> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >>>> pls->audio_setup_info.setup_data_length > 0 && >>>> + pls->ctx->nb_streams == 1) >>>> + ret = >> ff_hls_parse_audio_setup_info(pls->ctx->streams[0], >>>> &pls->audio_setup_info); >>>> + else >>>> ret = avformat_find_stream_info(pls->ctx, NULL); >>>> + >>>> if (ret < 0) >>>> goto fail; >>>> } >>>> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, >>>> AVPacket *pkt) >>>> while (1) { >>>> int64_t ts_diff; >>>> AVRational tb; >>>> + struct segment *seg = NULL; >>>> ret = av_read_frame(pls->ctx, &pls->pkt); >>>> if (ret < 0) { >>>> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) >>>> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, >>>> AVPacket *pkt) >>>> get_timebase(pls), AV_TIME_BASE_Q); >>>> } >>>> >>>> + seg = current_segment(pls); >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >>>> + enum AVCodecID codec_id = >>>> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; >>>> + memcpy(pls->crypto_ctx.iv, seg->iv, >> sizeof(seg->iv)); >>>> + memcpy(pls->crypto_ctx.key, pls->key, >>>> sizeof(pls->key)); >>>> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, >>>> &pls->pkt); >>>> + } >>>> + >>>> if (pls->seek_timestamp == AV_NOPTS_VALUE) >>>> break; >>>> >>>> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c >>>> new file mode 100644 >>>> index 0000000000..0407a15b0f >>>> --- /dev/null >>>> +++ b/libavformat/hls_sample_aes.c >>>> @@ -0,0 +1,391 @@ >>>> +/* >>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption >>>> + * >>>> + * Copyright (c) 2021 Nachiket Tarate >>>> + * >>>> + * This file is part of FFmpeg. >>>> + * >>>> + * FFmpeg is free software; you can redistribute it and/or >>>> + * modify it under the terms of the GNU Lesser General Public >>>> + * License as published by the Free Software Foundation; either >>>> + * version 2.1 of the License, or (at your option) any later version. >>>> + * >>>> + * FFmpeg is distributed in the hope that it will be useful, >>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >>>> + * Lesser General Public License for more details. >>>> + * >>>> + * You should have received a copy of the GNU Lesser General Public >>>> + * License along with FFmpeg; if not, write to the Free Software >>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >>>> 02110-1301 USA >>>> + */ >>>> + >>>> +/** >>>> + * @file >>>> + * Apple HTTP Live Streaming Sample Encryption >>>> + * >>>> >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>> + */ >>>> + >>>> +#include "hls_sample_aes.h" >>>> + >>>> +#include "libavcodec/adts_header.h" >>>> +#include "libavcodec/adts_parser.h" >>>> +#include "libavcodec/ac3_parser_internal.h" >>>> + >>>> + >>>> +typedef struct NALUnit { >>>> + uint8_t *data; >>>> + int type; >>>> + int length; >>>> + int start_code_length; >>>> +} NALUnit; >>>> + >>>> +typedef struct AudioFrame { >>>> + uint8_t *data; >>>> + int length; >>>> + int header_length; >>>> +} AudioFrame; >>>> + >>>> +typedef struct CodecParserContext { >>>> + const uint8_t *buf_ptr; >>>> + const uint8_t *buf_end; >>>> +} CodecParserContext; >>>> + >>>> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; >>>> + >>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const >> uint8_t >>>> *buf, size_t size) >>>> +{ >>>> + if (size < 8) >>>> + return; >>>> + >>>> + info->codec_tag = AV_RL32(buf); >>>> + >>>> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) >>>> + info->codec_id = AV_CODEC_ID_AAC; >>>> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) >>>> + info->codec_id = AV_CODEC_ID_AC3; >>>> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) >>>> + info->codec_id = AV_CODEC_ID_EAC3; >>>> + else >>>> + info->codec_id = AV_CODEC_ID_NONE; >>>> + >>>> + buf += 4; >>>> + info->priming = AV_RL16(buf); >>>> + buf += 2; >>>> + info->version = *buf++; >>>> + info->setup_data_length = *buf++; >>>> + >>>> + if (info->setup_data_length > size - 8) >>>> + info->setup_data_length = size - 8; >>>> + >>>> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) >>>> + return; >>>> + >>>> + memcpy(info->setup_data, buf, info->setup_data_length); >>>> +} >>>> + >>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo >> *info) >>>> +{ >>>> + int ret = 0; >>>> + >>>> + st->codecpar->codec_tag = info->codec_tag; >>>> + >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) >>>> + return 0; >>>> + >>>> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && >>>> st->codecpar->codec_id != AV_CODEC_ID_EAC3) >>>> + return AVERROR_INVALIDDATA; >>>> + >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { >>>> + >>>> + AC3HeaderInfo *ac3hdr = NULL; >>>> + >>>> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, >>>> info->setup_data_length); >>>> + if (ret < 0) { >>>> + if (ret != AVERROR(ENOMEM)) >>>> + av_free(ac3hdr); >>>> + return ret; >>>> + } >>>> + >>>> + st->codecpar->sample_rate = ac3hdr->sample_rate; >>>> + st->codecpar->channels = ac3hdr->channels; >>>> + st->codecpar->channel_layout = ac3hdr->channel_layout; >>>> + st->codecpar->bit_rate = ac3hdr->bit_rate; >>>> + >>>> + av_free(ac3hdr); >>>> + } else { /* Parse 'dec3' EC3SpecificBox */ >>>> + >>>> + GetBitContext gb; >>>> + int data_rate, fscod, acmod, lfeon; >>>> + >>>> + ret = init_get_bits8(&gb, info->setup_data, >>>> info->setup_data_length); >>>> + if (ret < 0) >>>> + return AVERROR_INVALIDDATA; >>>> + >>>> + data_rate = get_bits(&gb, 13); >>>> + skip_bits(&gb, 3); >>>> + fscod = get_bits(&gb, 2); >>>> + skip_bits(&gb, 10); >>>> + acmod = get_bits(&gb, 3); >>>> + lfeon = get_bits(&gb, 1); >>>> + >>>> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; >>>> + >>>> + st->codecpar->channel_layout = >>>> avpriv_ac3_channel_layout_tab[acmod]; >>>> + if (lfeon) >>>> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; >>>> + >>>> + st->codecpar->channels = >>>> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); >>>> + >>>> + st->codecpar->bit_rate = data_rate*1000; >>>> + } >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +/* >>>> + * Remove start code emulation prevention 0x03 bytes >>>> + */ >>>> +static void remove_scep_3_bytes(NALUnit *nalu) >>>> +{ >>>> + int i = 0; >>>> + int j = 0; >>>> + >>>> + uint8_t *data = nalu->data; >>>> + >>>> + while (i < nalu->length) { >>>> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { >>>> + data[j++] = data[i++]; >>>> + data[j++] = data[i++]; >>>> + i++; >>>> + } else { >>>> + data[j++] = data[i++]; >>>> + } >>>> + } >>>> + >>>> + nalu->length = j; >>>> +} >>>> + >>>> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) >>>> +{ >>>> + const uint8_t *nalu_start = ctx->buf_ptr; >>>> + >>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == >>>> 0x00000001) >>>> + nalu->start_code_length = 4; >>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) >> == >>>> 0x000001) >>>> + nalu->start_code_length = 3; >>>> + else /* No start code at the beginning of the NAL unit */ >>>> + return -1; >>>> + >>>> + ctx->buf_ptr += nalu->start_code_length; >>>> + >>>> + while (ctx->buf_ptr < ctx->buf_end) { >>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) >> == >>>> 0x00000001) >>>> + break; >>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && >>>> AV_RB24(ctx->buf_ptr) == 0x000001) >>>> + break; >>>> + ctx->buf_ptr++; >>>> + } >>>> + >>>> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; >>>> + nalu->length = ctx->buf_ptr - nalu->data; >>>> + nalu->type = *nalu->data & 0x1F; >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit >> *nalu) >>>> +{ >>>> + int ret = 0; >>>> + int rem_bytes; >>>> + uint8_t *data; >>>> + uint8_t iv[16]; >>>> + >>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >>>> + if (ret < 0) >>>> + return ret; >>>> + >>>> + /* Remove start code emulation prevention 0x03 bytes */ >>>> + remove_scep_3_bytes(nalu); >>>> + >>>> + data = nalu->data + 32; >>>> + rem_bytes = nalu->length - 32; >>>> + >>>> + memcpy(iv, crypto_ctx->iv, 16); >>>> + >>>> + while (rem_bytes > 0) { >>>> + if (rem_bytes > 16) { >>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); >>>> + data += 16; >>>> + rem_bytes -= 16; >>>> + } >>>> + data += FFMIN(144, rem_bytes); >>>> + rem_bytes -= FFMIN(144, rem_bytes); >>>> + } >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket >>>> *pkt) >>>> +{ >>>> + int ret = 0; >>>> + CodecParserContext ctx; >>>> + NALUnit nalu; >>>> + uint8_t *data_ptr; >>>> + int move_nalu = 0; >>>> + >>>> + memset(&ctx, 0, sizeof(ctx)); >>>> + ctx.buf_ptr = pkt->data; >>>> + ctx.buf_end = pkt->data + pkt->size; >>>> + >>>> + data_ptr = pkt->data; >>>> + >>>> + while (ctx.buf_ptr < ctx.buf_end) { >>>> + memset(&nalu, 0, sizeof(nalu)); >>>> + ret = get_next_nal_unit(&ctx, &nalu); >>>> + if (ret < 0) >>>> + return ret; >>>> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > >> 48) >>>> { >>>> + int encrypted_nalu_length = nalu.length; >>>> + ret = decrypt_nal_unit(crypto_ctx, &nalu); >>>> + if (ret < 0) >>>> + return ret; >>>> + move_nalu = nalu.length != encrypted_nalu_length; >>>> + } >>>> + if (move_nalu) >>>> + memmove(data_ptr, nalu.data - nalu.start_code_length, >>>> nalu.start_code_length + nalu.length); >>>> + data_ptr += nalu.start_code_length + nalu.length; >>>> + } >>>> + >>>> + av_shrink_packet(pkt, data_ptr - pkt->data); >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame >> *frame) >>>> +{ >>>> + int ret = 0; >>>> + >>>> + AACADTSHeaderInfo *adts_hdr = NULL; >>>> + >>>> + /* Find next sync word 0xFFF */ >>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { >>>> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == >> 0xF0) >>>> + break; >>>> + ctx->buf_ptr++; >>>> + } >>>> + >>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) >>>> + return -1; >>>> + >>>> + frame->data = (uint8_t*)ctx->buf_ptr; >>>> + >>>> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, >> ctx->buf_end >>>> - frame->data); >>>> + if (ret < 0) >>>> + return ret; >>>> + >>>> + frame->header_length = adts_hdr->crc_absent ? >> AV_AAC_ADTS_HEADER_SIZE >>>> : AV_AAC_ADTS_HEADER_SIZE + 2; >>>> + frame->length = adts_hdr->frame_length; >>>> + >>>> + av_free(adts_hdr); >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, >>>> AudioFrame *frame) >>>> +{ >>>> + int ret = 0; >>>> + >>>> + AC3HeaderInfo *hdr = NULL; >>>> + >>>> + /* Find next sync word 0x0B77 */ >>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { >>>> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) >>>> + break; >>>> + ctx->buf_ptr++; >>>> + } >>>> + >>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) >>>> + return -1; >>>> + >>>> + frame->data = (uint8_t*)ctx->buf_ptr; >>>> + frame->header_length = 0; >>>> + >>>> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - >>>> frame->data); >>>> + if (ret < 0) { >>>> + if (ret != AVERROR(ENOMEM)) >>>> + av_free(hdr); >>>> + return ret; >>>> + } >>>> + >>>> + frame->length = hdr->frame_size; >>>> + >>>> + av_free(hdr); >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int get_next_sync_frame(enum AVCodecID codec_id, >>>> CodecParserContext *ctx, AudioFrame *frame) >>>> +{ >>>> + if (codec_id == AV_CODEC_ID_AAC) >>>> + return get_next_adts_frame(ctx, frame); >>>> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == >> AV_CODEC_ID_EAC3) >>>> + return get_next_ac3_eac3_sync_frame(ctx, frame); >>>> + else >>>> + return AVERROR_INVALIDDATA; >>>> +} >>>> + >>>> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext >>>> *crypto_ctx, AudioFrame *frame) >>>> +{ >>>> + int ret = 0; >>>> + uint8_t *data; >>>> + int num_of_encrypted_blocks; >>>> + >>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >>>> + if (ret < 0) >>>> + return ret; >>>> + >>>> + data = frame->data + frame->header_length + 16; >>>> + >>>> + num_of_encrypted_blocks = (frame->length - frame->header_length - >>>> 16)/16; >>>> + >>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, >>>> num_of_encrypted_blocks, crypto_ctx->iv, 1); >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int decrypt_audio_frame(enum AVCodecID codec_id, >> HLSCryptoContext >>>> *crypto_ctx, AVPacket *pkt) >>>> +{ >>>> + int ret = 0; >>>> + CodecParserContext ctx; >>>> + AudioFrame frame; >>>> + >>>> + memset(&ctx, 0, sizeof(ctx)); >>>> + ctx.buf_ptr = pkt->data; >>>> + ctx.buf_end = pkt->data + pkt->size; >>>> + >>>> + while (ctx.buf_ptr < ctx.buf_end) { >>>> + memset(&frame, 0, sizeof(frame)); >>>> + ret = get_next_sync_frame(codec_id, &ctx, &frame); >>>> + if (ret < 0) >>>> + return ret; >>>> + if (frame.length - frame.header_length > 31) { >>>> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); >>>> + if (ret < 0) >>>> + return ret; >>>> + } >>>> + ctx.buf_ptr += frame.length; >>>> + } >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >>>> *crypto_ctx, AVPacket *pkt) >>>> +{ >>>> + if (codec_id == AV_CODEC_ID_H264) >>>> + return decrypt_video_frame(crypto_ctx, pkt); >>>> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 >>>> || codec_id == AV_CODEC_ID_EAC3) >>>> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); >>>> + >>>> + return AVERROR_INVALIDDATA; >>>> +} >>>> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h >>>> new file mode 100644 >>>> index 0000000000..cf80e41cb0 >>>> --- /dev/null >>>> +++ b/libavformat/hls_sample_aes.h >>>> @@ -0,0 +1,66 @@ >>>> +/* >>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption >>>> + * >>>> + * Copyright (c) 2021 Nachiket Tarate >>>> + * >>>> + * This file is part of FFmpeg. >>>> + * >>>> + * FFmpeg is free software; you can redistribute it and/or >>>> + * modify it under the terms of the GNU Lesser General Public >>>> + * License as published by the Free Software Foundation; either >>>> + * version 2.1 of the License, or (at your option) any later version. >>>> + * >>>> + * FFmpeg is distributed in the hope that it will be useful, >>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >>>> + * Lesser General Public License for more details. >>>> + * >>>> + * You should have received a copy of the GNU Lesser General Public >>>> + * License along with FFmpeg; if not, write to the Free Software >>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >>>> 02110-1301 USA >>>> + */ >>>> + >>>> +/** >>>> + * @file >>>> + * Apple HTTP Live Streaming Sample Encryption >>>> + * >>>> >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>> + */ >>>> + >>>> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H >>>> +#define AVFORMAT_HLS_SAMPLE_AES_H >>>> + >>>> +#include <stdint.h> >>>> + >>>> +#include "avformat.h" >>>> + >>>> +#include "libavcodec/avcodec.h" >>>> +#include "libavutil/aes.h" >>>> + >>>> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 >>>> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 >>>> + >>>> + >>>> +typedef struct HLSCryptoContext { >>>> + struct AVAES *aes_ctx; >>>> + uint8_t key[16]; >>>> + uint8_t iv[16]; >>>> +} HLSCryptoContext; >>>> + >>>> +typedef struct HLSAudioSetupInfo { >>>> + enum AVCodecID codec_id; >>>> + uint32_t codec_tag; >>>> + uint16_t priming; >>>> + uint8_t version; >>>> + uint8_t setup_data_length; >>>> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; >>>> +} HLSAudioSetupInfo; >>>> + >>>> + >>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const >> uint8_t >>>> *buf, size_t size); >>>> + >>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo >> *info); >>>> + >>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >>>> *crypto_ctx, AVPacket *pkt); >>>> + >>>> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ >>>> + >>>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c >>>> index e283ec09d7..dc611ae788 100644 >>>> --- a/libavformat/mpegts.c >>>> +++ b/libavformat/mpegts.c >>>> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { >>>> { 0 }, >>>> }; >>>> >>>> +/* HLS Sample Encryption Types */ >>>> +static const StreamType HLS_SAMPLE_ENC_types[] = { >>>> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, >>>> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, >>>> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, >>>> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, >>>> + { 0 }, >>>> +}; >>>> + >>>> + >>>> static const StreamType REGD_types[] = { >>>> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC >> }, >>>> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 >> }, >>>> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, >>>> PESContext *pes, >>>> } >>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >>>> mpegts_find_stream_type(st, pes->stream_type, MISC_types); >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >>>> + mpegts_find_stream_type(st, pes->stream_type, >>>> HLS_SAMPLE_ENC_types); >>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { >>>> st->codecpar->codec_id = old_codec_id; >>>> st->codecpar->codec_type = old_codec_type; >>>> -- >>>> 2.17.1 >>>> >>>> >>> _______________________________________________ >>> ffmpeg-devel mailing list >>> ffmpeg-devel@ffmpeg.org >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >>> >>> To unsubscribe, visit link above, or email >>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >> >> Thanks >> >> Steven Liu >> >> >> >> _______________________________________________ >> ffmpeg-devel mailing list >> ffmpeg-devel@ffmpeg.org >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >> >> To unsubscribe, visit link above, or email >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". Thanks Steven Liu
On Mon, Mar 1, 2021 at 11:30 AM Steven Liu <lq@chinaffmpeg.org> wrote: > > > > > 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > > > This is an updated version of the patch in which I have added the check. If > > the segments are in Fragmented MP4 format, HLS demuxer quits by giving an > > error message: > > > > "SAMPLE-AES encryption is not supported for fragmented MP4 format yet” > I don’t think is a good resolution for SAMPLE-AES encryption and decryption. > You should support that if you want support SAMPLE-AES in hls, > because SAMPLE-AES not only support in MPEG-TS, but also support fragment mp4. > Whatever, if you only support mpegts en[de]cryption, it should be a half part patch. Two completely different technologies/specifications have been used for SAMPLE-AES encryption of HLS streams in MPEG-TS and fragmented MP4 formats. Fragmented MP4 media segments are encrypted using the 'cbcs' scheme of Common Encryption [CENC]: https://www.iso.org/standard/68042.html Encryption of other media segment formats such as MPEG-TS or external audio tracks containing H.264, AAC, AC-3 and Enhanced AC-3 media streams is described in the Apple's HTTP Live Streaming (HLS) Sample Encryption specifications: https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption This patch implements the later specifications and enables decryption of media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 formats. So I think we should merge this patch right now. In future, we will add support for CENC or see how can we use existing things from MOV demuxer. Best Regards, Nachiket Tarate > > > > Best Regards, > > Nachiket Tarate > > > > On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: > > > >> > >> > >>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > >>> > >>> @Steven Liu <lq@chinaffmpeg.org> > >>> > >>> Can we merge this patch ? > >> I’m waiting update patch for fragment mp4 encryption. > >> After new version of the patchset I will test and review. > >>> > >>> Best Regards, > >>> Nachiket Tarate > >>> > >>> On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < > >>> nachiket.programmer@gmail.com> wrote: > >>> > >>>> Apple HTTP Live Streaming Sample Encryption: > >>>> > >>>> > >>>> > >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>> > >>>> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> > >>>> --- > >>>> libavformat/Makefile | 2 +- > >>>> libavformat/hls.c | 105 ++++++++-- > >>>> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ > >>>> libavformat/hls_sample_aes.h | 66 ++++++ > >>>> libavformat/mpegts.c | 12 ++ > >>>> 5 files changed, 562 insertions(+), 14 deletions(-) > >>>> create mode 100644 libavformat/hls_sample_aes.c > >>>> create mode 100644 libavformat/hls_sample_aes.h > >>>> > >>>> diff --git a/libavformat/Makefile b/libavformat/Makefile > >>>> index fcb39ce133..62627d50a6 100644 > >>>> --- a/libavformat/Makefile > >>>> +++ b/libavformat/Makefile > >>>> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o > >>>> pcm.o > >>>> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o > >>>> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o > >>>> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o > >>>> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o > >>>> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o > >>>> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o > >>>> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o > >>>> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o > >>>> diff --git a/libavformat/hls.c b/libavformat/hls.c > >>>> index af2468ad9b..3cb3853c79 100644 > >>>> --- a/libavformat/hls.c > >>>> +++ b/libavformat/hls.c > >>>> @@ -2,6 +2,7 @@ > >>>> * Apple HTTP Live Streaming demuxer > >>>> * Copyright (c) 2010 Martin Storsjo > >>>> * Copyright (c) 2013 Anssi Hannula > >>>> + * Copyright (c) 2021 Nachiket Tarate > >>>> * > >>>> * This file is part of FFmpeg. > >>>> * > >>>> @@ -39,6 +40,8 @@ > >>>> #include "avio_internal.h" > >>>> #include "id3v2.h" > >>>> > >>>> +#include "hls_sample_aes.h" > >>>> + > >>>> #define INITIAL_BUFFER_SIZE 32768 > >>>> > >>>> #define MAX_FIELD_LEN 64 > >>>> @@ -145,6 +148,10 @@ struct playlist { > >>>> int id3_changed; /* ID3 tag data has changed at some point */ > >>>> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer > >>>> is opened */ > >>>> > >>>> + /* Used in case of SAMPLE-AES encryption method */ > >>>> + HLSAudioSetupInfo audio_setup_info; > >>>> + HLSCryptoContext crypto_ctx; > >>>> + > >>>> int64_t seek_timestamp; > >>>> int seek_flags; > >>>> int seek_stream_index; /* into subdemuxer stream array */ > >>>> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) > >>>> pls->ctx->pb = NULL; > >>>> avformat_close_input(&pls->ctx); > >>>> } > >>>> + if (pls->crypto_ctx.aes_ctx) > >>>> + av_free(pls->crypto_ctx.aes_ctx); > >>>> av_free(pls); > >>>> } > >>>> av_freep(&c->playlists); > >>>> @@ -994,7 +1003,10 @@ fail: > >>>> > >>>> static struct segment *current_segment(struct playlist *pls) > >>>> { > >>>> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; > >>>> + int64_t n = pls->cur_seq_no - pls->start_seq_no; > >>>> + if (n >= pls->n_segments) > >>>> + return NULL; > >>>> + return pls->segments[n]; > >>>> } > >>>> > >>>> static struct segment *next_segment(struct playlist *pls) > >>>> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, > >>>> struct segment *seg, > >>>> > >>>> /* Parse the raw ID3 data and pass contents to caller */ > >>>> static void parse_id3(AVFormatContext *s, AVIOContext *pb, > >>>> - AVDictionary **metadata, int64_t *dts, > >>>> + AVDictionary **metadata, int64_t *dts, > >>>> HLSAudioSetupInfo *audio_setup_info, > >>>> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta > >>>> **extra_meta) > >>>> { > >>>> static const char id3_priv_owner_ts[] = > >>>> "com.apple.streaming.transportStreamTimestamp"; > >>>> + static const char id3_priv_owner_audio_setup[] = > >>>> "com.apple.streaming.audioDescription"; > >>>> ID3v2ExtraMeta *meta; > >>>> > >>>> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); > >>>> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, > >>>> AVIOContext *pb, > >>>> *dts = ts; > >>>> else > >>>> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio > >>>> timestamp %"PRId64"\n", ts); > >>>> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, > >>>> id3_priv_owner_audio_setup)) { > >>>> + ff_hls_read_audio_setup_info(audio_setup_info, > >>>> priv->data, priv->datasize); > >>>> } > >>>> } else if (!strcmp(meta->tag, "APIC") && apic) > >>>> *apic = &meta->data.apic; > >>>> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct > >>>> playlist *pls) > >>>> ID3v2ExtraMeta *extra_meta = NULL; > >>>> int64_t timestamp = AV_NOPTS_VALUE; > >>>> > >>>> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); > >>>> + parse_id3(pls->ctx, pb, &metadata, ×tamp, > >>>> &pls->audio_setup_info, &apic, &extra_meta); > >>>> > >>>> if (timestamp != AV_NOPTS_VALUE) { > >>>> pls->id3_mpegts_timestamp = timestamp; > >>>> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct > >>>> playlist *pls, struct segment *seg, > >>>> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', > >> offset > >>>> %"PRId64", playlist %d\n", > >>>> seg->url, seg->url_offset, pls->index); > >>>> > >>>> - if (seg->key_type == KEY_NONE) { > >>>> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >>>> &is_http); > >>>> - } else if (seg->key_type == KEY_AES_128) { > >>>> - char iv[33], key[33], url[MAX_URL_SIZE]; > >>>> + if (seg->key_type == KEY_AES_128 || seg->key_type == > >> KEY_SAMPLE_AES) { > >>>> if (strcmp(seg->key, pls->key_url)) { > >>>> AVIOContext *pb = NULL; > >>>> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, > >> opts, > >>>> NULL) == 0) { > >>>> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct > >>>> playlist *pls, struct segment *seg, > >>>> } > >>>> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); > >>>> } > >>>> + } > >>>> + > >>>> + if (seg->key_type == KEY_AES_128) { > >>>> + char iv[33], key[33], url[MAX_URL_SIZE]; > >>>> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); > >>>> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); > >>>> iv[32] = key[32] = '\0'; > >>>> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct > >>>> playlist *pls, struct segment *seg, > >>>> goto cleanup; > >>>> } > >>>> ret = 0; > >>>> - } else if (seg->key_type == KEY_SAMPLE_AES) { > >>>> - av_log(pls->parent, AV_LOG_ERROR, > >>>> - "SAMPLE-AES encryption is not supported yet\n"); > >>>> - ret = AVERROR_PATCHWELCOME; > >>>> + } else { > >>>> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >>>> &is_http); > >>>> } > >>>> - else > >>>> - ret = AVERROR(ENOSYS); > >>>> > >>>> /* Seek to the requested position. If this was a HTTP request, the > >>>> offset > >>>> * should already be where want it to, but this allows e.g. local > >>>> testing > >>>> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) > >>>> struct playlist *pls = c->playlists[i]; > >>>> char *url; > >>>> ff_const59 AVInputFormat *in_fmt = NULL; > >>>> + struct segment *seg = NULL; > >>>> > >>>> if (!(pls->ctx = avformat_alloc_context())) { > >>>> ret = AVERROR(ENOMEM); > >>>> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) > >>>> pls->ctx = NULL; > >>>> goto fail; > >>>> } > >>>> + > >>>> ffio_init_context(&pls->pb, pls->read_buffer, > >>>> INITIAL_BUFFER_SIZE, 0, pls, > >>>> read_data, NULL, NULL); > >>>> + > >>>> + /* > >>>> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of > >>>> + * external audio track that contains audio setup information > >>>> + */ > >>>> + seg = current_segment(pls); > >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >> pls->n_renditions > > >>>> 0 && > >>>> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { > >>>> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; > >>>> + if ((ret = avio_read(&pls->pb, buf, > >>>> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { > >>>> + /* Fail if error was not end of file */ > >>>> + if (ret != AVERROR_EOF) { > >>>> + avformat_free_context(pls->ctx); > >>>> + pls->ctx = NULL; > >>>> + goto fail; > >>>> + } > >>>> + } > >>>> + ret = 0; > >>>> + } > >>>> + > >>>> + /* > >>>> + * If encryption scheme is SAMPLE-AES and audio setup > >> information > >>>> is present in external audio track, > >>>> + * use that information to find the media format, otherwise > >> probe > >>>> input data > >>>> + */ > >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >>>> pls->is_id3_timestamped && > >>>> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { > >>>> + void *iter = NULL; > >>>> + while ((in_fmt = (ff_const59 AVInputFormat > >>>> *)av_demuxer_iterate(&iter))) > >>>> + if (in_fmt->raw_codec_id == > >>>> pls->audio_setup_info.codec_id) { > >>>> + break; > >>>> + } > >>>> + } else { > >>>> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * > >> 4; > >>>> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? > >>>> s->max_analyze_duration : 4 * AV_TIME_BASE; > >>>> pls->ctx->interrupt_callback = s->interrupt_callback; > >>>> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) > >>>> goto fail; > >>>> } > >>>> av_free(url); > >>>> + } > >>>> + > >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >>>> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && > >>>> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && > >>>> + strcmp(in_fmt->name, "mpegts")) { > >>>> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not > >>>> supported for fragmented MP4 format yet\n"); > >>>> + ret = AVERROR_PATCHWELCOME; > >>>> + } else { > >>>> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); > >>>> + if (!pls->crypto_ctx.aes_ctx) > >>>> + ret = AVERROR(ENOMEM); > >>>> + } > >>>> + if (ret != 0) { > >>>> + avformat_free_context(pls->ctx); > >>>> + pls->ctx = NULL; > >>>> + goto fail; > >>>> + } > >>>> + } > >>>> + > >>>> pls->ctx->pb = &pls->pb; > >>>> pls->ctx->io_open = nested_io_open; > >>>> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; > >>>> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) > >>>> * on us if they want to. > >>>> */ > >>>> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && > >>>> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { > >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >>>> pls->audio_setup_info.setup_data_length > 0 && > >>>> + pls->ctx->nb_streams == 1) > >>>> + ret = > >> ff_hls_parse_audio_setup_info(pls->ctx->streams[0], > >>>> &pls->audio_setup_info); > >>>> + else > >>>> ret = avformat_find_stream_info(pls->ctx, NULL); > >>>> + > >>>> if (ret < 0) > >>>> goto fail; > >>>> } > >>>> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, > >>>> AVPacket *pkt) > >>>> while (1) { > >>>> int64_t ts_diff; > >>>> AVRational tb; > >>>> + struct segment *seg = NULL; > >>>> ret = av_read_frame(pls->ctx, &pls->pkt); > >>>> if (ret < 0) { > >>>> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) > >>>> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, > >>>> AVPacket *pkt) > >>>> get_timebase(pls), AV_TIME_BASE_Q); > >>>> } > >>>> > >>>> + seg = current_segment(pls); > >>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >>>> + enum AVCodecID codec_id = > >>>> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; > >>>> + memcpy(pls->crypto_ctx.iv, seg->iv, > >> sizeof(seg->iv)); > >>>> + memcpy(pls->crypto_ctx.key, pls->key, > >>>> sizeof(pls->key)); > >>>> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, > >>>> &pls->pkt); > >>>> + } > >>>> + > >>>> if (pls->seek_timestamp == AV_NOPTS_VALUE) > >>>> break; > >>>> > >>>> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c > >>>> new file mode 100644 > >>>> index 0000000000..0407a15b0f > >>>> --- /dev/null > >>>> +++ b/libavformat/hls_sample_aes.c > >>>> @@ -0,0 +1,391 @@ > >>>> +/* > >>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >>>> + * > >>>> + * Copyright (c) 2021 Nachiket Tarate > >>>> + * > >>>> + * This file is part of FFmpeg. > >>>> + * > >>>> + * FFmpeg is free software; you can redistribute it and/or > >>>> + * modify it under the terms of the GNU Lesser General Public > >>>> + * License as published by the Free Software Foundation; either > >>>> + * version 2.1 of the License, or (at your option) any later version. > >>>> + * > >>>> + * FFmpeg is distributed in the hope that it will be useful, > >>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >>>> + * Lesser General Public License for more details. > >>>> + * > >>>> + * You should have received a copy of the GNU Lesser General Public > >>>> + * License along with FFmpeg; if not, write to the Free Software > >>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >>>> 02110-1301 USA > >>>> + */ > >>>> + > >>>> +/** > >>>> + * @file > >>>> + * Apple HTTP Live Streaming Sample Encryption > >>>> + * > >>>> > >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>> + */ > >>>> + > >>>> +#include "hls_sample_aes.h" > >>>> + > >>>> +#include "libavcodec/adts_header.h" > >>>> +#include "libavcodec/adts_parser.h" > >>>> +#include "libavcodec/ac3_parser_internal.h" > >>>> + > >>>> + > >>>> +typedef struct NALUnit { > >>>> + uint8_t *data; > >>>> + int type; > >>>> + int length; > >>>> + int start_code_length; > >>>> +} NALUnit; > >>>> + > >>>> +typedef struct AudioFrame { > >>>> + uint8_t *data; > >>>> + int length; > >>>> + int header_length; > >>>> +} AudioFrame; > >>>> + > >>>> +typedef struct CodecParserContext { > >>>> + const uint8_t *buf_ptr; > >>>> + const uint8_t *buf_end; > >>>> +} CodecParserContext; > >>>> + > >>>> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; > >>>> + > >>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > >> uint8_t > >>>> *buf, size_t size) > >>>> +{ > >>>> + if (size < 8) > >>>> + return; > >>>> + > >>>> + info->codec_tag = AV_RL32(buf); > >>>> + > >>>> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) > >>>> + info->codec_id = AV_CODEC_ID_AAC; > >>>> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) > >>>> + info->codec_id = AV_CODEC_ID_AC3; > >>>> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) > >>>> + info->codec_id = AV_CODEC_ID_EAC3; > >>>> + else > >>>> + info->codec_id = AV_CODEC_ID_NONE; > >>>> + > >>>> + buf += 4; > >>>> + info->priming = AV_RL16(buf); > >>>> + buf += 2; > >>>> + info->version = *buf++; > >>>> + info->setup_data_length = *buf++; > >>>> + > >>>> + if (info->setup_data_length > size - 8) > >>>> + info->setup_data_length = size - 8; > >>>> + > >>>> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) > >>>> + return; > >>>> + > >>>> + memcpy(info->setup_data, buf, info->setup_data_length); > >>>> +} > >>>> + > >>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > >> *info) > >>>> +{ > >>>> + int ret = 0; > >>>> + > >>>> + st->codecpar->codec_tag = info->codec_tag; > >>>> + > >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) > >>>> + return 0; > >>>> + > >>>> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && > >>>> st->codecpar->codec_id != AV_CODEC_ID_EAC3) > >>>> + return AVERROR_INVALIDDATA; > >>>> + > >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { > >>>> + > >>>> + AC3HeaderInfo *ac3hdr = NULL; > >>>> + > >>>> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, > >>>> info->setup_data_length); > >>>> + if (ret < 0) { > >>>> + if (ret != AVERROR(ENOMEM)) > >>>> + av_free(ac3hdr); > >>>> + return ret; > >>>> + } > >>>> + > >>>> + st->codecpar->sample_rate = ac3hdr->sample_rate; > >>>> + st->codecpar->channels = ac3hdr->channels; > >>>> + st->codecpar->channel_layout = ac3hdr->channel_layout; > >>>> + st->codecpar->bit_rate = ac3hdr->bit_rate; > >>>> + > >>>> + av_free(ac3hdr); > >>>> + } else { /* Parse 'dec3' EC3SpecificBox */ > >>>> + > >>>> + GetBitContext gb; > >>>> + int data_rate, fscod, acmod, lfeon; > >>>> + > >>>> + ret = init_get_bits8(&gb, info->setup_data, > >>>> info->setup_data_length); > >>>> + if (ret < 0) > >>>> + return AVERROR_INVALIDDATA; > >>>> + > >>>> + data_rate = get_bits(&gb, 13); > >>>> + skip_bits(&gb, 3); > >>>> + fscod = get_bits(&gb, 2); > >>>> + skip_bits(&gb, 10); > >>>> + acmod = get_bits(&gb, 3); > >>>> + lfeon = get_bits(&gb, 1); > >>>> + > >>>> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; > >>>> + > >>>> + st->codecpar->channel_layout = > >>>> avpriv_ac3_channel_layout_tab[acmod]; > >>>> + if (lfeon) > >>>> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; > >>>> + > >>>> + st->codecpar->channels = > >>>> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); > >>>> + > >>>> + st->codecpar->bit_rate = data_rate*1000; > >>>> + } > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +/* > >>>> + * Remove start code emulation prevention 0x03 bytes > >>>> + */ > >>>> +static void remove_scep_3_bytes(NALUnit *nalu) > >>>> +{ > >>>> + int i = 0; > >>>> + int j = 0; > >>>> + > >>>> + uint8_t *data = nalu->data; > >>>> + > >>>> + while (i < nalu->length) { > >>>> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { > >>>> + data[j++] = data[i++]; > >>>> + data[j++] = data[i++]; > >>>> + i++; > >>>> + } else { > >>>> + data[j++] = data[i++]; > >>>> + } > >>>> + } > >>>> + > >>>> + nalu->length = j; > >>>> +} > >>>> + > >>>> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) > >>>> +{ > >>>> + const uint8_t *nalu_start = ctx->buf_ptr; > >>>> + > >>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > >>>> 0x00000001) > >>>> + nalu->start_code_length = 4; > >>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) > >> == > >>>> 0x000001) > >>>> + nalu->start_code_length = 3; > >>>> + else /* No start code at the beginning of the NAL unit */ > >>>> + return -1; > >>>> + > >>>> + ctx->buf_ptr += nalu->start_code_length; > >>>> + > >>>> + while (ctx->buf_ptr < ctx->buf_end) { > >>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) > >> == > >>>> 0x00000001) > >>>> + break; > >>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && > >>>> AV_RB24(ctx->buf_ptr) == 0x000001) > >>>> + break; > >>>> + ctx->buf_ptr++; > >>>> + } > >>>> + > >>>> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; > >>>> + nalu->length = ctx->buf_ptr - nalu->data; > >>>> + nalu->type = *nalu->data & 0x1F; > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit > >> *nalu) > >>>> +{ > >>>> + int ret = 0; > >>>> + int rem_bytes; > >>>> + uint8_t *data; > >>>> + uint8_t iv[16]; > >>>> + > >>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + > >>>> + /* Remove start code emulation prevention 0x03 bytes */ > >>>> + remove_scep_3_bytes(nalu); > >>>> + > >>>> + data = nalu->data + 32; > >>>> + rem_bytes = nalu->length - 32; > >>>> + > >>>> + memcpy(iv, crypto_ctx->iv, 16); > >>>> + > >>>> + while (rem_bytes > 0) { > >>>> + if (rem_bytes > 16) { > >>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); > >>>> + data += 16; > >>>> + rem_bytes -= 16; > >>>> + } > >>>> + data += FFMIN(144, rem_bytes); > >>>> + rem_bytes -= FFMIN(144, rem_bytes); > >>>> + } > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket > >>>> *pkt) > >>>> +{ > >>>> + int ret = 0; > >>>> + CodecParserContext ctx; > >>>> + NALUnit nalu; > >>>> + uint8_t *data_ptr; > >>>> + int move_nalu = 0; > >>>> + > >>>> + memset(&ctx, 0, sizeof(ctx)); > >>>> + ctx.buf_ptr = pkt->data; > >>>> + ctx.buf_end = pkt->data + pkt->size; > >>>> + > >>>> + data_ptr = pkt->data; > >>>> + > >>>> + while (ctx.buf_ptr < ctx.buf_end) { > >>>> + memset(&nalu, 0, sizeof(nalu)); > >>>> + ret = get_next_nal_unit(&ctx, &nalu); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > > >> 48) > >>>> { > >>>> + int encrypted_nalu_length = nalu.length; > >>>> + ret = decrypt_nal_unit(crypto_ctx, &nalu); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + move_nalu = nalu.length != encrypted_nalu_length; > >>>> + } > >>>> + if (move_nalu) > >>>> + memmove(data_ptr, nalu.data - nalu.start_code_length, > >>>> nalu.start_code_length + nalu.length); > >>>> + data_ptr += nalu.start_code_length + nalu.length; > >>>> + } > >>>> + > >>>> + av_shrink_packet(pkt, data_ptr - pkt->data); > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame > >> *frame) > >>>> +{ > >>>> + int ret = 0; > >>>> + > >>>> + AACADTSHeaderInfo *adts_hdr = NULL; > >>>> + > >>>> + /* Find next sync word 0xFFF */ > >>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >>>> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == > >> 0xF0) > >>>> + break; > >>>> + ctx->buf_ptr++; > >>>> + } > >>>> + > >>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >>>> + return -1; > >>>> + > >>>> + frame->data = (uint8_t*)ctx->buf_ptr; > >>>> + > >>>> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, > >> ctx->buf_end > >>>> - frame->data); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + > >>>> + frame->header_length = adts_hdr->crc_absent ? > >> AV_AAC_ADTS_HEADER_SIZE > >>>> : AV_AAC_ADTS_HEADER_SIZE + 2; > >>>> + frame->length = adts_hdr->frame_length; > >>>> + > >>>> + av_free(adts_hdr); > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, > >>>> AudioFrame *frame) > >>>> +{ > >>>> + int ret = 0; > >>>> + > >>>> + AC3HeaderInfo *hdr = NULL; > >>>> + > >>>> + /* Find next sync word 0x0B77 */ > >>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >>>> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) > >>>> + break; > >>>> + ctx->buf_ptr++; > >>>> + } > >>>> + > >>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >>>> + return -1; > >>>> + > >>>> + frame->data = (uint8_t*)ctx->buf_ptr; > >>>> + frame->header_length = 0; > >>>> + > >>>> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - > >>>> frame->data); > >>>> + if (ret < 0) { > >>>> + if (ret != AVERROR(ENOMEM)) > >>>> + av_free(hdr); > >>>> + return ret; > >>>> + } > >>>> + > >>>> + frame->length = hdr->frame_size; > >>>> + > >>>> + av_free(hdr); > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int get_next_sync_frame(enum AVCodecID codec_id, > >>>> CodecParserContext *ctx, AudioFrame *frame) > >>>> +{ > >>>> + if (codec_id == AV_CODEC_ID_AAC) > >>>> + return get_next_adts_frame(ctx, frame); > >>>> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == > >> AV_CODEC_ID_EAC3) > >>>> + return get_next_ac3_eac3_sync_frame(ctx, frame); > >>>> + else > >>>> + return AVERROR_INVALIDDATA; > >>>> +} > >>>> + > >>>> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>> *crypto_ctx, AudioFrame *frame) > >>>> +{ > >>>> + int ret = 0; > >>>> + uint8_t *data; > >>>> + int num_of_encrypted_blocks; > >>>> + > >>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + > >>>> + data = frame->data + frame->header_length + 16; > >>>> + > >>>> + num_of_encrypted_blocks = (frame->length - frame->header_length - > >>>> 16)/16; > >>>> + > >>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, > >>>> num_of_encrypted_blocks, crypto_ctx->iv, 1); > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +static int decrypt_audio_frame(enum AVCodecID codec_id, > >> HLSCryptoContext > >>>> *crypto_ctx, AVPacket *pkt) > >>>> +{ > >>>> + int ret = 0; > >>>> + CodecParserContext ctx; > >>>> + AudioFrame frame; > >>>> + > >>>> + memset(&ctx, 0, sizeof(ctx)); > >>>> + ctx.buf_ptr = pkt->data; > >>>> + ctx.buf_end = pkt->data + pkt->size; > >>>> + > >>>> + while (ctx.buf_ptr < ctx.buf_end) { > >>>> + memset(&frame, 0, sizeof(frame)); > >>>> + ret = get_next_sync_frame(codec_id, &ctx, &frame); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + if (frame.length - frame.header_length > 31) { > >>>> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); > >>>> + if (ret < 0) > >>>> + return ret; > >>>> + } > >>>> + ctx.buf_ptr += frame.length; > >>>> + } > >>>> + > >>>> + return 0; > >>>> +} > >>>> + > >>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>> *crypto_ctx, AVPacket *pkt) > >>>> +{ > >>>> + if (codec_id == AV_CODEC_ID_H264) > >>>> + return decrypt_video_frame(crypto_ctx, pkt); > >>>> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 > >>>> || codec_id == AV_CODEC_ID_EAC3) > >>>> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); > >>>> + > >>>> + return AVERROR_INVALIDDATA; > >>>> +} > >>>> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h > >>>> new file mode 100644 > >>>> index 0000000000..cf80e41cb0 > >>>> --- /dev/null > >>>> +++ b/libavformat/hls_sample_aes.h > >>>> @@ -0,0 +1,66 @@ > >>>> +/* > >>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >>>> + * > >>>> + * Copyright (c) 2021 Nachiket Tarate > >>>> + * > >>>> + * This file is part of FFmpeg. > >>>> + * > >>>> + * FFmpeg is free software; you can redistribute it and/or > >>>> + * modify it under the terms of the GNU Lesser General Public > >>>> + * License as published by the Free Software Foundation; either > >>>> + * version 2.1 of the License, or (at your option) any later version. > >>>> + * > >>>> + * FFmpeg is distributed in the hope that it will be useful, > >>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >>>> + * Lesser General Public License for more details. > >>>> + * > >>>> + * You should have received a copy of the GNU Lesser General Public > >>>> + * License along with FFmpeg; if not, write to the Free Software > >>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >>>> 02110-1301 USA > >>>> + */ > >>>> + > >>>> +/** > >>>> + * @file > >>>> + * Apple HTTP Live Streaming Sample Encryption > >>>> + * > >>>> > >> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>> + */ > >>>> + > >>>> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H > >>>> +#define AVFORMAT_HLS_SAMPLE_AES_H > >>>> + > >>>> +#include <stdint.h> > >>>> + > >>>> +#include "avformat.h" > >>>> + > >>>> +#include "libavcodec/avcodec.h" > >>>> +#include "libavutil/aes.h" > >>>> + > >>>> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 > >>>> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 > >>>> + > >>>> + > >>>> +typedef struct HLSCryptoContext { > >>>> + struct AVAES *aes_ctx; > >>>> + uint8_t key[16]; > >>>> + uint8_t iv[16]; > >>>> +} HLSCryptoContext; > >>>> + > >>>> +typedef struct HLSAudioSetupInfo { > >>>> + enum AVCodecID codec_id; > >>>> + uint32_t codec_tag; > >>>> + uint16_t priming; > >>>> + uint8_t version; > >>>> + uint8_t setup_data_length; > >>>> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; > >>>> +} HLSAudioSetupInfo; > >>>> + > >>>> + > >>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > >> uint8_t > >>>> *buf, size_t size); > >>>> + > >>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > >> *info); > >>>> + > >>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>> *crypto_ctx, AVPacket *pkt); > >>>> + > >>>> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ > >>>> + > >>>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c > >>>> index e283ec09d7..dc611ae788 100644 > >>>> --- a/libavformat/mpegts.c > >>>> +++ b/libavformat/mpegts.c > >>>> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { > >>>> { 0 }, > >>>> }; > >>>> > >>>> +/* HLS Sample Encryption Types */ > >>>> +static const StreamType HLS_SAMPLE_ENC_types[] = { > >>>> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, > >>>> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, > >>>> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > >>>> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, > >>>> + { 0 }, > >>>> +}; > >>>> + > >>>> + > >>>> static const StreamType REGD_types[] = { > >>>> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC > >> }, > >>>> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 > >> }, > >>>> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, > >>>> PESContext *pes, > >>>> } > >>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >>>> mpegts_find_stream_type(st, pes->stream_type, MISC_types); > >>>> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >>>> + mpegts_find_stream_type(st, pes->stream_type, > >>>> HLS_SAMPLE_ENC_types); > >>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { > >>>> st->codecpar->codec_id = old_codec_id; > >>>> st->codecpar->codec_type = old_codec_type; > >>>> -- > >>>> 2.17.1 > >>>> > >>>> > >>> _______________________________________________ > >>> ffmpeg-devel mailing list > >>> ffmpeg-devel@ffmpeg.org > >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >>> > >>> To unsubscribe, visit link above, or email > >>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > >> > >> Thanks > >> > >> Steven Liu > >> > >> > >> > >> _______________________________________________ > >> ffmpeg-devel mailing list > >> ffmpeg-devel@ffmpeg.org > >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >> > >> To unsubscribe, visit link above, or email > >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > _______________________________________________ > > ffmpeg-devel mailing list > > ffmpeg-devel@ffmpeg.org > > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > > > To unsubscribe, visit link above, or email > > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > Thanks > > Steven Liu > > > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> 2021年3月1日 下午3:22,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > On Mon, Mar 1, 2021 at 11:30 AM Steven Liu <lq@chinaffmpeg.org> wrote: >> >> >> >>> 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: >>> >>> This is an updated version of the patch in which I have added the check. If >>> the segments are in Fragmented MP4 format, HLS demuxer quits by giving an >>> error message: >>> >>> "SAMPLE-AES encryption is not supported for fragmented MP4 format yet” >> I don’t think is a good resolution for SAMPLE-AES encryption and decryption. >> You should support that if you want support SAMPLE-AES in hls, >> because SAMPLE-AES not only support in MPEG-TS, but also support fragment mp4. >> Whatever, if you only support mpegts en[de]cryption, it should be a half part patch. > > Two completely different technologies/specifications have been used > for SAMPLE-AES encryption of HLS streams in MPEG-TS and fragmented MP4 > formats. > > Fragmented MP4 media segments are encrypted using the 'cbcs' scheme of > Common Encryption [CENC]: > > https://www.iso.org/standard/68042.html > > Encryption of other media segment formats such as MPEG-TS or external > audio tracks containing H.264, AAC, AC-3 and Enhanced AC-3 media > streams is described in the Apple's HTTP Live Streaming (HLS) Sample > Encryption specifications: > > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > This patch implements the later specifications and enables decryption > of media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 formats. So > I think we should merge this patch right now. I think sample ads should support not only mpegts, but also support fmp4 too. Reference rfc8216 context: An encryption method of SAMPLE-AES means that the Media Segments contain media samples, such as audio or video, that are encrypted using the Advanced Encryption Standard [AES_128]. How these media streams are encrypted and encapsulated in a segment depends on the media encoding and the media format of the segment. fMP4 Media Segments are encrypted using the 'cbcs' scheme of Common Encryption [COMMON_ENC]. Encryption of other Media Segment formats containing H.264 [H_264], AAC [ISO_14496], AC-3 [AC_3], and Enhanced AC-3 [AC_3] media streams is described in the HTTP Live Streaming (HLS) Sample Encryption specification [SampleEnc]. The IV attribute MAY be present; see Section 5.2. And I saw the m3u8 context, the METHOD is SAMPLE-AES. (base) liuqi05:ufbuild liuqi$ head -n10 prog_index.m3u8 #EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-VERSION:7 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-INDEPENDENT-SEGMENTS Focus on this line. #EXT-X-KEY:METHOD=SAMPLE-AES,URI="http://127.0.0.1/keyOnly.bin",IV=0xcece273e2737a58ca785e257eb080482 #EXT-X-MAP:URI="fileSequence0.mp4" #EXTINF:8.31667, #EXT-X-BITRATE:7064 You can point me the part if I misunderstood SAMPLE-AES. > > In future, we will add support for CENC or see how can we use existing > things from MOV demuxer. > > Best Regards, > Nachiket Tarate > >>> >>> Best Regards, >>> Nachiket Tarate >>> >>> On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: >>> >>>> >>>> >>>>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: >>>>> >>>>> @Steven Liu <lq@chinaffmpeg.org> >>>>> >>>>> Can we merge this patch ? >>>> I’m waiting update patch for fragment mp4 encryption. >>>> After new version of the patchset I will test and review. >>>>> >>>>> Best Regards, >>>>> Nachiket Tarate >>>>> >>>>> On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < >>>>> nachiket.programmer@gmail.com> wrote: >>>>> >>>>>> Apple HTTP Live Streaming Sample Encryption: >>>>>> >>>>>> >>>>>> >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>>>> >>>>>> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> >>>>>> --- >>>>>> libavformat/Makefile | 2 +- >>>>>> libavformat/hls.c | 105 ++++++++-- >>>>>> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ >>>>>> libavformat/hls_sample_aes.h | 66 ++++++ >>>>>> libavformat/mpegts.c | 12 ++ >>>>>> 5 files changed, 562 insertions(+), 14 deletions(-) >>>>>> create mode 100644 libavformat/hls_sample_aes.c >>>>>> create mode 100644 libavformat/hls_sample_aes.h >>>>>> >>>>>> diff --git a/libavformat/Makefile b/libavformat/Makefile >>>>>> index fcb39ce133..62627d50a6 100644 >>>>>> --- a/libavformat/Makefile >>>>>> +++ b/libavformat/Makefile >>>>>> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o >>>>>> pcm.o >>>>>> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o >>>>>> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o >>>>>> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o >>>>>> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o >>>>>> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o >>>>>> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o >>>>>> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o >>>>>> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o >>>>>> diff --git a/libavformat/hls.c b/libavformat/hls.c >>>>>> index af2468ad9b..3cb3853c79 100644 >>>>>> --- a/libavformat/hls.c >>>>>> +++ b/libavformat/hls.c >>>>>> @@ -2,6 +2,7 @@ >>>>>> * Apple HTTP Live Streaming demuxer >>>>>> * Copyright (c) 2010 Martin Storsjo >>>>>> * Copyright (c) 2013 Anssi Hannula >>>>>> + * Copyright (c) 2021 Nachiket Tarate >>>>>> * >>>>>> * This file is part of FFmpeg. >>>>>> * >>>>>> @@ -39,6 +40,8 @@ >>>>>> #include "avio_internal.h" >>>>>> #include "id3v2.h" >>>>>> >>>>>> +#include "hls_sample_aes.h" >>>>>> + >>>>>> #define INITIAL_BUFFER_SIZE 32768 >>>>>> >>>>>> #define MAX_FIELD_LEN 64 >>>>>> @@ -145,6 +148,10 @@ struct playlist { >>>>>> int id3_changed; /* ID3 tag data has changed at some point */ >>>>>> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer >>>>>> is opened */ >>>>>> >>>>>> + /* Used in case of SAMPLE-AES encryption method */ >>>>>> + HLSAudioSetupInfo audio_setup_info; >>>>>> + HLSCryptoContext crypto_ctx; >>>>>> + >>>>>> int64_t seek_timestamp; >>>>>> int seek_flags; >>>>>> int seek_stream_index; /* into subdemuxer stream array */ >>>>>> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) >>>>>> pls->ctx->pb = NULL; >>>>>> avformat_close_input(&pls->ctx); >>>>>> } >>>>>> + if (pls->crypto_ctx.aes_ctx) >>>>>> + av_free(pls->crypto_ctx.aes_ctx); >>>>>> av_free(pls); >>>>>> } >>>>>> av_freep(&c->playlists); >>>>>> @@ -994,7 +1003,10 @@ fail: >>>>>> >>>>>> static struct segment *current_segment(struct playlist *pls) >>>>>> { >>>>>> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; >>>>>> + int64_t n = pls->cur_seq_no - pls->start_seq_no; >>>>>> + if (n >= pls->n_segments) >>>>>> + return NULL; >>>>>> + return pls->segments[n]; >>>>>> } >>>>>> >>>>>> static struct segment *next_segment(struct playlist *pls) >>>>>> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, >>>>>> struct segment *seg, >>>>>> >>>>>> /* Parse the raw ID3 data and pass contents to caller */ >>>>>> static void parse_id3(AVFormatContext *s, AVIOContext *pb, >>>>>> - AVDictionary **metadata, int64_t *dts, >>>>>> + AVDictionary **metadata, int64_t *dts, >>>>>> HLSAudioSetupInfo *audio_setup_info, >>>>>> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta >>>>>> **extra_meta) >>>>>> { >>>>>> static const char id3_priv_owner_ts[] = >>>>>> "com.apple.streaming.transportStreamTimestamp"; >>>>>> + static const char id3_priv_owner_audio_setup[] = >>>>>> "com.apple.streaming.audioDescription"; >>>>>> ID3v2ExtraMeta *meta; >>>>>> >>>>>> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); >>>>>> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, >>>>>> AVIOContext *pb, >>>>>> *dts = ts; >>>>>> else >>>>>> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio >>>>>> timestamp %"PRId64"\n", ts); >>>>>> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, >>>>>> id3_priv_owner_audio_setup)) { >>>>>> + ff_hls_read_audio_setup_info(audio_setup_info, >>>>>> priv->data, priv->datasize); >>>>>> } >>>>>> } else if (!strcmp(meta->tag, "APIC") && apic) >>>>>> *apic = &meta->data.apic; >>>>>> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct >>>>>> playlist *pls) >>>>>> ID3v2ExtraMeta *extra_meta = NULL; >>>>>> int64_t timestamp = AV_NOPTS_VALUE; >>>>>> >>>>>> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); >>>>>> + parse_id3(pls->ctx, pb, &metadata, ×tamp, >>>>>> &pls->audio_setup_info, &apic, &extra_meta); >>>>>> >>>>>> if (timestamp != AV_NOPTS_VALUE) { >>>>>> pls->id3_mpegts_timestamp = timestamp; >>>>>> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct >>>>>> playlist *pls, struct segment *seg, >>>>>> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', >>>> offset >>>>>> %"PRId64", playlist %d\n", >>>>>> seg->url, seg->url_offset, pls->index); >>>>>> >>>>>> - if (seg->key_type == KEY_NONE) { >>>>>> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >>>>>> &is_http); >>>>>> - } else if (seg->key_type == KEY_AES_128) { >>>>>> - char iv[33], key[33], url[MAX_URL_SIZE]; >>>>>> + if (seg->key_type == KEY_AES_128 || seg->key_type == >>>> KEY_SAMPLE_AES) { >>>>>> if (strcmp(seg->key, pls->key_url)) { >>>>>> AVIOContext *pb = NULL; >>>>>> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, >>>> opts, >>>>>> NULL) == 0) { >>>>>> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct >>>>>> playlist *pls, struct segment *seg, >>>>>> } >>>>>> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); >>>>>> } >>>>>> + } >>>>>> + >>>>>> + if (seg->key_type == KEY_AES_128) { >>>>>> + char iv[33], key[33], url[MAX_URL_SIZE]; >>>>>> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); >>>>>> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); >>>>>> iv[32] = key[32] = '\0'; >>>>>> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct >>>>>> playlist *pls, struct segment *seg, >>>>>> goto cleanup; >>>>>> } >>>>>> ret = 0; >>>>>> - } else if (seg->key_type == KEY_SAMPLE_AES) { >>>>>> - av_log(pls->parent, AV_LOG_ERROR, >>>>>> - "SAMPLE-AES encryption is not supported yet\n"); >>>>>> - ret = AVERROR_PATCHWELCOME; >>>>>> + } else { >>>>>> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, >>>>>> &is_http); >>>>>> } >>>>>> - else >>>>>> - ret = AVERROR(ENOSYS); >>>>>> >>>>>> /* Seek to the requested position. If this was a HTTP request, the >>>>>> offset >>>>>> * should already be where want it to, but this allows e.g. local >>>>>> testing >>>>>> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) >>>>>> struct playlist *pls = c->playlists[i]; >>>>>> char *url; >>>>>> ff_const59 AVInputFormat *in_fmt = NULL; >>>>>> + struct segment *seg = NULL; >>>>>> >>>>>> if (!(pls->ctx = avformat_alloc_context())) { >>>>>> ret = AVERROR(ENOMEM); >>>>>> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) >>>>>> pls->ctx = NULL; >>>>>> goto fail; >>>>>> } >>>>>> + >>>>>> ffio_init_context(&pls->pb, pls->read_buffer, >>>>>> INITIAL_BUFFER_SIZE, 0, pls, >>>>>> read_data, NULL, NULL); >>>>>> + >>>>>> + /* >>>>>> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of >>>>>> + * external audio track that contains audio setup information >>>>>> + */ >>>>>> + seg = current_segment(pls); >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >>>> pls->n_renditions > >>>>>> 0 && >>>>>> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { >>>>>> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; >>>>>> + if ((ret = avio_read(&pls->pb, buf, >>>>>> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { >>>>>> + /* Fail if error was not end of file */ >>>>>> + if (ret != AVERROR_EOF) { >>>>>> + avformat_free_context(pls->ctx); >>>>>> + pls->ctx = NULL; >>>>>> + goto fail; >>>>>> + } >>>>>> + } >>>>>> + ret = 0; >>>>>> + } >>>>>> + >>>>>> + /* >>>>>> + * If encryption scheme is SAMPLE-AES and audio setup >>>> information >>>>>> is present in external audio track, >>>>>> + * use that information to find the media format, otherwise >>>> probe >>>>>> input data >>>>>> + */ >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >>>>>> pls->is_id3_timestamped && >>>>>> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { >>>>>> + void *iter = NULL; >>>>>> + while ((in_fmt = (ff_const59 AVInputFormat >>>>>> *)av_demuxer_iterate(&iter))) >>>>>> + if (in_fmt->raw_codec_id == >>>>>> pls->audio_setup_info.codec_id) { >>>>>> + break; >>>>>> + } >>>>>> + } else { >>>>>> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * >>>> 4; >>>>>> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? >>>>>> s->max_analyze_duration : 4 * AV_TIME_BASE; >>>>>> pls->ctx->interrupt_callback = s->interrupt_callback; >>>>>> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) >>>>>> goto fail; >>>>>> } >>>>>> av_free(url); >>>>>> + } >>>>>> + >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >>>>>> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && >>>>>> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && >>>>>> + strcmp(in_fmt->name, "mpegts")) { >>>>>> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not >>>>>> supported for fragmented MP4 format yet\n"); >>>>>> + ret = AVERROR_PATCHWELCOME; >>>>>> + } else { >>>>>> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); >>>>>> + if (!pls->crypto_ctx.aes_ctx) >>>>>> + ret = AVERROR(ENOMEM); >>>>>> + } >>>>>> + if (ret != 0) { >>>>>> + avformat_free_context(pls->ctx); >>>>>> + pls->ctx = NULL; >>>>>> + goto fail; >>>>>> + } >>>>>> + } >>>>>> + >>>>>> pls->ctx->pb = &pls->pb; >>>>>> pls->ctx->io_open = nested_io_open; >>>>>> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; >>>>>> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) >>>>>> * on us if they want to. >>>>>> */ >>>>>> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && >>>>>> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && >>>>>> pls->audio_setup_info.setup_data_length > 0 && >>>>>> + pls->ctx->nb_streams == 1) >>>>>> + ret = >>>> ff_hls_parse_audio_setup_info(pls->ctx->streams[0], >>>>>> &pls->audio_setup_info); >>>>>> + else >>>>>> ret = avformat_find_stream_info(pls->ctx, NULL); >>>>>> + >>>>>> if (ret < 0) >>>>>> goto fail; >>>>>> } >>>>>> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, >>>>>> AVPacket *pkt) >>>>>> while (1) { >>>>>> int64_t ts_diff; >>>>>> AVRational tb; >>>>>> + struct segment *seg = NULL; >>>>>> ret = av_read_frame(pls->ctx, &pls->pkt); >>>>>> if (ret < 0) { >>>>>> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) >>>>>> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, >>>>>> AVPacket *pkt) >>>>>> get_timebase(pls), AV_TIME_BASE_Q); >>>>>> } >>>>>> >>>>>> + seg = current_segment(pls); >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { >>>>>> + enum AVCodecID codec_id = >>>>>> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; >>>>>> + memcpy(pls->crypto_ctx.iv, seg->iv, >>>> sizeof(seg->iv)); >>>>>> + memcpy(pls->crypto_ctx.key, pls->key, >>>>>> sizeof(pls->key)); >>>>>> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, >>>>>> &pls->pkt); >>>>>> + } >>>>>> + >>>>>> if (pls->seek_timestamp == AV_NOPTS_VALUE) >>>>>> break; >>>>>> >>>>>> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c >>>>>> new file mode 100644 >>>>>> index 0000000000..0407a15b0f >>>>>> --- /dev/null >>>>>> +++ b/libavformat/hls_sample_aes.c >>>>>> @@ -0,0 +1,391 @@ >>>>>> +/* >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption >>>>>> + * >>>>>> + * Copyright (c) 2021 Nachiket Tarate >>>>>> + * >>>>>> + * This file is part of FFmpeg. >>>>>> + * >>>>>> + * FFmpeg is free software; you can redistribute it and/or >>>>>> + * modify it under the terms of the GNU Lesser General Public >>>>>> + * License as published by the Free Software Foundation; either >>>>>> + * version 2.1 of the License, or (at your option) any later version. >>>>>> + * >>>>>> + * FFmpeg is distributed in the hope that it will be useful, >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >>>>>> + * Lesser General Public License for more details. >>>>>> + * >>>>>> + * You should have received a copy of the GNU Lesser General Public >>>>>> + * License along with FFmpeg; if not, write to the Free Software >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >>>>>> 02110-1301 USA >>>>>> + */ >>>>>> + >>>>>> +/** >>>>>> + * @file >>>>>> + * Apple HTTP Live Streaming Sample Encryption >>>>>> + * >>>>>> >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>>>> + */ >>>>>> + >>>>>> +#include "hls_sample_aes.h" >>>>>> + >>>>>> +#include "libavcodec/adts_header.h" >>>>>> +#include "libavcodec/adts_parser.h" >>>>>> +#include "libavcodec/ac3_parser_internal.h" >>>>>> + >>>>>> + >>>>>> +typedef struct NALUnit { >>>>>> + uint8_t *data; >>>>>> + int type; >>>>>> + int length; >>>>>> + int start_code_length; >>>>>> +} NALUnit; >>>>>> + >>>>>> +typedef struct AudioFrame { >>>>>> + uint8_t *data; >>>>>> + int length; >>>>>> + int header_length; >>>>>> +} AudioFrame; >>>>>> + >>>>>> +typedef struct CodecParserContext { >>>>>> + const uint8_t *buf_ptr; >>>>>> + const uint8_t *buf_end; >>>>>> +} CodecParserContext; >>>>>> + >>>>>> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; >>>>>> + >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const >>>> uint8_t >>>>>> *buf, size_t size) >>>>>> +{ >>>>>> + if (size < 8) >>>>>> + return; >>>>>> + >>>>>> + info->codec_tag = AV_RL32(buf); >>>>>> + >>>>>> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) >>>>>> + info->codec_id = AV_CODEC_ID_AAC; >>>>>> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) >>>>>> + info->codec_id = AV_CODEC_ID_AC3; >>>>>> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) >>>>>> + info->codec_id = AV_CODEC_ID_EAC3; >>>>>> + else >>>>>> + info->codec_id = AV_CODEC_ID_NONE; >>>>>> + >>>>>> + buf += 4; >>>>>> + info->priming = AV_RL16(buf); >>>>>> + buf += 2; >>>>>> + info->version = *buf++; >>>>>> + info->setup_data_length = *buf++; >>>>>> + >>>>>> + if (info->setup_data_length > size - 8) >>>>>> + info->setup_data_length = size - 8; >>>>>> + >>>>>> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) >>>>>> + return; >>>>>> + >>>>>> + memcpy(info->setup_data, buf, info->setup_data_length); >>>>>> +} >>>>>> + >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo >>>> *info) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + >>>>>> + st->codecpar->codec_tag = info->codec_tag; >>>>>> + >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) >>>>>> + return 0; >>>>>> + >>>>>> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && >>>>>> st->codecpar->codec_id != AV_CODEC_ID_EAC3) >>>>>> + return AVERROR_INVALIDDATA; >>>>>> + >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { >>>>>> + >>>>>> + AC3HeaderInfo *ac3hdr = NULL; >>>>>> + >>>>>> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, >>>>>> info->setup_data_length); >>>>>> + if (ret < 0) { >>>>>> + if (ret != AVERROR(ENOMEM)) >>>>>> + av_free(ac3hdr); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + st->codecpar->sample_rate = ac3hdr->sample_rate; >>>>>> + st->codecpar->channels = ac3hdr->channels; >>>>>> + st->codecpar->channel_layout = ac3hdr->channel_layout; >>>>>> + st->codecpar->bit_rate = ac3hdr->bit_rate; >>>>>> + >>>>>> + av_free(ac3hdr); >>>>>> + } else { /* Parse 'dec3' EC3SpecificBox */ >>>>>> + >>>>>> + GetBitContext gb; >>>>>> + int data_rate, fscod, acmod, lfeon; >>>>>> + >>>>>> + ret = init_get_bits8(&gb, info->setup_data, >>>>>> info->setup_data_length); >>>>>> + if (ret < 0) >>>>>> + return AVERROR_INVALIDDATA; >>>>>> + >>>>>> + data_rate = get_bits(&gb, 13); >>>>>> + skip_bits(&gb, 3); >>>>>> + fscod = get_bits(&gb, 2); >>>>>> + skip_bits(&gb, 10); >>>>>> + acmod = get_bits(&gb, 3); >>>>>> + lfeon = get_bits(&gb, 1); >>>>>> + >>>>>> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; >>>>>> + >>>>>> + st->codecpar->channel_layout = >>>>>> avpriv_ac3_channel_layout_tab[acmod]; >>>>>> + if (lfeon) >>>>>> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; >>>>>> + >>>>>> + st->codecpar->channels = >>>>>> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); >>>>>> + >>>>>> + st->codecpar->bit_rate = data_rate*1000; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +/* >>>>>> + * Remove start code emulation prevention 0x03 bytes >>>>>> + */ >>>>>> +static void remove_scep_3_bytes(NALUnit *nalu) >>>>>> +{ >>>>>> + int i = 0; >>>>>> + int j = 0; >>>>>> + >>>>>> + uint8_t *data = nalu->data; >>>>>> + >>>>>> + while (i < nalu->length) { >>>>>> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { >>>>>> + data[j++] = data[i++]; >>>>>> + data[j++] = data[i++]; >>>>>> + i++; >>>>>> + } else { >>>>>> + data[j++] = data[i++]; >>>>>> + } >>>>>> + } >>>>>> + >>>>>> + nalu->length = j; >>>>>> +} >>>>>> + >>>>>> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) >>>>>> +{ >>>>>> + const uint8_t *nalu_start = ctx->buf_ptr; >>>>>> + >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == >>>>>> 0x00000001) >>>>>> + nalu->start_code_length = 4; >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) >>>> == >>>>>> 0x000001) >>>>>> + nalu->start_code_length = 3; >>>>>> + else /* No start code at the beginning of the NAL unit */ >>>>>> + return -1; >>>>>> + >>>>>> + ctx->buf_ptr += nalu->start_code_length; >>>>>> + >>>>>> + while (ctx->buf_ptr < ctx->buf_end) { >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) >>>> == >>>>>> 0x00000001) >>>>>> + break; >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && >>>>>> AV_RB24(ctx->buf_ptr) == 0x000001) >>>>>> + break; >>>>>> + ctx->buf_ptr++; >>>>>> + } >>>>>> + >>>>>> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; >>>>>> + nalu->length = ctx->buf_ptr - nalu->data; >>>>>> + nalu->type = *nalu->data & 0x1F; >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit >>>> *nalu) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + int rem_bytes; >>>>>> + uint8_t *data; >>>>>> + uint8_t iv[16]; >>>>>> + >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + >>>>>> + /* Remove start code emulation prevention 0x03 bytes */ >>>>>> + remove_scep_3_bytes(nalu); >>>>>> + >>>>>> + data = nalu->data + 32; >>>>>> + rem_bytes = nalu->length - 32; >>>>>> + >>>>>> + memcpy(iv, crypto_ctx->iv, 16); >>>>>> + >>>>>> + while (rem_bytes > 0) { >>>>>> + if (rem_bytes > 16) { >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); >>>>>> + data += 16; >>>>>> + rem_bytes -= 16; >>>>>> + } >>>>>> + data += FFMIN(144, rem_bytes); >>>>>> + rem_bytes -= FFMIN(144, rem_bytes); >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket >>>>>> *pkt) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + CodecParserContext ctx; >>>>>> + NALUnit nalu; >>>>>> + uint8_t *data_ptr; >>>>>> + int move_nalu = 0; >>>>>> + >>>>>> + memset(&ctx, 0, sizeof(ctx)); >>>>>> + ctx.buf_ptr = pkt->data; >>>>>> + ctx.buf_end = pkt->data + pkt->size; >>>>>> + >>>>>> + data_ptr = pkt->data; >>>>>> + >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { >>>>>> + memset(&nalu, 0, sizeof(nalu)); >>>>>> + ret = get_next_nal_unit(&ctx, &nalu); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > >>>> 48) >>>>>> { >>>>>> + int encrypted_nalu_length = nalu.length; >>>>>> + ret = decrypt_nal_unit(crypto_ctx, &nalu); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + move_nalu = nalu.length != encrypted_nalu_length; >>>>>> + } >>>>>> + if (move_nalu) >>>>>> + memmove(data_ptr, nalu.data - nalu.start_code_length, >>>>>> nalu.start_code_length + nalu.length); >>>>>> + data_ptr += nalu.start_code_length + nalu.length; >>>>>> + } >>>>>> + >>>>>> + av_shrink_packet(pkt, data_ptr - pkt->data); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame >>>> *frame) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + >>>>>> + AACADTSHeaderInfo *adts_hdr = NULL; >>>>>> + >>>>>> + /* Find next sync word 0xFFF */ >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { >>>>>> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == >>>> 0xF0) >>>>>> + break; >>>>>> + ctx->buf_ptr++; >>>>>> + } >>>>>> + >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) >>>>>> + return -1; >>>>>> + >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; >>>>>> + >>>>>> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, >>>> ctx->buf_end >>>>>> - frame->data); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + >>>>>> + frame->header_length = adts_hdr->crc_absent ? >>>> AV_AAC_ADTS_HEADER_SIZE >>>>>> : AV_AAC_ADTS_HEADER_SIZE + 2; >>>>>> + frame->length = adts_hdr->frame_length; >>>>>> + >>>>>> + av_free(adts_hdr); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, >>>>>> AudioFrame *frame) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + >>>>>> + AC3HeaderInfo *hdr = NULL; >>>>>> + >>>>>> + /* Find next sync word 0x0B77 */ >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { >>>>>> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) >>>>>> + break; >>>>>> + ctx->buf_ptr++; >>>>>> + } >>>>>> + >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) >>>>>> + return -1; >>>>>> + >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; >>>>>> + frame->header_length = 0; >>>>>> + >>>>>> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - >>>>>> frame->data); >>>>>> + if (ret < 0) { >>>>>> + if (ret != AVERROR(ENOMEM)) >>>>>> + av_free(hdr); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + frame->length = hdr->frame_size; >>>>>> + >>>>>> + av_free(hdr); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int get_next_sync_frame(enum AVCodecID codec_id, >>>>>> CodecParserContext *ctx, AudioFrame *frame) >>>>>> +{ >>>>>> + if (codec_id == AV_CODEC_ID_AAC) >>>>>> + return get_next_adts_frame(ctx, frame); >>>>>> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == >>>> AV_CODEC_ID_EAC3) >>>>>> + return get_next_ac3_eac3_sync_frame(ctx, frame); >>>>>> + else >>>>>> + return AVERROR_INVALIDDATA; >>>>>> +} >>>>>> + >>>>>> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext >>>>>> *crypto_ctx, AudioFrame *frame) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + uint8_t *data; >>>>>> + int num_of_encrypted_blocks; >>>>>> + >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + >>>>>> + data = frame->data + frame->header_length + 16; >>>>>> + >>>>>> + num_of_encrypted_blocks = (frame->length - frame->header_length - >>>>>> 16)/16; >>>>>> + >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, >>>>>> num_of_encrypted_blocks, crypto_ctx->iv, 1); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int decrypt_audio_frame(enum AVCodecID codec_id, >>>> HLSCryptoContext >>>>>> *crypto_ctx, AVPacket *pkt) >>>>>> +{ >>>>>> + int ret = 0; >>>>>> + CodecParserContext ctx; >>>>>> + AudioFrame frame; >>>>>> + >>>>>> + memset(&ctx, 0, sizeof(ctx)); >>>>>> + ctx.buf_ptr = pkt->data; >>>>>> + ctx.buf_end = pkt->data + pkt->size; >>>>>> + >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { >>>>>> + memset(&frame, 0, sizeof(frame)); >>>>>> + ret = get_next_sync_frame(codec_id, &ctx, &frame); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + if (frame.length - frame.header_length > 31) { >>>>>> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); >>>>>> + if (ret < 0) >>>>>> + return ret; >>>>>> + } >>>>>> + ctx.buf_ptr += frame.length; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >>>>>> *crypto_ctx, AVPacket *pkt) >>>>>> +{ >>>>>> + if (codec_id == AV_CODEC_ID_H264) >>>>>> + return decrypt_video_frame(crypto_ctx, pkt); >>>>>> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 >>>>>> || codec_id == AV_CODEC_ID_EAC3) >>>>>> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); >>>>>> + >>>>>> + return AVERROR_INVALIDDATA; >>>>>> +} >>>>>> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h >>>>>> new file mode 100644 >>>>>> index 0000000000..cf80e41cb0 >>>>>> --- /dev/null >>>>>> +++ b/libavformat/hls_sample_aes.h >>>>>> @@ -0,0 +1,66 @@ >>>>>> +/* >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption >>>>>> + * >>>>>> + * Copyright (c) 2021 Nachiket Tarate >>>>>> + * >>>>>> + * This file is part of FFmpeg. >>>>>> + * >>>>>> + * FFmpeg is free software; you can redistribute it and/or >>>>>> + * modify it under the terms of the GNU Lesser General Public >>>>>> + * License as published by the Free Software Foundation; either >>>>>> + * version 2.1 of the License, or (at your option) any later version. >>>>>> + * >>>>>> + * FFmpeg is distributed in the hope that it will be useful, >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >>>>>> + * Lesser General Public License for more details. >>>>>> + * >>>>>> + * You should have received a copy of the GNU Lesser General Public >>>>>> + * License along with FFmpeg; if not, write to the Free Software >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >>>>>> 02110-1301 USA >>>>>> + */ >>>>>> + >>>>>> +/** >>>>>> + * @file >>>>>> + * Apple HTTP Live Streaming Sample Encryption >>>>>> + * >>>>>> >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption >>>>>> + */ >>>>>> + >>>>>> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H >>>>>> +#define AVFORMAT_HLS_SAMPLE_AES_H >>>>>> + >>>>>> +#include <stdint.h> >>>>>> + >>>>>> +#include "avformat.h" >>>>>> + >>>>>> +#include "libavcodec/avcodec.h" >>>>>> +#include "libavutil/aes.h" >>>>>> + >>>>>> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 >>>>>> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 >>>>>> + >>>>>> + >>>>>> +typedef struct HLSCryptoContext { >>>>>> + struct AVAES *aes_ctx; >>>>>> + uint8_t key[16]; >>>>>> + uint8_t iv[16]; >>>>>> +} HLSCryptoContext; >>>>>> + >>>>>> +typedef struct HLSAudioSetupInfo { >>>>>> + enum AVCodecID codec_id; >>>>>> + uint32_t codec_tag; >>>>>> + uint16_t priming; >>>>>> + uint8_t version; >>>>>> + uint8_t setup_data_length; >>>>>> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; >>>>>> +} HLSAudioSetupInfo; >>>>>> + >>>>>> + >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const >>>> uint8_t >>>>>> *buf, size_t size); >>>>>> + >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo >>>> *info); >>>>>> + >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext >>>>>> *crypto_ctx, AVPacket *pkt); >>>>>> + >>>>>> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ >>>>>> + >>>>>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c >>>>>> index e283ec09d7..dc611ae788 100644 >>>>>> --- a/libavformat/mpegts.c >>>>>> +++ b/libavformat/mpegts.c >>>>>> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { >>>>>> { 0 }, >>>>>> }; >>>>>> >>>>>> +/* HLS Sample Encryption Types */ >>>>>> +static const StreamType HLS_SAMPLE_ENC_types[] = { >>>>>> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, >>>>>> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, >>>>>> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, >>>>>> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, >>>>>> + { 0 }, >>>>>> +}; >>>>>> + >>>>>> + >>>>>> static const StreamType REGD_types[] = { >>>>>> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC >>>> }, >>>>>> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 >>>> }, >>>>>> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, >>>>>> PESContext *pes, >>>>>> } >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >>>>>> mpegts_find_stream_type(st, pes->stream_type, MISC_types); >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) >>>>>> + mpegts_find_stream_type(st, pes->stream_type, >>>>>> HLS_SAMPLE_ENC_types); >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { >>>>>> st->codecpar->codec_id = old_codec_id; >>>>>> st->codecpar->codec_type = old_codec_type; >>>>>> -- >>>>>> 2.17.1 >>>>>> >>>>>> >>>>> _______________________________________________ >>>>> ffmpeg-devel mailing list >>>>> ffmpeg-devel@ffmpeg.org >>>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >>>>> >>>>> To unsubscribe, visit link above, or email >>>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >>>> >>>> Thanks >>>> >>>> Steven Liu >>>> >>>> >>>> >>>> _______________________________________________ >>>> ffmpeg-devel mailing list >>>> ffmpeg-devel@ffmpeg.org >>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >>>> >>>> To unsubscribe, visit link above, or email >>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >>> _______________________________________________ >>> ffmpeg-devel mailing list >>> ffmpeg-devel@ffmpeg.org >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >>> >>> To unsubscribe, visit link above, or email >>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >> >> Thanks >> >> Steven Liu >> >> >> >> _______________________________________________ >> ffmpeg-devel mailing list >> ffmpeg-devel@ffmpeg.org >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >> >> To unsubscribe, visit link above, or email >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". Thanks Steven Liu
On Mon, Mar 1, 2021 at 1:05 PM Steven Liu <lq@chinaffmpeg.org> wrote: > > > > > 2021年3月1日 下午3:22,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > > > On Mon, Mar 1, 2021 at 11:30 AM Steven Liu <lq@chinaffmpeg.org> wrote: > >> > >> > >> > >>> 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > >>> > >>> This is an updated version of the patch in which I have added the check. If > >>> the segments are in Fragmented MP4 format, HLS demuxer quits by giving an > >>> error message: > >>> > >>> "SAMPLE-AES encryption is not supported for fragmented MP4 format yet” > >> I don’t think is a good resolution for SAMPLE-AES encryption and decryption. > >> You should support that if you want support SAMPLE-AES in hls, > >> because SAMPLE-AES not only support in MPEG-TS, but also support fragment mp4. > >> Whatever, if you only support mpegts en[de]cryption, it should be a half part patch. > > > > Two completely different technologies/specifications have been used > > for SAMPLE-AES encryption of HLS streams in MPEG-TS and fragmented MP4 > > formats. > > > > Fragmented MP4 media segments are encrypted using the 'cbcs' scheme of > > Common Encryption [CENC]: > > > > https://www.iso.org/standard/68042.html > > > > Encryption of other media segment formats such as MPEG-TS or external > > audio tracks containing H.264, AAC, AC-3 and Enhanced AC-3 media > > streams is described in the Apple's HTTP Live Streaming (HLS) Sample > > Encryption specifications: > > > > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > > > This patch implements the later specifications and enables decryption > > of media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 formats. So > > I think we should merge this patch right now. > > I think sample ads should support not only mpegts, but also support fmp4 too. > Reference rfc8216 context: > An encryption method of SAMPLE-AES means that the Media Segments > contain media samples, such as audio or video, that are encrypted > using the Advanced Encryption Standard [AES_128]. How these media > streams are encrypted and encapsulated in a segment depends on the > media encoding and the media format of the segment. fMP4 Media > Segments are encrypted using the 'cbcs' scheme of Common > Encryption [COMMON_ENC]. Encryption of other Media Segment > formats containing H.264 [H_264], AAC [ISO_14496], AC-3 [AC_3], > and Enhanced AC-3 [AC_3] media streams is described in the HTTP > Live Streaming (HLS) Sample Encryption specification [SampleEnc]. > The IV attribute MAY be present; see Section 5.2. > > And I saw the m3u8 context, the METHOD is SAMPLE-AES. > > (base) liuqi05:ufbuild liuqi$ head -n10 prog_index.m3u8 > #EXTM3U > #EXT-X-TARGETDURATION:10 > #EXT-X-VERSION:7 > #EXT-X-MEDIA-SEQUENCE:0 > #EXT-X-PLAYLIST-TYPE:VOD > #EXT-X-INDEPENDENT-SEGMENTS > > Focus on this line. > #EXT-X-KEY:METHOD=SAMPLE-AES,URI="http://127.0.0.1/keyOnly.bin",IV=0xcece273e2737a58ca785e257eb080482 > > > #EXT-X-MAP:URI="fileSequence0.mp4" > #EXTINF:8.31667, > #EXT-X-BITRATE:7064 > > > You can point me the part if I misunderstood SAMPLE-AES. Your understanding of SAMPLE-AES is absolutely correct. I am insisting that let us add support for it in 2 phases as completely 2 different specifications have been used. Phase 1 : Media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 formats (Apple's specifications) Phase 2 : Media segments in fragmented MP4 format (CENC specifications) Kindly let me know your opinion. > > > > In future, we will add support for CENC or see how can we use existing > > things from MOV demuxer. > > > > Best Regards, > > Nachiket Tarate > > > >>> > >>> Best Regards, > >>> Nachiket Tarate > >>> > >>> On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: > >>> > >>>> > >>>> > >>>>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > >>>>> > >>>>> @Steven Liu <lq@chinaffmpeg.org> > >>>>> > >>>>> Can we merge this patch ? > >>>> I’m waiting update patch for fragment mp4 encryption. > >>>> After new version of the patchset I will test and review. > >>>>> > >>>>> Best Regards, > >>>>> Nachiket Tarate > >>>>> > >>>>> On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < > >>>>> nachiket.programmer@gmail.com> wrote: > >>>>> > >>>>>> Apple HTTP Live Streaming Sample Encryption: > >>>>>> > >>>>>> > >>>>>> > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>>>> > >>>>>> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> > >>>>>> --- > >>>>>> libavformat/Makefile | 2 +- > >>>>>> libavformat/hls.c | 105 ++++++++-- > >>>>>> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ > >>>>>> libavformat/hls_sample_aes.h | 66 ++++++ > >>>>>> libavformat/mpegts.c | 12 ++ > >>>>>> 5 files changed, 562 insertions(+), 14 deletions(-) > >>>>>> create mode 100644 libavformat/hls_sample_aes.c > >>>>>> create mode 100644 libavformat/hls_sample_aes.h > >>>>>> > >>>>>> diff --git a/libavformat/Makefile b/libavformat/Makefile > >>>>>> index fcb39ce133..62627d50a6 100644 > >>>>>> --- a/libavformat/Makefile > >>>>>> +++ b/libavformat/Makefile > >>>>>> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o > >>>>>> pcm.o > >>>>>> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o > >>>>>> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o > >>>>>> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o > >>>>>> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o > >>>>>> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o > >>>>>> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o > >>>>>> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o > >>>>>> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o > >>>>>> diff --git a/libavformat/hls.c b/libavformat/hls.c > >>>>>> index af2468ad9b..3cb3853c79 100644 > >>>>>> --- a/libavformat/hls.c > >>>>>> +++ b/libavformat/hls.c > >>>>>> @@ -2,6 +2,7 @@ > >>>>>> * Apple HTTP Live Streaming demuxer > >>>>>> * Copyright (c) 2010 Martin Storsjo > >>>>>> * Copyright (c) 2013 Anssi Hannula > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > >>>>>> * > >>>>>> * This file is part of FFmpeg. > >>>>>> * > >>>>>> @@ -39,6 +40,8 @@ > >>>>>> #include "avio_internal.h" > >>>>>> #include "id3v2.h" > >>>>>> > >>>>>> +#include "hls_sample_aes.h" > >>>>>> + > >>>>>> #define INITIAL_BUFFER_SIZE 32768 > >>>>>> > >>>>>> #define MAX_FIELD_LEN 64 > >>>>>> @@ -145,6 +148,10 @@ struct playlist { > >>>>>> int id3_changed; /* ID3 tag data has changed at some point */ > >>>>>> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer > >>>>>> is opened */ > >>>>>> > >>>>>> + /* Used in case of SAMPLE-AES encryption method */ > >>>>>> + HLSAudioSetupInfo audio_setup_info; > >>>>>> + HLSCryptoContext crypto_ctx; > >>>>>> + > >>>>>> int64_t seek_timestamp; > >>>>>> int seek_flags; > >>>>>> int seek_stream_index; /* into subdemuxer stream array */ > >>>>>> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) > >>>>>> pls->ctx->pb = NULL; > >>>>>> avformat_close_input(&pls->ctx); > >>>>>> } > >>>>>> + if (pls->crypto_ctx.aes_ctx) > >>>>>> + av_free(pls->crypto_ctx.aes_ctx); > >>>>>> av_free(pls); > >>>>>> } > >>>>>> av_freep(&c->playlists); > >>>>>> @@ -994,7 +1003,10 @@ fail: > >>>>>> > >>>>>> static struct segment *current_segment(struct playlist *pls) > >>>>>> { > >>>>>> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; > >>>>>> + int64_t n = pls->cur_seq_no - pls->start_seq_no; > >>>>>> + if (n >= pls->n_segments) > >>>>>> + return NULL; > >>>>>> + return pls->segments[n]; > >>>>>> } > >>>>>> > >>>>>> static struct segment *next_segment(struct playlist *pls) > >>>>>> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, > >>>>>> struct segment *seg, > >>>>>> > >>>>>> /* Parse the raw ID3 data and pass contents to caller */ > >>>>>> static void parse_id3(AVFormatContext *s, AVIOContext *pb, > >>>>>> - AVDictionary **metadata, int64_t *dts, > >>>>>> + AVDictionary **metadata, int64_t *dts, > >>>>>> HLSAudioSetupInfo *audio_setup_info, > >>>>>> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta > >>>>>> **extra_meta) > >>>>>> { > >>>>>> static const char id3_priv_owner_ts[] = > >>>>>> "com.apple.streaming.transportStreamTimestamp"; > >>>>>> + static const char id3_priv_owner_audio_setup[] = > >>>>>> "com.apple.streaming.audioDescription"; > >>>>>> ID3v2ExtraMeta *meta; > >>>>>> > >>>>>> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); > >>>>>> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, > >>>>>> AVIOContext *pb, > >>>>>> *dts = ts; > >>>>>> else > >>>>>> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio > >>>>>> timestamp %"PRId64"\n", ts); > >>>>>> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, > >>>>>> id3_priv_owner_audio_setup)) { > >>>>>> + ff_hls_read_audio_setup_info(audio_setup_info, > >>>>>> priv->data, priv->datasize); > >>>>>> } > >>>>>> } else if (!strcmp(meta->tag, "APIC") && apic) > >>>>>> *apic = &meta->data.apic; > >>>>>> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct > >>>>>> playlist *pls) > >>>>>> ID3v2ExtraMeta *extra_meta = NULL; > >>>>>> int64_t timestamp = AV_NOPTS_VALUE; > >>>>>> > >>>>>> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); > >>>>>> + parse_id3(pls->ctx, pb, &metadata, ×tamp, > >>>>>> &pls->audio_setup_info, &apic, &extra_meta); > >>>>>> > >>>>>> if (timestamp != AV_NOPTS_VALUE) { > >>>>>> pls->id3_mpegts_timestamp = timestamp; > >>>>>> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct > >>>>>> playlist *pls, struct segment *seg, > >>>>>> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', > >>>> offset > >>>>>> %"PRId64", playlist %d\n", > >>>>>> seg->url, seg->url_offset, pls->index); > >>>>>> > >>>>>> - if (seg->key_type == KEY_NONE) { > >>>>>> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >>>>>> &is_http); > >>>>>> - } else if (seg->key_type == KEY_AES_128) { > >>>>>> - char iv[33], key[33], url[MAX_URL_SIZE]; > >>>>>> + if (seg->key_type == KEY_AES_128 || seg->key_type == > >>>> KEY_SAMPLE_AES) { > >>>>>> if (strcmp(seg->key, pls->key_url)) { > >>>>>> AVIOContext *pb = NULL; > >>>>>> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, > >>>> opts, > >>>>>> NULL) == 0) { > >>>>>> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct > >>>>>> playlist *pls, struct segment *seg, > >>>>>> } > >>>>>> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); > >>>>>> } > >>>>>> + } > >>>>>> + > >>>>>> + if (seg->key_type == KEY_AES_128) { > >>>>>> + char iv[33], key[33], url[MAX_URL_SIZE]; > >>>>>> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); > >>>>>> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); > >>>>>> iv[32] = key[32] = '\0'; > >>>>>> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct > >>>>>> playlist *pls, struct segment *seg, > >>>>>> goto cleanup; > >>>>>> } > >>>>>> ret = 0; > >>>>>> - } else if (seg->key_type == KEY_SAMPLE_AES) { > >>>>>> - av_log(pls->parent, AV_LOG_ERROR, > >>>>>> - "SAMPLE-AES encryption is not supported yet\n"); > >>>>>> - ret = AVERROR_PATCHWELCOME; > >>>>>> + } else { > >>>>>> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > >>>>>> &is_http); > >>>>>> } > >>>>>> - else > >>>>>> - ret = AVERROR(ENOSYS); > >>>>>> > >>>>>> /* Seek to the requested position. If this was a HTTP request, the > >>>>>> offset > >>>>>> * should already be where want it to, but this allows e.g. local > >>>>>> testing > >>>>>> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) > >>>>>> struct playlist *pls = c->playlists[i]; > >>>>>> char *url; > >>>>>> ff_const59 AVInputFormat *in_fmt = NULL; > >>>>>> + struct segment *seg = NULL; > >>>>>> > >>>>>> if (!(pls->ctx = avformat_alloc_context())) { > >>>>>> ret = AVERROR(ENOMEM); > >>>>>> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) > >>>>>> pls->ctx = NULL; > >>>>>> goto fail; > >>>>>> } > >>>>>> + > >>>>>> ffio_init_context(&pls->pb, pls->read_buffer, > >>>>>> INITIAL_BUFFER_SIZE, 0, pls, > >>>>>> read_data, NULL, NULL); > >>>>>> + > >>>>>> + /* > >>>>>> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of > >>>>>> + * external audio track that contains audio setup information > >>>>>> + */ > >>>>>> + seg = current_segment(pls); > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >>>> pls->n_renditions > > >>>>>> 0 && > >>>>>> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { > >>>>>> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; > >>>>>> + if ((ret = avio_read(&pls->pb, buf, > >>>>>> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { > >>>>>> + /* Fail if error was not end of file */ > >>>>>> + if (ret != AVERROR_EOF) { > >>>>>> + avformat_free_context(pls->ctx); > >>>>>> + pls->ctx = NULL; > >>>>>> + goto fail; > >>>>>> + } > >>>>>> + } > >>>>>> + ret = 0; > >>>>>> + } > >>>>>> + > >>>>>> + /* > >>>>>> + * If encryption scheme is SAMPLE-AES and audio setup > >>>> information > >>>>>> is present in external audio track, > >>>>>> + * use that information to find the media format, otherwise > >>>> probe > >>>>>> input data > >>>>>> + */ > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >>>>>> pls->is_id3_timestamped && > >>>>>> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { > >>>>>> + void *iter = NULL; > >>>>>> + while ((in_fmt = (ff_const59 AVInputFormat > >>>>>> *)av_demuxer_iterate(&iter))) > >>>>>> + if (in_fmt->raw_codec_id == > >>>>>> pls->audio_setup_info.codec_id) { > >>>>>> + break; > >>>>>> + } > >>>>>> + } else { > >>>>>> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * > >>>> 4; > >>>>>> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? > >>>>>> s->max_analyze_duration : 4 * AV_TIME_BASE; > >>>>>> pls->ctx->interrupt_callback = s->interrupt_callback; > >>>>>> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) > >>>>>> goto fail; > >>>>>> } > >>>>>> av_free(url); > >>>>>> + } > >>>>>> + > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >>>>>> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && > >>>>>> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && > >>>>>> + strcmp(in_fmt->name, "mpegts")) { > >>>>>> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not > >>>>>> supported for fragmented MP4 format yet\n"); > >>>>>> + ret = AVERROR_PATCHWELCOME; > >>>>>> + } else { > >>>>>> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); > >>>>>> + if (!pls->crypto_ctx.aes_ctx) > >>>>>> + ret = AVERROR(ENOMEM); > >>>>>> + } > >>>>>> + if (ret != 0) { > >>>>>> + avformat_free_context(pls->ctx); > >>>>>> + pls->ctx = NULL; > >>>>>> + goto fail; > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> pls->ctx->pb = &pls->pb; > >>>>>> pls->ctx->io_open = nested_io_open; > >>>>>> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; > >>>>>> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) > >>>>>> * on us if they want to. > >>>>>> */ > >>>>>> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && > >>>>>> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > >>>>>> pls->audio_setup_info.setup_data_length > 0 && > >>>>>> + pls->ctx->nb_streams == 1) > >>>>>> + ret = > >>>> ff_hls_parse_audio_setup_info(pls->ctx->streams[0], > >>>>>> &pls->audio_setup_info); > >>>>>> + else > >>>>>> ret = avformat_find_stream_info(pls->ctx, NULL); > >>>>>> + > >>>>>> if (ret < 0) > >>>>>> goto fail; > >>>>>> } > >>>>>> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, > >>>>>> AVPacket *pkt) > >>>>>> while (1) { > >>>>>> int64_t ts_diff; > >>>>>> AVRational tb; > >>>>>> + struct segment *seg = NULL; > >>>>>> ret = av_read_frame(pls->ctx, &pls->pkt); > >>>>>> if (ret < 0) { > >>>>>> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) > >>>>>> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, > >>>>>> AVPacket *pkt) > >>>>>> get_timebase(pls), AV_TIME_BASE_Q); > >>>>>> } > >>>>>> > >>>>>> + seg = current_segment(pls); > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > >>>>>> + enum AVCodecID codec_id = > >>>>>> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; > >>>>>> + memcpy(pls->crypto_ctx.iv, seg->iv, > >>>> sizeof(seg->iv)); > >>>>>> + memcpy(pls->crypto_ctx.key, pls->key, > >>>>>> sizeof(pls->key)); > >>>>>> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, > >>>>>> &pls->pkt); > >>>>>> + } > >>>>>> + > >>>>>> if (pls->seek_timestamp == AV_NOPTS_VALUE) > >>>>>> break; > >>>>>> > >>>>>> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c > >>>>>> new file mode 100644 > >>>>>> index 0000000000..0407a15b0f > >>>>>> --- /dev/null > >>>>>> +++ b/libavformat/hls_sample_aes.c > >>>>>> @@ -0,0 +1,391 @@ > >>>>>> +/* > >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >>>>>> + * > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > >>>>>> + * > >>>>>> + * This file is part of FFmpeg. > >>>>>> + * > >>>>>> + * FFmpeg is free software; you can redistribute it and/or > >>>>>> + * modify it under the terms of the GNU Lesser General Public > >>>>>> + * License as published by the Free Software Foundation; either > >>>>>> + * version 2.1 of the License, or (at your option) any later version. > >>>>>> + * > >>>>>> + * FFmpeg is distributed in the hope that it will be useful, > >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >>>>>> + * Lesser General Public License for more details. > >>>>>> + * > >>>>>> + * You should have received a copy of the GNU Lesser General Public > >>>>>> + * License along with FFmpeg; if not, write to the Free Software > >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >>>>>> 02110-1301 USA > >>>>>> + */ > >>>>>> + > >>>>>> +/** > >>>>>> + * @file > >>>>>> + * Apple HTTP Live Streaming Sample Encryption > >>>>>> + * > >>>>>> > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>>>> + */ > >>>>>> + > >>>>>> +#include "hls_sample_aes.h" > >>>>>> + > >>>>>> +#include "libavcodec/adts_header.h" > >>>>>> +#include "libavcodec/adts_parser.h" > >>>>>> +#include "libavcodec/ac3_parser_internal.h" > >>>>>> + > >>>>>> + > >>>>>> +typedef struct NALUnit { > >>>>>> + uint8_t *data; > >>>>>> + int type; > >>>>>> + int length; > >>>>>> + int start_code_length; > >>>>>> +} NALUnit; > >>>>>> + > >>>>>> +typedef struct AudioFrame { > >>>>>> + uint8_t *data; > >>>>>> + int length; > >>>>>> + int header_length; > >>>>>> +} AudioFrame; > >>>>>> + > >>>>>> +typedef struct CodecParserContext { > >>>>>> + const uint8_t *buf_ptr; > >>>>>> + const uint8_t *buf_end; > >>>>>> +} CodecParserContext; > >>>>>> + > >>>>>> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; > >>>>>> + > >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > >>>> uint8_t > >>>>>> *buf, size_t size) > >>>>>> +{ > >>>>>> + if (size < 8) > >>>>>> + return; > >>>>>> + > >>>>>> + info->codec_tag = AV_RL32(buf); > >>>>>> + > >>>>>> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) > >>>>>> + info->codec_id = AV_CODEC_ID_AAC; > >>>>>> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) > >>>>>> + info->codec_id = AV_CODEC_ID_AC3; > >>>>>> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) > >>>>>> + info->codec_id = AV_CODEC_ID_EAC3; > >>>>>> + else > >>>>>> + info->codec_id = AV_CODEC_ID_NONE; > >>>>>> + > >>>>>> + buf += 4; > >>>>>> + info->priming = AV_RL16(buf); > >>>>>> + buf += 2; > >>>>>> + info->version = *buf++; > >>>>>> + info->setup_data_length = *buf++; > >>>>>> + > >>>>>> + if (info->setup_data_length > size - 8) > >>>>>> + info->setup_data_length = size - 8; > >>>>>> + > >>>>>> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) > >>>>>> + return; > >>>>>> + > >>>>>> + memcpy(info->setup_data, buf, info->setup_data_length); > >>>>>> +} > >>>>>> + > >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > >>>> *info) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + > >>>>>> + st->codecpar->codec_tag = info->codec_tag; > >>>>>> + > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) > >>>>>> + return 0; > >>>>>> + > >>>>>> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && > >>>>>> st->codecpar->codec_id != AV_CODEC_ID_EAC3) > >>>>>> + return AVERROR_INVALIDDATA; > >>>>>> + > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { > >>>>>> + > >>>>>> + AC3HeaderInfo *ac3hdr = NULL; > >>>>>> + > >>>>>> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, > >>>>>> info->setup_data_length); > >>>>>> + if (ret < 0) { > >>>>>> + if (ret != AVERROR(ENOMEM)) > >>>>>> + av_free(ac3hdr); > >>>>>> + return ret; > >>>>>> + } > >>>>>> + > >>>>>> + st->codecpar->sample_rate = ac3hdr->sample_rate; > >>>>>> + st->codecpar->channels = ac3hdr->channels; > >>>>>> + st->codecpar->channel_layout = ac3hdr->channel_layout; > >>>>>> + st->codecpar->bit_rate = ac3hdr->bit_rate; > >>>>>> + > >>>>>> + av_free(ac3hdr); > >>>>>> + } else { /* Parse 'dec3' EC3SpecificBox */ > >>>>>> + > >>>>>> + GetBitContext gb; > >>>>>> + int data_rate, fscod, acmod, lfeon; > >>>>>> + > >>>>>> + ret = init_get_bits8(&gb, info->setup_data, > >>>>>> info->setup_data_length); > >>>>>> + if (ret < 0) > >>>>>> + return AVERROR_INVALIDDATA; > >>>>>> + > >>>>>> + data_rate = get_bits(&gb, 13); > >>>>>> + skip_bits(&gb, 3); > >>>>>> + fscod = get_bits(&gb, 2); > >>>>>> + skip_bits(&gb, 10); > >>>>>> + acmod = get_bits(&gb, 3); > >>>>>> + lfeon = get_bits(&gb, 1); > >>>>>> + > >>>>>> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; > >>>>>> + > >>>>>> + st->codecpar->channel_layout = > >>>>>> avpriv_ac3_channel_layout_tab[acmod]; > >>>>>> + if (lfeon) > >>>>>> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; > >>>>>> + > >>>>>> + st->codecpar->channels = > >>>>>> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); > >>>>>> + > >>>>>> + st->codecpar->bit_rate = data_rate*1000; > >>>>>> + } > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +/* > >>>>>> + * Remove start code emulation prevention 0x03 bytes > >>>>>> + */ > >>>>>> +static void remove_scep_3_bytes(NALUnit *nalu) > >>>>>> +{ > >>>>>> + int i = 0; > >>>>>> + int j = 0; > >>>>>> + > >>>>>> + uint8_t *data = nalu->data; > >>>>>> + > >>>>>> + while (i < nalu->length) { > >>>>>> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { > >>>>>> + data[j++] = data[i++]; > >>>>>> + data[j++] = data[i++]; > >>>>>> + i++; > >>>>>> + } else { > >>>>>> + data[j++] = data[i++]; > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> + nalu->length = j; > >>>>>> +} > >>>>>> + > >>>>>> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) > >>>>>> +{ > >>>>>> + const uint8_t *nalu_start = ctx->buf_ptr; > >>>>>> + > >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > >>>>>> 0x00000001) > >>>>>> + nalu->start_code_length = 4; > >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) > >>>> == > >>>>>> 0x000001) > >>>>>> + nalu->start_code_length = 3; > >>>>>> + else /* No start code at the beginning of the NAL unit */ > >>>>>> + return -1; > >>>>>> + > >>>>>> + ctx->buf_ptr += nalu->start_code_length; > >>>>>> + > >>>>>> + while (ctx->buf_ptr < ctx->buf_end) { > >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) > >>>> == > >>>>>> 0x00000001) > >>>>>> + break; > >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && > >>>>>> AV_RB24(ctx->buf_ptr) == 0x000001) > >>>>>> + break; > >>>>>> + ctx->buf_ptr++; > >>>>>> + } > >>>>>> + > >>>>>> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; > >>>>>> + nalu->length = ctx->buf_ptr - nalu->data; > >>>>>> + nalu->type = *nalu->data & 0x1F; > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit > >>>> *nalu) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + int rem_bytes; > >>>>>> + uint8_t *data; > >>>>>> + uint8_t iv[16]; > >>>>>> + > >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + > >>>>>> + /* Remove start code emulation prevention 0x03 bytes */ > >>>>>> + remove_scep_3_bytes(nalu); > >>>>>> + > >>>>>> + data = nalu->data + 32; > >>>>>> + rem_bytes = nalu->length - 32; > >>>>>> + > >>>>>> + memcpy(iv, crypto_ctx->iv, 16); > >>>>>> + > >>>>>> + while (rem_bytes > 0) { > >>>>>> + if (rem_bytes > 16) { > >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); > >>>>>> + data += 16; > >>>>>> + rem_bytes -= 16; > >>>>>> + } > >>>>>> + data += FFMIN(144, rem_bytes); > >>>>>> + rem_bytes -= FFMIN(144, rem_bytes); > >>>>>> + } > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket > >>>>>> *pkt) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + CodecParserContext ctx; > >>>>>> + NALUnit nalu; > >>>>>> + uint8_t *data_ptr; > >>>>>> + int move_nalu = 0; > >>>>>> + > >>>>>> + memset(&ctx, 0, sizeof(ctx)); > >>>>>> + ctx.buf_ptr = pkt->data; > >>>>>> + ctx.buf_end = pkt->data + pkt->size; > >>>>>> + > >>>>>> + data_ptr = pkt->data; > >>>>>> + > >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { > >>>>>> + memset(&nalu, 0, sizeof(nalu)); > >>>>>> + ret = get_next_nal_unit(&ctx, &nalu); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > > >>>> 48) > >>>>>> { > >>>>>> + int encrypted_nalu_length = nalu.length; > >>>>>> + ret = decrypt_nal_unit(crypto_ctx, &nalu); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + move_nalu = nalu.length != encrypted_nalu_length; > >>>>>> + } > >>>>>> + if (move_nalu) > >>>>>> + memmove(data_ptr, nalu.data - nalu.start_code_length, > >>>>>> nalu.start_code_length + nalu.length); > >>>>>> + data_ptr += nalu.start_code_length + nalu.length; > >>>>>> + } > >>>>>> + > >>>>>> + av_shrink_packet(pkt, data_ptr - pkt->data); > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame > >>>> *frame) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + > >>>>>> + AACADTSHeaderInfo *adts_hdr = NULL; > >>>>>> + > >>>>>> + /* Find next sync word 0xFFF */ > >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >>>>>> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == > >>>> 0xF0) > >>>>>> + break; > >>>>>> + ctx->buf_ptr++; > >>>>>> + } > >>>>>> + > >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >>>>>> + return -1; > >>>>>> + > >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; > >>>>>> + > >>>>>> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, > >>>> ctx->buf_end > >>>>>> - frame->data); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + > >>>>>> + frame->header_length = adts_hdr->crc_absent ? > >>>> AV_AAC_ADTS_HEADER_SIZE > >>>>>> : AV_AAC_ADTS_HEADER_SIZE + 2; > >>>>>> + frame->length = adts_hdr->frame_length; > >>>>>> + > >>>>>> + av_free(adts_hdr); > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, > >>>>>> AudioFrame *frame) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + > >>>>>> + AC3HeaderInfo *hdr = NULL; > >>>>>> + > >>>>>> + /* Find next sync word 0x0B77 */ > >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > >>>>>> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) > >>>>>> + break; > >>>>>> + ctx->buf_ptr++; > >>>>>> + } > >>>>>> + > >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > >>>>>> + return -1; > >>>>>> + > >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; > >>>>>> + frame->header_length = 0; > >>>>>> + > >>>>>> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - > >>>>>> frame->data); > >>>>>> + if (ret < 0) { > >>>>>> + if (ret != AVERROR(ENOMEM)) > >>>>>> + av_free(hdr); > >>>>>> + return ret; > >>>>>> + } > >>>>>> + > >>>>>> + frame->length = hdr->frame_size; > >>>>>> + > >>>>>> + av_free(hdr); > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int get_next_sync_frame(enum AVCodecID codec_id, > >>>>>> CodecParserContext *ctx, AudioFrame *frame) > >>>>>> +{ > >>>>>> + if (codec_id == AV_CODEC_ID_AAC) > >>>>>> + return get_next_adts_frame(ctx, frame); > >>>>>> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == > >>>> AV_CODEC_ID_EAC3) > >>>>>> + return get_next_ac3_eac3_sync_frame(ctx, frame); > >>>>>> + else > >>>>>> + return AVERROR_INVALIDDATA; > >>>>>> +} > >>>>>> + > >>>>>> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>>>> *crypto_ctx, AudioFrame *frame) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + uint8_t *data; > >>>>>> + int num_of_encrypted_blocks; > >>>>>> + > >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + > >>>>>> + data = frame->data + frame->header_length + 16; > >>>>>> + > >>>>>> + num_of_encrypted_blocks = (frame->length - frame->header_length - > >>>>>> 16)/16; > >>>>>> + > >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, > >>>>>> num_of_encrypted_blocks, crypto_ctx->iv, 1); > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +static int decrypt_audio_frame(enum AVCodecID codec_id, > >>>> HLSCryptoContext > >>>>>> *crypto_ctx, AVPacket *pkt) > >>>>>> +{ > >>>>>> + int ret = 0; > >>>>>> + CodecParserContext ctx; > >>>>>> + AudioFrame frame; > >>>>>> + > >>>>>> + memset(&ctx, 0, sizeof(ctx)); > >>>>>> + ctx.buf_ptr = pkt->data; > >>>>>> + ctx.buf_end = pkt->data + pkt->size; > >>>>>> + > >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { > >>>>>> + memset(&frame, 0, sizeof(frame)); > >>>>>> + ret = get_next_sync_frame(codec_id, &ctx, &frame); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + if (frame.length - frame.header_length > 31) { > >>>>>> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); > >>>>>> + if (ret < 0) > >>>>>> + return ret; > >>>>>> + } > >>>>>> + ctx.buf_ptr += frame.length; > >>>>>> + } > >>>>>> + > >>>>>> + return 0; > >>>>>> +} > >>>>>> + > >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>>>> *crypto_ctx, AVPacket *pkt) > >>>>>> +{ > >>>>>> + if (codec_id == AV_CODEC_ID_H264) > >>>>>> + return decrypt_video_frame(crypto_ctx, pkt); > >>>>>> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 > >>>>>> || codec_id == AV_CODEC_ID_EAC3) > >>>>>> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); > >>>>>> + > >>>>>> + return AVERROR_INVALIDDATA; > >>>>>> +} > >>>>>> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h > >>>>>> new file mode 100644 > >>>>>> index 0000000000..cf80e41cb0 > >>>>>> --- /dev/null > >>>>>> +++ b/libavformat/hls_sample_aes.h > >>>>>> @@ -0,0 +1,66 @@ > >>>>>> +/* > >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > >>>>>> + * > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > >>>>>> + * > >>>>>> + * This file is part of FFmpeg. > >>>>>> + * > >>>>>> + * FFmpeg is free software; you can redistribute it and/or > >>>>>> + * modify it under the terms of the GNU Lesser General Public > >>>>>> + * License as published by the Free Software Foundation; either > >>>>>> + * version 2.1 of the License, or (at your option) any later version. > >>>>>> + * > >>>>>> + * FFmpeg is distributed in the hope that it will be useful, > >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > >>>>>> + * Lesser General Public License for more details. > >>>>>> + * > >>>>>> + * You should have received a copy of the GNU Lesser General Public > >>>>>> + * License along with FFmpeg; if not, write to the Free Software > >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > >>>>>> 02110-1301 USA > >>>>>> + */ > >>>>>> + > >>>>>> +/** > >>>>>> + * @file > >>>>>> + * Apple HTTP Live Streaming Sample Encryption > >>>>>> + * > >>>>>> > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > >>>>>> + */ > >>>>>> + > >>>>>> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H > >>>>>> +#define AVFORMAT_HLS_SAMPLE_AES_H > >>>>>> + > >>>>>> +#include <stdint.h> > >>>>>> + > >>>>>> +#include "avformat.h" > >>>>>> + > >>>>>> +#include "libavcodec/avcodec.h" > >>>>>> +#include "libavutil/aes.h" > >>>>>> + > >>>>>> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 > >>>>>> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 > >>>>>> + > >>>>>> + > >>>>>> +typedef struct HLSCryptoContext { > >>>>>> + struct AVAES *aes_ctx; > >>>>>> + uint8_t key[16]; > >>>>>> + uint8_t iv[16]; > >>>>>> +} HLSCryptoContext; > >>>>>> + > >>>>>> +typedef struct HLSAudioSetupInfo { > >>>>>> + enum AVCodecID codec_id; > >>>>>> + uint32_t codec_tag; > >>>>>> + uint16_t priming; > >>>>>> + uint8_t version; > >>>>>> + uint8_t setup_data_length; > >>>>>> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; > >>>>>> +} HLSAudioSetupInfo; > >>>>>> + > >>>>>> + > >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > >>>> uint8_t > >>>>>> *buf, size_t size); > >>>>>> + > >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > >>>> *info); > >>>>>> + > >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > >>>>>> *crypto_ctx, AVPacket *pkt); > >>>>>> + > >>>>>> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ > >>>>>> + > >>>>>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c > >>>>>> index e283ec09d7..dc611ae788 100644 > >>>>>> --- a/libavformat/mpegts.c > >>>>>> +++ b/libavformat/mpegts.c > >>>>>> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { > >>>>>> { 0 }, > >>>>>> }; > >>>>>> > >>>>>> +/* HLS Sample Encryption Types */ > >>>>>> +static const StreamType HLS_SAMPLE_ENC_types[] = { > >>>>>> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, > >>>>>> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, > >>>>>> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > >>>>>> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, > >>>>>> + { 0 }, > >>>>>> +}; > >>>>>> + > >>>>>> + > >>>>>> static const StreamType REGD_types[] = { > >>>>>> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC > >>>> }, > >>>>>> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 > >>>> }, > >>>>>> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, > >>>>>> PESContext *pes, > >>>>>> } > >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >>>>>> mpegts_find_stream_type(st, pes->stream_type, MISC_types); > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > >>>>>> + mpegts_find_stream_type(st, pes->stream_type, > >>>>>> HLS_SAMPLE_ENC_types); > >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { > >>>>>> st->codecpar->codec_id = old_codec_id; > >>>>>> st->codecpar->codec_type = old_codec_type; > >>>>>> -- > >>>>>> 2.17.1 > >>>>>> > >>>>>> > >>>>> _______________________________________________ > >>>>> ffmpeg-devel mailing list > >>>>> ffmpeg-devel@ffmpeg.org > >>>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >>>>> > >>>>> To unsubscribe, visit link above, or email > >>>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > >>>> > >>>> Thanks > >>>> > >>>> Steven Liu > >>>> > >>>> > >>>> > >>>> _______________________________________________ > >>>> ffmpeg-devel mailing list > >>>> ffmpeg-devel@ffmpeg.org > >>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >>>> > >>>> To unsubscribe, visit link above, or email > >>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > >>> _______________________________________________ > >>> ffmpeg-devel mailing list > >>> ffmpeg-devel@ffmpeg.org > >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >>> > >>> To unsubscribe, visit link above, or email > >>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > >> > >> Thanks > >> > >> Steven Liu > >> > >> > >> > >> _______________________________________________ > >> ffmpeg-devel mailing list > >> ffmpeg-devel@ffmpeg.org > >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >> > >> To unsubscribe, visit link above, or email > >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > Thanks > > Steven Liu > > >
On Wed, Mar 3, 2021 at 11:15 AM Nachiket Tarate <nachiket.programmer@gmail.com> wrote: > > On Mon, Mar 1, 2021 at 1:05 PM Steven Liu <lq@chinaffmpeg.org> wrote: > > > > > > > > > 2021年3月1日 下午3:22,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > > > > > On Mon, Mar 1, 2021 at 11:30 AM Steven Liu <lq@chinaffmpeg.org> wrote: > > >> > > >> > > >> > > >>> 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > >>> > > >>> This is an updated version of the patch in which I have added the check. If > > >>> the segments are in Fragmented MP4 format, HLS demuxer quits by giving an > > >>> error message: > > >>> > > >>> "SAMPLE-AES encryption is not supported for fragmented MP4 format yet” > > >> I don’t think is a good resolution for SAMPLE-AES encryption and decryption. > > >> You should support that if you want support SAMPLE-AES in hls, > > >> because SAMPLE-AES not only support in MPEG-TS, but also support fragment mp4. > > >> Whatever, if you only support mpegts en[de]cryption, it should be a half part patch. > > > > > > Two completely different technologies/specifications have been used > > > for SAMPLE-AES encryption of HLS streams in MPEG-TS and fragmented MP4 > > > formats. > > > > > > Fragmented MP4 media segments are encrypted using the 'cbcs' scheme of > > > Common Encryption [CENC]: > > > > > > https://www.iso.org/standard/68042.html > > > > > > Encryption of other media segment formats such as MPEG-TS or external > > > audio tracks containing H.264, AAC, AC-3 and Enhanced AC-3 media > > > streams is described in the Apple's HTTP Live Streaming (HLS) Sample > > > Encryption specifications: > > > > > > https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > > > > > This patch implements the later specifications and enables decryption > > > of media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 formats. So > > > I think we should merge this patch right now. > > > > I think sample ads should support not only mpegts, but also support fmp4 too. > > Reference rfc8216 context: > > An encryption method of SAMPLE-AES means that the Media Segments > > contain media samples, such as audio or video, that are encrypted > > using the Advanced Encryption Standard [AES_128]. How these media > > streams are encrypted and encapsulated in a segment depends on the > > media encoding and the media format of the segment. fMP4 Media > > Segments are encrypted using the 'cbcs' scheme of Common > > Encryption [COMMON_ENC]. Encryption of other Media Segment > > formats containing H.264 [H_264], AAC [ISO_14496], AC-3 [AC_3], > > and Enhanced AC-3 [AC_3] media streams is described in the HTTP > > Live Streaming (HLS) Sample Encryption specification [SampleEnc]. > > The IV attribute MAY be present; see Section 5.2. > > > > And I saw the m3u8 context, the METHOD is SAMPLE-AES. > > > > (base) liuqi05:ufbuild liuqi$ head -n10 prog_index.m3u8 > > #EXTM3U > > #EXT-X-TARGETDURATION:10 > > #EXT-X-VERSION:7 > > #EXT-X-MEDIA-SEQUENCE:0 > > #EXT-X-PLAYLIST-TYPE:VOD > > #EXT-X-INDEPENDENT-SEGMENTS > > > > Focus on this line. > > #EXT-X-KEY:METHOD=SAMPLE-AES,URI="http://127.0.0.1/keyOnly.bin",IV=0xcece273e2737a58ca785e257eb080482 > > > > > > #EXT-X-MAP:URI="fileSequence0.mp4" > > #EXTINF:8.31667, > > #EXT-X-BITRATE:7064 > > > > > > You can point me the part if I misunderstood SAMPLE-AES. > > Your understanding of SAMPLE-AES is absolutely correct. > > I am insisting that let us add support for it in 2 phases as > completely 2 different specifications have been used. > > Phase 1 : Media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 > formats (Apple's specifications) > > Phase 2 : Media segments in fragmented MP4 format (CENC specifications) > > Kindly let me know your opinion. Hello Steven, I am looking forward to hearing from you. Best Regards, Nachiket > > > > > > > In future, we will add support for CENC or see how can we use existing > > > things from MOV demuxer. > > > > > > Best Regards, > > > Nachiket Tarate > > > > > >>> > > >>> Best Regards, > > >>> Nachiket Tarate > > >>> > > >>> On Mon, Mar 1, 2021 at 10:13 AM Steven Liu <lq@chinaffmpeg.org> wrote: > > >>> > > >>>> > > >>>> > > >>>>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: > > >>>>> > > >>>>> @Steven Liu <lq@chinaffmpeg.org> > > >>>>> > > >>>>> Can we merge this patch ? > > >>>> I’m waiting update patch for fragment mp4 encryption. > > >>>> After new version of the patchset I will test and review. > > >>>>> > > >>>>> Best Regards, > > >>>>> Nachiket Tarate > > >>>>> > > >>>>> On Wed, Feb 24, 2021 at 4:44 PM Nachiket Tarate < > > >>>>> nachiket.programmer@gmail.com> wrote: > > >>>>> > > >>>>>> Apple HTTP Live Streaming Sample Encryption: > > >>>>>> > > >>>>>> > > >>>>>> > > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > >>>>>> > > >>>>>> Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> > > >>>>>> --- > > >>>>>> libavformat/Makefile | 2 +- > > >>>>>> libavformat/hls.c | 105 ++++++++-- > > >>>>>> libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ > > >>>>>> libavformat/hls_sample_aes.h | 66 ++++++ > > >>>>>> libavformat/mpegts.c | 12 ++ > > >>>>>> 5 files changed, 562 insertions(+), 14 deletions(-) > > >>>>>> create mode 100644 libavformat/hls_sample_aes.c > > >>>>>> create mode 100644 libavformat/hls_sample_aes.h > > >>>>>> > > >>>>>> diff --git a/libavformat/Makefile b/libavformat/Makefile > > >>>>>> index fcb39ce133..62627d50a6 100644 > > >>>>>> --- a/libavformat/Makefile > > >>>>>> +++ b/libavformat/Makefile > > >>>>>> @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o > > >>>>>> pcm.o > > >>>>>> OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o > > >>>>>> OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o > > >>>>>> OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o > > >>>>>> -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o > > >>>>>> +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o > > >>>>>> OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o > > >>>>>> OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o > > >>>>>> OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o > > >>>>>> diff --git a/libavformat/hls.c b/libavformat/hls.c > > >>>>>> index af2468ad9b..3cb3853c79 100644 > > >>>>>> --- a/libavformat/hls.c > > >>>>>> +++ b/libavformat/hls.c > > >>>>>> @@ -2,6 +2,7 @@ > > >>>>>> * Apple HTTP Live Streaming demuxer > > >>>>>> * Copyright (c) 2010 Martin Storsjo > > >>>>>> * Copyright (c) 2013 Anssi Hannula > > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > > >>>>>> * > > >>>>>> * This file is part of FFmpeg. > > >>>>>> * > > >>>>>> @@ -39,6 +40,8 @@ > > >>>>>> #include "avio_internal.h" > > >>>>>> #include "id3v2.h" > > >>>>>> > > >>>>>> +#include "hls_sample_aes.h" > > >>>>>> + > > >>>>>> #define INITIAL_BUFFER_SIZE 32768 > > >>>>>> > > >>>>>> #define MAX_FIELD_LEN 64 > > >>>>>> @@ -145,6 +148,10 @@ struct playlist { > > >>>>>> int id3_changed; /* ID3 tag data has changed at some point */ > > >>>>>> ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer > > >>>>>> is opened */ > > >>>>>> > > >>>>>> + /* Used in case of SAMPLE-AES encryption method */ > > >>>>>> + HLSAudioSetupInfo audio_setup_info; > > >>>>>> + HLSCryptoContext crypto_ctx; > > >>>>>> + > > >>>>>> int64_t seek_timestamp; > > >>>>>> int seek_flags; > > >>>>>> int seek_stream_index; /* into subdemuxer stream array */ > > >>>>>> @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) > > >>>>>> pls->ctx->pb = NULL; > > >>>>>> avformat_close_input(&pls->ctx); > > >>>>>> } > > >>>>>> + if (pls->crypto_ctx.aes_ctx) > > >>>>>> + av_free(pls->crypto_ctx.aes_ctx); > > >>>>>> av_free(pls); > > >>>>>> } > > >>>>>> av_freep(&c->playlists); > > >>>>>> @@ -994,7 +1003,10 @@ fail: > > >>>>>> > > >>>>>> static struct segment *current_segment(struct playlist *pls) > > >>>>>> { > > >>>>>> - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; > > >>>>>> + int64_t n = pls->cur_seq_no - pls->start_seq_no; > > >>>>>> + if (n >= pls->n_segments) > > >>>>>> + return NULL; > > >>>>>> + return pls->segments[n]; > > >>>>>> } > > >>>>>> > > >>>>>> static struct segment *next_segment(struct playlist *pls) > > >>>>>> @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, > > >>>>>> struct segment *seg, > > >>>>>> > > >>>>>> /* Parse the raw ID3 data and pass contents to caller */ > > >>>>>> static void parse_id3(AVFormatContext *s, AVIOContext *pb, > > >>>>>> - AVDictionary **metadata, int64_t *dts, > > >>>>>> + AVDictionary **metadata, int64_t *dts, > > >>>>>> HLSAudioSetupInfo *audio_setup_info, > > >>>>>> ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta > > >>>>>> **extra_meta) > > >>>>>> { > > >>>>>> static const char id3_priv_owner_ts[] = > > >>>>>> "com.apple.streaming.transportStreamTimestamp"; > > >>>>>> + static const char id3_priv_owner_audio_setup[] = > > >>>>>> "com.apple.streaming.audioDescription"; > > >>>>>> ID3v2ExtraMeta *meta; > > >>>>>> > > >>>>>> ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); > > >>>>>> @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, > > >>>>>> AVIOContext *pb, > > >>>>>> *dts = ts; > > >>>>>> else > > >>>>>> av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio > > >>>>>> timestamp %"PRId64"\n", ts); > > >>>>>> + } else if (priv->datasize >= 8 && !strcmp(priv->owner, > > >>>>>> id3_priv_owner_audio_setup)) { > > >>>>>> + ff_hls_read_audio_setup_info(audio_setup_info, > > >>>>>> priv->data, priv->datasize); > > >>>>>> } > > >>>>>> } else if (!strcmp(meta->tag, "APIC") && apic) > > >>>>>> *apic = &meta->data.apic; > > >>>>>> @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct > > >>>>>> playlist *pls) > > >>>>>> ID3v2ExtraMeta *extra_meta = NULL; > > >>>>>> int64_t timestamp = AV_NOPTS_VALUE; > > >>>>>> > > >>>>>> - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); > > >>>>>> + parse_id3(pls->ctx, pb, &metadata, ×tamp, > > >>>>>> &pls->audio_setup_info, &apic, &extra_meta); > > >>>>>> > > >>>>>> if (timestamp != AV_NOPTS_VALUE) { > > >>>>>> pls->id3_mpegts_timestamp = timestamp; > > >>>>>> @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct > > >>>>>> playlist *pls, struct segment *seg, > > >>>>>> av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', > > >>>> offset > > >>>>>> %"PRId64", playlist %d\n", > > >>>>>> seg->url, seg->url_offset, pls->index); > > >>>>>> > > >>>>>> - if (seg->key_type == KEY_NONE) { > > >>>>>> - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > > >>>>>> &is_http); > > >>>>>> - } else if (seg->key_type == KEY_AES_128) { > > >>>>>> - char iv[33], key[33], url[MAX_URL_SIZE]; > > >>>>>> + if (seg->key_type == KEY_AES_128 || seg->key_type == > > >>>> KEY_SAMPLE_AES) { > > >>>>>> if (strcmp(seg->key, pls->key_url)) { > > >>>>>> AVIOContext *pb = NULL; > > >>>>>> if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, > > >>>> opts, > > >>>>>> NULL) == 0) { > > >>>>>> @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct > > >>>>>> playlist *pls, struct segment *seg, > > >>>>>> } > > >>>>>> av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); > > >>>>>> } > > >>>>>> + } > > >>>>>> + > > >>>>>> + if (seg->key_type == KEY_AES_128) { > > >>>>>> + char iv[33], key[33], url[MAX_URL_SIZE]; > > >>>>>> ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); > > >>>>>> ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); > > >>>>>> iv[32] = key[32] = '\0'; > > >>>>>> @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct > > >>>>>> playlist *pls, struct segment *seg, > > >>>>>> goto cleanup; > > >>>>>> } > > >>>>>> ret = 0; > > >>>>>> - } else if (seg->key_type == KEY_SAMPLE_AES) { > > >>>>>> - av_log(pls->parent, AV_LOG_ERROR, > > >>>>>> - "SAMPLE-AES encryption is not supported yet\n"); > > >>>>>> - ret = AVERROR_PATCHWELCOME; > > >>>>>> + } else { > > >>>>>> + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, > > >>>>>> &is_http); > > >>>>>> } > > >>>>>> - else > > >>>>>> - ret = AVERROR(ENOSYS); > > >>>>>> > > >>>>>> /* Seek to the requested position. If this was a HTTP request, the > > >>>>>> offset > > >>>>>> * should already be where want it to, but this allows e.g. local > > >>>>>> testing > > >>>>>> @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) > > >>>>>> struct playlist *pls = c->playlists[i]; > > >>>>>> char *url; > > >>>>>> ff_const59 AVInputFormat *in_fmt = NULL; > > >>>>>> + struct segment *seg = NULL; > > >>>>>> > > >>>>>> if (!(pls->ctx = avformat_alloc_context())) { > > >>>>>> ret = AVERROR(ENOMEM); > > >>>>>> @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) > > >>>>>> pls->ctx = NULL; > > >>>>>> goto fail; > > >>>>>> } > > >>>>>> + > > >>>>>> ffio_init_context(&pls->pb, pls->read_buffer, > > >>>>>> INITIAL_BUFFER_SIZE, 0, pls, > > >>>>>> read_data, NULL, NULL); > > >>>>>> + > > >>>>>> + /* > > >>>>>> + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of > > >>>>>> + * external audio track that contains audio setup information > > >>>>>> + */ > > >>>>>> + seg = current_segment(pls); > > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > > >>>> pls->n_renditions > > > >>>>>> 0 && > > >>>>>> + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { > > >>>>>> + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; > > >>>>>> + if ((ret = avio_read(&pls->pb, buf, > > >>>>>> HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { > > >>>>>> + /* Fail if error was not end of file */ > > >>>>>> + if (ret != AVERROR_EOF) { > > >>>>>> + avformat_free_context(pls->ctx); > > >>>>>> + pls->ctx = NULL; > > >>>>>> + goto fail; > > >>>>>> + } > > >>>>>> + } > > >>>>>> + ret = 0; > > >>>>>> + } > > >>>>>> + > > >>>>>> + /* > > >>>>>> + * If encryption scheme is SAMPLE-AES and audio setup > > >>>> information > > >>>>>> is present in external audio track, > > >>>>>> + * use that information to find the media format, otherwise > > >>>> probe > > >>>>>> input data > > >>>>>> + */ > > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > > >>>>>> pls->is_id3_timestamped && > > >>>>>> + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { > > >>>>>> + void *iter = NULL; > > >>>>>> + while ((in_fmt = (ff_const59 AVInputFormat > > >>>>>> *)av_demuxer_iterate(&iter))) > > >>>>>> + if (in_fmt->raw_codec_id == > > >>>>>> pls->audio_setup_info.codec_id) { > > >>>>>> + break; > > >>>>>> + } > > >>>>>> + } else { > > >>>>>> pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * > > >>>> 4; > > >>>>>> pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? > > >>>>>> s->max_analyze_duration : 4 * AV_TIME_BASE; > > >>>>>> pls->ctx->interrupt_callback = s->interrupt_callback; > > >>>>>> @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) > > >>>>>> goto fail; > > >>>>>> } > > >>>>>> av_free(url); > > >>>>>> + } > > >>>>>> + > > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > > >>>>>> + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && > > >>>>>> pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && > > >>>>>> + strcmp(in_fmt->name, "mpegts")) { > > >>>>>> + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not > > >>>>>> supported for fragmented MP4 format yet\n"); > > >>>>>> + ret = AVERROR_PATCHWELCOME; > > >>>>>> + } else { > > >>>>>> + pls->crypto_ctx.aes_ctx = av_aes_alloc(); > > >>>>>> + if (!pls->crypto_ctx.aes_ctx) > > >>>>>> + ret = AVERROR(ENOMEM); > > >>>>>> + } > > >>>>>> + if (ret != 0) { > > >>>>>> + avformat_free_context(pls->ctx); > > >>>>>> + pls->ctx = NULL; > > >>>>>> + goto fail; > > >>>>>> + } > > >>>>>> + } > > >>>>>> + > > >>>>>> pls->ctx->pb = &pls->pb; > > >>>>>> pls->ctx->io_open = nested_io_open; > > >>>>>> pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; > > >>>>>> @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) > > >>>>>> * on us if they want to. > > >>>>>> */ > > >>>>>> if (pls->is_id3_timestamped || (pls->n_renditions > 0 && > > >>>>>> pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { > > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES && > > >>>>>> pls->audio_setup_info.setup_data_length > 0 && > > >>>>>> + pls->ctx->nb_streams == 1) > > >>>>>> + ret = > > >>>> ff_hls_parse_audio_setup_info(pls->ctx->streams[0], > > >>>>>> &pls->audio_setup_info); > > >>>>>> + else > > >>>>>> ret = avformat_find_stream_info(pls->ctx, NULL); > > >>>>>> + > > >>>>>> if (ret < 0) > > >>>>>> goto fail; > > >>>>>> } > > >>>>>> @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, > > >>>>>> AVPacket *pkt) > > >>>>>> while (1) { > > >>>>>> int64_t ts_diff; > > >>>>>> AVRational tb; > > >>>>>> + struct segment *seg = NULL; > > >>>>>> ret = av_read_frame(pls->ctx, &pls->pkt); > > >>>>>> if (ret < 0) { > > >>>>>> if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) > > >>>>>> @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, > > >>>>>> AVPacket *pkt) > > >>>>>> get_timebase(pls), AV_TIME_BASE_Q); > > >>>>>> } > > >>>>>> > > >>>>>> + seg = current_segment(pls); > > >>>>>> + if (seg && seg->key_type == KEY_SAMPLE_AES) { > > >>>>>> + enum AVCodecID codec_id = > > >>>>>> pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; > > >>>>>> + memcpy(pls->crypto_ctx.iv, seg->iv, > > >>>> sizeof(seg->iv)); > > >>>>>> + memcpy(pls->crypto_ctx.key, pls->key, > > >>>>>> sizeof(pls->key)); > > >>>>>> + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, > > >>>>>> &pls->pkt); > > >>>>>> + } > > >>>>>> + > > >>>>>> if (pls->seek_timestamp == AV_NOPTS_VALUE) > > >>>>>> break; > > >>>>>> > > >>>>>> diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c > > >>>>>> new file mode 100644 > > >>>>>> index 0000000000..0407a15b0f > > >>>>>> --- /dev/null > > >>>>>> +++ b/libavformat/hls_sample_aes.c > > >>>>>> @@ -0,0 +1,391 @@ > > >>>>>> +/* > > >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > > >>>>>> + * > > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > > >>>>>> + * > > >>>>>> + * This file is part of FFmpeg. > > >>>>>> + * > > >>>>>> + * FFmpeg is free software; you can redistribute it and/or > > >>>>>> + * modify it under the terms of the GNU Lesser General Public > > >>>>>> + * License as published by the Free Software Foundation; either > > >>>>>> + * version 2.1 of the License, or (at your option) any later version. > > >>>>>> + * > > >>>>>> + * FFmpeg is distributed in the hope that it will be useful, > > >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > > >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > >>>>>> + * Lesser General Public License for more details. > > >>>>>> + * > > >>>>>> + * You should have received a copy of the GNU Lesser General Public > > >>>>>> + * License along with FFmpeg; if not, write to the Free Software > > >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > > >>>>>> 02110-1301 USA > > >>>>>> + */ > > >>>>>> + > > >>>>>> +/** > > >>>>>> + * @file > > >>>>>> + * Apple HTTP Live Streaming Sample Encryption > > >>>>>> + * > > >>>>>> > > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > >>>>>> + */ > > >>>>>> + > > >>>>>> +#include "hls_sample_aes.h" > > >>>>>> + > > >>>>>> +#include "libavcodec/adts_header.h" > > >>>>>> +#include "libavcodec/adts_parser.h" > > >>>>>> +#include "libavcodec/ac3_parser_internal.h" > > >>>>>> + > > >>>>>> + > > >>>>>> +typedef struct NALUnit { > > >>>>>> + uint8_t *data; > > >>>>>> + int type; > > >>>>>> + int length; > > >>>>>> + int start_code_length; > > >>>>>> +} NALUnit; > > >>>>>> + > > >>>>>> +typedef struct AudioFrame { > > >>>>>> + uint8_t *data; > > >>>>>> + int length; > > >>>>>> + int header_length; > > >>>>>> +} AudioFrame; > > >>>>>> + > > >>>>>> +typedef struct CodecParserContext { > > >>>>>> + const uint8_t *buf_ptr; > > >>>>>> + const uint8_t *buf_end; > > >>>>>> +} CodecParserContext; > > >>>>>> + > > >>>>>> +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; > > >>>>>> + > > >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > > >>>> uint8_t > > >>>>>> *buf, size_t size) > > >>>>>> +{ > > >>>>>> + if (size < 8) > > >>>>>> + return; > > >>>>>> + > > >>>>>> + info->codec_tag = AV_RL32(buf); > > >>>>>> + > > >>>>>> + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) > > >>>>>> + info->codec_id = AV_CODEC_ID_AAC; > > >>>>>> + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) > > >>>>>> + info->codec_id = AV_CODEC_ID_AC3; > > >>>>>> + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) > > >>>>>> + info->codec_id = AV_CODEC_ID_EAC3; > > >>>>>> + else > > >>>>>> + info->codec_id = AV_CODEC_ID_NONE; > > >>>>>> + > > >>>>>> + buf += 4; > > >>>>>> + info->priming = AV_RL16(buf); > > >>>>>> + buf += 2; > > >>>>>> + info->version = *buf++; > > >>>>>> + info->setup_data_length = *buf++; > > >>>>>> + > > >>>>>> + if (info->setup_data_length > size - 8) > > >>>>>> + info->setup_data_length = size - 8; > > >>>>>> + > > >>>>>> + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) > > >>>>>> + return; > > >>>>>> + > > >>>>>> + memcpy(info->setup_data, buf, info->setup_data_length); > > >>>>>> +} > > >>>>>> + > > >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > > >>>> *info) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + > > >>>>>> + st->codecpar->codec_tag = info->codec_tag; > > >>>>>> + > > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) > > >>>>>> + return 0; > > >>>>>> + > > >>>>>> + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && > > >>>>>> st->codecpar->codec_id != AV_CODEC_ID_EAC3) > > >>>>>> + return AVERROR_INVALIDDATA; > > >>>>>> + > > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { > > >>>>>> + > > >>>>>> + AC3HeaderInfo *ac3hdr = NULL; > > >>>>>> + > > >>>>>> + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, > > >>>>>> info->setup_data_length); > > >>>>>> + if (ret < 0) { > > >>>>>> + if (ret != AVERROR(ENOMEM)) > > >>>>>> + av_free(ac3hdr); > > >>>>>> + return ret; > > >>>>>> + } > > >>>>>> + > > >>>>>> + st->codecpar->sample_rate = ac3hdr->sample_rate; > > >>>>>> + st->codecpar->channels = ac3hdr->channels; > > >>>>>> + st->codecpar->channel_layout = ac3hdr->channel_layout; > > >>>>>> + st->codecpar->bit_rate = ac3hdr->bit_rate; > > >>>>>> + > > >>>>>> + av_free(ac3hdr); > > >>>>>> + } else { /* Parse 'dec3' EC3SpecificBox */ > > >>>>>> + > > >>>>>> + GetBitContext gb; > > >>>>>> + int data_rate, fscod, acmod, lfeon; > > >>>>>> + > > >>>>>> + ret = init_get_bits8(&gb, info->setup_data, > > >>>>>> info->setup_data_length); > > >>>>>> + if (ret < 0) > > >>>>>> + return AVERROR_INVALIDDATA; > > >>>>>> + > > >>>>>> + data_rate = get_bits(&gb, 13); > > >>>>>> + skip_bits(&gb, 3); > > >>>>>> + fscod = get_bits(&gb, 2); > > >>>>>> + skip_bits(&gb, 10); > > >>>>>> + acmod = get_bits(&gb, 3); > > >>>>>> + lfeon = get_bits(&gb, 1); > > >>>>>> + > > >>>>>> + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; > > >>>>>> + > > >>>>>> + st->codecpar->channel_layout = > > >>>>>> avpriv_ac3_channel_layout_tab[acmod]; > > >>>>>> + if (lfeon) > > >>>>>> + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; > > >>>>>> + > > >>>>>> + st->codecpar->channels = > > >>>>>> av_get_channel_layout_nb_channels(st->codecpar->channel_layout); > > >>>>>> + > > >>>>>> + st->codecpar->bit_rate = data_rate*1000; > > >>>>>> + } > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +/* > > >>>>>> + * Remove start code emulation prevention 0x03 bytes > > >>>>>> + */ > > >>>>>> +static void remove_scep_3_bytes(NALUnit *nalu) > > >>>>>> +{ > > >>>>>> + int i = 0; > > >>>>>> + int j = 0; > > >>>>>> + > > >>>>>> + uint8_t *data = nalu->data; > > >>>>>> + > > >>>>>> + while (i < nalu->length) { > > >>>>>> + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { > > >>>>>> + data[j++] = data[i++]; > > >>>>>> + data[j++] = data[i++]; > > >>>>>> + i++; > > >>>>>> + } else { > > >>>>>> + data[j++] = data[i++]; > > >>>>>> + } > > >>>>>> + } > > >>>>>> + > > >>>>>> + nalu->length = j; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) > > >>>>>> +{ > > >>>>>> + const uint8_t *nalu_start = ctx->buf_ptr; > > >>>>>> + > > >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == > > >>>>>> 0x00000001) > > >>>>>> + nalu->start_code_length = 4; > > >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) > > >>>> == > > >>>>>> 0x000001) > > >>>>>> + nalu->start_code_length = 3; > > >>>>>> + else /* No start code at the beginning of the NAL unit */ > > >>>>>> + return -1; > > >>>>>> + > > >>>>>> + ctx->buf_ptr += nalu->start_code_length; > > >>>>>> + > > >>>>>> + while (ctx->buf_ptr < ctx->buf_end) { > > >>>>>> + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) > > >>>> == > > >>>>>> 0x00000001) > > >>>>>> + break; > > >>>>>> + else if (ctx->buf_end - ctx->buf_ptr >= 3 && > > >>>>>> AV_RB24(ctx->buf_ptr) == 0x000001) > > >>>>>> + break; > > >>>>>> + ctx->buf_ptr++; > > >>>>>> + } > > >>>>>> + > > >>>>>> + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; > > >>>>>> + nalu->length = ctx->buf_ptr - nalu->data; > > >>>>>> + nalu->type = *nalu->data & 0x1F; > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit > > >>>> *nalu) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + int rem_bytes; > > >>>>>> + uint8_t *data; > > >>>>>> + uint8_t iv[16]; > > >>>>>> + > > >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + > > >>>>>> + /* Remove start code emulation prevention 0x03 bytes */ > > >>>>>> + remove_scep_3_bytes(nalu); > > >>>>>> + > > >>>>>> + data = nalu->data + 32; > > >>>>>> + rem_bytes = nalu->length - 32; > > >>>>>> + > > >>>>>> + memcpy(iv, crypto_ctx->iv, 16); > > >>>>>> + > > >>>>>> + while (rem_bytes > 0) { > > >>>>>> + if (rem_bytes > 16) { > > >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); > > >>>>>> + data += 16; > > >>>>>> + rem_bytes -= 16; > > >>>>>> + } > > >>>>>> + data += FFMIN(144, rem_bytes); > > >>>>>> + rem_bytes -= FFMIN(144, rem_bytes); > > >>>>>> + } > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket > > >>>>>> *pkt) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + CodecParserContext ctx; > > >>>>>> + NALUnit nalu; > > >>>>>> + uint8_t *data_ptr; > > >>>>>> + int move_nalu = 0; > > >>>>>> + > > >>>>>> + memset(&ctx, 0, sizeof(ctx)); > > >>>>>> + ctx.buf_ptr = pkt->data; > > >>>>>> + ctx.buf_end = pkt->data + pkt->size; > > >>>>>> + > > >>>>>> + data_ptr = pkt->data; > > >>>>>> + > > >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { > > >>>>>> + memset(&nalu, 0, sizeof(nalu)); > > >>>>>> + ret = get_next_nal_unit(&ctx, &nalu); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > > > >>>> 48) > > >>>>>> { > > >>>>>> + int encrypted_nalu_length = nalu.length; > > >>>>>> + ret = decrypt_nal_unit(crypto_ctx, &nalu); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + move_nalu = nalu.length != encrypted_nalu_length; > > >>>>>> + } > > >>>>>> + if (move_nalu) > > >>>>>> + memmove(data_ptr, nalu.data - nalu.start_code_length, > > >>>>>> nalu.start_code_length + nalu.length); > > >>>>>> + data_ptr += nalu.start_code_length + nalu.length; > > >>>>>> + } > > >>>>>> + > > >>>>>> + av_shrink_packet(pkt, data_ptr - pkt->data); > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame > > >>>> *frame) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + > > >>>>>> + AACADTSHeaderInfo *adts_hdr = NULL; > > >>>>>> + > > >>>>>> + /* Find next sync word 0xFFF */ > > >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > > >>>>>> + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == > > >>>> 0xF0) > > >>>>>> + break; > > >>>>>> + ctx->buf_ptr++; > > >>>>>> + } > > >>>>>> + > > >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > > >>>>>> + return -1; > > >>>>>> + > > >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; > > >>>>>> + > > >>>>>> + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, > > >>>> ctx->buf_end > > >>>>>> - frame->data); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + > > >>>>>> + frame->header_length = adts_hdr->crc_absent ? > > >>>> AV_AAC_ADTS_HEADER_SIZE > > >>>>>> : AV_AAC_ADTS_HEADER_SIZE + 2; > > >>>>>> + frame->length = adts_hdr->frame_length; > > >>>>>> + > > >>>>>> + av_free(adts_hdr); > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, > > >>>>>> AudioFrame *frame) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + > > >>>>>> + AC3HeaderInfo *hdr = NULL; > > >>>>>> + > > >>>>>> + /* Find next sync word 0x0B77 */ > > >>>>>> + while (ctx->buf_ptr < ctx->buf_end - 1) { > > >>>>>> + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) > > >>>>>> + break; > > >>>>>> + ctx->buf_ptr++; > > >>>>>> + } > > >>>>>> + > > >>>>>> + if (ctx->buf_ptr >= ctx->buf_end - 1) > > >>>>>> + return -1; > > >>>>>> + > > >>>>>> + frame->data = (uint8_t*)ctx->buf_ptr; > > >>>>>> + frame->header_length = 0; > > >>>>>> + > > >>>>>> + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - > > >>>>>> frame->data); > > >>>>>> + if (ret < 0) { > > >>>>>> + if (ret != AVERROR(ENOMEM)) > > >>>>>> + av_free(hdr); > > >>>>>> + return ret; > > >>>>>> + } > > >>>>>> + > > >>>>>> + frame->length = hdr->frame_size; > > >>>>>> + > > >>>>>> + av_free(hdr); > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int get_next_sync_frame(enum AVCodecID codec_id, > > >>>>>> CodecParserContext *ctx, AudioFrame *frame) > > >>>>>> +{ > > >>>>>> + if (codec_id == AV_CODEC_ID_AAC) > > >>>>>> + return get_next_adts_frame(ctx, frame); > > >>>>>> + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == > > >>>> AV_CODEC_ID_EAC3) > > >>>>>> + return get_next_ac3_eac3_sync_frame(ctx, frame); > > >>>>>> + else > > >>>>>> + return AVERROR_INVALIDDATA; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext > > >>>>>> *crypto_ctx, AudioFrame *frame) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + uint8_t *data; > > >>>>>> + int num_of_encrypted_blocks; > > >>>>>> + > > >>>>>> + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + > > >>>>>> + data = frame->data + frame->header_length + 16; > > >>>>>> + > > >>>>>> + num_of_encrypted_blocks = (frame->length - frame->header_length - > > >>>>>> 16)/16; > > >>>>>> + > > >>>>>> + av_aes_crypt(crypto_ctx->aes_ctx, data, data, > > >>>>>> num_of_encrypted_blocks, crypto_ctx->iv, 1); > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +static int decrypt_audio_frame(enum AVCodecID codec_id, > > >>>> HLSCryptoContext > > >>>>>> *crypto_ctx, AVPacket *pkt) > > >>>>>> +{ > > >>>>>> + int ret = 0; > > >>>>>> + CodecParserContext ctx; > > >>>>>> + AudioFrame frame; > > >>>>>> + > > >>>>>> + memset(&ctx, 0, sizeof(ctx)); > > >>>>>> + ctx.buf_ptr = pkt->data; > > >>>>>> + ctx.buf_end = pkt->data + pkt->size; > > >>>>>> + > > >>>>>> + while (ctx.buf_ptr < ctx.buf_end) { > > >>>>>> + memset(&frame, 0, sizeof(frame)); > > >>>>>> + ret = get_next_sync_frame(codec_id, &ctx, &frame); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + if (frame.length - frame.header_length > 31) { > > >>>>>> + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); > > >>>>>> + if (ret < 0) > > >>>>>> + return ret; > > >>>>>> + } > > >>>>>> + ctx.buf_ptr += frame.length; > > >>>>>> + } > > >>>>>> + > > >>>>>> + return 0; > > >>>>>> +} > > >>>>>> + > > >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > > >>>>>> *crypto_ctx, AVPacket *pkt) > > >>>>>> +{ > > >>>>>> + if (codec_id == AV_CODEC_ID_H264) > > >>>>>> + return decrypt_video_frame(crypto_ctx, pkt); > > >>>>>> + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 > > >>>>>> || codec_id == AV_CODEC_ID_EAC3) > > >>>>>> + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); > > >>>>>> + > > >>>>>> + return AVERROR_INVALIDDATA; > > >>>>>> +} > > >>>>>> diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h > > >>>>>> new file mode 100644 > > >>>>>> index 0000000000..cf80e41cb0 > > >>>>>> --- /dev/null > > >>>>>> +++ b/libavformat/hls_sample_aes.h > > >>>>>> @@ -0,0 +1,66 @@ > > >>>>>> +/* > > >>>>>> + * Apple HTTP Live Streaming Sample Encryption/Decryption > > >>>>>> + * > > >>>>>> + * Copyright (c) 2021 Nachiket Tarate > > >>>>>> + * > > >>>>>> + * This file is part of FFmpeg. > > >>>>>> + * > > >>>>>> + * FFmpeg is free software; you can redistribute it and/or > > >>>>>> + * modify it under the terms of the GNU Lesser General Public > > >>>>>> + * License as published by the Free Software Foundation; either > > >>>>>> + * version 2.1 of the License, or (at your option) any later version. > > >>>>>> + * > > >>>>>> + * FFmpeg is distributed in the hope that it will be useful, > > >>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > > >>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > >>>>>> + * Lesser General Public License for more details. > > >>>>>> + * > > >>>>>> + * You should have received a copy of the GNU Lesser General Public > > >>>>>> + * License along with FFmpeg; if not, write to the Free Software > > >>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > > >>>>>> 02110-1301 USA > > >>>>>> + */ > > >>>>>> + > > >>>>>> +/** > > >>>>>> + * @file > > >>>>>> + * Apple HTTP Live Streaming Sample Encryption > > >>>>>> + * > > >>>>>> > > >>>> https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption > > >>>>>> + */ > > >>>>>> + > > >>>>>> +#ifndef AVFORMAT_HLS_SAMPLE_AES_H > > >>>>>> +#define AVFORMAT_HLS_SAMPLE_AES_H > > >>>>>> + > > >>>>>> +#include <stdint.h> > > >>>>>> + > > >>>>>> +#include "avformat.h" > > >>>>>> + > > >>>>>> +#include "libavcodec/avcodec.h" > > >>>>>> +#include "libavutil/aes.h" > > >>>>>> + > > >>>>>> +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 > > >>>>>> +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 > > >>>>>> + > > >>>>>> + > > >>>>>> +typedef struct HLSCryptoContext { > > >>>>>> + struct AVAES *aes_ctx; > > >>>>>> + uint8_t key[16]; > > >>>>>> + uint8_t iv[16]; > > >>>>>> +} HLSCryptoContext; > > >>>>>> + > > >>>>>> +typedef struct HLSAudioSetupInfo { > > >>>>>> + enum AVCodecID codec_id; > > >>>>>> + uint32_t codec_tag; > > >>>>>> + uint16_t priming; > > >>>>>> + uint8_t version; > > >>>>>> + uint8_t setup_data_length; > > >>>>>> + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; > > >>>>>> +} HLSAudioSetupInfo; > > >>>>>> + > > >>>>>> + > > >>>>>> +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const > > >>>> uint8_t > > >>>>>> *buf, size_t size); > > >>>>>> + > > >>>>>> +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo > > >>>> *info); > > >>>>>> + > > >>>>>> +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext > > >>>>>> *crypto_ctx, AVPacket *pkt); > > >>>>>> + > > >>>>>> +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ > > >>>>>> + > > >>>>>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c > > >>>>>> index e283ec09d7..dc611ae788 100644 > > >>>>>> --- a/libavformat/mpegts.c > > >>>>>> +++ b/libavformat/mpegts.c > > >>>>>> @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { > > >>>>>> { 0 }, > > >>>>>> }; > > >>>>>> > > >>>>>> +/* HLS Sample Encryption Types */ > > >>>>>> +static const StreamType HLS_SAMPLE_ENC_types[] = { > > >>>>>> + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, > > >>>>>> + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, > > >>>>>> + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, > > >>>>>> + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, > > >>>>>> + { 0 }, > > >>>>>> +}; > > >>>>>> + > > >>>>>> + > > >>>>>> static const StreamType REGD_types[] = { > > >>>>>> { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC > > >>>> }, > > >>>>>> { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 > > >>>> }, > > >>>>>> @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, > > >>>>>> PESContext *pes, > > >>>>>> } > > >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > > >>>>>> mpegts_find_stream_type(st, pes->stream_type, MISC_types); > > >>>>>> + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) > > >>>>>> + mpegts_find_stream_type(st, pes->stream_type, > > >>>>>> HLS_SAMPLE_ENC_types); > > >>>>>> if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { > > >>>>>> st->codecpar->codec_id = old_codec_id; > > >>>>>> st->codecpar->codec_type = old_codec_type; > > >>>>>> -- > > >>>>>> 2.17.1 > > >>>>>> > > >>>>>> > > >>>>> _______________________________________________ > > >>>>> ffmpeg-devel mailing list > > >>>>> ffmpeg-devel@ffmpeg.org > > >>>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > >>>>> > > >>>>> To unsubscribe, visit link above, or email > > >>>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > >>>> > > >>>> Thanks > > >>>> > > >>>> Steven Liu > > >>>> > > >>>> > > >>>> > > >>>> _______________________________________________ > > >>>> ffmpeg-devel mailing list > > >>>> ffmpeg-devel@ffmpeg.org > > >>>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > >>>> > > >>>> To unsubscribe, visit link above, or email > > >>>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > >>> _______________________________________________ > > >>> ffmpeg-devel mailing list > > >>> ffmpeg-devel@ffmpeg.org > > >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > >>> > > >>> To unsubscribe, visit link above, or email > > >>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > >> > > >> Thanks > > >> > > >> Steven Liu > > >> > > >> > > >> > > >> _______________________________________________ > > >> ffmpeg-devel mailing list > > >> ffmpeg-devel@ffmpeg.org > > >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > >> > > >> To unsubscribe, visit link above, or email > > >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > > > > Thanks > > > > Steven Liu > > > > > >
> 2021年3月8日 下午3:15,Nachiket Tarate <nachiket.programmer@gmail.com> 写道: >> >> Your understanding of SAMPLE-AES is absolutely correct. >> >> I am insisting that let us add support for it in 2 phases as >> completely 2 different specifications have been used. >> >> Phase 1 : Media segments in MPEG-TS, AAC, AC-3 and Enhanced AC-3 >> formats (Apple's specifications) >> >> Phase 2 : Media segments in fragmented MP4 format (CENC specifications) >> >> Kindly let me know your opinion. > > Hello Steven, > > I am looking forward to hearing from you. You could make 4 or 5 patch merge two phase to one patchset. > > Best Regards, > Nachiket Thanks Steven Liu
diff --git a/libavformat/Makefile b/libavformat/Makefile index fcb39ce133..62627d50a6 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -236,7 +236,7 @@ OBJS-$(CONFIG_HCOM_DEMUXER) += hcom.o pcm.o OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o -OBJS-$(CONFIG_HLS_DEMUXER) += hls.o +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o hls_sample_aes.o OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o avc.o OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o diff --git a/libavformat/hls.c b/libavformat/hls.c index af2468ad9b..3cb3853c79 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -2,6 +2,7 @@ * Apple HTTP Live Streaming demuxer * Copyright (c) 2010 Martin Storsjo * Copyright (c) 2013 Anssi Hannula + * Copyright (c) 2021 Nachiket Tarate * * This file is part of FFmpeg. * @@ -39,6 +40,8 @@ #include "avio_internal.h" #include "id3v2.h" +#include "hls_sample_aes.h" + #define INITIAL_BUFFER_SIZE 32768 #define MAX_FIELD_LEN 64 @@ -145,6 +148,10 @@ struct playlist { int id3_changed; /* ID3 tag data has changed at some point */ ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */ + /* Used in case of SAMPLE-AES encryption method */ + HLSAudioSetupInfo audio_setup_info; + HLSCryptoContext crypto_ctx; + int64_t seek_timestamp; int seek_flags; int seek_stream_index; /* into subdemuxer stream array */ @@ -266,6 +273,8 @@ static void free_playlist_list(HLSContext *c) pls->ctx->pb = NULL; avformat_close_input(&pls->ctx); } + if (pls->crypto_ctx.aes_ctx) + av_free(pls->crypto_ctx.aes_ctx); av_free(pls); } av_freep(&c->playlists); @@ -994,7 +1003,10 @@ fail: static struct segment *current_segment(struct playlist *pls) { - return pls->segments[pls->cur_seq_no - pls->start_seq_no]; + int64_t n = pls->cur_seq_no - pls->start_seq_no; + if (n >= pls->n_segments) + return NULL; + return pls->segments[n]; } static struct segment *next_segment(struct playlist *pls) @@ -1023,10 +1035,11 @@ static int read_from_url(struct playlist *pls, struct segment *seg, /* Parse the raw ID3 data and pass contents to caller */ static void parse_id3(AVFormatContext *s, AVIOContext *pb, - AVDictionary **metadata, int64_t *dts, + AVDictionary **metadata, int64_t *dts, HLSAudioSetupInfo *audio_setup_info, ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta **extra_meta) { static const char id3_priv_owner_ts[] = "com.apple.streaming.transportStreamTimestamp"; + static const char id3_priv_owner_audio_setup[] = "com.apple.streaming.audioDescription"; ID3v2ExtraMeta *meta; ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); @@ -1041,6 +1054,8 @@ static void parse_id3(AVFormatContext *s, AVIOContext *pb, *dts = ts; else av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio timestamp %"PRId64"\n", ts); + } else if (priv->datasize >= 8 && !strcmp(priv->owner, id3_priv_owner_audio_setup)) { + ff_hls_read_audio_setup_info(audio_setup_info, priv->data, priv->datasize); } } else if (!strcmp(meta->tag, "APIC") && apic) *apic = &meta->data.apic; @@ -1084,7 +1099,7 @@ static void handle_id3(AVIOContext *pb, struct playlist *pls) ID3v2ExtraMeta *extra_meta = NULL; int64_t timestamp = AV_NOPTS_VALUE; - parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); + parse_id3(pls->ctx, pb, &metadata, ×tamp, &pls->audio_setup_info, &apic, &extra_meta); if (timestamp != AV_NOPTS_VALUE) { pls->id3_mpegts_timestamp = timestamp; @@ -1238,10 +1253,7 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset %"PRId64", playlist %d\n", seg->url, seg->url_offset, pls->index); - if (seg->key_type == KEY_NONE) { - ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, &is_http); - } else if (seg->key_type == KEY_AES_128) { - char iv[33], key[33], url[MAX_URL_SIZE]; + if (seg->key_type == KEY_AES_128 || seg->key_type == KEY_SAMPLE_AES) { if (strcmp(seg->key, pls->key_url)) { AVIOContext *pb = NULL; if (open_url(pls->parent, &pb, seg->key, &c->avio_opts, opts, NULL) == 0) { @@ -1257,6 +1269,10 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, } av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); } + } + + if (seg->key_type == KEY_AES_128) { + char iv[33], key[33], url[MAX_URL_SIZE]; ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); iv[32] = key[32] = '\0'; @@ -1273,13 +1289,9 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, goto cleanup; } ret = 0; - } else if (seg->key_type == KEY_SAMPLE_AES) { - av_log(pls->parent, AV_LOG_ERROR, - "SAMPLE-AES encryption is not supported yet\n"); - ret = AVERROR_PATCHWELCOME; + } else { + ret = open_url(pls->parent, in, seg->url, &c->avio_opts, opts, &is_http); } - else - ret = AVERROR(ENOSYS); /* Seek to the requested position. If this was a HTTP request, the offset * should already be where want it to, but this allows e.g. local testing @@ -1948,6 +1960,7 @@ static int hls_read_header(AVFormatContext *s) struct playlist *pls = c->playlists[i]; char *url; ff_const59 AVInputFormat *in_fmt = NULL; + struct segment *seg = NULL; if (!(pls->ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); @@ -1980,8 +1993,41 @@ static int hls_read_header(AVFormatContext *s) pls->ctx = NULL; goto fail; } + ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls, read_data, NULL, NULL); + + /* + * If encryption scheme is SAMPLE-AES, try to read ID3 tags of + * external audio track that contains audio setup information + */ + seg = current_segment(pls); + if (seg && seg->key_type == KEY_SAMPLE_AES && pls->n_renditions > 0 && + pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO) { + uint8_t buf[HLS_MAX_ID3_TAGS_DATA_LEN]; + if ((ret = avio_read(&pls->pb, buf, HLS_MAX_ID3_TAGS_DATA_LEN)) < 0) { + /* Fail if error was not end of file */ + if (ret != AVERROR_EOF) { + avformat_free_context(pls->ctx); + pls->ctx = NULL; + goto fail; + } + } + ret = 0; + } + + /* + * If encryption scheme is SAMPLE-AES and audio setup information is present in external audio track, + * use that information to find the media format, otherwise probe input data + */ + if (seg && seg->key_type == KEY_SAMPLE_AES && pls->is_id3_timestamped && + pls->audio_setup_info.codec_id != AV_CODEC_ID_NONE) { + void *iter = NULL; + while ((in_fmt = (ff_const59 AVInputFormat *)av_demuxer_iterate(&iter))) + if (in_fmt->raw_codec_id == pls->audio_setup_info.codec_id) { + break; + } + } else { pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * 4; pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? s->max_analyze_duration : 4 * AV_TIME_BASE; pls->ctx->interrupt_callback = s->interrupt_callback; @@ -1999,6 +2045,25 @@ static int hls_read_header(AVFormatContext *s) goto fail; } av_free(url); + } + + if (seg && seg->key_type == KEY_SAMPLE_AES) { + if (!pls->is_id3_timestamped && pls->n_renditions > 0 && pls->renditions[0]->type != AVMEDIA_TYPE_AUDIO && + strcmp(in_fmt->name, "mpegts")) { + av_log(s, AV_LOG_ERROR, "SAMPLE-AES encryption is not supported for fragmented MP4 format yet\n"); + ret = AVERROR_PATCHWELCOME; + } else { + pls->crypto_ctx.aes_ctx = av_aes_alloc(); + if (!pls->crypto_ctx.aes_ctx) + ret = AVERROR(ENOMEM); + } + if (ret != 0) { + avformat_free_context(pls->ctx); + pls->ctx = NULL; + goto fail; + } + } + pls->ctx->pb = &pls->pb; pls->ctx->io_open = nested_io_open; pls->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO; @@ -2027,7 +2092,12 @@ static int hls_read_header(AVFormatContext *s) * on us if they want to. */ if (pls->is_id3_timestamped || (pls->n_renditions > 0 && pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) { + if (seg && seg->key_type == KEY_SAMPLE_AES && pls->audio_setup_info.setup_data_length > 0 && + pls->ctx->nb_streams == 1) + ret = ff_hls_parse_audio_setup_info(pls->ctx->streams[0], &pls->audio_setup_info); + else ret = avformat_find_stream_info(pls->ctx, NULL); + if (ret < 0) goto fail; } @@ -2157,6 +2227,7 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) while (1) { int64_t ts_diff; AVRational tb; + struct segment *seg = NULL; ret = av_read_frame(pls->ctx, &pls->pkt); if (ret < 0) { if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) @@ -2175,6 +2246,14 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) get_timebase(pls), AV_TIME_BASE_Q); } + seg = current_segment(pls); + if (seg && seg->key_type == KEY_SAMPLE_AES) { + enum AVCodecID codec_id = pls->ctx->streams[pls->pkt.stream_index]->codecpar->codec_id; + memcpy(pls->crypto_ctx.iv, seg->iv, sizeof(seg->iv)); + memcpy(pls->crypto_ctx.key, pls->key, sizeof(pls->key)); + ff_hls_decrypt_frame(codec_id, &pls->crypto_ctx, &pls->pkt); + } + if (pls->seek_timestamp == AV_NOPTS_VALUE) break; diff --git a/libavformat/hls_sample_aes.c b/libavformat/hls_sample_aes.c new file mode 100644 index 0000000000..0407a15b0f --- /dev/null +++ b/libavformat/hls_sample_aes.c @@ -0,0 +1,391 @@ +/* + * Apple HTTP Live Streaming Sample Encryption/Decryption + * + * Copyright (c) 2021 Nachiket Tarate + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Apple HTTP Live Streaming Sample Encryption + * https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption + */ + +#include "hls_sample_aes.h" + +#include "libavcodec/adts_header.h" +#include "libavcodec/adts_parser.h" +#include "libavcodec/ac3_parser_internal.h" + + +typedef struct NALUnit { + uint8_t *data; + int type; + int length; + int start_code_length; +} NALUnit; + +typedef struct AudioFrame { + uint8_t *data; + int length; + int header_length; +} AudioFrame; + +typedef struct CodecParserContext { + const uint8_t *buf_ptr; + const uint8_t *buf_end; +} CodecParserContext; + +static const int eac3_sample_rate_tab[] = { 48000, 44100, 32000, 0 }; + +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t *buf, size_t size) +{ + if (size < 8) + return; + + info->codec_tag = AV_RL32(buf); + + if (info->codec_tag == MKTAG('z','a', 'a', 'c')) + info->codec_id = AV_CODEC_ID_AAC; + else if (info->codec_tag == MKTAG('z','a', 'c', '3')) + info->codec_id = AV_CODEC_ID_AC3; + else if (info->codec_tag == MKTAG('z','e', 'c', '3')) + info->codec_id = AV_CODEC_ID_EAC3; + else + info->codec_id = AV_CODEC_ID_NONE; + + buf += 4; + info->priming = AV_RL16(buf); + buf += 2; + info->version = *buf++; + info->setup_data_length = *buf++; + + if (info->setup_data_length > size - 8) + info->setup_data_length = size - 8; + + if (info->setup_data_length > HLS_MAX_AUDIO_SETUP_DATA_LEN) + return; + + memcpy(info->setup_data, buf, info->setup_data_length); +} + +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info) +{ + int ret = 0; + + st->codecpar->codec_tag = info->codec_tag; + + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) + return 0; + + if (st->codecpar->codec_id != AV_CODEC_ID_AC3 && st->codecpar->codec_id != AV_CODEC_ID_EAC3) + return AVERROR_INVALIDDATA; + + if (st->codecpar->codec_id == AV_CODEC_ID_AC3) { + + AC3HeaderInfo *ac3hdr = NULL; + + ret = avpriv_ac3_parse_header(&ac3hdr, info->setup_data, info->setup_data_length); + if (ret < 0) { + if (ret != AVERROR(ENOMEM)) + av_free(ac3hdr); + return ret; + } + + st->codecpar->sample_rate = ac3hdr->sample_rate; + st->codecpar->channels = ac3hdr->channels; + st->codecpar->channel_layout = ac3hdr->channel_layout; + st->codecpar->bit_rate = ac3hdr->bit_rate; + + av_free(ac3hdr); + } else { /* Parse 'dec3' EC3SpecificBox */ + + GetBitContext gb; + int data_rate, fscod, acmod, lfeon; + + ret = init_get_bits8(&gb, info->setup_data, info->setup_data_length); + if (ret < 0) + return AVERROR_INVALIDDATA; + + data_rate = get_bits(&gb, 13); + skip_bits(&gb, 3); + fscod = get_bits(&gb, 2); + skip_bits(&gb, 10); + acmod = get_bits(&gb, 3); + lfeon = get_bits(&gb, 1); + + st->codecpar->sample_rate = eac3_sample_rate_tab[fscod]; + + st->codecpar->channel_layout = avpriv_ac3_channel_layout_tab[acmod]; + if (lfeon) + st->codecpar->channel_layout |= AV_CH_LOW_FREQUENCY; + + st->codecpar->channels = av_get_channel_layout_nb_channels(st->codecpar->channel_layout); + + st->codecpar->bit_rate = data_rate*1000; + } + + return 0; +} + +/* + * Remove start code emulation prevention 0x03 bytes + */ +static void remove_scep_3_bytes(NALUnit *nalu) +{ + int i = 0; + int j = 0; + + uint8_t *data = nalu->data; + + while (i < nalu->length) { + if (nalu->length - i > 3 && AV_RB24(&data[i]) == 0x000003) { + data[j++] = data[i++]; + data[j++] = data[i++]; + i++; + } else { + data[j++] = data[i++]; + } + } + + nalu->length = j; +} + +static int get_next_nal_unit(CodecParserContext *ctx, NALUnit *nalu) +{ + const uint8_t *nalu_start = ctx->buf_ptr; + + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == 0x00000001) + nalu->start_code_length = 4; + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) == 0x000001) + nalu->start_code_length = 3; + else /* No start code at the beginning of the NAL unit */ + return -1; + + ctx->buf_ptr += nalu->start_code_length; + + while (ctx->buf_ptr < ctx->buf_end) { + if (ctx->buf_end - ctx->buf_ptr >= 4 && AV_RB32(ctx->buf_ptr) == 0x00000001) + break; + else if (ctx->buf_end - ctx->buf_ptr >= 3 && AV_RB24(ctx->buf_ptr) == 0x000001) + break; + ctx->buf_ptr++; + } + + nalu->data = (uint8_t *)nalu_start + nalu->start_code_length; + nalu->length = ctx->buf_ptr - nalu->data; + nalu->type = *nalu->data & 0x1F; + + return 0; +} + +static int decrypt_nal_unit(HLSCryptoContext *crypto_ctx, NALUnit *nalu) +{ + int ret = 0; + int rem_bytes; + uint8_t *data; + uint8_t iv[16]; + + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); + if (ret < 0) + return ret; + + /* Remove start code emulation prevention 0x03 bytes */ + remove_scep_3_bytes(nalu); + + data = nalu->data + 32; + rem_bytes = nalu->length - 32; + + memcpy(iv, crypto_ctx->iv, 16); + + while (rem_bytes > 0) { + if (rem_bytes > 16) { + av_aes_crypt(crypto_ctx->aes_ctx, data, data, 1, iv, 1); + data += 16; + rem_bytes -= 16; + } + data += FFMIN(144, rem_bytes); + rem_bytes -= FFMIN(144, rem_bytes); + } + + return 0; +} + +static int decrypt_video_frame(HLSCryptoContext *crypto_ctx, AVPacket *pkt) +{ + int ret = 0; + CodecParserContext ctx; + NALUnit nalu; + uint8_t *data_ptr; + int move_nalu = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf_ptr = pkt->data; + ctx.buf_end = pkt->data + pkt->size; + + data_ptr = pkt->data; + + while (ctx.buf_ptr < ctx.buf_end) { + memset(&nalu, 0, sizeof(nalu)); + ret = get_next_nal_unit(&ctx, &nalu); + if (ret < 0) + return ret; + if ((nalu.type == 0x01 || nalu.type == 0x05) && nalu.length > 48) { + int encrypted_nalu_length = nalu.length; + ret = decrypt_nal_unit(crypto_ctx, &nalu); + if (ret < 0) + return ret; + move_nalu = nalu.length != encrypted_nalu_length; + } + if (move_nalu) + memmove(data_ptr, nalu.data - nalu.start_code_length, nalu.start_code_length + nalu.length); + data_ptr += nalu.start_code_length + nalu.length; + } + + av_shrink_packet(pkt, data_ptr - pkt->data); + + return 0; +} + +static int get_next_adts_frame(CodecParserContext *ctx, AudioFrame *frame) +{ + int ret = 0; + + AACADTSHeaderInfo *adts_hdr = NULL; + + /* Find next sync word 0xFFF */ + while (ctx->buf_ptr < ctx->buf_end - 1) { + if (*ctx->buf_ptr == 0xFF && *(ctx->buf_ptr + 1) & 0xF0 == 0xF0) + break; + ctx->buf_ptr++; + } + + if (ctx->buf_ptr >= ctx->buf_end - 1) + return -1; + + frame->data = (uint8_t*)ctx->buf_ptr; + + ret = avpriv_adts_header_parse (&adts_hdr, frame->data, ctx->buf_end - frame->data); + if (ret < 0) + return ret; + + frame->header_length = adts_hdr->crc_absent ? AV_AAC_ADTS_HEADER_SIZE : AV_AAC_ADTS_HEADER_SIZE + 2; + frame->length = adts_hdr->frame_length; + + av_free(adts_hdr); + + return 0; +} + +static int get_next_ac3_eac3_sync_frame(CodecParserContext *ctx, AudioFrame *frame) +{ + int ret = 0; + + AC3HeaderInfo *hdr = NULL; + + /* Find next sync word 0x0B77 */ + while (ctx->buf_ptr < ctx->buf_end - 1) { + if (*ctx->buf_ptr == 0x0B && *(ctx->buf_ptr + 1) == 0x77) + break; + ctx->buf_ptr++; + } + + if (ctx->buf_ptr >= ctx->buf_end - 1) + return -1; + + frame->data = (uint8_t*)ctx->buf_ptr; + frame->header_length = 0; + + ret = avpriv_ac3_parse_header(&hdr, frame->data, ctx->buf_end - frame->data); + if (ret < 0) { + if (ret != AVERROR(ENOMEM)) + av_free(hdr); + return ret; + } + + frame->length = hdr->frame_size; + + av_free(hdr); + + return 0; +} + +static int get_next_sync_frame(enum AVCodecID codec_id, CodecParserContext *ctx, AudioFrame *frame) +{ + if (codec_id == AV_CODEC_ID_AAC) + return get_next_adts_frame(ctx, frame); + else if (codec_id == AV_CODEC_ID_AC3 || codec_id == AV_CODEC_ID_EAC3) + return get_next_ac3_eac3_sync_frame(ctx, frame); + else + return AVERROR_INVALIDDATA; +} + +static int decrypt_sync_frame(enum AVCodecID codec_id, HLSCryptoContext *crypto_ctx, AudioFrame *frame) +{ + int ret = 0; + uint8_t *data; + int num_of_encrypted_blocks; + + ret = av_aes_init(crypto_ctx->aes_ctx, crypto_ctx->key, 16 * 8, 1); + if (ret < 0) + return ret; + + data = frame->data + frame->header_length + 16; + + num_of_encrypted_blocks = (frame->length - frame->header_length - 16)/16; + + av_aes_crypt(crypto_ctx->aes_ctx, data, data, num_of_encrypted_blocks, crypto_ctx->iv, 1); + + return 0; +} + +static int decrypt_audio_frame(enum AVCodecID codec_id, HLSCryptoContext *crypto_ctx, AVPacket *pkt) +{ + int ret = 0; + CodecParserContext ctx; + AudioFrame frame; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf_ptr = pkt->data; + ctx.buf_end = pkt->data + pkt->size; + + while (ctx.buf_ptr < ctx.buf_end) { + memset(&frame, 0, sizeof(frame)); + ret = get_next_sync_frame(codec_id, &ctx, &frame); + if (ret < 0) + return ret; + if (frame.length - frame.header_length > 31) { + ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame); + if (ret < 0) + return ret; + } + ctx.buf_ptr += frame.length; + } + + return 0; +} + +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext *crypto_ctx, AVPacket *pkt) +{ + if (codec_id == AV_CODEC_ID_H264) + return decrypt_video_frame(crypto_ctx, pkt); + else if (codec_id == AV_CODEC_ID_AAC || codec_id == AV_CODEC_ID_AC3 || codec_id == AV_CODEC_ID_EAC3) + return decrypt_audio_frame(codec_id, crypto_ctx, pkt); + + return AVERROR_INVALIDDATA; +} diff --git a/libavformat/hls_sample_aes.h b/libavformat/hls_sample_aes.h new file mode 100644 index 0000000000..cf80e41cb0 --- /dev/null +++ b/libavformat/hls_sample_aes.h @@ -0,0 +1,66 @@ +/* + * Apple HTTP Live Streaming Sample Encryption/Decryption + * + * Copyright (c) 2021 Nachiket Tarate + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Apple HTTP Live Streaming Sample Encryption + * https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption + */ + +#ifndef AVFORMAT_HLS_SAMPLE_AES_H +#define AVFORMAT_HLS_SAMPLE_AES_H + +#include <stdint.h> + +#include "avformat.h" + +#include "libavcodec/avcodec.h" +#include "libavutil/aes.h" + +#define HLS_MAX_ID3_TAGS_DATA_LEN 138 +#define HLS_MAX_AUDIO_SETUP_DATA_LEN 10 + + +typedef struct HLSCryptoContext { + struct AVAES *aes_ctx; + uint8_t key[16]; + uint8_t iv[16]; +} HLSCryptoContext; + +typedef struct HLSAudioSetupInfo { + enum AVCodecID codec_id; + uint32_t codec_tag; + uint16_t priming; + uint8_t version; + uint8_t setup_data_length; + uint8_t setup_data[HLS_MAX_AUDIO_SETUP_DATA_LEN]; +} HLSAudioSetupInfo; + + +void ff_hls_read_audio_setup_info(HLSAudioSetupInfo *info, const uint8_t *buf, size_t size); + +int ff_hls_parse_audio_setup_info(AVStream *st, HLSAudioSetupInfo *info); + +int ff_hls_decrypt_frame(enum AVCodecID codec_id, HLSCryptoContext *crypto_ctx, AVPacket *pkt); + +#endif /* AVFORMAT_HLS_SAMPLE_AES_H */ + diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c index e283ec09d7..dc611ae788 100644 --- a/libavformat/mpegts.c +++ b/libavformat/mpegts.c @@ -839,6 +839,16 @@ static const StreamType MISC_types[] = { { 0 }, }; +/* HLS Sample Encryption Types */ +static const StreamType HLS_SAMPLE_ENC_types[] = { + { 0xdb, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264}, + { 0xcf, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, + { 0xc1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, + { 0xc2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3}, + { 0 }, +}; + + static const StreamType REGD_types[] = { { MKTAG('d', 'r', 'a', 'c'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC }, { MKTAG('A', 'C', '-', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, @@ -948,6 +958,8 @@ static int mpegts_set_stream_info(AVStream *st, PESContext *pes, } if (st->codecpar->codec_id == AV_CODEC_ID_NONE) mpegts_find_stream_type(st, pes->stream_type, MISC_types); + if (st->codecpar->codec_id == AV_CODEC_ID_NONE) + mpegts_find_stream_type(st, pes->stream_type, HLS_SAMPLE_ENC_types); if (st->codecpar->codec_id == AV_CODEC_ID_NONE) { st->codecpar->codec_id = old_codec_id; st->codecpar->codec_type = old_codec_type;
Apple HTTP Live Streaming Sample Encryption: https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption Signed-off-by: Nachiket Tarate <nachiket.programmer@gmail.com> --- libavformat/Makefile | 2 +- libavformat/hls.c | 105 ++++++++-- libavformat/hls_sample_aes.c | 391 +++++++++++++++++++++++++++++++++++ libavformat/hls_sample_aes.h | 66 ++++++ libavformat/mpegts.c | 12 ++ 5 files changed, 562 insertions(+), 14 deletions(-) create mode 100644 libavformat/hls_sample_aes.c create mode 100644 libavformat/hls_sample_aes.h