目录
🚗本文主要是基于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转码示例
🟢转码类的定义:
1 |
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; }; |
🟢具体步骤如下:
1 |
#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等格式,同理,在主函数更改后缀即可!
1 |
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技术中的转码部分,如有疑问,欢迎各位评论区学习交流!
✍ 觉得博主写的不错的,麻烦!😀点赞!😀评论!😀收藏!支持一下哈!蟹蟹你们!