基于Qt、FFMpeg的音视频播放器设计二(FFMpeg视频处理之类封装)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hfuu1504011020/article/details/82661783

在上一篇中我们实现了视频的解码、格式转换,但其基本是堆出来的代码,可复用性以及扩展性比较低,现在我们对它进行类的封装。这里我们把它分为四个小部分。

1、重构封装FFMpeg类完成打开和关闭视频接口

2、重构读取视频帧接口

3、重构解码接口

4、重构ToRGB接口

一、重构封装FFMpeg类完成打开和关闭视频接口

我们使用VS的类向导在该项目下添加XFFMpeg类,将上一篇中编辑好的视频打开和关闭的部分代码移植过来,同时进行一些调整,需要注意的时考虑到后面的多线程应用,在打开关闭以及处理时我们都加入了互斥锁,如何处理看下方的代码,注释也都比较清晰。

XFFMpeg.h文件

#pragma once
#include <iostream>
#include <QMutex>
extern "C"
{
	//调用FFMpeg的头文件
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

class XFFmpeg
{
public:
	static XFFmpeg *Get()//单件模式
	{
		static XFFmpeg ff;
		return &ff;
	}
////////////////////////////////////////
///打开视频文件,如果上次已经打开会先关闭
///@para path  视频文件路径
///@return bool 成功失败,失败错误信息通过 GetError获取
	bool Open(const char *path);//打开视频文件
	void Close();//关闭文件
	std::string GetError();//获取错误信息
	virtual ~XFFmpeg();
	int totalMs=0;//总时长
protected:
	char errorbuff[1024];//打开时发生的错误信息
	XFFmpeg();
	QMutex mutex;//互斥变量,多线程时避免同时间的读写
	AVFormatContext *ic = NULL;//解封装上下文
	
};

XFFMpeg.cpp

#include "XFFmpeg.h"

//调用FFMpeg的lib库
#pragma comment(lib,"avformat.lib")
#pragma  comment(lib,"avutil.lib")
#pragma  comment(lib,"avcodec.lib")
#pragma  comment(lib,"swscale.lib")

XFFmpeg::XFFmpeg()
{
	errorbuff[0] = '\0';//初始化
	av_register_all();//注册FFMpeg的库
}


XFFmpeg::~XFFmpeg()
{
}

bool XFFmpeg::Open(const char *path)
{
	Close();//打开前先关闭清理
	mutex.lock();//锁
	int re = avformat_open_input(&ic, path, 0, 0);//打开解封装器
	if (re != 0)//打开错误时
	{
		mutex.unlock();//解锁
		av_strerror(re, errorbuff, sizeof(errorbuff));//错误信息
		printf("open %s failed :%s\n", path, errorbuff);
		return false;
	}
    totalMs = ic->duration / (AV_TIME_BASE);//获取视频的总时间
	printf("file totalSec is %d-%d\n", totalMs/ 60, totalMs % 60);//以分秒计时
	mutex.unlock();
	return true;


}

void XFFmpeg::Close()
{
	mutex.lock();//需要上锁,以防多线程中你这里在close,另一个线程中在读取,
	if (ic) avformat_close_input(&ic);//关闭ic上下文
	mutex.unlock();

}

std::string XFFmpeg::GetError()
{
	mutex.lock();
	std::string re = this->errorbuff;
	mutex.unlock();
	return re;
}

在主函数中这样调用,用来测试是否正确打开文件

#include "aginexplay.h"
#include "XFFmpeg.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
	if (XFFmpeg::Get()->Open("1080.mp4"))//是否打开视频
	{
		printf("open success!\n");
	}
	else
	{
		printf("open failed!%s\n",XFFmpeg::Get()->GetError().c_str());
		getchar();
		return -1;
	}

	QApplication a(argc, argv);
	agineXplay w;
	w.show();
	return a.exec();

二、重构读取视频帧接口

封装读取视频帧,我们在XFFMpeg.h中申明

//读取视频的每一帧,返回每帧后需要清理空间
	AVPacket Read();

在XFFMpeg中定义

AVPacket XFFmpeg::Read()
{
	AVPacket pkt;
	memset(&pkt,0,sizeof(AVPacket));
	mutex.lock();
	if (!ic)
	{
		mutex.unlock();
		return pkt;
	}
	int err = av_read_frame(ic, &pkt);//读取视频帧
	if (err != 0)//读取失败
	{
		av_strerror(err,errorbuff,sizeof(errorbuff));
	}
	mutex.unlock();
	return pkt;

}

同样考虑到多线程中,避免在一个线程中刚读取到一帧视频,而在另一个线程中就将其清理里,所以在读取这一部分也需要加入互斥锁,读取完后解锁。

三、重构解码接口

在XFFMpeg.h中申明解码函数以及变量

//读取到每帧数据后需要对其进行解码
	AVFrame *Decode(const AVPacket *pkt);

    AVFrame *yuv = NULL;//解码后的视频帧数据
	int videoStream = 0;//视频流

在XFFMpeg.cpp中定义解码函数

AVFrame * XFFmpeg::Decode(const AVPacket *pkt)
{
	mutex.lock();
	if (!ic)//若未打开视频
	{
		mutex.unlock();
		return NULL;

	}
	if (yuv == NULL)//申请解码的对象空间
	{
		yuv = av_frame_alloc();
	}
	int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec,pkt);//发送之前读取的视频帧pkt
	if (re != 0)
	{
		mutex.unlock();
		return NULL;
	}
	re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec,yuv);//解码pkt后存入yuv中
	if (re != 0)
	{
		mutex.unlock();
		return NULL;
	}


	mutex.unlock();

	return yuv;
}

同时在解码时我们需要解码器所以在XFFMpeg.cpp的Open函数中需要打开解码器,代码如下位置

//解码器
	for (int i = 0; i < ic->nb_streams; i++)
	{
		AVCodecContext *enc = ic->streams[i]->codec;//解码上下文

		if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//判断是否为视频
		{
			videoStream = i;
			//videoCtx = enc;
			AVCodec *codec = avcodec_find_decoder(enc->codec_id);//查找解码器
			if (!codec)//未找到解码器
			{
				mutex.unlock();
				printf("video code not find\n");
				return false;
			}
			int err = avcodec_open2(enc, codec, NULL);//打开解码器
			if (err != 0)//未打开解码器
			{
				mutex.unlock();
				char buf[1024] = { 0 };
				av_strerror(err, buf, sizeof(buf));
				printf(buf);
				return false;
			}
			printf("open codec success!\n");
		}
	}//至此为打开解码器过程
	
	printf("file totalSec is %d-%d\n", totalMs/ 60, totalMs % 60);//以分秒计时
	mutex.unlock();
	return true;

在主函数main中进行测试解码视频帧

for (;;)
	{
		AVPacket pkt = XFFmpeg::Get()->Read();//每次读取视频得一帧
		if (pkt.size == 0)
			break;
		printf("pts = %lld\n", pkt.pts);

		AVFrame *yuv = XFFmpeg::Get()->Decode(&pkt);//解码视频帧
		if (yuv)
		{
			printf("[D]\n");
		}

		av_packet_unref(&pkt);//重新置pkt为0
	}

同时别忘记在XFFMpeg.cpp的Close时同时还需要释放解码后的空间

if (yuv) av_frame_free(&yuv);//关闭时释放解码后的视频帧空间

四、重构ToRGB接口

在XFFMpeg.h中申明转码函数以及变量

//将解码后的YUV视频帧转化为RGB格式
	bool ToRGB(const AVFrame *yuv,char *out,int outwidth,int outheight);

SwsContext  *cCtx = NULL;//转码器上下文

在XFFMpeg.cpp中定义转码函数

bool XFFmpeg::ToRGB(const AVFrame *yuv, char *out, int outwidth, int outheight)
{

	mutex.lock();
	if (!ic)//未打开视频文件
	{
		mutex.unlock();
		return false;
	}
	AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;
	cCtx = sws_getCachedContext(cCtx, videoCtx->width,//初始化一个SwsContext
		videoCtx->height,
		videoCtx->pix_fmt, //输入像素格式
		outwidth, outheight,
		AV_PIX_FMT_BGRA,//输出像素格式
		SWS_BICUBIC,//转码的算法
		NULL, NULL, NULL);

	if (!cCtx)
	{
		mutex.unlock();
		printf("sws_getCachedContext  failed!\n");
		return false;
	}
	uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
	data[0] = (uint8_t *)out;//第一位输出RGB
	int linesize[AV_NUM_DATA_POINTERS] = { 0 };

	linesize[0] = outwidth * 4;//一行的宽度,32位4个字节
	int h = sws_scale(cCtx, yuv->data, //当前处理区域的每个通道数据指针
		yuv->linesize,//每个通道行字节数
		0, videoCtx->height,//原视频帧的高度
		data,//输出的每个通道数据指针	
		linesize//每个通道行字节数

		);//开始转码

	if (h > 0)
	{
		printf("(%d)", h);
	}
	mutex.unlock();
	return true;

}

在主函数main中进行测试转码

for (;;)
	{
		AVPacket pkt = XFFmpeg::Get()->Read();//每次读取视频得一帧
		if (pkt.size == 0)
			break;
		printf("pts = %lld\n", pkt.pts);

		if (pkt.stream_index != XFFmpeg::Get()->videoStream)//若不为视频流
		{
			av_packet_unref(&pkt);//重新置pkt为0
			continue;

		}
        AVFrame *yuv = XFFmpeg::Get()->Decode(&pkt);//解码视频帧
		if (yuv)
		{
			printf("[D]\n");
			XFFmpeg::Get()->ToRGB(yuv, rgb, 800, 600);//视频转码
		}
		
		av_packet_unref(&pkt);//重新置pkt为0
	}

考虑到一个视频有音频和视频,这里对于音频我们先直接过滤掉,只处理视频,同时在XFFMpeg.cpp的Close中对于转码上下文的空间也需要释放。

if (cCtx)
	{
		sws_freeContext(cCtx);//释放转码器上下文空间
		cCtx = NULL;
	}

至此FFMPEG视频处理原理以及实现基本完成了,下一篇对于QT界面设计和使用opengl绘制视频的总结。

下一篇链接:https://blog.csdn.net/hfuu1504011020/article/details/82686325

猜你喜欢

转载自blog.csdn.net/hfuu1504011020/article/details/82661783