本片文章旨在把CameraX框架里相机界面预览相关的Surface
、SurfaceView
、TextureView
、SurfaceTexture
和GLSurfaceView
组件讲解清楚。
文章包括如下部分:
Surface
、SurfaceView
、TextureView
、SurfaceTexture
和GLSurfaceView
组件相关文章推荐:
- Android Camera系列文章目录索引汇总
- Android CameraX 综述
- CameraXBasic —— 官方CameraX实例源码分析
- Camera1初始化销毁流程(二) —— 初始化基本框架和CameraView几种实现方式及其伪代码
- CameraX初始化、预览、销毁源码分析和最佳实践 (一) —— 介绍和CameraX相关配置
- Android Camera理论协议和规范
关注我,随时获取CameraX更多相关资讯。
相机预览界面,相当于把物理硬件摄像头捕获到的真实3D画面显示在2D的手机界面上。如下图,具体的详细流程,可参看文章开头相关文章推荐里面第七篇。
因此,相机画面预览界面简单理解就是提供一个容器给到系统层面,使现实世界的画面通过硬件摄像头处理后,绘制在容器上面。 这里就不得不引出Android
操作系统里Surface
的概念,没错Surface
就类似这样的容器功能。
接下来的内容,涉及到通用的View细节,我们这里不在细说,感兴趣的可参看相关文件推荐里面第四篇,或者自行网上搜索 本文后续内容会重点关注
在Android生态中,无论开发者用什么渲染 API,一切内容都会渲染到 Surface 上。我们从该类的头部可以看到如下的定义:
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
*
* A Surface is generally created by or from a consumer of image buffers such as a
* {android.graphics.SurfaceTexture},
* {android.media.MediaRecorder},
* {android.renderscript.Allocation}
*
* and is handed to some kind of producer such as
* {android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
{android.media.MediaPlayer#setSurface MediaPlayer}
{android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice} to draw
* into.
可以得到如下几个信息:
也即由消费方创建Surface
发送给生产方,生产方在上面进行绘制。到此我们即可得出,我们需要做的是在UI层面创建一个CameraDisplayView。并提供相应的Surface,传给生产方,这里也就是相机的底层。
Surface
通常有2种通用的使用方式来获取:
Surface
SurfaceTexture
创建public Surface(SurfaceTexture surfaceTexture) {
if (surfaceTexture == null) {
throw new IllegalArgumentException("surfaceTexture must not be null");
}
mIsSingleBuffered = surfaceTexture.isSingleBuffered();
synchronized (mLock) {
mName = surfaceTexture.toString();
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
}
定义 : Captures frames from an image stream as an OpenGL ES texture.
可以看出SurfaceTexture来源一个图像流。接着阅读如下:
The image stream may come from either camera preview or video decode.
这个图像流,可以来源于camera preview
or video decode
。也就是说,如果要在App上实现预览摄像头的画面,参考【二】,我们只需要通过UI组件生成SurfaceTexture
然后传递给Camera
。Camera
获取摄像头捕捉到的图像流在SurfaceTexture
上绘制,从而通过UI组件显示在用户面前。
- A Surface created from a SurfaceTexture can be used as an output destination for the camera2, MediaCodec, MediaPlayer, and etc.
- A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output destination of the older android.hardware.Camera API.
可以看到SurfaceTexture
一可以用来创建Surface
;二可以用于SurfaceHolder
。
其余还有2个比较重要的方法:updateTexImage
和 getTransformMatrix(float[])
updateTexImage
用于刷新内容为图像流的最新一帧 getTransformMatrix(float[])
用于获取纹理的转换矩阵SurfaceTexture
有三个构造方法,最终都会调用到Native
层的nativeInit
方法。
private native void nativeInit(boolean isDetached, int texName,
boolean singleBufferMode, WeakReference<SurfaceTexture> weakSelf)
throws Surface.OutOfResourcesException;
**构造函数**
public SurfaceTexture(int texName) {
this(texName, false);
}
public SurfaceTexture(int texName, boolean singleBufferMode) {
mCreatorLooper = Looper.myLooper();
mIsSingleBuffered = singleBufferMode;
nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
}
public SurfaceTexture(boolean singleBufferMode) {
mCreatorLooper = Looper.myLooper();
mIsSingleBuffered = singleBufferMode;
nativeInit(true, 0, singleBufferMode, new WeakReference<SurfaceTexture>(this));
}
这里面有两个重要的参数texName
和singleBufferMode
。
texName
是OpenGL ES
的纹理Id通过调用glGenTextures
该方法会生成一个纹理Buffer,最后返回一个Id。具体的细节这里不再阐述,感兴趣的可浏览OpenGL ES目录下文章内容singleBufferMode
从命名可知,该字段用于标记是否使用单缓冲还是双缓冲。在单缓冲模式下,应用只能串行的获取图像内容缓冲。其他常见的调用方法如下,都比较简单,自己浏览即可,具体的实现最终会调用到Native
层
//Register a callback to be invoked when a new image frame becomes available to the SurfaceTexture.
public void setOnFrameAvailableListener(@Nullable final OnFrameAvailableListener listener,
@Nullable Handler handler) {
if (listener != null) {
Looper looper = handler != null ? handler.getLooper() :
mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
mOnFrameAvailableHandler = new Handler(looper, null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
listener.onFrameAvailable(SurfaceTexture.this);
}
};
} else {
mOnFrameAvailableHandler = null;
}
}
public void setDefaultBufferSize(int width, int height) {
nativeSetDefaultBufferSize(width, height);
}
/**
* Update the texture image to the most recent frame from the image stream. This may only be
* called while the OpenGL ES context that owns the texture is current on the calling thread.
* It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
*/
public void updateTexImage() {
nativeUpdateTexImage();
}
/**
* Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
* the most recent call to updateTexImage.
*
* This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
* and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
* that location from the texture. Sampling the texture outside of the range of this transform
* is undefined.
*
* The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
* the glLoadMatrixf or glUniformMatrix4fv functions.
*
* @param mtx the array into which the 4x4 matrix will be stored. The array must have exactly
* 16 elements.
*/
public void getTransformMatrix(float[] mtx) {
// Note we intentionally don't check mtx for null, so this will result in a
// NullPointerException. But it's safe because it happens before the call to native.
if (mtx.length != 16) {
throw new IllegalArgumentException();
}
nativeGetTransformMatrix(mtx);
}
伪代码,这里的createOPGLESTexture可自行百度搜索模版代码。
private void setCameraImplSurfaceTexture(int wUI, int hUI) {
int texId = createOPGLESTexture();
SurfaceTexture surfaceTexture = new SurfaceTexture(texId);
surfaceTexture.setOnFrameAvailableListener(this);
getCameraImpl().setPreviewSurfaceTexture(mSurfaceTexture, wUI, hUI);
}
也可参考TextureView里面的Demo
if (createNewSurface) {
// Create a new SurfaceTexture for the layer.
mSurface = new SurfaceTexture(false);
nCreateNativeWindow(mSurface);
}
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
核心步骤为:
new SurfaceTexture
setDefaultBufferSize
surfaceTexture.setOnFrameAvailableListener(this)
SurfaceView
是Android发布第一个版本自带的API,看其源码可知,主要功能是通过SurfaceHolder
来对Surface
进行管理,SurfaceView
源码如下:
public class SurfaceView extends MockView {
public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@Override
public boolean isCreating() {
return false;
}
@Override
public void addCallback(Callback callback) {
}
@Override
public void removeCallback(Callback callback) {
}
@Override
public void setFixedSize(int width, int height) {
}
@Override
public void setSizeFromLayout() {
}
@Override
public void setFormat(int format) {
}
@Override
public void setType(int type) {
}
@Override
public void setKeepScreenOn(boolean screenOn) {
}
@Override
public Canvas lockCanvas() {
return null;
}
@Override
public Canvas lockCanvas(Rect dirty) {
return null;
}
@Override
public void unlockCanvasAndPost(Canvas canvas) {
}
@Override
public Surface getSurface() {
return null;
}
@Override
public Rect getSurfaceFrame() {
return null;
}
};
}
接下来我们来看SurfaceHolder
,它定义了一系列的接口用于展示、监听和控制Surface
,到这里我们发现了Surface
的踪迹。
Abstract interface to someone holding a display surface. Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface.
继续浏览源码,具体到对Surface
展示、监听和控制。SurfaceHolder
首先提供了一个CallBack
用于监听Surface
的创建、更新和销毁。与此同时,也提供了获取Surface
的方法,public Surface getSurface()
,详细的CallBack
代码如下:
/**
* Add a Callback interface for this holder. There can several Callback
* interfaces associated with a holder.
*
* @param callback The new Callback interface.
*/
public void addCallback(Callback callback);
/**
* Removes a previously added Callback interface from this holder.
*
* @param callback The Callback interface to remove.
*/
public void removeCallback(Callback callback);
/**
* A client may implement this interface to receive information about
* changes to the surface. When used with a {@link SurfaceView}, the
* Surface being held is only available between calls to
* {@link #surfaceCreated(SurfaceHolder)} and
* {@link #surfaceDestroyed(SurfaceHolder)}. The Callback is set with
* {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
*/
public interface Callback {
/**
* This is called immediately after the surface is first created.
* Implementations of this should start up whatever rendering code
* they desire. Note that only one thread can ever draw into
* a {@link Surface}, so you should not draw into the Surface here
* if your normal rendering will be in another thread.
*
* @param holder The SurfaceHolder whose surface is being created.
*/
public void surfaceCreated(SurfaceHolder holder);
/**
* This is called immediately after any structural changes (format or
* size) have been made to the surface. You should at this point update
* the imagery in the surface. This method is always called at least
* once, after {@link #surfaceCreated}.
*
* @param holder The SurfaceHolder whose surface has changed.
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
/**
* This is called immediately before a surface is being destroyed. After
* returning from this call, you should no longer try to access this
* surface. If you have a rendering thread that directly accesses
* the surface, you must ensure that thread is no longer touching the
* Surface before returning from this function.
*
* @param holder The SurfaceHolder whose surface is being destroyed.
*/
public void surfaceDestroyed(SurfaceHolder holder);
}
在三个方法里,我们都可以通过返回的参数获取到Surface
然后最终传递给相机实例用于绘制。
从官网我们会发现Android在 【Api Level1】 就有了SurfaceView
,那随之就有一个问题:既然有了SurfaceView
,为什么又引入了TextureView
【API Level 14】呢?。我们已经获知SurfaceView
提供了一个可以绘制的Surface
。并通过SurfaceHolder
可以监听Surface
的创建,更新和销毁。但具体到更新,这里不包括对普通View
的缩放,动画,裁剪等操作。而且SurfaceView
是和Window
关联起来的,我们来看如下描述:
The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed.
基于此,在Api Level14的时候官方又引入了TextureView
,根据其描述。我们发现相比较SurfaceView
,TextureView
没有创建一个分离的window
,而是表现的更像是一个正常的View
。支持透明、旋转和裁剪。
Unlike SurfaceView, TextureView does not create a separate window but behaves as a regular View. This key difference allows a TextureView to have translucency, arbitrary rotations, and complex clipping. For example, you can make a TextureView semi-translucent by calling myView.setAlpha(0.5f).
但这种支持,却又牺牲了性能。
One implication of this integration of TextureView into the view hierarchy is that it may have slower performance than SurfaceView. TextureView contents must be copied, internally, from the underlying surface into the view displaying those contents.
进而我们可以得出在TextureView
和SurfaceView
之间的差异:
TextureView
支持透明、旋转和裁剪,表现和正常View
的行为是一致的。TextureView
性能比SurfaceView
要差。官网也对其性能做了进一步的描述:
For that reason(Performance), SurfaceView is recommended as a more general solution to problems requiring rendering to surfaces.
这里我们对TextureView
重要的组件和方法进行简要分析,TextureView
有3个重要的组件:
TextureLayer mLayer
SurfaceTexture mSurface
SurfaceTextureListener mListener
TextureLayer represents a SurfaceTexture that will be composited by RenderThread into the frame when drawn in a HW accelerated Canvas.
TextureView
对SurfaceTexture
的操作最终都会调用到TextureLayer
里,并传递到HardwareRenderer
渲染,或者调用Native
方法。
//native方法***
private static native boolean nPrepare(long layerUpdater, int width, int height,
boolean isOpaque);
private static native void nSetLayerPaint(long layerUpdater, long paint);
private static native void nSetTransform(long layerUpdater, long matrix);
private static native void nSetSurfaceTexture(long layerUpdater, SurfaceTexture surface);
private static native void nUpdateSurfaceTexture(long layerUpdater);
//调用Render方法
public void setSurfaceTexture(SurfaceTexture surface) {
nSetSurfaceTexture(mFinalizer.get(), surface);
mRenderer.pushLayerUpdate(this);
}
public void updateSurfaceTexture() {
nUpdateSurfaceTexture(mFinalizer.get());
mRenderer.pushLayerUpdate(this);
}
public boolean copyInto(Bitmap bitmap) {
return mRenderer.copyLayerInto(this, bitmap);
}
public boolean prepare(int width, int height, boolean isOpaque) {
return nPrepare(mFinalizer.get(), width, height, isOpaque);
}
public void setTransform(Matrix matrix) {
nSetTransform(mFinalizer.get(), matrix.native_instance);
mRenderer.pushLayerUpdate(this);
}
/**
* Indicates that this layer has lost its texture.
*/
public void detachSurfaceTexture() {
mRenderer.detachSurfaceTexture(mFinalizer.get());
}
见【2.2】
类似SurfaceView
的CallBack
方法,用于监听SurfaceTexture
的创建,更新和销毁
/**
* This listener can be used to be notified when the surface texture
* associated with this texture view is available.
*/
public static interface SurfaceTextureListener {
/**
* Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
*
* @param surface The surface returned by
* {@link android.view.TextureView#getSurfaceTexture()}
* @param width The width of the surface
* @param height The height of the surface
*/
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height);
/**
* Invoked when the {@link SurfaceTexture}'s buffers size changed.
*
* @param surface The surface returned by
* {@link android.view.TextureView#getSurfaceTexture()}
* @param width The new width of the surface
* @param height The new height of the surface
*/
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
/**
* Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
* If returns true, no rendering should happen inside the surface texture after this method
* is invoked. If returns false, the client needs to call {@link SurfaceTexture#release()}.
* Most applications should return true.
*
* @param surface The surface about to be destroyed
*/
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface);
/**
* Invoked when the specified {@link SurfaceTexture} is updated through
* {@link SurfaceTexture#updateTexImage()}.
*
* @param surface The surface just updated
*/
public void onSurfaceTextureUpdated(SurfaceTexture surface);
}
抛开一些参数设置、创建销毁、attach和detach之外,TextureView
的核心代码流程再draw()
函数里。
@Override
public final void draw(Canvas canvas) {
// NOTE: Maintain this carefully (see View#draw)
mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
if (canvas.isHardwareAccelerated()) {
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
TextureLayer layer = getTextureLayer();
if (layer != null) {
applyUpdate();
applyTransformMatrix();
mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
recordingCanvas.drawTextureLayer(layer);
}
}
}
这里提醒一下TextureView
只能在支持硬件加速的设备上使用。也即如下注释:
TextureView can only be used in a hardware accelerated window. When rendered in software, TextureView will draw nothing.
从draw
流程中可以看到有如下几个步骤:
SurfaceTexture
的TextureLayer
。SurfaceTexture
内容transformMatrix
接下来我们分别来看各个步骤,并伴随着代码注释说明:
****************************************
1. 参考2.2 首先new SurfaceTexture
2. 传递到下层,并设置Size和监听
3. 回调
4. 查最后传递给CameraSurfaceView由UI层操作
****************************************
TextureLayer getTextureLayer() {
mLayer = mAttachInfo.mThreadedRenderer.createTextureLayer();
boolean createNewSurface = (mSurface == null);
if (createNewSurface) {
// Create a new SurfaceTexture for the layer.
mSurface = new SurfaceTexture(false);
nCreateNativeWindow(mSurface);
}
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
if (mListener != null && createNewSurface) {
mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
}
mLayer.setLayerPaint(mLayerPaint);
}
if (mUpdateSurface) {
mUpdateSurface = false;
updateLayer();
mMatrixChanged = true;
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
}
return mLayer;
}
****************************************
1. 设置新参数
2. updateSurfaceTexture更新
3. 最总调用到TextureLayer里,从而调用Native方法
****************************************
private void applyUpdate() {
if (mLayer == null) {
return;
}
synchronized (mLock) {
if (mUpdateLayer) {
mUpdateLayer = false;
} else {
return;
}
}
mLayer.prepare(getWidth(), getHeight(), mOpaque);
mLayer.updateSurfaceTexture();
if (mListener != null) {
mListener.onSurfaceTextureUpdated(mSurface);
}
}
private void applyTransformMatrix() {
if (mMatrixChanged && mLayer != null) {
mLayer.setTransform(mMatrix);
mMatrixChanged = false;
}
}
这里提供2个简单的实践Demo以供参考:
可以看到这里TextureView相比较【2.2】只执行了第四步,在onSurfaceTextureAvailable
的回调中把SurfaceTexture
传递给Camera
public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
private Camera mCamera;
private TextureView mTextureView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
setContentView(mTextureView);
}
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mCamera = Camera.open();
try {
mCamera.setPreviewTexture(surface);
mCamera.startPreview();
} catch (IOException ioe) {
// Something bad happened
}
}
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// Ignored, Camera does all the work for us
}
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
mCamera.stopPreview();
mCamera.release();
return true;
}
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// Invoked every time there's a new Camera preview frame
}
}
同样在onSurfaceTextureAvailable
的回调中把SurfaceTexture
传递给MediaPlayer.
public class MyActivity extends Activity implements TextureView.SurfaceTextureListener {
private MediaPlayer mMediaPlayer;
private TextureView mTextureView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMediaPlayer = new MediaPlayer();
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
setContentView(mTextureView);
}
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture,
int width, int height) {
AssetFileDescriptor fileDescriptor = // get file descriptor
mMediaPlayer.setDataSource(fileDescriptor);
mMediaPlayer.setSurface(new Surface(surfaceTexture));
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture,
int width, int height) {
// Handle size change depending on media needs
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
// Release unneeded resources
mMediaPlayer.stop();
mMediaPlayer.release();
return true;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
// Invoked every time there's a new video frame
}
}
GLSurfaceView
涉及到OpenGL ES
相关的开发,需要有相关OpenGL ES
的基础知识,详细的流程可参考官方文档和Develop Guide
。这里就不做赘述。
我们这里主要来说说在SurfaceView
和TextureView
存在的前提下GLSurfaceView
的使用场景。
如果去看官方的Demo和开源项目,会发现基本上都使用SurfaceView
或者TextureView
【根据不同场景具体区分】,如果你只是简单的开发一个相机APP用于拍照,选择SurfaceView
完全足够。 但如果你有如下CLSurfaceView
官网上说明提供的支持的诉求。则可以选择CLSurfaceView
。
- Manages a surface, which is a special piece of memory that can be composited into the Android view system.
- Manages an EGL display, which enables OpenGL to render into a surface.
- Accepts a user-provided Renderer object that does the actual rendering.
- Renders on a dedicated thread to decouple rendering performance from the UI thread.
- Supports both on-demand and continuous rendering.
- Optionally wraps, traces, and/or error-checks the renderer’s OpenGL calls.
SurfaceView
和TextureView
获取自带的Surface
或者SurfaceTexture
,传递给Camera
绘制,如果你想要在上面额外绘制一些特效,则不是一个比较好的UI组件。TextureView
里注释也有提到如下:
It is important to note that only one producer can use the TextureView. if you use a TextureView to display the camera preview, you cannot use {@link #lockCanvas()} to draw onto the TextureView at the same time.
而GLSurfaceView
则提供了OpenGL ES的环境。可以在SurfaceTexture
上绘制自己想要的特效纹理。再结合相机预览的图像流进行更多的扩展。具体的简单源码分析可参看如下文章: Android GLSurfaceView详解。感兴趣的同学可自行学习OpenGL ES相关的开发知识,如果只局限在相机层面的功能。 SurfaceView
和TextureView
则足够使用了。
Surface
或者SurfaceTexture
并传递给Camera
实例SurfaceTexture
一可以用来创建Surface
;二可以用于SurfaceHolder
。SurfaceView
,TextureView
支持透明、旋转和裁剪,表现和正常View
的行为是一致的。但 TextureView
性能比SurfaceView
要差。SurfaceView
完全足够。如果需要额外在相机预览图像流上做额外绘制,则GLSurfaceView
是比较好的选择。但需要OpenGL ES
相关的基础知识。