一、适用场景
腾讯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.
代码语言:java复制@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创建着色器程序。
代码语言:javascript复制ScreenFilter也是一样的,但是不同的是在片段着色器中,接收的纹理是Sampler2D,而不是 samplerExternalOES。这是因为,在CameraFilter中,传入的直接是SurfaceTexture,它不属于OpenGL中定义的东西,所以使用samplerExternalOES,经过CameraFilter使用FBO处理后,后续的所有操作都使用Sampler2D就可以了。
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
参考文章:https://blog.csdn.net/a360940265a/article/details/80627394?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control
https://blog.csdn.net/wangchao1412/article/details/103833620
https://blog.csdn.net/qq_32175491/article/details/79091647