FFmpeg HEVC编码YUV数据图像异常,求问题排查方案
HEVC编码YUV图像显示异常的原因分析
我使用FFmpeg对YUV数据进行HEVC编码后,图像显示异常(画面错乱、色彩失真),但通过以下命令行可正常编码出符合预期的图像:
ffmpeg -f rawvideo -s 480x256 -pix_fmt yuv420p -i origin.yuv -c:v hevc -f hevc -x265-params keyint=1:crf=18 out.h265
以下是我的编码代码:
void H265ImageCodec::InitCPUEncoder() { avcodec_register_all(); AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H265); CHECK(encoder) << "Can not find encoder with h265."; // context encode_context_ = avcodec_alloc_context3(encoder); CHECK(encode_context_) << "Could not allocate video codec context."; encode_context_->codec_id = AV_CODEC_ID_H265; encode_context_->profile = FF_PROFILE_HEVC_MAIN; encode_context_->codec_type = AVMEDIA_TYPE_VIDEO; encode_context_->width = width_; // it's 480 encode_context_->height = height_; // it's 256 encode_context_->bit_rate = 384 * 1024; encode_context_->pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P; encode_context_->time_base = (AVRational){1, 25}; encode_context_->framerate = (AVRational){25, 1}; AVDictionary* options = NULL; av_dict_set(&options, "preset", "ultrafast", 0); av_dict_set(&options, "tune", "zero-latency", 0); av_opt_set(encode_context_->priv_data, "x265-params", "keyint=1:crf=18", 0); // crf: Quality-controlled variable bitrate avcodec_open2(encode_context_, encoder, &options); encode_frame_ = av_frame_alloc(); encode_frame_->format = encode_context_->pix_fmt; encode_frame_->width = encode_context_->width; encode_frame_->height = encode_context_->height; av_frame_get_buffer(encode_frame_, 0); // packet init encode_packet_ = av_packet_alloc(); } std::string H265ImageCodec::EncodeImage(std::string_view raw_image) { av_packet_unref(encode_packet_); av_frame_make_writable(encode_frame_); const int64 y_size = width_ * height_; int64 offset = 0; memcpy(encode_frame_->data[0], raw_image.data() + offset, y_size); offset += y_size; memcpy(encode_frame_->data[1], raw_image.data() + offset, y_size / 4); offset += y_size / 4; memcpy(encode_frame_->data[2], raw_image.data() + offset, y_size / 4); avcodec_send_frame(encode_context_, encode_frame_); int ret = avcodec_receive_packet(encode_context_, encode_packet_); CHECK_EQ(ret, 0) << "receive encode packet ret: " << ret; std::string h265_frame(reinterpret_cast<char*>(encode_packet_->data), encode_packet_->size); return h265_frame; }
可能的原因及修正方案
1. YUV数据拷贝未处理行对齐问题
FFmpeg的AVFrame中,每个颜色平面的linesize(行跨度)会因内存对齐要求,可能大于图像实际宽度。直接用memcpy按平面总大小拷贝数据,会导致后续行的数据错位,最终画面错乱。命令行中FFmpeg会自动处理YUV数据的对齐逻辑,所以无异常。
修正方式:逐行拷贝数据,使用linesize作为每行的跨度:
std::string H265ImageCodec::EncodeImage(std::string_view raw_image) { av_packet_unref(encode_packet_); av_frame_make_writable(encode_frame_); const int y_size = width_ * height_; const int uv_width = width_ / 2; const int uv_height = height_ / 2; // 拷贝Y平面 for (int i = 0; i < height_; ++i) { memcpy(encode_frame_->data[0] + i * encode_frame_->linesize[0], raw_image.data() + i * width_, width_); } // 拷贝U平面 int offset = y_size; for (int i = 0; i < uv_height; ++i) { memcpy(encode_frame_->data[1] + i * encode_frame_->linesize[1], raw_image.data() + offset + i * uv_width, uv_width); } // 拷贝V平面 offset += y_size / 4; for (int i = 0; i < uv_height; ++i) { memcpy(encode_frame_->data[2] + i * encode_frame_->linesize[2], raw_image.data() + offset + i * uv_width, uv_width); } // 后续编码逻辑不变 avcodec_send_frame(encode_context_, encode_frame_); int ret = avcodec_receive_packet(encode_context_, encode_packet_); CHECK_EQ(ret, 0) << "receive encode packet ret: " << ret; std::string h265_frame(reinterpret_cast<char*>(encode_packet_->data), encode_packet_->size); return h265_frame; }
2. 未设置AVFrame的显示时间戳(pts)
编码器依赖pts参数做帧类型判断和时序管理,即使单帧编码,缺少pts也可能导致编码器行为异常,尤其是在keyint=1要求每帧为I帧的配置下。
修正方式:在编码前为encode_frame_设置递增的pts:
std::string H265ImageCodec::EncodeImage(std::string_view raw_image) { // ... 前面的拷贝逻辑 ... // 设置pts,维护帧计数器 static int64_t frame_idx = 0; encode_frame_->pts = frame_idx++; // 转换为编码器time_base对应的时间戳 encode_frame_->pts = av_rescale_q(encode_frame_->pts, (AVRational){1,25}, encode_context_->time_base); avcodec_send_frame(encode_context_, encode_frame_); // ... 后续逻辑 ... }
3. 参数设置方式不一致
代码中混合使用av_opt_set和AVDictionary传递编码器参数,可能导致参数传递异常。建议统一用AVDictionary传递所有参数。
修正方式:将x265-params加入options字典:
AVDictionary* options = NULL; av_dict_set(&options, "preset", "ultrafast", 0); av_dict_set(&options, "tune", "zero-latency", 0); av_dict_set(&options, "x265-params", "keyint=1:crf=18", 0); // 移到这里 avcodec_open2(encode_context_, encoder, &options);
同时删除原有的av_opt_set调用。
内容的提问来源于stack exchange,提问作者375
相关产品推荐
相关产品推荐

