基於Qt、FFMpeg的音視頻播放器設計二(FFMpeg視頻處理)

在上一篇中我們介紹瞭如何在VS2013中配置文件以及FFMpeg的開發環境準備,本篇我們說下視頻處理的原理以及實現。對於視頻的處理我們這裏對它分開總結,不然看起來會顯得很冗餘複雜,不易理解,主要分爲以下幾方面。

1、打開視頻獲取視頻信息

2、讀取視頻分析視頻包

3、打開視頻解碼器

4、視頻解碼並分析H264解碼

5、打開格式轉換和縮放

6、視頻轉RGB並縮放

一、打開視頻獲取視頻信息

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

//調用FFMpeg的lib庫
#pragma comment(lib,"avformat.lib")

extern "C"
{
//調用FFMpeg的頭文件
#include <libavformat/avformat.h>

}
int main(int argc, char *argv[])
{

    //打開視頻獲取視頻信息
	av_register_all();//註冊FFMpeg的庫

	char *path = "1080.mp4";
	AVFormatContext *ic = NULL;//解封裝上下文
	int re = avformat_open_input(&ic,path,0,0);//打開解封裝器
	if (re == 0)
	{
		int totalSec = ic->duration / (AV_TIME_BASE);//獲取視頻的總時間
		printf("file totalSec is %d-%d\n",totalSec/60,totalSec%60);//以分秒計時
		avformat_close_input(&ic);//釋放解封裝的空間,以防空間被快速消耗完
	}

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

打開視頻獲取視頻時加入解封裝的頭文件#include <libavformat/avformat.h>和它的鏈接庫#pragma comment(lib,"avformat.lib"),這一整個過程就是解封裝獲取視頻信息,需要注意的是獲取信息後一定要記得釋放解封裝空間,原因如上。

二、讀取視頻分析視頻包

上一個只是用來測試的打開視頻獲取信息的過程,現在我們需要讀取解封裝後的視頻進行分析,在未讀取完之前先不着急釋放其中的解封裝的空間,代碼也做相應的調整

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

//調用FFMpeg的lib庫
#pragma comment(lib,"avformat.lib")
#pragma  comment(lib,"avutil.lib")
#pragma  comment(lib,"avcodec.lib")
extern "C"
{
	//調用FFMpeg的頭文件
#include <libavformat/avformat.h>

}
int main(int argc, char *argv[])
{
     //打開視頻獲取視頻信息

	av_register_all();//註冊FFMpeg的庫

	char *path = "1080.mp4";
	AVFormatContext *ic = NULL;//解封裝上下文
	int re = avformat_open_input(&ic, path, 0, 0);//打開解封裝器
	if (re != 0)//打開錯誤時
	{
		char buf[1024] = { 0 };
		av_strerror(re, buf, sizeof(buf));
		printf("open %s failed :%s\n", path, buf);
		getchar();
		return -1;
	}
	int totalSec = ic->duration / (AV_TIME_BASE);//獲取視頻的總時間
	printf("file totalSec is %d-%d\n", totalSec / 60, totalSec % 60);//以分秒計時


      //讀取視頻並分析視頻包

	for (;;)//讀取視頻中的所有幀
	{
		AVPacket pkt;
		re = av_read_frame(ic, &pkt);//讀取解封裝後的數據以及信息放入到pkt中
		if (re != 0) break;
		printf("pts = %d",pkt.pts);//打印它的pts,用來顯示的時間戳,後面用來做同步。
		av_packet_unref(&pkt);//重新置0
	}


	avformat_close_input(&ic);//釋放解封裝器的空間,以防空間被快速消耗完




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

此時需要處理錯誤信息和後面的解碼過程,引入鏈接庫#pragma  comment(lib,"avutil.lib")
#pragma  comment(lib,"avcodec.lib"),對於解封裝後的視頻,我們需要它的視頻中的每一幀,幀有I、B、P幀,關鍵幀在後面視頻得播放和快進時都要使用到的,而對於pts它的顯示時間做同步,可以百度瞭解下。

三、打開視頻解碼器

上面兩部分已獲取了視頻信息以及它的視頻幀等信息,現在我們需要對獲得的消息進行解碼,先進行視頻解碼,音頻的解碼在後面再說明。對於一個視頻打開後有視頻流以及音頻流stream,先處理視頻流,在處理視頻時先打開解碼器,在打開視頻獲取視頻信息的後面和讀取視頻並分析視頻包前添加打開視頻解碼器的代碼

int totalSec = ic->duration / (AV_TIME_BASE);//獲取視頻的總時間
	printf("file totalSec is %d-%d\n", totalSec / 60, totalSec % 60);//以分秒計時

int videoStream = 0;
	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)//未找到解碼器
			{
				printf("video code not find\n");
				return -1;
			}
			int err = avcodec_open2(enc, codec, NULL);//打開解碼器
			if (err!= 0)//未打開解碼器
			{
				char buf[1024] = { 0 };
				av_strerror(err, buf, sizeof(buf));
				printf(buf);
				return -2;
			}
			printf("open codec success!\n");
		}
	}



      //讀取視頻並分析視頻包

	for (;;)//讀取視頻中的所有幀

四、視頻解碼並分析H264解碼

現在我們進行視頻得解碼過程,在上面我們已經獲得了視頻的幀,對於每次獲得的視頻幀我們需要對其解碼,但解碼之後我們要將這些視頻幀放入到AVFrame,正如我們知道的此時解碼後的視頻幀是YUV格式,但最後我們顯示的是RGB格式,這種轉換我們後面介紹,現在我們對於讀取視頻並分析視頻包的處理過程加入解碼視頻過程。


 //讀取視頻並分析視頻包
AVFrame *yuv = av_frame_alloc();//分配的只是AVFrame的對象空間
	for (;;)//讀取視頻中的所有幀
	{
		AVPacket pkt;
		re = av_read_frame(ic, &pkt);//讀取解封裝後的數據以及信息放入到pkt中
		if (re != 0) break;
		if (pkt.stream_index != videoStream) continue;
		int pts = pkt.pts * r2d(ic->streams[pkt.stream_index]->time_base) *1000;
		int got_picture = 0;//從這裏
		int re = avcodec_decode_video2(videoCtx, yuv,&got_picture,&pkt);//解碼視頻過程
		if (got_picture)
		{
			printf("[%d]",re);
		}//到這裏替換解碼過程
		printf("pts = %d\n",pts);//打印它的pts,用來顯示的時間戳,後面用來做同步。
		av_packet_unref(&pkt);//重新置0
	}

上面可以實現解碼過程,但目前新版本還有一個解碼方式,將上面的//從這裏.......//到這裏替換解碼過程的這段代碼替換成如下代碼

       int re = avcodec_send_packet(videoCtx ,&pkt);//發送視頻幀
		if (re != 0)
		{
			av_packet_unref(&pkt);//不成功就釋放這個pkt
			continue;
		}
		re = avcodec_receive_frame(videoCtx, yuv);//接受後對視頻幀進行解碼
		if (re != 0)
		{
			av_packet_unref(&pkt);//不成功就釋放這個pkt
			continue;
		}
		printf("D\n");
		

五、打開格式轉換和縮放

我們知道在完成了解碼之後的值是個YUV格式,是將亮度和色彩分離的一種存儲方式,這種方式優勢是黑白畫面,只取Y是黑白畫面,UV是色彩,但我們的程序和顯示器並不接受此格式,所以我們需要將它轉化爲RGB格式,我們需要使用轉碼器,在頭文件中加入庫#include <libswscale/swscale.h>和鏈接庫#pragma  comment(lib,"swscale.lib"),首先我們初始化一個SwsContext用來轉碼,對於解碼後的每幀視頻我們進行轉碼,在轉碼之前我們先打開轉碼器,在如下位置添加代碼。

for (;;)//讀取視頻中的所有幀
	{
		AVPacket pkt;
		re = av_read_frame(ic, &pkt);//讀取解封裝後的數據以及信息放入到pkt中
		if (re != 0) break;
		if (pkt.stream_index != videoStream) continue;
		int pts = pkt.pts * r2d(ic->streams[pkt.stream_index]->time_base) *1000;
		int re = avcodec_send_packet(videoCtx ,&pkt);//發送視頻幀
		if (re != 0)
		{
			av_packet_unref(&pkt);//不成功就釋放這個pkt
			continue;
		}
		re = avcodec_receive_frame(videoCtx, yuv);//接受後對視頻幀進行解碼
		if (re != 0)
		{
			av_packet_unref(&pkt);//不成功就釋放這個pkt
			continue;
		}
		printf("D\n");
		
		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)
		{
			printf("sws_getCachedContext  failed!\n");
			break;
		}

		/*
		int got_picture = 0;
		int re = avcodec_decode_video2(videoCtx, yuv,&got_picture,&pkt);//解碼視頻過程
		if (got_picture)
		{
			printf("[%d]",re);
		}
		*/
		printf("pts = %d\n",pts);//打印它的pts,用來顯示的時間戳,後面用來做同步。
		av_packet_unref(&pkt);//重新置0
	}
	if (cCtx)
	{
		sws_freeContext(cCtx);
		cCtx = NULL;
	}





	avformat_close_input(&ic);//釋放解封裝器的空間,以防空間被快速消耗完

轉碼器在最後需要釋放其上下文空間,並重新進行初始化爲NULL。

六、視頻轉RGB並縮放

上一節打開了轉碼器,現在我們開始對視頻進行轉碼,在打開轉碼器的下方添加視頻轉碼的代碼

 if (!cCtx)
		{
			printf("sws_getCachedContext  failed!\n");
			break;
		}
	    uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
	   data[0] = (uint8_t *)rgb;//第一位輸出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);
	   }
		/*
		int got_picture = 0;
		int re = avcodec_decode_video2(videoCtx, yuv,&got_picture,&pkt);//解碼視頻過程
		if (got_picture)
		{
			printf("[%d]",re);
		}
		*/

其中的data 和linesize是我們輸出的每個通道數據指針和所轉數據的行的字節數,其中的  data[0] = (uint8_t *)rgb中的rgb是我們所要轉化爲RGB的格式,在RGB中,我們設置了爲outheight*outwidth,有outheight*outwidth個像素,像素格式在sws_getCachedContext中設置爲AV_PIX_FMT_BGRA,是32位四個字節,所有這裏的的總大小爲outheight*outwidth*4,所以這裏申請的rgb爲char *rgb = new char[outheight*outwidth*4],同理每一行的字節數linesize 爲outwidth *4,可以參考代碼部分,至此YUV轉碼爲RGB過程結束。

從上面我們可以看到代碼基本上是堆出來的,自然它的可複用性相對來說比較差,對後面的擴展也有一定限制,所以在下一篇中我們對它進行類的封裝啊!

下一篇鏈接:https://blog.csdn.net/hfuu1504011020/article/details/82661783

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章