libavcodec/H.264编码首帧无法即时解码的问题分析与解决
我在一端用libavcodec库搭配H.264编解码器生成视频流,将编码后的帧传输到另一台PC解码。遇到的问题是:接收并向解码器输入第一个编码帧的数据包后,无法解码该帧;只有收到下一帧时,前一帧才能解码,但当前帧又无法解码,最终解码器端始终存在一帧延迟。
我试过不同的preset(重点测试了'ultrafast')、'zerolatency'调优参数,以及AVCodecContext的多种bit_rate值,都未解决问题。还尝试在注入第一帧数据后用空数据包刷新解码器,排查是否因内部缓冲区优化导致,但帧仍无法解码。测试mpeg4等其他编解码器时,首帧可解码前的延迟帧数更多。
想问:这种情况是编解码内部机制导致的正常、不可避免现象吗?否则如何实现真正的零延迟?
补充配置信息
max_b_frames设为0(值越高延迟越大)pix_fmt设为AV_PIX_FMT_YUV420P
编辑:补充细节回复评论
(1) 解码器(或播放系统)是什么?
使用libavcodec编写的自定义解码器,解码后的帧通过OpenGL显示在屏幕上。
解码器初始化代码
parser_ = av_parser_init(AV_CODEC_ID_H264); codec_ = avcodec_find_decoder(AV_CODEC_ID_H264); context_ = avcodec_alloc_context3(codec_); context_->width = 1024; context_->height = 768; context_->thread_count = 1; if ((codec_->capabilities & AV_CODEC_CAP_TRUNCATED) == 0) { context_->flags |= AV_CODEC_FLAG_TRUNCATED; } if (avcodec_open2(context_, codec_, nullptr) < 0) { throw std::runtime_error{"avcodec_open2 failed"}; } avcodec_flush_buffers(context_);
播放器解码逻辑(定期调用)
auto result = avcodec_receive_frame(context_, frame_); if (!buffer_.empty()) { // 上传新数据包用于解码 int used; if (upload_package(buffer_.data(), buffer_.size(), &used)) { buffer_.erase(buffer_.begin(), buffer_.begin() + used); } } if (result == AVERROR(EAGAIN) || result == AVERROR_EOF || result < 0) { return false; } yuv_to_rgb(); return true;
布尔返回值表示解码是否成功,每次调用都会检查存储输入数据包的缓冲区并上传至libavcodec解码器。
数据包上传方法
bool upload_package(const void* data, const std::size_t size, int* used) { auto result = av_parser_parse2(parser_, context_, &packet_->data, &packet_->size, reinterpret_cast<const std::uint8_t*>(data), size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (result < 0) { return false; } *used = result; if (packet_->size != 0) { result = avcodec_send_packet(context_, packet_); if (result < 0) { return false; } } return true; }
(2) 若可能,将每个帧保存为
.bin文件并分享测试链接。
我会尝试处理这个问题...
(3) 展示H264编码器设置的C++示例代码...
编码器初始化代码
codec_ = avcodec_find_encoder(AV_CODEC_ID_H264); context_ = avcodec_alloc_context3(codec_); context_->bit_rate = 1048576; // 1xMbit; context_->width = 1024; context_->height = 768; context_->time_base = {1, 30}; // 30 fps context_->pix_fmt = AV_PIX_FMT_YUV420P; context_->thread_count = 1; av_opt_set(context_->priv_data, "preset", "ultrafast", 0); av_opt_set(context_->priv_data, "tune", "zerolatency", 0); avcodec_open2(context_, codec_, nullptr); frame_->format = AV_PIX_FMT_YUV420P; frame_->width = 1024; frame_->height = 768; av_image_alloc(frame_->data, frame_->linesize, 1024, 768, AV_PIX_FMT_YUV420P, 32);
帧编码代码
rgb_to_yuv(); frame_->pts = frame_num_++; auto result = avcodec_send_frame(context_, frame_); while (result >= 0) { result = avcodec_receive_packet(context_, packet_); if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) { return; } else if (result < 0) { throw std::runtime_error{"avcodec_receive_packet failed"}; } // 此处将数据包发送到解码器,完整数据包存储在上述buffer_中并通过avcodec_send_packet上传 // 补充说明:完整缓冲区/数据包会一次性上传 stream_video_data(packet_->data, packet_->size); } av_packet_unref(packet_); }
编辑2:问题定位
我已经找到问题所在:
对于每个输入数据包(编码帧),我先调用av_parser_parse2,再通过avcodec_send_packet发送数据,但未在buffer_为空时重新调用该流程。导致第一帧数据通过avcodec_send_packet上传后,av_parser_parse2从未被调用;接收第二帧时才触发该方法,第一帧得以正常解码,但第二帧的parse2同样未被调用,以此类推,最终形成了始终延迟一帧的现象。
本质是处理编码数据时av_parser_parse2与avcodec_send_packet的调用逻辑顺序错误。
内容的提问来源于stack exchange,提问作者Marek Kijo

