之前的一篇文章中我们介绍了播放视频的时候调整音频的音量,我们能否在播放视频的时候在视频画面上加上水印?
有同学可能会说了,我直接用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接口
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