Android 示例:YUV 转 RGB

这个示例实现了一个简单的 OpenGL ES 2.0 渲染器,用于将 YUV420P 格式的图像帧转换为 RGB 并显示在屏幕上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package com.cmder.yuvgl

import android.content.Context
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class YuvToRgbRenderer(private val context: Context) : GLSurfaceView.Renderer {

// Vertex shader source code (GLSL)
// 顶点着色器处理全屏四边形的顶点坐标(aPosition)和纹理坐标(aTexCoord),通过uMVPMatrix变换顶点位置,传递纹理坐标给片段着色器。
private val vertexShaderCode = """
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform mat4 uMVPMatrix;

void main() {
gl_Position = uMVPMatrix * aPosition;
vTexCoord = aTexCoord;
}
""".trimIndent()

// Fragment shader source code (GLSL) for YUV420P to RGB conversion
// Assumes three separate textures: Y (luminance), U (chroma blue), V (chroma red)
// For other formats like NV21, adjust the sampling accordingly (e.g., VU interleaved).
// 片段着色器从Y、U、V纹理中采样数据,应用BT.601标准矩阵将YUV转换为RGB颜色值,输出到gl_FragColor。
private val fragmentShaderCode = """
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D yTexture;
uniform sampler2D uTexture;
uniform sampler2D vTexture;

void main() {
float y = texture2D(yTexture, vTexCoord).r;
float u = texture2D(uTexture, vTexCoord).r - 0.5;
float v = texture2D(vTexture, vTexCoord).r - 0.5;

// YUV to RGB conversion matrix (BT.601 standard)
float r = y + 1.402 * v;
float g = y - 0.344136 * u - 0.714136 * v;
float b = y + 1.772 * u;

gl_FragColor = vec4(r, g, b, 1.0);
}
""".trimIndent()

// Program handle
private var program: Int = 0

// Handles for attributes and uniforms
private var positionHandle: Int = 0
private var texCoordHandle: Int = 0
private var mvpMatrixHandle: Int = 0
private var yTextureHandle: Int = 0
private var uTextureHandle: Int = 0
private var vTextureHandle: Int = 0

// Texture IDs
private var yTextureId: Int = 0
private var uTextureId: Int = 0
private var vTextureId: Int = 0

// MVP matrix
private val mvpMatrix = FloatArray(16)

// Vertex data: positions and texture coordinates for a full-screen quad
private val vertexData = floatArrayOf(
-1f, -1f, 0f, 0f, 1f, // Bottom-left
1f, -1f, 0f, 1f, 1f, // Bottom-right
-1f, 1f, 0f, 0f, 0f, // Top-left
1f, 1f, 0f, 1f, 0f // Top-right
)
private lateinit var vertexBuffer: FloatBuffer

// Example YUV data dimensions (adjust based on your input)
private var width: Int = 640
private var height: Int = 480

// Example YUV buffers (in real use, fill these from Camera or Video source)
private lateinit var yBuffer: ByteBuffer
private lateinit var uBuffer: ByteBuffer
private lateinit var vBuffer: ByteBuffer

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// Compile and link shaders
// 编译并链接顶点和片段着色器,创建OpenGL程序。
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
program = GLES20.glCreateProgram()
GLES20.glAttachShader(program, vertexShader)
GLES20.glAttachShader(program, fragmentShader)
GLES20.glLinkProgram(program)

// Get handles
// 获取着色器中的属性和统一变量句柄(如aPosition、yTexture等)。
positionHandle = GLES20.glGetAttribLocation(program, "aPosition")
texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord")
mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix")
yTextureHandle = GLES20.glGetUniformLocation(program, "yTexture")
uTextureHandle = GLES20.glGetUniformLocation(program, "uTexture")
vTextureHandle = GLES20.glGetUniformLocation(program, "vTexture")

// Initialize vertex buffer
// 初始化顶点缓冲区,定义一个全屏四边形(2个三角形组成)。
vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData)
.apply { position(0) }

// Create textures
// 创建三个纹理(Y、U、V),分别绑定到GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2。
val textureIds = IntArray(3)
GLES20.glGenTextures(3, textureIds, 0)
yTextureId = textureIds[0]
uTextureId = textureIds[1]
vTextureId = textureIds[2]

// Bind and set textures (initially empty, update in draw)
bindTexture(yTextureId, GLES20.GL_TEXTURE0)
bindTexture(uTextureId, GLES20.GL_TEXTURE1)
bindTexture(vTextureId, GLES20.GL_TEXTURE2)

// Initialize example YUV buffers (in real app, update these dynamically)
initializeYuvBuffers()

// Set orthographic projection
// 设置单位矩阵作为MVP变换。
Matrix.setIdentityM(mvpMatrix, 0)
}

override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 根据屏幕尺寸设置OpenGL视口,确保渲染区域匹配屏幕。
GLES20.glViewport(0, 0, width, height)
}

override fun onDrawFrame(gl: GL10?) {
// 清空屏幕,激活着色器程序。
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
GLES20.glUseProgram(program)

// Update textures with YUV data
updateTextures()

// Set uniforms
// 设置统一变量(MVP矩阵和纹理单元)。
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0)
GLES20.glUniform1i(yTextureHandle, 0)
GLES20.glUniform1i(uTextureHandle, 1)
GLES20.glUniform1i(vTextureHandle, 2)

// Enable attributes
// 绑定顶点和纹理坐标数据,绘制四边形(GL_TRIANGLE_STRIP)。
val stride = 5 * 4 // 5 floats per vertex * 4 bytes
vertexBuffer.position(0)
GLES20.glVertexAttribPointer(
positionHandle,
3,
GLES20.GL_FLOAT,
false,
stride,
vertexBuffer
)
GLES20.glEnableVertexAttribArray(positionHandle)

vertexBuffer.position(3)
GLES20.glVertexAttribPointer(
texCoordHandle,
2,
GLES20.GL_FLOAT,
false,
stride,
vertexBuffer
)
GLES20.glEnableVertexAttribArray(texCoordHandle)

// Draw quad
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

// Disable attributes
// 禁用顶点属性,完成一帧渲染。
GLES20.glDisableVertexAttribArray(positionHandle)
GLES20.glDisableVertexAttribArray(texCoordHandle)
}

/**
* 编译GLSL着色器代码。
*/
private fun loadShader(type: Int, shaderCode: String): Int {
val shader = GLES20.glCreateShader(type)
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}

/**
* 配置纹理参数(如线性过滤和边缘处理)。
*/
private fun bindTexture(textureId: Int, unit: Int) {
GLES20.glActiveTexture(unit)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE
)
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE
)
}

/**
* 创建测试用的YUV数据缓冲区。
* 初始化YUV缓冲区,填充测试用的灰色数据(Y=128, U=128, V=128)。
*/
private fun initializeYuvBuffers() {
// Example: Allocate buffers (in real use, get from CameraPreview or MediaCodec)
yBuffer = ByteBuffer.allocateDirect(width * height)
uBuffer = ByteBuffer.allocateDirect(width * height / 4)
vBuffer = ByteBuffer.allocateDirect(width * height / 4)

// Fill with dummy data for testing (e.g., gray image)
yBuffer.put(ByteArray(width * height) { 128.toByte() })
uBuffer.put(ByteArray(width * height / 4) { 128.toByte() })
vBuffer.put(ByteArray(width * height / 4) { 128.toByte() })
yBuffer.position(0)
uBuffer.position(0)
vBuffer.position(0)
}

/**
* 将YUV数据上传到纹理(Y全尺寸,U/V半尺寸)。
* 更新Y、U、V纹理数据。
*/
private fun updateTextures() {
// Update Y texture
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yTextureId)
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_2D,
0,
GLES20.GL_LUMINANCE,
width,
height,
0,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
yBuffer
)

// Update U texture (half size)
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, uTextureId)
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_2D,
0,
GLES20.GL_LUMINANCE,
width / 2,
height / 2,
0,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
uBuffer
)

// Update V texture (half size)
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, vTextureId)
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_2D,
0,
GLES20.GL_LUMINANCE,
width / 2,
height / 2,
0,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
vBuffer
)
}

// In real app, call this to update YUV data from external source
/**
* 允许外部更新YUV数据和尺寸(实际应用中从相机或视频解码器获取)。
*/
fun updateYuvData(
newY: ByteArray,
newU: ByteArray,
newV: ByteArray,
newWidth: Int,
newHeight: Int
) {
width = newWidth
height = newHeight
yBuffer = ByteBuffer.wrap(newY)
uBuffer = ByteBuffer.wrap(newU)
vBuffer = ByteBuffer.wrap(newV)
}
}

项目地址:https://github.com/cmder/Demos/tree/main/YUVGL