目录
一:视频编码流程
二:FFmpeg编码流程
三:FFmpeg编码函数
四:FFmpeg编码的数据结构
五:FFmpeg数据结构简介
六:FFmpeg数据结构分析
七:编码视频数据
八:相关函数介绍
九:编码--代码具体实现
1.1 纯净的视频编码流程
像素数据->压缩编码数据。
例如编码YUV,就是“YUV->H.264”。
1.2 一般的视频编码流程
将像素流存储为一定封装格式(例如H264等)中。
例如将像素流编程成MP4格式,就是“YUV->H.264码流->MP4”。
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():关闭流
从摄像头获取,存储到本地流程:
1、摄像头数据 -> 像素数据->编码数据->.mp4文件
2、avPacket -> avFrame -> avPacket
编码类定义如下
#ifndef VIDEOCODE_H
#define VIDEOCODE_H
#include
//当前C++兼容C语言
extern "C"
{
//avcodec:编解码(最重要的库)
#include
//avformat:封装格式处理
#include
//swscale:视频像素数据格式转换
#include
//avdevice:各种设备的输入输出
#include
//avutil:工具库(大部分库都需要这个库的支持)
#include
//#include
//#include
}
class videoCode : public QObject
{
Q_OBJECT
public:
explicit videoCode(QObject *parent = 0);
//编码一帧图片的函数
void codeingOneFrame(AVFrame *frame);
AVOutputFormat *outputformat;
//视频文件上下文格式
AVFormatContext* avformat_context;
//编解码器上下文格式
AVCodecContext* avcodec_context;
AVStream* outputstream;
AVPacket* av_packet;
//编码帧计数定义
int pkt_index;
//写入尾部信息
void writeTrailer();
signals:
public slots:
};
#endif // VIDEOCODE_H
编码初始化+循环编码+尾部信息写入
编码初始化
videoCode::videoCode(QObject *parent) : QObject(parent)
{
//循环编码前,需要对相关对象进行初始化操作
qDebug()<<"1.编码--注册所有组件";
//1.注册初始化
av_register_all();
//硬件设备初始化--设备上播放视频内容
avdevice_register_all();
//2.定义编码输出文件以及数据包
QString outputfilename = "outfile.h264";
av_packet = av_packet_alloc();
//3.定义视频输入文件上下文
avformat_context = avformat_alloc_context();
if(nullptr == avformat_context)
{
qDebug()<<"视频封装器开辟失败";
}
//4.文件格式上下文猜测--av_guess_format 接收返回AVOutputFormat *outputformat;在头文件中定义
outputformat = av_guess_format(nullptr,outputfilename.toStdString().c_str(),nullptr);
//5.猜测的输出文件格式--把猜测得到的数据存入到视频文件上下文格式中
avformat_context->oformat = outputformat;
qDebug()<<"视频封装器初始化成功";
//6.打开视频流--根据流信息找到编码器 int avio_open(AVIOContext **s, const char *url, int flags);
int ret = avio_open(&avformat_context->pb,outputfilename.toStdString().c_str(),AVIO_FLAG_WRITE);
if(ret < 0)
{
qDebug()<<"编码--打开视频文件失败";
}
//7.新建流信息操作--AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
//接收返回AVStream* outputstream;在头文件中定义
outputstream = avformat_new_stream(avformat_context,nullptr);
if(nullptr == outputstream)
{
qDebug()<<"编码--新建流信息失败";
}
//8.编码时间基设置--1秒编码25张图片--编码频率设置
AVRational rate;
rate.num = 1;
rate.den = 25;
outputstream->time_base = rate;
//9.找到合适的编码器--根据不同文件格式的流信息找到合适编码器
avcodec_context = outputstream->codec;
//10.从上下文中获取编码参数--int avcodec_parameters_from_context(AVCodecParameters *par,const AVCodecContext *codec);
avcodec_parameters_from_context(outputstream->codecpar,avcodec_context);
qDebug()<<"----------------编码准备工作完成-----------------";
pkt_index = 0;//编码帧计数的初始化
}
循环编码
void videoCode::codeingOneFrame(AVFrame *frame)
{
//1.对一帧图片进行处理--包括有图片大小 帧率
frame->pts = pkt_index++;
//2.默认的参数配置
/*除了可以得到视频编解码的相关信息,编码的话还需要设置很大的参数
首先是它的宽度和高度*/
avcodec_context->width = frame->width;
avcodec_context->height = frame->height;
//设置码率,每一秒存的比特,这个值的设置也不要随意,码率太大,视频也会变大
avcodec_context->bit_rate = 400000;
//设置帧率,每一秒多少张图片 -- 25张 把1秒分成了25等分
avcodec_context->time_base = {1,25};
//设置显示的率,也叫码率
avcodec_context->framerate = {25,1};
/*设置每一组的图片数量,IPB帧 I帧存一帧的所有数据,p帧根据I解码,B帧根据前后的两帧解码 10帧为1组
后面的10帧解码不会与前10帧关联*/
avcodec_context->gop_size = 10;//官方建议10帧为1单位
//还有两个量化值需要设置,会影响视频的清晰度,越小越清晰,建议默认就可以了
avcodec_context->qmax = 51;
avcodec_context->qmin = 10;
//设置一下b帧为0,这样的话就只有I帧和p帧
avcodec_context->max_b_frames = 0;
//设置编码格式--YUV420P像素数据
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
//设置流的格式:视频流还是音频流--视频流
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
//设置编码器的id,根据匹配到的AVOutputFormat对应信息来设置
avcodec_context->codec_id = outputformat->video_codec;
//3.寻找合适的编码器--AVCodec *avcodec_find_encoder(enum AVCodecID id);
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if(nullptr == avcodec_context)
{
qDebug()<<"编码器查找失败";
}
qDebug()<<"编码器查找成功";
//4.打开编码器--int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
if(avcodec_open2(avcodec_context,avcodec,nullptr) < 0)
{
qDebug()<<"编码器打开失败";
}
//5.写封装格式头部信息--int avformat_write_header(AVFormatContext *s, AVDictionary **options);
if(avformat_write_header(avformat_context,nullptr) < 0)
{
qDebug()<<"写入编码头失败";
}
//6.循环写入编码图片数据--发送一帧数据,编码一帧数据
//int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
int ret = avcodec_send_frame(avcodec_context,frame);
if(ret != 0)
{
qDebug()<<"发送一帧数据进行编码";
}
if(ret >= 0)
{
//接收一帧数据进行编码 从帧收到包--int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
ret = avcodec_receive_packet(avcodec_context,av_packet);
if(ret != 0)
{
qDebug()<<"编码异常";
}
//写入包数据--int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
av_interleaved_write_frame(avformat_context,av_packet);
}
}
尾部信息写入
void videoCode::writeTrailer()
{
//写入尾部信息
av_write_trailer(avformat_context);
//关闭文件指针
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
}
pvideoCode = new videoCode(); //编码环境初始化
//解码得到的--分两部分走
//一部分做显示 发送信号(图片)--emit
emit sigGetOneFrame(image);
//一部分做编码
//循环 编码每一帧数据 传入pFramein
pvideoCode->codeingOneFrame(pFramein);
void videoDecodeThread::videoStop()
{
//停止线程--改变标志位
m_stop = true;
//写编码尾部信息 手动关闭xxx.h264这个文件指针
pvideoCode->writeTrailer();
}
结果测试:
播放视频,解码的同时,一部分进行显示,一部分进行编码
在bin文件下可查看到编码所得到的.h264文件
将.h264文件拖至软件中可查看视频播放信息