本章内容:主要是熟悉OpenGl使用的基本流程
版本:GLES20
android 音视频基础 系列----后续(调整):
1. OpenGl--渲染一个图片+灰度滤镜
2. OpenGl 渲染 CameraX的数据 + 滤镜
3. MediaCodec解码---h264
4. h264基础<一>
5. h264基础<二>
6. MediaCodec 硬编码---YUV数据
7. MediaCodec 硬编码---CameraX数据
8. MediaCodec 硬编码---MediaProjection录屏
9. 音频基础
10. MediaCodec硬编码---AudioRecord音频采集
11. MediaCodec解码---播放mp4
12. MediaCodec编码--CameraX+麦克风编码成mp4
13. 交叉编译基础
14. ffmpeg交叉编译
15. ffmpeg 视频---mediacodec硬解码
16. ffmpeg 视频---软解码
17. OpenGl在native层的使用
18. OpenGl在native层渲染YUV420数据
19. OpenGl在native层渲染NV12数据
20. OpenGl处理常见的几种滤镜.
21. Openssl播放音频
22. 音视频同步
待续..
为什么要先看OpenGl?
因为在音视频中, 渲染的时候经常涉及到纹理,涉及到数据在硬件间的传递,有了OpenGl基础, 后续对视频数据的流动才能更清晰, 例如:Camera的数据, 不经过我们应用就可以用MediaCodec编码. MediaCodec解码后你想让数据到那儿去了, Surface直接渲染、OpenGl中纹理、我们的应用, 都是可以的, 至于需要用哪种看需求.
目前这个系列主要以音视频开发为准, 并不会太过深入OpenGL和细节
OpenGl 是什么?
简单说:一套往GPU写程序的API
OpenGl主要往GPU写什么?
- 顶点程序:定位绘制的区域 ---> 需要了解OpenGL坐标--->世界坐标和纹理坐标
- 片元程序:给这块区域的每个像素点上色.
Android中使用OpenGl的基本流程:
以渲染一张图片+灰度滤镜为例,效果图如下:
OpenGL图片.jpg1. 配置EGL环境
我们写的代码在CPU,如果想要和GPU通讯, 就必须先创建好对应的环境,这个就叫EGL环境, EGL环境所在的线程叫OpenGl线程, 想要和GPU交互, 必须在这个线程中.
不过Android中的Java层有个控件已经帮我们创建好了环境,这个控件是android.opengl.GLSurfaceView.
如果要在native层使用, 那就得自己去创建EGL环境了.
2. 把顶点和片元程序 添加 到 GPU中, 并且编译链接.
①. 准备好片元和顶点程序
两个着色器都放在res/raw目录下
顶点着色器 image_vert.glsl
attribute vec4 vPosition;//顶点坐标
attribute vec4 vCoordinate; //纹理的坐标
varying vec2 tCoordinate; //传递给片元
void main(){
gl_Position = vPosition;
tCoordinate = vCoordinate.xy;
}
片元着色器 image_frag.glsl
precision lowp float;
varying vec2 tCoordinate; //纹理坐标
uniform sampler2D vTexture; //图层采样
void main() {
//初始颜色
// gl_FragColor = texture2D(vTexture, vec2(tCoordinate.x, tCoordinate.y));
//灰度图
vec4 rgba = texture2D(vTexture, vec2(tCoordinate.x, tCoordinate.y));
float value = (rgba.r+rgba.g+rgba.b)/3.0;
gl_FragColor = vec4(value, value, value, rgba.a);
}
②. 把两个着色器的代码加到GPU, 并进行编译链接
GLSurfaceView提供了一个接口Renderer, 只需继承这个接口在里面写代码就行了, 我做了一点简单的封装,这样可以更清晰的看清楚执行流程.
以下代码属于初始化, 所以也写在Renderer中的onSurfaceCreated中:
//顶点程序加到GPU中, vertexShaderCode为 image_vert.glsl的内容
val vertexShader = GLApp.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
//片元程序加到GPU中, fragmentShaderCode为 image_frag.glsl.glsl的内容
val fragmentShader = GLApp.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
//创建一个空的OpenGLES程序
mProgram = GLES20.glCreateProgram()
//将顶点着色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader)
//将片元着色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader)
//连接到着色器程序
GLES20.glLinkProgram(mProgram)
3. 获取顶点和片元程序中的句柄
以下代码属于初始化, 所以也写在Renderer中的onSurfaceCreated中
// --------------------------获取变量-------------------------------
//获取变量
positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
vCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
vTextureHandle = GLES20.glGetUniformLocation(mProgram, "vTexture");
4. 创建纹理
创建纹理, 并获取纹理id, 绑定纹理单元
// --------------------------纹理-------------------------------
textureId = GLApp.createTexture()
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
5. 通过句柄,给顶点或纹理赋值
//赋值坐标数据
GLApp.setCoordinate(positionHandle, vPositionBuffer!!)
GLApp.setCoordinate(vCoordinateHandle, vCoordinateBuffer!!)
//激活绑定纹理
GLApp.activeBindTexture(vTextureHandle, textureId, 0)
6. 通知GPU开始渲染
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //绘制
完整代码如下:
GLImageRenderer.kt 如下:
class GLImageRenderer(var bitmap: Bitmap): BaseRenderer() {
private var vPositionBuffer: FloatBuffer? = null //顶点坐标buf
private var vCoordinateBuffer: FloatBuffer? = null //纹理坐标buf
//句柄
private var positionHandle = 0
private var vCoordinateHandle = 0
private var vTextureHandle = 0
//纹理id
private var textureId = 0
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
LogApp.d("GL onSurfaceCreated")
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f)
//链接
initProgram(
GLApp.readRawTextFile(App.context, R.raw.image_vert),
GLApp.readRawTextFile(App.context, R.raw.image_frag),
)
// --------------------------获取变量-------------------------------
//获取变量
positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
vCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
vTextureHandle = GLES20.glGetUniformLocation(mProgram, "vTexture");
// --------------------------顶点转buf-----------------------------
vPositionBuffer = GLApp.coordinate2Buf(floatArrayOf(
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
))
vCoordinateBuffer = GLApp.coordinate2Buf(floatArrayOf(
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
))
// --------------------------纹理-------------------------------
textureId = GLApp.createTexture()
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
//其它数据可以用 GLES20.glTexImage2D(...)
}
override fun onDrawFrame(gl: GL10) {
LogApp.d("onDrawFrame")
GLES20.glClearColor(1.0f, 0f, 0f, 1.0f)
GLES20.glUseProgram(mProgram);
//赋值坐标数据
GLApp.setCoordinate(positionHandle, vPositionBuffer!!)
GLApp.setCoordinate(vCoordinateHandle, vCoordinateBuffer!!)
//激活绑定纹理
GLApp.activeBindTexture(vTextureHandle, textureId, 0)
//绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //绘制
GLES20.glDisableVertexAttribArray(positionHandle); //禁止顶点数组的句柄
}
}
BaseRenderer.kt 如下:
abstract class BaseRenderer : GLSurfaceView.Renderer {
//总程序id
var mProgram = 0
var width = 0
var height = 0
/**
* 初始化GPU程序
* @param vertexShaderCode 顶点程序
* @param fragmentShaderCode 片元程序
*/
fun initProgram(vertexShaderCode:String, fragmentShaderCode:String){
//加载,编译, 链接
val vertexShader = GLApp.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = GLApp.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
//创建一个空的OpenGLES程序
mProgram = GLES20.glCreateProgram()
//将顶点着色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader)
//将片元着色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader)
//连接到着色器程序
GLES20.glLinkProgram(mProgram)
}
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
LogApp.d("GL onSurfaceChanged")
GLES20.glViewport(0, 0, width, height); //指定宽高
this.width = width
this.height = height
}
}
工具类 GLApp.kt
object GLApp {
//坐标 转 buf
fun coordinate2Buf(coordinatePosition: FloatArray): FloatBuffer {
val bb = ByteBuffer.allocateDirect(coordinatePosition.size * 4)
.order(ByteOrder.nativeOrder())
//将坐标数据转换为FloatBuffer,用以传入OpenGL ES程序
val coordinateBuffer = bb.asFloatBuffer()
coordinateBuffer.put(coordinatePosition)
coordinateBuffer.position(0)
return coordinateBuffer
}
/**
* 坐标赋值
* @param coordinateHandle 坐标句柄
* @param coordinateBuffer 坐标buf
*/
fun setCoordinate(coordinateHandle:Int, coordinateBuffer:FloatBuffer){
coordinateBuffer.position(0);
GLES20.glVertexAttribPointer(coordinateHandle, 2, GLES20.GL_FLOAT, false, 0, coordinateBuffer);
GLES20.glEnableVertexAttribArray(coordinateHandle);
}
//加载程序
fun loadShader(type: Int, shaderCode: String): Int {
//根据type创建顶点着色器或者片元着色器
val shader = GLES20.glCreateShader(type)
//将资源加入到着色器中,并编译
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}
//创建纹理
fun createTexture():Int{
val textures = IntArray(1)
GLES20.glGenTextures(1, textures, 0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0])
//纹理环绕方式
GLES20.glTexParameterf(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S,
GLES32.GL_CLAMP_TO_BORDER.toFloat()
)
GLES20.glTexParameterf(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T,
GLES32.GL_CLAMP_TO_BORDER.toFloat()
)
//过滤器
GLES20.glTexParameterf(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_NEAREST.toFloat()
)
GLES20.glTexParameterf(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST.toFloat()
)
return textures[0];
}
/**
* 激活并绑定纹理
* @param samplerHandle 采样器变量句柄
* @param textureId 纹理ID
* @param texturePosition 第几个纹理 0开始
*/
fun activeBindTexture(samplerHandle:Int, textureId:Int, texturePosition:Int){
GLES20.glActiveTexture(GLES20.GL_TEXTURE0+texturePosition); //激活一个纹理层(GPU内置的图层),一共32层
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); //告诉GPU 我们要使用textureId这个纹理(自己创建的)
GLES20.glUniform1i(samplerHandle, texturePosition); //我们创建的纹理 + 采样器 + GPU图层 关联起来
}
//读取res/raw下的文件
fun readRawTextFile(context: Context, rawId: Int): String {
val `is`: InputStream = context.resources.openRawResource(rawId)
val br = BufferedReader(InputStreamReader(`is`))
var line = ""
val sb = StringBuilder()
try {
while (br.readLine().also { line = it } != null) {
sb.append(line)
sb.append("\n")
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
br.close()
} catch (e: IOException) {
e.printStackTrace()
}
return sb.toString()
}
}
Activity中使用
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test2)
//设置OpenGl版本号
binding.glSurfaceView.setEGLContextClientVersion(2);
binding.glSurfaceView.setRenderer(GLImageRenderer(bitmap)) //设置一个渲染器
binding.glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY //手动渲染 自动渲染(RENDERMODE_CONTINUOUSLY)
如何取出GPU中的数据
//取出数据
val buf = FloatBuffer.allocate(width * height * 8)
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
val byteBuffer = ByteBuffer.allocate(buff.capacity()*4);
byteBuffer.asFloatBuffer().put(buff) //数据已在buf了, 如果要转成bitmap注意宽高+数据格式
网友评论