GStreamer创建视频片段报错gst_segment_do_seek断言失败,求正确方案
GStreamer 创建视频片段的正确方法
问题背景
用FFmpeg仅靠-ss和-t参数就能轻松截取视频片段,但GStreamer中gst-launch-1.0没有直接对应参数,编写代码调用gst_element_seek时易触发错误。比如测试场景中,播放模式(启用PLAY宏)可正常播放12-20秒的视频片段,但转码导出模式(默认逻辑)会抛出断言错误:
gst_segment_do_seek: assertion 'segment->format == format' failed
测试环境:Windows + GStreamer 1.24.11(最新稳定版)
错误原因
转码管道中的mp4mux封装器初始化后,会将流的segment格式锁定为字节位置(GST_FORMAT_BYTES),但代码中调用gst_element_seek时使用的是时间格式(GST_FORMAT_TIME),两者格式不匹配触发了断言失败。而播放模式中的视频渲染sink不会修改segment格式,因此能正常工作。
正确实现方式
方式1:用gst-launch-1.0命令行实现(无需编写代码)
gst-launch-1.0支持通过--seek参数直接触发截取逻辑,无需手动写代码:
# 截取12秒至20秒的视频片段,导出为MP4(包含音视频流) gst-launch-1.0 filesrc location=sintel_trailer-480p.mp4 ! qtdemux name=demux \ demux.video_0 ! h264parse ! nvh264dec ! nvh264enc ! h264parse ! mp4mux.video_0 \ demux.audio_0 ! aacparse ! faac ! aacparse ! mp4mux.audio_0 \ mp4mux ! filesink location=clip.mp4 \ --seek 1.0 -1 0 12000000000 1 0 20000000000
参数说明:
--seek <速率> <格式> <标志> <起始类型> <起始时间> <结束类型> <结束时间>- 这里
format=-1表示自动适配格式,12000000000对应12秒(单位:纳秒),20000000000对应20秒
方式2:修复C代码逻辑
要让转码管道支持时间格式的seek,需先重置mp4mux的状态,避免其锁定字节格式,同时补充音频流的处理(原代码仅处理视频流,会导致封装器工作异常)。修改后的代码如下:
#include <gst/gst.h> GMainLoop* loop = NULL; GstElement* pipeline = NULL; GstElement* mux = NULL; gboolean gst_bus_handler(GstBus* bus, GstMessage* message, gpointer data) { GstMessageType type = message->type; if (type == GST_MESSAGE_EOS) { g_main_loop_quit(loop); } else if (type == GST_MESSAGE_STATE_CHANGED) { GstState old_state, new_state; gst_message_parse_state_changed(message, &old_state, &new_state, NULL); if (GST_MESSAGE_SRC(message) == GST_OBJECT_CAST(pipeline)) { if (old_state != GST_STATE_PAUSED && new_state == GST_STATE_PAUSED) { // 重置muxer状态,解除字节格式锁定 gst_element_set_state(mux, GST_STATE_READY); gst_element_set_state(mux, GST_STATE_PAUSED); // 执行时间格式的seek操作 gst_element_seek( pipeline, 1.0, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), GST_SEEK_TYPE_SET, 12 * GST_SECOND, GST_SEEK_TYPE_SET, 20 * GST_SECOND); } } } else if (type == GST_MESSAGE_ASYNC_DONE) { gst_element_set_state(pipeline, GST_STATE_PLAYING); } else if (type == GST_MESSAGE_ERROR) { GError *err; gchar *debug_info; gst_message_parse_error(message, &err, &debug_info); g_printerr("Error from %s: %s\n", GST_OBJECT_NAME(message->src), err->message); g_printerr("Debug info: %s\n", debug_info ? debug_info : "none"); g_clear_error(&err); g_free(debug_info); g_main_loop_quit(loop); } return TRUE; } int main(int argc, char* argv[]) { gst_init(NULL, NULL); // 创建包含音视频处理的完整管道 pipeline = gst_parse_launch( "filesrc location=sintel_trailer-480p.mp4 name=src ! qtdemux name=demux" " demux.video_0 ! nvh264dec name=dec ! nvh264enc name=enc ! h264parse name=parse ! mux.video_0" " demux.audio_0 ! aacparse ! faac ! aacparse ! mux.audio_0" " mp4mux name=mux ! filesink name=sink location=clip.mp4", NULL); if (!pipeline) exit(-1); // 获取mp4mux元素用于状态重置 mux = gst_bin_get_by_name(GST_BIN(pipeline), "mux"); if (!mux) { g_printerr("Failed to get mux element\n"); exit(-1); } loop = g_main_loop_new(NULL, FALSE); GstBus* bus = gst_element_get_bus(pipeline); guint watch_id = gst_bus_add_watch(bus, gst_bus_handler, NULL); gst_object_unref(bus); GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("Unable to set pipeline to PAUSED state\n"); } else { g_main_loop_run(loop); } g_printerr("Setting pipeline state to NULL\n"); gst_element_set_state(pipeline, GST_STATE_NULL); g_source_remove(watch_id); g_main_loop_unref(loop); gst_object_unref(mux); gst_object_unref(pipeline); gst_deinit(); return 0; }
方式3:使用GStreamer编辑服务(GES)
如果需要更复杂的视频剪辑操作,官方推荐使用GES框架,它封装了seek、剪辑、拼接等逻辑,代码更简洁:
#include <ges/ges.h> int main(int argc, char* argv[]) { ges_init(); // 创建导出项目 GESProject *project = ges_project_new(GES_URI_FORMAT_FILE, "clip.mp4"); GESTimeline *timeline = ges_project_get_timeline(project); // 添加视频源并设置截取范围(12秒开始,持续8秒) GESClip *clip = ges_uri_clip_new("sintel_trailer-480p.mp4"); ges_clip_set_inpoint(clip, 12 * GST_SECOND); ges_clip_set_duration(clip, 8 * GST_SECOND); // 将片段添加到时间线 GESLayer *layer = ges_layer_new(); ges_timeline_add_layer(timeline, layer); ges_layer_add_clip(layer, clip); // 渲染导出 ges_project_render(project, NULL); // 释放资源 g_object_unref(project); ges_deinit(); return 0; }
内容的提问来源于stack exchange,提问作者F32
相关产品推荐
相关产品推荐

