视频编码,就是把非压缩的像素数据编码为压缩的视频码流数据;
编码YUV,格式从YUV→H.264
一、FFmpeg编码函数
av_register_all():注册所有组件
av_guess_format(): 已经注册的最合适的输出格式
avcodec_find_encoder(): 查找一个已经注册的音视频编码器
avcode_open2():打开编码码器
avformat_write_header():把流头信息写入到媒体文件中
av_read_frame():读取一帧压缩数据
avcodec_send_frame():发送一帧像素数据
avcodec_receive_packet():接受一帧编码数据
av_packet_rescale_ts():时间基转换
av_write_frame():写一帧数据
flush_encoder():将最后一帧写入文件
av_write_trailer():把流尾信息写入文件
av_code_close():关闭流
二、视频编码流程图
三、编码数据结构介绍
AVFormatContext封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息AVInputFormat每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体
AVStream视频文件中每个视频(音频)流对应一个该结构体
AVCodecContext
编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodec每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
AVPacket存储一帧压缩编码数据。
AVFrame存储一帧解码后像素(采样)数据
四、实现代码
头文件
#ifndef CODING_H
#define CODING_H
#include <QObject>
#include "mythread.h"
#include <QDebug>
#include "video.h"
extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
//avformat:封装格式处理
#include <libavformat/avformat.h>
//swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
//avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
//avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include "libswresample/swresample.h"
}
class coding : public myThread//自定义线程,可直接继承QThread
{
Q_OBJECT
public:
coding(QString filename);
//初始化
void codingInit(QString filename);
//编码过程
void codingFrame(AVFrame *frameYUV);
//线程
void run();
void writeTailer();//写入尾部帧
void stop();
~coding();
private:
AVFormatContext *formatContent;//保存视频信息
AVCodecContext *codecContent;//保存编码器相关信息
int pkt_index;//帧序列
AVPacket *pkt;//码流包
bool m_stop;
};
源文件
#include "coding.h"
coding::coding(QString filename)
{
this->m_stop = false;
codingInit(filename);
}
void coding::codingInit(QString filename)
{
//1.注册组件
av_register_all();
avdevice_register_all();//硬件初始化
//2.编码前的系列准备-猜测输出格式
formatContent=avformat_alloc_context();
/*猜测输出的格式是否存在:
参数 1:用 null;
参数 2:输出的文件名;
参数 3:注册格式的类型,用null
如果没有猜测到返回NULL*/
AVOutputFormat *outformat=av_guess_format(nullptr,filename.toStdString().c_str(),nullptr);
if(outformat==nullptr)
{
qDebug()<<"猜测格式失败";
return;
}
//3.打开视频文件流
formatContent->oformat=outformat;
/*打开文件流:
参数 1:AVIOContext--输入输出的上下文对象(视频流信息结构体中的一个成员pb);
参数 2:文件名;
参数 3:文件以什么形式打开,编码是以写的方式打开;
返回值:成功与否,<0 为失败
*/
int res=avio_open(&formatContent->pb,filename.toStdString().c_str(),AVIO_FLAG_WRITE);
if(res<0)
{
qDebug()<<"打开视频文件失败";
return;
}
//4.新建视频流
/*打开成功就可以新建视频流
参数 1:视频流信息结构体;
参数 2:NULL
返回值:创建好的视频流
*/
AVStream *newStream=avformat_new_stream(formatContent,nullptr);
if(newStream==nullptr)
{
qDebug()<<"新建视频流失败";
return;
}
//5.编码器的参数设置:格式、宽、高、码率、帧率等
//编码器上下文->编码器 id
codecContent=newStream->codec;
//设置视频宽度高度
codecContent->width=640;
codecContent->height=360;
//设置码率,每一秒存的比特,这个值的设置也不要随意;码率太大,视频也会变大;
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;
//6.根据编码器id找到对应编码器
//查找编码器
AVCodec *codec=avcodec_find_encoder(codecContent->codec_id);
if(codec==nullptr)
{
qDebug()<<"没找到对应的编码器";
return;
}
//7.打开编码器
res=avcodec_open2(codecContent,codec,nullptr);
if(res<0)
{
qDebug()<<"打开编码器失败";
return;
}
//8.写入头部信息,完成初始化工作
res=avformat_write_header(formatContent,nullptr);
if(res<0)
{
qDebug()<<"写入头部信息失败";
return;
}
this->pkt_index=0;
pkt=av_packet_alloc();
qDebug()<<"初始化工作完成";
}
void coding::codingFrame(AVFrame *frameYUV)
{
//9.开始编码
/*发送一帧像素数据到编码器中的上下文对象中
* 参数 1: 编码器的上下文对象;
* 参数 2:发送的一帧解码线程传入的YUV像素数据
*/
int res=avcodec_send_frame(codecContent,frameYUV);
//判断发送成功与否
if(res<0)
{
qDebug()<<"发送给编码器失败";
return;
}
//10.循环处理 接收码流数据
/*发送成功
* 解码后可能一个 AVFrame 存不下像素数据,同样的,传进来的一帧 yuv 数据,
* 可能一个 AVPacket 够放,也有可能需要两个 AVPacket 来存,所以做循环来处理
*/
while(res>=0)
{
/*显示时间基,保证显示的顺序,定义一个变量,每次加+1,每次开始编码的时候赋值为 0*/
frameYUV->pts=pkt_index++;
/*发送像素数据与接收压缩数据是配合使用的哦!合起来统称为编码
参数 1:是编码器上下文对象;
参数 2:用来保存压缩数据的 AVPacket
返回值:读完或者出错
*/
res=avcodec_receive_packet(codecContent,pkt);
//没处理完
if(res<0)
{
qDebug()<<"打包失败";
return;
}
//否则的话,就可以写入一帧数据
//设置一下 pkt 的流确保是视频流
pkt->stream_index=0;
/* 写入数据到视频信息结构体中
* 参数 1:视频流的基本信息结构体(AVFormatContext);
* 参数 2:压缩数据
*/
av_interleaved_write_frame(formatContent,pkt);
}
qDebug()<<"write success";
//这里的 pkt 使用完之后一定要记得重置
av_packet_unref(pkt);
}
void coding::run()
{
while(m_stop==false)
{
while(!video::YUVQueue.isEmpty())//如果队列不为空
{
qDebug()<<"出队列前大小"<<video::YUVQueue.size();
this->codingFrame(video::YUVQueue.dequeue());//出队列
qDebug()<<"出队列后大小"<<video::YUVQueue.size();
}
}
}
void coding::writeTailer()
{
av_write_trailer(formatContent);
}
void coding::stop()
{
m_stop=true;
}
coding::~coding()
{
qDebug()<<"关闭所有编码组件";
//关闭输入流
avio_close(formatContent->pb);
//释放视频信息
avformat_free_context(formatContent);
}
编码完可以看到输出了.h264文件
用ESEyE软件可进行播放
感谢观看!!!!
以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!