背景
实现直播功能时,需要把带绿幕的摄像头内容和视频,渲染成透明叠加效果。
实现思路
视频绿幕抠图的部分,可以查看前面的文档和代码:juejin.cn/post/735714...
在原有的绿幕抠图的基础上,用多个视图叠加的方式,也能实现透明叠加效果,但是不利于后续功能扩展。 本次实现方式:ijkplayer使用surfaceview+gles(软解)的方式,把视频帧离屏渲染到FBO,再回调到JAVA层,在JAVA层通过绿幕抠图的shader处理绿幕,再把多个纹理通过共享Context及GLES BLEND方式叠加渲染到同一个GLSurfaceView上。
实现步骤
C层修改
去除window
由于ijkplayer默认是每个播放器对应一个SurfaceView,在C层通过ANativeWindow_fromSurface获取native_window,然后调用
arduino
surface = eglCreateWindowSurface(display, config, window, NULL);
创建surface并渲染的,所以需要把原逻辑里面的和WINDOW相关的逻辑全部改成离屏渲染
ijksdl_vout_android_nativewindow.c
static int func_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay)
{
SDL_Vout_Opaque *opaque = vout->opaque;
ANativeWindow *native_window = opaque->native_window;
if (!native_window) {
#if CUSTOM_NO_VIEW
#else
if (!opaque->null_native_window_warned) {
opaque->null_native_window_warned = 1;
ALOGW("func_display_overlay_l: NULL native_window");
}
return -1;
#endif
} else {
opaque->null_native_window_warned = 1;
}
xxxxxxxxx
}
ijksdl_egl.c
#if CUSTOM_NO_VIEW
static EGLBoolean IJK_EGL_makeCurrent(IJK_EGL* egl, EGLNativeWindowType window, int width, int height)
#else
static EGLBoolean IJK_EGL_makeCurrent(IJK_EGL* egl, EGLNativeWindowType window)
#endif
{
xxxxxxx
#if CUSTOM_NO_VIEW
EGLSurface surface;
EGLSurface context;
if (!window) {
//离屏渲染
EGLint PbufferAttributes[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE, EGL_NONE };
if (!(surface = eglCreatePbufferSurface(display, config, PbufferAttributes))) {
ALOGE("[EGL] eglCreatePbufferSurface failed,returned error %d", eglGetError());
return EGL_FALSE;
}
xxxx
}
}
window处理的相关代码就不全部贴出来了,可以在代码里面全局搜索CUSTOM_NO_VIEW找到全部相关的代码修改
共享GLES CONTEXT
由于每个ijkplayer的播放器是独立的,用来做渲染的线程也是独立的,所以如果要渲染到同一个GLSurfaceView上,就涉及到纹理的跨线程使用,这里通过把Android的JAVA层的GLES Context共享给ijkplayer的C层的方式实现。
ijksdl_egl.h
typedef struct IJK_EGL
{
SDL_Class *opaque_class;
IJK_EGL_Opaque *opaque;
EGLNativeWindowType window;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
EGLint width;
EGLint height;
#if CUSTOM_SHARE_EGL_CONTEXT
EGLContext share_egl_context;
#endif
#if 0
uint8_t gles2_extensions[IJK_GLES2__MAX_EXT];
#endif
} IJK_EGL;
ijkplayer_android.c
#if CUSTOM_SHARE_EGL_CONTEXT
#include "ijksdl_egl.h"
void ijkmp_android_set_share_egl_context(IjkMediaPlayer *mp){
EGLContext *currentContext = eglGetCurrentContext();
assert(currentContext);
mp->ffplayer->vout->share_egl_context = currentContext;
}
#endif
ijksdl_egl.c
#if CUSTOM_NO_VIEW
static EGLBoolean IJK_EGL_makeCurrent(IJK_EGL* egl, EGLNativeWindowType window, int width, int height)
#else
static EGLBoolean IJK_EGL_makeCurrent(IJK_EGL* egl, EGLNativeWindowType window)
#endif
{
xxxx
#if CUSTOM_SHARE_EGL_CONTEXT
if (egl->share_egl_context) {
context = eglCreateContext(display, config, egl->share_egl_context, contextAttribs);
}
#else
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
#endif
}
其它相关代码不再帖出来,可以在代码里面全局搜索CUSTOM_SHARE_EGL_CONTEXT找到相关代码实现
混合
VideoOverlayActivity.java
private void draw() {
GlUtil.checkGlError("draw start");
// Clear to a non-black color to make the content easily differentiable from
// the pillar-/letter-boxing.
GLES20.glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Textures may include alpha, so turn blending on.
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
if (mTargetVideo0.getTextureId() > 0) {
mTargetVideo0.draw(mTexProgram, mDisplayProjectionMatrix);
}
if (mTargetVideo1.getTextureId() > 0) {
mTargetVideo1.draw(mTexProgram, mDisplayProjectionMatrix);
}
mTargetImage.draw(mTexProgram, mDisplayProjectionMatrix);
GLES20.glDisable(GLES20.GL_BLEND);
GlUtil.checkGlError("draw done");
}