在碎片化的实时 MP4 流中发送周期性元数据?

Sending periodic metadata in fragmented live MP4 stream?

正如主题所建议的,我想知道是否可以在分段的 MP4 实时流中定期发送有关流内容的元数据。

我正在使用以下命令 (1) 来获取碎片化的 MP4:

ffmpeg -i rtsp://admin:12345@192.168.0.157 -c:v copy -an -movflags empty_moov+omit_tfhd_offset+frag_keyframe+default_base_moof -f mp4 ...

我的 主程序 从标准输出或(unix 域)套接字读取此命令的片段并获取:

ftyp
moov
moof
mdat
moof
mdat
moof
mdat 
...

所以,我得到的第一个片段是 ftypmoov,它们是元数据,描述了流内容。

现在,客户端程序稍后连接到主程序。问题是,在那一点上,ftypemoov 片段早已不复存在。

有没有办法(=ffmpeg 命令选项)使此工作类似于 MPEGTS(又名 mpeg 传输流),并使用流定期重新发送元数据? 像这样:

ftyp
moov
moof
mdat
moof
mdat
moof
mdat 
ftyp
moov
moof
mdat
moof
mdat
moof
mdat 
...

.. 或者是我在 主程序中缓存 ftypmoov 数据包的唯一选择 并在请求流时将它们重新发送到 客户端程序?

相关link:

每次新客户端连接时缓存和重新发送 ftypmoov 也不是那么简单......因为它以某种方式破坏了流(至少浏览器 MSE 扩展不喜欢这样的流)。 moof 数据包中似乎有很多序列号和东西需要修改。 (+)

另一种选择是通过另一个执行重新混合的 FFmpeg 进程传递流(并更正 moof 数据包)。由于命令 (1) 没有给出干净分隔的 ftypmoovmoof,等数据包.

感谢任何想法/解决方案。

编辑:关于(+),MSE似乎在播放有间隙的碎片化MP4时遇到问题:https://bugs.chromium.org/p/chromium/issues/detail?id=516114

ftyp/moov 形成了所谓的“初始化片段”,应该只在更改流时写入 MSE。这通常是通过在清单中包含 init 的 URL 来处理的,玩家的工作是在加入流时请求它。

我终于能够毫无问题地将碎片化的 MP4 提供给浏览器 MSE 扩展。

如果一个人开始用 moofmdat 数据包喂养 MSE 扩展,这些数据包不是在原始 之后立即出现的ftypmoov,然后..

.. 进入 MSE 扩展的第一个 moof 数据包必须是一个 moof 数据包,它有一个名为 first_sample_flags_preset 集(有关详细信息,请参阅 ISO/IEC 14496-12:2012(E) 规范)

.. 否则所有流行浏览器中的 MSE 都会冻结并且没有视频播放(顺便说一句,moof 序列号从 > 1 开始完全没有问题)。

这个 python 包对分析非常有用:https://github.com/beardypig/pymp4

为了获得该标志,此答案中提供了客户端 javascript 函数。

使用函数getBox找出盒子的类型(ftyp, moov, moof, 等等).

对于moof框,应用函数findFirstSampleFlag查看moof框是否有first_sample_flags_preset 已启用。

function toInt(arr, index) { // From bytes to big-endian 32-bit integer.  Input: Uint8Array, index
    var dv = new DataView(arr.buffer, 0);
    return dv.getInt32(index, false); // big endian
}

function toString(arr, fr, to) { // From bytes to string.  Input: Uint8Array, start index, stop index.
    return String.fromCharCode.apply(null, arr.slice(fr,to));
}

function getBox(arr, i) { // input Uint8Array, start index
    return [toInt(arr, i), toString(arr, i+4, i+8)]
}

function getSubBox(arr, box_name) { // input Uint8Array, box name
    var i = 0;
    res = getBox(arr, i);
    main_length = res[0]; name = res[1]; // this boxes length and name
    i = i + 8;

    var sub_box = null;

    while (i < main_length) {
        res = getBox(arr, i);
        l = res[0]; name = res[1];

        if (box_name == name) {
            sub_box = arr.slice(i, i+l)
        }
        i = i + l;
    }
    return sub_box;
}

function findFirstSampleFlag(arr) { // input Uint8Array
    // [moof [mfhd] [traf [tfhd] [tfdt] [trun]]]

    var traf = getSubBox(arr, "traf");
    if (traf==null) { return false; }

    var trun = getSubBox(traf, "trun");
    if (trun==null) { return false; }

    // ISO/IEC 14496-12:2012(E) .. pages 5 and 57
    // bytes: (size 4), (name 4), (version 1 + tr_flags 3)
    var flags = trun.slice(10,13); // console.log(flags);
    f = flags[1] & 4; // console.log(f);
    return f == 4;
}