做视频开发就需要用到Camera进行采集数据,关于Camera的用法就不多说了,如有兴趣的可以到Camera官网学习。
更加详细的SurfaceView,可以到Android音视频技术入门之绘制一张图片中了解。
TextureView是Android 4.0之后加入的。TextureView必须工作在开启硬件加速的环境中,也即配置文件里Activity的设置项里:android:hardwareAccelerated="true" 默认的这个属性就是true,因此不用再写了。但如果写成false,onSurfaceTextureAvailable()这个回调就进不来了。
Camera采集数据
-
打开摄像头
mCamera = Camera.open();
-
设置摄像头的预览数据界面
预览一般有两种方式,一种是调用setPreviewDisplay方法设置SurfaceHolder,也就是和SurfaceView进行绑定了,还有一种就是调用setPreviewTexture方法设置SurfaceTexture的,这个就和GLSurfaceView以及TextureView绑定了
-
获取到Camera.Parameters参数信息
Camera.Parameters parameters = mCamera.getParameters(); //获取摄像头参数 可以根据情况设置参数
-
在把添加好的参数信息设置回去,调用startPreview开始预览效果了
mCamera.startPreview();
-
释放摄像头
mCamera.release();
注意 Camera用完了之后一定要释放掉,不然别的地方调用不到相机的。
用SurfaceView预览Camera的数据
public class SurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private SurfaceView mSurfaceView;
private Camera mCamera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏
setContentView(R.layout.activity_surface_view);
mSurfaceView = findViewById(R.id.surface_view);
mSurfaceView.getHolder().addCallback(this);
mSurfaceView.setKeepScreenOn(true);
// 打开摄像头并将展示方向旋转90度
camera = Camera.open();
camera.setDisplayOrientation(90);
}
//------ Surface 预览 -------
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
//在控件创建的时候,进行相应的初始化工作
try {
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();//开始预览
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
//变化时,可以做相应操作
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mCamera.release();
}
}
用 TextureView 来预览 Camera 数据
public class TextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener{
TextureView textureView;
Camera camera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_texture_view);
textureView = findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(this);
// 打开摄像头并将展示方向旋转90度
camera = Camera.open();
camera.setDisplayOrientation(90);
}
//------ Texture 预览 -------
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
try {
camera.setPreviewTexture(surfaceTexture);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
camera.release();//释放相机
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
}
取到 NV21 的数据回调
Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是YV12。Android一般默认使用YCbCr_420_SP的格式(NV21)。可以配置数据回调的格式。如下:
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
camera.setParameters(parameters);
通过setPreviewCallback方法监听预览的回调:
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
//这里面的Bytes的数据就是NV21格式的数据。
}
});
下面就用ImageView来显示取到 NV21 的数据。
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//处理data
mPreviewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
//取发YUVIMAGE
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
mPreviewSize.width,
mPreviewSize.height,
null);
mBaos = new ByteArrayOutputStream();
//yuvimage 转换成jpg格式
yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
mImageBytes = mBaos.toByteArray();
//将mImageBytes转换成bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
icon.setImageBitmap(mBitmap);
// icon.setImageBitmap(rotateBitmap(mBitmap,getDegree()));
}
});
完整代码
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class SurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private SurfaceView mSurfaceView;
private Camera mCamera;
private ImageView icon;
private ByteArrayOutputStream mBaos;
private byte[] mImageBytes;
private Bitmap mBitmap;
private Camera.Size mPreviewSize;//预览尺寸大小
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏
setContentView(R.layout.activity_surface_view);
mSurfaceView = findViewById(R.id.surface_view);
mSurfaceView.getHolder().addCallback(this);
mSurfaceView.setKeepScreenOn(true);
icon = findViewById(R.id.icon);
}
// //------ Surface 预览 -------
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
//在控件创建的时候,进行相应的初始化工作
mCamera = Camera.open();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
doChange(surfaceHolder);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
}
//当我们的程序开始运行,即使我们没有开始录制视频,我们的surFaceView中也要显示当前摄像头显示的内容
private void doChange(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);//设置摄像机的预览界面 一般都是与SurfaceView#SurfaceHolder进行绑定
//设置surfaceView旋转的角度,系统默认的录制是横向的画面
mCamera.setDisplayOrientation(getDegree());
if (mCamera != null ){
try{
Camera.Parameters parameters = mCamera.getParameters(); //获取摄像头参数
// 可以根据情况设置参数
// parameters.setZoom(); //镜头缩放
// 设置预览照片的大小
// parameters.setPreviewSize(200, 200);
// 设置预览照片时每秒显示多少帧的最小值和最大值
// parameters.setPreviewFpsRange(4, 10);
// 设置图片格式
// parameters.setPictureFormat(ImageFormat.JPEG);
// 设置JPG照片的质量 图片的质量[0-100],100最高
// parameters.set("jpeg-quality", 85);
// 设置照片的大小
// parameters.setPictureSize(200, 200);
mCamera.setParameters(parameters);
}
catch (Exception e) {
e.printStackTrace();
Log.e("lu","Camera设置的参数错误:"+e.getMessage());
}
}
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//处理data
mPreviewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
//取发YUVIMAGE
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
mPreviewSize.width,
mPreviewSize.height,
null);
mBaos = new ByteArrayOutputStream();
//yuvimage 转换成jpg格式
yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
mImageBytes = mBaos.toByteArray();
//将mImageBytes转换成bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
// icon.setImageBitmap(mBitmap);
icon.setImageBitmap(rotateBitmap(mBitmap,getDegree()));
}
});
mCamera.startPreview();//开始预览
} catch (IOException e) {
e.printStackTrace();
Log.e("lu","Camera预览变化错误:"+e.getMessage());
}
}
private int getDegree() {
//获取当前屏幕旋转的角度
int rotating = this.getWindowManager().getDefaultDisplay().getRotation();
int degree = 0;//度数
//根据手机旋转的角度,来设置surfaceView的显示的角度
switch (rotating) {
case Surface.ROTATION_0:
degree = 90;
break;
case Surface.ROTATION_90:
degree = 0;
break;
case Surface.ROTATION_180:
degree = 270;
break;
case Surface.ROTATION_270:
degree = 180;
break;
}
return degree;
}
/**
* 选择变换
* @param origin 原图
* @param degree 旋转角度,可正可负
* @return 旋转后的图片
*/
private Bitmap rotateBitmap(Bitmap origin, float degree) {
if (origin == null) {
return null;
}
int width = origin.getWidth();
int height = origin.getHeight();
Matrix matrix = new Matrix();
matrix.setRotate(degree);
// 围绕原地进行旋转
Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
if (newBM.equals(origin)) {
return newBM;
}
origin.recycle();
return newBM;
}
}
总结
- 因为系统默认只能同时开启一个摄像头不管是前置摄像头还是后置摄像头,所以不用的时候一定要释放 。
- Android中的摄像头Camera是区分前置和后置的,所以这里就要做一个前置和后置摄像头的切换功能了(上面是没有写切换功能的)
- 注意预览画布旋角度问题。
- Camera在执行startPreview时必须保证TextureView的SurfaceTexture上来了,如果因为一些性能原因onSurfaceTextureAvailable()这个回调上不来就开预览,就开不了的。如果发生这种情况,就在onSurfaceTextureAvailable()回调里执行open和startPreview操作,保证万无一失。
- 视频画面帧的展示控件SurfaceView及TextureView对比