QT + FFMPEG实现基本播放器(二):FFMPEG解码功能实现
对于视频的解码模块,使用FFMPEG进行实现。
将在上一节中实现的OpenFileHanle槽函数中,增加以下的代码,将提取到的文件名称,传给FFmpegThread,FFmpegThread使用单例模式管理FFMPEG
int ret = FFmpegThread::Get()->OpenFile((const char *)strFileName.toLocal8Bit());
if(ret < 0)
{
QMessageBox::information(this, "err", "file open failed!");
}
else
{
}
FFmpegThread::Get()->OpenFile 将会调用FFmpegThread::Get()->Init进行初始化工作:
int FFmpegThread::Init(const char* pcFileName)
{
//打开媒体文件
int result = avformat_open_input(&g_MedieInfo.avFormatContext, pcFileName, NULL, NULL);
if (result < 0)
{
qDebug() << "open input error" << pcFileName;
return -1;
}
/*
获取流信息,主要用于给每个媒体流(音频/视频)的AVStream结构体赋值
发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。
下面看一下除了成员变量赋值之外,该函数的几个关键流程。
1.查找解码器:find_decoder()
2.打开解码器:avcodec_open2()
3.读取完整的一帧压缩编码的数据:read_frame_internal()
注:av_read_frame()内部实际上就是调用的read_frame_internal()。
4.解码一些压缩编码数据:try_decode_frame()
*/
result = avformat_find_stream_info(g_MedieInfo.avFormatContext, NULL);
if (result < 0)
{
qDebug()<< "find stream info error";
return -1;
}
/*----------视频流部分开始----------*/
//获取音视频对应的stream_index
AVCodec *videoDecoder;
g_MedieInfo.videoStreamIndex = av_find_best_stream(g_MedieInfo.avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &videoDecoder, 0);
if (g_MedieInfo.videoStreamIndex < 0)
{
qDebug() << "find video stream index error";
return -1;
}
//获取视频流
AVStream *videoStream = g_MedieInfo.avFormatContext->streams[g_MedieInfo.videoStreamIndex];
//获取视频流解码器,或者指定解码器
AVCodecContext * videoCodec = videoStream->codec;
videoDecoder = avcodec_find_decoder(videoCodec->codec_id);
if (videoDecoder == NULL)
{
qDebug() << "video decoder not found";
return -1;
}
//设置加速解码
videoCodec->lowres = videoDecoder->max_lowres;
videoCodec->flags2 |= AV_CODEC_FLAG2_FAST;
//打开视频解码器
result = avcodec_open2(videoCodec, videoDecoder, NULL);
if (result < 0)
{
qDebug() << "open video codec error";
return -1;
}
//获取分辨率大小
int videoWidth = videoStream->codec->width;
int videoHeight = videoStream->codec->height;
VideoThread::Get()->SetVideoInfo(videoWidth,videoHeight);
//获取帧率大小
g_MedieInfo.m_FrameRate = videoStream->avg_frame_rate.num/videoStream->avg_frame_rate.den;
qDebug() <<"rate "<<g_MedieInfo.m_FrameRate;
//如果没有获取到宽高则返回
if (videoWidth == 0 || videoHeight == 0)
{
qDebug()<< "find width height error";
return -1;
}
m_totalMs = g_MedieInfo.avFormatContext->duration / 1000000 * 1000; //视频的时间,结果是多少豪秒
/*----------视频流部分结束----------*/
/*----------音频流部分开始----------*/
//循环查找音频流索引,与上面的av_find_best_stream效果一致
g_MedieInfo.audioStreamIndex = -1;
for (uint i = 0; i < g_MedieInfo.avFormatContext->nb_streams; i++)
{
if (g_MedieInfo.avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
g_MedieInfo.audioStreamIndex = i;
break;
}
}
//有些没有音频流,所以这里不用返回
if (g_MedieInfo.audioStreamIndex == -1)
{
qDebug()<< "find audio stream index error";
}
else
{
//获取音频流
AVStream *audioStream = g_MedieInfo.avFormatContext->streams[g_MedieInfo.audioStreamIndex];
AVCodecContext * audioCodec = audioStream->codec;
//获取音频流解码器,或者指定解码器
AVCodec * audioDecoder = avcodec_find_decoder(audioCodec->codec_id);
if (audioDecoder == NULL)
{
qDebug() << "audio codec not found";
return -1;
}
//打开音频解码器
result = avcodec_open2(audioCodec, audioDecoder, NULL);
if (result < 0)
{
qDebug()<< "open audio codec error";
return -1;
}
AudioThread::Get()->SetAudioInfo(audioCodec->sample_rate,16,audioCodec->channels);
}
/*----------音频流部分结束----------*/
//比较上一次文件的宽度高度,当改变时,需要重新分配内存
if (oldWidth != videoWidth || oldHeight != videoHeight)
{
if(g_MedieInfo.buffer != NULL)
{
av_free(g_MedieInfo.buffer);
g_MedieInfo.buffer = NULL;
}
int byte = avpicture_get_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight);
g_MedieInfo.buffer = (uint8_t *)av_malloc(byte * sizeof(uint8_t));
oldWidth = videoWidth;
oldHeight = videoHeight;
}
//定义像素格式
AVPixelFormat srcFormat = videoCodec->pix_fmt;
AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;
//初始化VideoThread
VideoThread::Get()->InitvidioFrameConvert(dstFormat);
//图像转换
g_MedieInfo.swsContext = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth, videoHeight, dstFormat, SWS_BICUBIC, NULL, NULL, NULL);
//输出视频信息
// av_dump_format(g_MedieInfo.avFormatContext, 0, pcFileName, 0);
init_clock(&g_MedieInfo.m_PalyClock);
qDebug() << "init ffmpeg finsh";
return 0;
}
对于在Init中的 g_MedieInfo是用来保存媒体信息的:
MEDIE_INFO_S g_MedieInfo;
typedef struct MedieInfo
{
AVFormatContext *avFormatContext;//格式对象
uint8_t *buffer; //存储解码后图片buffer
SwsContext *swsContext; //处理图片数据对象
int videoStreamIndex; //视频流索引
int audioStreamIndex; //音频流索引
int m_FrameRate;
AvPacketQueue<AVPacket> m_VideoPacketQueue;
AvPacketQueue<AVPacket> m_AudioPacketQueue;
PALY_CLOCK_S m_PalyClock; //同步时钟
}MEDIE_INFO_S;
视频和音频的解码处理在FFmpegThread中进行,而视频包的处理则在VideoThread,音频包的处理在AudioThread中进行,三个线程分别进行处理,增加处理效率。
在FFmpegThread的run函数当中,读取数据保存到相应的队列当中:
void FFmpegThread::run()
{
qDebug()<<__func__<<__LINE__;
while (!m_IsQuit)
{
//还未打开程序
if (!m_IsPaly)
{
continue;
}
AVPacket avPacket;
if (av_read_frame(g_MedieInfo.avFormatContext, &avPacket) >= 0)
{
//判断当前包是视频还是音频
int packetSize = avPacket.size;
int index = avPacket.stream_index;
if (index == g_MedieInfo.videoStreamIndex)
{
g_MedieInfo.m_VideoPacketQueue.push(avPacket);
}
else if (index == g_MedieInfo.audioStreamIndex)
{
g_MedieInfo.m_AudioPacketQueue.push(avPacket);
}
}
}
//线程结束后释放资源
FreeSrc();
m_IsPaly = false;
m_IsQuit = false;
qDebug() << "Stop Ffmpeg Thread";
}
以上则为使用FFmpeg进行解码的过程,av_read_frame得到的为原始的数据包,需要在音视频线程中进一步处理,才能使用,音视频线程的处理在后面的章节中讲解。