介绍
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就是完整的一帧码流,可以直接传给解码器解码。