GStreamer技术问题:如何暂停H.264录制同时保留实时视频显示
我的GStreamer管道结构如下:
rtspsrc -> tee -> queue (Q1) -> mp4mux -> filesink tee -> queue -> decoder -> displaysink
我需要实现暂停/恢复录制的功能:
- 暂停录制时,在Q1的sink pad上通过探针丢弃样本;
- 恢复录制时,修改缓冲区时间戳保证单调性(避免MP4播放时因时间戳间隙卡顿),同时只在I帧处截断H.264流,确保录制内容是完整GOP。
当前探针代码片段:
if (This->RecordPaused) { return GST_PAD_PROBE_DROP; } else { This->monotonicTimestamp += GST_BUFFER_DURATION(buffer); GST_BUFFER_PTS(buffer) = This->monotonicTimestamp; GST_BUFFER_DTS(buffer) = This->monotonicTimestamp; }
遇到的问题
修改录制分支的时间戳后,显示分支的GstBuffer时间戳也被同步修改,解码器会报时间戳倒退警告;虽然通常不影响显示,但插入deinterlace元素后,画面会因时间戳间隙冻结。
查看gsttee.c源码后确认:tee发送的是GstBuffer的引用而非拷贝,所以修改一个分支的缓冲区会影响另一分支。
疑问与备选方案
- 我考虑把录制分支改成
appsink + appsrc的结构:
queue (Q1) -> appsink appsrc -> mp4mux -> filesink
这样可以创建独立的GstBuffer并设置单调时间戳,不影响显示分支,但不确定这是不是最优方案。
2. 查过Segment相关内容,但文档不足,不清楚是否适用于该场景,也不知道如何构造Segment事件、能否从pad探针发送。
3. 是否有现成元素可以实现GstBuffer的浅拷贝(避免深拷贝底层内存)?
1. 用queue元素的copy-buffers属性实现浅拷贝
GStreamer的queue元素本身就支持缓冲区拷贝,只需开启copy-buffers属性:
# 命令行中设置 queue name=Q1 copy-buffers=true
或者在代码中设置:
g_object_set(GST_ELEMENT(Q1), "copy-buffers", TRUE, NULL);
开启后,queue会为输出的每个缓冲区创建一个浅拷贝(只复制缓冲区元数据,共享底层内存),这样在Q1的sink pad探针中修改时间戳时,只会影响录制分支的缓冲区,不会波及显示分支。这是最轻量化的方案,无需额外元素或复杂逻辑。
2. 用bufferclone元素(GStreamer 1.18+)
如果你的GStreamer版本在1.18及以上,可以直接使用bufferclone元素,它专门用于创建缓冲区的浅拷贝:
rtspsrc -> tee -> bufferclone -> queue (Q1) -> mp4mux -> filesink tee -> queue -> decoder -> displaysink
bufferclone默认就是浅拷贝,无需额外配置,能直接隔离两个分支的缓冲区修改。
3. 关于Segment事件的思路(进阶)
Segment事件确实可以用来调整时间戳,但更适合处理整体时间轴的偏移或缩放,而非暂停恢复场景下的时间戳补全。如果要用,需要在恢复录制时发送包含新起始时间的Segment事件到mp4mux的sink pad,同时在探针中保留原始时间戳,让mp4mux根据Segment事件自动调整输出时间戳。但这种方式比拷贝缓冲区更复杂,需要处理事件同步,不推荐作为首选方案。
4. appsink + appsrc方案的可行性
这个方案是可行的,但属于重量级实现:你需要在appsink的回调中创建新的GstBuffer(可以用gst_buffer_copy()做浅拷贝),设置新时间戳后再推送到appsrc。虽然能完全隔离两个分支,但会增加代码复杂度和少量性能开销,适合需要对缓冲区做更多自定义处理的场景,否则优先用前两种方案。
内容的提问来源于stack exchange,提问作者Vincentz

