Android相機(jī)詳解

內(nèi)容概要

  • 使用系統(tǒng)相機(jī)
  • 自定義相機(jī)(Camera API)
    • 拍照
    • 錄制視頻
  • 使用相機(jī)特性
  • Camera2 API介紹

相機(jī)Feature和權(quán)限

<uses-feature android:name="android.hardware.camera" android:required="true" />
<!-- 使用代碼檢測 -->
hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)

<!-- 存儲(chǔ)圖像或視頻 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 錄制視頻 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<!-- 獲取位置信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />

使用系統(tǒng)相機(jī)

調(diào)用系統(tǒng)相機(jī),獲取被壓縮后的照片(適用于設(shè)置頭像場景)

static final int REQUEST_IMAGE_CAPTURE = 1;

// 打開系統(tǒng)相機(jī)
private void openSystemCameraForPhoto() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

// 獲取拍攝結(jié)果
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);
    }
}

調(diào)用系統(tǒng)相機(jī)呢袱,獲取原始圖片(適用于獲取高清圖像)

//設(shè)置MediaStore.EXTRA_OUTPUT參數(shù)(file://類型URI)暴氏,圖片會(huì)保存到這個(gè)位置
File saveFile = new File('/sdcard/Android/data/${packageName}/temp.jpg');
Uri photoUri = Uri.fromFile(saveFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);

<font color=#FF0000>注意在Android 7.0以后,暴露file:開頭的Uri給其外部應(yīng)用會(huì)觸發(fā)FileUriExposedException</font>

解決方案使用:android.support.v4.content.FileProvider

調(diào)用系統(tǒng)相機(jī)錄制視頻

發(fā)送Action為MediaStore.ACTION_VIDEO_CAPTURE的Intent音诈,返回intent.getData()獲取視頻URI

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);
    }
}

// 獲取錄制的視頻結(jié)果
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
        Uri videoUri = intent.getData();
        mVideoView.setVideoURI(videoUri);
    }
}

使用Camera自定義相機(jī)

使用步驟

  • 檢測并打開相機(jī):Camera.open()
  • 創(chuàng)建預(yù)覽界面:SurfaceView幻碱,實(shí)現(xiàn)SurfaceHolder.Callback
  • 預(yù)覽相機(jī):mCamera.setPreviewDisplay(mHolder), mCamera.startPreview()
  • 獲取照片:mCamera.mCamera.takePicture(null, null, jpegCallback)
  • 關(guān)閉相機(jī):mCamera.release()

打開/關(guān)閉Camera

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        mCamera = Camera.open(id);
        qOpened = (mCamera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    mPreview.setCamera(null);
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

創(chuàng)建相機(jī)預(yù)覽界面

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

拍照并保存圖片

private PictureCallback pictureCallbakc = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        // Do save image data 
        ......
    }
};

mCamera.takePicture(null, null, pictureCallbakc);

錄制視頻

基本步驟,調(diào)用順序很重要

  1. 打開相機(jī):Camera.open()
  2. 建立預(yù)覽界面:SurfaceView细溅,Camera.setPreviewDisplay()
  3. 預(yù)覽:Camera.startPreview()
  4. 使用MediaRecorder錄制視頻
    • Unlock the camera褥傍,MediaRecorder會(huì)調(diào)用camera.unlock()
    • 配置MediaRecoder
      1. 設(shè)置Camera:recoder.setCamera()
      2. setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
      3. setVideoSource(MediaRecorder.VideoSource.CAMERA)
      4. 設(shè)置視頻輸出格式和編碼方式
        • API 8及以上使用:recoder.setProfile(CamcorderProfile.get())
        • API 8以下必須設(shè)置視頻輸出格式和編碼參數(shù)
          1. recoder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
          2. recoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
          3. recoder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP)
      5. recoder.setOutputFile(fileToSaveVideo)
      6. recoder.setPreviewDisplay()
    • recoder.prepare()
    • recoder.start()
  5. 停止錄制視頻
    1. recoder.stop()
    2. recoder.reset()
    3. recoder.release()
    4. Lock the camera:camera.lock()(在API 14及以后不需要調(diào)用,MediaRecoder會(huì)自動(dòng)調(diào)用喇聊,除非MediaRecoder.prepare()方法調(diào)用失敗了)
  6. 停止預(yù)覽恍风,釋放camera: Camera.stopPreview(),Camera.release()

相機(jī)特性

大部分相機(jī)特性可以通過設(shè)置Camera.Parameters實(shí)現(xiàn),通過getParameters()方法獲取Parameters,更改設(shè)置后再設(shè)置給Camera對象:Camera.setParameters()

  • 測光和調(diào)焦(指定圖像中特定區(qū)域用于進(jìn)行調(diào)焦或光線設(shè)置)(API 14)
  • 面部識(shí)別 (API 14)
  • 延時(shí)攝影 (API 14)

更多特性,點(diǎn)擊查看

檢查是否支持某特性

// get Camera parameters
Camera.Parameters params = mCamera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}

測光和調(diào)焦

// Create an instance of Camera
mCamera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = mCamera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of image
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of image
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

面部識(shí)別

  1. 檢查設(shè)備是否支持面部識(shí)別朋贬;
  2. 創(chuàng)建一個(gè)面部識(shí)別的監(jiān)聽器凯楔;
  3. 把面部識(shí)別監(jiān)聽器添加給你的Camera對象;
  4. 在預(yù)覽開始之后(并且在每次重啟預(yù)覽窗口之后)都要啟動(dòng)面部識(shí)別锦募。
class MyFaceDetectionListener implements Camera.FaceDetectionListener {
    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}

// 設(shè)置面部識(shí)別監(jiān)聽器
mCamera.setFaceDetectionListener(new MyFaceDetectionListener());

// 開啟面部識(shí)別
public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}

延遲拍攝

延時(shí)攝影允許用戶把幾張圖片合成一個(gè)幾秒或幾分鐘的視頻剪輯摆屯。這個(gè)功能要使用MediaRecorder對象來記錄圖像的延時(shí)序列。

用MediaRecorder來記錄延時(shí)視頻糠亩,跟錄制普通視頻一樣虐骑,必須要配置的記錄器對象,如把每秒采集的幀數(shù)設(shè)置到較小的數(shù)字赎线,并且要使用一個(gè)延時(shí)品質(zhì)設(shè)置廷没,

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));

// Step 5.5: Set the video capture rate to a low number
mMediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds

Camera2 API介紹

主要對象介紹

CameraManager

相機(jī)管理對象,獲取相機(jī)列表氛驮,獲取相機(jī)屬性腕柜,打開相機(jī)等操作

CameraManager cameraManager = context.getSystemService(Context.CAMERA_SERVICE) 
// 獲取相機(jī)ID列表
cameraManager.getCameraIdList()

// 獲取相機(jī)屬性
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId)
// 打開相機(jī)
cameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);

CameraCharacteristics

攝像頭屬性,相當(dāng)于原CameraInfo矫废。通過CameraManager獲取指定id的攝像頭屬性

cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);

CameraDevice

是Camera2中抽象出來的一個(gè)對象盏缤,直接與系統(tǒng)硬件攝像頭相聯(lián)系

// 創(chuàng)建預(yù)覽 Request Builder
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)

// 創(chuàng)建拍照 Rquest Builder
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)

// 創(chuàng)建 CaptureSession
mCameraDevice.createCaptureSession(surfaceOutputList, stateCallback, handlder)

CameraDevice.StateCallback

CameraDevice相機(jī)相關(guān)的回調(diào):onOpened(相機(jī)打開時(shí)),onDisconnected(相機(jī)斷開時(shí))蓖扑,onError(出錯(cuò)時(shí))

CameraCaptureSession

CameraCaptureSession建立了一個(gè)和Camera設(shè)備的通道唉铜,當(dāng)這個(gè)通道建立完成后就可以向Camera發(fā)送請求獲取圖像(預(yù)覽,拍照等)律杠。

//預(yù)覽
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);

//拍照
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 設(shè)置照片輸出surface潭流,mImageReader為照片數(shù)據(jù)讀取工具
captureBuilder.addTarget(mImageReader.getSurface());
// 設(shè)置對焦模式
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 發(fā)送拍照請求
mCaptureSession.capture(captureBuilder.build(), takePictureCaptureCallback, null);

CameraCaptureSession.StateCallback

創(chuàng)建CameraCaptureSession的回調(diào)類,主要方法: onConfigured(創(chuàng)建成功), onConfigureFailed(創(chuàng)建失敼袢ァ)

CaptureRequest 和 CaptureRequest.Builder

一次捕獲請求灰嫉,對相機(jī)的操作都通過CaptureRequest完成(比如預(yù)覽,拍照嗓奢,錄視頻)讼撒,CaptureRequest.Builder 提供Builder模式創(chuàng)建CaptureRequest

CameraCaptureSession.CaptureCallback

捕獲回調(diào):onCaptureProgressed(捕獲進(jìn)行中),onCaptureCompleted(捕獲完成) ... 等等

ImageReader

ImageReader類允許應(yīng)用程序直接訪問呈現(xiàn)表面的圖像數(shù)據(jù)股耽,getSurface()獲取一個(gè)表面根盒,在創(chuàng)建CameraCaptureSession時(shí)傳遞給createCaptureSession方法,拍照時(shí)通過captureBuilder.addTarget(mImageReader.getSurface());讓其獲取拍照結(jié)果物蝙,通過設(shè)置監(jiān)聽ImageReader.OnImageAvailableListener炎滞,在回調(diào)方法onImageAvailable(ImageReader reader)中保存拍照圖像

// 創(chuàng)建 ImageReader
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2);

// 設(shè)置圖片監(jiān)聽,保存拍照圖片
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

// 傳遞surface給createCaptureSession方法诬乞,創(chuàng)建CameraCaptureSession
mCameraDevice.createCaptureSession(Arrays.asList(preViewSurface, mImageReader.getSurface()), stateCallback);

// 拍照時(shí)將imageReader的surface作為CaptureRequest捕獲輸出表面册赛,這個(gè)surface必須是在創(chuàng)建CameraCaptureSession時(shí)所包含的surface中的一個(gè)
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// 這里takePictureCaptureCallback的回調(diào)中并沒有圖片數(shù)據(jù)钠导,實(shí)際是在ImageReader的OnImageAvailableListener回調(diào)中保存圖片
mCaptureSession.capture(captureBuilder.build(), takePictureCaptureCallback, null);

// OnImageAvailableListener 實(shí)現(xiàn)
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireNextImage();
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            // do save image bytes to disk ...
        }
    };

Camera2 API 使用流程圖

Camera2 API流程圖

詳細(xì)API使用請參考Google官方Demo ?拍照Demo ??錄制視頻Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市击奶,隨后出現(xiàn)的幾起案子辈双,更是在濱河造成了極大的恐慌,老刑警劉巖柜砾,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湃望,死亡現(xiàn)場離奇詭異,居然都是意外死亡痰驱,警方通過查閱死者的電腦和手機(jī)证芭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來担映,“玉大人废士,你說我怎么就攤上這事∮辏” “怎么了官硝?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長短蜕。 經(jīng)常有香客問我氢架,道長,這世上最難降的妖魔是什么朋魔? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任岖研,我火速辦了婚禮,結(jié)果婚禮上警检,老公的妹妹穿的比我還像新娘孙援。我一直安慰自己,他們只是感情好扇雕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布拓售。 她就那樣靜靜地躺著,像睡著了一般镶奉。 火紅的嫁衣襯著肌膚如雪础淤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天腮鞍,我揣著相機(jī)與錄音,去河邊找鬼莹菱。 笑死移国,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的道伟。 我是一名探鬼主播迹缀,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼使碾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祝懂?” 一聲冷哼從身側(cè)響起票摇,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砚蓬,沒想到半個(gè)月后矢门,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灰蛙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年祟剔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摩梧。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡物延,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仅父,到底是詐尸還是另有隱情叛薯,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布笙纤,位于F島的核電站耗溜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粪糙。R本人自食惡果不足惜强霎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蓉冈。 院中可真熱鬧城舞,春花似錦、人聲如沸寞酿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伐弹。三九已至拉馋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惨好,已是汗流浹背煌茴。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留日川,地道東北人蔓腐。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像龄句,于是被迫代替她去往敵國和親回论。 傳聞我的和親對象是個(gè)殘疾皇子散罕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 上一篇介紹了如何使用系統(tǒng)相機(jī)簡單、快速的進(jìn)行拍照傀蓉,本篇將介紹如何使用框架提供的API直接控制攝像機(jī)硬件欧漱。 你還在為...
    Xiao_Mai閱讀 7,180評論 4 18
  • 轉(zhuǎn)載請注明出處(http://www.reibang.com/p/5f538820e370),您的打賞是小編繼續(xù)...
    福later閱讀 27,152評論 8 70
  • Android 框架提供對設(shè)備上可用的相機(jī)和各種相機(jī)功能的支持,通過它我們可以在應(yīng)用程序中拍攝圖片和視頻葬燎。本文將介...
    Xiao_Mai閱讀 2,520評論 0 4
  • 前言 年底公司趕項(xiàng)目误甚,忙得不亦樂乎,博客也很久沒更新了萨蚕。公司項(xiàng)目里用到了自定義攝像頭的模塊靶草,也參考了Google開...
    管弦_閱讀 14,153評論 2 32
  • 請嘗一嘗這些糖果吧 別管它們的滋味如何 我心都是由它們做的 在裝下你以后 請讀一讀這些小詩吧 別管它們的意律如何 ...
    山城過客閱讀 224評論 0 0