全景视频播放器代码分析
全景視頻播放器代碼分析
- 一、前期準(zhǔn)備
- (1)FFmpeg新舊接口對(duì)照使用一覽
- (2)libswscale圖片像素?cái)?shù)據(jù)處理類庫
- (3)OpenGL相關(guān)記錄
- (4)列隊(duì)與線程
- 二、代碼分析
來總結(jié)一下最近研究的全景視頻播放器代碼
平臺(tái):Windows
軟件:vs2019
代碼來源:OpenGL全景視頻.
一、前期準(zhǔn)備
剛開始的時(shí)候想先從代碼入手,和想象的不太一樣,本來以為C語言的代碼撐死每句指令都百度,打斷點(diǎn)看變量應(yīng)該也能看懂。于是先在b站找到了C++編寫視頻播放器的視頻,看的我一頭霧水,里面用了vlc的庫,代碼可以說是一句不懂。之后在csdn,博客園搜了好多,發(fā)現(xiàn)全景視頻播放器好多都是基于安卓開發(fā)的,里面許多名詞也都不懂。
大作業(yè)+期末的日子結(jié)束,回家后美賽練習(xí)過程中才又重新開始找資料看,看過一些音視頻入門的文章后才知道音視頻開發(fā),進(jìn)行的是采集、渲染、處理、傳輸等一系列的開發(fā)和應(yīng)用,這時(shí)候才有大致框架,有了解封裝,解碼,音頻流,視頻流等相關(guān)概念。和做大作業(yè)的時(shí)候一樣,習(xí)慣了直接上手,有什么問題再說,但其實(shí)首先做的應(yīng)該是弄清楚大的框架和要求,再著眼細(xì)節(jié)。
再之后就是知道了FFmpeg和OpenGL,拋下代碼先學(xué)了相關(guān)的理論。
FFmpeg參考了雷神的博客和在b站的視頻講解
把庫和函數(shù)列出來了:FFmpeg原理介紹與代碼實(shí)現(xiàn).
后來發(fā)現(xiàn)了一個(gè)寫的比較系統(tǒng)的教程:ffmpeg和SDL教程.
OpenGL有一個(gè)很系統(tǒng)的講解:LearnOpenGL CN.
萬字長文詳解如何用 Python 玩轉(zhuǎn) OpenGL | CSDN 博文精選.坐標(biāo)系、投影、變換的概念都有
另外有一些找到的寫的不錯(cuò)的文章:
最簡單的視音頻播放示例5:OpenGL播放RGB/YUV.有OpenGL渲染管線的步驟,窗口相關(guān)函數(shù)都有提到。
OpenGL正面剔除,深度測試,混合.有實(shí)際圖片示例
還有《OpenGL編程指南》好像也多人推薦
但我的感覺是代碼里好多東西這書和網(wǎng)站都沒有講到,像glut庫里函數(shù)都是分散的百度出來的,很雜很散的樣子。
除了這些,零零碎碎搜到的一些東西也大致記錄了一下。
(1)FFmpeg新舊接口對(duì)照使用一覽
FFmpeg新舊接口對(duì)照使用一覽.
(2)libswscale圖片像素?cái)?shù)據(jù)處理類庫
sws_getContext():初始化一個(gè)SwsContext。
sws_scale():處理圖像數(shù)據(jù)。
sws_freeContext():釋放一個(gè)SwsContext。
關(guān)于avpicture_fill與sws_scale:
關(guān)于avpicture_fill與sws_scale.對(duì)應(yīng)于FFmpeg解碼之后像素格式變換的代碼
FFmpeg解碼H264及swscale縮放詳解.函數(shù)解釋很細(xì)
(3)OpenGL相關(guān)記錄
總結(jié)到了另一篇:全景視頻播放器中OpenGL的相關(guān)記錄.
(4)列隊(duì)與線程
總結(jié)到了:列隊(duì)與線程(全景視頻播放器).
二、代碼分析
主要的思路是將全景視頻利用FFmpeg解封裝解碼后,將視頻幀利用OpenGL渲染顯示在一個(gè)球上。
值得注意的點(diǎn):
(1)FFmpeg解碼后還進(jìn)行了像素幀的格式轉(zhuǎn)換,好像OpenGL只能渲染RGB,yuv要轉(zhuǎn)成rgb?
(2)線程及列隊(duì)的使用
程序中使用兩個(gè)線程分別實(shí)現(xiàn)解碼和渲染,它們之間相互獨(dú)立。但解碼和渲染的速度我們無法控制,就通過列隊(duì)實(shí)現(xiàn)均衡。我們自己設(shè)定列隊(duì)的size,手動(dòng)將解碼出的數(shù)據(jù)存到列隊(duì)中。解碼出的視頻幀保存在列隊(duì)中,即向列隊(duì)中輸入使size增加,而OpenGL的渲染消耗列隊(duì)中的元素,使size減小。當(dāng)解碼的數(shù)據(jù)將列隊(duì)容量占滿時(shí),解碼線程會(huì)稍作等待,等渲染的線程繼續(xù)執(zhí)行使列隊(duì)中有剩余位置時(shí),解碼線程才會(huì)繼續(xù)運(yùn)行。
另外,臨界區(qū)就是一段不會(huì)被中斷的代碼,可以避免數(shù)據(jù)沖突而使程序崩潰的情況。在本程序中,列隊(duì)中元素增加和減少是都會(huì)在臨界區(qū)進(jìn)行操作。OpenGL用解碼出的圖像生成紋理后就已經(jīng)消耗掉了列隊(duì)中的元素,就可以離開臨界區(qū)了,之后計(jì)算各點(diǎn)坐標(biāo)將紋理對(duì)應(yīng)成像素點(diǎn)并進(jìn)行繪制。
(3)OpenGL繪制球體
通過設(shè)置經(jīng)線和緯線的數(shù)量,我們可將一個(gè)球分成數(shù)個(gè)長方形,OpenGL基本的繪制單元是三角形,一個(gè)矩形分兩個(gè)三角形,即6個(gè)頂點(diǎn)。每個(gè)頂點(diǎn)有xyz三維的空間坐標(biāo)和(s, t)二維紋理坐標(biāo)??臻g坐標(biāo)就是數(shù)學(xué)上的坐標(biāo)表示,而紋理坐標(biāo)是根據(jù)
[0,1]分份數(shù)算出來的。
(4)關(guān)于glut中的回調(diào)函數(shù),以對(duì)該事件或條件進(jìn)行響應(yīng)
(5)兩個(gè)線程打斷點(diǎn)調(diào)試不能反映真實(shí)的程序運(yùn)行情況,斷點(diǎn)打下這個(gè)線程不動(dòng)了另一個(gè)跑,但實(shí)際是兩個(gè)線程都在跑。(或者確實(shí)可以真實(shí)反映程序的調(diào)試俺不知道)
具體代碼及注釋如下:
// glPanorama.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。 //#include "stdafx.h"#define PI 3.1415926GLfloat xangle = 0.0; //X 旋轉(zhuǎn)量,之后可通過鼠標(biāo)或鍵盤的控制改變 GLfloat yangle = 0.0; //Y 旋轉(zhuǎn)量 GLfloat zangle = 0.0; //Z 旋轉(zhuǎn)量//交叉點(diǎn)的坐標(biāo) int cx = 0; int cy = 0;GLfloat distance =0;//0或1100.0; GLuint texturesArr;int cap_H = 1;//必須大于0,且cap_H應(yīng)等于cap_W int cap_W = 1;//繪制球體時(shí),每次增加的角度float* verticals; float* UV_TEX_VERTEX;void init(void); void reshape(int w, int h); void display(void); void getPointMatrix(GLfloat radius);#define MAXSIZE 10//列隊(duì)的最大容量//定義了一個(gè)結(jié)構(gòu)體Frame用于保存一幀視頻畫面、音頻 typedef struct Vid_Frame {AVFrame *frame;//視頻或音頻的解碼數(shù)據(jù)int serial;double pts; /* presentation timestamp for the frame */double duration; /* estimated duration of the frame */int64_t pos; /* byte position of the frame in the input file */uint8_t *buffer;int width;int height;AVRational sar; } Vid_Frame;//FrameQueue不是用鏈表實(shí)現(xiàn)隊(duì)列,而是用數(shù)組實(shí)現(xiàn)隊(duì)列(環(huán)形緩沖區(qū))。 typedef struct FrameQueue{Vid_Frame queue[MAXSIZE];隊(duì)列元素,用數(shù)組模擬隊(duì)列,其中就有AVFrame的解碼數(shù)據(jù)int front;int rear;//后int size;//當(dāng)前存儲(chǔ)的節(jié)點(diǎn)個(gè)數(shù)(或者說,當(dāng)前已寫入的節(jié)點(diǎn)個(gè)數(shù))CRITICAL_SECTION cs;//critica_section定義一個(gè)臨界區(qū)對(duì)象cs,它是全局變量 }FrameQueue;FrameQueue frame_queue; //frame_queue是一個(gè)循環(huán)隊(duì)列,解碼的時(shí)候入隊(duì),渲染的時(shí)候出隊(duì)void initQueue(FrameQueue *q)//初始化列隊(duì) {int i;for (i = 0; i<MAXSIZE; i++){if (!(q->queue[i].frame = av_frame_alloc()))//為數(shù)組queue中的每個(gè)元素的frame(AVFrame*)的字段分配內(nèi)存return ;q->queue[i].buffer = NULL;}q->front = 0;q->rear = 0;q->size = 0;InitializeCriticalSection(&q->cs);//初始化臨界區(qū),創(chuàng)立了一個(gè)叫cs的臨界區(qū)對(duì)象 }void deQueue(FrameQueue *q) {free(q); }void init(void) {initQueue(&frame_queue);//初始化列隊(duì)//創(chuàng)建紋理,輸入生成紋理的數(shù)量1,然后把它們儲(chǔ)存在第二個(gè)參數(shù)的unsigned int數(shù)組中glGenTextures(1, &texturesArr); glBindTexture(GL_TEXTURE_2D, texturesArr);//綁定它,讓之后任何的紋理指令都可以配置當(dāng)前綁定的紋理//IplImage *image = cvLoadImage("5.png", 1);//生成一個(gè)紋理//glTexImage2D(GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image->imageData);//我們需要自己告訴OpenGL在紋理中采取哪種采樣方式//紋理被放大和縮小時(shí)都使用了線性過濾glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //線形濾波glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //線形濾波glClearColor(0.0, 0.0, 0.0, 0.0);//設(shè)置當(dāng)前使用的清除顏色值,這里為黑色,參數(shù)為RGBaglClearDepth(1);// 清除深度緩存 1.0是最大深度([0.0,1.0])glShadeModel(GL_SMOOTH);//設(shè)定opengl中繪制指定兩點(diǎn)間其他點(diǎn)顏色的過渡模式,啟用柵格化glEnable(GL_TEXTURE_2D);//允許采用 2D 紋理技術(shù)glEnable(GL_DEPTH_TEST);//啟用深度測試,決定何時(shí)覆蓋一個(gè)像素glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//表示顏色和紋理坐標(biāo)插補(bǔ)的質(zhì)量。 如果角度更正參數(shù)插值不有效地支持由 OpenGL 實(shí)現(xiàn),提示 GL_DONT_CARE 或 GL_FASTEST 可以導(dǎo)致簡單線性插值的顏色和/或紋理坐標(biāo)。getPointMatrix(500);//用函數(shù)得到頂點(diǎn)坐標(biāo)和紋理坐標(biāo) }void getPointMatrix(GLfloat radius) {//開辟空間用來儲(chǔ)存頂點(diǎn)坐標(biāo)和紋理坐標(biāo),頂點(diǎn)坐標(biāo)每個(gè)頂點(diǎn)有3個(gè)維度,紋理坐標(biāo)2個(gè)(一個(gè)矩形中按6個(gè)頂點(diǎn))verticals = new float[(180 / cap_H) * (360 / cap_W) * 6 * 3];UV_TEX_VERTEX = new float[(180 / cap_H) * (360 / cap_W) * 6 * 2];float x = 0;float y = 0;float z = 0;int index = 0;int index1 = 0;float r = radius;//球體半徑double d = cap_H * PI / 180;//每次遞增的弧度for (int i = 0; i < 180; i += cap_H) {double d1 = i * PI / 180;for (int j = 0; j < 360; j += cap_W) {double d2 = j * PI / 180;//獲得球體上切分的超小片矩形的頂點(diǎn)坐標(biāo)(兩個(gè)三角形組成,所以有六點(diǎn)頂點(diǎn)) verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2 + d));verticals[index++] = (float)(y + r * cos(d1 + d));verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2 + d));//獲得球體上切分的超小片三角形的紋理坐標(biāo),紋理坐標(biāo)范圍是(0,1)UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / 360;UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / 180;verticals[index++] = (float)(x + r * sin(d1) * cos(d2));verticals[index++] = (float)(y + r * cos(d1));verticals[index++] = (float)(z + r * sin(d1) * sin(d2));UV_TEX_VERTEX[index1++] = j * 1.0f / 360;UV_TEX_VERTEX[index1++] = i * 1.0f / 180;verticals[index++] = (float)(x + r * sin(d1) * cos(d2 + d));verticals[index++] = (float)(y + r * cos(d1));verticals[index++] = (float)(z + r * sin(d1) * sin(d2 + d));UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / 360;UV_TEX_VERTEX[index1++] = i * 1.0f / 180;verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2 + d));verticals[index++] = (float)(y + r * cos(d1 + d));verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2 + d));UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / 360;UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / 180;verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2));verticals[index++] = (float)(y + r * cos(d1 + d));verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2));UV_TEX_VERTEX[index1++] = j * 1.0f / 360;UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / 180;verticals[index++] = (float)(x + r * sin(d1) * cos(d2));verticals[index++] = (float)(y + r * cos(d1));verticals[index++] = (float)(z + r * sin(d1) * sin(d2));UV_TEX_VERTEX[index1++] = j * 1.0f / 360;UV_TEX_VERTEX[index1++] = i * 1.0f / 180;}} }void reshape(int w, int h) {glViewport(0, 0, (GLsizei)w, (GLsizei)h);glMatrixMode(GL_PROJECTION);//接下來要做投影相關(guān)的操作glLoadIdentity();//在進(jìn)行變換前把當(dāng)前矩陣設(shè)置為單位矩陣//glOrtho(-250.0, 250, -250.0, 250, -500, 500);//glFrustum(-250.0, 250, -250.0, 250, -5, -500);gluPerspective(60, (GLfloat)w / h, 1.0f, 1000.0f); //設(shè)置投影矩陣glMatrixMode(GL_MODELVIEW);//對(duì)模型視景的操作glLoadIdentity(); }//渲染時(shí)把解出來的數(shù)據(jù)從隊(duì)列中取出生成新的紋理。 //渲染采用glDrawArrays函數(shù),使用的GL_TRIANGLES參數(shù),使用這個(gè)參數(shù) //對(duì)于計(jì)算球的頂點(diǎn)坐標(biāo)和紋理坐標(biāo)來說不需要考慮很多,比較方便,就是點(diǎn)數(shù)過多的時(shí)候可能會(huì)影響渲染的效率。 void display(void) {glLoadIdentity();//恢復(fù)初始坐標(biāo)系 注釋掉后會(huì)一直閃glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除兩個(gè)緩沖區(qū),clear all pixels像素gluLookAt(0, 0, distance, 0, 0, 500, 0, 1, 0);//在球里面的時(shí)候,500處的值越小,離得越近//前三個(gè)是腦袋的位置,中間三個(gè)是眼睛看向的位置,后三個(gè)是頭頂朝向的位置printf("distance: %f \n", distance);//glRotatef(Angle, Xvector, Yvector, Zvector) 用于繞軸旋轉(zhuǎn)物體。 //Angle 是一個(gè)用于指定旋轉(zhuǎn)角度的數(shù)字(通常存儲(chǔ)于變量中)。 //Xvector, Yvector 和 Zvector 這三個(gè)參數(shù)用于描述一條向量, 以規(guī)定物體的旋轉(zhuǎn)軸。//在鼠標(biāo)或鍵盤操控時(shí),xangle會(huì)隨之變化所以才會(huì)旋轉(zhuǎn)glRotatef(xangle, 1.0f, 0.0f, 0.0f); //繞X軸旋轉(zhuǎn)glRotatef(yangle, 0.0f, 1.0f, 0.0f); //繞Y軸旋轉(zhuǎn)glRotatef(zangle, 0.0f, 0.0f, 1.0f); //繞Z軸旋轉(zhuǎn)EnterCriticalSection(&frame_queue.cs);printf("display size = %d \n", frame_queue.size);if (frame_queue.size > 0){Vid_Frame *vp = &frame_queue.queue[frame_queue.front];//vp指向隊(duì)首//glGenTextures(1, &texturesArr);glBindTexture(GL_TEXTURE_2D, texturesArr);//glTexImage2D第七第八個(gè)參數(shù)定義了源圖的格式和數(shù)據(jù)類型,最后一個(gè)參數(shù)是真正的圖像數(shù)據(jù)//當(dāng)前綁定的紋理對(duì)象就會(huì)被附加上紋理圖像,生成紋理glTexImage2D(GL_TEXTURE_2D, 0, 3, vp->width, vp->height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, vp->buffer);frame_queue.size--;frame_queue.front = (frame_queue.front + 1) % MAXSIZE;}LeaveCriticalSection(&frame_queue.cs);//glColor3f(1.0, 0.0, 0.0); //繪制物體所使用的顏色// 啟用頂點(diǎn)數(shù)組glEnableClientState(GL_VERTEX_ARRAY);//啟用紋理數(shù)組 頂點(diǎn)坐標(biāo)+紋理坐標(biāo)glEnableClientState(GL_TEXTURE_COORD_ARRAY);/*glVertexPointer指定頂點(diǎn)數(shù)組的位置,3表示每個(gè)頂點(diǎn)由三個(gè)量構(gòu)成(x, y,z),GL_FLOAT表示每個(gè)量都是一個(gè)GLfloat類型的值。第三個(gè)參數(shù)0表示緊密排列。最后一個(gè)指明了數(shù)組實(shí)際的位置。*/glVertexPointer(3, GL_FLOAT, 0, verticals);glTexCoordPointer(2, GL_FLOAT, 0, UV_TEX_VERTEX);//繪制,第二個(gè)參數(shù)是從數(shù)組緩存中的哪一位開始繪制,一般為0。第三個(gè)參數(shù)為數(shù)組中頂點(diǎn)的數(shù)量。glDrawArrays(GL_TRIANGLES, 0, (180 / cap_H) * (360 / cap_W) * 6);glDisableClientState(GL_TEXTURE_COORD_ARRAY);glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arraysglFlush();//保證繪圖命令將實(shí)際進(jìn)行,而不是存儲(chǔ)在緩沖區(qū)等待其他命令}DWORD WINAPI ThreadFunc(LPVOID n) {AVFormatContext* pFormatCtx;int i, videoindex;AVCodec* pCodec;//解碼器AVCodecContext* pCodecCtx = NULL;char filepath[] = "cuc_ieschool.mp4";av_register_all();//注冊(cè)組件avformat_network_init();//支持網(wǎng)絡(luò)流pFormatCtx = avformat_alloc_context();//創(chuàng)建AVFormatContext結(jié)構(gòu)體//該函數(shù)讀取文件頭并將有關(guān)文件格式的信息存儲(chǔ)在我們提供的AVFormatContext結(jié)構(gòu)中。//最后兩個(gè)參數(shù)用于指定文件格式,緩沖區(qū)大小和格式選項(xiàng),但是通過將其設(shè)置為NULL或0,libavformat將自動(dòng)檢測它們。if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0){//打開一個(gè)輸入流printf("Couldn't open input stream.(無法打開輸入流)\n");return -1;}if (avformat_find_stream_info(pFormatCtx, NULL) < 0)//獲取流信息{printf("Couldn't find stream information.(無法獲取流信息)\n");return -1;}videoindex = -1;for (i = 0; i < pFormatCtx->nb_streams; i++){//找到流隊(duì)列中,視頻流所在位置if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){videoindex = i;break;}}if (videoindex == -1){printf("Didn't find a video stream.(沒有找到視頻流)\n");return -1;}//查找解碼器pCodecCtx = pFormatCtx->streams[videoindex]->codec;pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if (pCodec == NULL){printf("Codec not found.(沒有找到解碼器)\n");return -1;}if (avcodec_open2(pCodecCtx, pCodec, NULL)<0){printf("Could not open codec.(無法打開解碼器)\n");return -1;}AVFrame *pFrame;pFrame = av_frame_alloc();//分配視頻幀,存儲(chǔ)從packet中解碼出來的原始視頻幀int ret, got_picture;AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));AVFrame *pFrameBGR = NULL;pFrameBGR = av_frame_alloc();struct SwsContext *img_convert_ctx;int index = 0;while (av_read_frame(pFormatCtx, packet) >= 0)//return 0 if OK, < 0 on error or end of file{if (packet->stream_index == videoindex)//判斷是不是來自視頻流的數(shù)據(jù)包,不是會(huì)直接跳出{//解碼,將數(shù)據(jù)包轉(zhuǎn)換為幀。輸入為packet,輸出為original_video_frame//其中的pFrame存儲(chǔ)解碼視頻的AVFrame。ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if (ret < 0){printf("Decode Error.(解碼錯(cuò)誤)\n");continue;}if (got_picture){index++;flag_wait:if (frame_queue.size >= MAXSIZE)//如果解碼過快列隊(duì)存儲(chǔ)不下,則此線程暫緩,讓主線程渲染后再繼續(xù)運(yùn)行{printf("size = %d I'm WAITING ... \n", frame_queue.size);Sleep(100);goto flag_wait;}EnterCriticalSection(&frame_queue.cs);//防止數(shù)據(jù)錯(cuò)亂Vid_Frame *vp;vp = &frame_queue.queue[frame_queue.rear];//vp指向列隊(duì)的尾部,自動(dòng)就知道rear?//vp->frame->pts = pFrame->pts;/* alloc or resize hardware picture buffer *///令vp的buffer的size width height都等于pFrame,就是給存儲(chǔ)數(shù)據(jù)的buffer賦值if (vp->buffer == NULL || vp->width != pFrame->width || vp->height != pFrame->height){if (vp->buffer != NULL){av_free(vp->buffer);vp->buffer = NULL;}//int iSize = avpicture_get_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height);int iSize = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height, 1);av_free(vp->buffer);vp->buffer = (uint8_t *)av_mallocz(iSize);vp->width = pFrame->width;vp->height = pFrame->height;}av_image_fill_arrays(vp->frame->data, vp->frame->linesize, vp->buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);//frame和buffer都是已經(jīng)申請(qǐng)到的一段內(nèi)存, 會(huì)將frame的數(shù)據(jù)按BGR24的格式自動(dòng)"關(guān)聯(lián)"到buffer。//avpicture_fill((AVPicture *)vp->frame, vp->buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);if (vp->buffer){//用sws_getContext初始化SwsContex/*srcW:源圖像的寬srcH:源圖像的高srcFormat:源圖像的像素格式dstW:目標(biāo)圖像的寬dstH:目標(biāo)圖像的高dstFormat:目標(biāo)圖像的像素格式flags:設(shè)定圖像拉伸使用的算法*/img_convert_ctx = sws_getContext(vp->width, vp->height, (AVPixelFormat)pFrame->format, vp->width, vp->height,AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); //AV_PIX_FMT_YUV420P, AV_PIX_FMT_BGR24//轉(zhuǎn)換一幀圖像,轉(zhuǎn)換完成的數(shù)據(jù)保存到了vp,也自動(dòng)到了buffer里面。sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, vp->height, vp->frame->data, vp->frame->linesize);//釋放SwsContext結(jié)構(gòu)體sws_freeContext(img_convert_ctx);//vp->pts = pFrame->pts;}frame_queue.size++;frame_queue.rear = (frame_queue.rear + 1) % MAXSIZE;LeaveCriticalSection(&frame_queue.cs);}}av_free_packet(packet);}avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0; }void reDraw(int millisec) {glutTimerFunc(millisec, reDraw, millisec);glutPostRedisplay(); }void keyboard(unsigned char key, int x, int y) {switch (key){case 'x': //當(dāng)按下鍵盤上x時(shí),以沿X軸旋轉(zhuǎn)為主xangle += 1.0f; //設(shè)置旋轉(zhuǎn)增量break;case 'X':xangle -= 1.0f; //設(shè)置旋轉(zhuǎn)增量break;case 'y':yangle += 1.0f;break;case 'Y':yangle -= 1.0f;break;case 'z':zangle += 1.0f;break;case 'Z':zangle -= 1.0f;break;case 'd':distance += 10.0f;break;case 'D':distance -= 10.0f;break;default:return;}glutPostRedisplay(); //重繪函數(shù) }//處理鼠標(biāo)點(diǎn)擊 void Mouse(int button, int state, int x, int y) {if (state == GLUT_DOWN) //第一次鼠標(biāo)按下時(shí),記錄鼠標(biāo)在窗口中的初始坐標(biāo){//記住鼠標(biāo)點(diǎn)擊后光標(biāo)坐標(biāo)cx = x;cy = y;} }//處理鼠標(biāo)拖動(dòng) void onMouseMove(int x, int y) {float offset =0.3;// 0.18;值越大,到相同位置轉(zhuǎn)的角度就越大,即需要拖得越長才能到相應(yīng)的位置//計(jì)算拖動(dòng)后的偏移量,然后進(jìn)行xy疊加減yangle -= ((x - cx) * offset);if ( y > cy) {//往下拉xangle += ((y - cy) * offset);}else if ( y < cy) {//往上拉xangle += ((y - cy) * offset);}glutPostRedisplay();//保存好當(dāng)前拖放后光標(biāo)坐標(biāo)點(diǎn)cx = x;cy = y; }int main(int argc, char* argv[]){printf("可通過按鍵或者鼠標(biāo)控制視頻旋轉(zhuǎn)\n");glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);//創(chuàng)建窗口的模式:單緩沖區(qū)和RGB模式、使用深度緩存glutInitWindowSize(1280, 720);//窗口大小glutInitWindowPosition(50, 50);//窗口左上角屏幕位置glutCreateWindow("OpenGL全景");init();glutReshapeFunc(reshape); //窗口大小改變或窗口位置改變時(shí)候要調(diào)用的函數(shù)glutDisplayFunc(display); //指定當(dāng)窗口內(nèi)容需要重繪時(shí)要調(diào)用的函數(shù),在窗口剛打開,彈出,改變位置,點(diǎn)擊等,都會(huì)觸發(fā)事件。glutKeyboardFunc(keyboard);//當(dāng)一個(gè)能生成ASCII字符的鍵按下時(shí),keyboard函數(shù)會(huì)被調(diào)用glutMouseFunc(Mouse);//當(dāng)一個(gè)鼠標(biāo)按鈕按下或釋放,調(diào)用Mouse函數(shù)glutMotionFunc(onMouseMove);//當(dāng)鼠標(biāo)按下并在窗口移動(dòng)鼠標(biāo)時(shí),調(diào)用onMouseMove函數(shù)glutTimerFunc(25, reDraw,1);//原最后一個(gè)參數(shù)為25HANDLE hThrd = NULL;DWORD threadId;hThrd = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId);//創(chuàng)建一個(gè)新線程//該函數(shù)才真正進(jìn)入GLUT事件循環(huán),語句阻塞在此。//當(dāng)對(duì)應(yīng)的事件發(fā)生時(shí),被注冊(cè)的回調(diào)函數(shù)如glutDisplayFunc中注冊(cè)的就會(huì)被調(diào)用。glutMainLoop();//無限執(zhí)行的循環(huán),判斷窗口是否需要重繪//WaitForSingleObject(hThrd, INFINITE);if (hThrd){CloseHandle(hThrd);//線程句柄生命周期結(jié)束}return 0; }總結(jié)
以上是生活随笔為你收集整理的全景视频播放器代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于Arduino开发板的光学指纹识别模
- 下一篇: Apple屏下TouchID申请专利 短