ffempeg 学习总结

视频编码

void Class_mp4io::write_3D_buffer_to_mp4(const char filename[], unsigned char * buffer)
{
    
    AVFrame * pFrame = NULL;//RAVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候
    AVDictionary *params = NULL;//存关键字和值
    AVFormatContext   *pFmtCtx = NULL;//是API中直接接触到的结构体,该文件中包含了多路流,包括音频流、视频流、字幕流等
    struct SwsContext *sws = NULL;//主要用于视频图像的转换,比如格式转换

    try
    {
        using std::endl;
        using std::cout;
        
        //no warning no error notice 
        av_log_set_level(AV_LOG_QUIET);//这个函数是用来设置日志的标准, 其意义是指: 设置日志打印的标准, 高于这个标准的将不会打印

        TRY(filename);
        
        av_register_all();//初始化所有组件,只有调用了该函数,才能使用复用器和编解码器
        
        size_t width = shape[0];
        size_t height = shape[1];
        size_t dirNum = shape[2];

        
        //-nostats

        //av_dict_set(&params, "nostats","", 0);
        av_dict_set(&params, "preset", "slow", 0);
        av_dict_set(&params, "crf", crfInClass, 0);
        av_dict_set(&params, "tune", "film", 0);//tune的值有: film: 电影、真人类型;
        av_dict_set(&params, "nostats", "yes", 0);

        
        //alloc "pFmtCtx" and  guess "pFmtCtx->oformat" by ext 
        AVTRY(avformat_alloc_output_context2(&pFmtCtx, NULL, NULL, filename), "Failed to detect output file format from the file name.");//初始化输出码流的AVFormatContext
        AVCodec *pCodec = NULL;//存储编解码器信息的结构体

        TRY(pCodec = avcodec_find_encoder(pFmtCtx->oformat->video_codec));//用于查找FFmpeg的编码器

        /***********************************************/
        //anthor method
        /*pFmtCtx = avformat_alloc_context();
        AVOutputFormat    *oFmt = av_guess_format(NULL, outPath.c_str(), NULL);
        pFmtCtx -> oformat = oFmt;*/
        /***********************************************/

        TRY(avformat_new_stream(pFmtCtx, pCodec));//创建输出码流的AVStream。

        //´ò¿ªÊä³öÁ÷
        AVTRY(avio_open(&pFmtCtx->pb, filename, AVIO_FLAG_WRITE), "Failed to open output file.");//该函数用于打开FFmpeg的输入输出文件

        //avformat_write_header(pFmtCtx, NULL);

        //is necessary?
        //********************************************************
        pFmtCtx->streams[0]->codec->codec = pCodec;
        pFmtCtx->streams[0]->codec->pix_fmt = pCodec->pix_fmts[0];

 

        pFmtCtx->streams[0]->codec->width = width;
        pFmtCtx->streams[0]->codec->height = height;

        pFmtCtx->streams[0]->codec->time_base.num = 1;
        pFmtCtx->streams[0]->codec->time_base.den = 25;
        //***********************************************************

        //open stream[0]'s codecContext
        AVTRY(avcodec_open2(pFmtCtx->streams[0]->codec, pCodec, &params), "Failed to initialize encoder.");//构造AVCodecContext结构体,打开编码器。

        pFrame = av_frame_alloc();//这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定。


        TRY(av_image_alloc(pFrame->data, pFrame->linesize, width, height, pFmtCtx->streams[0]->codec->pix_fmt, 1));//此函数的功能是按照指定的宽、高、像素格式来分析图像内存,返回值:所申请的内存空间的总大小。如果是负值,表示申请失败

        AVTRY(avformat_write_header(pFmtCtx, &params), "Failed to write header.");//写视频文件头
        
        if (pFrame->pts == AV_NOPTS_VALUE)
        {
            pFrame->pts = 0;
        }

        AVPixelFormat sorcePixFormat = AV_PIX_FMT_NONE;

        switch (color_bytes)
        {
        case 1:
            sorcePixFormat = AV_PIX_FMT_GRAY8;
            break;

        case 2:
            sorcePixFormat = AV_PIX_FMT_GRAY16LE;
            break;

        default:
            break;
        }


        

        sws = sws_getContext(width, height, sorcePixFormat,
            width, height, pFmtCtx->streams[0]->codec->pix_fmt,
            SWS_BICUBIC, NULL, NULL, NULL);//初始化一个SwsContext, 函数可以看做是初始化函数

 

        size_t planeStride = width * height * color_bytes;

        for (int i = 0; i < dirNum; i++, pFrame->pts++)
        {

            //            //Nathan
            const uint8_t* plane = (uint8_t*)buffer + planeStride * i;  //
            const uint8_t* slice[4] = { plane + color_bytes * 0,
                plane + color_bytes * 1,
                plane + color_bytes * 2,
                plane + color_bytes * 3 };

            size_t linestride = color_bytes * width;

            const int stride[4] = { linestride,linestride,linestride,linestride };

 

            int got_packet = 0;
            //av_init_packet(&p);
            AVPacket p = { 0 };
            sws_scale(sws, slice, stride, 0, height, pFrame->data, pFrame->linesize); //
            AVTRY(avcodec_encode_video2(pFmtCtx->streams[0]->codec, &p, pFrame, &got_packet), pFrame ? "Failed to encode frame." : "Failed to encode terminating frame.");//编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。

            if (got_packet)
            {
                if ((&p)->pts != AV_NOPTS_VALUE)
                    (&p)->pts = av_rescale_q((&p)->pts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);//是用来把时间戳从一个时基调整到另外一个时基时候用的函数。它基本的动作是计算a*b/c,


                if ((&p)->dts != AV_NOPTS_VALUE)
                    (&p)->dts = av_rescale_q((&p)->dts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);

                AVTRY(av_write_frame(pFmtCtx, &p), "Failed to write packet.");

                av_packet_unref(&p);    //???
            }
        }

 

        //flush
        if (pFmtCtx->streams[0]->codec->codec->capabilities & CODEC_CAP_DELAY)// AVCodecContext codec 第二个AVCodec  codec
        {
            AVPacket temp = { 0 };
            int got_packet_temp = 1;
            //av_init_packet(&temp);
            while (got_packet_temp)
            {
                avcodec_encode_video2(pFmtCtx->streams[0]->codec, &temp, NULL, &got_packet_temp);
                if (got_packet_temp)
                {
                    if ((&temp)->pts != AV_NOPTS_VALUE)
                        (&temp)->pts = av_rescale_q((&temp)->pts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);


                    if ((&temp)->dts != AV_NOPTS_VALUE)
                        (&temp)->dts = av_rescale_q((&temp)->dts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);

                    av_write_frame(pFmtCtx, &temp);
                    av_packet_unref(&temp);    //p±»Ä¨µôÁË
                }
            }
        }

        av_write_trailer(pFmtCtx);
    }

    //*********************³öÏÖÒì³£µÄÇé¿öÏÂÊÍ·Å×ÊÔ´
    catch (Exception_mp4io e)
    {
        if (params)
        {
            av_dict_free(&params);
        }
        if (pFrame)
        {
            av_free(pFrame);
        }

        if (pFmtCtx)
        {
            if (pFmtCtx->streams[0]->codec)
            {
                avcodec_close(pFmtCtx->streams[0]->codec);
            }
            if (pFmtCtx->pb)
            {
                avio_close(pFmtCtx->pb);
            }
            avformat_free_context(pFmtCtx);
        }
        if (sws)
        {
            sws_freeContext(sws);
        }
    }//end of catch
    //********************************


    //normal release
    //*****************************ûÓÐÒì³£µÄÇé¿öÏÂÊÍ·Å×ÊÔ´
    if (params)
    {
        av_dict_free(&params);
    }
    if (pFrame)
    {
        av_free(pFrame);
    }

    if (pFmtCtx)
    {
        if (pFmtCtx->streams[0]->codec)
        {
            avcodec_close(pFmtCtx->streams[0]->codec);
        }
        if (pFmtCtx->pb)
        {
            avio_close(pFmtCtx->pb);
        }
        avformat_free_context(pFmtCtx);
    }
    if (sws)
    {
        sws_freeContext(sws);
    }
    //**********************************************

}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值