代码封装实际是一个见仁见智的工作,可能不同的人对代码结构的理解不同,实现的封装方式也会存在差异。
在使用QT做窗体程序时有一些占用时间较长的函数在运行时会使QT的窗体控件无法得到响应,也就是常说的程序假死,其实程序还是在运行的,只是你得不到反馈而已,这种情况,可以使用线程来把运行时间长的函数与窗体主线成分开了。
以下是线程类的代码:
class DecodeThread : public QThread
{
public:
DecodeThread();
void run();
void stop();
bool m_stop;
};
怎么使用线程,具体的看代码注释
DecodeThread::DecodeThread()
{
this->m_stop = false;
}
void DecodeThread::run()
{
qDebug() << "线程开始";
while(!m_stop)
{
//线程处理函数
for(int i = 0; i < 1000; i++)
{
qDebug() << i ;
}
sleep(1000);
}
qDebug() << "线程结束";
}
void DecodeThread::stop()
{
m_stop = true;
}
读者可根据音视频解码流程进行封装函数。经分析,第六步:循环读取视频帧,进行循环解码 操作应放入线程执行函数中进行调用。偷个懒我把前面五个步骤统一封装成一个函数,把音视频参数分别放入对应结构体进行管理,以视频解码线程封装伟例子进行分析:
第一步:注册所有组件 第二步:打开视频输入文件 第三步:查找视频文件信息 第四步:查找解码器 第五步:打开解码器 第六步:循环读取视频帧,进行循环解码 第七步:关闭解码组件
视频参数结构体
struct VideoState{
QString cintFilePath; //输入文件
QString coutFilePath; //输出文件
AVFormatContext *pFormatContext; //封装格式上下文
AVStream *pvideoStream; //视频流
AVCodecContext *pCodecContext; //视频解码器上下文
AVCodec *pCodec; //解码器
int av_stream_index;
AVPacket* pPacket;
AVFrame* pFramein;
AVFrame* pFrameyuv420p;
uint8_t *pOutbuffer;
SwsContext* pSwsContext; //转码视频
FILE* pyuvfp;
};
视频解码线程类结构如下
class VideoDecoding : public QThread
{
public:
VideoDecoding(QString &cinfilename);
~VideoDecoding();
bool createVideoState(QString &cinfilename);
void run();
void stop();
private:
VideoState *pVideoState;
int ret;
bool m_stop;
};
部分核心代码如下:
1、创建解码视频对象操作
//创建视频对象
bool VideoDecoding::createVideoState(QString &cinfilename)
{
qDebug()<<"第一步:注册所有组件";
av_register_all();
qDebug()<<"第二步:打开视频输入文件";
pVideoState->pFormatContext = avformat_alloc_context();
ret = avformat_open_input(&pVideoState->pFormatContext,cinfilename.toStdString().c_str(),NULL,NULL);
if (ret != 0)
{
return false;
}
qDebug()<<"第三步:查找视频文件信息";
ret = avformat_find_stream_info(pVideoState->pFormatContext, NULL);
if (ret < 0)
{
return false;
}
qDebug()<<"第四步:查找解码器";
int av_stream_index = -1;
for (int i = 0; i < pVideoState->pFormatContext->nb_streams; ++i) {
//循环遍历每一流
//视频流、音频流、字幕流等等...
if (pVideoState->pFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
//找到了
av_stream_index = i;
break;
}
}
if (av_stream_index == -1 )
{
qDebug()<查找到视频解码器上下文->视频压缩数据
pVideoState->pCodecContext = pVideoState->pFormatContext->streams[av_stream_index]->codec;
//第三点:根据解码器上下文->获取解码器ID
pVideoState->pCodec = avcodec_find_decoder(pVideoState->pCodecContext->codec_id);
if (pVideoState->pCodec == NULL)
{
qDebug()<pCodecContext,pVideoState->pCodec,NULL);
if (ret != 0)
{
return false;
}
//输出视频信息
//输出:文件格式
qDebug()<pFormatContext->iformat->name);
//输出:解码器名称
qDebug()<pCodec->name);
qDebug()<pCodecContext->width).arg(pVideoState->pCodecContext->height);
//此函数打印输入或输出的详细信息
av_dump_format(pVideoState->pFormatContext, 0, cinfilename.toStdString().c_str(), 0);
return true;
}
2、解码操作
void VideoDecoding::run()
{
qDebug() << "线程开始";
char savePath[32];
QDateTime current_date_time =QDateTime::currentDateTime();
QString current_date =current_date_time.toString("yyyyMMddhh");
sprintf(savePath,"%s.yuv",current_date.toStdString().c_str());
pVideoState->pyuvfp = fopen(savePath,"wb+");
if (pVideoState->pyuvfp == NULL)
{
qDebug()<输出YUV420P视频->格式:yuv格式
qDebug()<<"第六步:循环读取视频帧,进行循环解码";
//准备读取
//AVPacket用于存储一帧一帧的压缩数据(H264)
//缓冲区,开辟空间
pVideoState->pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));
//输入->环境一帧数据->缓冲区->类似于一张图
pVideoState->pFramein = av_frame_alloc();
//输出->帧数据->视频像素数据格式->yuv420p
pVideoState->pFrameyuv420p = av_frame_alloc();
//只有指定了AVFrame的像素格式、画面大小才能真正分配内存
//缓冲区分配内存
pVideoState->pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pVideoState->pCodecContext->width, pVideoState->pCodecContext->height));
//初始化缓冲区
avpicture_fill((AVPicture *)pVideoState->pFrameyuv420p, pVideoState->pOutbuffer, AV_PIX_FMT_YUV420P, pVideoState->pCodecContext->width, pVideoState->pCodecContext->height);
//解码的状态类型(0:表示解码完毕,非0:表示正在解码)
int y_size, u_size, v_size, current_frame_index = 0;
//用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
//准备一个视频像素数据格式上下文
//参数一:输入帧数据宽
//参数二:输入帧数据高
//参数三:输入帧数据格式
//参数四:输出帧数据宽
//参数五:输出帧数据高
//参数六:输出帧数据格式->AV_PIX_FMT_YUV420P
//参数七:视频像素数据格式转换算法类型
//参数八:字节对齐类型(C/C++里面)->提高读取效率
pVideoState->pSwsContext = sws_getContext(pVideoState->pCodecContext->width,
pVideoState->pCodecContext->height,
pVideoState->pCodecContext->pix_fmt,
pVideoState->pCodecContext->width,
pVideoState->pCodecContext->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL,NULL,NULL);
//>=0:说明有数据,继续读取
//<0:说明读取完毕,结束
while (av_read_frame(pVideoState->pFormatContext,pVideoState->pPacket) >= 0){
//解码什么类型流(视频流、音频流、字幕流等等...)
if (pVideoState->pPacket->stream_index == pVideoState->av_stream_index){
//新的API操作
//发送一帧数据->接收一帧数据
//发送一帧数据
avcodec_send_packet(pVideoState->pCodecContext, pVideoState->pPacket);
//接收一帧数据->解码一帧
ret = avcodec_receive_frame(pVideoState->pCodecContext, pVideoState->pFramein);
//解码出来的每一帧数据成功之后,将每一帧数据保存为YUV420格式文件类型(.yuv文件格式)
if ( ret == 0 ){
//sws_scale:作用将视频像素数据格式->yuv420p格式
//输出.yuv文件->视频像素数据格式文件->输出到文件API
//参数一:视频像素数据格式->上下文
//参数二:输入数据
//参数三:输入画面每一行的大小
//参数四:输入画面每一行的要转码的开始位置
//参数五:每一帧数据高
//参数六:输出画面数据
//参数七:输出画面每一行的大小
sws_scale(pVideoState->pSwsContext,
(const uint8_t *const*)pVideoState->pFramein->data,
pVideoState->pFramein->linesize,
0,
pVideoState->pCodecContext->height,
pVideoState->pFrameyuv420p->data,
pVideoState->pFrameyuv420p->linesize);
//Y代表:亮度
//UV代表:色度
//第二点:分析yuv420规则->计算机图像原理 ->直播技术
//yuv420规则一:Y结构表示一个像素点
//yuv420规则二:四个Y对应一个U和一个V(也就是四个像素点,对应一个U和一个V)
//第三点:分析Y和U、V大小计算原理
// y = 宽 * 高
// u = y / 4
// v = y / 4
y_size = pVideoState->pCodecContext->width * pVideoState->pCodecContext->height;
u_size = y_size / 4;
v_size = y_size / 4;
//第四点:写入文件
//写入->Y
//pVideoState->pFramein->data[0]:表示Y
fwrite(pVideoState->pFramein->data[0], 1, y_size, pVideoState->pyuvfp);
//写入->U
//pVideoState->pFramein->data[1]:表示U
fwrite(pVideoState->pFramein->data[1], 1, u_size, pVideoState->pyuvfp);
//写入->V
//pVideoState->pFramein->data[2]:表示V
fwrite(pVideoState->pFramein->data[2], 1, v_size, pVideoState->pyuvfp);
current_frame_index++;
qDebug()<pPacket);
//关闭流
fclose(pVideoState->pyuvfp);
av_frame_free(&pVideoState->pFramein);
av_frame_free(&pVideoState->pFrameyuv420p);
avcodec_close(pVideoState->pCodecContext);
avformat_free_context(pVideoState->pFormatContext);
qDebug() << "线程结束";
}