使用 OpenGL API 播放 BIK 視頻
阿新 • • 發佈:2017-11-16
nbsp 鑰匙 紋理格式 ould 流程 聲音 tool window vendor
BIK作為在遊戲中廣泛使用的視頻格式,這裏就非常有必要普及一下了
直接貼代碼,看註釋吧。有不懂的地方就留言提問吧
/** * * 解碼BIK視頻文件為像素數據,使用PBO更新OpenGL紋理,繪制紋理完成視頻顯示 * * PBO 即 Pixel Buffer Object 如其名 用來保存像素的緩沖區 這種緩沖區也是一種標準的 OpenGL 緩沖區 * 可以用glMapBuffer函數映射內存地址 直接訪問該緩沖區的內存 * 在例子裏 我們使用glMapBuffer函數來獲得緩沖區的內存地址 然後直接把像素數據復制到該地址中 * 比直接調用glTexSubImage2D傳遞像素數據快上幾十倍 特別是數據量大的時候 * 當我們成功把數據復制到PBO裏 我們就可以調用glTexSubImage2D (最後一個參數設為NULL) * 把像素數據從PBO復制到紋理 完成紋理的更新 此過程完全在顯卡端實現 所以速度也是非常快的 * **/ #include <windows.h> #include <stdio.h> #include <string.h> #include <gl/glut.h> #include <gl/glext.h> #include "bink.h" #pragma comment( lib, "binkw32.lib" ) #include "Timer.h" PFNGLBINDBUFFERARBPROC glBindBufferARB; PFNGLGENBUFFERSARBPROC glGenBuffersARB; PFNGLBUFFERDATAARBPROC glBufferDataARB; PFNGLMAPBUFFERARBPROC glMapBufferARB; PFNGLUNMAPBUFFERARBPROC glUnmapBufferARB; HBINK g_pBink;int IMAGE_WIDTH; // BIK視頻的寬度,例如:1280 int IMAGE_HEIGHT; // BIK視頻的高度,例如:720 int DATA_PITCH; // 一行像素數據的大小,單位為字節 // 例如:視頻寬度為->1280 沒有Alpha通道(每個像素3字節,R/G/B分別1個字節)則一行像素數據大小為-> 1280 × 3 = 3840 int DATA_SIZE; // 一幀像素數據的大小,單位為字節 // 例如:視頻寬度為->1280 視頻高度為->720 沒有Alpha通道(每個像素3字節,R/G/B分別1個字節)則一幀像素數據大小為-> 1280 × 720 × 3 = 2764800int TEXTURE_WIDTH; // 2D紋理寬度。實際上請盡量使用2的N次方的數值,保證硬件兼容性 例如2/4/8/16/32/64/128之類 int TEXTURE_HEIGHT; // 2D紋理高度 // PBO緩沖區大小,最好和紋理大小相同以免復制內存越界什麽的,當然如果你保證不會越界,那就設為BIK視頻大小 int BUFFER_SIZE; // 用來顯示視頻的紋理(什麽?你不懂?紋理不斷變化就成了視頻) GLuint textureId; // 定義兩個PBO,我們可以用兩種方式通過PBO來更新紋理 // 1. 只使用1個PBO // 2. 使用兩個2PBO GLuint pboIds[2]; /** * 輸出一些顯卡信息 **/ void GL_HardwareInfo( void ) { char *pInfo1 = (char *)glGetString( GL_VENDOR ); char *pInfo2 = (char *)glGetString( GL_RENDERER ); char *pInfo3 = (char *)glGetString( GL_VERSION ); char *pInfo4 = (char *)glGetString( GL_SHADING_LANGUAGE_VERSION ); printf( "Vendor: %s\nRenderer: %s\nVersion: %s\nGLSL: %s\n", pInfo1, pInfo2, pInfo3, pInfo4 ); } /** * 為了支持所有設備,紋理的寬和高必須為2的N次冪 * 返回值例如2/4/8/16/32/64 .. **/ int suggestTexSize( int size ) { int texSize = 1; while ( texSize < size ) { texSize <<= 1; } return texSize; } /** * 只是為了看看內存申請而已.. 不用也可以 **/ void *RADLINK BinkMemAlloc( U32 bytes ) { return malloc( bytes ); } void RADLINK BinkMemFree( void PTR4 *ptr ) { free( ptr ); } /** * 打開BIK文件並創建合適大小的紋理和PBO **/ void initBink( void ) { BinkSetMemory( BinkMemAlloc, BinkMemFree ); // // 使用WaveOut接口輸出聲音,兼容大多數Windows系統 // BinkSoundUseWaveOut(); // // 打開BIK文件,加入BINKSNDTRACK標誌表示BIK文件包含音軌 // g_pBink = BinkOpen( "Era.bik", BINKSNDTRACK ); if ( !g_pBink ) { return; } // // 這裏假設打開的BIK文件不包含ALPHA通道的,所以紋理格式選擇了GL_RGB8,R/G/B通道分別占用1字節 // // 設置視頻大小 IMAGE_WIDTH = g_pBink->Width; IMAGE_HEIGHT = g_pBink->Height; // 設置像素數據大小 DATA_PITCH = IMAGE_WIDTH * 3; DATA_SIZE = DATA_PITCH * IMAGE_HEIGHT; // 設置2D紋理大小 TEXTURE_WIDTH = suggestTexSize( IMAGE_WIDTH ); TEXTURE_HEIGHT = suggestTexSize( IMAGE_HEIGHT ); // 設置PBO大小 BUFFER_SIZE = TEXTURE_WIDTH * TEXTURE_HEIGHT * 3; // 創建2D紋理 glGenTextures( 1, &textureId ); glBindTexture( GL_TEXTURE_2D, textureId ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glBindTexture( GL_TEXTURE_2D, 0 ); // 創建2個PBO glGenBuffersARB( 2, pboIds ); glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[0] ); glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, BUFFER_SIZE, NULL, GL_STREAM_DRAW_ARB ); glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[1] ); glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, BUFFER_SIZE, NULL, GL_STREAM_DRAW_ARB ); glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); } void init( void ) { GL_HardwareInfo(); glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress( "glBindBufferARB" ); glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress( "glGenBuffersARB" ); glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress( "glBufferDataARB" ); glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress( "glMapBufferARB" ); glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress( "glUnmapBufferARB" ); if (!glBindBufferARB || !glGenBuffersARB || !glBufferDataARB || !glMapBufferARB || !glUnmapBufferARB) { printf("Your video card does not support pixel buffer object extension\n"); return; } initBink(); glEnable( GL_TEXTURE_2D ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glColor3f( 1.0f, 1.0f, 1.0f ); } /** * 使用 glTexSubImage2D 函數更新紋理 **/ void updateTextureWithOutBuffer( void ) { } /** * 只用1個PBO來更新紋理 * 流程為:復制像素數據到PBO -> 從PBO復制到紋理 -> 完成更新 **/ void updateTextureSingleBuffer( void ) { // 復制像素數據(BIK -> PBO) glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[0] ); glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, BUFFER_SIZE, NULL, GL_STREAM_DRAW_ARB ); // 如果上一次glBufferData還有未處理的數據,直接丟掉 void *ptr = glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, GL_READ_WRITE_ARB ); if ( ptr ) { // 這裏直接把像素數據復制到PBO中(即顯存)速度非常快 BinkCopyToBuffer( g_pBink, ptr, DATA_PITCH, IMAGE_HEIGHT, 0, 0, BINKSURFACE24R ); glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB ); } // 復制像素數據(PBO -> 紋理) glBindTexture( GL_TEXTURE_2D, textureId ); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, NULL ); glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); // 復制完成解綁PBO //glBindTexture( GL_TEXTURE_2D, 0 ); } /** * 使用2個PBO來更新紋理 * 流程為:復制像素數據到PBO-1 -> 從PBO-2復制到紋理 -> 完成更新 -> 下一幀-> 復制像素數據到PBO-2 -> 從PBO-1復制到紋理 -> 下一幀 -> .. * 也就是說,實際上程序跑第2幀的時候紋理才更新為第1幀時候的數據,這個有點像屏幕雙緩沖 **/ void updateTextureDoubleBuffer( void ) { static int index = 0; int nextIndex = 0; // increment current index first then get the next index index = (index + 1) % 2; nextIndex = (index + 1) % 2; glBindTexture( GL_TEXTURE_2D, textureId ); // Copy from Buffer(0) to Texture glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index] ); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, NULL ); // Copy from Bink to Buffer(1) glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex] ); glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, BUFFER_SIZE, NULL, GL_STREAM_DRAW_ARB ); // flush data void *ptr = glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, GL_READ_WRITE_ARB ); if ( ptr ) { // 這裏直接把像素數據復制到PBO中(即顯存)速度非常快 // 註意這裏多了個 BINKCOPYALL 這個標誌表示復制的時候要復制完整的一幀數據(Bink好像默認是只復制數據變化的部分) 因為交替兩個PBO如果只復制變化的部分 那數據可能就重疊了 BinkCopyToBuffer( g_pBink, ptr, DATA_PITCH, IMAGE_HEIGHT, 0, 0, BINKCOPYALL | BINKSURFACE24R ); glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB ); } glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); //glBindTexture( GL_TEXTURE_2D, 0 ); } /** * 更新BIK和紋理 **/ void updateBink( void ) { if ( !g_pBink ) { return; } // // 處理當前幀,Bink內部會處理許多東西,例如更新內部計時器,更新聲音播放緩存區等等 // BinkDoFrame( g_pBink ); // 如果Bink需要等待,例如視頻已經暫停,就直接返回吧,不要處理任何東西 if ( BinkWait( g_pBink ) ) { return; } // // 如果當前需要跳過一些幀,這個函數就會返回TRUE,我們調用BinkNextFrame來跳過直到它返回FALSE就行了 // 如果兩次更新時間間隔太長(程序太卡),為了保證畫面和聲音同步,我們就可能要跳過一些幀了 // while ( BinkShouldSkip( g_pBink ) ) { BinkNextFrame( g_pBink ); BinkDoFrame( g_pBink ); } //updateTextureWithOutBuffer(); //最慢的更新方式,除非硬件不支持PBO,否則拒絕使用 //updateTextureSingleBuffer(); updateTextureDoubleBuffer(); // 速度比單個PBO快 // 已經是最後一幀了 什麽都不幹 if ( g_pBink->FrameNum == g_pBink->Frames ) { return; } // // 當前幀已經顯示完畢 進入下一幀 // BinkNextFrame( g_pBink ); } /** * 統計一下函數1秒鐘內運行了多少次.. 粗略的FPS計算吧 **/ void printInfo( void ) { static Timer timer; static int count = 0; double elapsedTime; elapsedTime = timer.getElapsedTime(); if ( elapsedTime < 1.0 ) { count++; } else { printf( "FPS:%d\n", count ); count = 0; timer.start(); } } void display( void ) { static Timer updatelimit; glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // // 這裏可以稍微降低更新頻率以減少CPU負擔 // if ( updatelimit.getElapsedTime() > 1.0 / 30.0 ) { updateBink(); updatelimit.start(); } // // 因為紋理大小不一定和視頻大小一樣,所以做個剪裁,以免繪制出紋理多余的部分 // int X = 0; int Y = 0; float S0 = 0.0f; float T0 = 0.0f; float S1 = (float)IMAGE_WIDTH / (float)TEXTURE_WIDTH; float T1 = (float)IMAGE_HEIGHT / (float)TEXTURE_HEIGHT; // // 繪制紋理 (顯示視頻畫面) // glBindTexture( GL_TEXTURE_2D, textureId ); // 繪制一個視頻大小的矩形 glColor3f( 1, 1, 1 ); glBegin( GL_QUADS ); glTexCoord2f( S0, T0 ); glVertex2f( X, Y ); glTexCoord2f( S1, T0 ); glVertex2f( X + IMAGE_WIDTH, Y ); glTexCoord2f( S1, T1 ); glVertex2f( X + IMAGE_WIDTH, Y + IMAGE_HEIGHT ); glTexCoord2f( S0, T1 ); glVertex2f( X, Y + IMAGE_HEIGHT ); glEnd(); glFlush(); printInfo(); glutSwapBuffers(); glutPostRedisplay(); } void reshape( int width, int height ) { glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluOrtho2D( 0, width, height, 0 ); glMatrixMode( GL_MODELVIEW ); glViewport( 0, 0, width, height ); } int main( int argc, char **argv ) { glutInitWindowSize( 1300, 730 ); glutInit( &argc, argv ); glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); glutCreateWindow( "Bink OpenGL" ); init(); glutDisplayFunc( display ); glutReshapeFunc( reshape ); glutMainLoop(); if ( g_pBink ) { BinkClose( g_pBink ); g_pBink = NULL; } return 0; }
glut.h glext.h 還要問去哪裏找嗎?
這裏偷偷放一個 B**k SDK
https://pan.baidu.com/s/1nv5Pm2H 鑰匙:5sx9View Code
還有一個我測試用的視頻
鏈接: https://pan.baidu.com/s/1c23zDx2 鑰匙: eh6z
BIK的視頻工具:搜索 《Rad Video Tools》即可
使用 OpenGL API 播放 BIK 視頻