【FFmpeg+Qt开发】转码流程 H.264 转(mov、mp4、avi、flv)等视频格式 示例详解

目录

 一、FFMPEG 转码

1.1转码流程

1.2转码示例


🚗本文主要是基于FFMPEG技术编解码,继续延申,对FFMPEG转码部分进行详细介绍

🚗希望对正在学习FFMPEG技术的小伙伴们有所帮助!

 一、FFMPEG 转码

1.1转码流程

上文对FFmpeg编码部分进行介绍完之后,我们得到了H.264的压缩码流数据。

但是,我们常用的播放器是没办法打开这样的视频文件的,那么可以由此思考一下,如何才能够将这样的文件转换成我们能够播放的视频文件呢?

没错!这里就需要用到我们的转码了!

🔴转码:视频转码技术将视频信号从一种格式转换 成另外一种格式 (例如:H.254 转成 MP4)

mp4、flv、avi、mov等 这些皆为我们常看到的视频格式。

另外,许多现有的视频会议系统是基于旧的视频编码标准H.263而建立,而最新的视频会议系统采用了H.264基线规范。因此,实时视频转码技术是实现两者之间通信的必不可少的因素!

转码的流程图,如下图所示:

在正式进入转码的讲解之前,首先先了解一下相关的原理,这其中就包括了I/B/P帧,以及我们的时间基PTS/DTS设置。

I/B/P帧:

  • I帧又称为内部画面,是关键帧,它采用帧内压缩法,也称为“关键帧”压缩法;
  • B帧是双向预测的帧间压缩算法。当把一帧压缩成B帧时,它根据相邻的前一帧、本帧以及后一帧数据的不同点来压缩本帧,也即仅记录本帧与前后帧的差值。一般地,I帧压缩效率最低,P帧较高,B帧最高;
  • P帧由在它前面的B帧或者I帧预测而来,它比较与它前面的P帧或者I帧之间的相同信息或数据,也即考虑运动的特性进行帧间压缩。

举例说明:

如下图所示,红色I帧、蓝色P帧、绿色B帧

显示的顺序是1,2,3,4,5,6,解码的顺序就为1,2,5,3,4

为什么是这样呢?这里就涉及了PTS和DTS,下面来讲一下。

时间基 PTS/DTS:

  • PTS是渲染用的时间戳,我们视频帧是按照PTS时间戳来展示的
  • DTS是解码时间戳,用于视频解码的

1.如果没有B帧,PTS=DTS ;

2.如果有B帧,就需要更大的缓存来存储解码的帧数据。B帧要等I帧和P帧解码完成之后才开始。

1.2转码示例

🟢转码类的定义

extern "C"   //ffmpeg使用c语言实现的,引入用c写的代码就要用extern
{
#include <libavcodec/avcodec.h>   //注册
#include <libavdevice/avdevice.h>  //设备
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
class fcoverh264
{
public:
    fcoverh264();

    //打开H264视频文件
    void openFile(QString file);
    //根据我们需要的封装格式进行处理
    void outPut(QString fileout);
private:
    AVFormatContext *forContext,*formatout;//保存数据的结构体 forContext存输入进来的视频信息;formatout存储最终输出的视频信息
    AVPacket *pkt;//pkt
    int videoType;
};

🟢具体步骤如下

#include "fcoverh264.h"
#include <QDebug>

extern "C"   //ffmpeg使用c语言实现的,引入用c写的代码就要用extern
{
#include <libavcodec/avcodec.h>   //注册
#include <libavdevice/avdevice.h>  //设备
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
fcoverh264::fcoverh264()
{
    /*
     * 转码的流程:
     * 1.注册组件
     * 2.打开视频流 打开视频文件
     * 3.查找有没有流数据
     * 4.查找视频码流数据
     *
     * 6.根据要的封装格式 来猜测格式对应编辑器
     * 7.打开对应文件
     * 8.新建流
     * 9.写入头部信息
     * 10.读取一帧一帧的码流数据
     * 11.转码---->时间基的转化
     * 所以在解码的时候:显示顺序和解码的顺序是一样的;
       处理其他视频的时候:就需要关注 显示顺序和解码的顺序是否一致了
       编码有B帧? 解码:IPB
     * 12.写入对应的一帧数据到文件中
     */

    //注册组件
    av_register_all();
    forContext= avformat_alloc_context();

}

void fcoverh264::openFile(QString file)
{
    //打开输入视频
    int res=avformat_open_input(&forContext,file.toStdString().c_str(),nullptr,nullptr);
    //判断是否打开成功
    if(res<0)
    {
        qDebug()<<"打开失败";
        return;
    }

    //打开视频文件成功,获取文件信息
    res = avformat_find_stream_info(forContext,nullptr);//查看有没有相关视频流信息
    if(res<0)//判断是否有流媒体
    {
        qDebug()<<"没有流媒体信息"<<endl;
        return;
    }

    //一个视频流有多股码流,存在forContentext中streams数组中
    int videoType=-1;
    for(int i=0;i<forContext->nb_streams;i++) //i小于流的个数
    {
        if(forContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//视频流
        {
            videoType=i;//标识类型
            break;
        }
    }
    if(videoType==-1)
    {
        qDebug()<<"没有视频流相关信息"<<endl;
        return;
    }
    qDebug()<<"输入的准备已经完成";
}

//根据我们需要的封装格式进行处理
void fcoverh264::outPut(QString fileout)
{
    //猜测编码器
    AVOutputFormat *avformat = av_guess_format(nullptr,fileout.toStdString().c_str(),nullptr);
    if(avformat==nullptr)
    {
        qDebug()<<"没有编码器!";
        return;
    }
    qDebug()<<"AVOutputFormat";

    //保存输出视频信息的结构体
    formatout = avformat_alloc_context();
    //设置输出格式
    formatout->oformat = avformat;

    //打开视频流 文件流
    //参数1:输入输出的上下文对象
    //参数2:文件流路径
    //参数3:文件打开格式 写的方式
    int res=avio_open(&formatout->pb,fileout.toStdString().c_str(),AVIO_FLAG_WRITE);
    if(res<0)
    {
        qDebug()<<"open file error";
        return;
    }
    qDebug()<<"avio_open";
    //新建视频流
    //参数1:视频信息结构体
    //参数2:新建流 的 返回新建流 的地址
    AVStream *newStream =avformat_new_stream(formatout,nullptr);
    if(newStream==nullptr)
    {
        qDebug()<<"打开视频流失败";
        return;
    }
    qDebug()<<"newStream";
    //编码器对应参数设置  拷贝参数设置   newStream:输入进入流的参数设置
    res = avcodec_parameters_copy(newStream->codecpar,forContext->streams[videoType]->codecpar);
    qDebug()<<"res="<<res;
    if(res<0)
    {
        qDebug()<<"拷贝失败!";
        return;
    }
    qDebug()<<"res="<<res;
    //设置新的流里面 codec_tag 设置为0
    newStream->codecpar->codec_tag = 0;

    //头部信息写入----写入成功与否
    res = avformat_write_header(formatout,nullptr);//formatout封装格式的结构体
    //判断写入成功与否
    if(res<0)
    {
        qDebug()<<"写入头部信息失败!";
        return;
    }
    qDebug()<<"res="<<res;

    //开始读取码流数据
    pkt = (AVPacket*)malloc(sizeof(AVPacket));
    //算出这张图有多大
    int size = newStream->codecpar->width*newStream->codecpar->height;
    av_new_packet(pkt,size);

    int frameCount=0;

    //一帧一帧的读取
    while(av_read_frame(forContext,pkt)==0)
    {
        //判断这一帧这是不是视频流
        if(pkt->stream_index==videoType)
        {
            frameCount++;
            //如果是视频流----判断有没有设置过 时间基
            if(pkt->pts==AV_NOPTS_VALUE)
            {
                //时间基  time_base AVRational属性
                AVRational timebase=forContext->streams[videoType]->time_base;
                //计算帧之间的长度(duration)   double强制转换
                int64_t duration=(double)AV_TIME_BASE/av_q2d(forContext->streams[videoType]->r_frame_rate);
                //计算显示时间基(pts):公式:(当前帧数*两帧之间的长度))/(输入时间基*AV_TIME_BASE)
                pkt->pts = (double)(frameCount*duration)/(av_q2d(timebase)*AV_TIME_BASE);
                //解码时间基(dts)
                pkt->dts = pkt->pts;
                //目标两帧之间的长度
                pkt->duration = duration/(double)(av_q2d(timebase)*AV_TIME_BASE);
            }
            else if(pkt->pts < pkt->dts)//显示 时间基 小于 解码时间基 不要这样子的
            {
                continue;
            }
            //上述步骤为  时间基设置



            //解码 时间基 真正的转换 如下:

            //显示时间基的转换
            pkt->pts = av_rescale_q_rnd(pkt->pts,forContext->streams[videoType]->time_base,
                                        newStream->time_base,(AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
            //解码时间基的转换
            pkt->dts = av_rescale_q_rnd(pkt->dts,forContext->streams[videoType]->time_base,
                                        newStream->time_base,(AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
            //数据时长设置
            pkt->duration = av_rescale_q(pkt->duration,forContext->streams[videoType]->time_base,
                                             newStream->time_base);
            //数据位置的设置 数据在流信息中的设置
            pkt->pos = -1;
            //数据包的标记:结合AV_PKT_FLAG_KEY使用  最小为1表示这一帧是一个关键帧
            pkt->flags |=AV_PKT_FLAG_KEY;
            //标记:当前写入的这一帧是视频流
            pkt->stream_index = 0;

            //转码后的数据包 写入 目标视频信息 结构体 中
            av_interleaved_write_frame(formatout,pkt);
        }
        //清空处理:重新设置包
        av_packet_unref(pkt);
    }

    //写入尾巴帧
    av_write_trailer(formatout);

    //用完之后进行 关闭 处理 :关闭猜测完的流
    avio_close(formatout->pb);//对应avio_open()
    qDebug()<<"avio_close";

    //释放malloc的空间 释放保存信息的结构体
    av_free(formatout);
    qDebug()<<"av_free";

    //关闭输入流
    avformat_close_input(&forContext);//对应avformat_open_inpu
    qDebug()<<"avformat_close_input";

    //释放forContext结构体空间
    av_free(forContext);
    qDebug()<<"av_free";
}

🟢测试主函数,代码如下:

需要改成avi、mov、flv等格式,同理,在主函数更改后缀即可!

int main(int argc, char *argv[])
{
    fcoverh264 *cover = new fcoverh264;       //转码
    cover->openFile("fileout/code_frame.h264");
    cover->outPut("fileout/code_frame.mp4");
    
    return a.exec();
}

保存的MP4文件及打开效果,如下所示:

🔵输入H.264文件,输出MP4文件

 🔵生成MP4打开效果

🎧接着奏乐接着舞!是用MP4格式进行播放!咱们继续放动漫!

🚀FFMPEG技术---环境配置,详见:

FFmpeg+Qt开发(一):Windows下 环境搭建 详细步骤_猿力猪的博客-CSDN博客_windows下qt使用ffmpeg

🚀​FFMPEG技术---解码流程,详见:

FFmpeg+Qt开发(二):解码流程 详细分析+代码示例 这一篇就够了_猿力猪的博客-CSDN博客_ffmpeg解码教程

🚀FFMPEG技术---编码流程,详见:

FFmpeg+Qt开发(三):编码流程 普通视频编码+示例详解 一学就会_猿力猪的博客-CSDN博客

✍ 本文主要介绍了FFmpeg技术中的转码部分,如有疑问,欢迎各位评论区学习交流!     

✍  觉得博主写的不错的,麻烦!😀点赞!😀评论!😀收藏!支持一下哈!蟹蟹你们! 

  • 89
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 96
    评论
课程摘要1 Qt音视频开发实战 是陈超老师用10年以上音视频一线实战开发经验,四年音视频开发教学经验打造的Qt音视频开发课程。2 Qt音视频开发实战以Qt c++开发为中心。所有课程全部围绕Qt音视频开发展开。3 非常适合那些只做Qt开发的工程师来学习,学习更容易一些。学习目标,培养高级Qt音视频开发工程师.4 Qt音视频开发实战适合那些掌握Qt基础开发,初级开发工程师,月薪在20k以下,通过学习,达到更高薪水的Qt高级开发工程师。 课程优势1 老师过去15年+ 音视频,网络通讯,数字图像处理一线实战经验。过去四年积累了丰富的音视频开发教学经验,学员从年薪30w~50w,就职于腾讯,阿里等各大互联网公司。2 老师10多年Qt开发经验,长期大量使用Qt开发项目,国内最早一批使用Qt的程序员。3 QQ 一对一教学指导,阶段性作业案例小项目指导,就业面试指导。 课程内容1 Qt 核心基础加强。对于那些学了很多年Qt开发而不得要领的学员非常友好。2 音视频开发基础。音视频原理,RGB YUV, 音频原理。图像压缩编码,音频压缩编码,H.264压缩,H.264编码原理 I P B SPS PPS解析,   视频存储容器,mp4 , AAC.  Qt音频视频采集,FFmpeg编码,解码,x264编码,AAC编码。mp3编码。格式换。视频播放器内核。图像渲染。3 OpenGL数字图像处理基础。OpenGL基础,渲染管线,shader编程。滤镜,美颜,后期处理。编写渲染引擎。4 网络socket通讯编程,自定义私有协议。TCP/UDP音视频传输。 学员要求1 熟悉c/c++,掌握Qt基本控件,类基础。2 每天学习两小时,学习周期3~5个月,做完练习,小项目。
评论 96
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猿力猪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值