预览Camera我们以前是使用SurfaceView得到SurfaceHolder , 然后myCamera.setPreviewDisplay(SurfaceHolder),详细例子见文章
相机Camera(预览--拍照)--两种方式(包含横竖屏切换两种方式) 。
但是我们这次使用的是OpenGLES中的 GLSurfaceView 来预览Camera,说这个之前。我们先来说一说 纹理贴图吧!
之前纹理贴图的思路是什么?
这次我们讲的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;
}
};
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文件夹中: