You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

GStreamer中Splitmuxsink生成文件的时间戳与视频实际起始时间不符的问题求助

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, &ltime);
    std::ostringstream s;
    s << std::put_time(&ltime, "%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, &ltime);
    std::ostringstream s;
    s << std::put_time(&ltime, "%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流会用系统时间戳。
  • 可以保留原来的系统时间函数,用于日志或其他场景,但文件名一定要用流提供的段起始时间。

这样修改后,生成的文件名时间就会和对应视频段的实际起始时间完全一致,不会再出现延迟问题了!

火山引擎 最新活动