OpenGL ES (17): 使用GLSurfaceView預覽Camera,並拍照
1.許可權
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Tell the system this app requires OpenGL ES 2.0. --> <uses-feature android:glEsVersion="0x00020000" android:required="true" />
2.思路-流程
預覽Camera我們以前是使用SurfaceView得到SurfaceHolder , 然後myCamera.setPreviewDisplay(SurfaceHolder),詳細例子見文章
但是我們這次使用的是OpenGLES中的 GLSurfaceView 來預覽Camera,說這個之前。我們先來說一說 紋理貼圖吧!
之前紋理貼圖的思路是什麼?
- 得到GLSurfaceView表面的紋理id
- 把圖片生成在對應的紋理id上面,並返回這個紋理id。
- 這樣圖片就和這個紋理id綁定了,但是還不知道紋理是如何繪製出來的。
- 所以需要傳入紋理座標,跟頂點座標一一對應。
- 然後繪製,就形成了紋理貼圖。
這次我們講的GLSurfaceView預覽Camera其實跟紋理貼圖的思路差不多。可以理解成在 GLSurfaceView上畫一個全屏大小的矩形,
把圖片換成了相機Camera資料,把Camera資料 紋理貼圖到 這個畫的矩形上面,就形成了預覽,是不是瞬間秒懂!
只不過還多了一個東西叫 SurfaceTexture ,這個東西是什麼?
我把它理解為 紋理層,相當於 view表面的一層衣服,它與GLSurfaceView的表面紋理id 掛鉤。
然後執行Camera.setPreviewTexture(SurfaceTexture)。相當於之前的 Camera.setPrieviewHolder(surfaceHolder)。
然後執行camera.startPrieView()就可以開始預覽了。沒結束,還要看下面的。
這個紋理層還需要設定一個監聽SurfaceTexture.setOnFrameAvailableListener(this);監聽相機傳來的新的流幀,一旦紋理層有新的資料,就要通知GLSurfaceView進行重繪。GLSurfaceView使用髒模式 setRenderMode(RENDERMODE_WHEN_DIRTY) 。
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//回撥介面,用於通知新的流幀可用
//紋理層有新資料,就通知view繪製
this.requestRender();
}
這樣下來,使用GLSurfaceView預覽相機Camera是不是變的很簡單!
使用相機拍照還是跟之前一樣!設定3個引數,第3個非常重要。
mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
ShutterCallback mShutterCallback = new ShutterCallback()
//如果這個引數設定為Null,將沒有卡擦的聲音
{
public void onShutter() {
}
};
PictureCallback mJpegPictureCallback = new PictureCallback()
//對jpeg影象資料的回撥,最重要的一個回撥
{
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap b = null;
if(null != data){
b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是位元組資料,將其解析成點陣圖
mCamera.stopPreview();
isPreviewing = false;
}
//儲存圖片到sdcard
if(null != b)
{
//圖片這裡要旋轉下,相機拍出來的照片是倒著的
Bitmap rotaBitmap = getRotateBitmap(b, 90.0f);
saveBitmap(rotaBitmap);
}
//再次進入預覽
mCamera.startPreview();
isPreviewing = true;
}
};
3.具體程式碼
- MainActivity.java
- CameraGLSurfaceView.java (實現了GLSurfaceView介面,Renderer介面,SurfaceTexture.OnFrameAvailableListener紋理層監聽介面)
- DirectDrawer.java (一旦需要繪製,在GLSurfaceView中的onDrawFrame()方法中呼叫DirectDrawer.draw()進行繪製 )
- CameraInterface.java (相機Camera封裝類,包含開啟、預覽、關閉)
MainActivity.java
public class MainActivity extends Activity{
private static final String TAG = "TAG";
CameraGLSurfaceView glSurfaceView = null;
ImageButton shutterBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glSurfaceView = (CameraGLSurfaceView)findViewById(R.id.camera_textureview);
shutterBtn = (ImageButton)findViewById(R.id.btn_shutter);
initViewParams();
//拍照
shutterBtn.setOnClickListener(new BtnListeners());
}
private void initViewParams(){
LayoutParams params = glSurfaceView.getLayoutParams();
Point p = getScreenMetrics(this);
params.width = p.x; //view寬
params.height = p.y; //view高
//設定GLSurfaceView的寬和高
glSurfaceView.setLayoutParams(params);
//設定ImageButton的大小
LayoutParams p2 = shutterBtn.getLayoutParams();
p2.width = 100;
p2.height = 100;
shutterBtn.setLayoutParams(p2);
}
private Point getScreenMetrics(Context context){
DisplayMetrics dm =context.getResources().getDisplayMetrics();
int w_screen = dm.widthPixels;
int h_screen = dm.heightPixels;
return new Point(w_screen, h_screen);
}
//拍照
private class BtnListeners implements OnClickListener{
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btn_shutter:
CameraInterface.getInstance().doTakePicture();
break;
default:break;
}
}
}
@Override
protected void onResume() {
super.onResume();
//更改檢視在樹中的z順序,因此它位於其他同級檢視之上。
glSurfaceView.bringToFront();
}
@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause();
}
}
CameraGLSurfaceView.java
public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener {
private static final String TAG = "TAG";
Context mContext;
//以OpenGL ES紋理的形式從影象流中捕獲幀,我把叫做紋理層
SurfaceTexture mSurface;
//使用的紋理id
int mTextureID = -1;
DirectDrawer mDirectDrawer;
public CameraGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setEGLContextClientVersion(2);
setRenderer(this);
//根據紋理層的監聽,有資料就繪製
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//得到view表面的紋理id
mTextureID = createTextureID();
//使用這個紋理id得到紋理層SurfaceTexture
mSurface = new SurfaceTexture(mTextureID);
//監聽紋理層
mSurface.setOnFrameAvailableListener(this);
mDirectDrawer = new DirectDrawer(mTextureID);
//開啟相機,並未預覽
CameraInterface.getInstance().doOpenCamera();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
//如果還未預覽,就開始預覽
if(!CameraInterface.getInstance().isPreviewing()){
CameraInterface.getInstance().doStartPreview(mSurface);
}
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//從影象流中將紋理影象更新為最近的幀
mSurface.updateTexImage();
mDirectDrawer.draw();
}
@Override
public void onPause() {
super.onPause();
CameraInterface.getInstance().doStopCamera();
}
private int createTextureID() {
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//回撥介面,用於通知新的流幀可用。
Log.i(TAG, "onFrameAvailable...");
//紋理層有新資料,就通知view繪製
this.requestRender();
}
}
CameraInterface.java
public class CameraInterface {
private Camera mCamera;
private Camera.Parameters mParams;
private boolean isPreviewing = false;
private static CameraInterface mCameraInterface;
private CameraInterface(){
}
public static synchronized CameraInterface getInstance(){
if(mCameraInterface == null){
mCameraInterface = new CameraInterface();
}
return mCameraInterface;
}
//開啟相機
public void doOpenCamera(){
if(mCamera == null){
mCamera = Camera.open();
}else{
doStopCamera();
}
}
/*使用TextureView預覽Camera*/
public void doStartPreview(SurfaceTexture surface){
if(isPreviewing){
mCamera.stopPreview();
return;
}
if(mCamera != null){
try {
//將相機畫面預覽到紋理層上,紋理層有資料了,再通知view繪製,此時未開始預覽
mCamera.setPreviewTexture(surface);
} catch (IOException e) {
e.printStackTrace();
}
//真正開啟預覽,Camera.startPrieView()
initCamera();
}
}
/**
* 停止預覽,釋放Camera
*/
public void doStopCamera(){
if(null != mCamera)
{
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
isPreviewing = false;
mCamera.release();
mCamera = null;
}
}
/**
* 拍照
*/
public void doTakePicture(){
if(isPreviewing && (mCamera != null)){
mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
}
}
public boolean isPreviewing(){
return isPreviewing;
}
private void initCamera(){
if(mCamera != null){
mParams = mCamera.getParameters();
mParams.setPictureFormat(PixelFormat.JPEG);//設定拍照後儲存的圖片格式
mCamera.setDisplayOrientation(90);
//設定攝像頭為持續自動聚焦模式
mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
mCamera.setParameters(mParams);
mCamera.startPreview();//開啟預覽
//設定預覽標誌位
isPreviewing = true;
}
}
ShutterCallback mShutterCallback = new ShutterCallback()
//如果這個引數設定為Null,將沒有卡擦的聲音
{
public void onShutter() {
}
};
PictureCallback mJpegPictureCallback = new PictureCallback()
//對jpeg影象資料的回撥,最重要的一個回撥
{
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap b = null;
if(null != data){
b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是位元組資料,將其解析成點陣圖
mCamera.stopPreview();
isPreviewing = false;
}
//儲存圖片到sdcard
if(null != b)
{
//圖片這裡要旋轉下,相機拍出來的照片是倒著的
Bitmap rotaBitmap = getRotateBitmap(b, 90.0f);
saveBitmap(rotaBitmap);
}
//再次進入預覽
mCamera.startPreview();
isPreviewing = true;
}
};
//旋轉圖片
private Bitmap getRotateBitmap(Bitmap b, float rotateDegree){
Matrix matrix = new Matrix();
matrix.postRotate((float)rotateDegree);
return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);
}
private static String initPath(){
String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/" + "PlayCamera";
File f = new File(storagePath);
if(!f.exists()){
f.mkdir();
}
return storagePath;
}
private void saveBitmap(Bitmap b){
String path = initPath();
String jpegName = path + "/" + System.currentTimeMillis() +".jpg";
try {
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(jpegName));
b.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最後我們來看 DirectDrawer.java中的程式碼:
頂點座標陣列為:
static float squareCoords[] = { -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, };
但是紋理座標陣列為:
static float textureVertices[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, };
為什麼不是:
static float textureVertices[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, };
我們看下面一張圖解釋:
因為攝像頭Camera自己旋轉了的原因,實際上對應頂點(-1,1)的位置在(0,1)處,而不在(0,0)處,所以。。。
我們的繪製順序是012023.
具體程式碼如下:
public class DirectDrawer {
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 inputTextureCoordinate;" +
"varying vec2 textureCoordinate;" +
"void main()" +
"{"+
"gl_Position = vPosition;"+
"textureCoordinate = inputTextureCoordinate;" +
"}";
private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n"+
"precision mediump float;" +
"varying vec2 textureCoordinate;\n" +
"uniform samplerExternalOES s_texture;\n" +
"void main() {" +
" gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
"}";
private FloatBuffer vertexBuffer, textureVerticesBuffer;
private ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mTextureCoordHandle;
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 };
private static final int COORDS_PER_VERTEX = 2;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
static float squareCoords[] = {
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
};
static float textureVertices[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
private int texture;
public DirectDrawer(int texture)
{
this.texture = texture;
//頂點座標
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
//頂點繪製順序
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
//紋理座標
ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
bb2.order(ByteOrder.nativeOrder());
textureVerticesBuffer = bb2.asFloatBuffer();
textureVerticesBuffer.put(textureVertices);
textureVerticesBuffer.position(0);
//編譯著色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
}
public void draw()
{
GLES20.glUseProgram(mProgram);
//使用紋理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
//頂點位置
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//紋理座標
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureVerticesBuffer);
//繪製
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
//結束
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
}
//編譯著色器
private int loadShader(int type, String shaderCode){
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
最終效果如圖,拍照照片在sd卡的PlayCamera資料夾中: