如何从FFmpeg libav获取decode_slice_header错误作为返回值?
我在MCU上使用libav v5.1解码H264视频流,通过WebRTC+RTP协议在互联网传输数据包。网络拥塞丢包时,libav会输出以下错误:
[h264 @ 0x7f8bd97f80c0] Frame num change from 77 to 7 [h264 @ 0x7f8bd97f80c0] decode_slice_header error [h264 @ 0x7f8bd97f80c0] Frame num change from 77 to 8 [h264 @ 0x7f8bd97f80c0] decode_slice_header error and etc...
网络恢复后不再丢包,但错误持续存在,解码器无法恢复,导致客户端视频冻结。我知道需要发送NACK或PLI,但没法拦截这些错误触发操作。查libav源码发现它只记录错误,不返回错误码。我检查了AVFrame->decode_error_flags(始终为0)和avcodec_receive_frame的返回值(出现错误时一直是AVERROR(EAGAIN)),是不是漏了什么?
相关代码
解码器初始化代码
//codec initialization ctx->decoder = avcodec_find_decoder(AV_CODEC_ID_H264); ctx->av_ctx = avcodec_alloc_context3(ctx->decoder); if (ctx->decoder->capabilities & AV_CODEC_CAP_TRUNCATED) ctx->av_ctx->flags |= AV_CODEC_FLAG_TRUNCATED; ctx->av_ctx->flags2 |= AV_CODEC_FLAG2_CHUNKS; avcodec_open2(ctx->av_ctx, ctx->decoder, NULL);
解码逻辑代码
//actual decoding ret = avcodec_send_packet(ctx->av_ctx, ctx->pkt); if (ret < 0) { printf("err send pkt to decoding: %d\n", ret); return NULL; } while (ret >= 0) { ret = avcodec_receive_frame(ctx->av_ctx, ctx->dec_frame); if (ctx->dec_frame->decode_error_flags) { //never executed printf("decode error flags %d\n", ctx->dec_frame->decode_error_flags); fflush(stdout); } if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { //need more pkts return ctx->scaled_frame; } else if (ret < 0) { //never executed printf("err recv pkt from decoding: %d\n", ret); return NULL; } }
问题分析与解决思路
捕获libav错误日志
libav默认将这类解码错误输出到stderr,你可以通过自定义日志回调捕获这些信息。注册av_log_set_callback,在回调中解析日志内容,当匹配到Frame num change或decode_slice_header error时,触发NACK/PLI请求。示例:static void custom_av_log(void* ptr, int level, const char* fmt, va_list vl) { char log_buf[1024]; vsnprintf(log_buf, sizeof(log_buf), fmt, vl); if (strstr(log_buf, "Frame num change") != NULL || strstr(log_buf, "decode_slice_header error") != NULL) { // 触发PLI请求的逻辑,比如向发送端发送RTCP PLI send_pli_request(); } // 保留原日志输出(可选) av_log_default_callback(ptr, level, fmt, vl); } // 初始化时注册回调 av_log_set_callback(custom_av_log);调整解码器参数增强错误恢复
- 开启
AV_CODEC_FLAG_GLOBAL_HEADER,确保解码器能正确跟踪帧序列:ctx->av_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - 通过
av_opt_set设置H264解码器的错误隐藏参数,提升恢复能力:av_opt_set(ctx->av_ctx->priv_data, "error_concealment", "3", 0); - 当检测到连续错误时,主动调用
avcodec_flush_buffers重置解码器状态,之后等待关键帧(PLI请求后获取)恢复解码。
- 开启
修复解码逻辑漏洞
你的解码循环中,avcodec_receive_frame返回AVERROR(EAGAIN)时直接返回scaled_frame,这可能导致客户端拿到旧帧或无效帧。正确逻辑应该是:返回NULL或标记需要更多数据,避免无效帧流出。修改后的解码循环:while (ret >= 0) { ret = avcodec_receive_frame(ctx->av_ctx, ctx->dec_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 需要更多数据包,不返回帧 return NULL; } else if (ret < 0) { printf("err recv pkt from decoding: %d\n", ret); // 重置解码器 avcodec_flush_buffers(ctx->av_ctx); return NULL; } // 解码成功,处理并返回帧 process_frame(ctx->dec_frame); return ctx->scaled_frame; }WebRTC层自动处理丢包
既然使用WebRTC,可以依赖其自身的丢包重传机制。检查WebRTC配置,开启NACK和PLI自动请求,比如设置RtpRtcpParameters中的相关参数,让WebRTC栈自动处理丢包恢复,减少libav层的手动干预。
内容的提问来源于stack exchange,提问作者frussian

