视频流添加水印方式较多 本文只从渲染角度修改
修改 CameraViewInterface 预览视图 自定义 CameraSurfaceView 将相机预览数据输出到外部纹理 通过fbo 混合水印纹理及Camera纹理 最终输出到 SurfaceView 上
打开相机输出到外部纹理
mHandlerL.startPreview(mUVCCameraViewL.getSurfaceTexture());
private final OnDeviceConnectListener mOnDeviceConnectListener = new OnDeviceConnectListener() {
@Override
public void onAttach(final UsbDevice device) {
if (DEBUG) Log.v(TAG, "onAttach:" + device);
Toast.makeText(MainActivity.this, "USB_DEVICE_ATTACHED", Toast.LENGTH_SHORT).show();
}
@Override
public void onConnect(final UsbDevice device, final UsbControlBlock ctrlBlock, final boolean createNew) {
if (DEBUG) Log.v(TAG, "onConnect:" + device);
if (!mHandlerL.isOpened()) {
if (DEBUG) Log.v(TAG, "mHandlerL not opend:");
mHandlerL.open(ctrlBlock);
mHandlerL.startPreview(mUVCCameraViewL.getSurfaceTexture());
runOnUiThread(new Runnable() {
@Override
public void run() {
mCaptureButtonL.setVisibility(View.VISIBLE);
}
});
} else if (!mHandlerR.isOpened()) {
if (DEBUG) Log.v(TAG, "mHandlerR not opend:");
mHandlerR.open(ctrlBlock);
mHandlerR.startPreview(mUVCCameraViewR.getSurfaceTexture());
runOnUiThread(new Runnable() {
@Override
public void run() {
mCaptureButtonR.setVisibility(View.VISIBLE);
}
});
}
}
创建外部纹理 及 SurfaceTexture
cameraTexture = GlUtil.createRecordCameraTextureID();
surfaceTexture = new SurfaceTexture(cameraTexture);
RenderThread 中绑定EGL上下文 绑定显示窗口缓冲区
private final void init() {
if (DEBUG) Log.v(TAG, "RenderThread#init:");
try {
glRenderManager.setCameraRotate(180);
glRenderManager = new GlRenderManager(appContext, mTexId, mDispSurface, mPreviewSurface);
glRenderManager.onInputSizeChanged(640, 480);
glRenderManager.onDisplaySizeChanged(mViewWidth, mViewHeight);
// notify to caller thread that previewSurface is ready
} catch (GlUtil.OpenGlException e) {
e.printStackTrace();
}
}
public GlRenderManager(Context context, int texture, Surface disPlaySurface, SurfaceTexture surfaceTexture) throws GlUtil.OpenGlException {
this.context = context;
this.texture = texture;
this.surfaceTexture = surfaceTexture;
mEglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3);
setDisPlaySurface(disPlaySurface);
mDisplaySurface.makeCurrent();
//关闭深度测试和绘制背面
GLES20.glDisable(GL10.GL_DEPTH_TEST);
GLES20.glDisable(GL10.GL_CULL_FACE);
init();
}
/** * Prepares EGL display and context. *
* * @param sharedContext The context to share, or null if sharing is not desired. * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. */ public EglCore(EGLContext sharedContext, int flags) throws GlUtil.OpenGlException { if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("EGL already set up"); } if (sharedContext == null) { sharedContext = EGL14.EGL_NO_CONTEXT; } mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("unable to get EGL14 display"); } int[] version = new int[2]; if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { mEGLDisplay = null; throw new RuntimeException("unable to initialize EGL14"); } // Try to get a GLES3 context, if requested. if ((flags & FLAG_TRY_GLES3) != 0) { //Log.d(TAG, "Trying GLES 3"); EGLConfig config = getConfig(flags, 3); if (config != null) { int[] attrib3_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE }; EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib3_list, 0); if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { //Log.d(TAG, "Got GLES 3 config"); mEGLConfig = config; mEGLContext = context; mGlVersion = 3; } } } if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed //Log.d(TAG, "Trying GLES 2"); EGLConfig config = getConfig(flags, 2); if (config == null) { throw new RuntimeException("Unable to find a suitable EGLConfig"); } int[] attrib2_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE }; EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list, 0); checkEglError("eglCreateContext"); mEGLConfig = config; mEGLContext = context; mGlVersion = 2; } // Confirm with query. int[] values = new int[1]; EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0); Log.d(TAG, "EGLContext created, client version " + values[0]); }
RenderThread WHEN_DIRTY渲染
mPreviewSurface.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mHandler.post(new Runnable() {
@Override
public void run() {
callDrawFrame();
}
});
}
});
FBO渲染 NDK OpenGL ES 3.0 开发(五):FBO 离屏渲染_字节流动的博客-CSDN博客
1 创建FBO 关联 frameBuffferTex
/**
* 创建Sampler2D的Framebuffer 和 Texture
*
* @param frameBuffer
* @param frameBufferTex
* @param width
* @param height
*/
public static void createSampler2DFrameBuff(int[] frameBuffer, int[] frameBufferTex,
int width, int height, int position) throws GlUtil.OpenGlException {
GLES30.glGenFramebuffers(1, frameBuffer, position);
GLES30.glGenTextures(1, frameBufferTex, position);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameBufferTex[position]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[position]);
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D, frameBufferTex[position], 0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
checkGlError("createCamFrameBuff");
}
2 drwFrame (opengl render thread)
//.....
mDisplaySurface.makeCurrent();
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//刷新
try {
surfaceTexture.updateTexImage();
} catch (Exception e) {
e.printStackTrace();
return;
}
//....
//绘制相机输出流 及滤镜等 FBO
if (displayRenderGroup != null) {
displayRenderGroup.setMirroring(mirroring);
currentTexture = displayRenderGroup.drawFrame(currentTexture);
}
//后续操作 FBO切换到 currentTexture(可见窗口) 上处理
//....
//添加水印
drawWaterSign();
//送显
mDisplaySurface.swapBuffers();
绑定FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFramebuffers[0]);
FBO处理完后切换到正常显示窗口
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
拆分动态部分 防止闪烁 这里可以进一步优化缓存 bitmap
//初始化文字转图片所需的对象,避免多次生成新对象消耗过多内存
//将字符图片与纹理绑定,返回纹理id
for (int i = 0; i < 12; i++) {
if (i == 10) {
mWaterTexId[i] = TextureHelper.loadTexture(BitmapUtils.textToBitmap("-"), textureObjectIds);
} else if (i == 11) {
mWaterTexId[i] = TextureHelper.loadTexture(BitmapUtils.textToBitmap(":"), textureObjectIds);
} else {
mWaterTexId[i] = TextureHelper.loadTexture(BitmapUtils.textToBitmap(i + ""), textureObjectIds);
}
}
动态绘制时间纹理
private void drawWaterSign() {
String time = formatter.format(new Date());
int x = waterMaskStartX;
int y = waterMaskStartY;
if ("".equals(time)) {
return;
}
GLES20.glEnable(GLES20.GL_BLEND);
//开启GL的混合模式,即图像叠加
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
//画水印
GLES20.glViewport(x, y, waterMaskWidth, waterMaskHeight);//60
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(0, 1))]);
GLES20.glViewport(x + 15, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(1, 2))]);
GLES20.glViewport(x + 15 * 2, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(2, 3))]);
GLES20.glViewport(x + 15 * 3, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(3, 4))]);
GLES20.glViewport(x + 15 * 4, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[10]); // -
GLES20.glViewport(x + 15 * 5, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(5, 6))]);
GLES20.glViewport(x + 15 * 6, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(6, 7))]);
GLES20.glViewport(x + 15 * 7, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[10]); // -
GLES20.glViewport(x + 15 * 8, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(8, 9))]);
GLES20.glViewport(x + 15 * 9, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(9, 10))]);
GLES20.glViewport(x + 15 * 11, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(11, 12))]);
GLES20.glViewport(x + 15 * 12, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(12, 13))]);
GLES20.glViewport(x + 15 * 13, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[11]); // :
GLES20.glViewport(x + 15 * 14, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(14, 15))]);
GLES20.glViewport(x + 15 * 15, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(15, 16))]);
GLES20.glViewport(x + 15 * 16, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[11]); // :
GLES20.glViewport(x + 15 * 17, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(17, 18))]);
GLES20.glViewport(x + 15 * 18, y, waterMaskWidth, waterMaskHeight);
mWaterSign.drawFrame(mWaterTexId[Integer.parseInt(time.substring(18, 19))]);
//GLES30.glDisable(GLES30.GL_BLEND);
}
OPENGL 滤镜水印可参照GPUIMAGE库 https://link.csdn.net/?target=https%3A%2F%2Fgithub.com%2FBradLarson%2FGPUImage.git
在此基础上采集修改定制 这里也是用的他人的 渲染部分整合而来 实际项目中一般在底层 通过ffmpeg filter 实现 更加灵活 在推流端处理视频帧
QtyearLin/UVCCamera_Water · GitHub