ffmpeg解码H264缺少帧的解决办法

最近用ffmpeg解码H264裸码流文件,发现解码总是少几帧。上网查了些资料,解决了。

当使用avcodec_decode_video2时,如果第三个参数的值为1,则表示完成一帧的解码,如果为0,表示没有解码完成。此时需要计算未解码的帧数,以便再次调用avcodec_decode_video2函数。如下getFrame函数,当解码成功一帧时返回,如果没有解码,则累加。另外实现getSkippedFrame函数,将之前未解码的数据再次解码。

代码如下:

int CH264Decoder::getFrame(unsigned char** yuvBuffer, unsigned char** rgbBuffer, int* size, int* width, int* height)
{
    int got_picture = 0;    // 找到帧标志
    int len = 0;
    AVPacket avpkt;

    av_init_packet(&avpkt);
    //int frame = 0;
    // av_read_fram返回下一帧,发生错误或文件结束返回<0
    while (av_read_frame(m_fmtctx, &avpkt) >= 0)
    {
        // 解码视频流
        if (avpkt.stream_index == m_videoStream)
        {
            len = avcodec_decode_video2(m_avctx, m_picture, &got_picture, &avpkt);
            if (len < 0)
            {
                debug("error while decoding frame.\n");
                return -1;
            }
            if (got_picture)
            {
                m_picWidth  = m_avctx->width;
                m_picHeight = m_avctx->height;
                // 传出原始数据指针,由于内部已经申请了,不用再开辟数据
                if (yuvBuffer != NULL)
                {
                    *yuvBuffer = m_picture->data[0];
                    if (size != NULL)
                    {
                        *size = len; // to check
                    }
                }
                if (rgbBuffer != NULL)
                {
                    *rgbBuffer = convertToRgb();
                    if (size != NULL)
                    {
                        *size = m_picWidth * m_picHeight * 3; // 上面指定了rgb24,所以是w*h*3
                    }
                }
                //printf("frame fmt: %d\n", m_picture->format);


                if (width != NULL)
                {
                    *width = m_picWidth;
                }
                if (height != NULL)
                {
                    *height = m_picHeight;
                }
                //printf("bit_rate: %d width: %d height:%d\n", m_avctx->bit_rate, m_avctx->width, m_avctx->height);
                return 1;
            } // end of got picture
            else
            {
                m_skippedFrame++;
                //debug("skipped count: %d\n", m_skippedFrame);
            }
        } // end of video stream

        av_free_packet(&avpkt);
    } // end of read frame

    return 0;
}

上面的函数已经统计了缓存起来的帧总数,下面根据m_skippedFrame的值再调用avcodec_decode_video2解码。

int CH264Decoder::getSkippedFrame(unsigned char** yuvBuffer, unsigned char** rgbBuffer, int* size, int* width, int* height)
{
    int got_picture = 0;    // 找到帧标志
    int len = 0;
    AVPacket avpkt;

    memset(&avpkt, '\0', sizeof(AVPacket));
    av_init_packet(&avpkt);

    // 是否还有缓存的帧
    while (m_skippedFrame-- > 0)
    {
        // 注:avpkt要清空data和size,否则无法解码
        avpkt.data = NULL;
        avpkt.size = 0;
        // 解码视频流
        len = avcodec_decode_video2(m_avctx, m_picture, &got_picture, &avpkt);
        if (len < 0)
        {
            debug("error while decoding frame.\n");
            return -1;
        }
        if (got_picture)
        {
            // 传出原始数据指针,由于内部已经申请了,不用再开辟数据
            if (yuvBuffer != NULL)
            {
                *yuvBuffer = m_picture->data[0];
            }
            if (rgbBuffer != NULL)
            {
                *rgbBuffer = convertToRgb();
            }
            //printf("frame fmt: %d\n", m_picture->format);
            if (size != NULL)
            {
                *size = len;
            }
            m_picWidth  = m_avctx->width;
            m_picHeight = m_avctx->height;
            if (width != NULL)
            {
                *width = m_picWidth;
            }
            if (height != NULL)
            {
                *height = m_picHeight;
            }
            //printf("bit_rate: %d width: %d height:%d\n", m_avctx->bit_rate, m_avctx->width, m_avctx->height);
            return 1;
        } // end of got picture

        av_packet_unref(&avpkt);
    } // end of read frame

    return 0;
}

2016.4.23 周日的补充:

注意,在调用avcodec_decode_video2对缓存的帧解码时,一定要将avpkt.data置为NULL,并将avpkt.size置为0,否则会解码不成功。在调用av_init_packet前,也要手工对AVPacket结构体进行清零操作。直到最后在VS环境使用该类时才发现这个问题。在解码函数avcodec_decode_video2注释中有如下说明:

 * @note Codecs which have the CODEC_CAP_DELAY capability set have a delay
 * between input and output, these need to be fed with avpkt->data=NULL,
 * avpkt->size=0 at the end to return the remaining frames.



李迟 2015.12.12 中午

你可能感兴趣的:(FFMPEG,H.264/H.265)