播放视频时如何在视频帧上添加水印

2020-11-11 17:02:11 浏览数 (1)

之前的一篇文章中我们介绍了播放视频的时候调整音频的音量,我们能否在播放视频的时候在视频画面上加上水印?

有同学可能会说了,我直接用TextureView渲染视频画面,然后在TextureView上盖一层ImageView可以吗?

好像显示效果上没有什么问题,但是仔细分析还是不能满足要求?

  • 1.ImageView和TextureView有明显的层级关系,如果出现View层级的问题,不太好处理
  • 2.TextureView渲染视频的时候,提供了getBitmap()接口来截取视频的某一帧,如果盖上一层ImageView无法实现截图的功能
  • 3.ImageView盖在TextureView,会拦截TextureView的事件,造成播放器交互方面的问题。

上面三个问题表示TextureView上面盖一层ImageView的方式是行不通的。 既然行不通,有没有方法可以解决这个问题?

其实除了TextureView和SurfaceView渲染视频之外,GLSurfaceView也是渲染视频的一种View,GLSurfaceView和OpenGL结合,可以实现给播放中的视频添加水印的目的。

1.GLSurfaceView介绍

GLSurfaceView从Android 1.5(API level 3)开始加入,继承自SurfaceView,实现了SurfaceHolder.Callback2接口,拥有SurfaceView的全部特性,也有view所有的功能和属性,特别是处理事件的能力,它主要是在SurfaceView的基础上它加入了EGL的管理,并自带了一个GLThread绘制线程(EGLContext创建GL环境所在线程即为GL线程),绘制的工作直接通过OpenGL在绘制线程进行,不会阻塞主线程,绘制的结果输出到SurfaceView所提供的Surface上,这使得GLSurfaceView也拥有了OpenGlES所提供的图形处理能力,通过它定义的Render接口,使更改具体的Render的行为非常灵活性,只需要将实现了渲染函数的Renderer的实现类设置给GLSurfaceView即可。

GLSurfaceView提供了下列特性:

  • 1.提供并且管理一个独立的Surface。
  • 2.提供并且管理一个EGL display,它能让opengl把内容渲染到上述的Surface上。
  • 3.支持用户自定义渲染器(Render),通过setRenderer设置一个自定义的Renderer。
  • 4.让渲染器在独立的GLThread线程里运作,和UI线程分离。
  • 5.支持按需渲染(on-demand)和连续渲染(continuous)两种模式。
  • 6.GPU加速:GLSurfaceView的效率是SurfaceView的30倍以上,SurfaceView使用画布进行绘制,GLSurfaceView利用GPU加速提高了绘制效率
  • 7.View的绘制onDraw(Canvas canvas)使用Skia渲染引擎渲染,而GLSurfaceView的渲染器Renderer的onDrawFrame(GL10 gl)使用opengl绘制引擎进行渲染。

2.GLSurfaceView渲染视频

  • 首先定义一个CustomGLSurfaceView 继承GLSurfaceView
  • 定义一个CustomRenderer实现Renderer接口
代码语言:javascript复制
    public interface Renderer {

        void onSurfaceCreated(GL10 gl, EGLConfig config);

        void onSurfaceChanged(GL10 gl, int width, int height);

        void onDrawFrame(GL10 gl);
    }

onSurfaceCreated 是当渲染线程启动的时候,surfacetexture被创建,这个surfacetexture设置到播放器中,之后会在这个surfacetexture上渲染数据。

onSurfaceCreated是surface大小发生变化的时候的回调,渲染的画布宽高都会写明。

onDrawFrame绘制操作,将surfacetexture设置进播放器之后,codec中的surface会不断地被填充新的视频帧,在onDrawFrame中将视频帧surfaceTexture.updateTexImage之后,开始绘制水印图片。

代码语言:javascript复制
    public void onDrawFrame(GL10 gl) {
      if (videoProcessor == null) {
        return;
      }

      if (!initialized) {
        videoProcessor.initialize();
        initialized = true;
      }

      if (width != -1 && height != -1) {
        videoProcessor.setSurfaceSize(width, height);
        width = -1;
        height = -1;
      }

      if (frameAvailable.compareAndSet(true, false)) {
        SurfaceTexture surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
        surfaceTexture.updateTexImage();
        long lastFrameTimestampNs = surfaceTexture.getTimestamp();
        Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
        if (frameTimestampUs != null) {
          this.frameTimestampUs = frameTimestampUs;
        }
      }

      videoProcessor.draw(texture, frameTimestampUs);
    }

videoProcessor.draw(texture, frameTimestampleUs)开始在视频帧基础上绘制水印。

设置水印图片的定点着色器和边缘着色器:

代码语言:javascript复制
attribute vec4 a_position;
attribute vec3 a_texcoord;
varying vec2 v_texcoord;
void main() {
 gl_Position = a_position;
 v_texcoord = a_texcoord.xy;
}
代码语言:javascript复制
#extension GL_OES_EGL_image_external : require
precision mediump float;
// External texture containing video decoder output.
uniform samplerExternalOES tex_sampler_0;
// Texture containing the overlap bitmap.
uniform sampler2D tex_sampler_1;
// Horizontal scaling factor for the overlap bitmap.
uniform float scaleX;
// Vertical scaling factory for the overlap bitmap.
uniform float scaleY;
varying vec2 v_texcoord;
void main() {
  vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
  vec4 overlayColor = texture2D(tex_sampler_1,
                                vec2(v_texcoord.x * scaleX,
                                     v_texcoord.y * scaleY));
  // Blend the video decoder output and the overlay bitmap.
  gl_FragColor = videoColor * (1.0 - overlayColor.a)
        overlayColor * overlayColor.a;
}

通过Cavas将水印图片画出来。

代码语言:javascript复制
  public void draw(int frameTexture, long frameTimestampUs) {
    // Draw to the canvas and store it in a texture.
    String text = String.format(Locale.US, "%.02f", frameTimestampUs / (float) C.MICROS_PER_SECOND);
    overlayBitmap.eraseColor(Color.TRANSPARENT);
    overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint);
    overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint);
    GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    GLUtils.texSubImage2D(
        GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
    GlUtil.checkGlError();

    // Run the shader program.
    GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms);
    GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes);
    GLES20.glUseProgram(program);
    for (GlUtil.Uniform uniform : uniforms) {
      switch (uniform.name) {
        case "tex_sampler_0":
          uniform.setSamplerTexId(frameTexture, /* unit= */ 0);
          break;
        case "tex_sampler_1":
          uniform.setSamplerTexId(textures[0], /* unit= */ 1);
          break;
        case "scaleX":
          uniform.setFloat(bitmapScaleX);
          break;
        case "scaleY":
          uniform.setFloat(bitmapScaleY);
          break;
      }
    }
    for (GlUtil.Attribute copyExternalAttribute : attributes) {
      copyExternalAttribute.bind();
    }
    for (GlUtil.Uniform copyExternalUniform : uniforms) {
      copyExternalUniform.bind();
    }
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
    GlUtil.checkGlError();
  }

下图左上角的地方有一个Android icon,就是我们绘制上去的水印。

具体代码参考开源项目:https://github.com/JeffMony/PlayerSDK

0 人点赞