zoukankan      html  css  js  c++  java
  • 使用 OpenGL API 播放 BIK 视频

    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 = 2764800
    
    
    int 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
    Renderer: %s
    Version: %s
    GLSL: %s
    ", 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
    ");
            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
    ", 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
    钥匙:5sx9
    View Code

    还有一个我测试用的视频

    链接: https://pan.baidu.com/s/1c23zDx2 钥匙: eh6z

    BIK的视频工具:搜索 《Rad Video Tools》即可

  • 相关阅读:
    ORACLE数据库找回用户密码
    PO、POJO、BO、DTO、VO之间的区别(转)
    Http报头Accept与Content-Type的区别
    java.lang.IllegalStateException: getWriter() has already been called for this response
    利用策略模式实现了同一接口的多个Servicel实现类,如何同时注入Controller
    java.util.Stack类简介
    java为什么要重写hashCode和equals方法?
    PowerDesigner15连接Oracle数据库并导出Oracle的表结构
    解决ODBC连接Oracle数据库报Unable to connect SQLState=08004问题
    IIS 返回 405
  • 原文地址:https://www.cnblogs.com/crsky/p/7846124.html
Copyright © 2011-2022 走看看