嵌入式Linux下ffmpeg用h264_v4l2m2m解码视频报错的问题排查
问题分析与修复方案
核心原因
h264_v4l2m2m是基于V4L2的硬件解码器,它输出的视频帧内存是硬件DMA缓冲区,和软件解码器(比如默认的h264)输出的用户态内存帧本质不同:
- 硬件帧的
data指针可能未直接映射到用户态,或者多平面格式下部分指针为空 - 硬件帧的生命周期由V4L2驱动管理,提前释放会导致swscale访问无效内存
- 你的代码大概率直接复用了软解的帧处理逻辑,没适配硬件帧的格式与内存规则
修复步骤
1. 匹配swscale的输入格式与硬件帧格式
bad src image pointers报错主要是因为swscale初始化时指定的输入格式和硬件解码输出的帧格式不匹配。先打印解码后帧的格式,再用实际格式初始化swscale:
// 先获取硬件解码输出帧的实际格式 enum AVPixelFormat src_fmt = decoded_frame->format; // 初始化swscale上下文时用真实的src_fmt,别硬编码成YUV420P这类软解常用格式 struct SwsContext *sws_ctx = sws_getContext( decoded_frame->width, decoded_frame->height, src_fmt, dst_width, dst_height, AV_PIX_FMT_YUV420P, // 目标格式按需调整 SWS_BILINEAR, NULL, NULL, NULL );
2. 确保硬件帧内存可被用户态访问
硬件解码器输出的帧可能处于硬件地址空间,swscale无法直接访问,必须调用av_frame_make_writable将数据拷贝到用户态内存:
if (av_frame_make_writable(decoded_frame) < 0) { av_frame_unref(decoded_frame); continue; // 跳过无效帧,避免程序崩溃 }
如果想避免拷贝(追求性能),可以直接用V4L2的内存映射接口操作硬件缓冲区,但嵌入式场景下先优先用上面的方法快速解决问题。
3. 修正帧的释放顺序
硬件帧不能在swscale处理前调用av_frame_unref,否则V4L2驱动会立刻回收硬件缓冲区,导致swscale访问已失效的指针。正确流程是:
// 1. 解码得到decoded_frame // 2. 执行格式转换 sws_scale(sws_ctx, decoded_frame->data, decoded_frame->linesize, 0, decoded_frame->height, dst_frame->data, dst_frame->linesize); // 3. 处理转换后的dst_frame(比如显示、保存) // 4. 最后再释放decoded_frame av_frame_unref(decoded_frame);
4. 检查ffmpeg编译与驱动兼容性
- 确认编译ffmpeg 4.4.4时开启了
--enable-v4l2m2m和--enable-swscale选项,这两个是硬件解码和格式转换的必要开关 - 检查kernel 5.10.24的V4L2驱动是否存在DMA缓冲区相关BUG,可尝试给解码器设置
thread_count=1,避免多线程导致的帧竞争:av_opt_set_int(codec_ctx->priv_data, "thread_count", 1, 0);
验证标准
修复后运行程序,满足以下三点说明问题解决:
- 不再输出
bad src image pointers错误日志 - 程序运行时长和原视频一致(约13秒)
- 输出的视频帧无花屏、黑屏等异常
内容的提问来源于stack exchange,提问作者wangt13
相关产品推荐
相关产品推荐

