按: 最近做了一个直播的预研项目, 因此记录下直播的技术的实现,在这过程中一些问题解决的思路,以android平台的实现说明。
项目结构
- unity纹理插件和视频采集(视频源)
VideoSourceCamera - 麦克风采集(音频源)
AudioSourceMIC - 视频编码
VideoEncoder - 音频编码
AudioEncoder - FLV编码(混合)
MuxerFLV - http流上传(上传源)
PublisherHttp - 流视频播放(回放)
play - OpenGL图形图象处理
从本篇文章开始将会介绍这几个组件的实现细节,相互依赖关系的处理方式。
(1) —— unity纹理插件
我们的直播项目服务于unity,而unity是一个跨平台的游戏引擎,底层根据不同平台,采用了directx, opengl, opengles, 因此需要实现不同平台的图形插件。
(unity的图形插件文档)
https://docs.unity3d.com/Manual/NativePluginInterface.html
在anroid平台下的直播,unity图形插件作用主要是渲染线程通知,
因为无论视频采集,创建surface, 图像处理(shader),还是编码视频纹理传入,都需要工作在unity的渲染线程下,
unity创建纹理,将纹理ID传递到直播插件。
打开camera设备,准备好采集surface,
mCameraGLTexture =
new GLTexture(width, height, GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_RGBA);
note: camera surface是一种特殊类型的纹理,通过GLES11Ext.GL_TEXTURE_EXTERNAL_OES参数创建-
回调通知每一帧数据准备完成
public void onFrameAvailable(final SurfaceTexture surfaceTexture)
{
//这里将采集线程的图象push到渲染线程处理
getProcessor().append (new Task() {
@Override
public void run() {
surfaceTexture.updateTexImage();
}
});
}camera surface也需要做特殊纹理声明
#extension GL_OES_EGL_image_external : require precision mediump float; uniform samplerExternalOES uTexture0; varying vec2 texCoordinate; void main(){ gl_FragColor = texture2D(uTexture0, texCoordinate); }
-
将camera surface纹理写入到 unity的纹理,
将一张纹理写入到另一纹理,可以两种办法,通过glReadPixels, 但这样会导致巨大的内存拷贝,CPU压力。
-
渲染到纹理(render to texture)
mTextureCanvas = new GLRenderTexture(mGLTexture);//声明rendertexturevoid renderCamera2Texture() { mTextureCanvas.begin(); cameraDrawObject.draw(); mTextureCanvas.end(); }
GLRenderTexture的实现, 如下
GLRenderTexture(GLTexture tex)
{
mTex = tex;
int fboTex = tex.getTextureID();
GLES20.glGenFramebuffers(1, bufferObjects, 0);
GLHelper.checkGlError("glGenFramebuffers");
fobID = bufferObjects[0];//创建render buffer GLES20.glGenRenderbuffers(1, bufferObjects, 0); renderBufferId = bufferObjects[0]; //绑定Frame buffer GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fobID); GLHelper.checkGlError("glBindFramebuffer"); //Bind render buffer and define buffer dimension GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBufferId); GLHelper.checkGlError("glBindRenderbuffer"); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, tex.getWidth(), tex.getHeight()); GLHelper.checkGlError("glRenderbufferStorage"); //设置为framebuffer为texutre类型 GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fboTex, 0); GLHelper.checkGlError("glFramebufferTexture2D"); //设置depthbuffer GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderBufferId); GLHelper.checkGlError("glFramebufferRenderbuffer"); //we are done, reset GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0); GLHelper.checkGlError("glBindRenderbuffer"); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLHelper.checkGlError("glBindFramebuffer"); } void begin() { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fobID); GLHelper.checkGlError("glBindFramebuffer"); GLES20.glViewport(0, 0, mTex.getWidth(), mTex.getHeight()); GLHelper.checkGlError("glViewport"); } void end() { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); }
美颜
通过shader实现实时的美颜效果,美白,磨皮
(美颜效果的原理可参考)
http://meituplus.com/?p=101
(更多的实时shader处理可参考)
https://github.com/wuhaoyu1990/MagicCamera