当前位置 博文首页 > parser_parse2)_wanggao的专栏:ffmpeg学习(19)文件流、网络

    parser_parse2)_wanggao的专栏:ffmpeg学习(19)文件流、网络

    作者:[db:作者] 时间:2021-09-09 09:43

    前文 【ffmpeg学习(17)文件流、网络流的格式解析(使用AVIOContext )】 使用AVIOContext来指定AVFormat的输入,从内存数据中读取音视频数据进行解析。

    当我们需要解析网络流时,通常是使用socket手段接收到数据,主动使用ffmpeg进行解析解码。如果继续使用AVIOContext,必须将接收的socket数据复制到AVIOContext的回调中进行处理,或者使用队列进行数据的同步处理,并且要能完整每个数据(因为缓冲区可能不足以放下当前包的所有数据,详见上一篇博客);使用了libavformat库支持解析ffmpeg支持的所有数据格式,但是当网络流格式明确时就不需要使libavformat库以减少移植依赖。

    本文介绍仅使用libavcodec库,不依赖libavformat库使用av_parser_parse2来解析传入的指定编码格式的数据。

    1、av_parser_parse2()介绍

    AVCodecParser用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。他的核心函数是av_parser_parse2()。它的定义如下所示。

    /**
     * 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);
    

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

    2、示例代码

    这里使用每次读取固定长度的数据方式模拟接收到网络数据。

    av_parser_parse2()函数将从给定长度的数据中尝试解析完整的一帧压缩编码数据,若给定的数据不足以解析则需要提供更多的数据;如果解析成一帧还有剩余数据,需要处理完当前解析帧之后继续读取剩余数据,尝试解析出下一帧压缩编码数据。

    #include <stdio.h>
    
    extern "C"{
    #include "libavcodec/avcodec.h"
    };
    
    //test different codec
    #define TEST_H264  1
    #define TEST_HEVC  0
    
    int main(int argc, char* argv[])
    {
    #if TEST_HEVC
        AVCodecID codec_id = AV_CODEC_ID_HEVC;
        char filepath_in[] = "bigbuckbunny_480x272.hevc";
    #elif TEST_H264
        AVCodecID codec_id = AV_CODEC_ID_H264;
        //char filepath_in[] = "../../simplest_ffmpeg_decoder_pure/bigbuckbunny_480x272.h264";
        //char filepath_in[] = "../../files/BladeRunner2049.h264";
        char filepath_in[] = "C:/Users/wanggao/Desktop/111_clyicrow.h264";
        //char filepath_in[] = "C:/Users/wanggao/Desktop/111.h264";
    #else
        AVCodecID codec_id = AV_CODEC_ID_MPEG2VIDEO;
        char filepath_in[] = "bigbuckbunny_480x272.m2v";
    #endif
    
        char filepath_out[] = "BladeRunner2049.yuv";
        int first_time = 1;
        int ret;
    
        // 初始化 decoder 和 parser
        AVCodecContext *pCodecCtx = NULL;
        AVCodecParserContext *pCodecParserCtx = NULL;
    
        //AVCodec *pCodec = avcodec_find_decoder(codec_id);
        AVCodec *pCodec = avcodec_find_decoder_by_name("h264_cuvid"); // 硬解码器
        if(!pCodec) {
            av_log(NULL, AV_LOG_ERROR, "Codec not found\n");
            return -1;
        }
        pCodecCtx = avcodec_alloc_context3(pCodec);
        if(!pCodecCtx) {
            av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
            return -1;
        }
    
        //codec_id = pCodecCtx->codec_id;
        pCodecParserCtx = av_parser_init(codec_id);
        if(!pCodecParserCtx) {
            av_log(NULL, AV_LOG_ERROR, "Could not allocate video parser context\n");
            return -1;
        }
    
        //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
        //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; 
    
        if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Could not open codec\n");
            return -1;
        }
    
        // 输入输出文件
        FILE *fp_in;
        FILE *fp_out;
        //Input File
        fp_in = fopen(filepath_in, "rb");
        if(!fp_in) {
            printf("Could not open input stream\n");
            return -1;
        }
        //Output File
        fp_out = fopen(filepath_out, "wb");
        if(!fp_out) {
            printf("Could not open output YUV file\n");
            return -1;
        }
    
    
        const int in_buffer_size = 4096;
        unsigned char in_buffer[in_buffer_size] = {0};
        unsigned char *cur_ptr;
        int cur_size;
    
        AVFrame	*pFrame;
        AVPacket packet;
    
        pFrame = av_frame_alloc();
        av_init_packet(&packet);
    
        while(1) {  // 模拟网络数据到来的回调
            cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
            if(cur_size == 0)
                break;
    
            cur_ptr = in_buffer;
    
            while(cur_size>0)   // 剩余数据未进行解析
            {
                // 尝试从长度为cur_size的数据cur_ptr解析一帧压缩编码数据
                int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,
                                           &packet.data, &packet.size,cur_ptr, cur_size,
                                           AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
                cur_ptr += len;
                cur_size -= len;
    
                if(packet.size == 0) 
                    continue; // 未解析出完整一帧
    
                //Some Info from AVCodecParserContext
                printf("[Packet]Size:%6d\t", packet.size);
                switch(pCodecParserCtx->pict_type) {
                    case AV_PICTURE_TYPE_I: printf("Type:I\t"); break;
                    case AV_PICTURE_TYPE_P: printf("Type:P\t"); break;
                    case AV_PICTURE_TYPE_B: printf("Type:B\t"); break;
                    default: printf("Type:Other\t"); break;
                }
                printf("Number:%4d\n", pCodecParserCtx->output_picture_number);
    
    
                // 解码一帧压缩编码数据为YUV420P图像
                ret = avcodec_send_packet(pCodecCtx, &packet);
                while(ret >= 0) {
                    ret = avcodec_receive_frame(pCodecCtx, pFrame);
                    if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                        break;
                    }
                    else if(ret < 0) {
                        av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
                        exit;
                    }
    
                    //Y, U, V
                    for(int i = 0; i < pFrame->height; i++)
                        fwrite(pFrame->data[0] + pFrame->linesize[0] * i, 1, pFrame->width, fp_out);
                    //for(int i = 0; i < pFrame->height / 2; i++)
                    //    fwrite(pFrame->data[1] + pFrame->linesize[1] * i, 1, pFrame->width / 2, fp_out);
                    //for(int i = 0; i < pFrame->height / 2; i++)
                    //    fwrite(pFrame->data[2] + pFrame->linesize[2] * i, 1, pFrame->width / 2, fp_out);
    
                    printf("Succeed to decode 1 frame!\n");
                }
            }
        }
    
        // flush (实际网络流中可以忽略)
        while(1) {
            ret = avcodec_send_packet(pCodecCtx, NULL);
            if(ret < 0)
                break;
    
            while(ret >= 0) {
                ret = avcodec_receive_frame(pCodecCtx, pFrame);
                if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    continue;
                }
                else if(ret < 0) {
                    av_log(