You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在GStreamer中连续播放N个视频为单个视频的问题排查

问题分析与解决方案

一、命令行无缝播放示例

直接使用playbin元素即可实现多视频无缝连续播放,这是最简洁的命令行方案:

gst-launch-1.0 playbin uri=file:///绝对路径/video0000.mp4 uris="file:///绝对路径/video0001.mp4,file:///绝对路径/video0002.mp4"

注意:替换绝对路径为你的视频文件实际存储路径,多个URI用英文逗号分隔

如果需要按通配符批量加载序列视频(比如video0000.mp4到video0002.mp4),可以结合shell命令自动生成URI列表:

gst-launch-1.0 playbin uris="$(printf 'file:///绝对路径/video%04d.mp4,' {0..2} | sed 's/,$//')"

二、C代码问题修复

你的代码核心错误是为每个视频创建了独立的完整播放链路(包含多个输出sink),同时错误地修改了pipeline的Pad模板,导致资源管理混乱。正确思路是复用一套播放链路,在当前视频播放结束(收到EOS信号)时切换到下一个视频文件。

修复后的代码如下:

#include <gst/gst.h>

typedef struct {
    GstElement *pipeline;
    GstElement *filesrc;
    GstElement *qtdemux;
    GstElement *h264parse;
    GstElement *avdec_h264;
    GstElement *videoconvert;
    GstElement *autovideosink;
    int current_video;
    int total_videos;
} PlayData;

static gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) {
    PlayData *play_data = (PlayData *)data;

    switch (GST_MESSAGE_TYPE(msg)) {
        case GST_MESSAGE_EOS: {
            // 当前视频播放完毕,切换到下一个
            play_data->current_video++;
            if (play_data->current_video < play_data->total_videos) {
                gchar *filename = g_strdup_printf("video%04d.mp4", play_data->current_video);
                // 将pipeline切换到READY状态,修改filesrc的文件路径
                gst_element_set_state(play_data->pipeline, GST_STATE_READY);
                g_object_set(play_data->filesrc, "location", filename, NULL);
                g_free(filename);
                // 重新启动播放
                gst_element_set_state(play_data->pipeline, GST_STATE_PLAYING);
            } else {
                // 所有视频播放完成,退出主循环
                g_main_loop_quit((GMainLoop *)data);
            }
            break;
        }
        case GST_MESSAGE_ERROR: {
            GError *err;
            gchar *debug_info;
            gst_message_parse_error(msg, &err, &debug_info);
            g_printerr("元素%s发生错误: %s\n", GST_OBJECT_NAME(msg->src), err->message);
            g_printerr("调试信息: %s\n", debug_info ? debug_info : "无");
            g_clear_error(&err);
            g_free(debug_info);
            g_main_loop_quit((GMainLoop *)data);
            break;
        }
        default:
            break;
    }
    return TRUE;
}

int main(int argc, char* argv[]) {
    gst_init(&argc, &argv);

    const int num_videos = 3;
    PlayData play_data = {
        .current_video = 0,
        .total_videos = num_videos
    };

    // 创建pipeline和所有元素
    play_data.pipeline = gst_pipeline_new("video_pipeline");
    play_data.filesrc = gst_element_factory_make("filesrc", "filesrc");
    play_data.qtdemux = gst_element_factory_make("qtdemux", "qtdemux");
    play_data.h264parse = gst_element_factory_make("h264parse", "h264parse");
    play_data.avdec_h264 = gst_element_factory_make("avdec_h264", "avdec_h264");
    play_data.videoconvert = gst_element_factory_make("videoconvert", "videoconvert");
    play_data.autovideosink = gst_element_factory_make("autovideosink", "autovideosink");

    if (!play_data.pipeline || !play_data.filesrc || !play_data.qtdemux || !play_data.h264parse ||
        !play_data.avdec_h264 || !play_data.videoconvert || !play_data.autovideosink) {
        g_printerr("元素创建失败\n");
        return -1;
    }

    // 设置第一个视频文件路径
    gchar *filename = g_strdup_printf("video%04d.mp4", play_data.current_video);
    g_object_set(play_data.filesrc, "location", filename, NULL);
    g_free(filename);

    // 将元素添加到pipeline
    gst_bin_add_many(GST_BIN(play_data.pipeline), play_data.filesrc, play_data.qtdemux,
                     play_data.h264parse, play_data.avdec_h264, play_data.videoconvert, play_data.autovideosink, NULL);

    // 链接静态Pad的元素(filesrc与qtdemux直接链接,qtdemux的输出Pad动态处理)
    if (!gst_element_link(play_data.filesrc, play_data.qtdemux)) {
        g_printerr("filesrc与qtdemux链接失败\n");
        return -1;
    }
    if (!gst_element_link_many(play_data.h264parse, play_data.avdec_h264, play_data.videoconvert, play_data.autovideosink, NULL)) {
        g_printerr("解码与输出链路链接失败\n");
        return -1;
    }

    // 处理qtdemux的动态输出Pad,找到H264视频流后链接到解析器
    g_signal_connect(play_data.qtdemux, "pad-added", G_CALLBACK((void (*)(GstElement*, GstPad*, gpointer))
    ([](GstElement *demux, GstPad *src_pad, gpointer data) {
        PlayData *pd = (PlayData *)data;
        GstPad *sink_pad = gst_element_get_static_pad(pd->h264parse, "sink");
        if (gst_pad_is_linked(sink_pad)) {
            g_object_unref(sink_pad);
            return;
        }
        // 验证Pad是否为H264视频流
        GstCaps *caps = gst_pad_get_current_caps(src_pad);
        const GstStructure *str = gst_caps_get_structure(caps, 0);
        if (g_strcmp0(gst_structure_get_name(str), "video/x-h264") == 0) {
            gst_pad_link(src_pad, sink_pad);
        }
        gst_caps_unref(caps);
        g_object_unref(sink_pad);
    })), &play_data);

    // 设置总线监听,处理EOS和错误消息
    GstBus *bus = gst_element_get_bus(play_data.pipeline);
    gst_bus_add_watch(bus, bus_callback, &play_data);
    gst_object_unref(bus);

    // 启动播放
    gst_element_set_state(play_data.pipeline, GST_STATE_PLAYING);

    // 启动主循环
    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(loop);

    // 资源清理
    gst_element_set_state(play_data.pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(play_data.pipeline));
    g_main_loop_unref(loop);

    return 0;
}

代码修复说明

  1. 复用播放链路:只创建一套解码输出链路,通过修改filesrc的文件路径实现多视频连续播放
  2. 动态Pad处理:qtdemux是动态输出Pad,通过pad-added信号监听并链接到H264解析器
  3. EOS信号响应:监听总线的EOS消息,在当前视频播放完毕后自动切换下一个文件并重启播放
  4. 规范资源管理:移除错误的pipeline Pad模板修改逻辑,元素的创建、链接和清理流程更严谨

编译命令(假设文件名为seamless_play.c):

gcc seamless_play.c -o seamless_play `pkg-config --cflags --libs gstreamer-1.0 gstreamer-video-1.0`

内容的提问来源于stack exchange,提问作者Hma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:34:52