FFmpeg零基础学习(三)——视频分辨率更改

前言

本篇文章的需求为:
1、输入视频文件的路径。
2、输入要输出的视频的分辨率。
3、将视频文件转为指定分辨率的视频。

当前进展:目前仅将数据解码出,且使用sws_scale 更改为其他分辨率的AVFrame,但将该帧编码成视频的方式,目前还未成功。后续完善。

正文

一、将视频的每一帧重编辑为指定的格式及宽高

code

void CFFmpegDemoTest::on_btnChangePix_clicked()
{
    qDebug() << "--> CFFmpegDemoTest::on_btnChangePix_clicked Start";
    int ret = 0; int err;
    int read_end = 0;
    int frame_num = 0;

    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();

    //打开输入文件
    char filename[] = "juren-30s.mp4";
    struct SwsContext* img_convert_ctx = NULL;
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        qDebug() << "fmt_ctx error code %d " << AVERROR(ENOMEM);
        return;
    }
    if((err = avformat_open_input(&fmt_ctx, filename,NULL,NULL)) < 0){
        qDebug() << "can not open file:"<<err;
        return;
    }

    //打开解码器
    AVCodecContext *avctx = avcodec_alloc_context3(NULL);
    ret = avcodec_parameters_to_context(avctx, fmt_ctx->streams[0]->codecpar);

    if (ret < 0)
    {
        qDebug() << " avcodec_parameters_to_contexterror code:" << ret;
        return;
    }
    AVCodec *codec = avcodec_find_decoder(avctx->codec_id);
    if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) {
        qDebug() << "open codec faile :"<<ret;
        return;
    }

    int sws_flags = SWS_BICUBIC;
    int result_format = AV_PIX_FMT_BGRA;
    int result_width = 300;
    int result_height = 300;

    //确定内存的大小
    int buf_size = av_image_get_buffer_size(static_cast<AVPixelFormat>(result_format), result_width, result_height, 1);

    for(;;)
    {
        if( 1 == read_end ){
            break;
        }

        ret = av_read_frame(fmt_ctx, pkt);
        //跳过不处理音频包
        if( 1 == pkt->stream_index)
        {
            av_packet_unref(pkt);
            continue;
        }

        if ( AVERROR_EOF == ret)
        {
            //读取完文件,这时候 pkt 的 data 跟 size 应该是 null
            avcodec_send_packet(avctx, NULL);
        }
        else
        {
            if( 0 != ret)
            {
                qDebug() << "read error code:" << ret;
                return;
            }else{
                retry:
                if (avcodec_send_packet(avctx, pkt) == AVERROR(EAGAIN)) {
                    qDebug() << "Receive_frame and send_packet both returned EAGAIN, which is an API violation";
                    //这里可以考虑休眠 0.1 秒,返回 EAGAIN 通常是 ffmpeg 的内部 api 有bug
                    goto retry;
                }
                //释放 pkt 里面的编码数据
                av_packet_unref(pkt);
            }

        }

        //循环不断从解码器读数据,直到没有数据可读。
        for(;;){
            //读取 AVFrame
            ret = avcodec_receive_frame(avctx, frame);
            /* 释放 frame 里面的YUV数据,
             * 由于 avcodec_receive_frame 函数里面会调用 av_frame_unref,所以下面的代码可以注释。
             * 所以我们不需要 手动 unref 这个 AVFrame
             * */
            //av_frame_unref(frame);

            if( AVERROR(EAGAIN) == ret )
            {
                //提示 EAGAIN 代表 解码器 需要 更多的 AVPacket
                //跳出 第一层 for,让 解码器拿到更多的 AVPacket
                break;
            }
            else if( AVERROR_EOF == ret )
            {
                /* 提示 AVERROR_EOF 代表之前已经往 解码器发送了一个 data 跟 size 都是 NULL 的 AVPacket
                 * 发送 NULL 的 AVPacket 是提示解码器把所有的缓存帧全都刷出来。
                 * 通常只有在 读完输入文件才会发送 NULL 的 AVPacket,或者需要用现有的解码器解码另一个的视频流才会这么干。
                 * */

                //跳出 第二层 for,文件已经解码完毕。
                read_end = 1;
                break;
            }
            else if( ret >= 0 )
            {
                if( NULL == img_convert_ctx )
                {
                    img_convert_ctx = sws_getCachedContext(img_convert_ctx,
                                                           frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
                                                           result_width, result_height, static_cast<AVPixelFormat>(result_format),
                                                           sws_flags, NULL, NULL, NULL);
                    if (NULL == img_convert_ctx) {
                        av_log(NULL, AV_LOG_FATAL, "no memory 1\n");
                        return;
                    }
                }

                //申请内存
                uint8_t* buffer = (uint8_t *)av_malloc(buf_size);
                if( !buffer ){
                    av_log(NULL, AV_LOG_FATAL, "no memory 2\n");
                    return;
                }
                uint8_t *pixels[4];
                int pitch[4];
                //把内存映射到数组里面
                av_image_fill_arrays(pixels, pitch, buffer, static_cast<AVPixelFormat>(result_format), result_width, result_height, 1);

                sws_scale(img_convert_ctx,
                          (const uint8_t * const *)frame->data, frame->linesize, 0, frame->height,
                          pixels,pitch);

                //处理%20 ==0 的帧
                if (frame_num % 20 == 0)
                {
                    Save_rgb_to_file(pixels, pitch, result_height, frame_num);
                }
                //减少引用,释放内存。
                av_frame_unref(frame);
                av_freep(&buffer);

                frame_num++;

            }
            else
            {
                av_log(NULL, AV_LOG_FATAL, "other fail \n");
                qDebug() << "other fail";
                return;
            }
        }
    }

    av_frame_free(&frame);
    av_packet_free(&pkt);

    sws_freeContext(img_convert_ctx);
    img_convert_ctx = NULL;

    //关闭编码器,解码器。
    avcodec_close(avctx);

    //释放容器内存。
    avformat_free_context(fmt_ctx);
    qDebug() <<"done \n";
    return;
}

END、总结的知识与问题

1、avcodec_send_frame和avcodec_receive_packet的关系

1、编码器和解码器都维护了一个缓冲区,在刚开始输入数据时,需要多输入几帧,等缓冲区被填充满后,才会在receive端接收到编码或解码后的数据。

2、存在AVPacket中的数据不一定是一帧(比如音频的数据可能1个AVPacket包含1s的数据,帧率为25的话,就包含25帧),但存在AVFrame中的是一帧数据,所以avcodec_send_packet和avcodec_receive_frame不一定一一对应,调用一次avcodec_send_packet后,可能需要多次调用avcodec_receive_frame。

3、frame只需要分配对象本身空间就好,frame->data的空间并不需要分配。函数会判断是否已经给frame->data分配空间,如果没有分配会在函数内部为frame->data分配合适的空间。

4、avcodec_receive_frame:send端只把数据放入缓冲区,recive端才是解码并获得数据的函数,如果receive发现已有解码后的数据则直接获取,如果没有则开始解码。

5、数据遗留:在最后应该向avcodec_send_frame(enc_ctx, NULL)传入NULL数据,这样编码器知道后面不会再有数据,就把放在缓冲区中的数据,全部编码并通过avcodec_receive_packet输出出来。

6、返回值:EAGAIN,意思是需要输入更多的数据,才会有新的数据编码后的数据返回。AVERROR_EOF在从文件中读取数据推流时,出现这个错误是因为文件内的数据读完了。

7、参数内存:newpkt->data并不需要分配数据空间,只需要给newpkt本身结构体分配空间就好。
参考:avcodec_send_frame和avcodec_receive_packet

2、Input picture width (640) is greater than stride (200)

问题: 在调用sws_scale 时,一定传入的目的图像的宽高小于输入图像的宽高时,则出现该问题。

参考

1、avcodec_send_frame和avcodec_receive_packet
2、FFmpeg Basics 中文文档-命令行教程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值