第四章:ffmpeg和QT开发播放器之代码初封装

写在前面:

    编写完视频的编码转码程序之后,就需要将整个程序重新封装一下,以便于后续的工作,这里对应视频课程中的3-7~3-10。由于之前没怎么接触过C++,所以蛮多东西都不是很懂,所以笔记只能原原本本的记录下来了,以后要是懂点的话,在进行修正吧。

1、创建类

       为了让代码更简洁,这时候就需重构FFmpeg类,这里我们创建一个虚析构函数。

       为什么要创建虚虚构函数?

       在继承之后,如果没有设置虚析构函数,对父类指针删除的时候,不会自动调用释放内存的函数。

                        第四章:ffmpeg和QT开发播放器之代码初封装_第1张图片

       然后将之前main.cpp文件里面的函数逐步移动到新创建的这个类里面,我们可以将extern “C”{}这个C库函数放在FFmpeg.h文件里面,但是不能将库(lib)文件放到FFmepg中。而应该放在FFmpeg.cpp中,因为.h文件会被多次调用。

2、XFFmpeg对象

       我们做的只是简单的视频播放器,所以暂时使用单件模式,只有一个对象,所以没必要让用户去维护这个对象的生命周期。

       另外我们将构造函数放在保护类型protected里面,这是为了不让外部生成对象,保证内部的唯一性。

       在XFFmpeg类里面添加open函数,但是open过程中有可能会失败,所以还应该添加错误打印信息,那么就直接在protected保护类型里面创建一个errorbuf存错误信息,然后再定义一个获取错误信息的函数。

       用string GetError();为什么要用string,主要考虑到错误信息有可能被多个线程应用,为了线程安全,再复制一份。

       由于后期还要用到多线程处理,才能加快解码速度,那么多线程访问就需要用到互斥变量,所以我们直接添加QT自带的互斥变量#include ,然后再保护类型protected里面追加QMutex mutex。

       然后再添加close函数。

       另外我们将构造函数放在保护类型protected里面,这是为了不让外部生成对象,保证内部的唯一性。

       在XFFmpeg类里面添加open函数,但是open过程中有可能会失败,所以还应该添加错误打印信息,那么就直接在protected保护类型里面创建一个errorbuf存错误信息,然后再定义一个获取错误信息的函数。

       用string GetError();为什么要用string,主要考虑到错误信息有可能被多个线程应用,为了线程安全,再复制一份。

       由于后期还要用到多线程处理,才能加快解码速度,那么多线程访问就需要用到互斥变量,所以我们直接添加QT自带的互斥变量#include ,然后再保护类型protected里面追加QMutex mutex。

       然后再添加close函数。

3、Open、Close、GetError函数的实现和使用

       open函数的内容和之前未封装的内容一致,但是要注意的是,由于接下来可能用到多线程,使用open和close的时候要注意上锁,open之前也要注意先close一下,以防止之前被人打开过。

       close函数则做对已申请的空间进行释放的工作。

       geterror函数,用来打印错误消息的,函数内部定义一个std::string re = this->errorbuf;使用这种方法返回出去的re会被多创建一份,那样返回出去的是一份复制的空间,就不会造成多线程的异常。

       调用open函数:XFFmpeg::Get()->Open("my.mp4")

       调用geterror的方法XFFmpeg::Get()->GetError().c_str(),通过Get函数获取XFFmpeg类的地址,然后再指向GetError函数,由于GetError函数返回的是string类型的,是不能直接打印的,所以要转成char*,通过.c_str()函数来实现。

4、Read函数的实现

       read函数是用于读取视频里面的帧数据的,然后返回读取读取的视频的pkt数据包,那么这里就需要考虑到,读取完数据包之后,要在哪释放掉这个数据包所占用的空间,这里我们选择在外部释放掉。

       为什么不在函数内部实现每次释放掉上一帧的空间呢,由于要考虑到之后多线程处理,有可能在读取下一帧数据的时候,上一帧数据还未被解码。

5、Decode函数的实现

     Decode函数是用于将读取到的帧,也就是将pkt解码成yuv。要讲读取到的帧转换成yuv格式的话,那么我们在调用avcodec_send(receive)_packet函数前,要记得先打开这个解码器,所以我们需要将之前的打开解码器函数放在open函数里面。

       解码完就需要将yuv格式转换成rgb格式,那么就需要创建转码的函数。

以下是代码:

XXFmpeg.h

#pragma once
extern "C"{
#include 
#include 
}
#include 
#include 
class XFFmpeg
{
public:
	static XFFmpeg *Get()
	{
		static XFFmpeg ff;
		return &ff;
	}
	
	///打开视频文件,如果上次已经打开就会先关闭
	///@path path 视频文件路径
	///@return bool 成功失败 失败信息通过GetError获取
	
	bool Open(const char *path);
	void Close();
	///返回值需要用户清理
	AVPacket Read();

	AVFrame *Decode(const AVPacket *pkt);

	bool ToRGB(const AVFrame *yuv,char *out,int outwidth, int outheight);
	std::string GetError();
	virtual ~XFFmpeg();
	
	int totalMs = 0;	//时长
	int videoStream = 0;
protected:
	
	char errorbuf[1024];
	AVFormatContext *ic = NULL;		//存放视频(流)的文件信息
	AVFrame *yuv = NULL;
	SwsContext *cCtx = NULL;		//创建转码器的容器
	QMutex mutex;
	XFFmpeg();
};

XFFmpeg.cpp

#include "XFFmpeg.h"
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swscale.lib")
bool XFFmpeg::Open(const char *path)
{
	int re = 0;
	int err = 0;

	Close();
	mutex.lock();
	re = avformat_open_input(&ic, path, 0, 0);		//打开
	if (re != 0)
	{
		mutex.unlock();
		av_strerror(re, errorbuf, sizeof(errorbuf));
		printf("open failed %s\n", path, errorbuf);		
		return false;
	}
	totalMs = (ic->duration / AV_TIME_BASE)*1000;

	for (int i = 0; i < ic->nb_streams; i++)
	{
		AVCodecContext *enc = ic->streams[i]->codec;
				
		if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//表示为一个视频
		{
			videoStream = i;
			
			AVCodec *codec = avcodec_find_decoder(enc->codec_id); //判断系统有没有这个解码器

			if (!codec)
			{
				mutex.unlock();
				printf("video code not find!!\n");
				return false;
			}
			err = avcodec_open2(enc, codec,NULL);	//找到系统中这个解码器的话,就打开这个解码器。
			if (err != 0)
			{
				mutex.unlock();
				char buf[1024] = { 0 };
				av_strerror(re, errorbuf, sizeof(errorbuf));
				printf("buf\n", path, errorbuf);
				return false;
			}
			printf("open avcodec_open2 success!!!\n");
		}
	}

	mutex.unlock();
	return true;
}

AVPacket XFFmpeg::Read()
{
	AVPacket pkt;
	int err = 0;
	memset(&pkt,0,sizeof(AVPacket));
	mutex.lock();

	if (!ic)	//判断视频文件被打开没,没被打开的话,直接退出。
	{
		mutex.unlock();
		return pkt;	//这时候的pkt也是空的
	}

	err = av_read_frame(ic, &pkt);
	if (err != 0)
	{
		mutex.unlock();
		av_strerror(err, errorbuf, sizeof(errorbuf));
	}
	mutex.unlock();

	return pkt;
}

void XFFmpeg::Close()
{
	mutex.lock();
	if (ic)	avformat_close_input(&ic);
	if (yuv) av_frame_free(&yuv);
	if (cCtx)
	{
		sws_freeContext(cCtx);
		cCtx = NULL;
	}

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

AVFrame *XFFmpeg::Decode(const AVPacket *pkt)
{
	int re = 0;
	mutex.lock();
	if (!ic)	//判断视频文件被打开没,没被打开的话,直接退出。
	{
		mutex.unlock();
		return NULL;	//这时候的pkt也是空的
	}

	if (yuv == NULL)
	{
		yuv = av_frame_alloc();
	}

	re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec,pkt);
	if (re != 0)
	{
		mutex.unlock();
		//av_packet_unref(&pkt);
		return NULL;
	}
	re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec,yuv);
	if (re != 0)
	{
		mutex.unlock();
		//av_packet_unref(&pkt);
		return NULL;
	}

	mutex.unlock();
	return yuv;
}
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;//在ic中的streams数组中找到这个codec,
	//打开转码器
	cCtx = sws_getCachedContext(cCtx, videoCtx->width,
		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;
	int linesize[AV_NUM_DATA_POINTERS] = { 0 };
	linesize[0] = outwidth * 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;
}

XFFmpeg::XFFmpeg()
{
	errorbuf[0] = '\0';
	av_register_all();		//注册所有的编解码器和文件格式。
}

XFFmpeg::~XFFmpeg()
{
}

main.cpp

#include "xplay.h"
#include 
#include "XFFmpeg.h"

#define OUT_WIDTH	800
#define OUT_HEIGHT	600

int main(int argc, char *argv[])
{
	char *rgb = new char[OUT_WIDTH*OUT_HEIGHT * 4];	//图像的数据RGBA
	
	if (XFFmpeg::Get()->Open("my.mp4"))
	{
		printf("open success!\n");
	}
	else
	{
		printf("open failed!%s", XFFmpeg::Get()->GetError().c_str());
		getchar();
		return -1;
	}
	
	while (1)
	{
		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);	//如果不是视频格式的 就释放掉
			continue;
		}

		AVFrame *yuv = XFFmpeg::Get()->Decode(&pkt);	//解码视频
		if (yuv)
		{
			printf("[D]");
			XFFmpeg::Get()->ToRGB(yuv, rgb, OUT_WIDTH, OUT_HEIGHT);
		}

		av_packet_unref(&pkt);
	}
	QApplication a(argc, argv);
	Xplay w;
	w.show();
	return a.exec();
}

你可能感兴趣的:(第四章:ffmpeg和QT开发播放器之代码初封装)