Qt基于FFmpeg实现视频转码

一、简述转码

  • 转码的作用:封装格式的实现。(h264—>mov、mp4、flv、avi(封装格式)等)
  • 之前博客中提到如何把像素数据编码得到 H264 的压缩码流数据,但是一般的播放工具是没法直接操作h264文件,需要把这样的视频文件封装成带有封装格式的,所以就需要用到转码。
  • 转码就是把解码和编码的某些操作进行整合,需要注意的就是转换规则(时间基的设置等)。

二、流程图

三、实现效果

 

四、代码


transH264::transH264()
{
    //1、注册
    av_register_all();
    in_formatContext = avformat_alloc_context();
    out_formatContext = avformat_alloc_context();
    videoType = -1;//是否为视频流
    pkt=(AVPacket *)malloc(sizeof(AVPacket));
}

void transH264::openH264(QString fileName)
{
    //2、打开视频文件
    int res=avformat_open_input(&in_formatContext,fileName.toStdString().c_str(),nullptr,nullptr);
    if(res<0)
    {
        qDebug()<<"打开视频失败";
        return ;
    }
    //3、查找视频流
    res = avformat_find_stream_info(in_formatContext,nullptr);
    if(res<0)
    {
        qDebug()<<"打开流媒体失败";
        return ;
    }

    //一个视频中有多股码流(用循环),存在AVFormatContext的streams数组中
    for(int i=0;i<in_formatContext->nb_streams;i++)
    {
        if(in_formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//streams->有AVStream结构体,AVStream->codec
        {
            //找到视频流(只有一个)
            videoType = i;//标识视频流这个类型
            break;
        }
    }
    if(videoType == -1)
    {
        qDebug()<<"没有找到视频流相关信息";
        return;
    }
    qDebug()<<"视频打开成功,输入的准备已完成";
}

void transH264::transToVideo(QString fileName)
{
    //4、猜测封装格式
    outputFormat = av_guess_format(nullptr,fileName.toStdString().c_str(),nullptr);//已经注册的最合适的输出格式
    if(outputFormat == NULL)//没有匹配到的格式
    {
        qDebug()<<"没有找到对应格式的相关信息"<<endl;
        return ;
    }
    out_formatContext->oformat = outputFormat;//输出格式为上面匹配到的格式

    //5、打开目标文件流
    int res = avio_open(&out_formatContext->pb,fileName.toStdString().c_str(),AVIO_FLAG_WRITE);
    if(res<0)
    {
        qDebug()<<"打开输出文件失败"<<endl;
        return ;
    }
    //6、新建目标(存储码流数据)视频流
    AVStream *outnewStream = avformat_new_stream(out_formatContext,NULL);
    if(outnewStream == NULL)
    {
        qDebug()<<"新建目标视频流失败"<<endl;
        return ;
    }
    //7、编码器参数配置
    res =avcodec_parameters_copy(outnewStream->codecpar,in_formatContext->streams[videoType]->codecpar);
    if(res<0)
    {
        qDebug()<<"编码器参数配置失败"<<endl;
        return ;
    }
    outnewStream->codec->codec_tag=0;


    //8、写入编码的头部信息
    res = avformat_write_header(out_formatContext,nullptr);
    if(res<0)
    {
        qDebug()<<"文件头封装失败"<<endl;
        return ;
    }

    int bufSize = outnewStream->codec->width*outnewStream->codec->height;//计算一帧(图)数据的大小
    av_new_packet(pkt,bufSize);
    int frame_count=0;
    //9、一帧一帧读取码流数据
    while (av_read_frame(in_formatContext,pkt)==0)
    {
        if(pkt->stream_index == videoType)//是视频流
        {
            frame_count++;
            qDebug()<<"第"<<frame_count<<"帧"<<endl;
            //10、转码
            //查看是否有做时间基的设置
            if(pkt->pts == AV_NOPTS_VALUE)//没有设置
            {
                //时间基的转换
                AVRational time_base1 = in_formatContext->streams[videoType]->time_base;
                //计算两帧码流数据之间的长度
                int64_t duration = (double)AV_TIME_BASE/av_q2d(in_formatContext->streams[videoType]->r_frame_rate);
                //计算显示时间基(当前帧数*两帧之间的长度)/(输入时间基*AV_TIME_BASE)
                pkt->pts =(double)(frame_count*duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                pkt->dts = pkt->pts;//没有b帧
                pkt->duration = duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);

            }
            else if(pkt->pts <pkt->dts)//解码时间基大于显示时间基 (正常 继续处理)
            {
                continue;

            }
       
                pkt->pts = av_rescale_q_rnd(pkt->pts,//显示时间基的转换
                                            in_formatContext->streams[videoType]->time_base,
                                            outnewStream->time_base,
                                            (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
                pkt->dts = av_rescale_q_rnd(pkt->dts,
                                            in_formatContext->streams[videoType]->time_base,
                                            outnewStream->time_base,
                                            (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
                pkt->duration = av_rescale_q(pkt->duration,
                                             in_formatContext->streams[videoType]->time_base,
                                             outnewStream->time_base);
            
            //准备写入码流数据
            pkt->pos=-1;
            pkt->flags|=AV_PKT_FLAG_KEY;//或等于:|=
            pkt->stream_index=0;
            //11、写入数据到视频信息结构体汇总
            av_interleaved_write_frame(out_formatContext,pkt);

        }
        av_packet_unref(pkt);
    }
    //12、写入尾巴帧
    av_write_trailer(out_formatContext);
    //关闭编码器
    avcodec_close(out_formatContext->streams[videoType]->codec);
    av_freep(&out_formatContext->streams[videoType]->codec);
    //关闭输出流
    avio_close(out_formatContext->pb);
    //释放输出信息结构体
    av_free(out_formatContext);
    avformat_close_input(&in_formatContext);
    av_free(in_formatContext);
    av_packet_free(&pkt);
}

注意:

在进行时间基的转换时,这里不能加上else,否则转码导出的视频时长会变得很长(亲测,35s变成8min,甚至更长),而且播放卡顿

main中测试:

 源码下载链接:

Qt基于FFmpeg实现视频转码-编解码文档类资源-CSDN下载

原创不易,转载请附上原文出处:

https://blog.csdn.net/hml111666/article/details/122612151

关于转码原理:

https://blog.csdn.net/hml111666/article/details/122615861

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ze言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值