內(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>
調(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)用順序很重要
- 打開相機(jī):Camera.open()
- 建立預(yù)覽界面:SurfaceView细溅,Camera.setPreviewDisplay()
- 預(yù)覽:Camera.startPreview()
- 使用MediaRecorder錄制視頻
- Unlock the camera褥傍,MediaRecorder會(huì)調(diào)用camera.unlock()
- 配置MediaRecoder
- 設(shè)置Camera:recoder.setCamera()
- setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
- setVideoSource(MediaRecorder.VideoSource.CAMERA)
- 設(shè)置視頻輸出格式和編碼方式
- API 8及以上使用:recoder.setProfile(CamcorderProfile.get())
- API 8以下必須設(shè)置視頻輸出格式和編碼參數(shù)
- recoder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
- recoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
- recoder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP)
- recoder.setOutputFile(fileToSaveVideo)
- recoder.setPreviewDisplay()
- recoder.prepare()
- recoder.start()
- 停止錄制視頻
- recoder.stop()
- recoder.reset()
- recoder.release()
- Lock the camera:camera.lock()(在API 14及以后不需要調(diào)用,MediaRecoder會(huì)自動(dòng)調(diào)用喇聊,除非MediaRecoder.prepare()方法調(diào)用失敗了)
- 停止預(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)
檢查是否支持某特性
// 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í)別
- 檢查設(shè)備是否支持面部識(shí)別朋贬;
- 創(chuàng)建一個(gè)面部識(shí)別的監(jiān)聽器凯楔;
- 把面部識(shí)別監(jiān)聽器添加給你的Camera對象;
- 在預(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 ...
}
};