- 📢欢迎各位读者:点赞 👍 收藏 ⭐留言 📝
- 📢博客主页:https://blog.csdn.net/qq_59134387😀
- 📢原创不易,转载请标明出处;如有错误,敬请指正批评!💦
- 📢我不去想是否能够成功,既然选择了远方,便只顾风雨兼程!✨
文章目录
前言
本篇,作者将分享如何使用FFmpeg对解码后的YUV数据编码成H.246格式视频。
一、编码流程
- 编码流程可分为两大步骤:一是编码初始化,二是进行循环编码,如下图所示。
二、具体实现
1.mp4视频解码为YUV数据
- 在编码之前,我们需要将mp4视频解码为YUV数据,如何解码可查看以下博客。
(1)浅显易懂 FFmpeg学习(02)— QT+FFmpeg解码本地视频(上篇)
(2)浅显易懂 FFmpeg学习(03)— QT+FFmpeg解码本地视频(中篇)
(3)浅显易懂 FFmpeg学习(04)— QT+FFmpeg解码本地视频(下篇)
- 本次将采集本地摄像头数据进行编码,需要修改解封装函数的参数,如下图所示。
//获取摄像头信息
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
//数据缓冲区扩容
AVDictionary* dict = NULL;
av_dict_set(&dict, "rtbufsize", "655350000", 0);
//设置视频的推流格式
AVInputFormat *ifmt=av_find_input_format("dshow");
//对视频流进行解封装
avformat_open_input(&this->formatContext,"video="+cameras[0].description().toLatin1(),ifmt,&dict);
获取本地摄像头方法可参考:【QT学习】如何获取本地摄像头信息?
- 从mp4中读取数据帧,并转换为YUV帧
//给缓冲区动态分配内存
this->pOutbufferYUV = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,this->codecContext->width,this->codecContext->height));
//初始化缓冲区
avpicture_fill((AVPicture *)this->frameYUV, this->pOutbufferYUV,AV_PIX_FMT_YUV420P, this->codecContext->width,this->codecContext->height);
//设置转换格式
this->swsYUV = sws_getContext(this->codecContext->width,this->codecContext->height,this->codecContext->pix_fmt,
this->codecContext->width,this->codecContext->height,AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL,NULL,NULL);
//帧数据转换
sws_scale(this->swsYUV, (const unsigned char* const*)this->frame->data, this->frame->linesize, 0,
this->codecContext->height,this->frameYUV->data,this->frameYUV->linesize);
- 将mp4视频读取到的帧转化为YUV数据帧,并发送至编码器进行编码
//格式转换
sws_scale(this->swsYUV, (const unsigned char* const*)this->frame->data, this->frame->linesize, 0,
this->codecContext->height,this->frameYUV->data,this->frameYUV->linesize);
//编码一帧
this->videoEncode->codecFrame(this->frameYUV);
2.编码类定义
class videoencode : public QObject
{
Q_OBJECT
public:
explicit videoencode(QObject *parent = 0);
void codecInit();
void codecFrame(AVFrame *frame);
void writeTailer();
private:
AVFormatContext *formatContext;
AVOutputFormat *outputFormat;
AVStream *newStream;
AVCodecContext *codecContext;
AVCodec *codec;
AVPacket *packet;
int pktIndex;
signals:
public slots:
};
3.编码初始化
(1)环境注册
av_register_all(); //针对复用器、编码器等进行注册
(2)猜测并设置输出格式
//封装格式上下文初始化
this->formatContext = avformat_alloc_context();
//根据输出文件名,猜测并设置输出格式
const char* out_file="./codeH264.h264";
this->outputFormat = av_guess_format(NULL,out_file,NULL);
if(this->outputFormat == NULL)
{
qDebug() << "猜测格式失败!";
}
//设置输出格式
this->formatContext->oformat = this->outputFormat;
(3)打开视频流文件
ret = avio_open(&this->formatContext->pb,out_file,AVIO_FLAG_WRITE);
if(ret < 0)
{
char *result = new char[64];
av_strerror(ret,result,64);
qDebug() << QString("打开视频流文件失败:%1").arg(result);
}
(4)创建新视频流
this->newStream = avformat_new_stream(this->formatContext,NULL);
(5)设置编码器参数
this->codecContext = this->newStream->codec; //获取编码器上下文
this->codecContext->width = 1280; //设置编码器宽度
this->codecContext->height = 720; //设置编码器高度
this->codecContext->bit_rate=400000; //设置每一秒存的bit
this->codecContext->time_base={1,25}; //设置帧率,25帧/秒
this->codecContext->framerate={25,1}; //设置码率,25码/帧
this->codecContext->gop_size=10; //设置每组帧数,IPB帧(官方建议10帧一组)
this->codecContext->qmax=51; //设置最大量化值(影响视频清晰度)
this->codecContext->qmin=10; //设置最小量化值(影响视频清晰度)
this->codecContext->max_b_frames=0; //设置B帧为0,仅存在I帧和P帧
this->codecContext->pix_fmt=AV_PIX_FMT_YUV420P; //设置编码格式YUV420P 像素数据
this->codecContext->codec_type=AVMEDIA_TYPE_VIDEO; //设置流格式为视频流
this->codecContext->codec_id=this->formatContext->oformat->video_codec; //设置编码器id(根据AVFormatContext)
(6)寻找合适的编码器
this->codec = avcodec_find_encoder(this->codecContext->codec_id);
if(this->codec == NULL)
{
qDebug() << "找不到合适的编码器!";
}
qDebug() << "寻找编码器成功!";
(7)打开编码器
ret = avcodec_open2(this->codecContext,this->codec,NULL);
if(ret < 0)
{
char *result = new char[64];
av_strerror(ret,result,64);
qDebug() << QString("打开编码器失败:%1").arg(result);
}
(8)编写编码头部信息
ret = avformat_write_header(this->formatContext,NULL);
if(ret < 0)
{
char *result = new char[64];
av_strerror(ret,result,64);
qDebug() << QString("编写编码头部信息失败:%1").arg(result);
}
(9)初始化数据包
this->pktIndex = 0;
this->packet = av_packet_alloc();
4.循环编码流程
(1)发送数据帧至编码器上下文
int ret = avcodec_send_frame(this->codecContext,frame);
if(ret < 0)
{
char *result = new char[64];
av_strerror(ret,result,64);
qDebug() << QString("发送像素数据失败:%1").arg(result);
}
(2)将编码器上下文循环打包
while(ret >= 0)
{
//设置事件基,确保视频播放顺序
frame->pts = this->pktIndex++;
//从编码器上下文中接收数据包
ret = avcodec_receive_packet(this->codecContext,this->packet);
if(ret < 0)
{
qDebug()<<"打包完成!";
return;
}
//设置数据包信息为视频流数据
this->packet->stream_index = 0;
//将数据包写入封装格式上下文(码流数据写入文件)
av_interleaved_write_frame(this->formatContext,this->packet);
//清空数据包
av_packet_unref(this->packet);
}
(3)编写编码尾部信息
//写入编码的尾部信息
av_write_trailer(this->formatContext);
//关闭输入流
avio_close(this->formatContext->pb);
//释放视频信息
avformat_free_context(this->formatContext);
三、实现效果
- 在工程文件目录下,生成了后缀.h264文件,并且该文件有数据。
- 通过特定的播放器打开.h264文件进行播放,可以发现已经将摄像头推流数据编码为.h264文件。
总结
以上就是浅显易懂 FFmpeg学习(05)— QT+FFmpeg编码(YUV编H.264)的所有内容,希望大家阅读后都能有所收获!原创不易,转载请标明出处,若文章出现有误之处,欢迎读者留言指正批评!