GStreamer中Splitmuxsink生成文件的时间戳与视频实际起始时间不符的问题求助
最近我在做一个基于GStreamer的滚动录制工具,用splitmuxsink分割RTSP流到多个MKV文件,但是遇到了一个很头疼的问题:
第一个生成的文件(比如segment-00-47-17.mkv)的时间戳和视频里的起始时间是对得上的,但后续生成的文件(比如segment-00-47-47.mkv),实际视频内容却是从00-47-37开始的——每个后续文件的文件名时间都比视频内容的起始时间晚10秒,而且文件的系统创建时间也和文件名时间一致,和视频内容完全脱节。调整max-size-time参数替换max-size-bytes后,这个10秒的延迟依然存在。
以下是我目前的代码:
#include <gst/gst.h> #include <log4cplus/logger.h> #include <log4cplus/configurator.h> #include <chrono> #include <iomanip> #include <sstream> #include <string> #include <signal.h> bool running = true; void intHandler(int) { running = false; } std::string getTime() { std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::tm ltime; localtime_r(&t, <ime); std::ostringstream s; s << std::put_time(<ime, "%H-%M-%S"); return s.str(); } gchararray cb_splitmuxsink_format_location(GstElement*, guint fragmentId, gpointer) { std::string name = std::string("segment-") + getTime() + ".mkv"; LOG4CPLUS_INFO(log4cplus::Logger::getInstance(""), "Creating file " << name); return g_strdup(name.c_str()); } int main(int argc, char** argv) { gst_init(&argc, &argv); log4cplus::initialize(); log4cplus::BasicConfigurator::doConfigure(); auto logger = log4cplus::Logger::getInstance("main"); auto* pipeline = gst_parse_launch("rtspsrc location=rtsp://127.0.0.1:8554/test latency=0 ! \ rtph264depay ! h264parse ! queue ! splitmuxsink name=\"splitmuxsink\" location=./segment%02d.mkv \ max-size-bytes=6000000 muxer=matroskamux \ muxer-properties=\"properties, streamable=true\"", NULL); if (!pipeline) { LOG4CPLUS_FATAL(logger, "Failed to create elements"); return 1; } GstElement* splitmuxsink = gst_bin_get_by_name(GST_BIN(pipeline), "splitmuxsink"); g_signal_connect(splitmuxsink, "format-location", G_CALLBACK(cb_splitmuxsink_format_location), NULL); GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { LOG4CPLUS_FATAL(logger, "Failed to start pipeline"); return 1; } signal(SIGINT, intHandler); while (running) { } gst_element_set_state(pipeline, GST_STATE_NULL); gst_deinit(); return 0; }
问题原因分析
后来查了GStreamer的文档才搞明白:format-location信号是在splitmuxsink准备创建新文件的瞬间触发的,而不是在这个文件对应的视频段实际开始的时间触发的。
当流达到分割条件(比如max-size-bytes的阈值)时,splitmuxsink才会切换到新文件,这时候调用回调获取文件名——但此时这段视频内容已经在缓存里待了一段时间(直到达到分割大小),所以回调里用的系统当前时间,自然就比视频段实际的起始时间晚了,导致文件名和内容时间对不上。
至于第一个文件是对的,是因为程序启动时就创建了第一个文件,此时视频流刚启动,系统时间和视频起始时间几乎同步。
解决方案
核心思路是:不要用回调触发时的系统时间命名,而是用splitmuxsink提供的视频段实际起始时间戳。
splitmuxsink提供了format-location-full信号,这个信号会传递当前段的起始时钟时间(GstClockTime,纳秒单位),我们可以把这个时间戳转换成可读时间来命名文件,这样文件名的时间就和视频内容的起始时间完全对齐了。
修改后的关键代码如下:
1. 新增时间戳转换函数
// 把GStreamer的时钟时间(纳秒)转换成可读时间字符串 std::string getTimeFromStreamTimestamp(GstClockTime stream_ts) { // 把纳秒转换成秒 std::time_t t = stream_ts / GST_SECOND; std::tm ltime; localtime_r(&t, <ime); std::ostringstream s; s << std::put_time(<ime, "%H-%M-%S"); return s.str(); }
2. 替换回调函数为format-location-full版本
gchararray cb_splitmuxsink_format_location_full(GstElement*, guint, GstClockTime segment_start_ts, gpointer) { std::string name = std::string("segment-") + getTimeFromStreamTimestamp(segment_start_ts) + ".mkv"; LOG4CPLUS_INFO(log4cplus::Logger::getInstance(""), "Creating file " << name); return g_strdup(name.c_str()); }
3. 替换信号连接
在main函数里,把原来的format-location信号连接改成:
g_signal_connect(splitmuxsink, "format-location-full", G_CALLBACK(cb_splitmuxsink_format_location_full), NULL);
额外注意点
- 确保RTSP流的时间戳是正确的:如果流的源设备时间和本地系统时间对齐,这个转换是完全准确的;如果流用的是运行时间而非系统时间,可能需要结合
GstClock做进一步转换,但大多数IP摄像头的RTSP流会用系统时间戳。 - 可以保留原来的系统时间函数,用于日志或其他场景,但文件名一定要用流提供的段起始时间。
这样修改后,生成的文件名时间就会和对应视频段的实际起始时间完全一致,不会再出现延迟问题了!




