如何用FFmpeg sws_scaler API转换AV_PIX_FMT_VAAPI到YUV420?代码返回NULL求解
解决FFmpeg sws_scaler无法转换VAAPI硬件帧到YUV420P的问题
你遇到的sws_getContext返回NULL的核心原因是:sws_scaler是处理系统内存中像素数据的软件缩放器,它不支持直接与存储在GPU显存中的VAAPI硬件帧(AV_PIX_FMT_VAAPI)交互。要完成转换,你需要先把VAAPI硬件帧下载到系统内存,再用sws_scaler处理。
以下是具体的解决步骤和代码示例:
步骤1:将VAAPI硬件帧下载到系统内存
VAAPI帧存储在GPU显存中,无法直接被sws_scaler读取。你需要使用av_hwframe_transfer_data函数把硬件帧的数据拷贝到系统内存的AVFrame中,选择一个软件支持的像素格式(比如AV_PIX_FMT_NV12,这是VAAPI常用的兼容格式)作为中间格式。
步骤2:用sws_scaler转换为YUV420P
拿到系统内存中的帧后,就可以正常创建sws上下文并执行缩放转换了。
完整示例代码
// 假设你已经有解码后的VAAPI源帧src(格式AV_PIX_FMT_VAAPI) // 1. 创建用于接收硬件帧下载数据的系统内存帧 AVFrame* hw_download_frame = av_frame_alloc(); if (!hw_download_frame) { fprintf(stderr, "Failed to allocate download frame\n"); return -1; } // 设置中间格式为NV12(软件支持),宽高与源帧一致 hw_download_frame->format = AV_PIX_FMT_NV12; hw_download_frame->width = src->width; hw_download_frame->height = src->height; // 为中间帧分配内存 int ret = av_frame_get_buffer(hw_download_frame, 32); if (ret < 0) { fprintf(stderr, "Failed to get buffer for download frame\n"); av_frame_free(&hw_download_frame); return ret; } // 将VAAPI硬件帧下载到系统内存 ret = av_hwframe_transfer_data(hw_download_frame, src, 0); if (ret < 0) { fprintf(stderr, "Failed to transfer VAAPI frame to system memory\n"); av_frame_free(&hw_download_frame); return ret; } // 2. 创建sws缩放上下文(源格式为NV12,目标为YUV420P) SwsContext* conversion_context = sws_getContext( hw_download_frame->width, hw_download_frame->height, AV_PIX_FMT_NV12, scaler_->getWidth(), scaler_->getHeight(), AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr ); if (!conversion_context) { fprintf(stderr, "Failed to create sws context\n"); av_frame_free(&hw_download_frame); return -1; } // 3. 创建目标YUV420P帧并分配内存 AVFrame* dst_yuv = av_frame_alloc(); if (!dst_yuv) { fprintf(stderr, "Failed to allocate target YUV frame\n"); sws_freeContext(conversion_context); av_frame_free(&hw_download_frame); return -1; } dst_yuv->format = AV_PIX_FMT_YUV420P; dst_yuv->width = scaler_->getWidth(); dst_yuv->height = scaler_->getHeight(); ret = av_frame_get_buffer(dst_yuv, 32); if (ret < 0) { fprintf(stderr, "Failed to get buffer for target YUV frame\n"); av_frame_free(&dst_yuv); sws_freeContext(conversion_context); av_frame_free(&hw_download_frame); return ret; } // 4. 执行缩放转换 sws_scale( conversion_context, (const uint8_t* const*)hw_download_frame->data, hw_download_frame->linesize, 0, hw_download_frame->height, dst_yuv->data, dst_yuv->linesize ); // 5. 释放资源(必须做,避免内存泄漏) sws_freeContext(conversion_context); av_frame_free(&hw_download_frame); // 注意:dst_yuv使用完后也要记得释放 // av_frame_free(&dst_yuv);
额外注意事项
- 确保你的FFmpeg编译时启用了VAAPI支持(编译参数加
--enable-vaapi),否则av_hwframe_transfer_data等函数会不可用。 - 源VAAPI帧的
hw_frames_ctx必须正确初始化(这通常在解码前的硬件上下文配置阶段完成),否则帧传输会失败。 - 如果你不需要缩放,只是想把VAAPI帧转成YUV420P,可以直接把中间帧的格式设为
AV_PIX_FMT_YUV420P,跳过sws_scaler的步骤,这样效率更高。
内容的提问来源于stack exchange,提问作者Harshil Makwana
相关产品推荐
相关产品推荐

