> 文章列表 > UVCCamera OpenGL 添加时间戳水印

UVCCamera OpenGL 添加时间戳水印

UVCCamera OpenGL 添加时间戳水印

视频流添加水印方式较多 本文只从渲染角度修改 

修改  CameraViewInterface 预览视图 自定义 CameraSurfaceView 将相机预览数据输出到外部纹理 通过fbo 混合水印纹理及Camera纹理 最终输出到 SurfaceView 上

打开相机输出到外部纹理

mHandlerL.startPreview(mUVCCameraViewL.getSurfaceTexture());

private final OnDeviceConnectListener mOnDeviceConnectListener = new OnDeviceConnectListener() {@Overridepublic void onAttach(final UsbDevice device) {if (DEBUG) Log.v(TAG, "onAttach:" + device);Toast.makeText(MainActivity.this, "USB_DEVICE_ATTACHED", Toast.LENGTH_SHORT).show();}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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.* <p>** @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() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {mHandler.post(new Runnable() {@Overridepublic 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;}//....//绘制相机输出流 及滤镜等 FBOif (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 

 //初始化文字转图片所需的对象,避免多次生成新对象消耗过多内存//将字符图片与纹理绑定,返回纹理idfor (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);//60mWaterSign.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