1. 程式人生 > >基於Qt、FFMpeg的音視訊播放器設計二(封裝)

基於Qt、FFMpeg的音視訊播放器設計二(封裝)

在上一篇中我們實現了視訊的解碼、格式轉換,但其基本是堆出來的程式碼,可複用性以及擴充套件性比較低,現在我們對它進行類的封裝。這裡我們把它分為四個小部分。

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繪製視訊的總結。