工作小记 ffmpeg avcodec_receive_frame 缓存问题

最近的流媒体项目中,在拉车辆流的过程中,发现调用ffmpeg avcodec_send_packetavcodec_receive_frame 时总是不能做到送入1包,立刻吐出对应pts的一帧,总是有缓存2-3帧的问题。对于普通的播放器已经足够,但是平行驾驶要求的极低延时就无法做到了。因为传输的h264流完全是只有IP帧,并没有依赖后向的B帧。所以开始调查问题解决办法。
1 首先尝试一些低延时参数,无效

AVDictionary *avdic=NULL;
av_dict_set(&avdic, "rtsp_transport", "tcp", 1);
av_dict_set(&avdic, "muxdelay", "0", 0);
av_dict_set(&avdic, "fflags", "nobuffer", 0);
av_dict_set(&avdic, "tune", "zerolatency", 0);
av_dict_set(&avdic, "max_delay", "0", 0);
av_dict_set(&avdic, "preset", "superfast", 0);
av_dict_set(&avdic, "packet-buffering", "0", 0);

2 查看官网发现有送入flush包的说法,用于刷新解码器,但是只是用于seek操作
https://ffmpeg.org/doxygen/4.0/group__lavc__encdec.html
3 开始dump ffmepg和webrtc两端的流,终于发现端倪
dump类工具

#include <fstream>

class H264Dump
{
public:
    //static H264Dump &Instance();
    H264Dump();
    ~H264Dump();

    //void inputFrame(const H264Frame::Ptr &frame);
    void inputPkt(const char* data, int size);
    static int i;
    std::ofstream out;
    // std::mutex frame_list_mutex;
    // std::list<std::string> frame_list;
};

int H264Dump::i = 0;
H264Dump::H264Dump()
{
    std::string _ = "player_11_5_" + std::to_string(++H264Dump::i) + ".h264";
    out = std::ofstream( _ , std::ios_base::out | std::ios::trunc | std::ios::binary);
}
H264Dump::~H264Dump()
{
    out.close();
}

void H264Dump::inputPkt(const char* data, int size)
{
    out.write(data, size);
    out.flush();
}

_ffmpeg_dump_h264.inputPkt((char*)packet->data, packet->size);

相同的文件,webrtc在收端,接到的流和原文件有一些小小的差异。在每个I帧(5)pps帧(8)之前,即**sps帧(7)**有一些不同,影响到了解码器能否立刻输出的情况!
在这里插入图片描述
H264码流中SPS PPS详解
vui参数的提问
H264裸流分析中,能获取哪些信息?
BSAnalyzer工具分析dump下的流
在这里插入图片描述
可以看到主要的不同就是在于num_reorder_framesmax_dec_frame_buffering的设置,webrtc是01,而我们的是22对于IPPP这种时不要缓存或者重排的,修改编码器后,果然解决了。
能正常拉到的流不一定就没问题,要仔细分析sps,pps等参数!

framerate=(time_scale/num_unit_in_tick)/2;
(pic_width_in_mbs_minus1+1 )*16 = 宽
(pic_height_in_map_units_minus1+1)*16 = 高

00 00 00 01 27 SPS
00 00 00 01 28 PPS
00 00 00 01 65(25) I帧
00 00 00 01 41 P帧

### 使用 `avcodec_send_packet` 和 `avcodec_receive_frame` 在 FFmpeg 库中,`avcodec_send_packet` 函数用于向解码器发送编码后的数据包以便处理。而 `avcodec_receive_frame` 则是从解码器获取已解码的数据帧。 #### 初始化编解码上下文 为了能够正常使用这两个函数,首先需要初始化一个合适的 `AVCodecContext` 实例并打开相应的编解码器: ```c // 找到所需的解码器 const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Failed to find decoder\n"); exit(1); } // 创建一个新的编解码上下文实例 AVCodecContext* codec_context = avcodec_alloc_context3(codec); if (!codec_context) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } // 设置参数... int ret; if ((ret = avcodec_open2(codec_context, codec, NULL)) < 0) { fprintf(stderr, "Cannot open codec: %s\n", av_err2str(ret)); exit(1); } ``` #### 向解码器提交数据包 当准备好要解码的数据包之后,可以使用 `avcodec_send_packet` 将其传递给解码器进行处理: ```c AVPacket packet; // 假设已经填充好了packet... ret = avcodec_send_packet(codec_context, &packet); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { fprintf(stderr, "Error during decoding: %s\n", av_err2str(ret)); } else if (ret == AVERROR(EAGAIN)){ // 需要更多输入或等待输出可用 } ``` 这里需要注意的是,在某些情况下可能会返回 `EAGAIN` 错误码表示当前无法接受新的数据包直到有空间为止;或者是遇到其他类型的错误时应该适当地处理这些异常情况[^2]。 #### 获取解码完成的视频帧 一旦成功地把数据送入了解码队列里边,则可以通过调用 `avcodec_receive_frame` 来取出已经被完全解析出来的图像信息: ```c AVFrame* frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Out of memory\n"); exit(1); } do { ret = avcodec_receive_frame(codec_context, frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF){ fprintf(stderr, "Error receiving a frame from the decoder.\n"); break; } if (ret >= 0) { // 处理解码得到的一帧画面 // 渲染或其他操作完成后记得释放资源 av_frame_unref(frame); } } while (ret == EAGAIN || ret == 0); av_frame_free(&frame); ``` 上述代码片段展示了如何持续尝试接收来自解码器的新帧直至不再可能获得更多的有效输出位置。如果此时仍然存在未被消耗掉的数据包则应当继续重复此过程来确保所有的媒体流都被正确地转换成可视化的形式[^3]。 #### 关闭编解码器 最后不要忘记关闭之前开启过的任何资源以防止内存泄漏等问题的发生: ```c avcodec_close(codec_context); av_freep(&codec_context->extradata); avcodec_free_context(&codec_context); ``` 通过以上步骤就可以实现基本的功能需求了。当然实际应用当中还涉及到很多细节上的优化以及对于不同场景下的特殊考虑,比如多线程支持、硬件加速等等特性都需要额外关注。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值