QT + FFMPEG实现基本播放器(三):视频播放实现
在 《QT + FFMPEG实现基本播放器(二):FFMPEG解码功能实现》中,将所有avPacket放入到了全局的一个结构体的队列当中 g_MedieInfo.m_VideoPacketQueue.push(avPacket);
因此可以实现视频的播放线程,从队列当中取出数据,从而可以将avPacket解码成原始的数据,从而将其转为QImage,发送到VideoWidget中进行显示:
VideoThread继承QThread,因此主要实现run函数
void VideoThread::run()
{
qDebug()<<"Start Video Thread";
while(m_IsRuning)
{
if( g_MedieInfo.m_VideoPacketQueue.empty())
{
msleep(1);
continue;
}
AVPacket avPacket;
g_MedieInfo.m_VideoPacketQueue.wait_and_pop(avPacket);
//按解码顺序发送packet,将视频文件中的packet序列依次发送给解码器。发送packet的顺序如IPBBPBB
int ret = avcodec_send_packet(g_MedieInfo.avFormatContext->streams[g_MedieInfo.videoStreamIndex]->codec, &avPacket);
if (ret != 0)
{
qDebug()<<__func__<<__LINE__<<"send packet error";
continue;
}
//按显示顺序输出frame,frame输出顺序是按pts递增的顺序。pts是解码时间戳。pts与dts不一致的问题
//由解码器进行了处理,用户程序不必关心。从解码器接收frame的顺序如IBBPBBP
//解码器中会缓存一定数量的帧,一个新的解码动作启动后,向解码器送入好几个packet解码器才会输出第一个packet
//因为解码时帧之间有信赖关系,例如IPB三个帧被送入解码器后,B帧解码需要依赖I帧和P帧
//所以在B帧输出前,I帧和P帧必须存在于解码器中而不能删除
ret = avcodec_receive_frame(g_MedieInfo.avFormatContext->streams[g_MedieInfo.videoStreamIndex]->codec,vidioFrameRaw);
if (ret < 0)
{
if (ret == AVERROR(EAGAIN))
{
continue;
}
if (ret == AVERROR_EOF)
{
qDebug()<<__func__<<__LINE__<<"receive frame error";
}
}
//解码后的视频帧数据保存在vidioFrameRaw变量中,然后经过swscale函数转换后,将视频帧数据保存在vidioFrameConvert变量中
sws_scale(g_MedieInfo.swsContext, (const uint8_t *const *)vidioFrameRaw->data, vidioFrameRaw->linesize, 0, videoHeight, vidioFrameConvert->data, vidioFrameConvert->linesize);
//根据之前设置的输出图片格式,将原始数据转成一张图片
QImage image(vidioFrameConvert->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
if (!image.isNull())
{
//将该QImage数据通过receiveImage信号发出
emit ReceiveImage(image);
}
//根据帧率进行延时
usleep(1000*1000/g_MedieInfo.m_FrameRate);
av_packet_unref(&avPacket);
av_freep(&avPacket);
}
qDebug()<<"Stop Video Thread";
}
其中需要注意的是,对于视频的播放,需要根据视频的帧率进行适当的延时,比如30帧的视频的含义为1秒钟播放30帧的画面,因此对于每一帧的播放都需要延时1000*1000/30 = 33333.333 us ,如果不进行延时的话,那么视频将会很快的播放完毕。根据这个原理,我们可以对延时时间进行处理,以达到倍速播放的目的。
使用emit ReceiveImage(image);发出的ReceiveImage信号,将会在VideoWidget中接收,然后转到对应的槽函数UpdataImage
void VideoWidget::UpdataImage(const QImage &image)
{
m_Image = image;
this->update();
}
通过调用update函数将会对页面进行刷新,从而将该QImage显示到VideoWidget上,从而实现视频播放的功能