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\n");
		return false;
	}

	//根據AVCodec對象分配AVCodecContext
	ctx.pCodecContext = avcodec_alloc_context3(ctx.pCodec);
	if (!ctx.pCodecContext)
	{
		fprintf(stderr, "Could not allocate video codec context\n");
		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\n");
		return false;
	}

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

	//分配AVFrame對象
	ctx.frame = av_frame_alloc();
	if (!ctx.frame) 
	{
		fprintf(stderr, "Could not allocate video frame\n");
		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\n", 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.\n", 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.\n");
				return ret;
			}

			if (got_picture) 
			{
				//獲得一幀完整的圖像,寫出到輸出文件
				write_out_yuv_frame(ctx, inputoutput);
				printf("Succeed to decode 1 frame! Frame pts: %d\n", 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.\n");
			return ret;
		}

		if (got_picture) 
		{
			write_out_yuv_frame(ctx, inputoutput);
			printf("Flush Decoder: Succeed to decode 1 frame!\n");
		}
		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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM