音视频开发-- 坑整理

1. 解码时,一定要用avcodec_parameters_to_context(),将流的参数(stream->codecpar)复制到解码器中,否则某些流可能无法正常解码。

    //第七步,给给解码器上下文添加参数, avcodec_parameters_to_context():

    ret = avcodec_parameters_to_context(mp3decodercontext, mp3avstrem->codecpar);

2.解码第一帧前,一定要将解码器的timebase设置为流的timebase(即:dec_ctx->pkt_timebase = stream->time_base),否则提示“Could not update timestamps for skipped samples”。

    //for fix "error   Could not update timestamps for skipped samples. "
    mp3decodercontext->pkt_timebase = mp3avstrem->time_base;

实际上上述两个问题的本质是:

AVStream 和 AVCodecContext 得到的信息不一样,严格来说,是AVStream获得的多。具体分析一下:

AVStream 是从 av_find_best_stream获得的,而 AVCodecContext 是从直接通过 avcodec_find_decoder(enum AVCodecID id) 获得的,

而AVCodecID 就是固定的那几种,例如 AV_CODEC_ID_H264,可以想象,ffmpeg内部的实现一定是有限制的,其实现一定是参考 h264的spec 。因此才有了上述两个方法的必要性。

3. avcodec_send_packet 调用后,要立即av_packet_unref;但是avcodec_receive_frame调用后,不用 av_frame_unref.

当 调用

avcodec_send_packet(mp3decodercontext,mp3avpacket);

后,为什么调用

av_packet_unref(mp3avpacket);

呢?

但是从 

avcodec_receive_frame(mp3decodercontext, mp3avframe);

给mp3avframe 中写入数据后,为什么不用调用 

av_frame_unref(mp3avframe)

呢?

不对称呀,没有问题吗?翻看了一下ffmpeg提供的例子,确实是这么写的呀,难道例子有内存泄漏?

基于上述这个问题,查看了下ffmpeg如下两个方法的源码:

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
 avcodec_send_packet源码

从 源码我们可以看到,AVCodecContex这个数据结构中有一个AVCodecInternal

AVCodecInternal *avci = avctx->internal;

在没有error发生的时候,调用 av_packet_unref(avci->buffer_pkt);将原本里面的buffer_pkt数据清空然后还原成默认值。

下来就是在条件成立的时候,调用ret = av_packet_ref(avci->buffer_pkt, avpkt);

这个av_packet_ref方法内部的实现实际上也就是 将 第二个参数的内容 拷贝 给第一个参数。

然后整个方法就结束了,从整个流程来看,

我们再调用 int avcodec_send_packet方法的整个过程中,调用了该方法:

 av_packet_ref(avci->buffer_pkt, avpkt); 也就是说,增加了avpkt的引用计数,因此,我们在代码内部紧接着调用av_packet_unref(mp3avpacket); 是完全有必要的。

int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);

    if (avctx->internal->draining)
        return AVERROR_EOF;

    if (avpkt && !avpkt->size && avpkt->data)
        return AVERROR(EINVAL);

    av_packet_unref(avci->buffer_pkt);
    if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
        ret = av_packet_ref(avci->buffer_pkt, avpkt);
        if (ret < 0)
            return ret;
    }

    ret = av_bsf_send_packet(avci->bsf, avci->buffer_pkt);
    if (ret < 0) {
        av_packet_unref(avci->buffer_pkt);
        return ret;
    }

    if (!avci->buffer_frame->buf[0]) {
        ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

avcodec_receive_frame源码

int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    av_frame_unref(frame);

    if (av_codec_is_decoder(avctx->codec))
        return ff_decode_receive_frame(avctx, frame);
    return ff_encode_receive_frame(avctx, frame);
}

上来先将 frame 清空:av_frame_unref(frame); 联想我们在实际代码中总是会在一个循环中 avcodec_receive_frame数据,也就是说,只要不是最后一次,从第一次到中间的任何一次,都会将frame 清空。

那么最后一次的avframe是谁清空的呢?就是我们在自己写的代码中 

    av_frame_free(&mp3avframe);
 

如果是解码操作:执行 ff_decode_receive_frame函数

再看核心函数

av_frame_move_ref(frame, avci->buffer_frame); 给frame中填充数据,并转移ref,count并没有增加。

和 核心函数

ret = decode_receive_frame_internal(avctx, frame);

总结

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpacket)
avpacket 解码和解封装是2个线程,如果解封装后,调用此函数后,会将avpacket的引用计数加1 或者 复制一份(没有计数引用)。因此在调用了后,释放掉 avpacket
 

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
frame会每次清掉上一次frame,然后重新赋值,可以给同一个frame

4. QT中如果调用了C++代码,弹出窗口的那种,例如 cout<<"hello world"<<endl;

在有中文的情况下,请保证QT中的代码是 GB2312的,如果是UTF的,那么在windows上窗口显示的出来的是乱码,且有可能后面的log 就不显示了

5. 关于 avcodec_send_packet 调用后,调用 av_packet_unref;avcodec_receive_frame调用后,立即 调用 av_frame_unref.

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值