Android 自定義Camera + TextureView拍照#
自定義camera需要注意這幾點:
- camera預覽的角度。
- textureview的寬高比和camera預覽時設置的寬高比。
- 拍照之後圖片的旋轉角度。
在自定義相機之前可以看下這篇文章,瞭解一下相機傳感器的方向問題https://blog.csdn.net/c10WTiybQ1Ye3/article/details/78098459
在解決詳解預覽的角度問題,官方有一個推薦的寫法。
/**
* 保證預覽方向正確
*
* @param context
* @param cameraId
* @param camera
*/
public void setCameraDisplayOrientation(Activity context,
int cameraId, Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = context.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
在預覽是如果寬高設置的不恰當不僅可能會出現崩潰的情況,還會出現預覽界面變形的情況,爲了解決變形的情況,我們應該讓預覽界面的寬高比例和textureview的寬高比例儘量達到一致,同時在解決因爲寬高設置的不恰當問題,我們也應該在相機支持的分辨率中去尋找。我爲了更方便的控制寬高的比例,我設置的textureview的寬高是充滿屏幕的。那麼屏幕的寬高比例,就是相機分辨率的寬高比例。注意:因爲相機傳感器是橫屏安裝的,所以應該用屏幕的h/w,來確定設置的分辨率的w/h
/**
*
*
* @param sizes 相機支持的size
* @param targetRatio h/w
* @param comparator 升序或者降序
* @param minWidth 最小的支持寬度
* @return
*/
public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, float targetRatio, int comparator, int minWidth) {
if (sizes == null)
return null;
Camera.Size optimalSize = null;
// 對size進行排序
Collections.sort(sizes, getComparator(comparator));
//先找出寬度大於最小寬度的size
List<Camera.Size> tempList = new ArrayList<>();
for(Camera.Size size : sizes){
Log.i("sss", "....width.....:"+size.width+"...height.."+size.height);
if(size.width >= minWidth){
tempList.add(size);
}
}
if(tempList.size() > 0){
// 找比例相同的,這裏是整個屏幕的高和寬的比。
for (Camera.Size size : tempList) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - targetRatio == 0) {
optimalSize = size;
break;
}
}
}else{
//比例相同的
for (Camera.Size size : sizes) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - targetRatio == 0) {
optimalSize = size;
break;
}
}
}
// 如果沒有就找個相近的
if(optimalSize == null){
float tempRation;
float minRation = Float.MAX_VALUE;
if(tempList.size() > 0){
for (Camera.Size size : tempList) {
float curRatio = ((float) size.width) / size.height;
tempRation = Math.abs(targetRatio - curRatio);
if(tempRation <minRation){
minRation = tempRation;
optimalSize = size;
}
}
}else{
for (Camera.Size size : sizes) {
float curRatio = ((float) size.width) / size.height;
tempRation = Math.abs(targetRatio - curRatio);
if(tempRation <minRation){
minRation = tempRation;
optimalSize = size;
}
}
}
}
Log.i("sss", "最後的選擇是:"+optimalSize.width+"....."+optimalSize.height);
return optimalSize;
}
拍照之後圖片旋轉角度,對於圖片的處理可以參考這片文章https://blog.csdn.net/Lamphogani/article/details/79197015?utm_source=blogxgwz9
我想要的要效果是圖片的方向跟着手機的方向來設定,因此我使用的是
orientationEventListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int orientation) {
if (ORIENTATION_UNKNOWN == orientation) {
return;
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
orientation = (orientation + 45) / 90 * 90;
rotation = 0;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation - orientation + 360) % 360;
} else {
rotation = (info.orientation + orientation) % 360;
}
// 我不知道怎麼回事,我的這個設置並沒起到在拍照之後圖片旋轉的效果
if (null != mCamera) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(rotation);
mCamera.setParameters(parameters);
}
}
};
我只是在這記錄拍照是的手機的角度,之後對圖片進行的旋轉
/**
* 把相機拍照返回照片轉正
*
* @param angle 旋轉角度
* @return bitmap 圖片
*/
public Bitmap rotaingImageView(int id, int angle, Bitmap bitmap) {
//旋轉圖片 動作
Matrix matrix = new Matrix();
matrix.postRotate(angle);
//加入翻轉 把相機拍照返回照片轉正
if (id == 1) {
matrix.postScale(-1, 1);
}
// 創建新的圖片, 如果傳入的角度是bitmap當前的角度的話,
// 就不會做重新生成一個新的bitmap。只是把bitmap的值給了resizeBitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
if(resizedBitmap == null){
resizedBitmap = bitmap;
}
return resizedBitmap;
}
注意到以上幾個問題就差不多了,剩下的就是具體的自定義了。
首先做簡單的設置,因爲我使用的是camera+textureview,所以應該開啓窗口加速,我在setcontentview中設置了
//此行代碼必須存在,是TextureView必要在窗口加速中才能使用
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
requestWindowFeature(Window.FEATURE_NO_TITLE); //設置無標題
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //設置全屏
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//拍照過程屏幕一直處於高亮
//設置手機屏幕朝向,一共有7種
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
然後就是權限的申請,同時也註冊了textureView.setSurfaceTextureListener(this);在權限申請通過和textureview創建成功之後,就可以初始化camera同時開啓camera的預覽了
` // 權限
String[] PERMISSIONS = {Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
};`
初始化camera
`/**
*
* @param surfaceTexture
* @param cameraId
*/
@Override
public void startPreview(SurfaceTexture surfaceTexture, int cameraId) {
this.cameraId = cameraId;
this.mSurfaceTexture = surfaceTexture;
if (mCamera == null && mSurfaceTexture != null) {
if(orientationEventListener != null){
orientationEventListener.enable();
}
// 獲取camera實例對象
mCamera = getCameraInstance(cameraId);
// 開啓預覽
try {
if (mCamera != null) {
mCamera.setPreviewTexture(mSurfaceTexture);
mCamera.lock();
setCameraDisplayOrientation(context, cameraId, mCamera);
initCameraParameters();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/** 安全獲取Camera對象實例的方法 */
private Camera getCameraInstance(int cameraId) {
try {
mCamera = Camera.open(cameraId); // 試圖獲取Camera實例
}
catch (Exception e) {
// 攝像頭不可用(正被佔用或不存在)
}
return mCamera; // 不可用則返回null
}
` /**
* 初始化攝像頭參數
*/
private void initCameraParameters() {
// 初始化攝像頭參數
mParameters = mCamera.getParameters();
mCamera.lock();
List<String> focusModes = mParameters.getSupportedFocusModes();
// 設置對焦模式
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// Autofocus mode is supported 自動對焦
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
if(focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)){
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 1連續對焦
}
// 預覽尺寸
previewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), DisplayUtils.getScreenRate(context), Constants.COMPARATOR_ASCEND, 1280);
if (previewSize != null) {
mParameters.setPreviewSize(previewSize.width, previewSize.height);
}
// 圖片尺寸
Camera.Size pictrueSize = getOptimalPreviewSize(mParameters.getSupportedPictureSizes(), DisplayUtils.getScreenRate(context), Constants.COMPARATOR_ASCEND, 1280);
if (pictrueSize != null) {
mParameters.setPictureSize(pictrueSize.width, pictrueSize.height);
}
//設置預覽格式
mParameters.setPreviewFormat(ImageFormat.NV21);
try {
mCamera.setParameters(mParameters);
mCamera.setPreviewCallback(mRecordingUtils);
mCamera.startPreview();
// 2如果要實現連續的自動對焦,這一句必須加上
mCamera.cancelAutoFocus();
} catch (Exception e) {
e.printStackTrace();
}
}
`
最後就是camera的拍照操作了。。。拍照有兩個方法,一個是從camera.setPreviewCallback的監聽中,獲取拍照時的流,然後生成圖片,另一種就是通過camera的takePicture方法獲取。我使用的是第二種。雖然後面也的使用第一種監聽中的方法進行視屏的錄製。
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
//進行了圖片的旋轉,因爲是耗時所以我放在了子線程中去做
Bitmap savebitmap = rotaingImageView(cameraId, tempRotation, bitmap);
String img_path = imagePath;
if(TextUtils.isEmpty(img_path)){
img_path = Constants.DEFAULT_DIRECTORY;
}
img_path = img_path + "/" + System.currentTimeMillis() + ".jpeg";
File file = BitmapUtils.saveJPGE_After(context, savebitmap, img_path, 100);
if(cameraResultCallBack != null){
cameraResultCallBack.takePhotoResult(file);
}
if(bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
}
if(savebitmap != null && !savebitmap.isRecycled()){
savebitmap.recycle();
savebitmap = null;
}
}
}).start();
mCamera.startPreview();
}
});
``
以上就是自定義camera的基本操作流程