一、FFMPEG 编码
1.1编码流程
1.2编码示例
本文主要是基于FFMPEG技术解码部分,继续延申,对FFMPEG编码部分进行详细介绍
希望对在学习FFMPEG技术的小伙伴们有所帮助!
编码:像素数据➡编码后➡压缩码流数据。
例如:将YUV的像素数据➡编码后➡得到H.264的压缩码流数据。
编码的流程图,如下所示:
编码与解码同理,在使用FFMPEG编码视频文件之前,要确定完成 注册所有组件 这一步骤
其中,包含了相关结构体,本文不做过多阐述,详细可于文末链接,查看解码部分内容。
编码思路分析:
- 注册所有的组件
- 根据需要的码流数据的格式,来猜测的需要的编码器
- 打开目标文件流
- 新建视频流
- 设置编码器上下结构的一系列参数,为编码做好准备
- 查找对应的编码器
- 打开编码器
- 读取普通视频数据 or 摄像头数据,进行解码,保证得到的是YUV的像素数据
- 先写入编码的头部信息
- 正式开始,进行编码,将一帧像素数据压缩成码流数据
- 保存写入文件中
- 得到最终的编码后的码流数据
下面,博主举例说明,对解码后的视频文件来进行编码,究竟是怎么样的操作呢?
解码类的定义:
class fecodec
{
public:
fecodec();
//注册组件
void registerFFmpeg();
//编码器 参数初始化
void condeInit(char *flie_out);
//编码
void codecFrame(AVFrame *frame);
//写入尾巴帧
void writeEndFrame();
private:
AVFormatContext *formatContent;//用来保存视频相关信息的结构体
AVCodecContext *codecContent;//用来保存编解码信息的上下文结构体
AVPacket *pkt;//码流包
int pkt_inpex;//每一帧像素序号
int flag;
};
具体实现如下:
注册所有组件
void fdecodec::registerFFmpeg()
{
//注册所有的组件
av_register_all();
}
猜测输出格式
/*猜测输出的格式是否存在:参数详解
参数1:null
参数2:输出文件名
参数3:注册格式类型,用null
返回值:猜到的转换格式,没匹配到,返回NULL
*/
formatContent=avformat_alloc_context();
//猜测编码器
AVOutputFormat *outformat = av_guess_format(nullptr,file_out,nullptr);
if(outformat==nullptr)
{
qDebug()<<"猜测格式错误!";
return;
}
打开视频文件流
//查找对应的编译器 设置视频信息最终的输出格式
formatContent->oformat = outformat;//oformat输出的格式
//打开视频流 文件流
//参数1:输入输出的上下文对象
//参数2:文件流路径
//参数3:文件打开格式 写的方式
int res=avio_open(&formatContext->pb,file_out,AVIO_FLAG_WRITE);
if(res<0)
{
qDebug()<<"打开文件失败!";
return;
}
新建视频流
//新建视频流
//参数1:视频信息结构体
//参数2:新建流 的 返回新建流 的地址
AVStream *newStream =avformat_new_stream(formatContent,nullptr);
if(newStream==nullptr)
{
qDebug()<<"打开视频流失败";
return;
}
编码器的参数设置:格式、宽、高、码率、帧率
/*数据初始化--AVCodecContext 编码器的上下文结构体,
保存视频编解码的相关信息,就可以得到解码器 id*/
codecContent=newStream->codec;
/*除了可以得到视频编解码的相关信息,编码的话还需要设置很多的参数
首先是它的宽度和高度*/
codecContent->width=640;
codecContent->height=368;
//设置码率,每一秒存的比特,这个值的设置也不要随意;码率太大,视频也会变大;
codecContent->bit_rate=400000;
//设置帧率-每一秒多少张图片--25 张
codecContent->time_base={1,25};
//设置显示的率,也叫码率
codecContent->framerate={25,1};
/*设置每一组的图片数量,IPB 帧,I 帧存一帧的所有数据,P 帧根据 I 解码,B 帧根据前后的两帧解码;10 帧为一组;
后面的 10 帧解码不会与前 10 帧有关联。*/
codecContent->gop_size=10;//官方建议 10 帧为一个单位
//还有两个量化值需要设置:会影响视频的清晰度,越小越清晰,建议默认就可以了
codecContent->qmax=51;
codecContent->qmin=10;
//设置一下 b 帧为 0,这样的话就只有 I 帧和 P 帧
codecContent->max_b_frames=0;
//设置编码格式--YUV420P 像素数据
codecContent->pix_fmt=AV_PIX_FMT_YUV420P;
//设置流的格式:视频流还是音频流--视频流
codecContent->codec_type=AVMEDIA_TYPE_VIDEO;
//设置编码器的 id,根据匹配到的 AVOutputFormat 对应信息来设置
codecContent->codec_id=outformat->video_codec;
根据猜测到的编码器ID查找编码器
//根据编码器的id查找有没有这样的编码器存在
AVCodec *codec = avcodec_find_encoder(codecContent->codec_id);
if(codec==nullptr)
{
qDebug()<<"编码器不存在!";
return;
}
找到之后打开编码器
//打开编码器
res = avcodec_open2(codecContent,codec,nullptr);
if(res!=0)
{
qDebug()<<"打开编码器失败!";
return;
}
写入编码的头部信息,完成编码前的所有初始化工作
//将编码相关的流信息写入到媒体的文件当中
res = avformat_write_header(formatContent,nullptr);
if(res<0)
{
qDebug()<<"写入头部信息失败!";
return;
}
this->pkt_index=0;
pkt=av_packet_alloc();
qDebug()<<"编码初始化准备完毕!";
开始真正的编码
//frame是像素数据
qDebug()<<"开始编码中!";
//发送像素数据给编码器上下文结构体
//参数1:编码器的上下文对象
//参数2:发送的一帧yuv像素数据
int res = avcodec_send_frame(codecContent,frame);
if(res<0)
{
qDebug()<<"发送失败!";
return;
}
循环处理去接收码流数据、写入视频信息完成一帧编码
PS:编码的一帧像素数据给编码器进行编码的时候,可能一个 AVPacket 放不下,就需要两个
AVPacket。另外,将接收到的 AVPacket 码流数据写入视频信息中,完成一帧像素数据的编码。
/*发送成功,进行编码的时候要注意:
*有可能一帧的像素数据压缩之后一个AVPacket可能存不下
*/
while (res>=0)//多次的打包 每一帧
{
//显示时间基 用来保存数据先后顺序的
frame->pts = pkt_inpex++;
//发送像素数据与接收压缩数据是配合使用的 合起来统称为编码
//参数 1:是编码器上下文对象;
//参数 2:用来保存压缩数据的 AVPacket
//返回值:读完或者出错
//接受编码之后的码流数据
res = avcodec_receive_packet(codecContent,pkt);
if(res == AVERROR(EAGAIN) || res == AVERROR_EOF)
{
//说明这一帧的像素数据一个包没有存完
qDebug()<<"打包当中!";
}
else if(res<0)//失败了
{
qDebug()<<"打包失败!";
return;
}
//设置当前这一帧是视频流 索引为0
pkt->stream_index=0;
//记得将编码得到的码流数据写入到文件当中
av_interleaved_write_frame(forContent,pkt);
}
qDebug()<<"写入成功!";
//packet使用完要清空 清空前面的码流->存储新的码流数据
av_packet_unref(pkt);//清空处理
qDebug()<<"编码完成一帧";
写入尾巴帧、关闭输入流、释放空间
void fecodec::writeEndFrame()
{
//写入尾巴帧信息
av_write_trailer(formatContent);
//关闭输入流
avio_close(formatContent->pb);
//释放视频信息
avformat_free_context(formatContent);
}
基本流程已经完成,接下来结合前一篇文章(解码部分),把解码得到的像素数据给编码类去实现编码
fdecodec::fdecodec()
{
ecode=new fecodec;
ecode->registerFFmpeg();
ecode->codecInit("fileout/code_frame.h264");
}
QImage image((uchar *)pictureRGB->data[0],pictureRGB->width,pictureRGB->height,QImage::Format_RGB32);//像素数据
emit sendImage(image);//实时播放
emit SendImage(pictureYUV);//编码yuv像素数据
ecode->writeEndFrame();//尾巴帧 编码
qDebug()<<"编码完成!"<
测试主函数,代码如下:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//这边是自己创建的一个播放器界面
PlayerWidget *play=new PlayerWidget;
play->show();
return a.exec();
}
保存的H.264文件及打开效果,如下所示:
H.264文件
生成H.264打开效果
FFMPEG技术---环境配置,详见:
FFmpeg+Qt开发(一):Windows下 环境搭建 详细步骤_猿力猪的博客-CSDN博客_ffmpeg库
FFMPEG技术---解码流程,详见:
FFmpeg+Qt开发(二):解码流程 详细分析+代码示例 这一篇就够了_猿力猪的博客-CSDN博客_ffmpeg解码教程
FFMPEG技术---转码流程,详见:
FFmpeg+Qt开发(四):转码流程 H.264 转(mov、mp4、avi、flv)等视频格式 示例详解_猿力猪的博客-CSDN博客
✍ 本文主要介绍了FFmpeg技术中的编码部分,如有疑问,欢迎各位评论区学习交流!
✍ 觉得博主写的不错的,麻烦!点赞!评论!收藏!支持一下哈!蟹蟹你们!