Android Camera瞭解一下

今天咱們來了解下Android Camera的一些基本知識,包括下面一些內容android

  1. 調用設備的相機app拍攝照片程序員

  2. 調用設備的相機app拍攝視頻api

  3. 經過相機api拍攝照片和視頻app

1.調用設備的相機app拍攝照片

先看下效果圖,拍攝能夠返回縮略圖和原圖,這裏看下返回原圖的效果,點擊CAPTURE按鈕會調用設備的camera app,拍攝後會返回Bitmap:異步

take pic.jpg

1.1 獲取相機特徵權限

這個和日常的相機權限不同,聲明改特徵是若是應用的主要特徵是跟相機攝像頭有關,那麼應用商店Google Play會根據設備是否有相機來決定是否下載該應用:ide

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>
複製代碼

若是android:required設置爲true,那麼若是設備沒有相機就不會下載該應用;設置爲false,會容許下載,這個時候程序員就須要在代碼中增長是否有相機的特徵判斷:函數

hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
複製代碼
1.2 經過設備camera拍攝照片

作Android的小夥伴們都知道要委託其餘app完成某些工做須要經過系統的Intent來作。因此,咱們經過設備camera app也是須要Intent,包含三個步驟:佈局

  1. 構造Intent
  2. 啓動相機app的activity
  3. 處理返回的數據

看下前面兩個步驟經過startActivityForResult的實現:post

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}
複製代碼

這裏有一點須要注意的,startActivityForResult以前須要增長一個判斷resolveActivity,它會返回第一個能夠處理這個intent的activity,若是找不到能夠處理這個intent的app,那麼咱們的app就會crash,因此注意增長這個判斷。ui

第三點,處理返回的數據分紅兩部分,一個是返回縮略圖,像素值小,另一個就是返回原圖片,分別來看下這兩種情形。

1.3 返回縮略圖

Camera app會在返回intent的extras中的「data」這個能夠下帶回縮小版的Bitmap,看下代碼:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(imageBitmap);
    }
}
複製代碼
1.4 保存原圖片

能夠給Camera app一個路徑用於保存原圖。若是不是敏感的圖片,Android系統推薦經過

getExternalStoragePublicDirectory(DIRECTORY_PICTURES)
複製代碼

放在這個路徑下的圖片能夠被全部的app訪問到。當前前提是要聲明寫權限:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>
複製代碼

若是但願只有本app能夠訪問到,能夠經過下面路徑訪問到:

getExternalFilesDir(Environment.DIRECTORY_PICTURES)
複製代碼

因此能夠抽離出一個函數返回保存圖片的路徑:

String mCurrentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}
複製代碼

接着返回構造一個FileProvider,這樣Camera app才能訪問到:

private void dispatchTakeFullSizePicIntent() {
        Intent takePicIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePicIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = FileUtils.createImageFile(this);
            } catch (IOException e) {
                // Error occurred while creating the File
                e.printStackTrace();
            }

            if (photoFile != null) {
                currentPhotoPath = photoFile.getAbsolutePath();
                Uri photoURI = FileProvider.getUriForFile(
                        this,
                        getResources().getString(R.string.fileprovider_authority),
                        photoFile
                );
                takePicIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePicIntent, REQUEST_TAKE_PHOTO);
            }
        }
    }
複製代碼

FileProvider須要在清單文件中註冊,

<provider
            android:authorities="@string/fileprovider_authority"
            android:name="android.support.v4.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
複製代碼

須要注意的是authorities須要和getUriForFile的第二個參數同樣,在meta-data標籤中經過在xml目錄下的file_paths路徑的xml文件指定圖片的存儲路徑:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>
複製代碼

這個路徑其實就是上面的photoFile,也就是經過getExternalFilesDir(Environment.DIRECTORY_PICTURES)獲得的。

若是要把上面保存的圖片放到相冊能夠經過下面的方式:

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}
複製代碼

2. 調用設備的相機app拍攝視頻

跟上面調用設備的相機app拍攝照片其實差很少,首先也是獲取相機特徵權限,這個和上面是徹底同樣的,再也不重複說了。調用設備的相機app拍攝視頻也是須要三個步驟,

  1. 構造Intent
  2. 啓動相機app的activity
  3. 處理返回的數據

首先看下構造intent和啓動:

static final int REQUEST_VIDEO_CAPTURE = 1;

private void dispatchTakeVideoIntent() {
    Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
    }
}
複製代碼

和拍攝照片惟一不同的就是action,視頻的是MediaStore.ACTION_VIDEO_CAPTURE.

第三步就是處理返回的數據了:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
            Uri videoUri = data.getData();
            videoView.setVideoURI(videoUri);
            if (videoView.getVisibility() == View.GONE) {
                videoView.setVisibility(View.VISIBLE);
            }

            videoView.start();
        }
    }
複製代碼

經過intent的getData能夠拿到視頻的uri,經過VideoView進行播放。

3.控制相機

通常建立自定義相機界面有下面幾個步驟:

  1. 確認設備有相機和申請權限
  2. 建立一個預覽界面,能夠用TextureView或者SurfaceView
  3. 建立一個佈局,包含上面的預覽界面和控制接口UI,好比按鈕
  4. 創建UI和預覽界面之間的聯繫,好比點擊拍攝,控制相機拍攝而後預覽界面顯示
  5. 保存拍攝的文件,照片或者視頻
  6. 釋放相機
3.1 確認設備是否有相機

如前面第一部分說的,若是沒有在清單文件中申明app須要相機,那麼就須要在代碼中增長判斷設備是否有相機,能夠經過PackageManager.hasSystemFeature()方法判斷:

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}
複製代碼

在Android 2.3版本及以上,若是設備有多個相機,能夠經過Camera.getNumberOfCameras()確認

3.2 申請權限

在Android 6.0以上須要運行時申請權限,首先須要確認是否已受權:

ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)
複製代碼

若是未受權,須要申請:

//ask for authorisation
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE);
複製代碼

最後一個參數是常量請求碼,在權限反饋結果返回中須要用到,在onRequestPermissionsResult返回結果:

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_PERMISSION_CODE:
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted
                    mPreview.startPreview();
                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                    finish();
                }
                break;
                default:
                    break;
        }
    }
複製代碼
3.3 獲取相機實例

確認過設備有相機和app有相機權限後須要獲取相機實例,其中id能夠是前置相機(Camera.CameraInfo.CAMERA_FACING_FRONT)或者後置相機(Camera.CameraInfo.CAMERA_FACING_BACK):

private Camera safeCameraOpen(final int id) {
        Camera camera;

        try {
            releaseCameraAndPreview();
            camera = Camera.open(id);
        } catch (Exception e) {
            e.printStackTrace();
            camera = null;
        }

        latch.countDown();

        return camera;
    }

    private void releaseCameraAndPreview() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }
複製代碼

open操做須要catch 異常,有可能其餘app在使用相機或者相機設備不存在,另外open操做是耗時操做,建議放在線程中。

3.4 獲取更多相機特徵

成功獲取相機實例後,能夠經過Camera.getParameters()獲取更多相機的參數信息,經過Camera.getCameraInfo()獲取相機是前置仍是後置,和圖片的旋轉方向。

3.5 建立預覽界面

這裏經過TextureView建立預覽界面,TextureView須要實現接口TextureView.SurfaceTextureListener:

private void initTextureView() {
        if (null == textureView) {
            textureView = new TextureView(context);
            textureView.setKeepScreenOn(true);
            textureView.setSurfaceTextureListener(this);
        }
        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
        addView(textureView, layoutParams);
    }
複製代碼

在系統建立好TextureView後會回調onSurfaceTextureAvailable,在這裏就能夠建立相機實例了:

@Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        initImg();

        coverImg.setImageResource(R.drawable.second);
        final Camera[] camera = new Camera[1];

        WorkerManager.getInstance().postTask(new Runnable() {
            @Override
            public void run() {
                camera[0] = safeCameraOpen(Camera.CameraInfo.CAMERA_FACING_BACK);
            }
        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        setCamera(surface, camera[0]);
    }
複製代碼

其中coverImg是ImageView,在相機建立以前顯示一張封面圖用的。在異步線程中打開相機,而後把SurfaceTexture傳遞給相機就能夠預覽到界面了。

在接口onSurfaceTextureDestroyed記得釋放相機:

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        stopPreviewAndFreeCamera();
        return true;
}

    /**
     * When this function returns, mCamera will be null.
     */
private void stopPreviewAndFreeCamera() {
    if (mCamera != null) {
        mCamera.stopPreview();

        mCamera.release();

        mCamera = null;
}
複製代碼
3.6 建立一個佈局

佈局很簡單,添加一個容器用來放置上面的預覽界面CameraPreview:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <Button
        android:id="@+id/control"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/btn_bg"
        android:text="@string/capture_image" />

    <com.example.juexingzhe.jueapp.view.CameraPreview
        android:id="@+id/cameraShower"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:scaleType="fitXY" />

</LinearLayout>
複製代碼
3.7 創建UI和預覽界面的關係

接下來就是創建UI,就是上面的按鈕,和預覽界面CameraPreview之間的關係,這裏就是點擊按鈕拍攝一張照片:

private void initView() {
        mPreview = findViewById(R.id.cameraShower);
        controlBtn = findViewById(R.id.control);
        controlBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPreview != null) {
                    mPreview.takePicture();
                }
            }
        });
    }
複製代碼
3.8 拍攝照片

上面點擊按鈕調用CameraPreview的takePicture拍攝照片,這裏須要實現Camera.PictureCallback接口,在接口會回調回來二進制數據:

/**
     * 拍照片
     */
    public void takePicture() {
        if (mCamera != null) {
            mCamera.takePicture(null, null, new Camera.PictureCallback() {
                @Override
                public void onPictureTaken(byte[] data, Camera camera) {

                    if (coverImg == null) {
                        return;
                    }

                    if (data == null || data.length == 0) {
                        return;
                    }

                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    if (bitmap != null) {
                        coverImg.setVisibility(VISIBLE);
                        coverImg.setImageBitmap(BitmapUtils.rotateBitmap(bitmap, (180 - rotationDigree)));
                        stopPreviewAndFreeCamera();
                    }
                }
            });
        }
    }
複製代碼

這裏直接decode二進制數據生成Bitmap,而後給ImageView顯示。

4.總結

上面總結了Camera的一些用法,包括經過調用系統的相機app拍攝照片和視頻,自定義控制相機拍攝照片,其實還有一個自定義控制相機拍攝視頻,官方文檔推薦結合用MediaRecorder錄製,可是如今比較經常使用的方式就是經過MediaCodec進行編碼,而後經過MediaMuxer混合成視頻格式,這個後面專門寫篇博客分享。

相關文章
相關標籤/搜索