[FFmpeg-devel] [PATCH 2/2] avformat/movenccenc: add support for CENC AV1 encryption
Andreas Rheinhardt
andreas.rheinhardt at outlook.com
Fri Mar 21 04:55:16 EET 2025
James Almer:
> Signed-off-by: James Almer <jamrial at gmail.com>
> ---
> configure | 2 +-
> libavformat/Makefile | 1 +
> libavformat/cbs.c | 1 +
> libavformat/cbs_av1.c | 1 +
> libavformat/movenc.c | 13 ++-
> libavformat/movenccenc.c | 223 ++++++++++++++++++++++++++++++++++++++-
> libavformat/movenccenc.h | 20 +++-
> 7 files changed, 255 insertions(+), 6 deletions(-)
> create mode 100644 libavformat/cbs.c
> create mode 100644 libavformat/cbs_av1.c
>
> diff --git a/configure b/configure
> index 14f7bcde0e..0f6b6c20fb 100755
> --- a/configure
> +++ b/configure
> @@ -3689,7 +3689,7 @@ mlp_demuxer_select="mlp_parser"
> mmf_muxer_select="riffenc"
> mov_demuxer_select="iso_media riffdec"
> mov_demuxer_suggest="iamfdec zlib"
> -mov_muxer_select="iso_media iso_writer riffenc rtpenc_chain vp9_superframe_bsf aac_adtstoasc_bsf ac3_parser"
> +mov_muxer_select="iso_media iso_writer riffenc rtpenc_chain vp9_superframe_bsf aac_adtstoasc_bsf ac3_parser cbs_av1"
> mov_muxer_suggest="iamfenc"
> mp3_demuxer_select="mpegaudio_parser"
> mp3_muxer_select="mpegaudioheader"
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 7730e7c4e6..1e57ae7d8a 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -740,6 +740,7 @@ SHLIBOBJS-$(CONFIG_JNI) += ffjni.o
> SHLIBOBJS-$(CONFIG_JPEGXL_ANIM_DEMUXER) += jpegxl_parse.o
> SHLIBOBJS-$(CONFIG_MATROSKA_DEMUXER) += mpeg4audio_sample_rates.o
> SHLIBOBJS-$(CONFIG_MOV_DEMUXER) += ac3_channel_layout_tab.o
> +SHLIBOBJS-$(CONFIG_MOV_MUXER) += cbs.o cbs_av1.o
> SHLIBOBJS-$(CONFIG_MP3_MUXER) += mpegaudiotabs.o
> SHLIBOBJS-$(CONFIG_MXF_MUXER) += golomb_tab.o \
> rangecoder_dec.o
> diff --git a/libavformat/cbs.c b/libavformat/cbs.c
> new file mode 100644
> index 0000000000..8f1235da8d
> --- /dev/null
> +++ b/libavformat/cbs.c
> @@ -0,0 +1 @@
> +#include "libavcodec/cbs.c"
> diff --git a/libavformat/cbs_av1.c b/libavformat/cbs_av1.c
> new file mode 100644
> index 0000000000..32b9e72c57
> --- /dev/null
> +++ b/libavformat/cbs_av1.c
> @@ -0,0 +1 @@
> +#include "libavcodec/cbs_av1.c"
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index af013e1fc6..c60c46012f 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -6767,7 +6767,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
> } else {
> size = ff_vvc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL);
> }
> - } else if (par->codec_id == AV_CODEC_ID_AV1) {
> + } else if (par->codec_id == AV_CODEC_ID_AV1 && !trk->cenc.aes_ctr) {
> if (trk->hint_track >= 0 && trk->hint_track < mov->nb_tracks) {
> ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data,
> &size, &offset);
> @@ -6815,6 +6815,13 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
> ret = ff_mov_cenc_avc_write_nal_units(s, &trk->cenc, nal_size_length, pb, pkt->data, size);
> } else if(par->codec_id == AV_CODEC_ID_VVC) {
> ret = AVERROR_PATCHWELCOME;
> + } else if(par->codec_id == AV_CODEC_ID_AV1) {
> + av_assert0(size == pkt->size);
> + ret = ff_mov_cenc_av1_write_obus(s, &trk->cenc, pb, pkt);
> + if (ret > 0) {
> + size = ret;
> + ret = 0;
> + }
> } else {
> ret = ff_mov_cenc_write_packet(&trk->cenc, pb, pkt->data, size);
> }
> @@ -8135,8 +8142,8 @@ static int mov_init(AVFormatContext *s)
> if (mov->encryption_scheme == MOV_ENC_CENC_AES_CTR) {
> ret = ff_mov_cenc_init(&track->cenc, mov->encryption_key,
> (track->par->codec_id == AV_CODEC_ID_H264 || track->par->codec_id == AV_CODEC_ID_HEVC ||
> - track->par->codec_id == AV_CODEC_ID_VVC),
> - s->flags & AVFMT_FLAG_BITEXACT);
> + track->par->codec_id == AV_CODEC_ID_VVC || track->par->codec_id == AV_CODEC_ID_AV1),
> + track->par->codec_id, s->flags & AVFMT_FLAG_BITEXACT);
> if (ret)
> return ret;
> }
> diff --git a/libavformat/movenccenc.c b/libavformat/movenccenc.c
> index f54d3bcbca..af801eb3a0 100644
> --- a/libavformat/movenccenc.c
> +++ b/libavformat/movenccenc.c
> @@ -19,6 +19,9 @@
> * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> */
> #include "movenccenc.h"
> +#include "libavcodec/av1_parse.h"
> +#include "libavcodec/bytestream.h"
> +#include "libavcodec/cbs_av1.h"
> #include "libavutil/intreadwrite.h"
> #include "libavutil/mem.h"
> #include "avio_internal.h"
> @@ -280,6 +283,203 @@ int ff_mov_cenc_avc_write_nal_units(AVFormatContext *s, MOVMuxCencContext* ctx,
> return 0;
> }
>
> +static int write_tiles(AVFormatContext *s, MOVMuxCencContext *ctx, AVIOContext *pb, AV1_OBU_Type type,
> + const AV1RawFrameHeader *frame_header, const uint8_t *fh_data, size_t fh_data_size,
> + const AV1RawTileGroup *tile_group)
> +{
> + GetByteContext gb;
> + size_t tgh_data_size = tile_group->data_size;
> + int cur_tile_num = frame_header->tile_cols * frame_header->tile_rows;
> + int total = 0;
> +
> + // Get the Frame Header size
> + if (type == AV1_OBU_FRAME)
> + fh_data_size -= tgh_data_size;
> + // Get the Tile Group Header size
> + tgh_data_size -= tile_group->tile_data.data_size;
> +
> + if (ctx->tile_num < cur_tile_num) {
> + int ret = av_reallocp_array(&ctx->tile_group_sizes, cur_tile_num,
> + sizeof(*ctx->tile_group_sizes));
> + if (ret < 0) {
> + ctx->tile_num = 0;
> + return ret;
> + }
> + }
> + ctx->tile_num = cur_tile_num;
> +
> + total = fh_data_size + tgh_data_size;
> + ctx->clear_bytes += total;
> +
> + bytestream2_init(&gb, tile_group->tile_data.data, tile_group->tile_data.data_size);
> +
> + // Build a table with block sizes for encrypted bytes and clear bytes
> + for (unsigned tile_num = tile_group->tg_start; tile_num <= tile_group->tg_end; tile_num++) {
> + uint32_t encrypted_bytes, tile_size_bytes, tile_size = 0;
> +
> + if (tile_num == tile_group->tg_end) {
> + tile_size = bytestream2_get_bytes_left(&gb);
> + encrypted_bytes = tile_size & ~0xFU;
> + ctx->clear_bytes += tile_size & 0xFU;
> +
> + ctx->tile_group_sizes[tile_num].encrypted_bytes = encrypted_bytes;
> + ctx->tile_group_sizes[tile_num].aux_clear_bytes = encrypted_bytes ? ctx->clear_bytes : 0;
> + ctx->tile_group_sizes[tile_num].write_clear_bytes = tile_size & 0xFU;
> +
> + if (encrypted_bytes)
> + ctx->clear_bytes = 0;
> + total += tile_size;
> +
> + break;
> + }
> +
> + tile_size_bytes = frame_header->tile_size_bytes_minus1 + 1;
> + if (bytestream2_get_bytes_left(&gb) < tile_size_bytes)
> + return AVERROR_INVALIDDATA;
> +
> + for (int i = 0; i < tile_size_bytes; i++)
> + tile_size |= bytestream2_get_byteu(&gb) << 8 * i;
> + if (bytestream2_get_bytes_left(&gb) <= tile_size)
> + return AVERROR_INVALIDDATA;
> + tile_size++;
> +
> + // The spec requires encrypted bytes to be in blocks multiple of 16
> + encrypted_bytes = tile_size & ~0xFU;
> + ctx->clear_bytes += (tile_size & 0xFU) + tile_size_bytes;
> +
> + ctx->tile_group_sizes[tile_num].encrypted_bytes = encrypted_bytes;
> + ctx->tile_group_sizes[tile_num].aux_clear_bytes = encrypted_bytes ? ctx->clear_bytes : 0;
> + ctx->tile_group_sizes[tile_num].write_clear_bytes = (tile_size & 0xFU) + tile_size_bytes;
> +
> + if (encrypted_bytes)
> + ctx->clear_bytes = 0;
> +
> + total += tile_size + tile_size_bytes;
> + bytestream2_skipu(&gb, tile_size);
> + }
> +
> + bytestream2_init(&gb, tile_group->tile_data.data, tile_group->tile_data.data_size);
> +
> + avio_write(pb, fh_data, fh_data_size);
> + avio_write(pb, tile_group->data, tgh_data_size);
> +
> + for (unsigned tile_num = tile_group->tg_start; tile_num <= tile_group->tg_end; tile_num++) {
> + const struct MOVMuxCencAV1TGInfo *sizes = &ctx->tile_group_sizes[tile_num];
> +
> + avio_write(pb, gb.buffer, sizes->write_clear_bytes);
> + bytestream2_skipu(&gb, sizes->write_clear_bytes);
> + mov_cenc_write_encrypted(ctx, pb, gb.buffer, sizes->encrypted_bytes);
> + bytestream2_skipu(&gb, sizes->encrypted_bytes);
> + if (sizes->encrypted_bytes) {
> + unsigned clear_bytes = sizes->aux_clear_bytes;
> + if (clear_bytes > UINT16_MAX) {
> + auxiliary_info_add_subsample(ctx, UINT16_MAX, 0);
> + clear_bytes -= UINT16_MAX;
> + }
> + auxiliary_info_add_subsample(ctx, clear_bytes, sizes->encrypted_bytes);
> + }
> + }
> +
> + return total;
> +}
> +
> +int ff_mov_cenc_av1_write_obus(AVFormatContext *s, MOVMuxCencContext* ctx,
> + AVIOContext *pb, const AVPacket *pkt)
> +{
> + CodedBitstreamFragment *td = &ctx->temporal_unit;
> + const CodedBitstreamAV1Context *av1 = ctx->cbc->priv_data;
> + const AV1RawFrameHeader *frame_header = NULL;
> + const uint8_t *fh_data = NULL;
> + size_t fh_data_size;
> + int out_size = 0, ret;
> +
> + ret = mov_cenc_start_packet(ctx);
> + if (ret) {
> + return ret;
> + }
> +
> + ret = ff_cbs_read_packet(ctx->cbc, td, pkt);
> + if (ret < 0) {
> + av_log(s, AV_LOG_ERROR, "CENC-AV1: Failed to parse temporal unit.\n");
> + return ret;
> + }
> +
> + if (!av1->sequence_header) {
> + av_log(s, AV_LOG_ERROR, "CENC-AV1: No sequence header available\n");
> + ret = AVERROR_INVALIDDATA;
> + goto end;
> + }
> +
> + for (int i = 0; i < td->nb_units; i++) {
> + const CodedBitstreamUnit *unit = &td->units[i];
> + const AV1RawOBU *obu = unit->content;
> +
> + switch (unit->type) {
> + case AV1_OBU_FRAME_HEADER:
> + if (!obu->obu.frame_header.show_existing_frame) {
> + frame_header = &obu->obu.frame_header;
> + fh_data = unit->data;
> + fh_data_size = unit->data_size;
> + break;
> + }
> + // fall-through
> + case AV1_OBU_SEQUENCE_HEADER:
> + case AV1_OBU_METADATA:
> + avio_write(pb, unit->data, unit->data_size);
> + ctx->clear_bytes += unit->data_size;
> + out_size += unit->data_size;
> + break;
> + case AV1_OBU_FRAME:
> + frame_header = &obu->obu.frame.header;
> + fh_data = unit->data;
> + fh_data_size = unit->data_size;
> + // fall-through
> + case AV1_OBU_TILE_GROUP:
> + {
> + const AV1RawTileGroup *tile_group;
> +
> + if (!frame_header){
> + ret = AVERROR_INVALIDDATA;
> + goto end;
> + }
> +
> + if (unit->type == AV1_OBU_FRAME)
> + tile_group = &obu->obu.frame.tile_group;
> + else
> + tile_group = &obu->obu.tile_group;
> +
> + ret = write_tiles(s, ctx, pb, unit->type,
> + frame_header, fh_data, fh_data_size, tile_group);
> + if (ret < 0) {
> + av_log(s, AV_LOG_ERROR, "CENC-AV1: Failed to write tiles\n");
> + goto end;
> + }
> + av_assert0(ret == unit->data_size);
> + out_size += unit->data_size;
> + frame_header = NULL;
> + }
> + break;
> + default:
> + break;
> + }
> + }
> +
> + if (ctx->clear_bytes)
> + auxiliary_info_add_subsample(ctx, ctx->clear_bytes, 0);
> + ctx->clear_bytes = 0;
> +
> + ret = mov_cenc_end_packet(ctx);
> + if (ret) {
> + ret = AVERROR_INVALIDDATA;
> + goto end;
> + }
> +
> + ret = out_size;
> +end:
> + ff_cbs_fragment_reset(td);
> + return ret;
> +}
> +
> /* TODO: reuse this function from movenc.c */
> static int64_t update_size(AVIOContext *pb, int64_t pos)
> {
> @@ -388,8 +588,16 @@ int ff_mov_cenc_write_sinf_tag(MOVTrack* track, AVIOContext *pb, uint8_t* kid)
> return update_size(pb, pos);
> }
>
> +static const CodedBitstreamUnitType decompose_unit_types[] = {
> + AV1_OBU_TEMPORAL_DELIMITER,
> + AV1_OBU_SEQUENCE_HEADER,
> + AV1_OBU_FRAME_HEADER,
> + AV1_OBU_TILE_GROUP,
> + AV1_OBU_FRAME,
> +};
> +
> int ff_mov_cenc_init(MOVMuxCencContext* ctx, uint8_t* encryption_key,
> - int use_subsamples, int bitexact)
> + int use_subsamples, enum AVCodecID codec_id, int bitexact)
> {
> int ret;
>
> @@ -409,6 +617,15 @@ int ff_mov_cenc_init(MOVMuxCencContext* ctx, uint8_t* encryption_key,
>
> ctx->use_subsamples = use_subsamples;
>
> + if (codec_id == AV_CODEC_ID_AV1) {
> + ret = ff_cbs_init(&ctx->cbc, codec_id, NULL);
> + if (ret < 0)
> + return ret;
> +
> + ctx->cbc->decompose_unit_types = decompose_unit_types;
> + ctx->cbc->nb_decompose_unit_types = FF_ARRAY_ELEMS(decompose_unit_types);
> + }
> +
> return 0;
> }
>
> @@ -417,4 +634,8 @@ void ff_mov_cenc_free(MOVMuxCencContext* ctx)
> av_aes_ctr_free(ctx->aes_ctr);
> av_freep(&ctx->auxiliary_info);
> av_freep(&ctx->auxiliary_info_sizes);
> +
> + av_freep(&ctx->tile_group_sizes);
> + ff_cbs_fragment_free(&ctx->temporal_unit);
> + ff_cbs_close(&ctx->cbc);
> }
> diff --git a/libavformat/movenccenc.h b/libavformat/movenccenc.h
> index 7da5268090..722914dc1c 100644
> --- a/libavformat/movenccenc.h
> +++ b/libavformat/movenccenc.h
> @@ -23,6 +23,7 @@
> #define AVFORMAT_MOVENCCENC_H
>
> #include "libavutil/aes_ctr.h"
> +#include "libavcodec/cbs.h"
> #include "avformat.h"
> #include "avio.h"
>
> @@ -30,6 +31,12 @@
>
> struct MOVTrack;
>
> +struct MOVMuxCencAV1TGInfo {
> + uint32_t encrypted_bytes;
> + uint32_t write_clear_bytes;
> + uint32_t aux_clear_bytes;
> +};
> +
> typedef struct {
> struct AVAESCTR* aes_ctr;
> uint8_t* auxiliary_info;
> @@ -43,6 +50,14 @@ typedef struct {
> size_t auxiliary_info_subsample_start;
> uint8_t* auxiliary_info_sizes;
> size_t auxiliary_info_sizes_alloc_size;
> +
> + /* AV1 */
> + struct MOVMuxCencAV1TGInfo *tile_group_sizes;
> + uint32_t clear_bytes;
> + int tile_num;
> + /* CBS */
> + CodedBitstreamContext *cbc;
> + CodedBitstreamFragment temporal_unit;
> } MOVMuxCencContext;
>
> /**
> @@ -50,7 +65,8 @@ typedef struct {
> * @param key encryption key, must have a length of AES_CTR_KEY_SIZE
> * @param use_subsamples when enabled parts of a packet can be encrypted, otherwise the whole packet is encrypted
> */
> -int ff_mov_cenc_init(MOVMuxCencContext* ctx, uint8_t* encryption_key, int use_subsamples, int bitexact);
> +int ff_mov_cenc_init(MOVMuxCencContext* ctx, uint8_t* encryption_key, int use_subsamples,
> + enum AVCodecID codec_id, int bitexact);
>
> /**
> * Free a CENC context
> @@ -73,6 +89,8 @@ int ff_mov_cenc_avc_parse_nal_units(MOVMuxCencContext* ctx, AVIOContext *pb, con
> int ff_mov_cenc_avc_write_nal_units(AVFormatContext *s, MOVMuxCencContext* ctx, int nal_length_size,
> AVIOContext *pb, const uint8_t *buf_in, int size);
>
> +int ff_mov_cenc_av1_write_obus(AVFormatContext *s, MOVMuxCencContext* ctx,
> + AVIOContext *pb, const AVPacket *pkt);
> /**
> * Write the cenc atoms that should reside inside stbl
> */
Duplicating .o files is meant for small stuff, not for gigantic things
like cbs_av1.o (whose .text is 86519B here).
- Andreas
More information about the ffmpeg-devel
mailing list