Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avformat: add MMTP parser and MMT/TLV demuxer
@ 2023-04-28 17:31 SuperFashi
  2023-04-28 20:44 ` Michael Niedermayer
  2023-04-29  5:44 ` [FFmpeg-devel] [PATCH v2] " SuperFashi
  0 siblings, 2 replies; 17+ messages in thread
From: SuperFashi @ 2023-04-28 17:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

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 (loas), 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 filesystem format defined alongside the protocol. One of the 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       | 1169 ++++++++++++++++++++++++++++++++++++++
 libavformat/mmtp.h       |   61 ++
 libavformat/mmttlv.c     |  324 +++++++++++
 libavformat/version.h    |    2 +-
 8 files changed, 1562 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 b6f6682904..2483fdd547 100644
--- a/Changelog
+++ b/Changelog
@@ -6,6 +6,7 @@ version <next>:
 - Playdate video decoder and demuxer
 - Extend VAAPI support for libva-win32 on Windows
 - afireqsrc audio source 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..cb40b822fb
--- /dev/null
+++ b/libavformat/mmtp.c
@@ -0,0 +1,1169 @@
+/*
+ * 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 "libavutil/mem.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "network.h"
+#include "mmtp.h"
+#include "internal.h"
+#include "demux.h"
+
+#include <stdbool.h>
+
+#define LIMIT_READ(consume, block) \
+    if (size < (consume)) return AVERROR_INVALIDDATA; \
+    block; \
+    buf += (consume); \
+    size -= (consume);
+
+#define MUST_CONSUME(consume) \
+    av_assert1((consume) <= size); \
+    buf += (consume); \
+    size -= (consume);
+
+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;
+            in_port_t      dst_port;
+            uint16_t       packet_id;
+        } type1;
+        struct {
+            struct in6_addr ipv6_src_addr;
+            struct in6_addr ipv6_dst_addr;
+            in_port_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;
+            in_port_t       dst_port;
+            uint16_t        MPEG_2_PID: 13;
+        } type4;
+        struct {
+            char URL[0x100 + 1];
+        } type5;
+    };
+};
+
+static inline ssize_t
+parse_mmt_general_location_info(struct MMTGeneralLocationInfo *info, const uint8_t *buf, uint32_t size) {
+    if (size < 1) return AVERROR_INVALIDDATA;
+    switch (info->location_type = buf[0]) {
+    case 0x00:
+        if (size < 3) return AVERROR_INVALIDDATA;
+        info->type0.packet_id = AV_RB16(buf + 1);
+        return 3;
+    case 0x01:
+        if (size < (32 + 32 + 16 + 16) / 8 + 1) return AVERROR_INVALIDDATA;
+        memcpy(&info->type1.ipv4_src_addr, buf + 1, 4);
+        memcpy(&info->type1.ipv4_dst_addr, buf + 1 + 4, 4);
+        info->type1.dst_port  = AV_RB16(buf + 1 + 4 + 4);
+        info->type1.packet_id = AV_RB16(buf + 1 + 4 + 4 + 2);
+        return (32 + 32 + 16 + 16) / 8 + 1;
+    case 0x02:
+        if (size < (128 + 128 + 16 + 16) / 8 + 1) return AVERROR_INVALIDDATA;
+        memcpy(&info->type2.ipv6_src_addr, buf + 1, 16);
+        memcpy(&info->type2.ipv6_dst_addr, buf + 1 + 16, 16);
+        info->type2.dst_port  = AV_RB16(buf + 1 + 16 + 16);
+        info->type2.packet_id = AV_RB16(buf + 1 + 16 + 16 + 2);
+        return (128 + 128 + 16 + 16) / 8 + 1;
+    case 0x03:
+        if (size < (16 + 16 + 3 + 13) / 8 + 1) return AVERROR_INVALIDDATA;
+        info->type3.network_id                 = AV_RB16(buf + 1);
+        info->type3.MPEG_2_transport_stream_id = AV_RB16(buf + 1 + 2);
+        info->type3.MPEG_2_PID                 = AV_RB16(buf + 1 + 2 + 2) & 0b1111111111111;
+        return (16 + 16 + 3 + 13) / 8 + 1;
+    case 0x04:
+        if (size < (128 + 128 + 16 + 3 + 13) / 8 + 1) return AVERROR_INVALIDDATA;
+        memcpy(&info->type4.ipv6_src_addr, buf + 1, 16);
+        memcpy(&info->type4.ipv6_dst_addr, buf + 1 + 16, 16);
+        info->type4.dst_port   = AV_RB16(buf + 1 + 16 + 16);
+        info->type4.MPEG_2_PID = AV_RB16(buf + 1 + 16 + 16 + 2) & 0b1111111111111;
+        return (128 + 128 + 16 + 3 + 13) / 8 + 1;
+    case 0x05:
+        if (size < 2) return AVERROR_INVALIDDATA;
+        if (size < 1 + 1 + buf[1]) return AVERROR_INVALIDDATA;
+        memcpy(info->type5.URL, buf + 2, buf[1]);
+        info->type5.URL[buf[1]] = '\0';
+        return 1 + 1 + buf[1];
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+}
+
+
+struct Streams {
+    AVStream *stream;
+
+    int num_timestamp_descriptors;
+    struct MPUTimestampDescriptor {
+        uint32_t sequence_number;
+        int64_t  presentation_time;
+    } *timestamp_descriptor;
+
+    int num_ext_timestamp_descriptors;
+    struct MPUExtendedTimestampDescriptor {
+        uint32_t sequence_number;
+        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 inline 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 inline 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,
+    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 inline ssize_t parse_video_component_descriptor(AVStream *stream, const uint8_t *buf, uint32_t size) {
+    uint32_t read_size;
+    uint8_t  language_code[4];
+
+    LIMIT_READ((16 + 8 + 4 + 4 + 1 + 2 + 5 + 16 + 4 + 4 + 24) / 8, {
+        av_assert1(AV_RB16(buf) == VIDEO_COMPONENT_DESCRIPTOR_ID);
+        read_size = size = buf[2] + 3;
+        memcpy(language_code, buf + 8, 3);
+        language_code[3] = 0;
+    })
+
+    av_dict_set(&stream->metadata, "language", language_code, 0);
+
+    return read_size;
+}
+
+static inline ssize_t parse_mh_audio_component_descriptor(AVStream *stream, const uint8_t *buf, uint32_t size) {
+    uint32_t read_size;
+    uint8_t  stream_content;
+    uint8_t  stream_type;
+    bool     ES_multi_lingual_flag;
+    uint8_t  language_code[4];
+
+    LIMIT_READ((16 + 8 + 4 + 4 + 8 + 16 + 8 + 8 + 1 + 1 + 2 + 3 + 1 + 24) / 8, {
+        av_assert1(AV_RB16(buf) == MH_AUDIO_COMPONENT_DESCRIPTOR_ID);
+        read_size             = size = buf[2] + 3;
+        stream_content        = buf[3] & 0b1111;
+        stream_type           = buf[7];
+        ES_multi_lingual_flag = buf[9] >> 7;
+        memcpy(language_code, buf + 10, 3);
+        language_code[3] = 0;
+    })
+
+    if (ES_multi_lingual_flag) {
+        LIMIT_READ(3,)
+    }
+
+    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;
+    }
+
+    av_dict_set(&stream->metadata, "language", language_code, 0);
+
+    return read_size;
+}
+
+#define MAX_NUM_TIMESTAMP_DESCRIPTOR 32
+#define DIFF(a, b) ((a) > (b) ? ((a) - (b)) : ((b) - (a)))
+
+static inline ssize_t parse_mpu_timestamp_descriptor(struct Streams *streams, const uint8_t *buf, uint32_t size) {
+    uint32_t read_size;
+
+    LIMIT_READ((16 + 8) / 8, {
+        av_assert1(AV_RB16(buf) == MPU_TIMESTAMP_DESCRIPTOR_ID);
+        read_size = size = buf[2] + 3;
+    })
+
+    while (size > 0) {
+        uint64_t mpu_seq_num;
+        int64_t  mpu_presentation_time;
+
+        LIMIT_READ((32 + 64) / 8, {
+            mpu_seq_num           = AV_RB32(buf);
+            mpu_presentation_time = ff_parse_ntp_time(AV_RB64(buf + 4)) - NTP_OFFSET_US;
+        })
+
+        do {
+            struct MPUTimestampDescriptor *desc;
+            size_t                        i;
+
+            if (mpu_seq_num < streams->last_sequence_number) break;
+
+            for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                if (streams->timestamp_descriptor[i].sequence_number == mpu_seq_num) {
+                    desc = streams->timestamp_descriptor + i;
+                    goto end2;
+                }
+
+            for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                if (streams->timestamp_descriptor[i].sequence_number < 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].sequence_number, mpu_seq_num) > max_dist) {
+                        desc     = streams->timestamp_descriptor + i;
+                        max_dist = DIFF(streams->timestamp_descriptor[i].sequence_number, 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->sequence_number   = mpu_seq_num;
+            end2:
+            desc->presentation_time = mpu_presentation_time;
+        } while (0);
+    }
+
+    return read_size;
+}
+
+static inline ssize_t
+parse_mpu_extended_timestamp_descriptor(struct Streams *streams, const uint8_t *buf, uint32_t size) {
+    uint32_t read_size;
+
+    uint8_t  pts_offset_type;
+    bool     timescale_flag;
+    uint16_t default_pts_offset = 0;
+
+    AVStream                              *stream = streams->stream;
+    struct MPUExtendedTimestampDescriptor *desc;
+
+    LIMIT_READ((16 + 8 + 5 + 2 + 1) / 8, {
+        av_assert1(AV_RB16(buf) == MPU_EXTENDED_TIMESTAMP_DESCRIPTOR_ID);
+        read_size       = size = buf[2] + 3;
+        pts_offset_type = (buf[3] >> 1) & 0b11;
+        timescale_flag  = buf[3] & 1;
+    })
+
+    if (timescale_flag) {
+        LIMIT_READ(4, {
+            stream->time_base.num = 1;
+            stream->time_base.den = AV_RB32(buf);
+        })
+    }
+
+    if (pts_offset_type == 1) {
+        LIMIT_READ(2, default_pts_offset = AV_RB16(buf))
+    }
+
+    while (size > 0) {
+        size_t  i;
+        uint8_t num_of_au;
+
+        if (pts_offset_type == 0)
+            return AVERROR_PATCHWELCOME;
+
+        desc = NULL;
+        LIMIT_READ((32 + 2 + 6 + 16 + 8) / 8, do {
+            const uint64_t mpu_seq_num = AV_RB32(buf);
+            num_of_au                  = buf[7];
+
+            if (mpu_seq_num < streams->last_sequence_number) break;
+
+            for (i = 0; i < streams->num_ext_timestamp_descriptors; ++i)
+                if (streams->ext_timestamp_descriptor[i].sequence_number == 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].sequence_number < 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].sequence_number, mpu_seq_num) > max_diff) {
+                        desc     = streams->ext_timestamp_descriptor + i;
+                        max_diff = DIFF(streams->ext_timestamp_descriptor[i].sequence_number, 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->sequence_number      = mpu_seq_num;
+            end2:
+            desc->decoding_time_offset = AV_RB16(buf + 5);
+            desc->num_of_au            = num_of_au;
+        } while (0))
+
+        for (i = 0; i < num_of_au; ++i) {
+            LIMIT_READ(2, if (desc != NULL) desc->au[i].dts_pts_offset = AV_RB16(buf))
+            if (pts_offset_type == 2) {
+                LIMIT_READ(2, if (desc != NULL) desc->au[i].pts_offset = AV_RB16(buf))
+            } else if (desc != NULL) {
+                desc->au[i].pts_offset = default_pts_offset;
+            }
+        }
+    }
+
+    return read_size;
+}
+
+static int parse_additional_arib_subtitle_info(AVStream *stream, const uint8_t *buf, uint32_t size) {
+    bool    start_mpu_sequence_number_flag;
+    char    language_code[4];
+    uint8_t subtitle_format;
+
+    LIMIT_READ((8 + 4 + 1 + 3 + 24 + 2 + 4 + 2 + 4 + 4 + 4 + 4) / 8, {
+        start_mpu_sequence_number_flag = buf[1] >> 3;
+        memcpy(language_code, buf + 2, 3);
+        language_code[3] = '\0';
+        subtitle_format  = (buf[5] >> 2) & 0b1111;
+    })
+
+    if (start_mpu_sequence_number_flag) {
+        LIMIT_READ(32 / 8,);
+    }
+
+    switch (subtitle_format) {
+    case 0b0000:
+        stream->codecpar->codec_id = AV_CODEC_ID_TTML;
+        break;
+    }
+
+    av_dict_set(&stream->metadata, "language", language_code, 0);
+
+    return 0;
+}
+
+static ssize_t parse_mh_data_component_descriptor(AVStream *stream, const uint8_t *buf, uint32_t size) {
+    uint32_t read_size;
+    int      err;
+    uint16_t data_component_id;
+
+    LIMIT_READ((16 + 8 + 16) / 8, {
+        av_assert1(AV_RB16(buf) == MH_DATA_COMPONENT_DESCRIPTOR_ID);
+        read_size         = size = buf[2] + 3;
+        data_component_id = AV_RB16(buf + 3);
+    })
+
+    switch (data_component_id) {
+    case 0x0020: // additional ARIB subtitle info (Table 7-74, ARIB STD-B60, Version 1.14-E1)
+        err = parse_additional_arib_subtitle_info(stream, buf, size);
+        if (err < 0) return err;
+        break;
+    }
+
+    return read_size;
+}
+
+static ssize_t parse_descriptor(struct Streams *streams, const uint8_t *buf, uint32_t size) {
+    if (size < 3) return AVERROR_INVALIDDATA;
+    switch (AV_RB16(buf)) {
+    case MPU_TIMESTAMP_DESCRIPTOR:
+        return parse_mpu_timestamp_descriptor(streams, buf, size);
+    case VIDEO_COMPONENT_DESCRIPTOR:
+        return parse_video_component_descriptor(streams->stream, buf, size);
+    case MH_STREAM_IDENTIFIER_DESCRIPTOR:
+        return buf[2] + 3;
+    case MH_AUDIO_COMPONENT_DESCRIPTOR:
+        return parse_mh_audio_component_descriptor(streams->stream, buf, size);
+    case MH_DATA_COMPONENT_DESCRIPTOR:
+        return parse_mh_data_component_descriptor(streams->stream, buf, size);
+    case MPU_EXTENDED_TIMESTAMP_DESCRIPTOR:
+        return parse_mpu_extended_timestamp_descriptor(streams, buf, size);
+    }
+    return AVERROR_PATCHWELCOME;
+}
+
+static inline ssize_t parse_mmt_package_table(MMTPContext *ctx, const uint8_t *buf, uint32_t size) {
+    uint8_t  package_id_length;
+    uint16_t descriptors_length;
+    uint8_t  number_of_assets;
+
+    size_t   i, j;
+    ssize_t  read;
+    uint32_t read_size;
+
+    LIMIT_READ((8 + 8 + 16 + 8 + 8) / 8, {
+        av_assert1(buf[0] == MMT_PACKAGE_TABLE_ID);
+        read_size         = size = AV_RB16(buf + 2) + 4;
+        package_id_length = buf[5];
+    })
+
+    LIMIT_READ(package_id_length,)
+    LIMIT_READ(2, descriptors_length = AV_RB16(buf))
+    LIMIT_READ(descriptors_length,)
+    LIMIT_READ(1, number_of_assets = buf[0])
+
+    for (i = 0; i < number_of_assets; ++i) {
+        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;
+
+        LIMIT_READ((8 + 32 + 8) / 8, asset_id_length = buf[5])
+        LIMIT_READ(asset_id_length,)
+
+        LIMIT_READ(4, asset_type = AV_RL32(buf))
+
+        // skip reserved, asset_clock_relation_flag
+        LIMIT_READ((7 + 1 + 8) / 8, location_count = buf[1])
+
+        for (j = 0; j < location_count; ++j) {
+            if ((read = parse_mmt_general_location_info(&info, buf, size)) < 0)
+                return read;
+            MUST_CONSUME(read)
+        }
+        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
+        }
+
+        LIMIT_READ(2, asset_descriptors_length = AV_RB16(buf))
+        LIMIT_READ(asset_descriptors_length, if (stream != NULL) {
+            for (j = 0; j < asset_descriptors_length;) {
+                if ((read = parse_descriptor(stream, buf + j, size - j)) < 0)
+                    return read;
+                j += read;
+            }
+            if (j != asset_descriptors_length)
+                return AVERROR_INVALIDDATA;
+        })
+    }
+
+    if (size != 0) return AVERROR_INVALIDDATA;
+    return read_size;
+}
+
+static inline ssize_t parse_package_list_table(MMTPContext *ctx, const uint8_t *buf, uint32_t size) {
+    uint8_t num_of_package;
+    uint8_t num_of_ip_delivery;
+
+    size_t   i;
+    ssize_t  read;
+    uint32_t read_size;
+
+    LIMIT_READ((8 + 8 + 16 + 8) / 8, {
+        av_assert1(buf[0] == PACKAGE_LIST_TABLE_ID);
+        read_size      = size = AV_RB16(buf + 2) + 4;
+        num_of_package = buf[4];
+    })
+
+    for (i = 0; i < num_of_package; ++i) {
+        uint8_t                       package_id_length;
+        struct MMTGeneralLocationInfo info;
+
+        LIMIT_READ(1, package_id_length = buf[0])
+        LIMIT_READ(package_id_length,)
+
+        if ((read = parse_mmt_general_location_info(&info, buf, size)) < 0)
+            return read;
+        MUST_CONSUME(read)
+    }
+
+    LIMIT_READ(1, num_of_ip_delivery = buf[0])
+    if (num_of_ip_delivery > 0)
+        return AVERROR_PATCHWELCOME;
+
+    return read_size;
+}
+
+static ssize_t parse_table(MMTPContext *ctx, const uint8_t *buf, uint32_t size) {
+    if (size < 2) return AVERROR_INVALIDDATA;
+    switch (buf[0]) {
+    case MMT_PACKAGE_TABLE_ID:
+        return parse_mmt_package_table(ctx, buf, size);
+    case PACKAGE_LIST_TABLE_ID:
+        return parse_package_list_table(ctx, buf, size);
+    }
+    return size; // TODO
+}
+
+enum {
+    PA_MESSAGE_ID = 0x0000,
+};
+
+static inline int parse_pa_message(MMTPContext *ctx, const uint8_t *buf, uint32_t size) {
+    uint8_t number_of_tables;
+    ssize_t read;
+    size_t  i;
+
+    LIMIT_READ((16 + 8 + 32 + 8) / 8, {
+        av_assert1(AV_RB16(buf) == PA_MESSAGE_ID);
+        if (AV_RB32(buf + 3) != size - 7) return AVERROR_INVALIDDATA;
+        number_of_tables = buf[7];
+    })
+
+    for (i = 0; i < number_of_tables; ++i) {
+        LIMIT_READ((8 + 8 + 16) / 8,)
+    }
+
+    while (size > 0) {
+        if ((read = parse_table(ctx, buf, size)) < 0) return (int) read;
+        MUST_CONSUME(read)
+    }
+
+    return 0;
+}
+
+static int parse_signalling_message(MMTPContext *ctx, const uint8_t *buf, uint32_t size) {
+    if (size < 3) return AVERROR_INVALIDDATA;
+    switch (AV_RB16(buf)) {
+    case PA_MESSAGE_ID:
+        return parse_pa_message(ctx, buf, size);
+    }
+    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;
+};
+
+inline 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 inline 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 *, const uint8_t *, uint32_t),
+    MMTPContext *opaque) {
+    int err;
+
+    if (indicator == NOT_FRAGMENTED) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = NOT_STARTED;
+        return parser(opaque, data, size);
+    }
+
+    if (indicator == FIRST_FRAGMENT) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = IN_FRAGMENT;
+        return append_data(ctx, data, size);
+    }
+
+    if (indicator == 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);
+    }
+
+    if (indicator == 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;
+        err      = parser(opaque, ctx->data, ctx->size);
+        ctx->size  = 0;
+        ctx->state = NOT_STARTED;
+        return err;
+    }
+
+    return AVERROR_OPTION_NOT_FOUND;
+}
+
+static inline 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 inline int parse_signalling_messages(
+    MMTPContext *ctx, uint32_t seq_num, const uint8_t *buf, uint16_t size) {
+    int                         err;
+    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);
+
+    LIMIT_READ(2, {
+        fragmentation_indicator = buf[0] >> 6;
+        length_extension_flag   = (buf[0] >> 1) & 1;
+        aggregation_flag        = buf[0] & 1;
+    })
+
+    if ((err = check_state(ctx, assembler, seq_num)) < 0)
+        return err;
+
+    if (!aggregation_flag)
+        return assemble_fragment(assembler, seq_num, fragmentation_indicator, buf, size, parse_signalling_message, ctx);
+
+    if (fragmentation_indicator != NOT_FRAGMENTED)
+        return AVERROR_INVALIDDATA; // cannot be both fragmented and aggregated
+
+    while (size > 0) {
+        uint32_t length;
+
+        if (length_extension_flag) {
+            LIMIT_READ(4, length = AV_RB32(buf))
+        } else {
+            LIMIT_READ(2, length = AV_RB16(buf))
+        }
+        LIMIT_READ(length, if ((err = parse_signalling_message(ctx, buf, length)) < 0) return err)
+    }
+
+    return 0;
+}
+
+static inline 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].sequence_number == 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].sequence_number == 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 inline int emit_closed_caption_mfu(MMTPContext *ctx, struct Streams *st, const uint8_t *buf, uint32_t size) {
+    uint8_t  data_type, subsample_number, last_subsample_number;
+    uint32_t data_size;
+    size_t   i;
+    int      err;
+    bool     length_ext_flag, subsample_info_list_flag;
+
+    av_assert0(ctx->pkt != NULL);
+
+    LIMIT_READ((8 + 8 + 8 + 8 + 4 + 1 + 1 + 2) / 8, {
+        subsample_number         = buf[2];
+        last_subsample_number    = buf[3];
+        data_type                = buf[4] >> 4;
+        length_ext_flag          = (buf[4] >> 3) & 1;
+        subsample_info_list_flag = (buf[4] >> 2) & 1;
+    });
+
+    if (data_type != 0b0000) return AVERROR_PATCHWELCOME;
+
+    if (length_ext_flag) {
+        LIMIT_READ(4, data_size = AV_RB32(buf));
+    } else {
+        LIMIT_READ(2, data_size = AV_RB16(buf));
+    }
+
+    if (subsample_number == 0 && last_subsample_number > 0 && subsample_info_list_flag) {
+        for (i = 0; i < last_subsample_number; ++i) {
+            LIMIT_READ((4 + 4) / 8,);
+            if (length_ext_flag) {
+                LIMIT_READ(4,);
+            } else {
+                LIMIT_READ(2,);
+            }
+        }
+    }
+
+    if (size < data_size) return AVERROR_INVALIDDATA;
+    if ((err = av_new_packet(ctx->pkt, data_size)) < 0) return err;
+    memcpy(ctx->pkt->data, buf, 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, const uint8_t *buf, uint32_t size) {
+    int            err;
+    AVBufferRef    *buf_ref;
+    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:
+        LIMIT_READ(4, if (AV_RB32(buf) != size - 4) return AVERROR_INVALIDDATA)
+        if (size < 1) return AVERROR_INVALIDDATA; // we expect to extract NAL unit header type below
+        if ((buf[0] >> 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);
+        memcpy(st->pending_buffer->data + old_size + 4, buf, size);
+        if (((buf[0] >> 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);
+        }
+        break;
+    case AV_CODEC_ID_AAC_LATM:
+        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;
+        memcpy(buf_ref->data + 3, buf, 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, buf, size);
+    default:
+        return AVERROR_PATCHWELCOME;
+    }
+
+    return 0;
+}
+
+static inline int parse_mfu_timed_data(
+    MMTPContext *ctx, struct FragmentAssembler *assembler,
+    uint32_t seq_num, enum FragmentationIndicator indicator,
+    const uint8_t *buf, uint16_t size) {
+    LIMIT_READ((32 + 32 + 32 + 8 + 8) / 8,)
+    return assemble_fragment(assembler, seq_num, indicator, buf, size, consume_mfu, ctx);
+}
+
+static inline int parse_mfu_non_timed_data(
+    MMTPContext *ctx, struct FragmentAssembler *assembler,
+    uint32_t seq_num, enum FragmentationIndicator indicator,
+    const uint8_t *buf, uint16_t size) {
+    LIMIT_READ(32 / 8,)
+    return assemble_fragment(assembler, seq_num, indicator, buf, size, consume_mfu, ctx);
+}
+
+static inline int parse_mpu(MMTPContext *ctx, uint32_t seq_num, const uint8_t *buf, uint16_t size) {
+    int                         err;
+    uint8_t                     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) return 0;
+    if (streams->stream->discard >= AVDISCARD_ALL)
+        return 0;
+
+    assembler = find_or_allocate_assembler(ctx, ctx->current_pid);
+    if (assembler == NULL) return AVERROR(errno);
+
+    LIMIT_READ((16 + 4 + 1 + 2 + 1 + 8 + 32) / 8, {
+        if (AV_RB16(buf) != size - 2)
+            return AVERROR_INVALIDDATA;
+
+        fragment_type           = buf[2] >> 4;
+        timed_flag              = (buf[2] >> 3) & 1;
+        fragmentation_indicator = (buf[2] >> 1) & 0b11;
+        aggregation_flag        = buf[2] & 1;
+
+        mpu_sequence_number = AV_RB32(buf + 4);
+    })
+
+    if (aggregation_flag && fragmentation_indicator != NOT_FRAGMENTED)
+        return AVERROR_INVALIDDATA; // cannot be both fragmented and aggregated
+
+    if (fragment_type != 2) return 0;
+
+    if (assembler->state == INIT && !ctx->is_rap) {
+        // wait for the first RAP
+        return FFERROR_REDO;
+    }
+
+    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 (size > 0) {
+                LIMIT_READ(2, length = AV_RB16(buf))
+                LIMIT_READ(length, {
+                    err = parse_mfu_timed_data(ctx, assembler, seq_num, NOT_FRAGMENTED, buf, length);
+                    if (err < 0) return err;
+                })
+            }
+        } else {
+            return parse_mfu_timed_data(ctx, assembler, seq_num, fragmentation_indicator, buf, size);
+        }
+    } else {
+        if (aggregation_flag) {
+            while (size > 0) {
+                LIMIT_READ(2, length = AV_RB16(buf))
+                LIMIT_READ(length, {
+                    err = parse_mfu_non_timed_data(ctx, assembler, seq_num, NOT_FRAGMENTED, buf, length);
+                    if (err < 0) return err;
+                })
+            }
+        } else {
+            return parse_mfu_non_timed_data(ctx, assembler, seq_num, fragmentation_indicator, buf, size);
+        }
+    }
+
+    return 0;
+}
+
+MMTPContext *avpriv_mmtp_parse_open(AVProgram *program) {
+    MMTPContext *ctx = av_mallocz(sizeof(MMTPContext));
+    if (ctx == NULL) return NULL;
+    ctx->program = program;
+    return ctx;
+}
+
+int avpriv_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;
+    int      err = 0;
+
+    ctx->s   = s;
+    ctx->pkt = pkt;
+
+    LIMIT_READ((2 + 1 + 2 + 1 + 1 + 1 + 2 + 6 + 16 + 32 + 32) / 8, {
+        packet_counter_flag    = (buf[0] >> 5) & 1;
+        extension_header_flag  = (buf[0] >> 1) & 1;
+        ctx->is_rap            = buf[0] & 1;
+        payload_type           = buf[1] & 0b111111;
+        ctx->current_pid       = AV_RB16(buf + 2);
+        packet_sequence_number = AV_RB32(buf + 8);
+    })
+
+    if (packet_counter_flag) {
+        LIMIT_READ(4,)
+    }
+
+    if (extension_header_flag) {
+        uint16_t extension_header_length;
+        LIMIT_READ(4, extension_header_length = AV_RB16(buf + 2))
+        LIMIT_READ(extension_header_length,)
+    }
+
+    switch (payload_type) {
+    case 0x00: // MPU
+        if (pkt != NULL)
+            err = parse_mpu(ctx, packet_sequence_number, buf, size);
+        break;
+    case 0x02: // signalling messages
+        err = parse_signalling_messages(ctx, packet_sequence_number, buf, size);
+        break;
+    }
+    if (err < 0) return err;
+    return ctx->pkt == NULL ? 0 : FFERROR_REDO;
+}
+
+void avpriv_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 avpriv_mmtp_parse_close(MMTPContext *ctx) {
+    struct FragmentAssembler *ass;
+    struct Streams           *streams;
+
+    for (ass = ctx->assembler; ass != NULL;) {
+        struct FragmentAssembler *next = ass->next;
+        if (ass->data != NULL)
+            av_free(ass->data);
+        av_free(ass);
+        ass = next;
+    }
+
+    for (streams = ctx->streams; streams != NULL;) {
+        struct Streams *next = streams->next;
+        if (streams->timestamp_descriptor != NULL)
+            av_free(streams->timestamp_descriptor);
+        if (streams->ext_timestamp_descriptor != NULL)
+            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..ff00115796
--- /dev/null
+++ b/libavformat/mmtp.h
@@ -0,0 +1,61 @@
+/*
+ * 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 *avpriv_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
+ * @param size
+ * @return 0 if a new AVPacket is emitted, FFERROR_REDO if the next packet is needed, or another negative value on error.
+ */
+int avpriv_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 avpriv_mmtp_reset_state(MMTPContext *ctx);
+
+/**
+ * Close an MMT protocol parser context, frees all associated resources.
+ *
+ * @param ctx The MMT protocol parser context.
+ */
+void avpriv_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..c0b25df7af
--- /dev/null
+++ b/libavformat/mmttlv.c
@@ -0,0 +1,324 @@
+/*
+ * 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/intreadwrite.h"
+#include "libavutil/avassert.h"
+#include "libavutil/internal.h"
+#include "avio_internal.h"
+#include "avformat.h"
+#include "mmtp.h"
+#include "demux.h"
+#include "internal.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 = avpriv_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 avpriv_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)
+            avpriv_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 FFERROR_REDO;
+    }
+
+    if (ctx->cap < size) {
+        if (ctx->buf != NULL)
+            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 && err != FFERROR_REDO)
+            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;
+        avpriv_mmtp_parse_close(program->mmtp);
+        av_free(program);
+        program = next;
+    }
+    if (priv->buf != NULL) av_free(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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-28 17:31 [FFmpeg-devel] [PATCH] avformat: add MMTP parser and MMT/TLV demuxer SuperFashi
@ 2023-04-28 20:44 ` Michael Niedermayer
  2023-04-29  5:44 ` [FFmpeg-devel] [PATCH v2] " SuperFashi
  1 sibling, 0 replies; 17+ messages in thread
From: Michael Niedermayer @ 2023-04-28 20:44 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 1963 bytes --]

On Sat, Apr 29, 2023 at 02:31:28AM +0900, SuperFashi wrote:
> 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 (loas), 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 filesystem format defined alongside the protocol. One of the 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>


fails to build

libavformat/mmtp.c: In function ‘parse_video_component_descriptor’:
libavformat/mmtp.c:216:36: error: ‘VIDEO_COMPONENT_DESCRIPTOR_ID’ undeclared (first use in this function); did you mean ‘VIDEO_COMPONENT_DESCRIPTOR’?
         av_assert1(AV_RB16(buf) == VIDEO_COMPONENT_DESCRIPTOR_ID);
                                    ^
libavformat/mmtp.c:33:5: note: in definition of macro ‘LIMIT_READ’
     block; \
     ^~~~~
[...]


> +#include <stdbool.h>
> +

> +#define LIMIT_READ(consume, block) \
> +    if (size < (consume)) return AVERROR_INVALIDDATA; \
> +    block; \
> +    buf += (consume); \
> +    size -= (consume);

This makes the code hard to debug and read


> +
> +#define MUST_CONSUME(consume) \
> +    av_assert1((consume) <= size); \
> +    buf += (consume); \
> +    size -= (consume);

this could maybe use some bytestream reader
either way a function should be cleaner than a macro

thx

[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

What does censorship reveal? It reveals fear. -- Julian Assange

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [FFmpeg-devel] [PATCH v2] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-28 17:31 [FFmpeg-devel] [PATCH] avformat: add MMTP parser and MMT/TLV demuxer SuperFashi
  2023-04-28 20:44 ` Michael Niedermayer
@ 2023-04-29  5:44 ` SuperFashi
  2023-04-29  5:49   ` [FFmpeg-devel] [PATCH v3] " SuperFashi
  2023-04-29 11:38   ` [FFmpeg-devel] [PATCH v2] " Jean-Baptiste Kempf
  1 sibling, 2 replies; 17+ messages in thread
From: SuperFashi @ 2023-04-29  5:44 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

v0 -> v1: Refactor using GetByteContext; Fix compile error.

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 filesystem 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       | 1375 ++++++++++++++++++++++++++++++++++++++
 libavformat/mmtp.h       |   61 ++
 libavformat/mmttlv.c     |  324 +++++++++
 libavformat/version.h    |    2 +-
 8 files changed, 1768 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 b6f6682904..2483fdd547 100644
--- a/Changelog
+++ b/Changelog
@@ -6,6 +6,7 @@ version <next>:
 - Playdate video decoder and demuxer
 - Extend VAAPI support for libva-win32 on Windows
 - afireqsrc audio source 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..a2f98e39f6
--- /dev/null
+++ b/libavformat/mmtp.c
@@ -0,0 +1,1375 @@
+/*
+ * 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 "libavutil/mem.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavcodec/bytestream.h"
+#include "network.h"
+#include "mmtp.h"
+#include "internal.h"
+#include "demux.h"
+
+#include <stdbool.h>
+
+#define AVERROR_INVALIDDATA (abort(), 0)
+
+#define ENSURE_BS_LEFT(bs, size) if (bytestream2_get_bytes_left(bs) < (size)) return AVERROR_INVALIDDATA
+
+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;
+            in_port_t      dst_port;
+            uint16_t       packet_id;
+        } type1;
+        struct {
+            struct in6_addr ipv6_src_addr;
+            struct in6_addr ipv6_dst_addr;
+            in_port_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;
+            in_port_t       dst_port;
+            uint16_t        MPEG_2_PID: 13;
+        } type4;
+        struct {
+            char URL[0x100 + 1];
+        } type5;
+    };
+};
+
+static inline int parse_mmt_general_location_info(struct MMTGeneralLocationInfo *info, GetByteContext *gbc) {
+    uint8_t url_size;
+
+    ENSURE_BS_LEFT(gbc, 1);
+    switch (info->location_type = bytestream2_get_byteu(gbc)) {
+    case 0x00:
+        ENSURE_BS_LEFT(gbc, 2);
+        info->type0.packet_id = bytestream2_get_be16u(gbc);
+        break;
+    case 0x01:
+        ENSURE_BS_LEFT(gbc, (32 + 32 + 16 + 16) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (128 + 128 + 16 + 16) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (16 + 16 + 3 + 13) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (128 + 128 + 16 + 3 + 13) / 8);
+        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 sequence_number;
+        int64_t  presentation_time;
+    } *timestamp_descriptor;
+
+    int num_ext_timestamp_descriptors;
+    struct MPUExtendedTimestampDescriptor {
+        uint32_t sequence_number;
+        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 inline 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 inline 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,
+    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 inline int parse_video_component_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+    uint8_t language_code[4];
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 4 + 4 + 1 + 2 + 5 + 16 + 4 + 4 + 24) / 8);
+    if (bytestream2_get_be16u(gbc) != VIDEO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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);
+
+        ENSURE_BS_LEFT(&ngbc, 3);
+        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 inline 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];
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 4 + 4 + 8 + 16 + 8 + 8 + 1 + 1 + 2 + 3 + 1 + 24) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_AUDIO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        uint8_t byte;
+        bool ES_multi_lingual_flag;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+
+        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;
+
+        ENSURE_BS_LEFT(&ngbc, 3);
+        bytestream2_get_bufferu(&ngbc, language_code, 3);
+        language_code[3] = '\0';
+
+        if (ES_multi_lingual_flag) {
+            ENSURE_BS_LEFT(&ngbc, 3);
+            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 inline int parse_mpu_timestamp_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8) / 8);
+
+    if (bytestream2_get_be16u(gbc) != MPU_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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;
+
+            ENSURE_BS_LEFT(&ngbc, (32 + 64) / 8);
+            mpu_seq_num           = bytestream2_get_be32u(&ngbc);
+            mpu_presentation_time = ff_parse_ntp_time(bytestream2_get_be64u(&ngbc)) - NTP_OFFSET_US;
+
+            do {
+                struct MPUTimestampDescriptor *desc;
+                size_t                        i;
+
+                if (mpu_seq_num < streams->last_sequence_number) break;
+
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].sequence_number == mpu_seq_num) {
+                        desc = streams->timestamp_descriptor + i;
+                        goto end2;
+                    }
+
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].sequence_number < 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].sequence_number, mpu_seq_num) > max_dist) {
+                            desc     = streams->timestamp_descriptor + i;
+                            max_dist = DIFF(streams->timestamp_descriptor[i].sequence_number, 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->sequence_number   = mpu_seq_num;
+                end2:
+                desc->presentation_time = mpu_presentation_time;
+            } while (0);
+        }
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static inline int parse_mpu_extended_timestamp_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    AVStream *stream = streams->stream;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 5 + 2 + 1) / 8);
+    if (bytestream2_get_be16u(gbc) != MPU_EXTENDED_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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);
+
+        ENSURE_BS_LEFT(&ngbc, (5 + 2 + 1) / 8);
+        byte            = bytestream2_get_byte(&ngbc);
+        pts_offset_type = (byte >> 1) & 0b11;
+        timescale_flag  = byte & 1;
+
+        if (timescale_flag) {
+            ENSURE_BS_LEFT(&ngbc, 4);
+            stream->time_base.num = 1;
+            stream->time_base.den = bytestream2_get_be32u(&ngbc);
+        }
+
+        if (pts_offset_type == 1) {
+            ENSURE_BS_LEFT(&ngbc, 2);
+            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
+
+            ENSURE_BS_LEFT(&ngbc, (32 + 2 + 6 + 16 + 8) / 8);
+            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].sequence_number == 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].sequence_number < 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].sequence_number, mpu_seq_num) > max_diff) {
+                            desc     = streams->ext_timestamp_descriptor + i;
+                            max_diff = DIFF(streams->ext_timestamp_descriptor[i].sequence_number, 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->sequence_number      = 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) {
+                ENSURE_BS_LEFT(&ngbc, 2);
+                if (desc != NULL)
+                    desc->au[i].dts_pts_offset = bytestream2_get_be16u(&ngbc);
+                else
+                    bytestream2_skipu(&ngbc, 2);
+
+                if (pts_offset_type == 2) {
+                    ENSURE_BS_LEFT(&ngbc, 2);
+                    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 inline int parse_additional_arib_subtitle_info(AVStream *stream, GetByteContext *gbc) {
+    bool    start_mpu_sequence_number_flag;
+    char    language_code[4];
+    uint8_t subtitle_format;
+
+    ENSURE_BS_LEFT(gbc, (8 + 4 + 1 + 3 + 24 + 2 + 4 + 2 + 4 + 4 + 4 + 4) / 8);
+    // 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 inline int parse_mh_data_component_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 16) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_DATA_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+        bytestream2_skipu(gbc, descriptor_length);
+
+        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 inline int parse_stream_identifier_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 16) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_STREAM_IDENTIFIER_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        // no need for now
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static int parse_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    ENSURE_BS_LEFT(gbc, 3);
+    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);
+    }
+    av_log(streams->stream, AV_LOG_INFO, "Unknown descriptor: 0x%04x\n", bytestream2_peek_be16u(gbc));
+    return AVERROR_PATCHWELCOME;
+}
+
+static inline int parse_mmt_package_table(MMTPContext *ctx, GetByteContext *gbc) {
+    uint16_t length;
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 16 + 6 + 2 + 8) / 8);
+    if (bytestream2_get_byteu(gbc) != MMT_PACKAGE_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        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);
+
+        // 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);
+
+        ENSURE_BS_LEFT(&ngbc, 1);
+        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;
+
+            ENSURE_BS_LEFT(&ngbc, (8 + 32 + 8) / 8);
+            /*
+             * 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);
+
+            ENSURE_BS_LEFT(&ngbc, 1);
+            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
+            }
+
+            ENSURE_BS_LEFT(&ngbc, 2);
+            asset_descriptors_length = bytestream2_get_be16u(&ngbc);
+            ENSURE_BS_LEFT(&ngbc, asset_descriptors_length);
+            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 inline int parse_package_list_table(MMTPContext *ctx, GetByteContext *gbc) {
+    size_t   i;
+    uint32_t length;
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 16 + 8) / 8);
+    if (bytestream2_get_byteu(gbc) != PACKAGE_LIST_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        int     err;
+        uint8_t num_of_package;
+        uint8_t num_of_ip_delivery;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        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;
+        }
+
+        ENSURE_BS_LEFT(&ngbc, 1);
+        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) {
+    ENSURE_BS_LEFT(gbc, 2);
+    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 inline int parse_pa_message(MMTPContext *ctx, GetByteContext *gbc) {
+    uint32_t length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 32 + 8) / 8);
+    if (bytestream2_get_be16u(gbc) != PA_MESSAGE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be32u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        size_t  i;
+        uint8_t num_of_tables;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        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) {
+    ENSURE_BS_LEFT(gbc, 4);
+    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;
+};
+
+inline 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 inline 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;
+
+    if (indicator == NOT_FRAGMENTED) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = NOT_STARTED;
+        bytestream2_init(&gbc, data, size);
+        return parser(opaque, &gbc);
+    }
+
+    if (indicator == FIRST_FRAGMENT) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = IN_FRAGMENT;
+        return append_data(ctx, data, size);
+    }
+
+    if (indicator == 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);
+    }
+
+    if (indicator == 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;
+    }
+
+    return AVERROR_OPTION_NOT_FOUND;
+}
+
+static inline 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 inline 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);
+
+    ENSURE_BS_LEFT(gbc, (2 + 4 + 1 + 1 + 8) / 8);
+    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);
+
+        ENSURE_BS_LEFT(gbc, length);
+        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 inline 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].sequence_number == 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].sequence_number == 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 inline 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);
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 8 + 8 + 4 + 1 + 1 + 2) / 8);
+
+    /*
+     * 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);
+            }
+        }
+    }
+
+    ENSURE_BS_LEFT(gbc, data_size);
+    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) return AVERROR_INVALIDDATA; // we expect to extract NAL unit header type below
+        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);
+        }
+        break;
+    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;
+    }
+
+    return 0;
+}
+
+static inline 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 inline 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 inline 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) return 0;
+    if (streams->stream->discard >= AVDISCARD_ALL)
+        return 0;
+
+    assembler = find_or_allocate_assembler(ctx, ctx->current_pid);
+    if (assembler == NULL) return AVERROR(errno);
+
+    ENSURE_BS_LEFT(gbc, (16 + 4 + 1 + 2 + 1 + 8 + 32) / 8);
+
+    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;
+
+    if (assembler->state == INIT && !ctx->is_rap) {
+        // wait for the first RAP
+        return FFERROR_REDO;
+    }
+
+    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);
+                ENSURE_BS_LEFT(gbc, length);
+                {
+                    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);
+                ENSURE_BS_LEFT(gbc, length);
+                {
+                    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 *avpriv_mmtp_parse_open(AVProgram *program) {
+    MMTPContext *ctx = av_mallocz(sizeof(MMTPContext));
+    if (ctx == NULL) return NULL;
+    ctx->program = program;
+    return ctx;
+}
+
+int avpriv_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);
+    ENSURE_BS_LEFT(&gbc, (2 + 1 + 2 + 1 + 1 + 1 + 2 + 6 + 16 + 32 + 32) / 8);
+
+    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 avpriv_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 avpriv_mmtp_parse_close(MMTPContext *ctx) {
+    struct FragmentAssembler *ass;
+    struct Streams           *streams;
+
+    for (ass = ctx->assembler; ass != NULL;) {
+        struct FragmentAssembler *next = ass->next;
+        if (ass->data != NULL)
+            av_free(ass->data);
+        av_free(ass);
+        ass = next;
+    }
+
+    for (streams = ctx->streams; streams != NULL;) {
+        struct Streams *next = streams->next;
+        if (streams->timestamp_descriptor != NULL)
+            av_free(streams->timestamp_descriptor);
+        if (streams->ext_timestamp_descriptor != NULL)
+            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..e4e24c6069
--- /dev/null
+++ b/libavformat/mmtp.h
@@ -0,0 +1,61 @@
+/*
+ * 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 *avpriv_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 avpriv_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 avpriv_mmtp_reset_state(MMTPContext *ctx);
+
+/**
+ * Close an MMT protocol parser context, frees all associated resources.
+ *
+ * @param ctx The MMT protocol parser context.
+ */
+void avpriv_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..c0b25df7af
--- /dev/null
+++ b/libavformat/mmttlv.c
@@ -0,0 +1,324 @@
+/*
+ * 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/intreadwrite.h"
+#include "libavutil/avassert.h"
+#include "libavutil/internal.h"
+#include "avio_internal.h"
+#include "avformat.h"
+#include "mmtp.h"
+#include "demux.h"
+#include "internal.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 = avpriv_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 avpriv_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)
+            avpriv_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 FFERROR_REDO;
+    }
+
+    if (ctx->cap < size) {
+        if (ctx->buf != NULL)
+            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 && err != FFERROR_REDO)
+            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;
+        avpriv_mmtp_parse_close(program->mmtp);
+        av_free(program);
+        program = next;
+    }
+    if (priv->buf != NULL) av_free(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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [FFmpeg-devel] [PATCH v3] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29  5:44 ` [FFmpeg-devel] [PATCH v2] " SuperFashi
@ 2023-04-29  5:49   ` SuperFashi
  2023-04-29  5:53     ` [FFmpeg-devel] [PATCH v4] " SuperFashi
  2023-04-29 11:38   ` [FFmpeg-devel] [PATCH v2] " Jean-Baptiste Kempf
  1 sibling, 1 reply; 17+ messages in thread
From: SuperFashi @ 2023-04-29  5:49 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

v0 -> v1: Refactor using GetByteContext; Fix compile error.
v1 -> v2: Remove debug statement.

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 filesystem 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>
---
 libavformat/mmtp.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/libavformat/mmtp.c b/libavformat/mmtp.c
index a2f98e39f6..ba1fcab281 100644
--- a/libavformat/mmtp.c
+++ b/libavformat/mmtp.c
@@ -29,8 +29,6 @@
 
 #include <stdbool.h>
 
-#define AVERROR_INVALIDDATA (abort(), 0)
-
 #define ENSURE_BS_LEFT(bs, size) if (bytestream2_get_bytes_left(bs) < (size)) return AVERROR_INVALIDDATA
 
 struct MMTGeneralLocationInfo {
@@ -115,7 +113,6 @@ static inline int parse_mmt_general_location_info(struct MMTGeneralLocationInfo
     return 0;
 }
 
-
 struct Streams {
     AVStream *stream;
 
@@ -155,7 +152,7 @@ struct MMTPContext {
     AVFormatContext *s;
     AVPacket        *pkt;
     uint16_t        current_pid;
-    bool is_rap;
+    bool            is_rap;
 };
 
 static inline struct Streams *find_current_stream(struct MMTPContext *ctx) {
-- 
2.25.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29  5:49   ` [FFmpeg-devel] [PATCH v3] " SuperFashi
@ 2023-04-29  5:53     ` SuperFashi
  2023-04-29 18:05       ` Michael Niedermayer
  2023-05-01 11:01       ` [FFmpeg-devel] [PATCH v5] " SuperFashi
  0 siblings, 2 replies; 17+ messages in thread
From: SuperFashi @ 2023-04-29  5:53 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

v1 -> v2: Refactor using GetByteContext; Fix compile error.
v2 -> v3: Remove debug statement.
v3 -> v4: Squash commits (sorry, first time git patch user :(

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 filesystem 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       | 1372 ++++++++++++++++++++++++++++++++++++++
 libavformat/mmtp.h       |   61 ++
 libavformat/mmttlv.c     |  324 +++++++++
 libavformat/version.h    |    2 +-
 8 files changed, 1765 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 b6f6682904..2483fdd547 100644
--- a/Changelog
+++ b/Changelog
@@ -6,6 +6,7 @@ version <next>:
 - Playdate video decoder and demuxer
 - Extend VAAPI support for libva-win32 on Windows
 - afireqsrc audio source 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..ba1fcab281
--- /dev/null
+++ b/libavformat/mmtp.c
@@ -0,0 +1,1372 @@
+/*
+ * 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 "libavutil/mem.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavcodec/bytestream.h"
+#include "network.h"
+#include "mmtp.h"
+#include "internal.h"
+#include "demux.h"
+
+#include <stdbool.h>
+
+#define ENSURE_BS_LEFT(bs, size) if (bytestream2_get_bytes_left(bs) < (size)) return AVERROR_INVALIDDATA
+
+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;
+            in_port_t      dst_port;
+            uint16_t       packet_id;
+        } type1;
+        struct {
+            struct in6_addr ipv6_src_addr;
+            struct in6_addr ipv6_dst_addr;
+            in_port_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;
+            in_port_t       dst_port;
+            uint16_t        MPEG_2_PID: 13;
+        } type4;
+        struct {
+            char URL[0x100 + 1];
+        } type5;
+    };
+};
+
+static inline int parse_mmt_general_location_info(struct MMTGeneralLocationInfo *info, GetByteContext *gbc) {
+    uint8_t url_size;
+
+    ENSURE_BS_LEFT(gbc, 1);
+    switch (info->location_type = bytestream2_get_byteu(gbc)) {
+    case 0x00:
+        ENSURE_BS_LEFT(gbc, 2);
+        info->type0.packet_id = bytestream2_get_be16u(gbc);
+        break;
+    case 0x01:
+        ENSURE_BS_LEFT(gbc, (32 + 32 + 16 + 16) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (128 + 128 + 16 + 16) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (16 + 16 + 3 + 13) / 8);
+        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:
+        ENSURE_BS_LEFT(gbc, (128 + 128 + 16 + 3 + 13) / 8);
+        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 sequence_number;
+        int64_t  presentation_time;
+    } *timestamp_descriptor;
+
+    int num_ext_timestamp_descriptors;
+    struct MPUExtendedTimestampDescriptor {
+        uint32_t sequence_number;
+        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 inline 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 inline 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,
+    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 inline int parse_video_component_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+    uint8_t language_code[4];
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 4 + 4 + 1 + 2 + 5 + 16 + 4 + 4 + 24) / 8);
+    if (bytestream2_get_be16u(gbc) != VIDEO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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);
+
+        ENSURE_BS_LEFT(&ngbc, 3);
+        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 inline 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];
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 4 + 4 + 8 + 16 + 8 + 8 + 1 + 1 + 2 + 3 + 1 + 24) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_AUDIO_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        uint8_t byte;
+        bool ES_multi_lingual_flag;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+
+        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;
+
+        ENSURE_BS_LEFT(&ngbc, 3);
+        bytestream2_get_bufferu(&ngbc, language_code, 3);
+        language_code[3] = '\0';
+
+        if (ES_multi_lingual_flag) {
+            ENSURE_BS_LEFT(&ngbc, 3);
+            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 inline int parse_mpu_timestamp_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8) / 8);
+
+    if (bytestream2_get_be16u(gbc) != MPU_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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;
+
+            ENSURE_BS_LEFT(&ngbc, (32 + 64) / 8);
+            mpu_seq_num           = bytestream2_get_be32u(&ngbc);
+            mpu_presentation_time = ff_parse_ntp_time(bytestream2_get_be64u(&ngbc)) - NTP_OFFSET_US;
+
+            do {
+                struct MPUTimestampDescriptor *desc;
+                size_t                        i;
+
+                if (mpu_seq_num < streams->last_sequence_number) break;
+
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].sequence_number == mpu_seq_num) {
+                        desc = streams->timestamp_descriptor + i;
+                        goto end2;
+                    }
+
+                for (i = 0; i < streams->num_timestamp_descriptors; ++i)
+                    if (streams->timestamp_descriptor[i].sequence_number < 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].sequence_number, mpu_seq_num) > max_dist) {
+                            desc     = streams->timestamp_descriptor + i;
+                            max_dist = DIFF(streams->timestamp_descriptor[i].sequence_number, 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->sequence_number   = mpu_seq_num;
+                end2:
+                desc->presentation_time = mpu_presentation_time;
+            } while (0);
+        }
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static inline int parse_mpu_extended_timestamp_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    AVStream *stream = streams->stream;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 5 + 2 + 1) / 8);
+    if (bytestream2_get_be16u(gbc) != MPU_EXTENDED_TIMESTAMP_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        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);
+
+        ENSURE_BS_LEFT(&ngbc, (5 + 2 + 1) / 8);
+        byte            = bytestream2_get_byte(&ngbc);
+        pts_offset_type = (byte >> 1) & 0b11;
+        timescale_flag  = byte & 1;
+
+        if (timescale_flag) {
+            ENSURE_BS_LEFT(&ngbc, 4);
+            stream->time_base.num = 1;
+            stream->time_base.den = bytestream2_get_be32u(&ngbc);
+        }
+
+        if (pts_offset_type == 1) {
+            ENSURE_BS_LEFT(&ngbc, 2);
+            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
+
+            ENSURE_BS_LEFT(&ngbc, (32 + 2 + 6 + 16 + 8) / 8);
+            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].sequence_number == 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].sequence_number < 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].sequence_number, mpu_seq_num) > max_diff) {
+                            desc     = streams->ext_timestamp_descriptor + i;
+                            max_diff = DIFF(streams->ext_timestamp_descriptor[i].sequence_number, 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->sequence_number      = 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) {
+                ENSURE_BS_LEFT(&ngbc, 2);
+                if (desc != NULL)
+                    desc->au[i].dts_pts_offset = bytestream2_get_be16u(&ngbc);
+                else
+                    bytestream2_skipu(&ngbc, 2);
+
+                if (pts_offset_type == 2) {
+                    ENSURE_BS_LEFT(&ngbc, 2);
+                    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 inline int parse_additional_arib_subtitle_info(AVStream *stream, GetByteContext *gbc) {
+    bool    start_mpu_sequence_number_flag;
+    char    language_code[4];
+    uint8_t subtitle_format;
+
+    ENSURE_BS_LEFT(gbc, (8 + 4 + 1 + 3 + 24 + 2 + 4 + 2 + 4 + 4 + 4 + 4) / 8);
+    // 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 inline int parse_mh_data_component_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 16) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_DATA_COMPONENT_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, descriptor_length);
+        bytestream2_skipu(gbc, descriptor_length);
+
+        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 inline int parse_stream_identifier_descriptor(AVStream *stream, GetByteContext *gbc) {
+    uint8_t descriptor_length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 16) / 8);
+    if (bytestream2_get_be16u(gbc) != MH_STREAM_IDENTIFIER_DESCRIPTOR)
+        return AVERROR_INVALIDDATA;
+    descriptor_length = bytestream2_get_byteu(gbc);
+
+    ENSURE_BS_LEFT(gbc, descriptor_length);
+    {
+        // no need for now
+    }
+    bytestream2_skipu(gbc, descriptor_length);
+
+    return 0;
+}
+
+static int parse_descriptor(struct Streams *streams, GetByteContext *gbc) {
+    ENSURE_BS_LEFT(gbc, 3);
+    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);
+    }
+    av_log(streams->stream, AV_LOG_INFO, "Unknown descriptor: 0x%04x\n", bytestream2_peek_be16u(gbc));
+    return AVERROR_PATCHWELCOME;
+}
+
+static inline int parse_mmt_package_table(MMTPContext *ctx, GetByteContext *gbc) {
+    uint16_t length;
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 16 + 6 + 2 + 8) / 8);
+    if (bytestream2_get_byteu(gbc) != MMT_PACKAGE_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        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);
+
+        // 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);
+
+        ENSURE_BS_LEFT(&ngbc, 1);
+        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;
+
+            ENSURE_BS_LEFT(&ngbc, (8 + 32 + 8) / 8);
+            /*
+             * 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);
+
+            ENSURE_BS_LEFT(&ngbc, 1);
+            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
+            }
+
+            ENSURE_BS_LEFT(&ngbc, 2);
+            asset_descriptors_length = bytestream2_get_be16u(&ngbc);
+            ENSURE_BS_LEFT(&ngbc, asset_descriptors_length);
+            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 inline int parse_package_list_table(MMTPContext *ctx, GetByteContext *gbc) {
+    size_t   i;
+    uint32_t length;
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 16 + 8) / 8);
+    if (bytestream2_get_byteu(gbc) != PACKAGE_LIST_TABLE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be16u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        int     err;
+        uint8_t num_of_package;
+        uint8_t num_of_ip_delivery;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        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;
+        }
+
+        ENSURE_BS_LEFT(&ngbc, 1);
+        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) {
+    ENSURE_BS_LEFT(gbc, 2);
+    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 inline int parse_pa_message(MMTPContext *ctx, GetByteContext *gbc) {
+    uint32_t length;
+
+    ENSURE_BS_LEFT(gbc, (16 + 8 + 32 + 8) / 8);
+    if (bytestream2_get_be16u(gbc) != PA_MESSAGE_ID)
+        return AVERROR_INVALIDDATA;
+    // skip: version
+    bytestream2_skipu(gbc, 1);
+    length = bytestream2_get_be32u(gbc);
+
+    ENSURE_BS_LEFT(gbc, length);
+    {
+        size_t  i;
+        uint8_t num_of_tables;
+
+        GetByteContext ngbc;
+        bytestream2_init(&ngbc, gbc->buffer, length);
+
+        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) {
+    ENSURE_BS_LEFT(gbc, 4);
+    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;
+};
+
+inline 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 inline 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;
+
+    if (indicator == NOT_FRAGMENTED) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = NOT_STARTED;
+        bytestream2_init(&gbc, data, size);
+        return parser(opaque, &gbc);
+    }
+
+    if (indicator == FIRST_FRAGMENT) {
+        if (ctx->state == IN_FRAGMENT) return AVERROR_INVALIDDATA;
+        ctx->state = IN_FRAGMENT;
+        return append_data(ctx, data, size);
+    }
+
+    if (indicator == 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);
+    }
+
+    if (indicator == 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;
+    }
+
+    return AVERROR_OPTION_NOT_FOUND;
+}
+
+static inline 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 inline 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);
+
+    ENSURE_BS_LEFT(gbc, (2 + 4 + 1 + 1 + 8) / 8);
+    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);
+
+        ENSURE_BS_LEFT(gbc, length);
+        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 inline 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].sequence_number == 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].sequence_number == 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 inline 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);
+
+    ENSURE_BS_LEFT(gbc, (8 + 8 + 8 + 8 + 4 + 1 + 1 + 2) / 8);
+
+    /*
+     * 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);
+            }
+        }
+    }
+
+    ENSURE_BS_LEFT(gbc, data_size);
+    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) return AVERROR_INVALIDDATA; // we expect to extract NAL unit header type below
+        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);
+        }
+        break;
+    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;
+    }
+
+    return 0;
+}
+
+static inline 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 inline 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 inline 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) return 0;
+    if (streams->stream->discard >= AVDISCARD_ALL)
+        return 0;
+
+    assembler = find_or_allocate_assembler(ctx, ctx->current_pid);
+    if (assembler == NULL) return AVERROR(errno);
+
+    ENSURE_BS_LEFT(gbc, (16 + 4 + 1 + 2 + 1 + 8 + 32) / 8);
+
+    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;
+
+    if (assembler->state == INIT && !ctx->is_rap) {
+        // wait for the first RAP
+        return FFERROR_REDO;
+    }
+
+    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);
+                ENSURE_BS_LEFT(gbc, length);
+                {
+                    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);
+                ENSURE_BS_LEFT(gbc, length);
+                {
+                    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 *avpriv_mmtp_parse_open(AVProgram *program) {
+    MMTPContext *ctx = av_mallocz(sizeof(MMTPContext));
+    if (ctx == NULL) return NULL;
+    ctx->program = program;
+    return ctx;
+}
+
+int avpriv_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);
+    ENSURE_BS_LEFT(&gbc, (2 + 1 + 2 + 1 + 1 + 1 + 2 + 6 + 16 + 32 + 32) / 8);
+
+    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 avpriv_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 avpriv_mmtp_parse_close(MMTPContext *ctx) {
+    struct FragmentAssembler *ass;
+    struct Streams           *streams;
+
+    for (ass = ctx->assembler; ass != NULL;) {
+        struct FragmentAssembler *next = ass->next;
+        if (ass->data != NULL)
+            av_free(ass->data);
+        av_free(ass);
+        ass = next;
+    }
+
+    for (streams = ctx->streams; streams != NULL;) {
+        struct Streams *next = streams->next;
+        if (streams->timestamp_descriptor != NULL)
+            av_free(streams->timestamp_descriptor);
+        if (streams->ext_timestamp_descriptor != NULL)
+            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..e4e24c6069
--- /dev/null
+++ b/libavformat/mmtp.h
@@ -0,0 +1,61 @@
+/*
+ * 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 *avpriv_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 avpriv_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 avpriv_mmtp_reset_state(MMTPContext *ctx);
+
+/**
+ * Close an MMT protocol parser context, frees all associated resources.
+ *
+ * @param ctx The MMT protocol parser context.
+ */
+void avpriv_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..c0b25df7af
--- /dev/null
+++ b/libavformat/mmttlv.c
@@ -0,0 +1,324 @@
+/*
+ * 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/intreadwrite.h"
+#include "libavutil/avassert.h"
+#include "libavutil/internal.h"
+#include "avio_internal.h"
+#include "avformat.h"
+#include "mmtp.h"
+#include "demux.h"
+#include "internal.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 = avpriv_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 avpriv_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)
+            avpriv_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 FFERROR_REDO;
+    }
+
+    if (ctx->cap < size) {
+        if (ctx->buf != NULL)
+            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 && err != FFERROR_REDO)
+            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;
+        avpriv_mmtp_parse_close(program->mmtp);
+        av_free(program);
+        program = next;
+    }
+    if (priv->buf != NULL) av_free(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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29  5:44 ` [FFmpeg-devel] [PATCH v2] " SuperFashi
  2023-04-29  5:49   ` [FFmpeg-devel] [PATCH v3] " SuperFashi
@ 2023-04-29 11:38   ` Jean-Baptiste Kempf
  2023-04-29 12:03     ` SuperFashi
  1 sibling, 1 reply; 17+ messages in thread
From: Jean-Baptiste Kempf @ 2023-04-29 11:38 UTC (permalink / raw)
  To: ffmpeg-devel

On Sat, 29 Apr 2023, at 07:44, SuperFashi wrote:
> +#define AVERROR_INVALIDDATA (abort(), 0)

Why are you aborting?

-- 
Jean-Baptiste Kempf -  President
+33 672 704 734
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29 11:38   ` [FFmpeg-devel] [PATCH v2] " Jean-Baptiste Kempf
@ 2023-04-29 12:03     ` SuperFashi
  0 siblings, 0 replies; 17+ messages in thread
From: SuperFashi @ 2023-04-29 12:03 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Please see new version.

On Sat, Apr 29, 2023 at 20:38 Jean-Baptiste Kempf <jb@videolan.org> wrote:

> On Sat, 29 Apr 2023, at 07:44, SuperFashi wrote:
> > +#define AVERROR_INVALIDDATA (abort(), 0)
>
> Why are you aborting?
>
> --
> Jean-Baptiste Kempf -  President
> +33 672 704 734
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29  5:53     ` [FFmpeg-devel] [PATCH v4] " SuperFashi
@ 2023-04-29 18:05       ` Michael Niedermayer
  2023-04-30  3:32         ` SuperFashi
  2023-05-01 11:01       ` [FFmpeg-devel] [PATCH v5] " SuperFashi
  1 sibling, 1 reply; 17+ messages in thread
From: Michael Niedermayer @ 2023-04-29 18:05 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 6225 bytes --]

On Sat, Apr 29, 2023 at 02:53:06PM +0900, SuperFashi wrote:
> v1 -> v2: Refactor using GetByteContext; Fix compile error.
> v2 -> v3: Remove debug statement.
> v3 -> v4: Squash commits (sorry, first time git patch user :(
> 
> 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 filesystem 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       | 1372 ++++++++++++++++++++++++++++++++++++++
>  libavformat/mmtp.h       |   61 ++
>  libavformat/mmttlv.c     |  324 +++++++++
>  libavformat/version.h    |    2 +-
>  8 files changed, 1765 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 b6f6682904..2483fdd547 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -6,6 +6,7 @@ version <next>:
>  - Playdate video decoder and demuxer
>  - Extend VAAPI support for libva-win32 on Windows
>  - afireqsrc audio source 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..ba1fcab281
> --- /dev/null
> +++ b/libavformat/mmtp.c
> @@ -0,0 +1,1372 @@
> +/*
> + * 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 "libavutil/mem.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavcodec/bytestream.h"
> +#include "network.h"
> +#include "mmtp.h"
> +#include "internal.h"
> +#include "demux.h"
> +
> +#include <stdbool.h>
> +

> +#define ENSURE_BS_LEFT(bs, size) if (bytestream2_get_bytes_left(bs) < (size)) return AVERROR_INVALIDDATA

please dont wrap libavcodec API in another API
if every file in libavformat did that, noone would be able to
read code except their own files


> +
> +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;

> +            in_port_t      dst_port;

src/libavformat/mmtp.c:43:13: error: unknown type name ‘in_port_t’
             in_port_t      dst_port;

             
[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

If the United States is serious about tackling the national security threats 
related to an insecure 5G network, it needs to rethink the extent to which it
values corporate profits and government espionage over security.-Bruce Schneier

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29 18:05       ` Michael Niedermayer
@ 2023-04-30  3:32         ` SuperFashi
  2023-04-30 15:14           ` Anton Khirnov
  0 siblings, 1 reply; 17+ messages in thread
From: SuperFashi @ 2023-04-30  3:32 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Thanks. Is there anything else that does not fit codebase tradition? Please
point everything out so I could send a new patch.

On Sun, Apr 30, 2023 at 3:06 AM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Sat, Apr 29, 2023 at 02:53:06PM +0900, SuperFashi wrote:
> > v1 -> v2: Refactor using GetByteContext; Fix compile error.
> > v2 -> v3: Remove debug statement.
> > v3 -> v4: Squash commits (sorry, first time git patch user :(
> >
> > 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 filesystem 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       | 1372 ++++++++++++++++++++++++++++++++++++++
> >  libavformat/mmtp.h       |   61 ++
> >  libavformat/mmttlv.c     |  324 +++++++++
> >  libavformat/version.h    |    2 +-
> >  8 files changed, 1765 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 b6f6682904..2483fdd547 100644
> > --- a/Changelog
> > +++ b/Changelog
> > @@ -6,6 +6,7 @@ version <next>:
> >  - Playdate video decoder and demuxer
> >  - Extend VAAPI support for libva-win32 on Windows
> >  - afireqsrc audio source 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..ba1fcab281
> > --- /dev/null
> > +++ b/libavformat/mmtp.c
> > @@ -0,0 +1,1372 @@
> > +/*
> > + * 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 "libavutil/mem.h"
> > +#include "libavutil/avassert.h"
> > +#include "libavutil/intreadwrite.h"
> > +#include "libavcodec/bytestream.h"
> > +#include "network.h"
> > +#include "mmtp.h"
> > +#include "internal.h"
> > +#include "demux.h"
> > +
> > +#include <stdbool.h>
> > +
>
> > +#define ENSURE_BS_LEFT(bs, size) if (bytestream2_get_bytes_left(bs) <
> (size)) return AVERROR_INVALIDDATA
>
> please dont wrap libavcodec API in another API
> if every file in libavformat did that, noone would be able to
> read code except their own files
>
>
> > +
> > +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;
>
> > +            in_port_t      dst_port;
>
> src/libavformat/mmtp.c:43:13: error: unknown type name ‘in_port_t’
>              in_port_t      dst_port;
>
>
> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> If the United States is serious about tackling the national security
> threats
> related to an insecure 5G network, it needs to rethink the extent to which
> it
> values corporate profits and government espionage over security.-Bruce
> Schneier
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-30  3:32         ` SuperFashi
@ 2023-04-30 15:14           ` Anton Khirnov
  2023-05-01  1:10             ` SuperFashi
  0 siblings, 1 reply; 17+ messages in thread
From: Anton Khirnov @ 2023-04-30 15:14 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Quoting SuperFashi (2023-04-30 05:32:46)
> Thanks. Is there anything else that does not fit codebase tradition? Please
> point everything out so I could send a new patch.

Some quick notes:
* why are there avpriv_ functions when everything you do is libavformat?
  avpriv is for sharing functions across libraries
* header order (first system, then ours, sort each of those
  alphabetically)
* opening brace for function declarations should be on its own line
* very long lines everywhere; we have a soft limit of 80 characters (can
  be exceeded where really needed, but should be done sparingly)
  if your code needs more then it's a sign that you have too much
  indentation or your names are too long
* inline specifiers all over the place, I think modern compilers largely
  ignore them; even if they didn't it's better to leave such decisions
  to compilers in most cases

-- 
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-30 15:14           ` Anton Khirnov
@ 2023-05-01  1:10             ` SuperFashi
  2023-05-01  7:39               ` Anton Khirnov
  0 siblings, 1 reply; 17+ messages in thread
From: SuperFashi @ 2023-05-01  1:10 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Thanks for the feedback. I thought across libraries meant across different
muxers/demuxers. In which case, is there any naming scheme for functions
that are only used within lavf? Can i just remove the avpriv_ prefix?

For inline, my habit is to add it when there’s only one place that uses it.
I can remove it.

I will make changes to other parts.

On Mon, May 1, 2023 at 00:14 Anton Khirnov <anton@khirnov.net> wrote:

> Quoting SuperFashi (2023-04-30 05:32:46)
> > Thanks. Is there anything else that does not fit codebase tradition?
> Please
> > point everything out so I could send a new patch.
>
> Some quick notes:
> * why are there avpriv_ functions when everything you do is libavformat?
>   avpriv is for sharing functions across libraries
> * header order (first system, then ours, sort each of those
>   alphabetically)
> * opening brace for function declarations should be on its own line
> * very long lines everywhere; we have a soft limit of 80 characters (can
>   be exceeded where really needed, but should be done sparingly)
>   if your code needs more then it's a sign that you have too much
>   indentation or your names are too long
> * inline specifiers all over the place, I think modern compilers largely
>   ignore them; even if they didn't it's better to leave such decisions
>   to compilers in most cases
>
> --
> Anton Khirnov
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4] avformat: add MMTP parser and MMT/TLV demuxer
  2023-05-01  1:10             ` SuperFashi
@ 2023-05-01  7:39               ` Anton Khirnov
  0 siblings, 0 replies; 17+ messages in thread
From: Anton Khirnov @ 2023-05-01  7:39 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Quoting SuperFashi (2023-05-01 03:10:36)
> Thanks for the feedback. I thought across libraries meant across different
> muxers/demuxers. In which case, is there any naming scheme for functions
> that are only used within lavf? Can i just remove the avpriv_ prefix?

tl;dr: use ff_ for non-static functions used within a single library

see http://ffmpeg.org/developer.html#Naming-conventions-1 for details

> For inline, my habit is to add it when there’s only one place that uses it.
> I can remove it.

I prefer to avoid it, because it's visual clutter that in most cases has
no meaningful effect.

-- 
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [FFmpeg-devel] [PATCH v5] avformat: add MMTP parser and MMT/TLV demuxer
  2023-04-29  5:53     ` [FFmpeg-devel] [PATCH v4] " SuperFashi
  2023-04-29 18:05       ` Michael Niedermayer
@ 2023-05-01 11:01       ` SuperFashi
  2023-05-02 12:57         ` Paul B Mahol
  2023-05-03 13:02         ` [FFmpeg-devel] [PATCH v6] " SuperFashi
  1 sibling, 2 replies; 17+ messages in thread
From: SuperFashi @ 2023-05-01 11:01 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

v1 -> v2: Refactor using GetByteContext; Fix compile error.
v2 -> v3: Remove debug statement.
v3 -> v4: Squash commits
v4 -> v5: Improve portability; Cosmetic changes.

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       | 1528 ++++++++++++++++++++++++++++++++++++++
 libavformat/mmtp.h       |   64 ++
 libavformat/mmttlv.c     |  334 +++++++++
 libavformat/version.h    |    2 +-
 8 files changed, 1934 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..c6eebbff38
--- /dev/null
+++ b/libavformat/mmtp.c
@@ -0,0 +1,1528 @@
+/*
+ * 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;
+        if (ass->data != NULL)
+            av_free(ass->data);
+        av_free(ass);
+        ass = next;
+    }
+
+    for (streams = ctx->streams; streams != NULL;) {
+        struct Streams *next = streams->next;
+        if (streams->timestamp_descriptor != NULL)
+            av_free(streams->timestamp_descriptor);
+        if (streams->ext_timestamp_descriptor != NULL)
+            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..53c9939ca4
--- /dev/null
+++ b/libavformat/mmttlv.c
@@ -0,0 +1,334 @@
+/*
+ * 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 FFERROR_REDO;
+    }
+
+    if (ctx->cap < size) {
+        if (ctx->buf != NULL)
+            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;
+    }
+    if (priv->buf != NULL) av_free(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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5] avformat: add MMTP parser and MMT/TLV demuxer
  2023-05-01 11:01       ` [FFmpeg-devel] [PATCH v5] " SuperFashi
@ 2023-05-02 12:57         ` Paul B Mahol
  2023-05-02 13:43           ` SuperFashi
  2023-05-03 13:02         ` [FFmpeg-devel] [PATCH v6] " SuperFashi
  1 sibling, 1 reply; 17+ messages in thread
From: Paul B Mahol @ 2023-05-02 12:57 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: SuperFashi

Use av_freep(), non need to check for NULL before av_freep() / av_free()
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5] avformat: add MMTP parser and MMT/TLV demuxer
  2023-05-02 12:57         ` Paul B Mahol
@ 2023-05-02 13:43           ` SuperFashi
  0 siblings, 0 replies; 17+ messages in thread
From: SuperFashi @ 2023-05-02 13:43 UTC (permalink / raw)
  To: Paul B Mahol; +Cc: SuperFashi, FFmpeg development discussions and patches

I can remove the NULL checks, sure.

I could change L289 in mmttlv.c to a freep and reset the priv_data. Other
places have no need to change.

On Tue, May 2, 2023 at 9:58 PM Paul B Mahol <onemda@gmail.com> wrote:

> Use av_freep(), non need to check for NULL before av_freep() / av_free()
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [FFmpeg-devel] [PATCH v6] avformat: add MMTP parser and MMT/TLV demuxer
  2023-05-01 11:01       ` [FFmpeg-devel] [PATCH v5] " SuperFashi
  2023-05-02 12:57         ` Paul B Mahol
@ 2023-05-03 13:02         ` SuperFashi
  2023-05-17  8:27           ` SuperFashi
  1 sibling, 1 reply; 17+ messages in thread
From: SuperFashi @ 2023-05-03 13:02 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [FFmpeg-devel] [PATCH v6] avformat: add MMTP parser and MMT/TLV demuxer
  2023-05-03 13:02         ` [FFmpeg-devel] [PATCH v6] " SuperFashi
@ 2023-05-17  8:27           ` SuperFashi
  0 siblings, 0 replies; 17+ messages in thread
From: SuperFashi @ 2023-05-17  8:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: SuperFashi

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
>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2023-05-17  8:28 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-28 17:31 [FFmpeg-devel] [PATCH] avformat: add MMTP parser and MMT/TLV demuxer SuperFashi
2023-04-28 20:44 ` Michael Niedermayer
2023-04-29  5:44 ` [FFmpeg-devel] [PATCH v2] " SuperFashi
2023-04-29  5:49   ` [FFmpeg-devel] [PATCH v3] " SuperFashi
2023-04-29  5:53     ` [FFmpeg-devel] [PATCH v4] " SuperFashi
2023-04-29 18:05       ` Michael Niedermayer
2023-04-30  3:32         ` SuperFashi
2023-04-30 15:14           ` Anton Khirnov
2023-05-01  1:10             ` SuperFashi
2023-05-01  7:39               ` Anton Khirnov
2023-05-01 11:01       ` [FFmpeg-devel] [PATCH v5] " SuperFashi
2023-05-02 12:57         ` Paul B Mahol
2023-05-02 13:43           ` SuperFashi
2023-05-03 13:02         ` [FFmpeg-devel] [PATCH v6] " SuperFashi
2023-05-17  8:27           ` SuperFashi
2023-04-29 11:38   ` [FFmpeg-devel] [PATCH v2] " Jean-Baptiste Kempf
2023-04-29 12:03     ` SuperFashi

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git