1. 核心概念
SurfaceTexture
是 Android 中的一个类,它允许你从某些图像源(例如相机预览)捕获帧,并将这些帧作为纹理(Texture)渲染到 OpenGL ES 环境中。它通常用于需要在自定义渲染管道中显示摄像头预览的场景。
- 纹理(Texture):
SurfaceTexture
将图像帧作为 OpenGL ES 纹理,方便与OpenGL
进行高效的图像处理。 - 帧缓冲区(Frame Buffer):
SurfaceTexture
内部维护着一个帧缓冲区,从源(如相机)接收帧。 - OpenGL 与 SurfaceTexture 结合:通常,
SurfaceTexture
会与 OpenGL 一起使用,以便更高效地渲染和处理图像帧。
2. 主要功能
- 用于相机预览:常用于相机 API 中的自定义预览(比如将相机帧直接传输到 OpenGL 纹理中)。
- 低延迟处理:与
OpenGL
结合时,它能够实现低延迟的图像流处理。 - 同步处理:提供了对帧同步的支持,通过
updateTexImage()
同步纹理。
3. 关键方法
1. 构造方法
SurfaceTexture(int texName)
:创建一个 SurfaceTexture,并将其与指定的 OpenGL ES 纹理 ID 绑定(texName
是纹理的句柄)。
2. 绑定 SurfaceTexture 与 Surface
Surface getSurface()
:获取与此SurfaceTexture
关联的Surface
对象,可以将其传递给相机或视频解码器等,用于图像捕获。setDefaultBufferSize(int width, int height)
:设置SurfaceTexture
的默认缓冲区大小,这通常是预览帧的大小。
3. 处理图像帧
updateTexImage()
:从缓冲区中更新当前的纹理图像。这个方法通常在每一帧图像到来时调用,确保纹理内容被更新。getTimestamp()
:获取当前纹理图像的时间戳,通常用于同步图像帧和其他信号。release()
:释放SurfaceTexture
占用的资源,通常在SurfaceTexture
不再需要时调用。
4. 监听帧更新
setOnFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener listener)
:设置帧可用时的监听器。当新的图像帧可用时,系统会调用此监听器。
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// 在新的图像帧到达时回调
}
});
4. 使用流程
1. 创建 SurfaceTexture 对象
首先,通过创建 SurfaceTexture
并传递一个 OpenGL 纹理 ID 来初始化它。
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0); // 创建一个 OpenGL 纹理
SurfaceTexture surfaceTexture = new SurfaceTexture(texture[0]); // 绑定这个纹理
2. 设置默认缓冲区大小
接下来,通常会设置 SurfaceTexture
的默认缓冲区大小,通常是相机预览的分辨率大小。
surfaceTexture.setDefaultBufferSize(1920, 1080); // 设置为 1920x1080 的分辨率
3. 获取 Surface 对象
你可以通过 SurfaceTexture.getSurface()
获取 Surface
对象,并将其传递给相机进行图像流的捕获。
Surface surface = new Surface(surfaceTexture);
camera.setPreviewSurface(surface); // 将 Surface 传递给相机
4. 同步纹理图像
在每一帧到达时,你需要调用 updateTexImage()
来同步纹理,并将其更新到 OpenGL 的纹理缓冲区中。
surfaceTexture.updateTexImage(); // 更新纹理图像
5. 释放资源
在不需要 SurfaceTexture
时,调用 release()
方法来释放它占用的资源。
surfaceTexture.release(); // 释放资源
5. 完整的示例代码
下面是一个完整的示例,展示如何使用 SurfaceTexture
来处理相机预览,并将其作为 OpenGL 纹理渲染。
public class MyGLRenderer implements GLSurfaceView.Renderer {
private SurfaceTexture surfaceTexture;
private int[] textures;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 创建 OpenGL 纹理
textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
// 创建 SurfaceTexture 并绑定到 OpenGL 纹理
surfaceTexture = new SurfaceTexture(textures[0]);
// 设置帧可用监听器
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// 当新的帧可用时处理
}
});
// 设置默认缓冲区大小
surfaceTexture.setDefaultBufferSize(1920, 1080);
}
@Override
public void onDrawFrame(GL10 gl) {
// 更新纹理内容
surfaceTexture.updateTexImage();
// 继续绘制
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
// 渲染代码
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
6. 典型应用场景
- 相机预览:可以将相机预览直接显示到
GLSurfaceView
上。 - 视频解码:可以用
SurfaceTexture
来渲染解码后的视频帧到自定义 OpenGL 表面。 - 图像处理:通过将图像帧渲染到 OpenGL 中,进行高效的图像处理和特效渲染。
总结
SurfaceTexture
提供了从视频源(如相机)到 OpenGL 纹理的高效转换,使得图像流可以被更灵活地处理和渲染。在使用 OpenGL 进行渲染时,它是处理视频流的核心工具之一。
TexturePreview与预览绑定:
@NonNull
private OutputConfiguration getOutputConfiguration(SurfaceTexture surfaceTexture, Size previewSize) {
OutputConfiguration texPreviewReader = null;
if (surfaceTexture == null) {
texPreviewReader = new OutputConfiguration(previewSize, SurfaceTexture.class);
} else {
final Surface surface = new Surface(surfaceTexture);
texPreviewReader = new OutputConfiguration(surface);
if (mSurface != null) {
mSurface.release();
}
mSurface = surface;
}
return texPreviewReader;
}
public Surface configPreview(CameraCaptureSession captureSession,
CaptureRequest.Builder previewRequestBuilder,
List<OutputConfiguration> outputConfigs,
SurfaceTexture surfaceTexture) {
LogUtil.v(TAG, "configPreview: ");
Surface texturePreviewSurface = null;
try {
if (captureSession ==null || outputConfigs == null || surfaceTexture == null) {
return null;
}
boolean needFinalizeOutputConfig = false;
for (OutputConfiguration outputConfig : outputConfigs) {
Surface surface = outputConfig.getSurface();
if (surface == null) {
texturePreviewSurface = new Surface(surfaceTexture);
outputConfig.addSurface(texturePreviewSurface);
needFinalizeOutputConfig = true;
break;
}
}
if (needFinalizeOutputConfig) {
captureSession.finalizeOutputConfigurations(outputConfigs);
}
} catch (CameraAccessException | IllegalStateException e) {
LogUtil.e(TAG, "finalizePreviewOutputConfigs", e);
}
if (texturePreviewSurface != null) {
if (mSurface != null) {
mSurface.release();
}
mSurface = texturePreviewSurface;
}
previewRequestBuilder.addTarget(mSurface);
return mSurface;
}
texturePreviewSurface = new Surface(surfaceTexture);
outputConfig.addSurface(texturePreviewSurface);
mSurface = texturePreviewSurface;
previewRequestBuilder.addTarget(mSurface);
两者用通过surfaceTexture 进行绑定,相机获取到的图像直接传送到SurfaceTexture
内部维护着一个帧缓冲区。
然后在每一帧到达后需要用updateTexImage()
来同步纹理,并将其更新到 OpenGL 的纹理缓冲区中。
protected void updateSurfaceTextureImage() {
try {
mSurfaceTexture.updateTexImage();
} catch (RuntimeException e) {
LogUtil.e(TAG, "updateTexImage failed, e = ", e);
}
}
进阶:预览图像算法处理后在显示
要是我们需要将图片处理一遍后在显示。那么就不能直接将surfaceTexture获得的surface与相机输出流进行绑定了。
不然我们就无法拿到image 来操作image,只能在这里处理纹理,不是我们想要的。(简单的滤镜可以在这里渲染)
@Override
public void onDrawFrame(GL10 gl) {
// 更新纹理内容
surfaceTexture.updateTexImage();
// 继续绘制
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
// 渲染代码
}
所以我们应该需要怎么操作,此时有个关键的类ImageWriter(之前的文章有过介绍,这里就不过多的解释了)中的queueInputImage方法:
public void queueInputImage(Image image) {}
mImageWriter.queueInputImage(image);
queueInputImage(image)
的作用是将一个 Image
对象传递给 ImageWriter
,并将其插入到 ImageWriter
的缓冲队列中。这个方法会将 Image
添加到队列里,等待将图像写入到它关联的 Surface
上。
上面方法可以将处理后的image 添加到suerface中的帧缓存区中,那么我们在使用ImageWriter.newInstance 使之与surfaceTexture进行绑定
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
@IntRange(from = 1) int maxImages, @Format int format);
mImageWriter = ImageWriter.newInstance(surfaceTexture, mYuvPreview.getMaxImage(), ImageFormat.YUV_420_888);
示例:(假设已经有了yuvpreview)
final Surface surface = new Surface(surfaceTexture);
mImageWriter = ImageWriter.newInstance(surface, mYuvPreview.getMaxImage(), ImageFormat.YUV_420_888);
mYuvPreview.configPreview(previewRequestBuilder, new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
final Image image = reader.acquireLatestImage();
if (image != null) {
if (mImageWriter != null) {
int orientation = mModuleReference.getSettingProxy().getDisplayOrientation();
if (isFrontCam()) {
switch (orientation % 360) {
case 0: orientation = 270; break;
case 90: orientation = 180; break;
case 180: orientation = 90; break;
case 270: orientation = 0; break;
}
} else {
orientation += 90;
}
int imageSize = image.getPlanes()[0].getRowStride() * image.getHeight();
int yuvSize = imageSize * 3 / 2;
BeautyShotUtil.ProcessPreview(image.getPlanes()[0].getBuffer(),
image.getPlanes()[1].getBuffer(), image.getPlanes()[2].getBuffer(),
null, false, image.getWidth(), image.getHeight(),
image.getPlanes()[0].getRowStride(), image.getPlanes()[1].getRowStride(),
image.getPlanes()[1].getPixelStride(), orientation);
mImageWriter.queueInputImage(image);
} else {
image.close();
}
}
}
}, mPreviewHandler);