视频编码
视频编码流程图
①对编码需要用到的数据进行初始化
②在编码类的构造函数中进行初始化,注册所有组件,开辟内存空间
④封装一个函数用于编码前的系列准备
猜测编码输出的格式是否存在并判断
打开视频文件流并判断是否打开成功
首先要设置输出格式
打开视频文件流
新建视频流
编码器的参数设置:格式、宽、高、码率、帧率等
根据猜测到的编码器的 ID 查找编码器并判断
找到之后,就可以打开编码器
编码器打开成功即可写入编码的头部信息,完成编码前的所有初始化工作
⑤封装一个编码的函数
首先发送一帧需要编码的像素数据给编码器并判断
.编码的一帧像素数据给编码器进行编码的时候,可能一个 AVPacket 放不下,就需要两个
AVPacket,循环处理去接收码流数据
⑥编码完成后,记得写入尾巴帧,结束此次的编码(并将需要释放的资源释放掉)
⑦应用
创建一个编码类对象
编码前准备
编码
写入尾巴帧
编码类package_ecoder代码
package_ecoder.h
#ifndef PACKAGE_ECODER_H
#define PACKAGE_ECODER_H
#include <QDebug>
#include <QImage>
#include <QThread>
#include "package_decoder.h"
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavutil/adler32.h>
#include <libswscale/swscale.h>
}
class package_ecoder:public QThread
{
Q_OBJECT
public:
package_ecoder();
void condeInit(int width,int height);
void codecFrame(AVFrame * frame);
void writeEnd();
void run();
private:
AVFormatContext * forContext;//保存视频信息结构体
AVCodecContext * codecContext;//保存编辑器信息的一个上下文结构体
AVOutputFormat * avformat;
AVStream *newStream;
AVCodec * codec;
AVPacket *pkt;
int pkt_index;
int flag;
AVFrame * yuv;
package_decoder *dec1;
public slots:
void recviceyuv(AVFrame * yuv);
void endrecvice();
};
#endif // PACKAGE_ECODER_H
package_ecoder.cpp
#include "package_ecoder.h"
package_ecoder::package_ecoder()
{
av_register_all();//注册组件
forContext = avformat_alloc_context();//分配内存空间
pkt = av_packet_alloc();//分配内存空间
pkt_index=0;
flag=0;
dec1 = new package_decoder;
}
void package_ecoder::condeInit(int width, int height)
{
char * fileout ="save.h264";
//猜测编码器
avformat =av_guess_format(nullptr,fileout,nullptr);
if(avformat==nullptr)//判断有无数据文件(.h264)
{
qDebug()<<"没有数据文件";
return;
}
//设置视频信息最终的输出格式
forContext->oformat=avformat;
//打开文件流(打开格式:写入的方式打开)
int res=avio_open(&forContext->pb,fileout,AVIO_FLAG_WRITE);
if(res<0)
{
qDebug()<<"打开文件流对象失败";
return;
}
//新建视频流
newStream=avformat_new_stream(forContext,nullptr);
if(newStream==nullptr)
{
qDebug()<<"新建流失败";
return;
}
//目标文件的设置已经完成
//对编码器进行一系列设置
//根据新建流去获取到编码器上下文对象结构体信息
codecContext=newStream->codec;
codecContext->width=width;
codecContext->height=height;
//编码格式的设置:使用的是yuv
codecContext->pix_fmt=AV_PIX_FMT_YUV420P;
//设置码率,码率不能太大,太大视频也会太大
codecContext->bit_rate=400000;
//设置帧率,每秒25张
codecContext->time_base={1,25};
//设置显示的帧率
codecContext->framerate={25,1};
//每10张图片为一组
codecContext->gop_size=10;
//影响视频的清晰度,越小越清晰
codecContext->qmax=51;
codecContext->qmin=10;
//整个视频中没有b帧
codecContext->max_b_frames=0;
//设置流为视频流
codecContext->codec_type=AVMEDIA_TYPE_VIDEO;
//设置编码器的id
codecContext->codec_id=avformat->video_codec;
//根据编码器的id去查找有无编码器存在
codec=avcodec_find_encoder(codecContext->codec_id);
if(codec==nullptr)
{
qDebug()<<"没有找到编码器";
return;
}
//打开编码器
res=avcodec_open2(codecContext,codec,nullptr);
if(res<0)
{
qDebug()<<"打开编码器失败";
return;
}
//将编码相关的流信息写入到媒体文件中
res=avformat_write_header(forContext,nullptr);
if(res<0)
{
qDebug()<<"写入失败";
return;
}
else
{
qDebug()<<"写入成功";
return;
}
}
void package_ecoder::codecFrame(AVFrame *frame)
{
int res= avcodec_send_frame(codecContext,frame);
if(res<0)
{
qDebug()<<"发送失败";
qDebug()<<res;
return;
}
else
{
qDebug()<<"发送成功";
qDebug()<<res;
}
//进行编码的时候要注意:有可能一帧的像素数据压缩为一个AVpacket可能存不下
while(res>=0)
{
frame->pts=pkt_index++;
//接收编码之后的压缩码流数据
res=avcodec_receive_packet(codecContext,pkt);
if(res==AVERROR(EAGAIN)||res==AVERROR(EOF))
{
qDebug()<<"一帧的的像素数据没有存完";
}
else if(res<0)
{
qDebug()<<"失败了";
return;
}
//设置当前这一帧是视频流
pkt->stream_index=0;
//将编码得到的码流数据写入文件中
av_interleaved_write_frame(forContext,pkt);
}
//packet在使用一次之后记得要清空,便于写入第二帧
av_packet_unref(pkt);
}
//写入尾巴帧
void package_ecoder::writeEnd()
{
av_write_trailer(forContext);
}
void package_ecoder::run()
{
while (1)
{
if(this->flag==0)
{
this->codecFrame(this->yuv);
}
else if(this->flag==1)
{
qDebug()<<"写入尾帧";
this->writeEnd();
break;
}
}
}
void package_ecoder::recviceyuv(AVFrame * yuv)
{
qDebug()<<"找到yuv";
this->yuv=yuv;
}
void package_ecoder::endrecvice()
{
qDebug()<<"结束";
this->flag=1;
}