当前位置:   article > 正文

Unity 渲染 Android播放的视频画面_unity 绘制texture 到android显示

unity 绘制texture 到android显示

码字不易,转载请注明出处喔
https://blog.csdn.net/newchenxf/article/details/119565585


1. 前言

Unity本身可以直接播放本地视频或网络视频,网上有很多例子,例如:
https://blog.csdn.net/cs874300/article/details/89294433
这可以完全在Unity封闭完成。

但是,有一种情况,是播放逻辑希望在Android的代码内完成(比如是直播的流,用的是android 的直播SDK),希望Unity只渲染Android播放的画面就可以。这种情况怎么做呢?本文一一说来。

首先,完成上面的工作,需要依赖FBO,所以先介绍FBO。

1.1 FBO简介

FBO,全名Frame Buffer Object,目前主要用于离屏渲染技术

在OpenGL渲染管线中几何数据和纹理经过变换和一些测试处理,最终会被展示到屏幕上。OpenGL渲染管线的最终位置是在帧缓冲区中。默认情况下OpenGL使用的是窗口系统提供的帧缓冲区

但总有场景是不想要直接渲染到窗口上的,于是OpenGL提供了一种方式来创建额外的帧缓冲区对象(FBO)。使用帧缓冲区对象,OpenGL可以将原先绘制到窗口提供的帧缓冲区重定向到FBO之中。

FBO本身不是一块内存,没有空间,真正存储东西,可实际读写的是依附于FBO的东西:纹理(texture)和渲染缓存(renderbuffer)
依附的方式,是一个二维数组(或者说是一个映射表)来管理。
在这里插入图片描述
简单的说,FBO为了管理这2个东东,于是用一些标签来表示Texture Object或Renderbuffer Object,例如颜色缓冲区(GL_COLOR_ATTACHMENT0)、深度缓冲区(GL_DEPTH_ATTACHMENT)、模板缓冲区以及累积缓冲区。(这些都是int值)

GL_COLOR_ATTCHMENT0为例子,一个绑定代码如下:

//1. 绑定FrameBuffer到当前的绘制环境上, 后续GL绘制都会到这个framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
//2. Framebuffer绑定纹理
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, unityTextureId, 0);
  • 1
  • 2
  • 3
  • 4

也就是,把一个纹理id叫unityTextureId纹理,绑定到FBO的GL_COLOR_ATTACHMENT0标签上。

有了FBO,程序员就可以重定向渲染目标到其他的存储空间,比如将渲染目标重定向到纹理空间,实现渲染到纹理功能(Render to Texture),这也是本文的目标!

上面的绑定代码,就是你可以自己创建一个纹理,把ID给FBO绑定,后面渲染的结果,就到你要的纹理上了!完美,真香

2. 正式开发

分两部分,一个是Unity部分,一个是Android部分。

2.1 Unity的工作

先Canvas下建立一个RawImage对象,然后写个脚本,脚本绑定到Canvas下。
脚本定义一个变量为

public RawImage rawImage;
  • 1

Inspector面板中,把创建的RawImage对象赋值给这个变量。

接下来,就是脚本的工作了:

   public RawImage rawImage;
    public GameObject playButton;
    private AndroidJavaObject nativeObject;
    private int width, height;
    private Texture2D texture2D;

    // Use this for initialization
    void Start()
    {    //nativeObject假装是VideoPlugin对象
        nativeObject = new AndroidJavaObject("com.pvr.videoplugin.VideoPlugin");
        width = 1600;
        height = 900;
        Debug.Log("VideoPlugin:" + width + ", " + height);
    }

    // Update is called once per frame
    void Update()
    {
        if (texture2D != null && nativeObject.Call<bool>("isUpdateFrame"))
        {
            Debug.Log("VideoPlugin:Update");
            nativeObject.Call("updateTexture");
            GL.InvalidateState();
        }
    }

    public void Click()
    {
        Debug.Log("VideoPlugin:Start");
        if (playButton != null) {
            playButton.SetActive(false);
        }
        if (texture2D == null)
        {    //关键:创建纹理
            texture2D = new Texture2D(width, height, TextureFormat.RGB24, false, false);

            Debug.Log("VideoPlugin:create texture2D");
            //关键:把纹理id传递给android 的VideoPlugin的start函数
            nativeObject.Call("start", (int)texture2D.GetNativeTexturePtr(), width, height);
            //关键:同时设置RawImage的纹理为刚建的texture2D
            rawImage.texture = texture2D;
            Debug.Log("finish set texture");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

Click函数,由随便加的一个按扭来触发执行。
Click函数,先自己建立一个纹理,把纹理Id给到Android层。Android则把这块纹理作为FBO的问题。Android播放的视频,最终都渲染到这块纹理上。

接着,也把这个纹理赋值给Raw Image(作为输入),所以,Android的视频输出,最终会在Raw Image显示出来。

于是流程打通。

2.2 Android 插件的工作

首先是VideoPlugin的实现:

public class VideoPlugin implements OnFrameAvailableListener {

    private SurfaceTexture mSurfaceTexture;
    private FilterFBOTexture mFilterFBOTexture;
    private MediaPlayer mMediaPlayer;
    private boolean mIsUpdateFrame;

    /**
     * Unity调用,把Unity创建的textureId传递过来,绑定到FBO
     */
    public void start(int unityTextureId, int width, int height) {
        FBOUtils.log("start, unityTextureId " +unityTextureId +" width " +width +" height " +height);
        //生成视频播放输出的纹理id
        int videoTextureId = FBOUtils.createVideoTextureID();
        //根据创建的纹理id生成一个SurfaceTexture, 视频播放输出到surface texture
        mSurfaceTexture = new SurfaceTexture(videoTextureId);
        mSurfaceTexture.setDefaultBufferSize(width, height);
        mSurfaceTexture.setOnFrameAvailableListener(this);

        //创建FBO相关资源
        mFilterFBOTexture = new FilterFBOTexture(width, height, unityTextureId, videoTextureId);

        initMediaPlayer();
    }

    private void initMediaPlayer() {
        FBOUtils.log("initMediaPlayer");
        mMediaPlayer = new MediaPlayer();
        //设置mediaplayer的输出为自定义surface
        mMediaPlayer.setSurface(new Surface(mSurfaceTexture));
        try {
            final File file = new File("/sdcard/test.mp4");
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setLooping(true);
            mMediaPlayer.setDataSource(Uri.fromFile(file).toString());
            mMediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                FBOUtils.log("MediaPlayer onPrepared");
                mMediaPlayer.start();
            }
        });
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        FBOUtils.log("onFrameAvailable");
        //视频播放开始有输出
        mIsUpdateFrame = true;
    }

    /**
     * Unity调用,更新视频纹理,然后绘制FBO
     */
    public void updateTexture() {
        FBOUtils.log("updateTexture");
        mIsUpdateFrame = false;
        mSurfaceTexture.updateTexImage();
        mFilterFBOTexture.draw();
    }

    public boolean isUpdateFrame() {
        return mIsUpdateFrame;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

关键函数start,把Unity创建的textureId传递过来,绑定到FBO。
然后,MediaPlayer的视频输出,不是SurfaceView了,而是自己建立的SurfaceTextute。

接下来是FilterFBOTexture的实现:

public class FilterFBOTexture {

    private final String vertexShaderCode =
            "attribute vec4 av_Position; \n" +
                    "attribute vec2 af_Position; \n" +
                    "varying vec2 v_texPo; \n" +
                    "void main() { \n" +
                    "   gl_Position = av_Position; \n" +
                    "   v_texPo = af_Position; \n" +
                    "}";

    private final String fragmentShaderCode =
            "#extension GL_OES_EGL_image_external : require \n" +
                    "precision mediump float; \n" +
                    "varying vec2 v_texPo;\n" +
                    "uniform samplerExternalOES s_Texture;\n" +
                    "void main() { \n" +
                    "   gl_FragColor = texture2D(s_Texture, v_texPo);\n" +
                    "}";

    static float[] vertexData = {
            -1f, 1f,
            1f, 1f,
            -1f, -1f,
            1f, -1f,
    };

    static float[] textureData = {
            0f, 0f,
            1f, 0f,
            0f, 1f,
            1f, 1f,
    };

    private final FloatBuffer vertexBuffer;
    private final FloatBuffer textureBuffer;

    private final int av_Position;
    private final int af_Position;
    private final int s_Texture;

    private final int program;

    private final int fboId;
    private final int width;
    private final int height;
    private final int videoTextureId;
    private final int unityTextureId;

    public FilterFBOTexture(int width, int height, int unityTextureId, int videoTextureId) {
        this.width = width;
        this.height = height;
        this.unityTextureId = unityTextureId;
        this.videoTextureId = videoTextureId;

        fboId = FBOUtils.createFBO();

        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);
        vertexBuffer.position(0);

        textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(textureData);
        textureBuffer.position(0);

        program = FBOUtils.buildProgram(vertexShaderCode, fragmentShaderCode);
        av_Position = GLES20.glGetAttribLocation(program, "av_Position");
        af_Position = GLES20.glGetAttribLocation(program, "af_Position");
        s_Texture = GLES20.glGetUniformLocation(program, "s_Texture");
    }

    public void draw() {
        FBOUtils.log("draw");
        //视口
        GLES20.glViewport(0, 0, width, height);

        //清除颜色缓冲
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //绑定FrameBuffer到当前的绘制环境上, 后续GL绘制都会到这个framebuffer
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
        //把一个纹理附加到帧缓冲
        //之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中
        //即所有渲染操作的结果将会被储存在unityTextureId对应的纹理图像中
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, unityTextureId, 0);
        //检查帧缓冲是否完整
        if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
            FBOUtils.log("FrameBuffer error");
            return;
        }

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);

        //激活着色器程序
        GLES20.glUseProgram(program);

        //告诉OpenGL该如何解析顶点数据(顶点坐标),并启用顶点属性
        GLES20.glEnableVertexAttribArray(av_Position);
        GLES20.glVertexAttribPointer(av_Position, 2, GLES20.GL_FLOAT, false, 2 * 4, vertexBuffer);

        //告诉OpenGL该如何解析顶点数据(纹理坐标),并启用顶点属性
        GLES20.glEnableVertexAttribArray(af_Position);
        GLES20.glVertexAttribPointer(af_Position, 2, GLES20.GL_FLOAT, false, 2 * 4, textureBuffer);

        //激活纹理单元
        GLES20.glActiveTexture(GLES20.GL_TEXTURE7);
        //绑定指定纹理到当前激活的纹理单元
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, videoTextureId);
        //为着色器中定义的采样器指定属于哪个纹理单元;不要忘记在设置uniform变量之前激活着色器程序
        GLES20.glUniform1i(s_Texture, 7);

        //绘制三角带
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        GLES20.glDisableVertexAttribArray(av_Position);
        GLES20.glDisableVertexAttribArray(af_Position);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        //激活默认帧缓冲
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126

2.3 整理流程

在这里插入图片描述
即,android播放视频的输出,到一块纹理上,这个纹理是FBO的输入。可以对这个纹理进行各种操作。
FBO的输出,又指向了Unity创建的另一个纹理。所以,上面的各种操作,直接体现在Unity创建的纹理。
而这个纹理,是RawImage的输入!

RawImage的输入有更新了,它就显示东西出来了!

2.4 其他说明

Android插件分为openGL ES 2.0和openGL ES 3.0。
在这里插入图片描述
2个模块都可以输出jar。

不管你选哪个,都需要Unity在打包时,自己定一下选用哪个版本。

即Unity -> File -> Build Settings -> Player Settings
在这里插入图片描述

3. 附录

完整的Unity项目

https://github.com/hywenbinger/EasyMovie-Unity

完整的Android插件代码

https://github.com/hywenbinger/EasyMovie-Android

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/119173
推荐阅读
相关标签
  

闽ICP备14008679号