ffmpeg的av_parser_parse2( )

1 概

执行完av_parser_parse2()后不管有没有构成一个packet,av_parser_parse2()告知我们已使用数据都可以不用再管了,因为其内部拷了一份;当然,如果提供buf数据是足够的,能通过返回的pkt.size判断有没有packet

2 正文

2.1 ffmpeg的解码流程

因为av_parser_parse2() 主要是用来在解码的时候解析读取数据,所以在这里提一下解码更容易理解这个函数,下面的程序从殷汶杰抄来的一段使用ffmpeg解码的例子,解码的流程很好理解,主要涉及5个的东西

Codec: 编解码器

CodecParserCtx: 码流解析器

CodecContext: 编解码context,存放着编解码的上下文

packet: 压缩数据

frame: 解压数据

解码的流程其实很简单,就是通过CodecParserCtx使用av_parser_parse2()从码流中读取完整的一帧数据,然后通过CodecContextpacket使用avcodec_decode_video2()进行解码

#include <stdio.h>

#include "InputOutput.h"
#include "Decoder.h"


void write_out_yuv_frame(const CodecCtx &ctx, IOParam &in_out)
{
	uint8_t **pBuf	= ctx.frame->data;
	int*	pStride = ctx.frame->linesize;
	
	for (int color_idx = 0; color_idx < 3; color_idx++)
	{
		int		nWidth	= color_idx == 0 ? ctx.frame->width : ctx.frame->width / 2;
		int		nHeight = color_idx == 0 ? ctx.frame->height : ctx.frame->height / 2;
		for(int idx=0;idx < nHeight; idx++)
		{
			fwrite(pBuf[color_idx],1, nWidth, in_out.pFout);
			pBuf[color_idx] += pStride[color_idx];
		}
		fflush(in_out.pFout);
	}
}

bool Open_deocder(CodecCtx &ctx)
{
	//注册编解码器对象
	avcodec_register_all();	

	//初始化AVPacket对象
	av_init_packet(&(ctx.pkt));

	//根据CODEC_ID查找AVCodec对象
	ctx.pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
	if (!ctx.pCodec) 
	{
		fprintf(stderr, "Codec not found
");
		return false;
	}

	//根据AVCodec对象分配AVCodecContext
	ctx.pCodecContext = avcodec_alloc_context3(ctx.pCodec);
	if (!ctx.pCodecContext)
	{
		fprintf(stderr, "Could not allocate video codec context
");
		return false;
	}

	if (ctx.pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
		ctx.pCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames

	//根据CODEC_ID初始化AVCodecParserContext对象
	ctx.pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
	if (!ctx.pCodecParserCtx)
	{
		printf("Could not allocate video parser context
");
		return false;
	}

	//打开AVCodec对象
	if (avcodec_open2(ctx.pCodecContext, ctx.pCodec, NULL) < 0)
	{
		fprintf(stderr, "Could not open codec
");
		return false;
	}

	//分配AVFrame对象
	ctx.frame = av_frame_alloc();
	if (!ctx.frame) 
	{
		fprintf(stderr, "Could not allocate video frame
");
		return false;
	}

	return true;
}


int main(int argc, char **argv)
{
	uint8_t *pDataPtr = NULL;
	int uDataSize = 0;
	int got_picture, len;

	CodecCtx ctx;
	IOParam inputoutput;
	inputoutput.pNameIn = "/media/soccor.264";
	inputoutput.pNameOut= "./soccor.yuv";


	Open_files(inputoutput);				//打开输入输出文件
	
	uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	
	memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
	
	printf("Decode video file %s to %s
", argv[1], argv[2]);

	Open_deocder(ctx);						//打开编解码器各个组件

	while(1)
	{
		//将码流文件按某长度读入输入缓存区
		uDataSize = fread(inbuf, 1, INBUF_SIZE, inputoutput.pFin);
		if (0 == uDataSize)
		{
			break;
		}

		pDataPtr = inbuf;

		while(uDataSize > 0)
		{
			//解析缓存区中的数据为AVPacket对象,包含一个NAL Unit的数据
			len = av_parser_parse2(ctx.pCodecParserCtx, ctx.pCodecContext, 
										&(ctx.pkt.data), &(ctx.pkt.size), 
										pDataPtr, uDataSize, 
										AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);	
			pDataPtr += len;
			uDataSize -= len;

			if (0 == ctx.pkt.size)
			{
				continue;
			}

			printf("Parse 1 packet. Packet pts: %d.
", ctx.pkt.pts);

			//根据AVCodecContext的设置,解析AVPacket中的码流,输出到AVFrame
			int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
			if (ret < 0) 
			{
				printf("Decode Error.
");
				return ret;
			}

			if (got_picture) 
			{
				//获得一帧完整的图像,写出到输出文件
				write_out_yuv_frame(ctx, inputoutput);
				printf("Succeed to decode 1 frame! Frame pts: %d
", ctx.frame->pts);
			}
		} //while(uDataSize > 0)
	}

    ctx.pkt.data = NULL;
    ctx.pkt.size = 0;
	while(1)
	{
		//将编码器中剩余的数据继续输出完
		int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
		if (ret < 0) 
		{
			printf("Decode Error.
");
			return ret;
		}

		if (got_picture) 
		{
			write_out_yuv_frame(ctx, inputoutput);
			printf("Flush Decoder: Succeed to decode 1 frame!
");
		}
		else
		{
			break;
		}
	} //while(1)

	//收尾工作
	Close_files(inputoutput);
	Close_decoder(ctx);

	return 1;
}

但是av_parser_parse2()输入参数比较多,隐藏了一下处理,需要额外分析

2.2 av_parser_parse2()

av_parser_parse2( )是解码处理过程中的核心函数之一,因为二进制码流不是连续的,解码上下文的一些东西还存在pps以及sps中,所以需要通过这个函数去解析出一个完整packet,以及解码上下文比如profile, level等内容,然后存储到CodecContext中以用来解码, 首先看一下官方对参数的说明:

/**
 * 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      buffer size in bytes without the padding. I.e. the full buffer
                        size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
                        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);

savctx分别是对应编码的解析器和解码器上下文

bufbuf_size 分别是输入的二进制流和大小

av_parser_parse2() 调用后会返回已经使用的二进制流的数据长度,要注意:这个时候,buf不一定提供够一帧数据去组成一个packet,这时会把输入数据拷贝一份存储在AVCodecParserContext *s 下,取x264的例子:

static int h264_parse(AVCodecParserContext *s,
                      AVCodecContext *avctx,
                      const uint8_t **poutbuf, int *poutbuf_size,
                      const uint8_t *buf, int buf_size)
{
    H264ParseContext *p = s->priv_data;
    ParseContext *pc = &p->pc;
    int next;

    if (!p->got_first) {
        p->got_first = 1;
        if (avctx->extradata_size) {
            ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
                                     &p->ps, &p->is_avc, &p->nal_length_size,
                                     avctx->err_recognition, avctx);
        }
    }

    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
        next = buf_size;
    } else {
        next = h264_find_frame_end(p, buf, buf_size, avctx);
		// 缓存数据
        if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
            *poutbuf      = NULL;
            *poutbuf_size = 0;
            return buf_size;
        }

        if (next < 0 && next != END_NOT_FOUND) {
            av_assert1(pc->last_index + next >= 0);
            h264_find_frame_end(p, &pc->buffer[pc->last_index + next], -next, avctx); // update state
        }
    }
    parse_nal_units(s, avctx, buf, buf_size);
    ....
}

s->flags默认没有设置PARSER_FLAG_COMPLETE_FRAMES(后续的处理才会设置),首先会走下面的分支,

然后调用h264_find_frame_end( )去找当前NAL尾也就是下一个NAL头0x00 00 00 01

然后会通过ff_combine_frame()函数将当前数据先拷贝到ParseContext内的一个buf中

后面就是使用parse_nal_units()nal本身做解析了

所以,在2.1的例子中,我们可以看到执行完av_parser_parse2()后不管有没有构成一个packet,av_parser_parse2()告知我们已使用数据都可以不用再管了,因为其内部拷了一份; 当然如果buf提取的数据是够的,就能够使用pkt.size判断有没有packet

原文地址:https://www.cnblogs.com/ishen/p/14804279.html