This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

The following commit(s) were added to refs/heads/master by this push:
     new 19a2758e55 avformat/mvrdec: add MVR CCTV demuxer
19a2758e55 is described below

commit 19a2758e55a6edcc185061958740fcac4b83378f
Author:     Zhao Zhili <[email protected]>
AuthorDate: Wed Jun 24 20:32:07 2026 +0800
Commit:     Zhao Zhili <[email protected]>
CommitDate: Fri Jul 10 17:45:09 2026 +0000

    avformat/mvrdec: add MVR CCTV demuxer
    
    The format stores video-only H.264 in a fixed header + index table +
    chunk layout. Each chunk is a self-contained GOP, enabling simple
    random access via the index.
    
    Fix #23540
---
 Changelog                |   1 +
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/mvrdec.c     | 208 +++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/version.h    |   2 +-
 5 files changed, 212 insertions(+), 1 deletion(-)

diff --git a/Changelog b/Changelog
index af804cf9f4..e9c2bda2ed 100644
--- a/Changelog
+++ b/Changelog
@@ -6,6 +6,7 @@ version <next>:
 - APV Vulkan encoder
 - iTerm2 inline image protocol muxer
 - LCEVC payload merging bitstream filter
+- MVR demuxer
 
 
 version 9.0:
diff --git a/libavformat/Makefile b/libavformat/Makefile
index b244eaabf9..9c761ba341 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -423,6 +423,7 @@ OBJS-$(CONFIG_MTV_DEMUXER)               += mtv.o
 OBJS-$(CONFIG_MUSX_DEMUXER)              += musx.o
 OBJS-$(CONFIG_MV_DEMUXER)                += mvdec.o
 OBJS-$(CONFIG_MVI_DEMUXER)               += mvi.o
+OBJS-$(CONFIG_MVR_DEMUXER)               += mvrdec.o
 OBJS-$(CONFIG_MXF_DEMUXER)               += mxfdec.o mxf.o avlanguage.o
 OBJS-$(CONFIG_MXF_MUXER)                 += mxfenc.o mxf.o
 OBJS-$(CONFIG_MXG_DEMUXER)               += mxg.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 05624e66eb..e121c7441c 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -320,6 +320,7 @@ extern const FFInputFormat  ff_mtv_demuxer;
 extern const FFInputFormat  ff_musx_demuxer;
 extern const FFInputFormat  ff_mv_demuxer;
 extern const FFInputFormat  ff_mvi_demuxer;
+extern const FFInputFormat  ff_mvr_demuxer;
 extern const FFInputFormat  ff_mxf_demuxer;
 extern const FFOutputFormat ff_mxf_muxer;
 extern const FFOutputFormat ff_mxf_d10_muxer;
diff --git a/libavformat/mvrdec.c b/libavformat/mvrdec.c
new file mode 100644
index 0000000000..ab594dd1e7
--- /dev/null
+++ b/libavformat/mvrdec.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2026 Zhao Zhili
+ *
+ * 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 <limits.h>
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "avformat.h"
+#include "demux.h"
+#include "internal.h"
+
+/*
+ * Layout: [512 B header][index: N x 8 B (start_ms:u32, offset:u32)]
+ *         [chunks: 16 B local header + H.264 Annex-B NALs]
+ * Each chunk is a self-contained GOP (SPS+PPS+IDR + P-slices).
+ */
+#define MVR_HEADER_SIZE     512
+#define MVR_ENTRY_SIZE      8
+#define MVR_CHUNK_HEADER    16
+
+typedef struct MVRContext {
+    /* index of the chunk to read next */
+    unsigned next_chunk;
+} MVRContext;
+
+static int mvr_probe(const AVProbeData *p)
+{
+    unsigned width, height;
+    unsigned index_table_size, index_capacity, index_count;
+    size_t index_size;
+
+    if (p->buf_size < 0x40)
+        return 0;
+
+    if (AV_RL32(p->buf) != 4)
+        return 0;
+
+    width = AV_RL32(p->buf + 0x04);
+    height = AV_RL32(p->buf + 0x08);
+    if (width == 0 || width > 8192 || height == 0 || height > 8192)
+        return 0;
+
+    index_table_size = AV_RL32(p->buf + 0x34);
+    index_capacity = AV_RL32(p->buf + 0x38);
+    index_count = AV_RL32(p->buf + 0x3c);
+
+    if (index_capacity == 0 ||
+        av_size_mult(index_capacity, MVR_ENTRY_SIZE, &index_size) < 0 ||
+        index_table_size != index_size ||
+        index_count > index_capacity)
+        return 0;
+
+    /* Must outrank h264 */
+    return AVPROBE_SCORE_EXTENSION + 2;
+}
+
+static int mvr_read_header(AVFormatContext *s)
+{
+    AVIOContext *pb = s->pb;
+    AVStream *st;
+    int64_t filesize;
+    uint64_t time_meta_ms;
+    uint32_t index_count, start_ms, offset;
+    int64_t index_table_size, data_start;
+    int ret;
+
+    filesize = avio_size(pb);
+    if (filesize < MVR_HEADER_SIZE + MVR_ENTRY_SIZE)
+        return AVERROR_INVALIDDATA;
+
+    st = avformat_new_stream(s, NULL);
+    if (!st)
+        return AVERROR(ENOMEM);
+
+    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+    st->codecpar->codec_id = AV_CODEC_ID_H264;
+    ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL;
+    avpriv_set_pts_info(st, 64, 1, 1000);
+
+    avio_skip(pb, 4);
+    st->codecpar->width = avio_rl32(pb);
+    st->codecpar->height = avio_rl32(pb);
+
+    avio_skip(pb, 0x28 - 0x0c);
+    time_meta_ms = avio_rl64(pb);
+    ff_dict_set_timestamp(&s->metadata, "creation_time",
+                          time_meta_ms * 1000);
+    st->duration = avio_rl32(pb);
+
+    /* Skip index_capacity; data starts after the full index table. */
+    index_table_size = avio_rl32(pb);
+    avio_skip(pb, 4);
+    index_count = avio_rl32(pb);
+
+    if (index_table_size == 0 ||
+        index_table_size > UINT32_MAX - MVR_HEADER_SIZE ||
+        index_table_size % MVR_ENTRY_SIZE != 0 ||
+        index_table_size / MVR_ENTRY_SIZE < index_count)
+        return AVERROR_INVALIDDATA;
+
+    if (avio_seek(pb, MVR_HEADER_SIZE, SEEK_SET) < 0)
+        return AVERROR_INVALIDDATA;
+
+    data_start = MVR_HEADER_SIZE + index_table_size;
+    if (filesize < data_start)
+        return AVERROR_INVALIDDATA;
+
+    start_ms = avio_rl32(pb);
+    offset = avio_rl32(pb);
+    for (unsigned i = 0; i < index_count; i++) {
+        uint32_t next_start_ms;
+        int64_t next_offset, chunk_size;
+
+        if (i + 1 < index_count) {
+            next_start_ms = avio_rl32(pb);
+            next_offset = avio_rl32(pb);
+        } else {
+            next_start_ms = 0;
+            next_offset = filesize;
+        }
+
+        if (offset < data_start || next_offset > filesize)
+            return AVERROR_INVALIDDATA;
+
+        chunk_size = next_offset - offset;
+        if (chunk_size <= MVR_CHUNK_HEADER || chunk_size > INT_MAX)
+            return AVERROR_INVALIDDATA;
+
+        ret = av_add_index_entry(st, offset, start_ms,
+                                 chunk_size, 0, AVINDEX_KEYFRAME);
+        if (ret < 0)
+            return ret;
+
+        start_ms = next_start_ms;
+        offset = next_offset;
+    }
+
+    return 0;
+}
+
+static int mvr_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    MVRContext *mvr = s->priv_data;
+    AVStream *st = s->streams[0];
+    FFStream *const sti = ffstream(st);
+    AVIndexEntry *e;
+    int64_t h264_pos;
+    int h264_size, ret;
+
+    /* Resync after a generic seek. */
+    if (s->io_repositioned) {
+        int idx = av_index_search_timestamp(st, sti->cur_dts,
+                                            AVSEEK_FLAG_BACKWARD);
+        if (idx < 0)
+            return AVERROR(EINVAL);
+        mvr->next_chunk = idx;
+        s->io_repositioned = 0;
+    }
+
+    if (mvr->next_chunk >= sti->nb_index_entries)
+        return AVERROR_EOF;
+
+    e = &sti->index_entries[mvr->next_chunk];
+    h264_pos = e->pos + MVR_CHUNK_HEADER;
+    h264_size = e->size - MVR_CHUNK_HEADER;
+
+    if (avio_seek(s->pb, h264_pos, SEEK_SET) < 0)
+        return AVERROR_EOF;
+
+    ret = av_get_packet(s->pb, pkt, h264_size);
+    if (ret < 0)
+        return ret;
+
+    pkt->pts = e->timestamp;
+    pkt->dts = AV_NOPTS_VALUE;
+    pkt->pos = e->pos;
+    mvr->next_chunk++;
+
+    return 0;
+}
+
+const FFInputFormat ff_mvr_demuxer = {
+    .p.name         = "mvr",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("MVR CCTV"),
+    .p.extensions   = "mvr",
+    .p.flags        = AVFMT_GENERIC_INDEX,
+    .priv_data_size = sizeof(MVRContext),
+    .read_probe     = mvr_probe,
+    .read_header    = mvr_read_header,
+    .read_packet    = mvr_read_packet,
+};
diff --git a/libavformat/version.h b/libavformat/version.h
index 7ff1483912..af7d0a1024 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -31,7 +31,7 @@
 
 #include "version_major.h"
 
-#define LIBAVFORMAT_VERSION_MINOR   3
+#define LIBAVFORMAT_VERSION_MINOR   4
 #define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to