diff mbox series

[FFmpeg-devel,v6] avformat: add MMTP parser and MMT/TLV demuxer

Message ID 20230503130223.15713-1-admin@superfashi.com
State New
Headers show
Series [FFmpeg-devel,v6] avformat: add MMTP parser and MMT/TLV demuxer | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

SuperFashi May 3, 2023, 1:02 p.m. UTC
v1 -> v2: Refactor using GetByteContext; Fix compile error.
v2 -> v3: Remove debug statement.
v3 -> v4: Squash commits.
v4 -> v5: Improve portability; Cosmetic changes.
v5 -> v6: remove unnecessary NULL checks.

This patch adds an MPEG Media Transport Protocol (MMTP) parser, as defined in ISO/IEC 23008-1, and an MMT protocol over TLV packets (MMT/TLV) demuxer, as defined in ARIB STD-B32. Currently, it supports HEVC, AAC LATM, and ARIB-TTML demuxing.

Since MMTP is designed to transmit over IP, there is no size information within each MMTP packet, and there is no on-disk format defined alongside the protocol. One industrial solution is a simple container format using type–length–value packets, which is defined in ARIB STD-B32.

Another known container format for MMTP is using packet capture (pcap) files which records network packets. This patch does not include the demuxer for this container format.

Signed-off-by: SuperFashi <admin@superfashi.com>
---
 Changelog                |    1 +
 doc/demuxers.texi        |    4 +
 libavformat/Makefile     |    1 +
 libavformat/allformats.c |    1 +
 libavformat/mmtp.c       | 1525 ++++++++++++++++++++++++++++++++++++++
 libavformat/mmtp.h       |   64 ++
 libavformat/mmttlv.c     |  335 +++++++++
 libavformat/version.h    |    2 +-
 8 files changed, 1932 insertions(+), 1 deletion(-)
 create mode 100644 libavformat/mmtp.c
 create mode 100644 libavformat/mmtp.h
 create mode 100644 libavformat/mmttlv.c

Comments

SuperFashi May 17, 2023, 8:27 a.m. UTC | #1
Bumping this, thx.

On Wed, May 3, 2023 at 22:02 SuperFashi <admin@superfashi.com> wrote:

> v1 -> v2: Refactor using GetByteContext; Fix compile error.
> v2 -> v3: Remove debug statement.
> v3 -> v4: Squash commits.
> v4 -> v5: Improve portability; Cosmetic changes.
> v5 -> v6: remove unnecessary NULL checks.
>
> This patch adds an MPEG Media Transport Protocol (MMTP) parser, as defined
> in ISO/IEC 23008-1, and an MMT protocol over TLV packets (MMT/TLV) demuxer,
> as defined in ARIB STD-B32. Currently, it supports HEVC, AAC LATM, and
> ARIB-TTML demuxing.
>
> Since MMTP is designed to transmit over IP, there is no size information
> within each MMTP packet, and there is no on-disk format defined alongside
> the protocol. One industrial solution is a simple container format using
> type–length–value packets, which is defined in ARIB STD-B32.
>
> Another known container format for MMTP is using packet capture (pcap)
> files which records network packets. This patch does not include the
> demuxer for this container format.
>
> Signed-off-by: SuperFashi <admin@superfashi.com>
> ---
>  Changelog                |    1 +
>  doc/demuxers.texi        |    4 +
>  libavformat/Makefile     |    1 +
>  libavformat/allformats.c |    1 +
>  libavformat/mmtp.c       | 1525 ++++++++++++++++++++++++++++++++++++++
>  libavformat/mmtp.h       |   64 ++
>  libavformat/mmttlv.c     |  335 +++++++++
>  libavformat/version.h    |    2 +-
>  8 files changed, 1932 insertions(+), 1 deletion(-)
>  create mode 100644 libavformat/mmtp.c
>  create mode 100644 libavformat/mmtp.h
>  create mode 100644 libavformat/mmttlv.c
>
> diff --git a/Changelog b/Changelog
> index 4901ef6ad7..594c445ea2 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -7,6 +7,7 @@ version <next>:
>  - Extend VAAPI support for libva-win32 on Windows
>  - afireqsrc audio source filter
>  - arls filter
> +- MMTP parser and MMT/TLV demuxer
>
>  version 6.0:
>  - Radiance HDR image support
> diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> index 2d33b47a56..56aab251b2 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -689,6 +689,10 @@ Set the sample rate for libopenmpt to output.
>  Range is from 1000 to INT_MAX. The value default is 48000.
>  @end table
>
> +@section mmttlv
> +
> +Demuxer for MMT protocol over TLV packets (MMT/TLV), as defined in ARIB
> STD-B32.
> +
>  @section mov/mp4/3gp
>
>  Demuxer for Quicktime File Format & ISO/IEC Base Media File Format
> (ISO/IEC 14496-12 or MPEG-4 Part 12, ISO/IEC 15444-12 or JPEG 2000 Part 12).
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index f8ad7c6a11..e32d6e71a3 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -354,6 +354,7 @@ OBJS-$(CONFIG_MLV_DEMUXER)               += mlvdec.o
> riffdec.o
>  OBJS-$(CONFIG_MM_DEMUXER)                += mm.o
>  OBJS-$(CONFIG_MMF_DEMUXER)               += mmf.o
>  OBJS-$(CONFIG_MMF_MUXER)                 += mmf.o rawenc.o
> +OBJS-$(CONFIG_MMTTLV_DEMUXER)            += mmtp.o mmttlv.o
>  OBJS-$(CONFIG_MODS_DEMUXER)              += mods.o
>  OBJS-$(CONFIG_MOFLEX_DEMUXER)            += moflex.o
>  OBJS-$(CONFIG_MOV_DEMUXER)               += mov.o mov_chan.o mov_esds.o \
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index efdb34e29d..d5f4f5680e 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -270,6 +270,7 @@ extern const AVInputFormat  ff_mlv_demuxer;
>  extern const AVInputFormat  ff_mm_demuxer;
>  extern const AVInputFormat  ff_mmf_demuxer;
>  extern const FFOutputFormat ff_mmf_muxer;
> +extern const AVInputFormat  ff_mmttlv_demuxer;
>  extern const AVInputFormat  ff_mods_demuxer;
>  extern const AVInputFormat  ff_moflex_demuxer;
>  extern const AVInputFormat  ff_mov_demuxer;
> diff --git a/libavformat/mmtp.c b/libavformat/mmtp.c
> new file mode 100644
> index 0000000000..6f002cc827
> --- /dev/null
> +++ b/libavformat/mmtp.c
> @@ -0,0 +1,1525 @@
> +/*
> + * MPEG Media Transport Protocol (MMTP) parser, as defined in ISO/IEC
> 23008-1.
> + * Copyright (c) 2023 SuperFashi
> + *
> + * 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
> + */
> +
> +#include <stdbool.h>
> +
> +#include "libavcodec/bytestream.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/mem.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "mmtp.h"
> +#include "network.h"
> +
> +struct MMTGeneralLocationInfo {
> +    uint8_t location_type;
> +    union {
> +        struct {
> +            uint16_t packet_id;
> +        } type0;
> +        struct {
> +            struct in_addr ipv4_src_addr;
> +            struct in_addr ipv4_dst_addr;
> +            uint16_t       dst_port;
> +            uint16_t       packet_id;
> +        } type1;
> +        struct {
> +            struct in6_addr ipv6_src_addr;
> +            struct in6_addr ipv6_dst_addr;
> +            uint16_t        dst_port;
> +            uint16_t        packet_id;
> +        } type2;
> +        struct {
> +            uint16_t network_id;
> +            uint16_t MPEG_2_transport_stream_id;
> +            uint16_t MPEG_2_PID: 13;
> +        } type3;
> +        struct {
> +            struct in6_addr ipv6_src_addr;
> +            struct in6_addr ipv6_dst_addr;
> +            uint16_t        dst_port;
> +            uint16_t        MPEG_2_PID: 13;
> +        } type4;
> +        struct {
> +            char URL[0x100 + 1];
> +        } type5;
> +    };
> +};
> +
> +static int parse_mmt_general_location_info(
> +    struct MMTGeneralLocationInfo *info, GetByteContext *gbc)
> +{
> +    uint8_t url_size;
> +
> +    if (bytestream2_get_bytes_left(gbc) < 1)
> +        return AVERROR_INVALIDDATA;
> +    switch (info->location_type = bytestream2_get_byteu(gbc)) {
> +    case 0x00:
> +        if (bytestream2_get_bytes_left(gbc) < 2)
> +            return AVERROR_INVALIDDATA;
> +        info->type0.packet_id = bytestream2_get_be16u(gbc);
> +        break;
> +    case 0x01:
> +        if (bytestream2_get_bytes_left(gbc) < (32 + 32 + 16 + 16) / 8)
> +            return AVERROR_INVALIDDATA;
> +        bytestream2_get_bufferu(gbc, (uint8_t *)
> &info->type1.ipv4_src_addr, 4);
> +        bytestream2_get_bufferu(gbc, (uint8_t *)
> &info->type1.ipv4_dst_addr, 4);
> +        info->type1.dst_port  = bytestream2_get_be16u(gbc);
> +        info->type1.packet_id = bytestream2_get_be16u(gbc);
> +        break;
> +    case 0x02:
> +        if (bytestream2_get_bytes_left(gbc) < (128 + 128 + 16 + 16) / 8)
> +            return AVERROR_INVALIDDATA;
> +        bytestream2_get_bufferu(
> +            gbc, (uint8_t *) &info->type2.ipv6_src_addr, 16);
> +        bytestream2_get_bufferu(
> +            gbc, (uint8_t *) &info->type2.ipv6_dst_addr, 16);
> +        info->type2.dst_port  = bytestream2_get_be16u(gbc);
> +        info->type2.packet_id = bytestream2_get_be16u(gbc);
> +        break;
> +    case 0x03:
> +        if (bytestream2_get_bytes_left(gbc) < (16 + 16 + 3 + 13) / 8)
> +            return AVERROR_INVALIDDATA;
> +        info->type3.network_id                 =
> bytestream2_get_be16u(gbc);
> +        info->type3.MPEG_2_transport_stream_id =
> bytestream2_get_be16u(gbc);
> +        info->type3.MPEG_2_PID =
> +            bytestream2_get_be16u(gbc) & 0b1111111111111;
> +        break;
> +    case 0x04:
> +        if (bytestream2_get_bytes_left(gbc) < (128 + 128 + 16 + 3 + 13) /
> 8)
> +            return AVERROR_INVALIDDATA;
> +        bytestream2_get_bufferu(
> +            gbc, (uint8_t *) &info->type4.ipv6_src_addr, 16);
> +        bytestream2_get_bufferu(
> +            gbc, (uint8_t *) &info->type4.ipv6_dst_addr, 16);
> +        info->type4.dst_port   = bytestream2_get_be16u(gbc);
> +        info->type4.MPEG_2_PID = bytestream2_get_be16u(gbc) &
> 0b1111111111111;
> +        break;
> +    case 0x05:
> +        url_size = bytestream2_get_byte(gbc);
> +        bytestream2_get_buffer(gbc, (uint8_t *) info->type5.URL,
> url_size);
> +        info->type5.URL[url_size] = '\0';
> +        break;
> +    default:
> +        return AVERROR_INVALIDDATA;
> +    }
> +    return 0;
> +}
> +
> +struct Streams {
> +    AVStream *stream;
> +
> +    int num_timestamp_descriptors;
> +    struct MPUTimestampDescriptor {
> +        uint32_t seq_num;
> +        int64_t  presentation_time;
> +    } *timestamp_descriptor;
> +
> +    int num_ext_timestamp_descriptors;
> +    struct MPUExtendedTimestampDescriptor {
> +        uint32_t seq_num;
> +        uint16_t decoding_time_offset;
> +        uint8_t  num_of_au;
> +        struct {
> +            uint16_t dts_pts_offset;
> +            uint16_t pts_offset;
> +        } au[0x100];
> +    } *ext_timestamp_descriptor;
> +
> +    uint32_t    last_sequence_number;
> +    uint16_t    au_count;
> +    AVBufferRef *pending_buffer;
> +    int64_t     offset;
> +    int         flags;
> +
> +    struct Streams *next;
> +};
> +
> +struct MMTPContext {
> +    struct FragmentAssembler *assembler;
> +    struct Streams           *streams;
> +    AVProgram                *program;
> +    // struct MMTGeneralLocationInfo mpt_location; TODO
> +
> +    // below are temporary fields available for the scope of a single
> packet
> +    AVFormatContext *s;
> +    AVPacket        *pkt;
> +    uint16_t        current_pid;
> +    bool            is_rap;
> +};
> +
> +static struct Streams *find_current_stream(struct MMTPContext *ctx)
> +{
> +    struct Streams *streams;
> +    for (streams = ctx->streams; streams != NULL; streams = streams->next)
> +        if (streams->stream->id == ctx->current_pid)
> +            return streams;
> +    return NULL;
> +}
> +
> +static struct Streams *
> +find_or_allocate_stream(struct MMTPContext *ctx, uint16_t pid)
> +{
> +    AVStream       *stream;
> +    struct Streams *streams;
> +    for (streams = ctx->streams; streams != NULL; streams = streams->next)
> +        if (streams->stream->id == pid) {
> +            ffstream(streams->stream)->need_context_update = 1;
> +            return streams;
> +        }
> +
> +    stream = avformat_new_stream(ctx->s, NULL);
> +    if (stream == NULL) return NULL;
> +    stream->id = pid;
> +    av_program_add_stream_index(ctx->s, ctx->program->id, stream->index);
> +
> +    streams = av_mallocz(sizeof(struct Streams));
> +    if (streams == NULL) return NULL;
> +    streams->stream = stream;
> +    streams->next   = ctx->streams;
> +    streams->offset = -1;
> +    ctx->streams    = streams;
> +    return streams;
> +}
> +
> +enum {
> +    MMT_PACKAGE_TABLE_ID  = 0x20,
> +    PACKAGE_LIST_TABLE_ID = 0x80,
> +};
> +
> +enum {
> +    MPU_TIMESTAMP_DESCRIPTOR          = 0x0001,
> +    ACCESS_CONTROL_DESCRIPTOR         = 0x8004,
> +    VIDEO_COMPONENT_DESCRIPTOR        = 0x8010,
> +    MH_STREAM_IDENTIFIER_DESCRIPTOR   = 0x8011,
> +    MH_AUDIO_COMPONENT_DESCRIPTOR     = 0x8014,
> +    MH_DATA_COMPONENT_DESCRIPTOR      = 0x8020,
> +    MPU_EXTENDED_TIMESTAMP_DESCRIPTOR = 0x8026,
> +};
> +
> +static int
> +parse_video_component_descriptor(AVStream *stream, GetByteContext *gbc)
> +{
> +    uint8_t descriptor_length;
> +    uint8_t language_code[4];
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != VIDEO_COMPONENT_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
> +        /*
> +         * skip:
> +         * - video_resolution
> +         * - video_aspect_ratio
> +         * - video_scan_flag
> +         * - reserved
> +         * - video_frame_rate
> +         * - component_tag
> +         * - video_transfer_characteristics
> +         * - reserved
> +         */
> +        bytestream2_skip(&ngbc, (4 + 4 + 1 + 2 + 5 + 16 + 4 + 4) / 8);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < 3)
> +            return AVERROR_INVALIDDATA;
> +        bytestream2_get_bufferu(&ngbc, language_code, 3);
> +        language_code[3] = '\0';
> +    }
> +    bytestream2_skipu(gbc, descriptor_length);
> +
> +    return av_dict_set(&stream->metadata, "language", language_code, 0);
> +}
> +
> +static int
> +parse_mh_audio_component_descriptor(AVStream *stream, GetByteContext *gbc)
> +{
> +    uint8_t descriptor_length;
> +    uint8_t stream_content;
> +    uint8_t stream_type;
> +    uint8_t language_code[4];
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != MH_AUDIO_COMPONENT_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        uint8_t byte;
> +        bool ES_multi_lingual_flag;
> +
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) <
> +            (4 + 4 + 8 + 16 + 8 + 8 + 1 + 1 + 2 + 3 + 1 + 24) / 8)
> +            return AVERROR_INVALIDDATA;
> +
> +        byte           = bytestream2_get_byteu(&ngbc);
> +        stream_content = byte & 0b1111;
> +
> +        /*
> +         * skip:
> +         * - component_type
> +         * - component_tag
> +         */
> +        bytestream2_skipu(&ngbc, 3);
> +        stream_type = bytestream2_get_byteu(&ngbc);
> +
> +        // skip: simulcast_group_tag
> +        bytestream2_skipu(&ngbc, 1);
> +
> +        byte                  = bytestream2_get_byteu(&ngbc);
> +        ES_multi_lingual_flag = byte >> 7;
> +
> +        bytestream2_get_bufferu(&ngbc, language_code, 3);
> +        language_code[3] = '\0';
> +
> +        if (ES_multi_lingual_flag) {
> +            if (bytestream2_get_bytes_left(&ngbc) < 3)
> +                return AVERROR_INVALIDDATA;
> +            bytestream2_skipu(&ngbc, 3);
> +        }
> +    }
> +    bytestream2_skipu(gbc, descriptor_length);
> +
> +    switch (stream_content) {
> +    case 0x3:
> +        switch (stream_type) {
> +        case 0x11:
> +            stream->codecpar->codec_id = AV_CODEC_ID_AAC_LATM;
> +            break;
> +        case 0x1c:
> +            stream->codecpar->codec_id = AV_CODEC_ID_AAC;
> +            break;
> +        }
> +        break;
> +    case 0x4:
> +        stream->codecpar->codec_id = AV_CODEC_ID_MP4ALS;
> +        break;
> +    }
> +
> +    return av_dict_set(&stream->metadata, "language", language_code, 0);
> +}
> +
> +#define MAX_NUM_TIMESTAMP_DESCRIPTOR 32
> +#define DIFF(a, b) ((a) > (b) ? ((a) - (b)) : ((b) - (a)))
> +
> +static int
> +parse_mpu_timestamp_descriptor(struct Streams *streams, GetByteContext
> *gbc)
> +{
> +    uint8_t descriptor_length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +
> +    if (bytestream2_get_be16u(gbc) != MPU_TIMESTAMP_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
> +
> +        while (bytestream2_get_bytes_left(&ngbc) > 0) {
> +            uint64_t mpu_seq_num;
> +            int64_t  mpu_presentation_time;
> +            size_t   i;
> +
> +            struct MPUTimestampDescriptor *desc;
> +
> +            if (bytestream2_get_bytes_left(&ngbc) < (32 + 64) / 8)
> +                return AVERROR_INVALIDDATA;
> +            mpu_seq_num = bytestream2_get_be32u(&ngbc);
> +            mpu_presentation_time =
> +                ff_parse_ntp_time(bytestream2_get_be64u(&ngbc)) -
> NTP_OFFSET_US;
> +
> +            if (mpu_seq_num >= streams->last_sequence_number) {
> +                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
> +                    if (streams->timestamp_descriptor[i].seq_num ==
> +                        mpu_seq_num) {
> +                        desc = streams->timestamp_descriptor + i;
> +                        goto end2;
> +                    }
> +
> +                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
> +                    if (streams->timestamp_descriptor[i].seq_num <
> +                        streams->last_sequence_number) {
> +                        desc = streams->timestamp_descriptor + i;
> +                        goto end1;
> +                    }
> +
> +                if (streams->num_timestamp_descriptors + 1 >
> +                    MAX_NUM_TIMESTAMP_DESCRIPTOR) {
> +                    // we have all descriptors larger than the current
> sequence number
> +                    // we can't add more, so we should evict the one with
> the largest distance
> +                    uint64_t max_dist = 0;
> +                    for (i = 0; i < streams->num_timestamp_descriptors;
> ++i)
> +                        if (DIFF(
> +                                streams->timestamp_descriptor[i].seq_num,
> +                                mpu_seq_num) > max_dist) {
> +                            desc     = streams->timestamp_descriptor + i;
> +                            max_dist = DIFF(
> +                                streams->timestamp_descriptor[i].seq_num,
> +                                mpu_seq_num);
> +                        }
> +                    av_assert1(desc != NULL); // should never fail
> +                    goto end1;
> +                }
> +
> +                desc = av_dynarray2_add(
> +                    (void **) &streams->timestamp_descriptor,
> +                    &streams->num_timestamp_descriptors,
> +                    sizeof(struct MPUTimestampDescriptor), NULL);
> +                if (desc == NULL) return AVERROR(ENOMEM);
> +
> +                end1:
> +                desc->seq_num           = mpu_seq_num;
> +                end2:
> +                desc->presentation_time = mpu_presentation_time;
> +            }
> +        }
> +    }
> +    bytestream2_skipu(gbc, descriptor_length);
> +
> +    return 0;
> +}
> +
> +static int parse_mpu_extended_timestamp_descriptor(
> +    struct Streams *streams, GetByteContext *gbc)
> +{
> +    uint8_t descriptor_length;
> +
> +    AVStream *stream = streams->stream;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != MPU_EXTENDED_TIMESTAMP_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        uint8_t  byte;
> +        uint8_t  pts_offset_type;
> +        bool timescale_flag;
> +        uint16_t default_pts_offset = 0;
> +
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < (5 + 2 + 1) / 8)
> +            return AVERROR_INVALIDDATA;
> +        byte            = bytestream2_get_byte(&ngbc);
> +        pts_offset_type = (byte >> 1) & 0b11;
> +        timescale_flag  = byte & 1;
> +
> +        if (timescale_flag) {
> +            if (bytestream2_get_bytes_left(&ngbc) < 4)
> +                return AVERROR_INVALIDDATA;
> +            stream->time_base.num = 1;
> +            stream->time_base.den = bytestream2_get_be32u(&ngbc);
> +        }
> +
> +        if (pts_offset_type == 1) {
> +            if (bytestream2_get_bytes_left(&ngbc) < 2)
> +                return AVERROR_INVALIDDATA;
> +            default_pts_offset = bytestream2_get_be16u(&ngbc);
> +        }
> +
> +        while (bytestream2_get_bytes_left(&ngbc) > 0) {
> +            size_t   i;
> +            uint8_t  num_of_au;
> +            uint16_t decoding_time_offset;
> +            uint64_t mpu_seq_num;
> +
> +            struct MPUExtendedTimestampDescriptor *desc = NULL;
> +
> +            if (pts_offset_type == 0)
> +                return AVERROR_PATCHWELCOME;  // we don't know how to
> handle this
> +
> +            if (bytestream2_get_bytes_left(&ngbc) < (32 + 2 + 6 + 16 + 8)
> / 8)
> +                return AVERROR_INVALIDDATA;
> +            mpu_seq_num = bytestream2_get_be32u(&ngbc);
> +            // skip: leap_indicator
> +            bytestream2_skip(&ngbc, (2 + 6) / 8);
> +            decoding_time_offset = bytestream2_get_be16u(&ngbc);
> +            num_of_au            = bytestream2_get_byteu(&ngbc);
> +
> +            if (mpu_seq_num >= streams->last_sequence_number) {
> +                for (i = 0; i < streams->num_ext_timestamp_descriptors;
> ++i)
> +                    if (streams->ext_timestamp_descriptor[i].seq_num ==
> +                        mpu_seq_num) {
> +                        desc = streams->ext_timestamp_descriptor + i;
> +                        goto end2;
> +                    }
> +
> +                for (i = 0; i < streams->num_ext_timestamp_descriptors;
> ++i)
> +                    if (streams->ext_timestamp_descriptor[i].seq_num <
> +                        streams->last_sequence_number) {
> +                        desc = streams->ext_timestamp_descriptor + i;
> +                        goto end1;
> +                    }
> +
> +                if (streams->num_ext_timestamp_descriptors + 1 >
> +                    MAX_NUM_TIMESTAMP_DESCRIPTOR) {
> +                    uint64_t max_diff = 0;
> +                    for (i = 0; i <
> streams->num_ext_timestamp_descriptors; ++i)
> +                        if (DIFF(
> +
> streams->ext_timestamp_descriptor[i].seq_num,
> +                                mpu_seq_num) > max_diff) {
> +                            desc     = streams->ext_timestamp_descriptor
> + i;
> +                            max_diff = DIFF(
> +
> streams->ext_timestamp_descriptor[i].seq_num,
> +                                mpu_seq_num);
> +                        }
> +                    av_assert1(desc != NULL);
> +                    goto end1;
> +                }
> +
> +                desc = av_dynarray2_add(
> +                    (void **) &streams->ext_timestamp_descriptor,
> +                    &streams->num_ext_timestamp_descriptors,
> +                    sizeof(struct MPUExtendedTimestampDescriptor), NULL);
> +                if (desc == NULL)
> +                    return AVERROR(ENOMEM);
> +
> +                end1:
> +                desc->seq_num              = mpu_seq_num;
> +                end2:
> +                desc->decoding_time_offset = decoding_time_offset;
> +                desc->num_of_au            = num_of_au;
> +            }
> +
> +            for (i = 0; i < num_of_au; ++i) {
> +                if (bytestream2_get_bytes_left(&ngbc) < 2)
> +                    return AVERROR_INVALIDDATA;
> +                if (desc != NULL)
> +                    desc->au[i].dts_pts_offset =
> bytestream2_get_be16u(&ngbc);
> +                else
> +                    bytestream2_skipu(&ngbc, 2);
> +
> +                if (pts_offset_type == 2) {
> +                    if (bytestream2_get_bytes_left(&ngbc) < 2)
> +                        return AVERROR_INVALIDDATA;
> +                    if (desc != NULL)
> +                        desc->au[i].pts_offset =
> bytestream2_get_be16u(&ngbc);
> +                    else
> +                        bytestream2_skipu(&ngbc, 2);
> +                } else if (desc != NULL) {
> +                    desc->au[i].pts_offset = default_pts_offset;
> +                }
> +            }
> +        }
> +    }
> +    bytestream2_skipu(gbc, descriptor_length);
> +
> +    return 0;
> +}
> +
> +static int
> +parse_additional_arib_subtitle_info(AVStream *stream, GetByteContext *gbc)
> +{
> +    bool    start_mpu_sequence_number_flag;
> +    char    language_code[4];
> +    uint8_t subtitle_format;
> +
> +    if (bytestream2_get_bytes_left(gbc) <
> +        (8 + 4 + 1 + 3 + 24 + 2 + 4 + 2 + 4 + 4 + 4 + 4) / 8)
> +        return AVERROR_INVALIDDATA;
> +    // skip: subtitle_tag
> +    bytestream2_skipu(gbc, 1);
> +    start_mpu_sequence_number_flag = (bytestream2_get_byteu(gbc) >> 3) &
> 1;
> +    bytestream2_get_bufferu(gbc, language_code, 3);
> +    language_code[3] = '\0';
> +    subtitle_format = (bytestream2_get_byteu(gbc) >> 2) & 0b1111;
> +    /*
> +     * skip:
> +     * - TMD
> +     * - DMF
> +     * - resolution
> +     * - compression_type
> +     */
> +    bytestream2_skipu(gbc, (4 + 4 + 4 + 4) / 8);
> +
> +    if (start_mpu_sequence_number_flag)
> +        bytestream2_skip(gbc, 32);
> +
> +    switch (subtitle_format) {
> +    case 0b0000:
> +        stream->codecpar->codec_id = AV_CODEC_ID_TTML;
> +        break;
> +    }
> +
> +    return av_dict_set(&stream->metadata, "language", language_code, 0);
> +}
> +
> +static int
> +parse_mh_data_component_descriptor(AVStream *stream, GetByteContext *gbc)
> +{
> +    uint8_t descriptor_length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != MH_DATA_COMPONENT_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
> +        bytestream2_skipu(gbc, descriptor_length);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < 16 / 8)
> +            return AVERROR_INVALIDDATA;
> +        switch (bytestream2_get_be16u(&ngbc)) {
> +        case 0x0020: // additional ARIB subtitle info (Table 7-74, ARIB
> STD-B60, Version 1.14-E1)
> +            return parse_additional_arib_subtitle_info(stream, &ngbc);
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static int
> +parse_stream_identifier_descriptor(AVStream *stream, GetByteContext *gbc)
> +{
> +    uint8_t descriptor_length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != MH_STREAM_IDENTIFIER_DESCRIPTOR)
> +        return AVERROR_INVALIDDATA;
> +    descriptor_length = bytestream2_get_byteu(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        // no need for now
> +    }
> +    bytestream2_skipu(gbc, descriptor_length);
> +
> +    return 0;
> +}
> +
> +static int parse_descriptor(struct Streams *streams, GetByteContext *gbc)
> +{
> +    if (bytestream2_get_bytes_left(gbc) < 3)
> +        return AVERROR_INVALIDDATA;
> +    switch (bytestream2_peek_be16u(gbc)) {
> +    case MPU_TIMESTAMP_DESCRIPTOR:
> +        return parse_mpu_timestamp_descriptor(streams, gbc);
> +    case VIDEO_COMPONENT_DESCRIPTOR:
> +        return parse_video_component_descriptor(streams->stream, gbc);
> +    case MH_STREAM_IDENTIFIER_DESCRIPTOR:
> +        return parse_stream_identifier_descriptor(streams->stream, gbc);
> +    case MH_AUDIO_COMPONENT_DESCRIPTOR:
> +        return parse_mh_audio_component_descriptor(streams->stream, gbc);
> +    case MH_DATA_COMPONENT_DESCRIPTOR:
> +        return parse_mh_data_component_descriptor(streams->stream, gbc);
> +    case MPU_EXTENDED_TIMESTAMP_DESCRIPTOR:
> +        return parse_mpu_extended_timestamp_descriptor(streams, gbc);
> +    case ACCESS_CONTROL_DESCRIPTOR:
> +        bytestream2_skipu(gbc, 2);
> +        bytestream2_skip(gbc, bytestream2_get_byteu(gbc));
> +        return 0;
> +    }
> +    av_log(streams->stream, AV_LOG_INFO, "Unknown descriptor: 0x%04x\n",
> +           bytestream2_peek_be16u(gbc));
> +    return AVERROR_PATCHWELCOME;
> +}
> +
> +static int parse_mmt_package_table(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    uint16_t length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 16) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_byteu(gbc) != MMT_PACKAGE_TABLE_ID)
> +        return AVERROR_INVALIDDATA;
> +    // skip: version
> +    bytestream2_skipu(gbc, 1);
> +    length = bytestream2_get_be16u(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        size_t   i, j;
> +        uint8_t  package_id_length;
> +        uint16_t descriptors_length;
> +        uint8_t  number_of_assets;
> +
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, length);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < (6 + 2 + 8) / 8)
> +            return AVERROR_INVALIDDATA;
> +
> +        // skip: MPT_mode
> +        bytestream2_skipu(&ngbc, 1);
> +        package_id_length = bytestream2_get_byteu(&ngbc);
> +
> +        bytestream2_skip(&ngbc, package_id_length);
> +
> +        descriptors_length = bytestream2_get_be16(&ngbc);
> +        bytestream2_skip(&ngbc, descriptors_length);
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < 1)
> +            return AVERROR_INVALIDDATA;
> +        number_of_assets = bytestream2_get_byteu(&ngbc);
> +
> +        for (i = 0; i < number_of_assets; ++i) {
> +            int err;
> +
> +            uint8_t  asset_id_length;
> +            uint8_t  location_count;
> +            uint16_t asset_descriptors_length;
> +            uint32_t asset_type;
> +
> +            struct Streams *stream = NULL;
> +
> +            struct MMTGeneralLocationInfo info;
> +
> +            if (bytestream2_get_bytes_left(&ngbc) < (8 + 32 + 8) / 8)
> +                return AVERROR_INVALIDDATA;
> +            /*
> +             * skip:
> +             * - identifier_type
> +             * - asset_id_scheme
> +             */
> +            bytestream2_skipu(&ngbc, (8 + 32) / 8);
> +            asset_id_length = bytestream2_get_byteu(&ngbc);
> +
> +            bytestream2_skip(&ngbc, asset_id_length);
> +
> +            asset_type = bytestream2_get_le32(&ngbc);
> +
> +            // skip: asset_clock_relation_flag
> +            bytestream2_skip(&ngbc, 1);
> +
> +            if (bytestream2_get_bytes_left(&ngbc) < 1)
> +                return AVERROR_INVALIDDATA;
> +            location_count = bytestream2_get_byteu(&ngbc);
> +
> +            for (j = 0; j < location_count; ++j)
> +                if ((err = parse_mmt_general_location_info(&info, &ngbc))
> < 0)
> +                    return err;
> +
> +            switch (asset_type) {
> +            case MKTAG('h', 'e', 'v', '1'):
> +                if (info.location_type != 0x00) return
> AVERROR_PATCHWELCOME;
> +                stream = find_or_allocate_stream(ctx,
> info.type0.packet_id);
> +                if (stream == NULL) return AVERROR(ENOMEM);
> +                stream->stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> +                stream->stream->codecpar->codec_id   = AV_CODEC_ID_HEVC;
> +                stream->stream->codecpar->codec_tag  = asset_type;
> +                break;
> +            case MKTAG('m', 'p', '4', 'a'):
> +                if (info.location_type != 0x00) return
> AVERROR_PATCHWELCOME;
> +                stream = find_or_allocate_stream(ctx,
> info.type0.packet_id);
> +                if (stream == NULL) return AVERROR(ENOMEM);
> +                stream->stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> +                stream->stream->codecpar->codec_tag  = asset_type;
> +                break;
> +            case MKTAG('s', 't', 'p', 'p'):
> +                if (info.location_type == 0x00) {
> +                    stream = find_or_allocate_stream(ctx,
> info.type0.packet_id);
> +                    if (stream == NULL) return AVERROR(ENOMEM);
> +                    stream->stream->codecpar->codec_type =
> AVMEDIA_TYPE_SUBTITLE;
> +                    stream->stream->codecpar->codec_tag  = asset_type;
> +                }
> +                break;
> +            case MKTAG('a', 'a', 'p', 'p'):
> +            case MKTAG('a', 's', 'g', 'd'):
> +            case MKTAG('a', 'a', 'g', 'd'):
> +                break; // TODO
> +            }
> +
> +            if (bytestream2_get_bytes_left(&ngbc) < 2)
> +                return AVERROR_INVALIDDATA;
> +            asset_descriptors_length = bytestream2_get_be16u(&ngbc);
> +            if (bytestream2_get_bytes_left(&ngbc) <
> asset_descriptors_length)
> +                return AVERROR_INVALIDDATA;
> +            if (stream != NULL) {
> +                GetByteContext nngbc;
> +                bytestream2_init(&nngbc, ngbc.buffer,
> asset_descriptors_length);
> +
> +                while (bytestream2_get_bytes_left(&nngbc) > 0)
> +                    if ((err = parse_descriptor(stream, &nngbc)) < 0)
> +                        return err;
> +            }
> +            bytestream2_skipu(&ngbc, asset_descriptors_length);
> +        }
> +    }
> +    bytestream2_skipu(gbc, length);
> +
> +    return 0;
> +}
> +
> +static int parse_package_list_table(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    size_t   i;
> +    uint32_t length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 16) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_byteu(gbc) != PACKAGE_LIST_TABLE_ID)
> +        return AVERROR_INVALIDDATA;
> +    // skip: version
> +    bytestream2_skipu(gbc, 1);
> +    length = bytestream2_get_be16u(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        int     err;
> +        uint8_t num_of_package;
> +        uint8_t num_of_ip_delivery;
> +
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, length);
> +
> +        if (bytestream2_get_bytes_left(gbc) < 1)
> +            return AVERROR_INVALIDDATA;
> +        num_of_package = bytestream2_get_byteu(&ngbc);
> +
> +        for (i = 0; i < num_of_package; ++i) {
> +            uint8_t                       package_id_length;
> +            struct MMTGeneralLocationInfo info;
> +
> +            package_id_length = bytestream2_get_byte(&ngbc);
> +            bytestream2_skip(&ngbc, package_id_length);
> +
> +            if ((err = parse_mmt_general_location_info(&info, &ngbc)) < 0)
> +                return err;
> +        }
> +
> +        if (bytestream2_get_bytes_left(&ngbc) < 1)
> +            return AVERROR_INVALIDDATA;
> +        num_of_ip_delivery = bytestream2_get_byteu(&ngbc);
> +
> +        for (i = 0; i < num_of_ip_delivery; ++i)
> +            return AVERROR_PATCHWELCOME;
> +    }
> +    bytestream2_skipu(gbc, length);
> +
> +    return 0;
> +}
> +
> +static int parse_table(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    if (bytestream2_get_bytes_left(gbc) < 2)
> +        return AVERROR_INVALIDDATA;
> +    switch (bytestream2_peek_byteu(gbc)) {
> +    case MMT_PACKAGE_TABLE_ID:
> +        return parse_mmt_package_table(ctx, gbc);
> +    case PACKAGE_LIST_TABLE_ID:
> +        return parse_package_list_table(ctx, gbc);
> +    }
> +    bytestream2_skipu(gbc, bytestream2_get_bytes_left(gbc)); // TODO
> +    return 0;
> +}
> +
> +enum {
> +    PA_MESSAGE_ID = 0x0000,
> +};
> +
> +static int parse_pa_message(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    uint32_t length;
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 8 + 32) / 8)
> +        return AVERROR_INVALIDDATA;
> +    if (bytestream2_get_be16u(gbc) != PA_MESSAGE_ID)
> +        return AVERROR_INVALIDDATA;
> +    // skip: version
> +    bytestream2_skipu(gbc, 1);
> +    length = bytestream2_get_be32u(gbc);
> +
> +    if (bytestream2_get_bytes_left(gbc) < length)
> +        return AVERROR_INVALIDDATA;
> +    {
> +        size_t  i;
> +        uint8_t num_of_tables;
> +
> +        GetByteContext ngbc;
> +        bytestream2_init(&ngbc, gbc->buffer, length);
> +
> +        if (bytestream2_get_bytes_left(gbc) < 1)
> +            return AVERROR_INVALIDDATA;
> +        num_of_tables = bytestream2_get_byteu(&ngbc);
> +
> +        for (i = 0; i < num_of_tables; ++i) {
> +            bytestream2_skip(&ngbc, (8 + 8 + 16) / 8);
> +        }
> +
> +        while (bytestream2_get_bytes_left(&ngbc) > 0) {
> +            int err = parse_table(ctx, &ngbc);
> +            if (err < 0) return err;
> +        }
> +    }
> +    bytestream2_skipu(gbc, length);
> +
> +    return 0;
> +}
> +
> +static int parse_signalling_message(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    if (bytestream2_get_bytes_left(gbc) < 4)
> +        return AVERROR_INVALIDDATA;
> +    switch (bytestream2_peek_be16u(gbc)) {
> +    case PA_MESSAGE_ID:
> +        return parse_pa_message(ctx, gbc);
> +    }
> +    return 0;
> +}
> +
> +enum FragmentationIndicator {
> +    NOT_FRAGMENTED  = 0b00,
> +    FIRST_FRAGMENT  = 0b01,
> +    MIDDLE_FRAGMENT = 0b10,
> +    LAST_FRAGMENT   = 0b11,
> +};
> +
> +struct FragmentAssembler {
> +    uint16_t                 pid;
> +    struct FragmentAssembler *next;
> +
> +    uint8_t *data;
> +    size_t  size, cap;
> +
> +    uint32_t last_seq;
> +
> +    enum {
> +        INIT = 0,
> +        NOT_STARTED,
> +        IN_FRAGMENT,
> +        SKIP,
> +    } state;
> +};
> +
> +static int
> +append_data(struct FragmentAssembler *ctx, const uint8_t *data, uint32_t
> size)
> +{
> +    if (ctx->size + size > UINT32_MAX) return AVERROR(EOVERFLOW);
> +    if (ctx->cap < ctx->size + size) {
> +        void   *new_data;
> +        size_t new_cap = ctx->cap == 0 ? 1024 : ctx->cap * 2;
> +        while (new_cap < ctx->size + size) new_cap *= 2;
> +
> +        new_data = av_realloc(ctx->data, new_cap);
> +        if (new_data == NULL) return AVERROR(errno);
> +        ctx->data = new_data;
> +        ctx->cap  = new_cap;
> +    }
> +    memcpy(ctx->data + ctx->size, data, size);
> +    ctx->size += size;
> +    return 0;
> +}
> +
> +static int
> +check_state(MMTPContext *ctx, struct FragmentAssembler *ass, uint32_t
> seq_num)
> +{
> +    if (ass->state == INIT) {
> +        ass->state = SKIP;
> +    } else if (seq_num != ass->last_seq + 1) {
> +        if (ass->size != 0) {
> +            av_log(ctx->s, AV_LOG_WARNING,
> +                   "Packet sequence number jump: %u + 1 != %u, drop %zu
> bytes\n",
> +                   ass->last_seq, seq_num, ass->size);
> +            ass->size = 0;
> +        } else {
> +            av_log(ctx->s, AV_LOG_WARNING,
> +                   "Packet sequence number jump: %u + 1 != %u\n",
> +                   ass->last_seq, seq_num);
> +        }
> +        ass->state = SKIP;
> +    }
> +    ass->last_seq = seq_num;
> +    return 0;
> +}
> +
> +static int assemble_fragment(
> +    struct FragmentAssembler *ctx, uint32_t seq_num,
> +    enum FragmentationIndicator indicator,
> +    const uint8_t *data, uint32_t size,
> +    int (*parser)(MMTPContext *, GetByteContext *),
> +    MMTPContext *opaque)
> +{
> +    GetByteContext gbc;
> +    int            err;
> +
> +    switch (indicator) {
> +    case NOT_FRAGMENTED:
> +        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
> +        ctx->state = NOT_STARTED;
> +        bytestream2_init(&gbc, data, size);
> +        return parser(opaque, &gbc);
> +    case FIRST_FRAGMENT:
> +        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
> +        ctx->state = IN_FRAGMENT;
> +        return append_data(ctx, data, size);
> +    case MIDDLE_FRAGMENT:
> +        if (ctx->state == SKIP) {
> +            av_log(opaque->s, AV_LOG_VERBOSE, "Drop packet %u\n",
> seq_num);
> +            return 0;
> +        }
> +        if (ctx->state != IN_FRAGMENT) return AVERROR_INVALIDDATA;
> +        return append_data(ctx, data, size);
> +    case LAST_FRAGMENT:
> +        if (ctx->state == SKIP) {
> +            av_log(opaque->s, AV_LOG_VERBOSE, "Drop packet %u\n",
> seq_num);
> +            return 0;
> +        }
> +        if (ctx->state != IN_FRAGMENT) return AVERROR_INVALIDDATA;
> +        if ((err = append_data(ctx, data, size)) < 0) return err;
> +
> +        bytestream2_init(&gbc, ctx->data, ctx->size);
> +        err = parser(opaque, &gbc);
> +
> +        ctx->size  = 0;
> +        ctx->state = NOT_STARTED;
> +        return err;
> +    default:
> +        return AVERROR_INVALIDDATA;
> +    }
> +}
> +
> +static struct FragmentAssembler *
> +find_or_allocate_assembler(MMTPContext *ctx, uint16_t pid)
> +{
> +    struct FragmentAssembler *ass;
> +    for (ass = ctx->assembler; ass != NULL; ass = ass->next)
> +        if (ass->pid == pid)
> +            return ass;
> +
> +    ass = av_mallocz(sizeof(struct FragmentAssembler));
> +    if (ass == NULL) return NULL;
> +    ass->pid              = pid;
> +    ass->next             = ctx->assembler;
> +    return ctx->assembler = ass;
> +}
> +
> +static int parse_signalling_messages(
> +    MMTPContext *ctx, uint32_t seq_num, GetByteContext *gbc)
> +{
> +    int                         err;
> +    uint8_t                     byte;
> +    enum FragmentationIndicator fragmentation_indicator;
> +    bool                        length_extension_flag;
> +    bool                        aggregation_flag;
> +
> +    struct FragmentAssembler *assembler = find_or_allocate_assembler(
> +        ctx, ctx->current_pid);
> +    if (assembler == NULL) return AVERROR(errno);
> +
> +    if (bytestream2_get_bytes_left(gbc) < (2 + 4 + 1 + 1 + 8) / 8)
> +        return AVERROR_INVALIDDATA;
> +    byte                    = bytestream2_get_byteu(gbc);
> +    fragmentation_indicator = byte >> 6;
> +    length_extension_flag   = (byte >> 1) & 1;
> +    aggregation_flag        = byte & 1;
> +
> +    bytestream2_skipu(gbc, 1);
> +
> +    if ((err = check_state(ctx, assembler, seq_num)) < 0)
> +        return err;
> +
> +    if (!aggregation_flag)
> +        return assemble_fragment(
> +            assembler, seq_num, fragmentation_indicator,
> +            gbc->buffer, bytestream2_get_bytes_left(gbc),
> +            parse_signalling_message, ctx);
> +
> +    if (fragmentation_indicator != NOT_FRAGMENTED)
> +        return AVERROR_INVALIDDATA; // cannot be both fragmented and
> aggregated
> +
> +    while (bytestream2_get_bytes_left(gbc) > 0) {
> +        uint32_t length;
> +
> +        if (length_extension_flag)
> +            length = bytestream2_get_be32(gbc);
> +        else
> +            length = bytestream2_get_be16(gbc);
> +
> +        if (bytestream2_get_bytes_left(gbc) < length)
> +            return AVERROR_INVALIDDATA;
> +        if ((err = assemble_fragment(
> +            assembler, seq_num, NOT_FRAGMENTED,
> +            gbc->buffer, length, parse_signalling_message, ctx)) < 0)
> +            return err;
> +        bytestream2_skipu(gbc, length);
> +    }
> +
> +    return 0;
> +}
> +
> +static int fill_pts_dts(MMTPContext *ctx, struct Streams *s)
> +{
> +    struct MPUTimestampDescriptor         *desc     = NULL;
> +    struct MPUExtendedTimestampDescriptor *ext_desc = NULL;
> +
> +    int64_t ptime;
> +    size_t  i, j;
> +
> +    for (i = 0; i < s->num_timestamp_descriptors; ++i) {
> +        if (s->timestamp_descriptor[i].seq_num ==
> +            s->last_sequence_number) {
> +            desc = s->timestamp_descriptor + i;
> +            break;
> +        }
> +    }
> +
> +    for (i = 0; i < s->num_ext_timestamp_descriptors; ++i) {
> +        if (s->ext_timestamp_descriptor[i].seq_num ==
> +            s->last_sequence_number) {
> +            ext_desc = s->ext_timestamp_descriptor + i;
> +            break;
> +        }
> +    }
> +
> +    if (desc == NULL || ext_desc == NULL) return FFERROR_REDO;
> +    ptime = av_rescale(desc->presentation_time, s->stream->time_base.den,
> +                       1000000ll * s->stream->time_base.num);
> +
> +    if (s->au_count >= ext_desc->num_of_au)
> +        return AVERROR_INVALIDDATA;
> +
> +    ctx->pkt->dts = ptime - ext_desc->decoding_time_offset;
> +
> +    for (j = 0; j < s->au_count; ++j)
> +        ctx->pkt->dts += ext_desc->au[j].pts_offset;
> +
> +    ctx->pkt->pts = ctx->pkt->dts +
> ext_desc->au[s->au_count].dts_pts_offset;
> +
> +    ++s->au_count;
> +    return 0;
> +}
> +
> +static int emit_closed_caption_mfu(MMTPContext *ctx, struct Streams *st,
> +                                   GetByteContext *gbc)
> +{
> +    uint8_t  data_type, subsample_number, last_subsample_number, byte;
> +    uint32_t data_size;
> +    size_t   i;
> +    int      err;
> +    bool     length_ext_flag, subsample_info_list_flag;
> +
> +    av_assert0(ctx->pkt != NULL);
> +
> +    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 8 + 8 + 4 + 1 + 1 + 2)
> / 8)
> +        return AVERROR_INVALIDDATA;
> +
> +    /*
> +     * skip:
> +     * - subtitle_tag
> +     * - subtitle_sequence_number
> +     */
> +    bytestream2_skipu(gbc, (8 + 8) / 8);
> +
> +    subsample_number      = bytestream2_get_byteu(gbc);
> +    last_subsample_number = bytestream2_get_byteu(gbc);
> +
> +    byte                     = bytestream2_get_byteu(gbc);
> +    data_type                = byte >> 4;
> +    length_ext_flag          = (byte >> 3) & 1;
> +    subsample_info_list_flag = (byte >> 2) & 1;
> +
> +    if (data_type != 0b0000) return AVERROR_PATCHWELCOME;
> +
> +    if (length_ext_flag)
> +        data_size = bytestream2_get_be32(gbc);
> +    else
> +        data_size = bytestream2_get_be16(gbc);
> +
> +    if (subsample_number == 0 && last_subsample_number > 0 &&
> +        subsample_info_list_flag) {
> +        for (i = 0; i < last_subsample_number; ++i) {
> +            // skip: subsample_i_data_type
> +            bytestream2_skip(gbc, (4 + 4) / 8);
> +            // skip: subsample_i_data_size
> +            if (length_ext_flag) {
> +                bytestream2_skip(gbc, 32 / 8);
> +            } else {
> +                bytestream2_skip(gbc, 16 / 8);
> +            }
> +        }
> +    }
> +
> +    if (bytestream2_get_bytes_left(gbc) < data_size)
> +        return AVERROR_INVALIDDATA;
> +    if ((err = av_new_packet(ctx->pkt, data_size)) < 0) return err;
> +    bytestream2_get_bufferu(gbc, ctx->pkt->data, data_size);
> +
> +    ctx->pkt->stream_index = st->stream->index;
> +    ctx->pkt->flags        = st->flags;
> +    ctx->pkt->pos          = st->offset;
> +    ctx->pkt               = NULL;
> +
> +    st->flags  = 0;
> +    st->offset = -1;
> +    return 0;
> +}
> +
> +static int emit_packet(MMTPContext *ctx, struct Streams *st, AVBufferRef
> *buf)
> +{
> +    int err;
> +    av_assert0(ctx->pkt != NULL);
> +    av_packet_unref(ctx->pkt);
> +    if ((err = fill_pts_dts(ctx, st)) < 0) {
> +        av_buffer_unref(&buf);
> +        return err;
> +    }
> +    ctx->pkt->buf          = buf;
> +    ctx->pkt->data         = buf->data;
> +    ctx->pkt->size         = buf->size - AV_INPUT_BUFFER_PADDING_SIZE;
> +    ctx->pkt->stream_index = st->stream->index;
> +    ctx->pkt->flags        = st->flags;
> +    ctx->pkt->pos          = st->offset;
> +    ctx->pkt               = NULL;
> +
> +    st->flags  = 0;
> +    st->offset = -1;
> +    return 0;
> +}
> +
> +static int consume_mfu(MMTPContext *ctx, GetByteContext *gbc)
> +{
> +    int            err;
> +    AVBufferRef    *buf_ref;
> +    unsigned int   size;
> +    uint8_t        byte;
> +    size_t         old_size;
> +    struct Streams *st = find_current_stream(ctx);
> +    av_assert0(st != NULL);
> +
> +    switch (st->stream->codecpar->codec_id) {
> +    case AV_CODEC_ID_HEVC:
> +        size = bytestream2_get_be32(gbc);
> +        if (size != bytestream2_get_bytes_left(gbc)) return
> AVERROR_INVALIDDATA;
> +        if (size < 1) // we expect to extract NAL unit header type below
> +            return AVERROR_INVALIDDATA;
> +        byte = bytestream2_peek_byteu(gbc);
> +        if ((byte >> 7) != 0) return AVERROR_INVALIDDATA; //
> forbidden_zero_bit
> +
> +        old_size = st->pending_buffer == NULL ? 0 :
> +                   (st->pending_buffer->size -
> AV_INPUT_BUFFER_PADDING_SIZE);
> +        if ((err = av_buffer_realloc(
> +            &st->pending_buffer,
> +            old_size + size + 4 + AV_INPUT_BUFFER_PADDING_SIZE)) < 0)
> +            return err;
> +        // fix start code (00 00 00 01)
> +        AV_WB32(st->pending_buffer->data + old_size, 1);
> +        bytestream2_get_bufferu(
> +            gbc, st->pending_buffer->data + old_size + 4, size);
> +        if (((byte >> 1) & 0b111111) < 0x20) { // a VCL NAL unit
> +            // Because we can't emit a packet without a valid PTS, we
> need to
> +            // aggregate the non-VCL NAL units with VCL ones. Although we
> didn't
> +            // technically identify an access unit here, this works for
> all samples
> +            // we have.
> +            buf_ref = st->pending_buffer;
> +            st->pending_buffer = NULL;
> +
> +            memset(buf_ref->data + old_size + size + 4, 0,
> +                   AV_INPUT_BUFFER_PADDING_SIZE);
> +            return emit_packet(ctx, st, buf_ref);
> +        }
> +        return 0;
> +    case AV_CODEC_ID_AAC_LATM:
> +        size = bytestream2_get_bytes_left(gbc);
> +        if (size >> 13) return AVERROR(EOVERFLOW);
> +        if ((buf_ref = av_buffer_alloc(
> +            size + 3 + AV_INPUT_BUFFER_PADDING_SIZE)) == NULL)
> +            return AVERROR(ENOMEM);
> +        buf_ref->data[0] = 0x56;
> +        buf_ref->data[1] = 0xe0 | (size >> 8);
> +        buf_ref->data[2] = size & 0xff;
> +        bytestream2_get_bufferu(gbc, buf_ref->data + 3, size);
> +        memset(buf_ref->data + 3 + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
> +        return emit_packet(ctx, st, buf_ref);
> +    case AV_CODEC_ID_TTML:
> +        return emit_closed_caption_mfu(ctx, st, gbc);
> +    default:
> +        return AVERROR_PATCHWELCOME;
> +    }
> +}
> +
> +static int parse_mfu_timed_data(
> +    MMTPContext *ctx, struct FragmentAssembler *assembler,
> +    uint32_t seq_num, enum FragmentationIndicator indicator,
> +    GetByteContext *gbc)
> +{
> +    bytestream2_skip(gbc, (32 + 32 + 32 + 8 + 8) / 8);
> +    return assemble_fragment(
> +        assembler, seq_num, indicator,
> +        gbc->buffer, bytestream2_get_bytes_left(gbc),
> +        consume_mfu, ctx);
> +}
> +
> +static int parse_mfu_non_timed_data(
> +    MMTPContext *ctx, struct FragmentAssembler *assembler,
> +    uint32_t seq_num, enum FragmentationIndicator indicator,
> +    GetByteContext *gbc)
> +{
> +    bytestream2_skip(gbc, 32 / 8);
> +    return assemble_fragment(
> +        assembler, seq_num, indicator,
> +        gbc->buffer, bytestream2_get_bytes_left(gbc),
> +        consume_mfu, ctx);
> +}
> +
> +static int parse_mpu(MMTPContext *ctx, uint32_t seq_num, GetByteContext
> *gbc)
> +{
> +    int                         err;
> +    uint8_t                     byte, fragment_type;
> +    bool                        timed_flag;
> +    enum FragmentationIndicator fragmentation_indicator;
> +    bool                        aggregation_flag;
> +    uint16_t                    length;
> +    uint32_t                    mpu_sequence_number;
> +    struct FragmentAssembler    *assembler;
> +    struct Streams              *streams;
> +
> +    streams = find_current_stream(ctx);
> +    if (streams == NULL || streams->stream->discard >= AVDISCARD_ALL)
> +        return 0;
> +
> +    assembler = find_or_allocate_assembler(ctx, ctx->current_pid);
> +    if (assembler == NULL) return AVERROR(errno);
> +
> +    if (bytestream2_get_bytes_left(gbc) < (16 + 4 + 1 + 2 + 1 + 8 + 32) /
> 8)
> +        return AVERROR_INVALIDDATA;
> +
> +    length = bytestream2_get_be16u(gbc);
> +    if (length != bytestream2_get_bytes_left(gbc))
> +        return AVERROR_INVALIDDATA;
> +
> +    byte                    = bytestream2_get_byteu(gbc);
> +    fragment_type           = byte >> 4;
> +    timed_flag              = (byte >> 3) & 1;
> +    fragmentation_indicator = (byte >> 1) & 0b11;
> +    aggregation_flag        = byte & 1;
> +
> +    // skip: fragment_counter
> +    bytestream2_skipu(gbc, 1);
> +
> +    mpu_sequence_number = bytestream2_get_be32u(gbc);
> +
> +    if (aggregation_flag && fragmentation_indicator != NOT_FRAGMENTED)
> +        return AVERROR_INVALIDDATA; // cannot be both fragmented and
> aggregated
> +
> +    if (fragment_type != 2)
> +        return 0; // not MFU
> +
> +    if (assembler->state == INIT && !ctx->is_rap)
> +        return 0; // wait for the first RAP
> +
> +    if (assembler->state == INIT) {
> +        streams->last_sequence_number = mpu_sequence_number;
> +    } else if (mpu_sequence_number == streams->last_sequence_number + 1) {
> +        streams->last_sequence_number = mpu_sequence_number;
> +        streams->au_count             = 0;
> +    } else if (mpu_sequence_number != streams->last_sequence_number) {
> +        av_log(streams->stream, AV_LOG_ERROR,
> +               "MPU sequence number jump: %u + 1 != %u\n",
> +               streams->last_sequence_number, mpu_sequence_number);
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    if ((err = check_state(ctx, assembler, seq_num)) < 0)
> +        return err;
> +
> +    if (fragmentation_indicator == NOT_FRAGMENTED ||
> +        fragmentation_indicator == FIRST_FRAGMENT)
> +        streams->offset = ctx->pkt->pos;
> +
> +    if (ctx->is_rap)
> +        streams->flags |= AV_PKT_FLAG_KEY;
> +
> +    if (timed_flag) {
> +        if (aggregation_flag) {
> +            while (bytestream2_get_bytes_left(gbc) > 0) {
> +                length = bytestream2_get_be16(gbc);
> +                if (bytestream2_get_bytes_left(gbc) < length)
> +                    return AVERROR_INVALIDDATA;
> +                {
> +                    GetByteContext ngbc;
> +                    bytestream2_init(&ngbc, gbc->buffer, length);
> +
> +                    err = parse_mfu_timed_data(
> +                        ctx, assembler, seq_num, NOT_FRAGMENTED, &ngbc);
> +                    if (err < 0) return err;
> +                }
> +                bytestream2_skipu(gbc, length);
> +            }
> +        } else {
> +            return parse_mfu_timed_data(
> +                ctx, assembler, seq_num, fragmentation_indicator, gbc);
> +        }
> +    } else {
> +        if (aggregation_flag) {
> +            while (bytestream2_get_bytes_left(gbc) > 0) {
> +                length = bytestream2_get_be16(gbc);
> +                if (bytestream2_get_bytes_left(gbc) < length)
> +                    return AVERROR_INVALIDDATA;
> +                {
> +                    GetByteContext ngbc;
> +                    bytestream2_init(&ngbc, gbc->buffer, length);
> +
> +                    err = parse_mfu_non_timed_data(
> +                        ctx, assembler, seq_num, NOT_FRAGMENTED, &ngbc);
> +                    if (err < 0) return err;
> +                }
> +                bytestream2_skipu(gbc, length);
> +            }
> +        } else {
> +            return parse_mfu_non_timed_data(
> +                ctx, assembler, seq_num, fragmentation_indicator, gbc);
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +MMTPContext *ff_mmtp_parse_open(AVProgram *program)
> +{
> +    MMTPContext *ctx = av_mallocz(sizeof(MMTPContext));
> +    if (ctx == NULL) return NULL;
> +    ctx->program = program;
> +    return ctx;
> +}
> +
> +int ff_mmtp_parse_packet(MMTPContext *ctx, AVFormatContext *s, AVPacket
> *pkt,
> +                         const uint8_t *buf, uint16_t size)
> +{
> +    bool     packet_counter_flag;
> +    bool     extension_header_flag;
> +    uint8_t  payload_type;
> +    uint32_t packet_sequence_number;
> +    uint8_t  byte;
> +    int      err = 0;
> +
> +    GetByteContext gbc;
> +
> +    ctx->s   = s;
> +    ctx->pkt = pkt;
> +
> +    bytestream2_init(&gbc, buf, size);
> +    if (bytestream2_get_bytes_left(&gbc) <
> +        (2 + 1 + 2 + 1 + 1 + 1 + 2 + 6 + 16 + 32 + 32) / 8)
> +        return AVERROR_INVALIDDATA;
> +
> +    byte                  = bytestream2_get_byteu(&gbc);
> +    packet_counter_flag   = (byte >> 5) & 1;
> +    extension_header_flag = (byte >> 1) & 1;
> +    ctx->is_rap = byte & 1;
> +
> +    byte         = bytestream2_get_byteu(&gbc);
> +    payload_type = byte & 0b111111;
> +
> +    ctx->current_pid = bytestream2_get_be16u(&gbc);
> +
> +    // skip: distribute_timestamp
> +    bytestream2_skipu(&gbc, 4);
> +
> +    packet_sequence_number = bytestream2_get_be32u(&gbc);
> +
> +    if (packet_counter_flag)
> +        bytestream2_skip(&gbc, 4);
> +
> +    if (extension_header_flag) {
> +        uint16_t extension_header_length;
> +        // skip: extension_type
> +        bytestream2_skip(&gbc, 2);
> +        extension_header_length = bytestream2_get_be16(&gbc);
> +        bytestream2_skip(&gbc, extension_header_length);
> +    }
> +
> +    switch (payload_type) {
> +    case 0x00: // MPU
> +        if (pkt != NULL)
> +            err = parse_mpu(ctx, packet_sequence_number, &gbc);
> +        break;
> +    case 0x02: // signalling messages
> +        err = parse_signalling_messages(ctx, packet_sequence_number,
> &gbc);
> +        break;
> +    }
> +    if (err < 0) return err;
> +    return ctx->pkt == NULL ? 0 : FFERROR_REDO;
> +}
> +
> +void ff_mmtp_reset_state(MMTPContext *ctx)
> +{
> +    struct Streams           *streams;
> +    struct FragmentAssembler *assembler;
> +
> +    for (assembler = ctx->assembler;
> +         assembler != NULL; assembler = assembler->next) {
> +        assembler->state = INIT;
> +        assembler->size  = 0;
> +    }
> +    for (streams = ctx->streams; streams != NULL; streams =
> streams->next) {
> +        streams->last_sequence_number = 0;
> +        streams->au_count             = 0;
> +        streams->flags                = 0;
> +        streams->offset               = -1;
> +        av_buffer_unref(&streams->pending_buffer);
> +    }
> +}
> +
> +void ff_mmtp_parse_close(MMTPContext *ctx)
> +{
> +    struct FragmentAssembler *ass;
> +    struct Streams           *streams;
> +
> +    for (ass = ctx->assembler; ass != NULL;) {
> +        struct FragmentAssembler *next = ass->next;
> +        av_free(ass->data);
> +        av_free(ass);
> +        ass = next;
> +    }
> +
> +    for (streams = ctx->streams; streams != NULL;) {
> +        struct Streams *next = streams->next;
> +        av_free(streams->timestamp_descriptor);
> +        av_free(streams->ext_timestamp_descriptor);
> +        av_buffer_unref(&streams->pending_buffer);
> +        av_free(streams);
> +        streams = next;
> +    }
> +
> +    av_free(ctx);
> +}
> diff --git a/libavformat/mmtp.h b/libavformat/mmtp.h
> new file mode 100644
> index 0000000000..300a6a1aea
> --- /dev/null
> +++ b/libavformat/mmtp.h
> @@ -0,0 +1,64 @@
> +/*
> + * MPEG Media Transport Protocol (MMTP) parser, as defined in ISO/IEC
> 23008-1.
> + * Copyright (c) 2023 SuperFashi
> + *
> + * 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
> + */
> +#ifndef AVFORMAT_MMTP_H
> +#define AVFORMAT_MMTP_H
> +
> +#include "avformat.h"
> +
> +typedef struct MMTPContext MMTPContext;
> +
> +/**
> + * Open an MMT protocol parser context.
> + * @param program The AVProgram this context is associated with.
> + * @return A new MMTPContext, or NULL on allocation error.
> + */
> +MMTPContext *ff_mmtp_parse_open(AVProgram *program);
> +
> +/**
> + * Parse an MMT protocol packet.
> + *
> + * @param ctx The MMT protocol parser context.
> + * @param s The AVFormatContext.
> + * @param pkt The AVPacket to fill.
> + * @param buf The packet data.
> + * @param size The size of the packet data.
> + * @return >= 0 if a new AVPacket is emitted,
> + *         FFERROR_REDO if the next packet is needed,
> + *         or another negative value on error.
> + */
> +int ff_mmtp_parse_packet(MMTPContext *ctx, AVFormatContext *s, AVPacket
> *pkt,
> +                         const uint8_t *buf, uint16_t size);
> +
> +/**
> + * Reset the state of the MMTP parser. Useful when seeking.
> + *
> + * @param ctx The MMT protocol parser context.
> + */
> +void ff_mmtp_reset_state(MMTPContext *ctx);
> +
> +/**
> + * Close an MMT protocol parser context, frees all associated resources.
> + *
> + * @param ctx The MMT protocol parser context.
> + */
> +void ff_mmtp_parse_close(MMTPContext *ctx);
> +
> +#endif /* AVFORMAT_MMTP_H */
> diff --git a/libavformat/mmttlv.c b/libavformat/mmttlv.c
> new file mode 100644
> index 0000000000..622840e4a1
> --- /dev/null
> +++ b/libavformat/mmttlv.c
> @@ -0,0 +1,335 @@
> +/*
> + * MMT protocol over TLV packets (MMT/TLV) demuxer, as defined in ARIB
> STD-B32.
> + * Copyright (c) 2023 SuperFashi
> + *
> + * 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
> + */
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/internal.h"
> +#include "libavutil/intreadwrite.h"
> +#include "avformat.h"
> +#include "avio_internal.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "mmtp.h"
> +
> +#define HEADER_BYTE 0b01111111
> +
> +enum {
> +    UNDEFINED_PACKET            = 0x00,
> +    IPV4_PACKET                 = 0x01,
> +    IPV6_PACKET                 = 0x02,
> +    HEADER_COMPRESSED_IP_PACKET = 0x03,
> +    TRANSMISSION_CONTROL_PACKET = 0xFE,
> +    NULL_PACKET                 = 0xFF,
> +};
> +
> +enum {
> +    CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER = 0x20,
> +    CONTEXT_IDENTIFICATION_IPV4_HEADER                         = 0x21,
> +    CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER = 0x60,
> +    CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER                = 0x61,
> +};
> +
> +static int mmttlv_probe(const AVProbeData *p)
> +{
> +    size_t   i, j;
> +    uint8_t  packet_type;
> +    uint16_t data_length;
> +
> +    int processed  = 0;
> +    int recognized = 0;
> +
> +    for (i = 0; i + 4 < p->buf_size && processed < 100; ++processed) {
> +        if (p->buf[i] != HEADER_BYTE) return 0;
> +
> +        packet_type = p->buf[i + 1];
> +        data_length = AV_RB16(p->buf + i + 2);
> +        i += 4;
> +
> +        if (packet_type == HEADER_COMPRESSED_IP_PACKET) {
> +            if (data_length < 3 || i + 2 >= p->buf_size) goto skip;
> +            switch (p->buf[i + 2]) {
> +            case
> CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER:
> +            case CONTEXT_IDENTIFICATION_IPV4_HEADER:
> +            case
> CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER:
> +            case CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER:
> +                ++recognized;
> +            }
> +        } else if (packet_type == NULL_PACKET) {
> +            // null packets should contain all 0xFFs
> +            for (j = i; j < i + data_length && j < p->buf_size; ++j) {
> +                if (p->buf[j] != 0xFF) goto skip;
> +            }
> +            ++recognized;
> +        }
> +
> +        skip:
> +        i += data_length;
> +    }
> +
> +    return recognized * AVPROBE_SCORE_MAX / FFMAX(processed, 10);
> +}
> +
> +struct MMTTLVContext {
> +    struct Program {
> +        uint32_t       cid;
> +        MMTPContext    *mmtp;
> +        struct Program *next;
> +    } *programs;
> +
> +    int64_t last_pos;
> +    size_t  resync_size;
> +
> +    size_t  cap;
> +    uint8_t *buf;
> +};
> +
> +static int mmttlv_read_compressed_ip_packet(
> +    struct MMTTLVContext *ctx, AVFormatContext *s, AVPacket *pkt,
> +    const uint8_t *buf, uint16_t size)
> +{
> +    // partial udp header are udp header without data length (16 bits)
> and checksum (16 bits)
> +#define PARTIAL_UDP_HEADER_LENGTH (8 - 4)
> +    // partial ipv6 header are ipv6 header without payload length (16
> bits)
> +#define PARTIAL_IPV6_HEADER_LENGTH (40 - 2)
> +
> +    uint32_t       context_id;
> +    struct Program *program;
> +
> +    if (size < 3)
> +        return AVERROR_INVALIDDATA;
> +    context_id = AV_RB16(buf) >> 4;
> +    buf += 3;
> +    size -= 3;
> +
> +    for (program = ctx->programs; program != NULL; program =
> program->next)
> +        if (program->cid == context_id)
> +            break;
> +
> +    if (program == NULL) {
> +        AVProgram *p = av_new_program(s, context_id);
> +        if (p == NULL) return AVERROR(errno);
> +
> +        program = av_malloc(sizeof(struct Program));
> +        if (program == NULL) return AVERROR(errno);
> +
> +        program->mmtp = ff_mmtp_parse_open(p);
> +        program->next = ctx->programs;
> +        ctx->programs = program;
> +        program->cid  = context_id;
> +    }
> +
> +    switch (buf[-1]) {
> +    case CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER:
> +    case CONTEXT_IDENTIFICATION_IPV4_HEADER:
> +        return AVERROR_PATCHWELCOME;
> +    case CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER:
> +        if (size < PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH)
> +            return AVERROR_INVALIDDATA;
> +        size -= PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH;
> +        buf += PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH;
> +    case CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER:
> +        break;
> +    default:
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    return ff_mmtp_parse_packet(program->mmtp, s, pkt, buf, size);
> +}
> +
> +static int mmttlv_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    uint8_t              header[4];
> +    uint16_t             size;
> +    int                  err;
> +    struct MMTTLVContext *ctx = s->priv_data;
> +    int64_t              pos  = avio_tell(s->pb);
> +
> +    if (pos < 0) return (int) pos;
> +    if (pos != ctx->last_pos) {
> +        ctx->last_pos = pos;
> +
> +        while (pos - ctx->last_pos < ctx->resync_size) {
> +            if ((err = ffio_ensure_seekback(s->pb, 4)) < 0)
> +                return err;
> +
> +            if ((err = avio_read(s->pb, header, 4)) < 0)
> +                return avio_feof(s->pb) ? AVERROR_EOF : err;
> +
> +            if (header[0] != HEADER_BYTE) {
> +                if ((pos = avio_seek(s->pb, -3, SEEK_CUR)) < 0)
> +                    return (int) pos;
> +                continue;
> +            }
> +
> +            size = AV_RB16(header + 2);
> +
> +            if ((pos = avio_seek(s->pb, -4, SEEK_CUR)) < 0)
> +                return (int) pos;
> +
> +            if ((err = ffio_ensure_seekback(s->pb, 4 + size + 1)) < 0)
> +                return err;
> +
> +            if ((pos = avio_skip(s->pb, 4 + size)) < 0)
> +                return (int) pos;
> +
> +            if ((err = avio_read(s->pb, header, 1)) < 0)
> +                return avio_feof(s->pb) ? AVERROR_EOF : err;
> +
> +            if (header[0] == HEADER_BYTE) {
> +                // found HEADER, [size], HEADER, should be good
> +                if ((pos = avio_seek(s->pb, -size - 1 - 4, SEEK_CUR)) < 0)
> +                    return (int) pos;
> +                goto success;
> +            }
> +
> +            if ((pos = avio_seek(s->pb, -size - 1 - 3, SEEK_CUR)) < 0)
> +                return (int) pos;
> +        }
> +        return AVERROR_INVALIDDATA;
> +
> +        success:
> +        ctx->last_pos = pos;
> +
> +        for (struct Program *program = ctx->programs;
> +             program != NULL; program = program->next)
> +            ff_mmtp_reset_state(program->mmtp);
> +    }
> +
> +    if (pkt != NULL) pkt->pos = ctx->last_pos;
> +    if ((err = ffio_read_size(s->pb, header, 4)) < 0)
> +        return avio_feof(s->pb) ? AVERROR_EOF : err;
> +    ctx->last_pos += 4;
> +
> +    if (header[0] != HEADER_BYTE)
> +        return AVERROR_INVALIDDATA;
> +
> +    size = AV_RB16(header + 2);
> +    if (header[1] != HEADER_COMPRESSED_IP_PACKET) {
> +        if ((ctx->last_pos = avio_skip(s->pb, size)) < 0)
> +            return (int) ctx->last_pos;
> +        return pkt == NULL ? 0 : FFERROR_REDO;
> +    }
> +
> +    if (ctx->cap < size) {
> +        av_free(ctx->buf);
> +        if ((ctx->buf = av_malloc(ctx->cap = size)) == NULL)
> +            return AVERROR(errno);
> +    }
> +    if ((err = ffio_read_size(s->pb, ctx->buf, size)) < 0)
> +        return avio_feof(s->pb) ? AVERROR_EOF : err;
> +    ctx->last_pos += size;
> +    return mmttlv_read_compressed_ip_packet(ctx, s, pkt, ctx->buf, size);
> +}
> +
> +static int mmttlv_read_header(AVFormatContext *s)
> +{
> +    int64_t              pos;
> +    int64_t              allow = s->probesize;
> +    struct MMTTLVContext *ctx  = s->priv_data;
> +
> +    ctx->last_pos = avio_tell(s->pb);
> +    if (ctx->last_pos < 0)
> +        return (int) ctx->last_pos;
> +    ctx->last_pos -= 1; // force resync
> +
> +    ctx->resync_size = 4096;
> +    s->ctx_flags |= AVFMTCTX_NOHEADER;
> +
> +    if (!s->pb->seekable)
> +        return 0;
> +
> +    if ((pos = avio_tell(s->pb)) < 0)
> +        return (int) pos;
> +
> +    while (s->nb_streams <= 0 && allow > 0) {
> +        const int64_t cur = ctx->last_pos;
> +        const int     err = mmttlv_read_packet(s, NULL);
> +        if (err < 0) return err;
> +        allow -= ctx->last_pos - cur;
> +    }
> +
> +    ctx->last_pos = avio_tell(s->pb);
> +    if (ctx->last_pos < 0)
> +        return (int) ctx->last_pos;
> +
> +    if ((pos = avio_seek(s->pb, pos, SEEK_SET)) < 0)
> +        return (int) pos;
> +
> +    return 0;
> +}
> +
> +static int mmttlv_read_close(AVFormatContext *ctx)
> +{
> +    struct Program       *program;
> +    struct MMTTLVContext *priv = ctx->priv_data;
> +    for (program = priv->programs; program != NULL;) {
> +        struct Program *next = program->next;
> +        ff_mmtp_parse_close(program->mmtp);
> +        av_free(program);
> +        program = next;
> +    }
> +    priv->programs = NULL;
> +    priv->cap = 0;
> +    av_freep(&priv->buf);
> +    return 0;
> +}
> +
> +static int64_t mmttlv_read_timestamp(
> +    struct AVFormatContext *s, int stream_index,
> +    int64_t *pos, int64_t pos_limit)
> +{
> +    struct MMTTLVContext *ctx = s->priv_data;
> +
> +    if ((*pos = avio_seek(s->pb, *pos, SEEK_SET)) < 0)
> +        return (int) *pos;
> +
> +    while (pos_limit > 0) {
> +        AVPacket      packet = {0};
> +        const int     err    = mmttlv_read_packet(s, &packet);
> +        const int64_t ts     = packet.dts;
> +        const int64_t off    = packet.pos;
> +        const int     sid    = packet.stream_index;
> +        av_packet_unref(&packet);
> +        if (err >= 0 && (stream_index < 0 || sid == stream_index)) {
> +            *pos = off;
> +            return ts;
> +        }
> +        pos_limit -= ctx->last_pos - *pos;
> +        *pos = ctx->last_pos;
> +        if (err < 0 && err != FFERROR_REDO)
> +            return AV_NOPTS_VALUE;
> +    }
> +
> +    return AV_NOPTS_VALUE;
> +}
> +
> +const AVInputFormat ff_mmttlv_demuxer = {
> +    .name           = "mmttlv",
> +    .long_name      = NULL_IF_CONFIG_SMALL(
> +        "MMT protocol over TLV packets (ARIB STD-B32)"),
> +    .priv_data_size = sizeof(struct MMTTLVContext),
> +    .flags_internal = FF_FMT_INIT_CLEANUP,
> +    .read_probe     = mmttlv_probe,
> +    .read_header    = mmttlv_read_header,
> +    .read_packet    = mmttlv_read_packet,
> +    .read_close     = mmttlv_read_close,
> +    .read_timestamp = mmttlv_read_timestamp,
> +    .flags          = AVFMT_SHOW_IDS,
> +};
> diff --git a/libavformat/version.h b/libavformat/version.h
> index e2634b85ae..4bde82abb4 100644
> --- a/libavformat/version.h
> +++ b/libavformat/version.h
> @@ -31,7 +31,7 @@
>
>  #include "version_major.h"
>
> -#define LIBAVFORMAT_VERSION_MINOR   5
> +#define LIBAVFORMAT_VERSION_MINOR   6
>  #define LIBAVFORMAT_VERSION_MICRO 100
>
>  #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR,
> \
> --
> 2.25.1
>
>
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index 4901ef6ad7..594c445ea2 100644
--- a/Changelog
+++ b/Changelog
@@ -7,6 +7,7 @@  version <next>:
 - Extend VAAPI support for libva-win32 on Windows
 - afireqsrc audio source filter
 - arls filter
+- MMTP parser and MMT/TLV demuxer
 
 version 6.0:
 - Radiance HDR image support
diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 2d33b47a56..56aab251b2 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -689,6 +689,10 @@  Set the sample rate for libopenmpt to output.
 Range is from 1000 to INT_MAX. The value default is 48000.
 @end table
 
+@section mmttlv
+
+Demuxer for MMT protocol over TLV packets (MMT/TLV), as defined in ARIB STD-B32.
+
 @section mov/mp4/3gp
 
 Demuxer for Quicktime File Format & ISO/IEC Base Media File Format (ISO/IEC 14496-12 or MPEG-4 Part 12, ISO/IEC 15444-12 or JPEG 2000 Part 12).
diff --git a/libavformat/Makefile b/libavformat/Makefile
index f8ad7c6a11..e32d6e71a3 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -354,6 +354,7 @@  OBJS-$(CONFIG_MLV_DEMUXER)               += mlvdec.o riffdec.o
 OBJS-$(CONFIG_MM_DEMUXER)                += mm.o
 OBJS-$(CONFIG_MMF_DEMUXER)               += mmf.o
 OBJS-$(CONFIG_MMF_MUXER)                 += mmf.o rawenc.o
+OBJS-$(CONFIG_MMTTLV_DEMUXER)            += mmtp.o mmttlv.o
 OBJS-$(CONFIG_MODS_DEMUXER)              += mods.o
 OBJS-$(CONFIG_MOFLEX_DEMUXER)            += moflex.o
 OBJS-$(CONFIG_MOV_DEMUXER)               += mov.o mov_chan.o mov_esds.o \
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index efdb34e29d..d5f4f5680e 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -270,6 +270,7 @@  extern const AVInputFormat  ff_mlv_demuxer;
 extern const AVInputFormat  ff_mm_demuxer;
 extern const AVInputFormat  ff_mmf_demuxer;
 extern const FFOutputFormat ff_mmf_muxer;
+extern const AVInputFormat  ff_mmttlv_demuxer;
 extern const AVInputFormat  ff_mods_demuxer;
 extern const AVInputFormat  ff_moflex_demuxer;
 extern const AVInputFormat  ff_mov_demuxer;
diff --git a/libavformat/mmtp.c b/libavformat/mmtp.c
new file mode 100644
index 0000000000..6f002cc827
--- /dev/null
+++ b/libavformat/mmtp.c
@@ -0,0 +1,1525 @@ 
+/*
+ * MPEG Media Transport Protocol (MMTP) parser, as defined in ISO/IEC 23008-1.
+ * Copyright (c) 2023 SuperFashi
+ *
+ * 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
+ */
+
+#include <stdbool.h>
+
+#include "libavcodec/bytestream.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "demux.h"
+#include "internal.h"
+#include "mmtp.h"
+#include "network.h"
+
+struct MMTGeneralLocationInfo {
+    uint8_t location_type;
+    union {
+        struct {
+            uint16_t packet_id;
+        } type0;
+        struct {
+            struct in_addr ipv4_src_addr;
+            struct in_addr ipv4_dst_addr;
+            uint16_t       dst_port;
+            uint16_t       packet_id;
+        } type1;
+        struct {
+            struct in6_addr ipv6_src_addr;
+            struct in6_addr ipv6_dst_addr;
+            uint16_t        dst_port;
+            uint16_t        packet_id;
+        } type2;
+        struct {
+            uint16_t network_id;
+            uint16_t MPEG_2_transport_stream_id;
+            uint16_t MPEG_2_PID: 13;
+        } type3;
+        struct {
+            struct in6_addr ipv6_src_addr;
+            struct in6_addr ipv6_dst_addr;
+            uint16_t        dst_port;
+            uint16_t        MPEG_2_PID: 13;
+        } type4;
+        struct {
+            char URL[0x100 + 1];
+        } type5;
+    };
+};
+
+static int parse_mmt_general_location_info(
+    struct MMTGeneralLocationInfo *info, GetByteContext *gbc)
+{
+    uint8_t url_size;
+
+    if (bytestream2_get_bytes_left(gbc) < 1)
+        return AVERROR_INVALIDDATA;
+    switch (info->location_type = bytestream2_get_byteu(gbc)) {
+    case 0x00:
+        if (bytestream2_get_bytes_left(gbc) < 2)
+            return AVERROR_INVALIDDATA;
+        info->type0.packet_id = bytestream2_get_be16u(gbc);
+        break;
+    case 0x01:
+        if (bytestream2_get_bytes_left(gbc) < (32 + 32 + 16 + 16) / 8)
+            return AVERROR_INVALIDDATA;
+        bytestream2_get_bufferu(gbc, (uint8_t *) &info->type1.ipv4_src_addr, 4);
+        bytestream2_get_bufferu(gbc, (uint8_t *) &info->type1.ipv4_dst_addr, 4);
+        info->type1.dst_port  = bytestream2_get_be16u(gbc);
+        info->type1.packet_id = bytestream2_get_be16u(gbc);
+        break;
+    case 0x02:
+        if (bytestream2_get_bytes_left(gbc) < (128 + 128 + 16 + 16) / 8)
+            return AVERROR_INVALIDDATA;
+        bytestream2_get_bufferu(
+            gbc, (uint8_t *) &info->type2.ipv6_src_addr, 16);
+        bytestream2_get_bufferu(
+            gbc, (uint8_t *) &info->type2.ipv6_dst_addr, 16);
+        info->type2.dst_port  = bytestream2_get_be16u(gbc);
+        info->type2.packet_id = bytestream2_get_be16u(gbc);
+        break;
+    case 0x03:
+        if (bytestream2_get_bytes_left(gbc) < (16 + 16 + 3 + 13) / 8)
+            return AVERROR_INVALIDDATA;
+        info->type3.network_id                 = bytestream2_get_be16u(gbc);
+        info->type3.MPEG_2_transport_stream_id = bytestream2_get_be16u(gbc);
+        info->type3.MPEG_2_PID =
+            bytestream2_get_be16u(gbc) & 0b1111111111111;
+        break;
+    case 0x04:
+        if (bytestream2_get_bytes_left(gbc) < (128 + 128 + 16 + 3 + 13) / 8)
+            return AVERROR_INVALIDDATA;
+        bytestream2_get_bufferu(
+            gbc, (uint8_t *) &info->type4.ipv6_src_addr, 16);
+        bytestream2_get_bufferu(
+            gbc, (uint8_t *) &info->type4.ipv6_dst_addr, 16);
+        info->type4.dst_port   = bytestream2_get_be16u(gbc);
+        info->type4.MPEG_2_PID = bytestream2_get_be16u(gbc) & 0b1111111111111;
+        break;
+    case 0x05:
+        url_size = bytestream2_get_byte(gbc);
+        bytestream2_get_buffer(gbc, (uint8_t *) info->type5.URL, url_size);
+        info->type5.URL[url_size] = '\0';
+        break;
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+    return 0;
+}
+
+struct Streams {
+    AVStream *stream;
+
+    int num_timestamp_descriptors;
+    struct MPUTimestampDescriptor {
+        uint32_t seq_num;
+        int64_t  presentation_time;
+    } *timestamp_descriptor;
+
+    int num_ext_timestamp_descriptors;
+    struct MPUExtendedTimestampDescriptor {
+        uint32_t seq_num;
+        uint16_t decoding_time_offset;
+        uint8_t  num_of_au;
+        struct {
+            uint16_t dts_pts_offset;
+            uint16_t pts_offset;
+        } au[0x100];
+    } *ext_timestamp_descriptor;
+
+    uint32_t    last_sequence_number;
+    uint16_t    au_count;
+    AVBufferRef *pending_buffer;
+    int64_t     offset;
+    int         flags;
+
+    struct Streams *next;
+};
+
+struct MMTPContext {
+    struct FragmentAssembler *assembler;
+    struct Streams           *streams;
+    AVProgram                *program;
+    // struct MMTGeneralLocationInfo mpt_location; TODO
+
+    // below are temporary fields available for the scope of a single packet
+    AVFormatContext *s;
+    AVPacket        *pkt;
+    uint16_t        current_pid;
+    bool            is_rap;
+};
+
+static struct Streams *find_current_stream(struct MMTPContext *ctx)
+{
+    struct Streams *streams;
+    for (streams = ctx->streams; streams != NULL; streams = streams->next)
+        if (streams->stream->id == ctx->current_pid)
+            return streams;
+    return NULL;
+}
+
+static struct Streams *
+find_or_allocate_stream(struct MMTPContext *ctx, uint16_t pid)
+{
+    AVStream       *stream;
+    struct Streams *streams;
+    for (streams = ctx->streams; streams != NULL; streams = streams->next)
+        if (streams->stream->id == pid) {
+            ffstream(streams->stream)->need_context_update = 1;
+            return streams;
+        }
+
+    stream = avformat_new_stream(ctx->s, NULL);
+    if (stream == NULL) return NULL;
+    stream->id = pid;
+    av_program_add_stream_index(ctx->s, ctx->program->id, stream->index);
+
+    streams = av_mallocz(sizeof(struct Streams));
+    if (streams == NULL) return NULL;
+    streams->stream = stream;
+    streams->next   = ctx->streams;
+    streams->offset = -1;
+    ctx->streams    = streams;
+    return streams;
+}
+
+enum {
+    MMT_PACKAGE_TABLE_ID  = 0x20,
+    PACKAGE_LIST_TABLE_ID = 0x80,
+};
+
+enum {
+    MPU_TIMESTAMP_DESCRIPTOR          = 0x0001,
+    ACCESS_CONTROL_DESCRIPTOR         = 0x8004,
+    VIDEO_COMPONENT_DESCRIPTOR        = 0x8010,
+    MH_STREAM_IDENTIFIER_DESCRIPTOR   = 0x8011,
+    MH_AUDIO_COMPONENT_DESCRIPTOR     = 0x8014,
+    MH_DATA_COMPONENT_DESCRIPTOR      = 0x8020,
+    MPU_EXTENDED_TIMESTAMP_DESCRIPTOR = 0x8026,
+};
+
+static int
+parse_video_component_descriptor(AVStream *stream, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+    uint8_t language_code[4];
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != VIDEO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+        /*
+         * skip:
+         * - video_resolution
+         * - video_aspect_ratio
+         * - video_scan_flag
+         * - reserved
+         * - video_frame_rate
+         * - component_tag
+         * - video_transfer_characteristics
+         * - reserved
+         */
+        bytestream2_skip(&ngbc, (4 + 4 + 1 + 2 + 5 + 16 + 4 + 4) / 8);
+
+        if (bytestream2_get_bytes_left(&ngbc) < 3)
+            return AVERROR_INVALIDDATA;
+        bytestream2_get_bufferu(&ngbc, language_code, 3);
+        language_code[3] = '\0';
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return av_dict_set(&stream->metadata, "language", language_code, 0);
+}
+
+static int
+parse_mh_audio_component_descriptor(AVStream *stream, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+    uint8_t stream_content;
+    uint8_t stream_type;
+    uint8_t language_code[4];
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != MH_AUDIO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        uint8_t byte;
+        bool ES_multi_lingual_flag;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+
+        if (bytestream2_get_bytes_left(&ngbc) <
+            (4 + 4 + 8 + 16 + 8 + 8 + 1 + 1 + 2 + 3 + 1 + 24) / 8)
+            return AVERROR_INVALIDDATA;
+
+        byte           = bytestream2_get_byteu(&ngbc);
+        stream_content = byte & 0b1111;
+
+        /*
+         * skip:
+         * - component_type
+         * - component_tag
+         */
+        bytestream2_skipu(&ngbc, 3);
+        stream_type = bytestream2_get_byteu(&ngbc);
+
+        // skip: simulcast_group_tag
+        bytestream2_skipu(&ngbc, 1);
+
+        byte                  = bytestream2_get_byteu(&ngbc);
+        ES_multi_lingual_flag = byte >> 7;
+
+        bytestream2_get_bufferu(&ngbc, language_code, 3);
+        language_code[3] = '\0';
+
+        if (ES_multi_lingual_flag) {
+            if (bytestream2_get_bytes_left(&ngbc) < 3)
+                return AVERROR_INVALIDDATA;
+            bytestream2_skipu(&ngbc, 3);
+        }
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    switch (stream_content) {
+    case 0x3:
+        switch (stream_type) {
+        case 0x11:
+            stream->codecpar->codec_id = AV_CODEC_ID_AAC_LATM;
+            break;
+        case 0x1c:
+            stream->codecpar->codec_id = AV_CODEC_ID_AAC;
+            break;
+        }
+        break;
+    case 0x4:
+        stream->codecpar->codec_id = AV_CODEC_ID_MP4ALS;
+        break;
+    }
+
+    return av_dict_set(&stream->metadata, "language", language_code, 0);
+}
+
+#define MAX_NUM_TIMESTAMP_DESCRIPTOR 32
+#define DIFF(a, b) ((a) > (b) ? ((a) - (b)) : ((b) - (a)))
+
+static int
+parse_mpu_timestamp_descriptor(struct Streams *streams, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+
+    if (bytestream2_get_be16u(gbc) != MPU_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+
+        while (bytestream2_get_bytes_left(&ngbc) > 0) {
+            uint64_t mpu_seq_num;
+            int64_t  mpu_presentation_time;
+            size_t   i;
+
+            struct MPUTimestampDescriptor *desc;
+
+            if (bytestream2_get_bytes_left(&ngbc) < (32 + 64) / 8)
+                return AVERROR_INVALIDDATA;
+            mpu_seq_num = bytestream2_get_be32u(&ngbc);
+            mpu_presentation_time =
+                ff_parse_ntp_time(bytestream2_get_be64u(&ngbc)) - NTP_OFFSET_US;
+
+            if (mpu_seq_num >= streams->last_sequence_number) {
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].seq_num ==
+                        mpu_seq_num) {
+                        desc = streams->timestamp_descriptor + i;
+                        goto end2;
+                    }
+
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].seq_num <
+                        streams->last_sequence_number) {
+                        desc = streams->timestamp_descriptor + i;
+                        goto end1;
+                    }
+
+                if (streams->num_timestamp_descriptors + 1 >
+                    MAX_NUM_TIMESTAMP_DESCRIPTOR) {
+                    // we have all descriptors larger than the current sequence number
+                    // we can't add more, so we should evict the one with the largest distance
+                    uint64_t max_dist = 0;
+                    for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                        if (DIFF(
+                                streams->timestamp_descriptor[i].seq_num,
+                                mpu_seq_num) > max_dist) {
+                            desc     = streams->timestamp_descriptor + i;
+                            max_dist = DIFF(
+                                streams->timestamp_descriptor[i].seq_num,
+                                mpu_seq_num);
+                        }
+                    av_assert1(desc != NULL); // should never fail
+                    goto end1;
+                }
+
+                desc = av_dynarray2_add(
+                    (void **) &streams->timestamp_descriptor,
+                    &streams->num_timestamp_descriptors,
+                    sizeof(struct MPUTimestampDescriptor), NULL);
+                if (desc == NULL) return AVERROR(ENOMEM);
+
+                end1:
+                desc->seq_num           = mpu_seq_num;
+                end2:
+                desc->presentation_time = mpu_presentation_time;
+            }
+        }
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static int parse_mpu_extended_timestamp_descriptor(
+    struct Streams *streams, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+
+    AVStream *stream = streams->stream;
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != MPU_EXTENDED_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        uint8_t  byte;
+        uint8_t  pts_offset_type;
+        bool timescale_flag;
+        uint16_t default_pts_offset = 0;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+
+        if (bytestream2_get_bytes_left(&ngbc) < (5 + 2 + 1) / 8)
+            return AVERROR_INVALIDDATA;
+        byte            = bytestream2_get_byte(&ngbc);
+        pts_offset_type = (byte >> 1) & 0b11;
+        timescale_flag  = byte & 1;
+
+        if (timescale_flag) {
+            if (bytestream2_get_bytes_left(&ngbc) < 4)
+                return AVERROR_INVALIDDATA;
+            stream->time_base.num = 1;
+            stream->time_base.den = bytestream2_get_be32u(&ngbc);
+        }
+
+        if (pts_offset_type == 1) {
+            if (bytestream2_get_bytes_left(&ngbc) < 2)
+                return AVERROR_INVALIDDATA;
+            default_pts_offset = bytestream2_get_be16u(&ngbc);
+        }
+
+        while (bytestream2_get_bytes_left(&ngbc) > 0) {
+            size_t   i;
+            uint8_t  num_of_au;
+            uint16_t decoding_time_offset;
+            uint64_t mpu_seq_num;
+
+            struct MPUExtendedTimestampDescriptor *desc = NULL;
+
+            if (pts_offset_type == 0)
+                return AVERROR_PATCHWELCOME;  // we don't know how to handle this
+
+            if (bytestream2_get_bytes_left(&ngbc) < (32 + 2 + 6 + 16 + 8) / 8)
+                return AVERROR_INVALIDDATA;
+            mpu_seq_num = bytestream2_get_be32u(&ngbc);
+            // skip: leap_indicator
+            bytestream2_skip(&ngbc, (2 + 6) / 8);
+            decoding_time_offset = bytestream2_get_be16u(&ngbc);
+            num_of_au            = bytestream2_get_byteu(&ngbc);
+
+            if (mpu_seq_num >= streams->last_sequence_number) {
+                for (i = 0; i < streams->num_ext_timestamp_descriptors; ++i)
+                    if (streams->ext_timestamp_descriptor[i].seq_num ==
+                        mpu_seq_num) {
+                        desc = streams->ext_timestamp_descriptor + i;
+                        goto end2;
+                    }
+
+                for (i = 0; i < streams->num_ext_timestamp_descriptors; ++i)
+                    if (streams->ext_timestamp_descriptor[i].seq_num <
+                        streams->last_sequence_number) {
+                        desc = streams->ext_timestamp_descriptor + i;
+                        goto end1;
+                    }
+
+                if (streams->num_ext_timestamp_descriptors + 1 >
+                    MAX_NUM_TIMESTAMP_DESCRIPTOR) {
+                    uint64_t max_diff = 0;
+                    for (i = 0; i < streams->num_ext_timestamp_descriptors; ++i)
+                        if (DIFF(
+                                streams->ext_timestamp_descriptor[i].seq_num,
+                                mpu_seq_num) > max_diff) {
+                            desc     = streams->ext_timestamp_descriptor + i;
+                            max_diff = DIFF(
+                                streams->ext_timestamp_descriptor[i].seq_num,
+                                mpu_seq_num);
+                        }
+                    av_assert1(desc != NULL);
+                    goto end1;
+                }
+
+                desc = av_dynarray2_add(
+                    (void **) &streams->ext_timestamp_descriptor,
+                    &streams->num_ext_timestamp_descriptors,
+                    sizeof(struct MPUExtendedTimestampDescriptor), NULL);
+                if (desc == NULL)
+                    return AVERROR(ENOMEM);
+
+                end1:
+                desc->seq_num              = mpu_seq_num;
+                end2:
+                desc->decoding_time_offset = decoding_time_offset;
+                desc->num_of_au            = num_of_au;
+            }
+
+            for (i = 0; i < num_of_au; ++i) {
+                if (bytestream2_get_bytes_left(&ngbc) < 2)
+                    return AVERROR_INVALIDDATA;
+                if (desc != NULL)
+                    desc->au[i].dts_pts_offset = bytestream2_get_be16u(&ngbc);
+                else
+                    bytestream2_skipu(&ngbc, 2);
+
+                if (pts_offset_type == 2) {
+                    if (bytestream2_get_bytes_left(&ngbc) < 2)
+                        return AVERROR_INVALIDDATA;
+                    if (desc != NULL)
+                        desc->au[i].pts_offset = bytestream2_get_be16u(&ngbc);
+                    else
+                        bytestream2_skipu(&ngbc, 2);
+                } else if (desc != NULL) {
+                    desc->au[i].pts_offset = default_pts_offset;
+                }
+            }
+        }
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static int
+parse_additional_arib_subtitle_info(AVStream *stream, GetByteContext *gbc)
+{
+    bool    start_mpu_sequence_number_flag;
+    char    language_code[4];
+    uint8_t subtitle_format;
+
+    if (bytestream2_get_bytes_left(gbc) <
+        (8 + 4 + 1 + 3 + 24 + 2 + 4 + 2 + 4 + 4 + 4 + 4) / 8)
+        return AVERROR_INVALIDDATA;
+    // skip: subtitle_tag
+    bytestream2_skipu(gbc, 1);
+    start_mpu_sequence_number_flag = (bytestream2_get_byteu(gbc) >> 3) & 1;
+    bytestream2_get_bufferu(gbc, language_code, 3);
+    language_code[3] = '\0';
+    subtitle_format = (bytestream2_get_byteu(gbc) >> 2) & 0b1111;
+    /*
+     * skip:
+     * - TMD
+     * - DMF
+     * - resolution
+     * - compression_type
+     */
+    bytestream2_skipu(gbc, (4 + 4 + 4 + 4) / 8);
+
+    if (start_mpu_sequence_number_flag)
+        bytestream2_skip(gbc, 32);
+
+    switch (subtitle_format) {
+    case 0b0000:
+        stream->codecpar->codec_id = AV_CODEC_ID_TTML;
+        break;
+    }
+
+    return av_dict_set(&stream->metadata, "language", language_code, 0);
+}
+
+static int
+parse_mh_data_component_descriptor(AVStream *stream, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != MH_DATA_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+        bytestream2_skipu(gbc, descriptor_length);
+
+        if (bytestream2_get_bytes_left(&ngbc) < 16 / 8)
+            return AVERROR_INVALIDDATA;
+        switch (bytestream2_get_be16u(&ngbc)) {
+        case 0x0020: // additional ARIB subtitle info (Table 7-74, ARIB STD-B60, Version 1.14-E1)
+            return parse_additional_arib_subtitle_info(stream, &ngbc);
+        }
+    }
+
+    return 0;
+}
+
+static int
+parse_stream_identifier_descriptor(AVStream *stream, GetByteContext *gbc)
+{
+    uint8_t descriptor_length;
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != MH_STREAM_IDENTIFIER_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < descriptor_length)
+        return AVERROR_INVALIDDATA;
+    {
+        // no need for now
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static int parse_descriptor(struct Streams *streams, GetByteContext *gbc)
+{
+    if (bytestream2_get_bytes_left(gbc) < 3)
+        return AVERROR_INVALIDDATA;
+    switch (bytestream2_peek_be16u(gbc)) {
+    case MPU_TIMESTAMP_DESCRIPTOR:
+        return parse_mpu_timestamp_descriptor(streams, gbc);
+    case VIDEO_COMPONENT_DESCRIPTOR:
+        return parse_video_component_descriptor(streams->stream, gbc);
+    case MH_STREAM_IDENTIFIER_DESCRIPTOR:
+        return parse_stream_identifier_descriptor(streams->stream, gbc);
+    case MH_AUDIO_COMPONENT_DESCRIPTOR:
+        return parse_mh_audio_component_descriptor(streams->stream, gbc);
+    case MH_DATA_COMPONENT_DESCRIPTOR:
+        return parse_mh_data_component_descriptor(streams->stream, gbc);
+    case MPU_EXTENDED_TIMESTAMP_DESCRIPTOR:
+        return parse_mpu_extended_timestamp_descriptor(streams, gbc);
+    case ACCESS_CONTROL_DESCRIPTOR:
+        bytestream2_skipu(gbc, 2);
+        bytestream2_skip(gbc, bytestream2_get_byteu(gbc));
+        return 0;
+    }
+    av_log(streams->stream, AV_LOG_INFO, "Unknown descriptor: 0x%04x\n",
+           bytestream2_peek_be16u(gbc));
+    return AVERROR_PATCHWELCOME;
+}
+
+static int parse_mmt_package_table(MMTPContext *ctx, GetByteContext *gbc)
+{
+    uint16_t length;
+
+    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 16) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_byteu(gbc) != MMT_PACKAGE_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < length)
+        return AVERROR_INVALIDDATA;
+    {
+        size_t   i, j;
+        uint8_t  package_id_length;
+        uint16_t descriptors_length;
+        uint8_t  number_of_assets;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        if (bytestream2_get_bytes_left(&ngbc) < (6 + 2 + 8) / 8)
+            return AVERROR_INVALIDDATA;
+
+        // skip: MPT_mode
+        bytestream2_skipu(&ngbc, 1);
+        package_id_length = bytestream2_get_byteu(&ngbc);
+
+        bytestream2_skip(&ngbc, package_id_length);
+
+        descriptors_length = bytestream2_get_be16(&ngbc);
+        bytestream2_skip(&ngbc, descriptors_length);
+
+        if (bytestream2_get_bytes_left(&ngbc) < 1)
+            return AVERROR_INVALIDDATA;
+        number_of_assets = bytestream2_get_byteu(&ngbc);
+
+        for (i = 0; i < number_of_assets; ++i) {
+            int err;
+
+            uint8_t  asset_id_length;
+            uint8_t  location_count;
+            uint16_t asset_descriptors_length;
+            uint32_t asset_type;
+
+            struct Streams *stream = NULL;
+
+            struct MMTGeneralLocationInfo info;
+
+            if (bytestream2_get_bytes_left(&ngbc) < (8 + 32 + 8) / 8)
+                return AVERROR_INVALIDDATA;
+            /*
+             * skip:
+             * - identifier_type
+             * - asset_id_scheme
+             */
+            bytestream2_skipu(&ngbc, (8 + 32) / 8);
+            asset_id_length = bytestream2_get_byteu(&ngbc);
+
+            bytestream2_skip(&ngbc, asset_id_length);
+
+            asset_type = bytestream2_get_le32(&ngbc);
+
+            // skip: asset_clock_relation_flag
+            bytestream2_skip(&ngbc, 1);
+
+            if (bytestream2_get_bytes_left(&ngbc) < 1)
+                return AVERROR_INVALIDDATA;
+            location_count = bytestream2_get_byteu(&ngbc);
+
+            for (j = 0; j < location_count; ++j)
+                if ((err = parse_mmt_general_location_info(&info, &ngbc)) < 0)
+                    return err;
+
+            switch (asset_type) {
+            case MKTAG('h', 'e', 'v', '1'):
+                if (info.location_type != 0x00) return AVERROR_PATCHWELCOME;
+                stream = find_or_allocate_stream(ctx, info.type0.packet_id);
+                if (stream == NULL) return AVERROR(ENOMEM);
+                stream->stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+                stream->stream->codecpar->codec_id   = AV_CODEC_ID_HEVC;
+                stream->stream->codecpar->codec_tag  = asset_type;
+                break;
+            case MKTAG('m', 'p', '4', 'a'):
+                if (info.location_type != 0x00) return AVERROR_PATCHWELCOME;
+                stream = find_or_allocate_stream(ctx, info.type0.packet_id);
+                if (stream == NULL) return AVERROR(ENOMEM);
+                stream->stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+                stream->stream->codecpar->codec_tag  = asset_type;
+                break;
+            case MKTAG('s', 't', 'p', 'p'):
+                if (info.location_type == 0x00) {
+                    stream = find_or_allocate_stream(ctx, info.type0.packet_id);
+                    if (stream == NULL) return AVERROR(ENOMEM);
+                    stream->stream->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+                    stream->stream->codecpar->codec_tag  = asset_type;
+                }
+                break;
+            case MKTAG('a', 'a', 'p', 'p'):
+            case MKTAG('a', 's', 'g', 'd'):
+            case MKTAG('a', 'a', 'g', 'd'):
+                break; // TODO
+            }
+
+            if (bytestream2_get_bytes_left(&ngbc) < 2)
+                return AVERROR_INVALIDDATA;
+            asset_descriptors_length = bytestream2_get_be16u(&ngbc);
+            if (bytestream2_get_bytes_left(&ngbc) < asset_descriptors_length)
+                return AVERROR_INVALIDDATA;
+            if (stream != NULL) {
+                GetByteContext nngbc;
+                bytestream2_init(&nngbc, ngbc.buffer, asset_descriptors_length);
+
+                while (bytestream2_get_bytes_left(&nngbc) > 0)
+                    if ((err = parse_descriptor(stream, &nngbc)) < 0)
+                        return err;
+            }
+            bytestream2_skipu(&ngbc, asset_descriptors_length);
+        }
+    }
+    bytestream2_skipu(gbc, length);
+
+    return 0;
+}
+
+static int parse_package_list_table(MMTPContext *ctx, GetByteContext *gbc)
+{
+    size_t   i;
+    uint32_t length;
+
+    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 16) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_byteu(gbc) != PACKAGE_LIST_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < length)
+        return AVERROR_INVALIDDATA;
+    {
+        int     err;
+        uint8_t num_of_package;
+        uint8_t num_of_ip_delivery;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        if (bytestream2_get_bytes_left(gbc) < 1)
+            return AVERROR_INVALIDDATA;
+        num_of_package = bytestream2_get_byteu(&ngbc);
+
+        for (i = 0; i < num_of_package; ++i) {
+            uint8_t                       package_id_length;
+            struct MMTGeneralLocationInfo info;
+
+            package_id_length = bytestream2_get_byte(&ngbc);
+            bytestream2_skip(&ngbc, package_id_length);
+
+            if ((err = parse_mmt_general_location_info(&info, &ngbc)) < 0)
+                return err;
+        }
+
+        if (bytestream2_get_bytes_left(&ngbc) < 1)
+            return AVERROR_INVALIDDATA;
+        num_of_ip_delivery = bytestream2_get_byteu(&ngbc);
+
+        for (i = 0; i < num_of_ip_delivery; ++i)
+            return AVERROR_PATCHWELCOME;
+    }
+    bytestream2_skipu(gbc, length);
+
+    return 0;
+}
+
+static int parse_table(MMTPContext *ctx, GetByteContext *gbc)
+{
+    if (bytestream2_get_bytes_left(gbc) < 2)
+        return AVERROR_INVALIDDATA;
+    switch (bytestream2_peek_byteu(gbc)) {
+    case MMT_PACKAGE_TABLE_ID:
+        return parse_mmt_package_table(ctx, gbc);
+    case PACKAGE_LIST_TABLE_ID:
+        return parse_package_list_table(ctx, gbc);
+    }
+    bytestream2_skipu(gbc, bytestream2_get_bytes_left(gbc)); // TODO
+    return 0;
+}
+
+enum {
+    PA_MESSAGE_ID = 0x0000,
+};
+
+static int parse_pa_message(MMTPContext *ctx, GetByteContext *gbc)
+{
+    uint32_t length;
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 8 + 32) / 8)
+        return AVERROR_INVALIDDATA;
+    if (bytestream2_get_be16u(gbc) != PA_MESSAGE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be32u(gbc);
+
+    if (bytestream2_get_bytes_left(gbc) < length)
+        return AVERROR_INVALIDDATA;
+    {
+        size_t  i;
+        uint8_t num_of_tables;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        if (bytestream2_get_bytes_left(gbc) < 1)
+            return AVERROR_INVALIDDATA;
+        num_of_tables = bytestream2_get_byteu(&ngbc);
+
+        for (i = 0; i < num_of_tables; ++i) {
+            bytestream2_skip(&ngbc, (8 + 8 + 16) / 8);
+        }
+
+        while (bytestream2_get_bytes_left(&ngbc) > 0) {
+            int err = parse_table(ctx, &ngbc);
+            if (err < 0) return err;
+        }
+    }
+    bytestream2_skipu(gbc, length);
+
+    return 0;
+}
+
+static int parse_signalling_message(MMTPContext *ctx, GetByteContext *gbc)
+{
+    if (bytestream2_get_bytes_left(gbc) < 4)
+        return AVERROR_INVALIDDATA;
+    switch (bytestream2_peek_be16u(gbc)) {
+    case PA_MESSAGE_ID:
+        return parse_pa_message(ctx, gbc);
+    }
+    return 0;
+}
+
+enum FragmentationIndicator {
+    NOT_FRAGMENTED  = 0b00,
+    FIRST_FRAGMENT  = 0b01,
+    MIDDLE_FRAGMENT = 0b10,
+    LAST_FRAGMENT   = 0b11,
+};
+
+struct FragmentAssembler {
+    uint16_t                 pid;
+    struct FragmentAssembler *next;
+
+    uint8_t *data;
+    size_t  size, cap;
+
+    uint32_t last_seq;
+
+    enum {
+        INIT = 0,
+        NOT_STARTED,
+        IN_FRAGMENT,
+        SKIP,
+    } state;
+};
+
+static int
+append_data(struct FragmentAssembler *ctx, const uint8_t *data, uint32_t size)
+{
+    if (ctx->size + size > UINT32_MAX) return AVERROR(EOVERFLOW);
+    if (ctx->cap < ctx->size + size) {
+        void   *new_data;
+        size_t new_cap = ctx->cap == 0 ? 1024 : ctx->cap * 2;
+        while (new_cap < ctx->size + size) new_cap *= 2;
+
+        new_data = av_realloc(ctx->data, new_cap);
+        if (new_data == NULL) return AVERROR(errno);
+        ctx->data = new_data;
+        ctx->cap  = new_cap;
+    }
+    memcpy(ctx->data + ctx->size, data, size);
+    ctx->size += size;
+    return 0;
+}
+
+static int
+check_state(MMTPContext *ctx, struct FragmentAssembler *ass, uint32_t seq_num)
+{
+    if (ass->state == INIT) {
+        ass->state = SKIP;
+    } else if (seq_num != ass->last_seq + 1) {
+        if (ass->size != 0) {
+            av_log(ctx->s, AV_LOG_WARNING,
+                   "Packet sequence number jump: %u + 1 != %u, drop %zu bytes\n",
+                   ass->last_seq, seq_num, ass->size);
+            ass->size = 0;
+        } else {
+            av_log(ctx->s, AV_LOG_WARNING,
+                   "Packet sequence number jump: %u + 1 != %u\n",
+                   ass->last_seq, seq_num);
+        }
+        ass->state = SKIP;
+    }
+    ass->last_seq = seq_num;
+    return 0;
+}
+
+static int assemble_fragment(
+    struct FragmentAssembler *ctx, uint32_t seq_num,
+    enum FragmentationIndicator indicator,
+    const uint8_t *data, uint32_t size,
+    int (*parser)(MMTPContext *, GetByteContext *),
+    MMTPContext *opaque)
+{
+    GetByteContext gbc;
+    int            err;
+
+    switch (indicator) {
+    case NOT_FRAGMENTED:
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = NOT_STARTED;
+        bytestream2_init(&gbc, data, size);
+        return parser(opaque, &gbc);
+    case FIRST_FRAGMENT:
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = IN_FRAGMENT;
+        return append_data(ctx, data, size);
+    case MIDDLE_FRAGMENT:
+        if (ctx->state == SKIP) {
+            av_log(opaque->s, AV_LOG_VERBOSE, "Drop packet %u\n", seq_num);
+            return 0;
+        }
+        if (ctx->state != IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        return append_data(ctx, data, size);
+    case LAST_FRAGMENT:
+        if (ctx->state == SKIP) {
+            av_log(opaque->s, AV_LOG_VERBOSE, "Drop packet %u\n", seq_num);
+            return 0;
+        }
+        if (ctx->state != IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        if ((err = append_data(ctx, data, size)) < 0) return err;
+
+        bytestream2_init(&gbc, ctx->data, ctx->size);
+        err = parser(opaque, &gbc);
+
+        ctx->size  = 0;
+        ctx->state = NOT_STARTED;
+        return err;
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+}
+
+static struct FragmentAssembler *
+find_or_allocate_assembler(MMTPContext *ctx, uint16_t pid)
+{
+    struct FragmentAssembler *ass;
+    for (ass = ctx->assembler; ass != NULL; ass = ass->next)
+        if (ass->pid == pid)
+            return ass;
+
+    ass = av_mallocz(sizeof(struct FragmentAssembler));
+    if (ass == NULL) return NULL;
+    ass->pid              = pid;
+    ass->next             = ctx->assembler;
+    return ctx->assembler = ass;
+}
+
+static int parse_signalling_messages(
+    MMTPContext *ctx, uint32_t seq_num, GetByteContext *gbc)
+{
+    int                         err;
+    uint8_t                     byte;
+    enum FragmentationIndicator fragmentation_indicator;
+    bool                        length_extension_flag;
+    bool                        aggregation_flag;
+
+    struct FragmentAssembler *assembler = find_or_allocate_assembler(
+        ctx, ctx->current_pid);
+    if (assembler == NULL) return AVERROR(errno);
+
+    if (bytestream2_get_bytes_left(gbc) < (2 + 4 + 1 + 1 + 8) / 8)
+        return AVERROR_INVALIDDATA;
+    byte                    = bytestream2_get_byteu(gbc);
+    fragmentation_indicator = byte >> 6;
+    length_extension_flag   = (byte >> 1) & 1;
+    aggregation_flag        = byte & 1;
+
+    bytestream2_skipu(gbc, 1);
+
+    if ((err = check_state(ctx, assembler, seq_num)) < 0)
+        return err;
+
+    if (!aggregation_flag)
+        return assemble_fragment(
+            assembler, seq_num, fragmentation_indicator,
+            gbc->buffer, bytestream2_get_bytes_left(gbc),
+            parse_signalling_message, ctx);
+
+    if (fragmentation_indicator != NOT_FRAGMENTED)
+        return AVERROR_INVALIDDATA; // cannot be both fragmented and aggregated
+
+    while (bytestream2_get_bytes_left(gbc) > 0) {
+        uint32_t length;
+
+        if (length_extension_flag)
+            length = bytestream2_get_be32(gbc);
+        else
+            length = bytestream2_get_be16(gbc);
+
+        if (bytestream2_get_bytes_left(gbc) < length)
+            return AVERROR_INVALIDDATA;
+        if ((err = assemble_fragment(
+            assembler, seq_num, NOT_FRAGMENTED,
+            gbc->buffer, length, parse_signalling_message, ctx)) < 0)
+            return err;
+        bytestream2_skipu(gbc, length);
+    }
+
+    return 0;
+}
+
+static int fill_pts_dts(MMTPContext *ctx, struct Streams *s)
+{
+    struct MPUTimestampDescriptor         *desc     = NULL;
+    struct MPUExtendedTimestampDescriptor *ext_desc = NULL;
+
+    int64_t ptime;
+    size_t  i, j;
+
+    for (i = 0; i < s->num_timestamp_descriptors; ++i) {
+        if (s->timestamp_descriptor[i].seq_num ==
+            s->last_sequence_number) {
+            desc = s->timestamp_descriptor + i;
+            break;
+        }
+    }
+
+    for (i = 0; i < s->num_ext_timestamp_descriptors; ++i) {
+        if (s->ext_timestamp_descriptor[i].seq_num ==
+            s->last_sequence_number) {
+            ext_desc = s->ext_timestamp_descriptor + i;
+            break;
+        }
+    }
+
+    if (desc == NULL || ext_desc == NULL) return FFERROR_REDO;
+    ptime = av_rescale(desc->presentation_time, s->stream->time_base.den,
+                       1000000ll * s->stream->time_base.num);
+
+    if (s->au_count >= ext_desc->num_of_au)
+        return AVERROR_INVALIDDATA;
+
+    ctx->pkt->dts = ptime - ext_desc->decoding_time_offset;
+
+    for (j = 0; j < s->au_count; ++j)
+        ctx->pkt->dts += ext_desc->au[j].pts_offset;
+
+    ctx->pkt->pts = ctx->pkt->dts + ext_desc->au[s->au_count].dts_pts_offset;
+
+    ++s->au_count;
+    return 0;
+}
+
+static int emit_closed_caption_mfu(MMTPContext *ctx, struct Streams *st,
+                                   GetByteContext *gbc)
+{
+    uint8_t  data_type, subsample_number, last_subsample_number, byte;
+    uint32_t data_size;
+    size_t   i;
+    int      err;
+    bool     length_ext_flag, subsample_info_list_flag;
+
+    av_assert0(ctx->pkt != NULL);
+
+    if (bytestream2_get_bytes_left(gbc) < (8 + 8 + 8 + 8 + 4 + 1 + 1 + 2) / 8)
+        return AVERROR_INVALIDDATA;
+
+    /*
+     * skip:
+     * - subtitle_tag
+     * - subtitle_sequence_number
+     */
+    bytestream2_skipu(gbc, (8 + 8) / 8);
+
+    subsample_number      = bytestream2_get_byteu(gbc);
+    last_subsample_number = bytestream2_get_byteu(gbc);
+
+    byte                     = bytestream2_get_byteu(gbc);
+    data_type                = byte >> 4;
+    length_ext_flag          = (byte >> 3) & 1;
+    subsample_info_list_flag = (byte >> 2) & 1;
+
+    if (data_type != 0b0000) return AVERROR_PATCHWELCOME;
+
+    if (length_ext_flag)
+        data_size = bytestream2_get_be32(gbc);
+    else
+        data_size = bytestream2_get_be16(gbc);
+
+    if (subsample_number == 0 && last_subsample_number > 0 &&
+        subsample_info_list_flag) {
+        for (i = 0; i < last_subsample_number; ++i) {
+            // skip: subsample_i_data_type
+            bytestream2_skip(gbc, (4 + 4) / 8);
+            // skip: subsample_i_data_size
+            if (length_ext_flag) {
+                bytestream2_skip(gbc, 32 / 8);
+            } else {
+                bytestream2_skip(gbc, 16 / 8);
+            }
+        }
+    }
+
+    if (bytestream2_get_bytes_left(gbc) < data_size)
+        return AVERROR_INVALIDDATA;
+    if ((err = av_new_packet(ctx->pkt, data_size)) < 0) return err;
+    bytestream2_get_bufferu(gbc, ctx->pkt->data, data_size);
+
+    ctx->pkt->stream_index = st->stream->index;
+    ctx->pkt->flags        = st->flags;
+    ctx->pkt->pos          = st->offset;
+    ctx->pkt               = NULL;
+
+    st->flags  = 0;
+    st->offset = -1;
+    return 0;
+}
+
+static int emit_packet(MMTPContext *ctx, struct Streams *st, AVBufferRef *buf)
+{
+    int err;
+    av_assert0(ctx->pkt != NULL);
+    av_packet_unref(ctx->pkt);
+    if ((err = fill_pts_dts(ctx, st)) < 0) {
+        av_buffer_unref(&buf);
+        return err;
+    }
+    ctx->pkt->buf          = buf;
+    ctx->pkt->data         = buf->data;
+    ctx->pkt->size         = buf->size - AV_INPUT_BUFFER_PADDING_SIZE;
+    ctx->pkt->stream_index = st->stream->index;
+    ctx->pkt->flags        = st->flags;
+    ctx->pkt->pos          = st->offset;
+    ctx->pkt               = NULL;
+
+    st->flags  = 0;
+    st->offset = -1;
+    return 0;
+}
+
+static int consume_mfu(MMTPContext *ctx, GetByteContext *gbc)
+{
+    int            err;
+    AVBufferRef    *buf_ref;
+    unsigned int   size;
+    uint8_t        byte;
+    size_t         old_size;
+    struct Streams *st = find_current_stream(ctx);
+    av_assert0(st != NULL);
+
+    switch (st->stream->codecpar->codec_id) {
+    case AV_CODEC_ID_HEVC:
+        size = bytestream2_get_be32(gbc);
+        if (size != bytestream2_get_bytes_left(gbc)) return AVERROR_INVALIDDATA;
+        if (size < 1) // we expect to extract NAL unit header type below
+            return AVERROR_INVALIDDATA;
+        byte = bytestream2_peek_byteu(gbc);
+        if ((byte >> 7) != 0) return AVERROR_INVALIDDATA; // forbidden_zero_bit
+
+        old_size = st->pending_buffer == NULL ? 0 :
+                   (st->pending_buffer->size - AV_INPUT_BUFFER_PADDING_SIZE);
+        if ((err = av_buffer_realloc(
+            &st->pending_buffer,
+            old_size + size + 4 + AV_INPUT_BUFFER_PADDING_SIZE)) < 0)
+            return err;
+        // fix start code (00 00 00 01)
+        AV_WB32(st->pending_buffer->data + old_size, 1);
+        bytestream2_get_bufferu(
+            gbc, st->pending_buffer->data + old_size + 4, size);
+        if (((byte >> 1) & 0b111111) < 0x20) { // a VCL NAL unit
+            // Because we can't emit a packet without a valid PTS, we need to
+            // aggregate the non-VCL NAL units with VCL ones. Although we didn't
+            // technically identify an access unit here, this works for all samples
+            // we have.
+            buf_ref = st->pending_buffer;
+            st->pending_buffer = NULL;
+
+            memset(buf_ref->data + old_size + size + 4, 0,
+                   AV_INPUT_BUFFER_PADDING_SIZE);
+            return emit_packet(ctx, st, buf_ref);
+        }
+        return 0;
+    case AV_CODEC_ID_AAC_LATM:
+        size = bytestream2_get_bytes_left(gbc);
+        if (size >> 13) return AVERROR(EOVERFLOW);
+        if ((buf_ref = av_buffer_alloc(
+            size + 3 + AV_INPUT_BUFFER_PADDING_SIZE)) == NULL)
+            return AVERROR(ENOMEM);
+        buf_ref->data[0] = 0x56;
+        buf_ref->data[1] = 0xe0 | (size >> 8);
+        buf_ref->data[2] = size & 0xff;
+        bytestream2_get_bufferu(gbc, buf_ref->data + 3, size);
+        memset(buf_ref->data + 3 + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+        return emit_packet(ctx, st, buf_ref);
+    case AV_CODEC_ID_TTML:
+        return emit_closed_caption_mfu(ctx, st, gbc);
+    default:
+        return AVERROR_PATCHWELCOME;
+    }
+}
+
+static int parse_mfu_timed_data(
+    MMTPContext *ctx, struct FragmentAssembler *assembler,
+    uint32_t seq_num, enum FragmentationIndicator indicator,
+    GetByteContext *gbc)
+{
+    bytestream2_skip(gbc, (32 + 32 + 32 + 8 + 8) / 8);
+    return assemble_fragment(
+        assembler, seq_num, indicator,
+        gbc->buffer, bytestream2_get_bytes_left(gbc),
+        consume_mfu, ctx);
+}
+
+static int parse_mfu_non_timed_data(
+    MMTPContext *ctx, struct FragmentAssembler *assembler,
+    uint32_t seq_num, enum FragmentationIndicator indicator,
+    GetByteContext *gbc)
+{
+    bytestream2_skip(gbc, 32 / 8);
+    return assemble_fragment(
+        assembler, seq_num, indicator,
+        gbc->buffer, bytestream2_get_bytes_left(gbc),
+        consume_mfu, ctx);
+}
+
+static int parse_mpu(MMTPContext *ctx, uint32_t seq_num, GetByteContext *gbc)
+{
+    int                         err;
+    uint8_t                     byte, fragment_type;
+    bool                        timed_flag;
+    enum FragmentationIndicator fragmentation_indicator;
+    bool                        aggregation_flag;
+    uint16_t                    length;
+    uint32_t                    mpu_sequence_number;
+    struct FragmentAssembler    *assembler;
+    struct Streams              *streams;
+
+    streams = find_current_stream(ctx);
+    if (streams == NULL || streams->stream->discard >= AVDISCARD_ALL)
+        return 0;
+
+    assembler = find_or_allocate_assembler(ctx, ctx->current_pid);
+    if (assembler == NULL) return AVERROR(errno);
+
+    if (bytestream2_get_bytes_left(gbc) < (16 + 4 + 1 + 2 + 1 + 8 + 32) / 8)
+        return AVERROR_INVALIDDATA;
+
+    length = bytestream2_get_be16u(gbc);
+    if (length != bytestream2_get_bytes_left(gbc))
+        return AVERROR_INVALIDDATA;
+
+    byte                    = bytestream2_get_byteu(gbc);
+    fragment_type           = byte >> 4;
+    timed_flag              = (byte >> 3) & 1;
+    fragmentation_indicator = (byte >> 1) & 0b11;
+    aggregation_flag        = byte & 1;
+
+    // skip: fragment_counter
+    bytestream2_skipu(gbc, 1);
+
+    mpu_sequence_number = bytestream2_get_be32u(gbc);
+
+    if (aggregation_flag && fragmentation_indicator != NOT_FRAGMENTED)
+        return AVERROR_INVALIDDATA; // cannot be both fragmented and aggregated
+
+    if (fragment_type != 2)
+        return 0; // not MFU
+
+    if (assembler->state == INIT && !ctx->is_rap)
+        return 0; // wait for the first RAP
+
+    if (assembler->state == INIT) {
+        streams->last_sequence_number = mpu_sequence_number;
+    } else if (mpu_sequence_number == streams->last_sequence_number + 1) {
+        streams->last_sequence_number = mpu_sequence_number;
+        streams->au_count             = 0;
+    } else if (mpu_sequence_number != streams->last_sequence_number) {
+        av_log(streams->stream, AV_LOG_ERROR,
+               "MPU sequence number jump: %u + 1 != %u\n",
+               streams->last_sequence_number, mpu_sequence_number);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if ((err = check_state(ctx, assembler, seq_num)) < 0)
+        return err;
+
+    if (fragmentation_indicator == NOT_FRAGMENTED ||
+        fragmentation_indicator == FIRST_FRAGMENT)
+        streams->offset = ctx->pkt->pos;
+
+    if (ctx->is_rap)
+        streams->flags |= AV_PKT_FLAG_KEY;
+
+    if (timed_flag) {
+        if (aggregation_flag) {
+            while (bytestream2_get_bytes_left(gbc) > 0) {
+                length = bytestream2_get_be16(gbc);
+                if (bytestream2_get_bytes_left(gbc) < length)
+                    return AVERROR_INVALIDDATA;
+                {
+                    GetByteContext ngbc;
+                    bytestream2_init(&ngbc, gbc->buffer, length);
+
+                    err = parse_mfu_timed_data(
+                        ctx, assembler, seq_num, NOT_FRAGMENTED, &ngbc);
+                    if (err < 0) return err;
+                }
+                bytestream2_skipu(gbc, length);
+            }
+        } else {
+            return parse_mfu_timed_data(
+                ctx, assembler, seq_num, fragmentation_indicator, gbc);
+        }
+    } else {
+        if (aggregation_flag) {
+            while (bytestream2_get_bytes_left(gbc) > 0) {
+                length = bytestream2_get_be16(gbc);
+                if (bytestream2_get_bytes_left(gbc) < length)
+                    return AVERROR_INVALIDDATA;
+                {
+                    GetByteContext ngbc;
+                    bytestream2_init(&ngbc, gbc->buffer, length);
+
+                    err = parse_mfu_non_timed_data(
+                        ctx, assembler, seq_num, NOT_FRAGMENTED, &ngbc);
+                    if (err < 0) return err;
+                }
+                bytestream2_skipu(gbc, length);
+            }
+        } else {
+            return parse_mfu_non_timed_data(
+                ctx, assembler, seq_num, fragmentation_indicator, gbc);
+        }
+    }
+
+    return 0;
+}
+
+MMTPContext *ff_mmtp_parse_open(AVProgram *program)
+{
+    MMTPContext *ctx = av_mallocz(sizeof(MMTPContext));
+    if (ctx == NULL) return NULL;
+    ctx->program = program;
+    return ctx;
+}
+
+int ff_mmtp_parse_packet(MMTPContext *ctx, AVFormatContext *s, AVPacket *pkt,
+                         const uint8_t *buf, uint16_t size)
+{
+    bool     packet_counter_flag;
+    bool     extension_header_flag;
+    uint8_t  payload_type;
+    uint32_t packet_sequence_number;
+    uint8_t  byte;
+    int      err = 0;
+
+    GetByteContext gbc;
+
+    ctx->s   = s;
+    ctx->pkt = pkt;
+
+    bytestream2_init(&gbc, buf, size);
+    if (bytestream2_get_bytes_left(&gbc) <
+        (2 + 1 + 2 + 1 + 1 + 1 + 2 + 6 + 16 + 32 + 32) / 8)
+        return AVERROR_INVALIDDATA;
+
+    byte                  = bytestream2_get_byteu(&gbc);
+    packet_counter_flag   = (byte >> 5) & 1;
+    extension_header_flag = (byte >> 1) & 1;
+    ctx->is_rap = byte & 1;
+
+    byte         = bytestream2_get_byteu(&gbc);
+    payload_type = byte & 0b111111;
+
+    ctx->current_pid = bytestream2_get_be16u(&gbc);
+
+    // skip: distribute_timestamp
+    bytestream2_skipu(&gbc, 4);
+
+    packet_sequence_number = bytestream2_get_be32u(&gbc);
+
+    if (packet_counter_flag)
+        bytestream2_skip(&gbc, 4);
+
+    if (extension_header_flag) {
+        uint16_t extension_header_length;
+        // skip: extension_type
+        bytestream2_skip(&gbc, 2);
+        extension_header_length = bytestream2_get_be16(&gbc);
+        bytestream2_skip(&gbc, extension_header_length);
+    }
+
+    switch (payload_type) {
+    case 0x00: // MPU
+        if (pkt != NULL)
+            err = parse_mpu(ctx, packet_sequence_number, &gbc);
+        break;
+    case 0x02: // signalling messages
+        err = parse_signalling_messages(ctx, packet_sequence_number, &gbc);
+        break;
+    }
+    if (err < 0) return err;
+    return ctx->pkt == NULL ? 0 : FFERROR_REDO;
+}
+
+void ff_mmtp_reset_state(MMTPContext *ctx)
+{
+    struct Streams           *streams;
+    struct FragmentAssembler *assembler;
+
+    for (assembler = ctx->assembler;
+         assembler != NULL; assembler = assembler->next) {
+        assembler->state = INIT;
+        assembler->size  = 0;
+    }
+    for (streams = ctx->streams; streams != NULL; streams = streams->next) {
+        streams->last_sequence_number = 0;
+        streams->au_count             = 0;
+        streams->flags                = 0;
+        streams->offset               = -1;
+        av_buffer_unref(&streams->pending_buffer);
+    }
+}
+
+void ff_mmtp_parse_close(MMTPContext *ctx)
+{
+    struct FragmentAssembler *ass;
+    struct Streams           *streams;
+
+    for (ass = ctx->assembler; ass != NULL;) {
+        struct FragmentAssembler *next = ass->next;
+        av_free(ass->data);
+        av_free(ass);
+        ass = next;
+    }
+
+    for (streams = ctx->streams; streams != NULL;) {
+        struct Streams *next = streams->next;
+        av_free(streams->timestamp_descriptor);
+        av_free(streams->ext_timestamp_descriptor);
+        av_buffer_unref(&streams->pending_buffer);
+        av_free(streams);
+        streams = next;
+    }
+
+    av_free(ctx);
+}
diff --git a/libavformat/mmtp.h b/libavformat/mmtp.h
new file mode 100644
index 0000000000..300a6a1aea
--- /dev/null
+++ b/libavformat/mmtp.h
@@ -0,0 +1,64 @@ 
+/*
+ * MPEG Media Transport Protocol (MMTP) parser, as defined in ISO/IEC 23008-1.
+ * Copyright (c) 2023 SuperFashi
+ *
+ * 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
+ */
+#ifndef AVFORMAT_MMTP_H
+#define AVFORMAT_MMTP_H
+
+#include "avformat.h"
+
+typedef struct MMTPContext MMTPContext;
+
+/**
+ * Open an MMT protocol parser context.
+ * @param program The AVProgram this context is associated with.
+ * @return A new MMTPContext, or NULL on allocation error.
+ */
+MMTPContext *ff_mmtp_parse_open(AVProgram *program);
+
+/**
+ * Parse an MMT protocol packet.
+ *
+ * @param ctx The MMT protocol parser context.
+ * @param s The AVFormatContext.
+ * @param pkt The AVPacket to fill.
+ * @param buf The packet data.
+ * @param size The size of the packet data.
+ * @return >= 0 if a new AVPacket is emitted,
+ *         FFERROR_REDO if the next packet is needed,
+ *         or another negative value on error.
+ */
+int ff_mmtp_parse_packet(MMTPContext *ctx, AVFormatContext *s, AVPacket *pkt,
+                         const uint8_t *buf, uint16_t size);
+
+/**
+ * Reset the state of the MMTP parser. Useful when seeking.
+ *
+ * @param ctx The MMT protocol parser context.
+ */
+void ff_mmtp_reset_state(MMTPContext *ctx);
+
+/**
+ * Close an MMT protocol parser context, frees all associated resources.
+ *
+ * @param ctx The MMT protocol parser context.
+ */
+void ff_mmtp_parse_close(MMTPContext *ctx);
+
+#endif /* AVFORMAT_MMTP_H */
diff --git a/libavformat/mmttlv.c b/libavformat/mmttlv.c
new file mode 100644
index 0000000000..622840e4a1
--- /dev/null
+++ b/libavformat/mmttlv.c
@@ -0,0 +1,335 @@ 
+/*
+ * MMT protocol over TLV packets (MMT/TLV) demuxer, as defined in ARIB STD-B32.
+ * Copyright (c) 2023 SuperFashi
+ *
+ * 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
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/internal.h"
+#include "libavutil/intreadwrite.h"
+#include "avformat.h"
+#include "avio_internal.h"
+#include "demux.h"
+#include "internal.h"
+#include "mmtp.h"
+
+#define HEADER_BYTE 0b01111111
+
+enum {
+    UNDEFINED_PACKET            = 0x00,
+    IPV4_PACKET                 = 0x01,
+    IPV6_PACKET                 = 0x02,
+    HEADER_COMPRESSED_IP_PACKET = 0x03,
+    TRANSMISSION_CONTROL_PACKET = 0xFE,
+    NULL_PACKET                 = 0xFF,
+};
+
+enum {
+    CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER = 0x20,
+    CONTEXT_IDENTIFICATION_IPV4_HEADER                         = 0x21,
+    CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER = 0x60,
+    CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER                = 0x61,
+};
+
+static int mmttlv_probe(const AVProbeData *p)
+{
+    size_t   i, j;
+    uint8_t  packet_type;
+    uint16_t data_length;
+
+    int processed  = 0;
+    int recognized = 0;
+
+    for (i = 0; i + 4 < p->buf_size && processed < 100; ++processed) {
+        if (p->buf[i] != HEADER_BYTE) return 0;
+
+        packet_type = p->buf[i + 1];
+        data_length = AV_RB16(p->buf + i + 2);
+        i += 4;
+
+        if (packet_type == HEADER_COMPRESSED_IP_PACKET) {
+            if (data_length < 3 || i + 2 >= p->buf_size) goto skip;
+            switch (p->buf[i + 2]) {
+            case CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER:
+            case CONTEXT_IDENTIFICATION_IPV4_HEADER:
+            case CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER:
+            case CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER:
+                ++recognized;
+            }
+        } else if (packet_type == NULL_PACKET) {
+            // null packets should contain all 0xFFs
+            for (j = i; j < i + data_length && j < p->buf_size; ++j) {
+                if (p->buf[j] != 0xFF) goto skip;
+            }
+            ++recognized;
+        }
+
+        skip:
+        i += data_length;
+    }
+
+    return recognized * AVPROBE_SCORE_MAX / FFMAX(processed, 10);
+}
+
+struct MMTTLVContext {
+    struct Program {
+        uint32_t       cid;
+        MMTPContext    *mmtp;
+        struct Program *next;
+    } *programs;
+
+    int64_t last_pos;
+    size_t  resync_size;
+
+    size_t  cap;
+    uint8_t *buf;
+};
+
+static int mmttlv_read_compressed_ip_packet(
+    struct MMTTLVContext *ctx, AVFormatContext *s, AVPacket *pkt,
+    const uint8_t *buf, uint16_t size)
+{
+    // partial udp header are udp header without data length (16 bits) and checksum (16 bits)
+#define PARTIAL_UDP_HEADER_LENGTH (8 - 4)
+    // partial ipv6 header are ipv6 header without payload length (16 bits)
+#define PARTIAL_IPV6_HEADER_LENGTH (40 - 2)
+
+    uint32_t       context_id;
+    struct Program *program;
+
+    if (size < 3)
+        return AVERROR_INVALIDDATA;
+    context_id = AV_RB16(buf) >> 4;
+    buf += 3;
+    size -= 3;
+
+    for (program = ctx->programs; program != NULL; program = program->next)
+        if (program->cid == context_id)
+            break;
+
+    if (program == NULL) {
+        AVProgram *p = av_new_program(s, context_id);
+        if (p == NULL) return AVERROR(errno);
+
+        program = av_malloc(sizeof(struct Program));
+        if (program == NULL) return AVERROR(errno);
+
+        program->mmtp = ff_mmtp_parse_open(p);
+        program->next = ctx->programs;
+        ctx->programs = program;
+        program->cid  = context_id;
+    }
+
+    switch (buf[-1]) {
+    case CONTEXT_IDENTIFICATION_PARTIAL_IPV4_AND_PARTIAL_UDP_HEADER:
+    case CONTEXT_IDENTIFICATION_IPV4_HEADER:
+        return AVERROR_PATCHWELCOME;
+    case CONTEXT_IDENTIFICATION_PARTIAL_IPV6_AND_PARTIAL_UDP_HEADER:
+        if (size < PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH)
+            return AVERROR_INVALIDDATA;
+        size -= PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH;
+        buf += PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH;
+    case CONTEXT_IDENTIFICATION_NO_COMPRESSED_HEADER:
+        break;
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+
+    return ff_mmtp_parse_packet(program->mmtp, s, pkt, buf, size);
+}
+
+static int mmttlv_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    uint8_t              header[4];
+    uint16_t             size;
+    int                  err;
+    struct MMTTLVContext *ctx = s->priv_data;
+    int64_t              pos  = avio_tell(s->pb);
+
+    if (pos < 0) return (int) pos;
+    if (pos != ctx->last_pos) {
+        ctx->last_pos = pos;
+
+        while (pos - ctx->last_pos < ctx->resync_size) {
+            if ((err = ffio_ensure_seekback(s->pb, 4)) < 0)
+                return err;
+
+            if ((err = avio_read(s->pb, header, 4)) < 0)
+                return avio_feof(s->pb) ? AVERROR_EOF : err;
+
+            if (header[0] != HEADER_BYTE) {
+                if ((pos = avio_seek(s->pb, -3, SEEK_CUR)) < 0)
+                    return (int) pos;
+                continue;
+            }
+
+            size = AV_RB16(header + 2);
+
+            if ((pos = avio_seek(s->pb, -4, SEEK_CUR)) < 0)
+                return (int) pos;
+
+            if ((err = ffio_ensure_seekback(s->pb, 4 + size + 1)) < 0)
+                return err;
+
+            if ((pos = avio_skip(s->pb, 4 + size)) < 0)
+                return (int) pos;
+
+            if ((err = avio_read(s->pb, header, 1)) < 0)
+                return avio_feof(s->pb) ? AVERROR_EOF : err;
+
+            if (header[0] == HEADER_BYTE) {
+                // found HEADER, [size], HEADER, should be good
+                if ((pos = avio_seek(s->pb, -size - 1 - 4, SEEK_CUR)) < 0)
+                    return (int) pos;
+                goto success;
+            }
+
+            if ((pos = avio_seek(s->pb, -size - 1 - 3, SEEK_CUR)) < 0)
+                return (int) pos;
+        }
+        return AVERROR_INVALIDDATA;
+
+        success:
+        ctx->last_pos = pos;
+
+        for (struct Program *program = ctx->programs;
+             program != NULL; program = program->next)
+            ff_mmtp_reset_state(program->mmtp);
+    }
+
+    if (pkt != NULL) pkt->pos = ctx->last_pos;
+    if ((err = ffio_read_size(s->pb, header, 4)) < 0)
+        return avio_feof(s->pb) ? AVERROR_EOF : err;
+    ctx->last_pos += 4;
+
+    if (header[0] != HEADER_BYTE)
+        return AVERROR_INVALIDDATA;
+
+    size = AV_RB16(header + 2);
+    if (header[1] != HEADER_COMPRESSED_IP_PACKET) {
+        if ((ctx->last_pos = avio_skip(s->pb, size)) < 0)
+            return (int) ctx->last_pos;
+        return pkt == NULL ? 0 : FFERROR_REDO;
+    }
+
+    if (ctx->cap < size) {
+        av_free(ctx->buf);
+        if ((ctx->buf = av_malloc(ctx->cap = size)) == NULL)
+            return AVERROR(errno);
+    }
+    if ((err = ffio_read_size(s->pb, ctx->buf, size)) < 0)
+        return avio_feof(s->pb) ? AVERROR_EOF : err;
+    ctx->last_pos += size;
+    return mmttlv_read_compressed_ip_packet(ctx, s, pkt, ctx->buf, size);
+}
+
+static int mmttlv_read_header(AVFormatContext *s)
+{
+    int64_t              pos;
+    int64_t              allow = s->probesize;
+    struct MMTTLVContext *ctx  = s->priv_data;
+
+    ctx->last_pos = avio_tell(s->pb);
+    if (ctx->last_pos < 0)
+        return (int) ctx->last_pos;
+    ctx->last_pos -= 1; // force resync
+
+    ctx->resync_size = 4096;
+    s->ctx_flags |= AVFMTCTX_NOHEADER;
+
+    if (!s->pb->seekable)
+        return 0;
+
+    if ((pos = avio_tell(s->pb)) < 0)
+        return (int) pos;
+
+    while (s->nb_streams <= 0 && allow > 0) {
+        const int64_t cur = ctx->last_pos;
+        const int     err = mmttlv_read_packet(s, NULL);
+        if (err < 0) return err;
+        allow -= ctx->last_pos - cur;
+    }
+
+    ctx->last_pos = avio_tell(s->pb);
+    if (ctx->last_pos < 0)
+        return (int) ctx->last_pos;
+
+    if ((pos = avio_seek(s->pb, pos, SEEK_SET)) < 0)
+        return (int) pos;
+
+    return 0;
+}
+
+static int mmttlv_read_close(AVFormatContext *ctx)
+{
+    struct Program       *program;
+    struct MMTTLVContext *priv = ctx->priv_data;
+    for (program = priv->programs; program != NULL;) {
+        struct Program *next = program->next;
+        ff_mmtp_parse_close(program->mmtp);
+        av_free(program);
+        program = next;
+    }
+    priv->programs = NULL;
+    priv->cap = 0;
+    av_freep(&priv->buf);
+    return 0;
+}
+
+static int64_t mmttlv_read_timestamp(
+    struct AVFormatContext *s, int stream_index,
+    int64_t *pos, int64_t pos_limit)
+{
+    struct MMTTLVContext *ctx = s->priv_data;
+
+    if ((*pos = avio_seek(s->pb, *pos, SEEK_SET)) < 0)
+        return (int) *pos;
+
+    while (pos_limit > 0) {
+        AVPacket      packet = {0};
+        const int     err    = mmttlv_read_packet(s, &packet);
+        const int64_t ts     = packet.dts;
+        const int64_t off    = packet.pos;
+        const int     sid    = packet.stream_index;
+        av_packet_unref(&packet);
+        if (err >= 0 && (stream_index < 0 || sid == stream_index)) {
+            *pos = off;
+            return ts;
+        }
+        pos_limit -= ctx->last_pos - *pos;
+        *pos = ctx->last_pos;
+        if (err < 0 && err != FFERROR_REDO)
+            return AV_NOPTS_VALUE;
+    }
+
+    return AV_NOPTS_VALUE;
+}
+
+const AVInputFormat ff_mmttlv_demuxer = {
+    .name           = "mmttlv",
+    .long_name      = NULL_IF_CONFIG_SMALL(
+        "MMT protocol over TLV packets (ARIB STD-B32)"),
+    .priv_data_size = sizeof(struct MMTTLVContext),
+    .flags_internal = FF_FMT_INIT_CLEANUP,
+    .read_probe     = mmttlv_probe,
+    .read_header    = mmttlv_read_header,
+    .read_packet    = mmttlv_read_packet,
+    .read_close     = mmttlv_read_close,
+    .read_timestamp = mmttlv_read_timestamp,
+    .flags          = AVFMT_SHOW_IDS,
+};
diff --git a/libavformat/version.h b/libavformat/version.h
index e2634b85ae..4bde82abb4 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -31,7 +31,7 @@ 
 
 #include "version_major.h"
 
-#define LIBAVFORMAT_VERSION_MINOR   5
+#define LIBAVFORMAT_VERSION_MINOR   6
 #define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \