GStreamer mp4mux 使用自定义 appsrc 时出现 "Buffer has no PTS" 错误
GStreamer mp4mux gives "Buffer has no PTS" error using custom appsrc
我有一个用 C++ 编码的管道,如下所示:
appsrc do-timestamp=TRUE is-live=TRUE caps=
“video/x-h264, stream-format=(string)byte-stream, alignment=(string)none, framerate=(fraction)0/1” min-latency=300000000 ! h264parse ! video/x-h264, stream-format=(string)avc, alignment=(string)au ! tee name=t \
t. ! queue ! valve drop=FALSE ! decodebin ! glupload ! glcolorconvert ! qtsink sync=FALSE \
t. ! queue ! valve drop=FALSE ! mp4mux reserved-max-duration=3600000000000 reserved-moov-update-period=10000000000 ! filesink sync=FALSE location=”....../out.mp4”
appsrc 将来自无人机 USB 无线视频接收器的视频注入管道。
更多上下文:
- USB 接收器硬件为我们提供了 512 字节的非时间戳块
原始 Annex-B h.264 视频
- 帧率应为 60 fps,但在
练习它很少跟上它,并且根据
信号强度(因此帧率=(分数)0/1”,这就是
qtsink 和 filesink 都没有同步到管道的原因
(同步=假))
- 硬件引入了至少 300 毫秒的延迟,
在 appsrc
中设置
- appsrc 自动为我的缓冲区添加时间戳
(do-timestamp=TRUE)
- 我正在使用 mp4mux reserved-max-duration 和
reserved-moov-update-period 以防止应用程序崩溃破坏
mp4 文件
- 我正在为 Android
使用 GStreamer 1.18.4
无人机不在空中时,视频录制正常。但是当它起飞时,在大约 15 秒的正确视频录制后,mp4mux 元素失败并显示消息“Buffer has no PTS”。不幸的是,这一直被一些用户报告,但我无法重现它(因为它需要驾驶我没有的无人机),这没有多大意义。到目前为止,我的猜测是,在那个特定的时刻,无线视频中可能存在一些拥塞 link,一些视频数据包可能会延迟几毫秒,这可能会造成一些麻烦。
这是创建 appsrc 的(简化)代码
_pAppSrc = gst_element_factory_make("appsrc", "artosyn_source");
gpointer pAppSrc = static_cast<gpointer>(_pAppSrc);
// Retain one more ref, so the source is destroyed
// in a controlled way
gst_object_ref(_pAppSrc);
pCaps = gst_caps_from_string("video/x-h264, stream-format=(string)byte-stream, alignment=none, framerate=(fraction)0/1"));
g_object_set(G_OBJECT(pAppSrc), "caps", pCaps,
"is-live", TRUE,
"min-latency", G_GINT64_CONSTANT(300000000),
"format", GST_FORMAT_TIME,
"do-timestamp", TRUE,
nullptr);
_pBufferPool = gst_buffer_pool_new();
pConfig = gst_buffer_pool_get_config (_pBufferPool);
static const guint kBufferSize = 512;
static const guint kPoolSize = 0x400000;
static const guint kPoolSizeMax = 0x600000;
qsizetype nBuffersMin = kPoolSize / kBufferSize;
qsizetype nBuffersMax = kPoolSizeMax / kBufferSize;
gst_buffer_pool_config_set_params(pConfig, pCaps, kBufferSize, nBuffersMin, nBuffersMax);
gst_buffer_pool_set_config(_pBufferPool, pConfig);
gst_buffer_pool_set_active(_pBufferPool, TRUE);
gst_caps_unref(GST_CAPS(pCaps));
当 USB 驱动程序填满新缓冲区时,它会像这样被推入管道:
bool unref = false;
gst_buffer_unmap(b->pBuffer, &b->mapInfo);
gst_buffer_set_size(b->pBuffer, xfer.pXfer->actual_length);
if(result == LIBUSB_TRANSFER_COMPLETED)
{
//-- DROP DATA IF NOT IN PLAYING STATE --
GstState st, pend;
GstStateChangeReturn scr = gst_element_get_state(GST_ELEMENT(_pAppSrc), &st, &pend, GST_CLOCK_TIME_NONE);
Q_UNUSED(scr)
bool drop = (st != GST_STATE_PLAYING);
if(!drop)
{
GstFlowReturn ret = GST_FLOW_OK;
// Push into pipeline
ret = gst_app_src_push_buffer(GST_APP_SRC(_pAppSrc), b->pBuffer);
if(ret != GST_FLOW_OK)
qCDebug(MMCVideoLog()) << "Can't push buffer to the pipeline (" << ret << ")";
else
unref = false; // Don't unref since gst_app_src_push_buffer() steals one reference and takes ownership
}
} else if(result == LIBUSB_TRANSFER_CANCELLED)
{
qCDebug(MMCVideoLog()) << "! Buffer canceled";
} else {
qCDebug(MMCVideoLog()) << "? Buffer result = " << result;
}
if(unref)
gst_buffer_unref(b->pBuffer);
这是我从 Android logcat 受影响的机器上得到的:
[07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: GStreamer error: [element ' "mp4mux0" '] Could not multiplex stream.
[07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: Details: ../gst/isomp4/gstqtmux.c(5010): gst_qt_mux_add_buffer (): /GstPipeline:receiver/GstBin:sinkbin/GstMP4Mux:mp4mux0:
Buffer has no PTS.
我尝试过的:
- 将 GstBaseParser pts_interpolation 设置为 TRUE,并将 infer_ts 设置为 TRUE
所以我的问题是:
- 你能看出我的代码有什么问题吗?我错过了什么?
- 我可以依靠matroskamux暂时避免这个问题,直到我找到真正的
原因?
编辑: 我设法在“原位”重现它,同时使用连接到我的 T 型元件的水槽垫上的探针打印出每个缓冲区的 PTS 和 DTS,并发现发现问题缓冲区 没有 DTS,也没有 PTS。也许我的 h264parse 或我的 capsfilter 在我的 appsrc 和我的 tee 之间做了一些令人讨厌的事情?
07-28 17:54:49.025 1932 2047 D : PTS: 295659241497 DTS: 295659241497
07-28 17:54:49.026 1932 2047 D : PTS: 295682488791 DTS: 295682488791
07-28 17:54:49.053 1932 2047 D : PTS: 295710463127 DTS: 295710463127
07-28 17:54:49.054 1932 2047 D : PTS: 18446744073709551615 DTS: 18446744073709551615
07-28 17:54:49.054 1932 2047 E : ************** NO PTS
07-28 17:54:49.054 1932 2047 E : ************** NO DTS
07-28 17:54:49.110 1932 2047 D : PTS: 295738607214 DTS: 295738607214
07-28 17:54:49.111 1932 2199 E : GStreamer error: [element ' "mp4mux1" '] Could not multiplex stream.
07-28 17:54:49.111 1932 2199 E : Details: ../gst/isomp4/gstqtmux.c(5010): gst_qt_mux_add_buffer (): /GstPipeline:receiver/GstBin:sinkbin/GstMP4Mux:mp4mux1:
07-28 17:54:49.111 1932 2199 E : Buffer has no PTS.
编辑 2: 经过更多挖掘,我发现了更多线索:我编写了一些代码将通过 USB 传输的每个视频数据包连同时间戳转储到二进制文件中,和播放器播放它。然后我去了现场,让客户驾驶无人机,直到触发错误。这样我就可以随意重现错误了。
通过使用两个探针,一个连接到我的 'appsrc' 元素的 'src' 焊盘,另一个连接到我的 'tee' 元素的 'sink' 焊盘,我打印了通过它们的每个数据包的 PTS 和 DTS。
这是我的发现:
TL;DR: 在某些(随机)点,即使 h264parse 被提供时间戳缓冲区,它输出的缓冲区没有 PTS 和 DTS。
生成我通过 USB 获得的流的硬件 h264 编码器在没有 VUI 的情况下插入 SPS NALS,当将 h264parse 调试级别设置为 6 时,我遇到很多这样的错误 "[parser] unable to compute timestamp: VUI not present"
“VUI 不存在”错误非常一致并且出现得非常频繁。大部分时间都没有引起注意,因此我不是 100% 确定这是原因
由于appsrc推送的h264 buffer没有对齐,而h264parse输出的是au对齐的buffer,appsrc出来的512-byte buffer的数量和数量没有直接关系来自 h264parse 的缓冲区。因此,我相信可以相当安全地说,appsrc 生成的时间戳与 h264parse 生成的时间戳之间没有直接关系。 h264parse 必须重新计算它们。
我的h264码流很简单:SPS->PPS->I-frame->(若干P-frames)。没有 B 帧,每个帧都包含在一个大的脂肪切片中(只有 1 个 NAL)。
所以,现在我要在这两个选项之间做出选择:
- 每次检测到无 VUI 的 SPS 时,手动计算并插入带有 VUI 的假 SPS NAL。由于无线电信号强度(尽管它以 60fps 编码)并且分辨率和图片属性始终相同,因此流应该具有可变帧率。
- 自己解析流以为管道提供带时间戳的 au 对齐缓冲区。
折腾了好久,终于弄明白了这个问题的根源。而且有点晦涩..
无人机中的无线视频发射器能够根据无线电 link 的可用带宽动态更改视频比特率。或者换句话说:当无人机距离太远或有强干扰时,视频质量会下降。
发生这种情况时,视频帧(仅包含在单个 NAL 中的一个切片中)开始变得明显变小。由于我正在读取没有特定对齐的 h264 流的 512 字节块并将它们作为 GstBuffers 转发给 GStreamer,如果一帧所需的数据大小低于 512 字节,则缓冲区可能包含多个帧.在这种情况下,h264parse 将其视为 N 个不同的缓冲区 具有相同的时间戳 。然后默认行为似乎是忽略上游 PTS 和 DTS,并尝试通过从 SPS 读取 VUI 来根据帧的持续时间计算时间戳,这在我的流中不存在。因此,离开h264parse source pad的缓冲区将没有PTS,也没有DTS,从而使mp4mux报错。
正如我之前提到的,我的流非常简单,因此我编写了一个简单的解析器来检测每个 NAL 的开头。这样我就可以 'unpack' 来自 USB 硬件的流,并确保推入我的管道的每个缓冲区都只包含一个 NAL(因此,最多一帧),独立时间戳。
为了冗余,我添加了一个连接到我的 T 型元件的接收器垫的探针,以确保我在每个缓冲区中都有正确的时间戳。否则,他们将被迫像这样进入元素的 运行 时间。
if (!GST_BUFFER_PTS_IS_VALID(buffer) || !GST_BUFFER_DTS_IS_VALID(buffer))
{
GstElement* elm = gst_pad_get_parent_element(pad);
qCDebug(VideoReceiverLog) << "Invalid timestamp out of source. Replacing with element running time.";
GstClockTime ts = gst_element_get_current_running_time(elm);
GST_BUFFER_PTS(buffer) = ts;
GST_BUFFER_DTS(buffer) = ts;
}
执行此操作后,我无法再用我的测试转储重现该问题。
我有一个用 C++ 编码的管道,如下所示:
appsrc do-timestamp=TRUE is-live=TRUE caps=
“video/x-h264, stream-format=(string)byte-stream, alignment=(string)none, framerate=(fraction)0/1” min-latency=300000000 ! h264parse ! video/x-h264, stream-format=(string)avc, alignment=(string)au ! tee name=t \
t. ! queue ! valve drop=FALSE ! decodebin ! glupload ! glcolorconvert ! qtsink sync=FALSE \
t. ! queue ! valve drop=FALSE ! mp4mux reserved-max-duration=3600000000000 reserved-moov-update-period=10000000000 ! filesink sync=FALSE location=”....../out.mp4”
appsrc 将来自无人机 USB 无线视频接收器的视频注入管道。
更多上下文:
- USB 接收器硬件为我们提供了 512 字节的非时间戳块 原始 Annex-B h.264 视频
- 帧率应为 60 fps,但在 练习它很少跟上它,并且根据 信号强度(因此帧率=(分数)0/1”,这就是 qtsink 和 filesink 都没有同步到管道的原因 (同步=假))
- 硬件引入了至少 300 毫秒的延迟, 在 appsrc 中设置
- appsrc 自动为我的缓冲区添加时间戳 (do-timestamp=TRUE)
- 我正在使用 mp4mux reserved-max-duration 和 reserved-moov-update-period 以防止应用程序崩溃破坏 mp4 文件
- 我正在为 Android 使用 GStreamer 1.18.4
无人机不在空中时,视频录制正常。但是当它起飞时,在大约 15 秒的正确视频录制后,mp4mux 元素失败并显示消息“Buffer has no PTS”。不幸的是,这一直被一些用户报告,但我无法重现它(因为它需要驾驶我没有的无人机),这没有多大意义。到目前为止,我的猜测是,在那个特定的时刻,无线视频中可能存在一些拥塞 link,一些视频数据包可能会延迟几毫秒,这可能会造成一些麻烦。
这是创建 appsrc 的(简化)代码
_pAppSrc = gst_element_factory_make("appsrc", "artosyn_source");
gpointer pAppSrc = static_cast<gpointer>(_pAppSrc);
// Retain one more ref, so the source is destroyed
// in a controlled way
gst_object_ref(_pAppSrc);
pCaps = gst_caps_from_string("video/x-h264, stream-format=(string)byte-stream, alignment=none, framerate=(fraction)0/1"));
g_object_set(G_OBJECT(pAppSrc), "caps", pCaps,
"is-live", TRUE,
"min-latency", G_GINT64_CONSTANT(300000000),
"format", GST_FORMAT_TIME,
"do-timestamp", TRUE,
nullptr);
_pBufferPool = gst_buffer_pool_new();
pConfig = gst_buffer_pool_get_config (_pBufferPool);
static const guint kBufferSize = 512;
static const guint kPoolSize = 0x400000;
static const guint kPoolSizeMax = 0x600000;
qsizetype nBuffersMin = kPoolSize / kBufferSize;
qsizetype nBuffersMax = kPoolSizeMax / kBufferSize;
gst_buffer_pool_config_set_params(pConfig, pCaps, kBufferSize, nBuffersMin, nBuffersMax);
gst_buffer_pool_set_config(_pBufferPool, pConfig);
gst_buffer_pool_set_active(_pBufferPool, TRUE);
gst_caps_unref(GST_CAPS(pCaps));
当 USB 驱动程序填满新缓冲区时,它会像这样被推入管道:
bool unref = false;
gst_buffer_unmap(b->pBuffer, &b->mapInfo);
gst_buffer_set_size(b->pBuffer, xfer.pXfer->actual_length);
if(result == LIBUSB_TRANSFER_COMPLETED)
{
//-- DROP DATA IF NOT IN PLAYING STATE --
GstState st, pend;
GstStateChangeReturn scr = gst_element_get_state(GST_ELEMENT(_pAppSrc), &st, &pend, GST_CLOCK_TIME_NONE);
Q_UNUSED(scr)
bool drop = (st != GST_STATE_PLAYING);
if(!drop)
{
GstFlowReturn ret = GST_FLOW_OK;
// Push into pipeline
ret = gst_app_src_push_buffer(GST_APP_SRC(_pAppSrc), b->pBuffer);
if(ret != GST_FLOW_OK)
qCDebug(MMCVideoLog()) << "Can't push buffer to the pipeline (" << ret << ")";
else
unref = false; // Don't unref since gst_app_src_push_buffer() steals one reference and takes ownership
}
} else if(result == LIBUSB_TRANSFER_CANCELLED)
{
qCDebug(MMCVideoLog()) << "! Buffer canceled";
} else {
qCDebug(MMCVideoLog()) << "? Buffer result = " << result;
}
if(unref)
gst_buffer_unref(b->pBuffer);
这是我从 Android logcat 受影响的机器上得到的:
[07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: GStreamer error: [element ' "mp4mux0" '] Could not multiplex stream.
[07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: Details: ../gst/isomp4/gstqtmux.c(5010): gst_qt_mux_add_buffer (): /GstPipeline:receiver/GstBin:sinkbin/GstMP4Mux:mp4mux0:
Buffer has no PTS.
我尝试过的:
- 将 GstBaseParser pts_interpolation 设置为 TRUE,并将 infer_ts 设置为 TRUE
所以我的问题是:
- 你能看出我的代码有什么问题吗?我错过了什么?
- 我可以依靠matroskamux暂时避免这个问题,直到我找到真正的 原因?
编辑: 我设法在“原位”重现它,同时使用连接到我的 T 型元件的水槽垫上的探针打印出每个缓冲区的 PTS 和 DTS,并发现发现问题缓冲区 没有 DTS,也没有 PTS。也许我的 h264parse 或我的 capsfilter 在我的 appsrc 和我的 tee 之间做了一些令人讨厌的事情?
07-28 17:54:49.025 1932 2047 D : PTS: 295659241497 DTS: 295659241497
07-28 17:54:49.026 1932 2047 D : PTS: 295682488791 DTS: 295682488791
07-28 17:54:49.053 1932 2047 D : PTS: 295710463127 DTS: 295710463127
07-28 17:54:49.054 1932 2047 D : PTS: 18446744073709551615 DTS: 18446744073709551615
07-28 17:54:49.054 1932 2047 E : ************** NO PTS
07-28 17:54:49.054 1932 2047 E : ************** NO DTS
07-28 17:54:49.110 1932 2047 D : PTS: 295738607214 DTS: 295738607214
07-28 17:54:49.111 1932 2199 E : GStreamer error: [element ' "mp4mux1" '] Could not multiplex stream.
07-28 17:54:49.111 1932 2199 E : Details: ../gst/isomp4/gstqtmux.c(5010): gst_qt_mux_add_buffer (): /GstPipeline:receiver/GstBin:sinkbin/GstMP4Mux:mp4mux1:
07-28 17:54:49.111 1932 2199 E : Buffer has no PTS.
编辑 2: 经过更多挖掘,我发现了更多线索:我编写了一些代码将通过 USB 传输的每个视频数据包连同时间戳转储到二进制文件中,和播放器播放它。然后我去了现场,让客户驾驶无人机,直到触发错误。这样我就可以随意重现错误了。
通过使用两个探针,一个连接到我的 'appsrc' 元素的 'src' 焊盘,另一个连接到我的 'tee' 元素的 'sink' 焊盘,我打印了通过它们的每个数据包的 PTS 和 DTS。
这是我的发现:
TL;DR: 在某些(随机)点,即使 h264parse 被提供时间戳缓冲区,它输出的缓冲区没有 PTS 和 DTS。
生成我通过 USB 获得的流的硬件 h264 编码器在没有 VUI 的情况下插入 SPS NALS,当将 h264parse 调试级别设置为 6 时,我遇到很多这样的错误
"[parser] unable to compute timestamp: VUI not present"
“VUI 不存在”错误非常一致并且出现得非常频繁。大部分时间都没有引起注意,因此我不是 100% 确定这是原因
由于appsrc推送的h264 buffer没有对齐,而h264parse输出的是au对齐的buffer,appsrc出来的512-byte buffer的数量和数量没有直接关系来自 h264parse 的缓冲区。因此,我相信可以相当安全地说,appsrc 生成的时间戳与 h264parse 生成的时间戳之间没有直接关系。 h264parse 必须重新计算它们。
我的h264码流很简单:SPS->PPS->I-frame->(若干P-frames)。没有 B 帧,每个帧都包含在一个大的脂肪切片中(只有 1 个 NAL)。
所以,现在我要在这两个选项之间做出选择:
- 每次检测到无 VUI 的 SPS 时,手动计算并插入带有 VUI 的假 SPS NAL。由于无线电信号强度(尽管它以 60fps 编码)并且分辨率和图片属性始终相同,因此流应该具有可变帧率。
- 自己解析流以为管道提供带时间戳的 au 对齐缓冲区。
折腾了好久,终于弄明白了这个问题的根源。而且有点晦涩..
无人机中的无线视频发射器能够根据无线电 link 的可用带宽动态更改视频比特率。或者换句话说:当无人机距离太远或有强干扰时,视频质量会下降。
发生这种情况时,视频帧(仅包含在单个 NAL 中的一个切片中)开始变得明显变小。由于我正在读取没有特定对齐的 h264 流的 512 字节块并将它们作为 GstBuffers 转发给 GStreamer,如果一帧所需的数据大小低于 512 字节,则缓冲区可能包含多个帧.在这种情况下,h264parse 将其视为 N 个不同的缓冲区 具有相同的时间戳 。然后默认行为似乎是忽略上游 PTS 和 DTS,并尝试通过从 SPS 读取 VUI 来根据帧的持续时间计算时间戳,这在我的流中不存在。因此,离开h264parse source pad的缓冲区将没有PTS,也没有DTS,从而使mp4mux报错。
正如我之前提到的,我的流非常简单,因此我编写了一个简单的解析器来检测每个 NAL 的开头。这样我就可以 'unpack' 来自 USB 硬件的流,并确保推入我的管道的每个缓冲区都只包含一个 NAL(因此,最多一帧),独立时间戳。
为了冗余,我添加了一个连接到我的 T 型元件的接收器垫的探针,以确保我在每个缓冲区中都有正确的时间戳。否则,他们将被迫像这样进入元素的 运行 时间。
if (!GST_BUFFER_PTS_IS_VALID(buffer) || !GST_BUFFER_DTS_IS_VALID(buffer))
{
GstElement* elm = gst_pad_get_parent_element(pad);
qCDebug(VideoReceiverLog) << "Invalid timestamp out of source. Replacing with element running time.";
GstClockTime ts = gst_element_get_current_running_time(elm);
GST_BUFFER_PTS(buffer) = ts;
GST_BUFFER_DTS(buffer) = ts;
}
执行此操作后,我无法再用我的测试转储重现该问题。