zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    ER/数据库建模工具之MySQL Workbench的使用
    HBase基础架构及原理
    HBase在HDFS上的目录介绍
    zookeeper的三种安装模式
    YCSB之HBase性能测试
    kerberos简单介绍
    springboot 文件上传大小配置
    List集合三种遍历方法
    Linux中给普通用户添加sudo权限
    Linux查看所有用户和组信息
  • 原文地址:https://www.cnblogs.com/ishen/p/14804279.html
Copyright © 2011-2022 走看看