Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Camera的使用(一)

Android Camera的使用(一)

編輯:關於Android編程

在Api21中Camera類被廢棄,取而代之的是camera2包。相對來說,camera2比Camera使用起來看似復雜了好多,但是在靈活性方面增加了很多。不過出於兼容性的考慮,加上當前Android設備5.0以下的占比還是比較大的,所以在相機開發的過程中,Camera類還是不得不掌握的。在本文中,講解的是Camera的基本使用。而關於camera2的使用,在以後的文章中會單獨再講。

拍照步驟

添加相機相關權限 通過Camera.open(int)獲得一個相機實例 利用camera.getParameters()得到相機實例的默認設置Camera.Parameters 如果需要的話,修改Camera.Parameters並調用camera.setParameters(Camera.Parameters)來修改相機設置 調用camera.setDisplayOrientation(int)來設置正確的預覽方向 調用camera.setPreviewDisplay(SurfaceHolder)來設置預覽,如果沒有這一步,相機是無法開始預覽的 調用camera.startPreview()來開啟預覽,對於拍照,這一步是必須的 在需要的時候調用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)來拍攝照片 拍攝照片後,相機會停止預覽,如果需要再次拍攝多張照片,需要再次調用camera.startPreview()來重新開始預覽 調用camera.stopPreview()來停止預覽 一定要在onPause()的時候調用camera.release()來釋放camera,在onResume中重新開始camera

相機權限

使用相機進行拍照,需要添加以下權限:

 
 
 

拍攝的照片需要存儲到內存卡的話,還需要內存卡讀寫的權限:

 

拍照注意事項

根據拍照步驟就可以使用相機進行拍照,但是在使用過程中,有諸多需要注意的地方。

開啟相機

開啟相機直接調用Camera.open(),理論上就可以得到一個相機實例。但是在開啟相機前,最好check一下。雖然目前基本上所有的手機都是前後攝像頭,但是也不排斥有些奇葩的手機,只有後攝像頭,或者干脆沒有攝像頭的。所有在open一個camera前,先檢查是否存在該id的camera。Camera.getNumberOfCameras()可以獲得當前設備的Camera的個數N,N為0表示不支持攝像頭,否則對應的Camera的ID就是0—(N-1)。

相機設置

Camera的Parameters提供了諸多屬性,可以對相機進行多種設置,包括預覽大小及格式、照片大小及格式、預覽頻率、拍攝場景、顏色效果、對焦方式等等,具體設置可參考官方手冊。
拍照尤其需要注意的是對預覽大小、照片大小以及對焦方式的設置。
在對預覽大小、照片大小及對焦方式設置時,設置的值必須是當前設備所支持的。否則,預覽大小和照片大小設置會無效,對焦方式設置會導致崩潰。它們都有相應的方法,獲取相應的支持的列表。對應的依次為getSupportedPictureSizes(),getSupportedPictureSizes(),getSupportedFocusModes()。

相機預覽方向

不對相機預覽方向和應用方向設置,通常情況下得到的預覽結果是無法接受的。一般應用設置的方向為固定豎向,預覽設置旋轉90度即可。嚴謹點來說,預覽方向的設置是根據當前window的rotation來設置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在Surface.ROTATION_0和Surface.ROTATION_180時,Camera設置displayOrientation為90,否則設置為0。

預覽View的設置

相機預覽前,必須調用camera.setPreviewDisplay(SurfaceHolder)來設置預覽的承載。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情況下,如果不對顯示的View大小做合理的設置,預覽中的場景都會被變形。
如何保證預覽不變形呢?預覽效果變形是因為設置的previewSize和預覽View的長寬比例不同造成的,將預覽View的長寬比設置的與Camera的previewSize相同(需要注意的是,豎屏下是預覽View的長寬比,要設置的與Camera的previewSize的寬長比相同)即可解決變形的問題。
以全屏預覽為例,假如手機屏幕分辨率為1280*720,相機支持的預覽大小沒有1280*720的,也沒有這個比例的,但是有1280*960的,相機預覽大小選的也是這個。這時候,將預覽View的大小設置為1280*960即可,超出屏幕的部分不予理會。值得注意的是,如果預覽View的父布局是RelativeLayout,設置寬度大於父布局是無效的。可以重寫RelativeLayout的onMeasure來實現,或者將父布局改為FrameLayout。

拍照監聽及圖片處理

相機拍照時在預覽時,調用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重載方法)來實現拍照的,其中第一個參數表示圖像捕獲時刻的回調。可以看到此方法的後三個參數類型是一樣的,都是圖像回調。分別表示原始圖數據回調、展示圖像數據的回調、JPEG圖像數據的回調。圖像回調中得到byte數組decode為image後,圖片的方向都是攝像頭原始的圖像方向。
可以通過parameters.setRotation(int)來改變最後一個回調中圖像數據的方向。個人推薦不設置,直接在回調中利用矩陣變換統一處理。因為利用parameters.setRotation(int)來旋轉圖像,在不同手機上會有差異。
與預覽View設置類似,pictureSize設置的值,影響了最後的拍照結果,處理時需要對拍照的結果進行裁剪,使圖片結果和在可視區域預覽的結果相同。前攝像頭拍攝的結果還需要做對稱變換,以保證“所見即所得”。

拍照示例

首先需要一個相機的控制類CameraKitKat,繼承自ACamera,ACamera是一個抽象類,具有open(int),close()方法,控制相機的開啟和關閉。這樣做主要是為了後面擴展使用Camera2。CameraKitKat源碼如下:

public class CameraKitKat extends ACamera{

    private Camera camera;
    private SurfaceHolder holder;
    private float displayScale;

    public CameraKitKat(SurfaceView surfaceView) {
        super(surfaceView);
        init();
    }

    private void init(){
        holder=displayView.getHolder();
    }

    @Override
    public void open(int type){
        int rotation=((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay().getRotation();
        if(!openCamera(type))return;
        setParameters(camera,rotation);
        setDisplayOrientation(camera,rotation);
        setPreviewDisplay(camera,holder);
        camera.startPreview();
    }

    @Override
    public void close(){
        camera.stopPreview();
        camera.release();
    }

    //調整SurfaceView的大小
    private void resizeDisplayView(){
        Camera.Parameters parameters=camera.getParameters();
        Camera.Size size=parameters.getPreviewSize();
        FrameLayout.LayoutParams p= (FrameLayout.LayoutParams) displayView.getLayoutParams();
        float scale=size.width/(float)size.height;
        displayScale=displayView.getHeight()/(float)displayView.getWidth();
        if(scale>displayScale){
            p.height= (int) (scale*displayView.getWidth());
            p.width=displayView.getWidth();
        }else{
            p.width= (int) (displayView.getHeight()/scale);
            p.height=displayView.getHeight();
        }
        Log.e("wuwang","-->"+size.width+"/"+size.height);
        Log.e("wuwang","--<"+p.height+"/"+p.width);
        displayView.setLayoutParams(p);
        displayView.invalidate();
    }

    private boolean checkCameraId(int cameraId){
        return cameraId>=0&&cameraId() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return lhs.width*lhs.height-rhs.width*rhs.height;
            }
        });
        parameters.setPreviewSize(size.width,size.height);

        //PictureSize設置為和預覽大小最近的
        Camera.Size picSize=Collections.max(parameters.getSupportedPictureSizes(), new Comparator() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return (int) (Math.sqrt(Math.pow(size.width-rhs.width,2)+Math.pow(size.height-rhs.height,2))-
                        Math.sqrt(Math.pow(size.width-lhs.width,2)+Math.pow(size.height-lhs.height,2)));
            }
        });
        parameters.setPictureSize(picSize.width,picSize.height);
        //如果相機支持自動聚焦,則設置相機自動聚焦,否則不設置
        if(parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)){
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        //設置顏色效果
//        parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);

        camera.setParameters(parameters);
        resizeDisplayView();
    }

    //相機使用第三步,設置相機預覽方向
    private void setDisplayOrientation(Camera camera,int rotation){
        if(rotation== Surface.ROTATION_0||rotation==Surface.ROTATION_180){
            camera.setDisplayOrientation(90);
        }else{
            camera.setDisplayOrientation(0);
        }
    }

    //相機使用第四步,設置相機預覽載體SurfaceHolder
    private void setPreviewDisplay(Camera camera,SurfaceHolder holder){
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void measureSize(int width, int height) {
        super.measureSize(width, height);
    }

    @Override
    public void takePicture() {
        super.takePicture();
        camera.takePicture(new Camera.ShutterCallback() {
            @Override
            public void onShutter() {

            }
        }, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {

            }
        }, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                if(pictureCallback!=null){
                    pictureCallback.onPictureTaken(data,displayScale);
                }
            }
        });
    }
}

本例中,使用SurfaceView作為預覽View,提供SurfaceHolder給Camera。考慮到預覽變形問題,使用FrameView作為其父布局。CameraPreview源碼如下:

public class CameraPreview extends FrameLayout implements SurfaceHolder.Callback{

    private SurfaceView surfaceView;
    private ACamera camera;

    private boolean isCameraBack=false;

    public CameraPreview(Context context) {
        this(context,null);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        addPreview();
        //後續增加CameraLollipop,根據系統版本使用Camera或者Camera2
        camera=new CameraKitKat(surfaceView);
        setKeepScreenOn(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        camera.measureSize(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void addPreview(){
        surfaceView=new SurfaceView(getContext());
        surfaceView.getHolder().addCallback(this);
        this.addView(surfaceView);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera.open(isCameraBack?0:1);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        setKeepScreenOn(false);
        camera.close();
    }

    public boolean isCameraBack(){
        return isCameraBack;
    }

    public void setOnPictureCallback(PictureCallback pictureCallback){
        camera.setOnPictureCallback(pictureCallback);
    }

    public void takePicture(){
        camera.takePicture();
    }
}

在拍照的Activity中,置入CameraPreview,長寬都設置為match_parent,然後增加一個拍照的按鈕。增加拍照監聽,並在監聽中對圖像數據進行處理即可。Activity的源碼如下:

public class MainActivity extends Activity implements View.OnClickListener{

    private View btnTake;
    private ImageView ivShower;
    private CameraPreview cameraPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btnTake).setOnClickListener(this);
        ivShower= (ImageView) findViewById(R.id.ivShower);
        cameraPreview= (CameraPreview) findViewById(R.id.cameraView);
        cameraPreview.setOnPictureCallback(new PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data,float scale) {
                ivShower.setVisibility(View.VISIBLE);
                Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length);
                Bitmap bitmap2=rotateAndCropBitmap(bitmap,cameraPreview.isCameraBack()?90:-90,scale);
                saveBitmapToPath(bitmap2,Environment.getExternalStorageDirectory()+"/temp.jpeg");
                ivShower.setImageBitmap(bitmap2);
                bitmap.recycle();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        File file=new File(Environment.getExternalStorageDirectory()+"/temp.jpeg");
        if(file.exists()){
            file.delete();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnTake:
                cameraPreview.takePicture();
                break;
            default:
                break;
        }
    }

    private Bitmap rotateAndCropBitmap(Bitmap bm,int orientationDegree,float rate){
        //TODO : 貌似有些問題
        int width,height;
        float bmScale=bm.getHeight()/(float)bm.getWidth();
        if(rate==bmScale)return bm;
        else if(rate>bmScale){
            width=bm.getWidth();
            height= (int) (width/rate);
        }else{
            height= bm.getHeight();
            width= (int) (height*rate);
        }
        Matrix m = new Matrix();
        if(orientationDegree==-90){ //前攝像頭,則左右鏡像
            m.postScale(1,-1);
        }
        m.postRotate(orientationDegree);
        return Bitmap.createBitmap(bm,0,bm.getHeight()-height,width,height,m,true);
    }

    private void saveBitmapToPath(Bitmap bitmap,String path){
        File file=new File(path);
        try {
            FileOutputStream fos=new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG,90,fos);
            fos.flush();
            fos.close();
            Log.e("wuwang","filePath-->"+file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved