在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; }
代码修复说明
- 复用播放链路:只创建一套解码输出链路,通过修改
filesrc的文件路径实现多视频连续播放 - 动态Pad处理:
qtdemux是动态输出Pad,通过pad-added信号监听并链接到H264解析器 - EOS信号响应:监听总线的EOS消息,在当前视频播放完毕后自动切换下一个文件并重启播放
- 规范资源管理:移除错误的
pipelinePad模板修改逻辑,元素的创建、链接和清理流程更严谨
编译命令(假设文件名为seamless_play.c):
gcc seamless_play.c -o seamless_play `pkg-config --cflags --libs gstreamer-1.0 gstreamer-video-1.0`
内容的提问来源于stack exchange,提问作者Hma
相关产品推荐
相关产品推荐

