GStreamer:使用gst::Buffer传递H264包时NAL异常,gst::Sample正常
解决GStreamer中AppSink提取Buffer推送到AppSrc时H264 NAL单元损坏的问题
问题场景
用Rust实现了两个GStreamer管道:
- 采集管道:以
appsink结尾,输出AVC格式的H264流 - 输出管道:以
appsrc开头,将流推送到RTMP服务器
直接拉取Sample推送到输出管道时一切正常,但提取Buffer(尝试copy_deep和clone)推送时,出现H264 NAL单元损坏的错误,日志提示:
[NULL @ 0x11de05440] missing picture in access unit with size 22553 [extract_extradata @ 0x11de058f0] No start code is found. rtmp://localhost/live/out: Invalid data found when processing input
插入h264parse后会报错未收到有效H264包,需要解决该问题以添加SEI消息。
采集管道代码:
let ingestPipeline = gst::parse_launch( "videotestsrc ! videoconvert ! videoscale ! videorate \ ! video/x-raw,width=1280,height=720,format=BGRx,framerate=30/1 \ ! videoconvert ! queue name=queuein ! timeoverlay time-mode=running-time \ ! x264enc speed-preset=ultrafast key-int-max=25 bitrate=2500 ! video/x-h264 ,profile=baseline,stream-format=(string)avc,alignment=(string)au,framerate=30/1,width=1280,height=720 ! appsink name=sink " )?.downcast::<gst::Pipeline>() .expect("Expected a gst::Pipeline");
输出管道代码:
let egressPipeline = gst::parse_launch( "appsrc is-live=true name=source caps=\"video/x-h264, profile=baseline,stream-format=avc,width=1280,height=720,framerate=30/1,alignment=au\" ! flvmux name=mux streamable=true ! queue ! rtmpsink location=rtmp://localhost/live/out")?.downcast::<gst::Pipeline>() .expect("Expected a gst::Pipeline");
问题原因
直接推送Buffer时丢失了Sample中的关键元数据:
- Caps与Extradata:H264的SPS/PPS等配置信息存储在Sample的Caps中(作为extradata),下游管道(如
flvmux、h264parse)需要这些信息来正确解析NAL单元 - 时间戳与单元边界:Sample包含PTS/DTS、duration等时间信息,以及访问单元(AU)的边界标记,直接推Buffer会丢失这些信息,导致下游无法正确识别帧结构
- AVC格式要求:原管道输出的是AVC格式(带4字节长度前缀的NAL单元),如果处理Buffer时破坏了这种格式,会导致解析失败
解决方案
核心是保留Sample的完整元数据,处理Buffer后重新构建包含所有必要信息的Sample再推送:
1. 正确处理Buffer并重建Sample
从Sample中提取Buffer后,处理完成(如添加SEI),必须用原Sample的Caps、时间戳等元数据构建新的Sample,再推送到AppSrc。
修改后的回调代码:
appsink.set_callbacks( gst_app::AppSinkCallbacks::builder() .new_sample(move |appsink| { // 拉取原始Sample let sample = appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?; let orig_caps = sample.caps().ok_or(gst::FlowError::Error)?; let orig_buffer = sample.buffer().ok_or(gst::FlowError::Error)?; // 深拷贝Buffer用于处理(保留所有内存区域和元数据) let mut modified_buffer = orig_buffer.copy_deep().map_err(|_| gst::FlowError::Error)?; // -------------------------- // 在这里添加SEI消息处理逻辑 // 示例:获取H264的extradata,构造SEI NAL单元并插入Buffer // 注意:必须遵循AVC格式,用4字节长度前缀标记NAL单元 // let video_caps = orig_caps.downcast_ref::<gst_video::VideoCaps>().unwrap(); // let h264_info = video_caps.info().downcast_ref::<gst_video::VideoCodecFrameH264>().unwrap(); // let sei_nal = create_sei_nal_unit(); // 自定义函数生成SEI NAL // insert_avc_nal_into_buffer(&mut modified_buffer, sei_nal); // 插入到Buffer中 // -------------------------- // 用原Sample的元数据构建新Sample let new_sample = gst::Sample::builder() .buffer(&modified_buffer) .caps(&orig_caps) .pts(sample.pts()) .dts(sample.dts()) .duration(sample.duration()) .build() .map_err(|_| gst::FlowError::Error)?; // 推送新Sample到AppSrc appsrc.push_sample(&new_sample).map_err(|_| gst::FlowError::Error)?; Ok(gst::FlowSuccess::Ok) }) .build(), );
2. 确保SEI插入符合AVC格式
添加SEI时需注意:
- 原管道输出的是AVC格式(
stream-format=avc),NAL单元前是4字节大端长度前缀,而非0x00000001起始码 - SEI NAL单元的类型是6,构造时要符合H.264规范,插入时要保证不破坏原有AU的结构
- 如果需要在关键帧前插入SEI,要确保插入位置正确,不影响SPS/PPS的传递
3. 正确配置h264parse(可选)
如果要使用h264parse,需要确保:
- AppSrc的Caps中包含正确的extradata(可从采集管道的Sample Caps中提取)
- 推送的第一个Sample必须包含SPS/PPS,或者在AppSrc启动前设置带有extradata的Caps
- 修改输出管道为:
appsrc is-live=true name=source caps="..." ! h264parse config-interval=1 ! flvmux ...
关键注意点
- 永远不要单独推送Buffer,必须用Sample包裹,保留所有元数据
- 处理H264 Buffer时,严格遵循当前流的格式(AVC/Byte-Stream)
- 调试时可以用
gst-inspect-1.0 h264parse查看参数,用gst-launch-1.0测试管道的正确性
内容的提问来源于stack exchange,提问作者lennel
相关产品推荐
相关产品推荐

