[FFmpeg-devel] [PATCH 2/3] libavformat/hls: add support for decryption of HLS streams in MPEG-TS format protected using SAMPLE-AES encryption

Nachiket Tarate nachiket.programmer at gmail.com
Mon Mar 1 09:22:51 EET 2021


On Mon, Mar 1, 2021 at 11:30 AM Steven Liu <lq at chinaffmpeg.org> wrote:
>
>
>
> > 2021年3月1日 下午12:55,Nachiket Tarate <nachiket.programmer at 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 at chinaffmpeg.org> wrote:
> >
> >>
> >>
> >>> 2021年3月1日 下午12:35,Nachiket Tarate <nachiket.programmer at gmail.com> 写道:
> >>>
> >>> @Steven Liu <lq at 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 at 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 at 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, &timestamp, &apic, &extra_meta);
> >>>> +    parse_id3(pls->ctx, pb, &metadata, &timestamp,
> >>>> &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 at ffmpeg.org
> >>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >>>
> >>> To unsubscribe, visit link above, or email
> >>> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
> >>
> >> Thanks
> >>
> >> Steven Liu
> >>
> >>
> >>
> >> _______________________________________________
> >> ffmpeg-devel mailing list
> >> ffmpeg-devel at ffmpeg.org
> >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >>
> >> To unsubscribe, visit link above, or email
> >> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
> > _______________________________________________
> > ffmpeg-devel mailing list
> > ffmpeg-devel at ffmpeg.org
> > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >
> > To unsubscribe, visit link above, or email
> > ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
>
> Thanks
>
> Steven Liu
>
>
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


More information about the ffmpeg-devel mailing list