TRTCSDK自定义采集Texture2D视频通话

2021-08-06 10:19:54 浏览数 (2)

一、适用场景

腾讯TRTCSDK,提供了摄像头通话、录屏通话、基础美颜、高级美颜功能。

摄像头通话功能,是TRTCSDK对系统摄像头进行了封装,采集摄像头数据,编码传输通话。

如果您自研(或者购买第三方)美颜和特效处理模块,则需要自己采集和处理摄像头拍摄画面,对采集到的YUV数据、纹理数据进行操作处理,将处理后的数据,交给TRTCSDK编码传输通话。TRTCSDK是有提供自定义采集功能接口的。

二、API介绍:

enableCustomVideoCapture

sendCustomVideoData

如官网api文档介绍:

enableCustomVideoCapture( boolean enable )启用视频自定义采集模式

开启该模式后,SDK 不在运行原有的视频采集流程,只保留编码和发送能力。 您需要用 sendCustomVideoData() 不断地向 SDK 塞入自己采集的视频画面。

sendCustomVideoData( TRTCCloudDef.TRTCVideoFrame frame )向 SDK 投送自己采集的视频数据

Android 平台有两种的方案:

  • buffer 方案:对接起来比较简单,但是性能较差,不适合分辨率较高的场景。
  • texture 方案:对接需要一定的 OpenGL 基础,但是性能较好,640 × 360 以上的分辨率请采用该方案。

TRTCVideoFrame参数释义如下

写法示例:

代码语言:javascript复制
//YUV buffer方案
        TRTCCloudDef.TRTCVideoFrame frame = new TRTCCloudDef.TRTCVideoFrame();
        frame.bufferType = TRTC_VIDEO_BUFFER_TYPE_BYTE_ARRAY;
        frame.pixelFormat = TRTC_VIDEO_PIXEL_FORMAT_I420;
//        frame.pixelFormat = TRTC_VIDEO_PIXEL_FORMAT_NV21;
        frame.data = byte[](i420Data);
        frame.width = 320;
        frame.height = 240;
        
        TRTCCloud.sendCustomVideoData(frame);
        
//纹理 texture方案
        TRTCCloudDef.TRTCVideoFrame frame = new TRTCCloudDef.TRTCVideoFrame();
        frame.bufferType = TRTC_VIDEO_BUFFER_TYPE_TEXTURE;
        frame.pixelFormat = TRTCCloudDef.TRTC_VIDEO_PIXEL_FORMAT_Texture_2D;
        frame.texture = new TRTCCloudDef.TRTCTexture();
        frame.texture.textureId = textureId;
        frame.texture.eglContext14 = eglContext;
        frame.width = 960;
        frame.height = 720;
        frame.timestamp = 0;
        
        TRTCCloud.sendCustomVideoData(frame);

三、Texture2D方案:

本篇主要介绍Texture方案:使用安卓系统封装的camera2 GLSurefaceView,采集到OES纹理,使用FBO复制成Texture2D纹理离屏渲染,将纹理ID交给TRTCSDK编码传输。

0、通话效果

写成的demo效果如下,源码地址点击下载。

两个图中,都用采集到了超高清画质1280X960,使用OpenGL方式渲染,直接操作手机GPU,渲染流畅,通话过程中无卡帧现象。这是Texture方案比buffer方案最大的优势:性能好。

在开始讲demo代码实现过程之前,我们先回顾一下几个知识点:OpenGL、安卓端OpenGL ES、FBO离屏渲染。

这三个知识点,是demo中需要用的音视频基础,下面讲串起来讲一下。

1、OpenGL

OpenGL这个状态机,大家平时应该都有接触过,学起来也并不简单,想要用三两句简介,把OpenGL说出个所以然来,实在是考验笔者的总结能力。在写本篇之前,有新开一篇专门讲OpenGL,有需要的同学可以跳过去阅览一番先。《OpenGL入门》

为了方便理解下面demo中代码流程,这里总结一下OpenGL渲染流程(注意并不一定全是这种顺序)

  • 申明OpenGl状态机上下文
  • 设置视图展示窗口(viewport)
  • 创建图形类,确定好顶点位置和图形颜色,将顶点和颜色数据转换为OpenGl使用的数据格式
  • 加载顶点着色器和片段着色器用来修改图形的颜色,纹理,坐标等属性
  • 创建程式(Program),连接顶点着色器片段着色器。
  • 将坐标数据传入到OpenGl 程式中:

2、安卓端OpenGL ES

OpenGl一般用于在图形工作站,在PC端使用,由于性能各方面原因,在移动端使用OpenGl基本带不动。为此,Khronos公司就为OpenGl提供了一个子集,OpenGl ES(OpenGl for Embedded System)

OpenGl ES是免费的跨平台的功能完善的2D/3D图形库接口的API,是OpenGL的一个子集。

移动端使用到的基本上都是OpenGl ES,当然Android开发下还专门为OpenGl提供了android.opengl包,并且提供了GlSurfaceView,GLU,GlUtils等工具类。

我们需要了解两个基本类别的Android框架:GlSurfaceView和GlSurfaceView.Renderer

GlSurfaceView是什么? GLSurfaceView的作用是什么? GLSurfaceView如何使用?

GlSurfaceView从名字就可以看出,它是一个SurfaceView,看源码可知,GlSurfaceView继承自SurfaceView。并增加了Renderer.它的作用就是专门为OpenGl显示渲染使用的。

GLSurfaceView的使用方法: 可以通过创建的实例使用这个类,并增加你的Renderer.

代码语言:javascript复制
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {

            }

            @Override
            public void onDrawFrame(GL10 gl) {

            }
        });
        setContentView(glSurfaceView);
    }

GlSurfaceView.Renderer是什么?GLSurfaceView.Renderer的作用?GLSurfaceView.Renderer的用法?

该接口定义了用于绘制在图形所需的方法GLSurfaceView。你必须提供这个接口作为一个单独的类的实现,并将其连接到您的GLSurfaceView使用实例 GLSurfaceView.setRenderer()。如上面的代码所示。作用就是提供各种渲染方法,OpenGl的渲染操作均在此接口中实习。下面说下实现该接口的方法含义:

  • onSurfaceCreated():系统调用这个方法一次创建时GLSurfaceView。使用此方法来执行只需要发生一次的操作,比如设置OpenGL的环境参数或初始化的OpenGL图形对象。
  • onDrawFrame():系统调用上的每个重绘此方法GLSurfaceView。使用此方法作为主要执行点用于绘制(和重新绘制)的图形对象。
  • 系统调用此方法时的GLSurfaceView几何形状的变化,包括尺寸变化GLSurfaceView或设备屏幕的取向。例如,当设备从纵向变为横向的系统调用这个方法。使用此方法可以在变化做出反应GLSurfaceView容器。

介绍完了GlSurfaceView和GlSurfaceView.renderer之后,接下来说下如何使用GlSurfaceView; 1. 创建一个GlSurfaceView 2. 为这个GlSurfaceView设置渲染 3. 在GlSurfaceView.renderer中绘制处理显示数据

代码语言:javascript复制
public class GlRenderView extends GLSurfaceView {

    public GlRenderView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public void setRender(){
        //设置EGL 版本
        setEGLContextClientVersion(2);
        glRender = new GlRenderWrapper(this, size);

        setRenderer(glRender);
        //手动渲染模式
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }
}

我们配置的是手动渲染模式,这里如果想执行onDrawFrame方法,需要每来一帧图像,调用 requestRender()。

代码语言:javascript复制
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    camera2Helper = new Camera2Helper((Activity) glRenderView.getContext(),size);

    mTextures = new int[1];
    //创建一个纹理
    GLES20.glGenTextures(mTextures.length, mTextures, 0);
    //将纹理和离屏buffer绑定
    mSurfaceTexture = new SurfaceTexture(mTextures[0]);
    //监听有新图像到来
    mSurfaceTexture.setOnFrameAvailableListener(this);

    //使用fbo 将samplerExternalOES 输入到sampler2D中
    cameraFilter = new CameraFilter(glRenderView.getContext());
    //负责将图像绘制到屏幕上
    screenFilter = new ScreenFilter(glRenderView.getContext());
    //获取OpenGL上下文对象
    eglContext = EGL14.eglGetCurrentContext();

}

onSurfaceCreated中,实例Camera2Helper对象,创建一个SurfaceTexture和纹理,并进行绑定。这个SurfaceTexture会传给Camera2中,camera2负责输入图像到SurfaceTexture中,这里的SurfaceTexture是一个离屏buffer。并且实例CameraFilter和ScreenFilter。

当有新图像来了,会执行onFrameAvailable,更新图像。

代码语言:javascript复制
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    glRenderView.requestRender();
}

问什么要将 samplerExternalOES 输入到sampler2D中

ScreenFilter:负责将图像绘制到屏幕上(加完滤镜美颜等效果,也是用这个类进行展示的)

CameraFilter的顶点着色器。

代码语言:javascript复制
// 把顶点坐标给这个变量, 确定要画画的形状
attribute vec4 vPosition;
//接收纹理坐标,接收采样器采样图片的坐标
attribute vec4 vCoord;
//变换矩阵, 需要将原本的vCoord(01,11,00,10) 与矩阵相乘 才能够得到 surfacetexure(特殊)的正确的采样坐标
uniform mat4 vMatrix;
//传给片段着色器 像素点
varying vec2 aCoord;

void main(){
    //内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
    gl_Position = vPosition;
    // 进过测试 和设备有关(有些设备直接就采集不到图像,有些呢则会镜像)
    aCoord = (vMatrix*vCoord).xy;
    //aCoord =  vec2((vCoord*vMatrix).x,(vCoord*vMatrix).y);
}

片段着色器:

代码语言:javascript复制
#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;
 
//采样点的坐标
varying vec2 aCoord;
 
//采样器
uniform samplerExternalOES vTexture;
 
void main(){
    //变量 接收像素值
    // texture2D:采样器 采集 aCoord的像素
    //赋值给 gl_FragColor 就可以了
    gl_FragColor = texture2D(vTexture,aCoord);
}

再实例化CameraFilter,会创建着色器程序。拿到着色器中的变量。

代码语言:javascript复制
public BaseFilter(Context mContext, int mVertexShaderId, int mFragShaderId) {
        this.mContext = mContext;
        this.mVertexShaderId = mVertexShaderId;
        this.mFragShaderId = mFragShaderId;


        mGlVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mGlVertexBuffer.clear();

//        float[] VERTEXT = {
//                -1.0f, -1.0f,
//                1.0f, -1.0f,
//                -1.0f, 1.0f,
//                1.0f, 1.0f
//        };

        float[] VERTEXT = {
                -1.0f, 1.0f,
                1.0f, 1.0f,
                -1.0f, -1.0f,
                1.0f, -1.0f
        };


        mGlVertexBuffer.put(VERTEXT);

        mGlTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mGlTextureBuffer.clear();

//        float[] TEXTURE = {
//                0.0f, 1.0f,
//                1.0f, 1.0f,
//                0.0f, 0.0f,
//                1.0f, 0.0f,
//        };

        float[] TEXTURE = {
                0.0f, 0.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
        };


        mGlTextureBuffer.put(TEXTURE);

        initilize(mContext);

        resetCoordinate();

    }

BaseFilter 是父类,首先初始化了顶点坐标和纹理坐标的值,

代码语言:javascript复制
private void initilize(Context mContext) {
    //读取着色器信息
    mVertexShader = OpenGlUtils.readRawShaderFile(mContext, mVertexShaderId);
    mFragShader = OpenGlUtils.readRawShaderFile(mContext, mFragShaderId);
    //创建着色器程序
    mProgramId = OpenGlUtils.loadProgram(mVertexShader, mFragShader);
    //获取着色器变量,需要赋值
    vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition");
    vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord");
    vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix");
    vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture");

}

会把着色器里的信息以String读取出来,OpenGlUtils是个工具类,OpenGlUtils.loadProgram创建着色器程序。

ScreenFilter也是一样的,但是不同的是在片段着色器中,接收的纹理是Sampler2D,而不是 samplerExternalOES。这是因为,在CameraFilter中,传入的直接是SurfaceTexture,它不属于OpenGL中定义的东西,所以使用samplerExternalOES,经过CameraFilter使用FBO处理后,后续的所有操作都使用Sampler2D就可以了。

代码语言:javascript复制
precision mediump float;

    varying vec2 aCoord;

    uniform sampler2D vTexture;

    void main(){
    gl_FragColor=texture2D(vTexture,aCoord);
    }

onSurfaceChanged

代码语言:javascript复制
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

    camera2Helper.setPreviewSizeListener(this);
    camera2Helper.setOnPreviewListener(this);
    //打开相机
    camera2Helper.openCamera(width, height, mSurfaceTexture);



    float scaleX = (float) mPreviewHeight / (float) width;
    float scaleY = (float) mPreviewWdith / (float) height;

    float max = Math.max(scaleX, scaleY);

    screenSurfaceWid = (int) (mPreviewHeight / max);
    screenSurfaceHeight = (int) (mPreviewWdith / max);
    screenX = width - (int) (mPreviewHeight / max);
    screenY = height - (int) (mPreviewWdith / max);

   //prepare 传如 绘制到屏幕上的宽 高 起始点的X坐标 起使点的Y坐标
    cameraFilter.prepare(screenSurfaceWid, screenSurfaceHeight, screenX, screenY);
    screenFilter.prepare(screenSurfaceWid, screenSurfaceHeight, screenX, screenY);

}

 onSurfaceChanged中开启相机,传入screenFilter要预览的大小参数。但对于cameraFilter..prepare中,还会创建FBO。

创建过程在 loadFBO中

代码语言:javascript复制
private void loadFOB() {

    if (mFrameBuffers != null) {
        destroyFrameBuffers();
    }
    //创建FrameBuffer
    mFrameBuffers = new int[1];
    GLES20.glGenFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
    //创建FBO中的纹理
    mFBOTextures = new int[1];
    OpenGlUtils.glGenTextures(mFBOTextures);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFBOTextures[0]);
    //指定FBO纹理的输出图像的格式 RGBA
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mOutputWidth, mOutputHeight,
            0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);

    //将fbo绑定到2d的纹理上
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
            GLES20.GL_TEXTURE_2D, mFBOTextures[0], 0);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

}

onDrawFrame方法

代码语言:javascript复制
@Override
public void onDrawFrame(GL10 gl) {
    int textureId;
    // 配置屏幕
    //清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
    GLES20.glClearColor(0, 0, 0, 1.0f);
    //执行上一个:glClearColor配置的屏幕颜色
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

    //更新获取一张图
    mSurfaceTexture.updateTexImage();

    mSurfaceTexture.getTransformMatrix(mtx);
    //cameraFiler需要一个矩阵,是Surface和我们手机屏幕的一个坐标之间的关系
    cameraFilter.setMatrix(mtx);

    textureId = cameraFilter.onDrawFrame(mTextures[0]);

    int id = screenFilter.onDrawFrame(textureId);
    //将TRTCSDK 发送纹理frame的关键参数,回调给外部
    onPreviewListener.onTexture(id, eglContext,mPreviewWdith,mPreviewHeight);
}

cameraFilter.onDrawFrame(mTextures[0])

mTextures[0]是SUrfaceTexture绑定的纹理ID

代码语言:javascript复制
@Override
public int onDrawFrame(int textureId) {

    //锁定绘制的区域  绘制是从左下角开始的
    GLES20.glViewport(0, 0, mOutputWidth, mOutputHeight);
    //绑定FBO,在FBO上操作
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);

    //使用着色器
    GLES20.glUseProgram(mProgramId);

    //赋值vPosition
    mGlVertexBuffer.position(0);
    GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGlVertexBuffer);
    GLES20.glEnableVertexAttribArray(vPosition);

    //赋值vCoord
    mGlTextureBuffer.position(0);
    GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGlTextureBuffer);
    GLES20.glEnableVertexAttribArray(vCoord);
    //赋值vMatrix
    GLES20.glUniformMatrix4fv(vMatrix, 1, false, matrix, 0);

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    //SurfaceTexture 对应 GL_TEXTURE_EXTERNAL_OES 类型
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
    //赋值vTexture
    GLES20.glUniform1i(vTexture, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);

    return mFBOTextures[0];
}

在FBO上操作,把输入的纹理ID上的图像,输出到FBO的纹理ID上然后返回。

screenFilter.onDrawFrame(textureId);

代码语言:javascript复制
public int onDrawFrame(int textureId) {

    GLES20.glViewport(x, y, mOutputWidth, mOutputHeight);

    GLES20.glUseProgram(mProgramId);

    mGlVertexBuffer.position(0);

    GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGlVertexBuffer);
    GLES20.glEnableVertexAttribArray(vPosition);

    mGlTextureBuffer.position(0);
    GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGlTextureBuffer);
    GLES20.glEnableVertexAttribArray(vCoord);

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    //传入的是GL_TEXTURE_2D类型
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    GLES20.glUniform1i(vTexture, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

    return textureId;


}

这里拿到的textureId是CameraFilter中FBO的纹理ID,赋值着色器变量,直接glDrawArrays绘制。这里

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);的意思是绘制三角形,从第一个点开始,总共有4个点。

1 2 3 画一个三角,2 3 4画一个三角,这四个点就是顶点坐标。这里两个三角正好绘制出来的是一个矩形。

3、FBO离屏渲染

如上文中代码所示,在cameraFilter.onDrawFrame(mTextures[0])方法里面,已经将纹理id绘制出来了GLES20.glDrawArrays

,为什么还要FBO离屏渲染呢,什么是FBO呢。

帧缓冲对象FBO(Frame buffer Object)。

OpenGL默认情况下,在GLSurfaceView中绘制的结果是显示到屏幕上的,但是实际情况中大部分时候都不需要渲染到屏幕中去,这个FBO就是来实现这个需求的,FBO可以让不渲染到屏幕当中去,而是渲染到离屏的buffer中。比如美颜操作、水印操作等,都需要处理纹理,将处理之后的Texture2D纹理渲染出来。

注意,目前TRTCSDK,传输纹理格式,仅支持Texture2D格式,不支持OES格式,而android系统carmera2采集的纹理格式,是OES格式的。

安卓端FBO写法

前文代码示例中,已经给出了FBO的写法了,这里再展示FBO的OpenGL.API

1、创建FBO

代码语言:javascript复制
//创建FrameBuffer
mFrameBuffers = new int[1];
GLES20.glGenFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);

2、绑定FBO

通过绑定纹理对象来锁定挂接渲染区

代码语言:javascript复制
//绑定FBO,在FBO上操作
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
//将fbo绑定到2d的纹理上
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFBOTextures[0], 0);
                
//这个Texture2D的纹理id需要事先创建
    private int createFBOTexture(int width, int height, int format) {
        final int[] textureIds = new int[1];
        GLES20.glGenTextures(1, textureIds, 0);
        if(textureIds[0] == 0){
            int i = GLES20.glGetError();
            throw new RuntimeException("Could not create a new texture buffer object, glErrorString : "  GLES20.glGetString(i));
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, format, width, height,
                0, format, GLES20.GL_UNSIGNED_BYTE, null);
        return textureIds[0];
    }

3、接着我们就可以离屏渲染了(draw)

代码语言:javascript复制
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

4、screenFilter.onDrawFrame(textureId)将FBO中的纹理Texture2D正常渲染到屏幕上

代码语言:javascript复制
//传入的是GL_TEXTURE_2D类型
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

5、预览结束,销毁当前帧的FBO。下一帧回调出来再继续循环。

代码语言:javascript复制
public void destroyFrameBuffers() {
    //删除fbo的纹理
    if (mFBOTextures != null) {
        GLES20.glDeleteTextures(1, mFBOTextures, 0);
        mFBOTextures = null;
    }
    //删除fbo
    if (mFrameBuffers != null) {
        GLES20.glDeleteFramebuffers(1, mFrameBuffers, 0);
        mFrameBuffers = null;
    }
}

4、TRTCSDK 视频通话

将Texture2D纹理数据,经过您三方美颜数据处理之后,就可以交给TRTCSDK的sendCustomVideoData接口了,即可实现自定义采集视频通话。

TRTCSDK集成、初始化等写法,官网有比较详细的介绍,写法比较简单,结合demo看代码、文档即可。

代码语言:javascript复制
@Override
public void onTexture(int textureId, android.opengl.EGLContext eglContext, int width, int height) {

    frame.texture = new TRTCCloudDef.TRTCTexture();
    frame.bufferType = TRTC_VIDEO_BUFFER_TYPE_TEXTURE;
    frame.timestamp = 0;
    frame.texture.textureId = textureId;
    frame.texture.eglContext14 = eglContext;
    frame.width = height;
    frame.height = width;
    frame.pixelFormat = TRTCCloudDef.TRTC_VIDEO_PIXEL_FORMAT_Texture_2D;
    mTRTCCloud.sendCustomVideoData(frame);


}

四、TRTC通话状态

使用TRTC提供的监控仪表盘,看到通话正常

五、demo下载

myCustomVideoCaptureDemo

0 人点赞