閒扯:最近開始學android開發,還好有些java基礎,直接找了個android教程的視頻,邊學邊寫。本來我是很懶惰的。不打算寫博客,但是在寫這個自定義相機的時候,坑還真是不少。容我吐槽下,那些沒事兒轉載的,搜來搜去都是一樣的內容,真是給跪了。
話入正題,本代碼是跟着視頻裏寫的,然後又完善的。首先說的一點,就是真的不難,但是很坑:相機寫好後,調試了下,有個問題,就是相片很模糊。大小隻有200kb,直接說原因,就是沒有設置,parameters.setPictureSize(picSize.width, picSize.height); parameters.setPreviewSize(preSize.width, preSize.height);這兩個參數,當然一定要 myCamera.setParameters(parameters);不然沒用哦,當然並不是所有手機都會這個問題。還有就是保存目錄的問題,這個好解決。其他問題,我都寫在註釋裏了,自己看吧
package com.example.ggpla.myapplication; import android.graphics.ImageFormat; import android.hardware.Camera; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.CompoundButton; import android.widget.ToggleButton; import com.example.ggpla.utils.AndroidSystemUtils; import com.example.ggpla.utils.FileOptionUtils; import java.io.File; import java.io.IOException; public class MyCamera extends AppCompatActivity implements SurfaceHolder.Callback{ private Camera myCamera; private SurfaceView surfaceView; private SurfaceHolder surfaceHolder; //硬件相機所支持的尺寸 private Camera.Size picSize; private Camera.Size preSize; private byte[] buffer = null; /*爲了實現拍照的快門聲音及拍照保存照片需要下面三個回調方法之一,可以爲空*/ private Camera.ShutterCallback shutter = new Camera.ShutterCallback() //快門按下的回調,在這裏我們可以設置類似播放“咔嚓”聲之類的操作。默認的就是咔嚓。 { public void onShutter() { } }; /** * 拍照完成後後回調,重寫onPictureTaken方法 */ private Camera.PictureCallback mPic = new Camera.PictureCallback(){ @Override public void onPictureTaken(byte[] data, Camera camera) { //保存數據到文件中去,這個已經被我寫成一個靜態方法,方便其他場合調用 FileOptionUtils.saveImageToFile(data,FileOptionUtils.NAMED_BY_TYPE_AND_DATE,"","JPEG","IMAGE"); //需要繼續拍照,需要重新開啓preview myCamera.startPreview(); } }; private ToggleButton toggleButton; @Override protected void onResume() { super.onResume(); if (myCamera == null){ myCamera = getCamera(); if(surfaceHolder != null){ Camera.Parameters parameters = myCamera.getParameters(); parameters.setPictureFormat(ImageFormat.JPEG); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); //獲取最大的支持的尺寸,這關乎你最終拍照的相片質量,一開始我也是被折磨的夠嗆 picSize = AndroidSystemUtils.getCameraSupportPicMaxSize(myCamera); preSize = AndroidSystemUtils.getCameraSupportPreMaxSize(myCamera); parameters.setPictureSize(picSize.width, picSize.height); parameters.setPreviewSize(preSize.width, preSize.height); //設置尺寸後必須將參數設置到硬件相機對象中,才能被執行 myCamera.setParameters(parameters); //啓動preview setStartPreview(surfaceHolder, myCamera); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); // webSocketStart(); surfaceView = (SurfaceView)findViewById(R.id.surfaceView); surfaceHolder = surfaceView.getHolder(); //需要添加回調,這裏由於已經實現了 SurfaceHolder.Callback的三個方法,所以就傳this就可以了 surfaceHolder.addCallback(this); // SURFACE_TYPE_NORMAL:用RAM緩存原生數據的普通Surface // SURFACE_TYPE_HARDWARE:適用於DMA(Direct memory access )引擎和硬件加速的Surface // SURFACE_TYPE_GPU:適用於GPU加速的Surface // SURFACE_TYPE_PUSH_BUFFERS:表明該Surface不包含原生數據,Surface用到的數據由其他對象提供, // 在Camera圖像預覽中就使用該類型的Surface,有Camera負責提供給預覽Surface數據,這樣圖像預覽會比較流暢。 // 如果設置這種類型則就不能調用lockCanvas來獲取Canvas對象了。 surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//貌似用來向下兼容的一個方法 //togglebutton 作爲拍照按鈕,當從關閉狀態切換到 開啓狀態時執行拍照方法 toggleButton = (ToggleButton)findViewById(R.id.toggleButton); toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { //自定義的拍照方法 capture(); } } }); //這裏給surfaceView 定義了一個點擊事件,當點擊surfaceview時,執行自動對焦方法 surfaceView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //相機自動對焦,null表示除了對焦外,我什麼也不敢。 myCamera.autoFocus(null); } }); } /** * 初始化硬件攝像頭 * @return */ private Camera getCamera(){ Camera camera = Camera.open(); //啓動人臉識別功能,需要添加監聽事件,如下所示 // camera.setFaceDetectionListener(new Camera.FaceDetectionListener() { // @Override // public void onFaceDetection(Camera.Face[] faces, Camera camera) { // Log.i("main","人臉數量:"+faces.length); // } // }); // 打開人臉識別方法 // camera.startFaceDetection(); return camera; } /** *自定義 拍照功能 */ public void capture(){ //可以在拍照前重新定義一些硬件相機的設置,這裏只是簡單說明 //爲相機添加自動對焦行爲,重寫onAutoFocus方法,在對焦完成後執行 myCamera.autoFocus(new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { //success表示對焦是否完成。 if (success) { //相機拍照方法。shutter爲快門聲音。mPic爲回調放拍照完成後後回調 camera.takePicture(shutter, null, mPic); } } }); } //開始 private void setStartPreview(SurfaceHolder surfaceHolder,Camera camera){ try { camera.setPreviewDisplay(surfaceHolder); //將系統預覽角度調整90度,不然你看到的內容是倒得 camera.setDisplayOrientation(90); camera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } //釋放相機資源,終止preview等操作 private void releseCamera(){ if(myCamera != null){ myCamera.setPreviewCallback(null); myCamera.stopPreview(); myCamera.release(); myCamera = null; } } /** * 重寫該方法,並調用釋放相機資源方法 * 釋放相機的目的,因爲硬件相機屬於獨佔資源,只能一個Activity用,所以,在暫停當前任務前必須釋放相機資源 */ @Override protected void onPause() { super.onPause(); releseCamera(); } //surfaceview.callback相關內容我也看不懂,可以參考http://www.android100.org/html/201406/10/23235.html //能看懂的可以給我教教我 O(∩_∩)O~ @Override public void surfaceCreated(SurfaceHolder holder) { //在創建surfaceview時執行,開啓 setStartPreview(surfaceHolder, myCamera); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { myCamera.stopPreview(); //在改變surface時執行,開啓 setStartPreview(surfaceHolder, myCamera); } @Override public void surfaceDestroyed(SurfaceHolder holder) { //在銷燬surface時執行,開啓 releseCamera(); } } /** 這個是我自己造的自定義保存圖片的靜態方法 */ package com.example.ggpla.utils; import android.net.Uri; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.Buffer; import java.security.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * Created by ggpla on 2016/1/9. */ public class FileOptionUtils { /** * 命名規則常量 */ public static int NAMED_BY_TYPE_AND_STAMPTIME = 1;//時間戳命名規則,如IMAGE_20424533214 public static int NAMED_BY_TYPE_AND_DATE = 2;//根據時間和類型,如VIDEO_20160110_100427,即2016_1_10拍攝的視頻文件 public static int NAMED_BY_SELF = 0;//自定義文件名 /** * 文件命名基礎參數 */ public static String VIDEO_BASE_FILE = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath(); //文件類型分類 public static Map<String, String> FILE_CONTENT_MAP = new HashMap<String, String>() { { put("VIDEO", "MyVideo"); put("IMAGE", "MyPictures"); } }; //文件擴展名 public static Map<String, String> FILE_EXT_MAP = new HashMap<String, String>() { { put("3GP", ".3gp"); put("MP3", ".mp3"); put("MP4", ".mp4"); put("AVI", ".avi"); put("JPEG", ".jpg"); put("PNG", ".png"); put("JPEG1", ".jpeg"); } }; /** * 獲取完整文件目錄和名稱 * @param rule 命名規則,默認爲 1,爲時間戳命名規則 * @param name 自定義名稱, * @param ext 文件擴展類型名稱 * @param type 文件類型,VIDEO 或 IMAGE * @return */ public static String getSavePath(int rule, String name, String ext, String type) { String path = VIDEO_BASE_FILE + File.separator + FILE_CONTENT_MAP.get(type) + File.separator; File mediaStorageDir = new File(path); if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { return null; } } if (rule == NAMED_BY_SELF) { path = path + name + FILE_EXT_MAP.get(ext); } else if (rule == NAMED_BY_TYPE_AND_DATE) { String currDate = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); path = path + type + "_" + currDate + FILE_EXT_MAP.get(ext); } else { long timestamp = System.currentTimeMillis(); path = path + type + "_" + timestamp + FILE_EXT_MAP.get(ext); } System.out.println(path); return path; } public static void saveImageToFile(byte[] buffer, int rule, String name,String ext,String type) { String path = getSavePath(rule,name,ext,type); File file = new File(path); if (file == null) { return; } if (buffer != null) { try { FileOutputStream fos = new FileOutputStream(file); fos.write(buffer); fos.close(); } catch (IOException e) { e.printStackTrace(); } } } //得到輸出文件的URI public static Uri getOutFileUri(int rule, String name, String ext, String type) { String path = getSavePath(rule, name, FILE_EXT_MAP.get("JPG"), FILE_CONTENT_MAP.get("IMAGE")); return Uri.fromFile(new File(path)); } } /** 這是獲取相機支持圖像大小的工具類,同樣是一些靜態方法,直接調用 */ package com.example.ggpla.utils; import android.hardware.Camera; import android.util.Log; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; /** * Created by ggpla on 2016/1/10. */ public class AndroidSystemUtils { public static List<Camera.Size> getCameraSupportSizeList(Camera myCamera){ Camera.Parameters parameters = myCamera.getParameters(); List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes(); return supportedPictureSizes; } public static Camera.Size getCameraSupportPicMaxSize(Camera myCamera){ AndroidSystemUtils androidSystemUtils = new AndroidSystemUtils(); Camera.Parameters parameters = myCamera.getParameters(); List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes(); Camera.Size size = androidSystemUtils.getMaxSupportSize(supportedPictureSizes, 0) return size; } public static Camera.Size getCameraSupportPreMaxSize(Camera myCamera){ AndroidSystemUtils androidSystemUtils = new AndroidSystemUtils(); Camera.Parameters parameters = myCamera.getParameters(); List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes(); Camera.Size size = androidSystemUtils.getMaxSupportSize(supportedPreviewSizes,0); return size; } private Camera.Size getMaxSupportSize(List<Camera.Size> size,int th){ Collections.sort(size,new CameraSizeComparator()); int i = 0; for(Camera.Size s:size){ if((s.width > th) && equalRate(s, 1.33f)){ Log.i("main", "最終設置圖片尺寸:w = " + s.width + "h = " + s.height); break; } i++; } return size.get(i); } public boolean equalRate(Camera.Size s, float rate){ float r = (float)(s.width)/(float)(s.height); if(Math.abs(r - rate) <= 0.2) { return true; } else{ return false; } } public class CameraSizeComparator implements Comparator<Camera.Size> { //按升序排列 public int compare(Camera.Size lhs, Camera.Size rhs) { // TODO Auto-generated method stub if(lhs.width == rhs.width){ return 0; } else if(lhs.width > rhs.width){ return -1; } else{ return 1; } } } }