介绍

FFMPEG解码音视频的一般来讲,都是直接从媒体容器文件(网络码流或者封装文件)中,读取出AVPaket传个解码器。但一般音视频解码并不是在这样的场景下,而是直接给解码器传送裸码流(AAC、h264等),此时我们需要知道每次传给解码器的音视频数据大小,即每帧音频/视频大小。AVCodecParser可通过音视频裸码流解析出每帧的大小等信息。


AVCodecParser

解析器通过音视频标准解析出每帧音视频信息包括长度信息等。其核心函数如下:

/**
 * Parse a packet.
 *
 * @param s             parser context.
 * @param avctx         codec context.
 * @param poutbuf       set to pointer to parsed buffer or NULL if not yet finished.
 * @param poutbuf_size  set to size of parsed buffer or zero if not yet finished.
 * @param buf           input buffer.
 * @param buf_size      input length, to signal EOF, this should be 0 (so that the last frame can be output).
 * @param pts           input presentation timestamp.
 * @param dts           input decoding timestamp.
 * @param pos           input byte position in stream.
 * @return the number of bytes of the input bitstream used.
 *
 * Example:
 * @code
 *   while(in_len){
 *       len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
 *                                        in_data, in_len,
 *                                        pts, dts, pos);
 *       in_data += len;
 *       in_len  -= len;
 *
 *       if(size)
 *          decode_frame(data, size);
 *   }
 * @endcode
 */
int av_parser_parse2(AVCodecParserContext *s,
                     AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts,
                     int64_t pos);

其中poutbuf指向解析后输出的压缩编码数据帧,buf指向输入的压缩编码数据。如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理。

从上边的参数中我们发现,解析码流帧信息,主要用到AVCodecParserContext 、AVCodecContext 等,这些参数的获取和使用可以参考以下例程:

示例

本示例主要是通过读取音视频裸码流文件,然后使用av_parser_parse2解析出每帧的信息,并将解析出来的每帧长度保存文件。已验证通过的码流类型有H264、Mpeg4、MP3,但aac解析出的长度异常、g719直接崩溃,有待经一步研究。

#include <stdio.h>

extern "C"
{
#include "libavcodec/avcodec.h"
};


#define ASSERT(X) if(!(X)){printf("####[%s:%d]assert failed:%s\n", __FUNCTION__, __LINE__, #X);}
#define _READ_BUF_SIZE (4<<10)
int main()
{

    int dwRet = 0;//各个接口返回值
    AVCodec* ptCodec = NULL;
    AVCodecContext* ptCodecCtx = NULL;
    AVCodecParserContext* ptCodecParserCtx = NULL;
    AVPacket tavPacket;
    av_init_packet(&tavPacket);

    enum AVCodecID emVidDecId = AV_CODEC_ID_MP3;//AV_CODEC_ID_MPEG4;//AV_CODEC_ID_H264;

    uint8_t* pbyReadBuf = new uint8_t[_READ_BUF_SIZE];//每次从文件中读取数据的缓冲
    memset(pbyReadBuf, 0, _READ_BUF_SIZE);
    int dwCurBufSize = 0;           //记录当前缓冲中剩余有效数据长度
    uint8_t* pbyCurBuf = NULL;              //记录缓冲读指针位置
    const char* pcbyInputFileName = "mp3_3.dat";//"704x576.mpeg4";//"1080P.h264";
    const char* pcbyOutPutFileName = "mp3_3.len";//"704x566.mpeg4.len";//"1080P.len"; //这里通过解析裸码流得到每帧长度
    FILE* pfInput = fopen(pcbyInputFileName, "rb");
    if (!pfInput)
    {
        ASSERT(pfInput != NULL);
        return -1;
    }

    FILE* pfOutPut = fopen(pcbyOutPutFileName, "wb");
    if (!pfOutPut)
    {
        ASSERT(pfOutPut != NULL);
        return -1;
    }

    avcodec_register_all();//注册解码器

    ptCodec = avcodec_find_decoder(emVidDecId);//获取指定类型的解码器
    if(!ptCodec)
    {
        ASSERT(ptCodec != NULL);
        return -1;
    }

    ptCodecParserCtx = av_parser_init(emVidDecId);//根据解码器类型获取解析器上下文
    if (!ptCodecParserCtx)
    {
        ASSERT(ptCodecParserCtx != NULL);
        return -1;
    }

    ptCodecCtx = avcodec_alloc_context3(ptCodec);//根据解码器获取解码器上下文
    if (!ptCodecCtx)
    {
        ASSERT(ptCodecCtx != NULL);
        return -1;
    }


    for (;;)
    {
        dwCurBufSize = fread(pbyReadBuf, 1, _READ_BUF_SIZE, pfInput);
        if (feof(pfInput))
        {
            printf("end of file\n");
            break;
        }
        if (dwCurBufSize == 0)
        {
            ASSERT(dwCurBufSize != 0);
            return -1;
        }
        pbyCurBuf = pbyReadBuf;

        while (dwCurBufSize > 0)
        {
            //解析处理,tavPacket.size不为0,说明解析出完整的一帧数据
            int len = av_parser_parse2(ptCodecParserCtx, ptCodecCtx, 
                &tavPacket.data, &tavPacket.size,      //输出
                pbyCurBuf, dwCurBufSize,                //输入
                AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

            pbyCurBuf += len; //移动缓冲读指针
            dwCurBufSize -= len;//剩余缓冲数据长度

            if (tavPacket.size == 0)
            {
                //如果输出size为0 说明输入长度不够解析为完整的一帧,需要继续输入数据
                continue;
            }

            //打印输出帧信息
            printf("Packet Seq Num:%d\t", ptCodecParserCtx->output_picture_number);
            printf("KeyFrame:%d pts:%d, dts:%d, duration:%d\t", ptCodecParserCtx->key_frame, 
                ptCodecParserCtx->pts, ptCodecParserCtx->dts, ptCodecParserCtx->duration);
            switch(ptCodecParserCtx->pict_type)
            {
            case AV_PICTURE_TYPE_I:
                printf("\tPacket Type:I\t");
                break;
            case AV_PICTURE_TYPE_P:
                printf("\tPacket Type:P\t");
                break;
            case AV_PICTURE_TYPE_B:
                printf("\tPacket Type:B\t");
                break;
            default:
                printf("\tPacket Type:error:%d\t", ptCodecParserCtx->pict_type);
                break;
            }

            printf("\tPacket Size:%d\n", tavPacket.size);
            //将长度信息保存到文件中
            fprintf(pfOutPut, "%d\n", tavPacket.size);
        }

    }   
}

以上解析出来的AvPacket就是完整的一帧码流,可以直接传给解码器解码。