設(shè)置權(quán)限
如果要在App中使用攝像頭,必須先在Manifest中聲明攝像頭權(quán)限
<uses-permission android:name="android.permission.CAMERA" />
以及聲明應(yīng)用需要有攝像頭
<uses-feature android:name="android.hardware.camera" /
如果攝像頭并非應(yīng)用必不可少的功能,則可以聲明如下
<uses-feature android:name="android.hardware.camera" android:required="false" />
如果應(yīng)用要在SD卡上存儲(chǔ)圖像或者視頻,則需要在Manifest文件中指定以下權(quán)限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在錄制視頻的時(shí)候,同時(shí)需要錄制音頻,所以也要聲明錄音權(quán)限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
自定義相機(jī)的一般步驟
- 是否有攝像頭、是否有訪問(wèn)攝像頭的權(quán)限
- 創(chuàng)建相機(jī)預(yù)覽類(lèi),繼承SurfaceView并實(shí)現(xiàn)SurfaceHolder接口,用來(lái)預(yù)覽相機(jī)的即時(shí)圖片;
- 有了相機(jī)預(yù)覽類(lèi)之后,搭建預(yù)覽布局將預(yù)覽視圖和界面控件結(jié)合起來(lái)。
- 連接控件的監(jiān)聽(tīng)器以響應(yīng)用戶(hù)的動(dòng)作,拍攝照片或視頻,比如一個(gè)按鈕點(diǎn)擊事件
- 拍攝照片和視頻并保存輸出
- 攝像頭使用完畢后,要正確地釋放相機(jī)以便于其他應(yīng)用使用
相機(jī)是共享資源,必須合理管理才不至于和其他同樣用到相機(jī)的應(yīng)用發(fā)生沖突。
注意 : 當(dāng)你的應(yīng)用使用完相機(jī)之后,記得調(diào)用
Camera.release()方法以釋放Camera對(duì)象。如果沒(méi)有正確地釋放相機(jī),設(shè)備上的任何應(yīng)用,隨后任何訪問(wèn)相機(jī)的嘗試,都有可能失敗、退出。
檢測(cè)相機(jī)
如果沒(méi)有在manifest中配置需要攝像頭功能,則需要在運(yùn)行時(shí)檢測(cè)是否有相機(jī)。
private boolean checkCameraHardware(Context context) {
// 支持所有版本
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
// Android 2.3 (API Level 9) 及以上的
// return Camera.getNumberOfCameras() > 0;
}
訪問(wèn)相機(jī)
public static Camera getCameraInstance(){
Camera c = null;
try {
// 在多個(gè)攝像頭時(shí),默認(rèn)打開(kāi)后置攝像頭
c = Camera.open();
// Android 2.3(API 9之后可指定cameraId攝像頭id,可選值為后置(CAMERA_FACING_BACK)/前置( CAMERA_FACING_FRONT)
// c = Camera.open(cameraId);
} catch (Exception e){
// Camera被占用或者設(shè)備上沒(méi)有相機(jī)時(shí)會(huì)崩潰。
}
return c; // returns null if camera is unavailable
}
注意: 在部分設(shè)備上,打開(kāi)攝像頭時(shí)可能會(huì)花費(fèi)較長(zhǎng)的時(shí)間,因此此操作應(yīng)該在子線(xiàn)程上執(zhí)行,然后以回調(diào)的方式傳遞
Camera對(duì)象,避免出現(xiàn)ANR;
檢測(cè)相機(jī)功能
在獲取到Camera對(duì)象之后,可以調(diào)用Camera.getParameters()方法并檢測(cè)該方法返回的Camera.Parameters對(duì)象所支持的功能以獲取到更多關(guān)于相機(jī)功能的信息。當(dāng)使用API 9及以上時(shí), 使用Camera.getCameraInfo()檢測(cè)攝像頭是前置還是后置、圖像的方向.
創(chuàng)建預(yù)覽類(lèi)
攝像頭預(yù)覽類(lèi)繼承自SurfaceView,能夠?qū)崟r(shí)的展示來(lái)自攝像頭的圖像數(shù)據(jù),方便用戶(hù)捕捉圖片和視頻。
下面的代碼演示了預(yù)覽類(lèi)的基本創(chuàng)建。這個(gè)類(lèi)繼承自SurfaceHolder.Callback以獲取創(chuàng)建和銷(xiāo)毀該預(yù)覽視圖的回調(diào)事件,這些事件在指派相機(jī)預(yù)覽輸入時(shí)所需要的。
/** 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;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
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) {
// 通知攝像頭可以在這里繪制預(yù)覽了
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// 什么都不做,但是在Activity中Camera要正確地釋放預(yù)覽視圖
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// 如果預(yù)覽視圖可變或者旋轉(zhuǎn),要在這里處理好這些事件
// 在重置大小或格式化時(shí),確保停止預(yù)覽
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// 變更之前要停止預(yù)覽
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// 在這里重置預(yù)覽視圖的大小、旋轉(zhuǎn)、格式化
// 使用新設(shè)置啟動(dòng)預(yù)覽視圖
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
如果要給預(yù)覽視圖設(shè)置指定的大小,請(qǐng)?jiān)?code>surfaceChanged()方法中像上面的注釋中提到的一樣設(shè)置。當(dāng)設(shè)置預(yù)覽視圖大小時(shí),必須使用從getSupportedPreviewSizes()獲取到的值。不要在setPreviewSize()中隨意設(shè)置值。
注意:Android 7.0(API 24及以上), Depending on the window size and aspect ratio, you may may have to fit a wide camera preview into a portrait-orientated layout, or vice versa, using a letterbox layout.
在布局文件中放預(yù)覽視圖
下面是一個(gè)簡(jiǎn)單的例子,其中FrameLayout作為預(yù)覽視圖的容器:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button
android:id="@+id/button_capture"
android:text="Capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
通常以橫屏錄制視頻,所以在Manifest中配置對(duì)應(yīng)的Activity中配置:
<activity android:name=".CameraActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
<!-- configure this activity to use landscape orientation -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
在Activity中獲取預(yù)覽視圖的容器視圖,然后將預(yù)覽視圖放置在容器視圖中
public class CameraActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 創(chuàng)建Camera實(shí)例
mCamera = getCameraInstance();
// 創(chuàng)建預(yù)覽視圖,并作為Activity的內(nèi)容
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
}
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
}
拍照
(后面再補(bǔ)上,暫時(shí)用不到)
錄制視頻
錄制視頻需要注意管理Camera以及MediaRecorder類(lèi)。通過(guò)攝像頭錄制視頻時(shí),必須控制 Camera.lock()和Camera.unlock()的調(diào)用以允許MediaRecorder訪問(wèn)攝像頭設(shè)備,另外還有 Camera.open() 和Camera.release()的調(diào)用。
從Android 4.0 (API 14)開(kāi)始, Camera.lock() 和 Camera.unlock() 的調(diào)用已經(jīng)被自動(dòng)管理了。
使用攝像頭錄制視頻和拍照不同,錄制視頻需要特別的調(diào)用順序。要想做好準(zhǔn)備工作,必須遵從特定的執(zhí)行順序。
- 打開(kāi)攝像頭 - 使用
Camera.open()獲取Camera實(shí)例 - 連接預(yù)覽視圖 - 使用
Camera.setPreviewDisplay()連接SurfaceView - 開(kāi)始預(yù)覽 - 使用
Camera.startPreview()方法顯示即時(shí)錄像圖片 - 開(kāi)始錄制視頻 - 要完成視頻錄制,必須按順序完成下面的不步驟:
解鎖攝像頭 - 調(diào)用
Camera.unlock()解鎖攝像頭以供MediaRecorder使用-
配置MediaRecorder - 依次調(diào)用以下MediaRecorder的方法
-
setCamera()- 設(shè)置視頻錄制所用到的攝像頭 -
setAudioSource- 設(shè)置音頻源,使用MediaRecorder.AudioSource.CAMCORDER -
setVideoSource()- 設(shè)置視頻源,使用MediaRecorder.VideoSource.CAMERA - 設(shè)置視頻的輸出格式和編碼方式。Android 2.2 (API 8)及以上版本,用
MediaRecorder.setProfile方法,用CamcorderProfile.get()方法獲取 profile 實(shí)例 -
setOutputFile()- 設(shè)置輸出文件 -
setPreviewDisplay()注意:MediaRecorder的這些方法必須依次調(diào)用,否則應(yīng)用會(huì)出錯(cuò),錄制失敗
-
準(zhǔn)備MediaRecorder - 調(diào)用
MediaRecorder.prepare()方法開(kāi)始錄制 - 調(diào)用
MediaRecorder.start()
- 停止錄制 - 依次調(diào)用下面的方法以完成錄制
- Stop MediaRecorder - 調(diào)用
MediaRecorder.stop() - Reset MediaRecorder - 非必須的,調(diào)用
MediaRecorder.reset()方法移除Recorder的配置設(shè)置 - Release MediaRecorder - 調(diào)用
MediaRecorder.release()方法 - Lock the Camera - 調(diào)用
Camera.lock()鎖定攝像頭,隨后其他的MediaRecorder才能使用它。從Android 4.0(API 14)開(kāi)始,只有在MediaRecorder.prepare()方法調(diào)用失敗時(shí),才需要調(diào)用這個(gè)方法。
- Stop MediaRecorder - 調(diào)用
- Stop the Preview - 當(dāng)Activity完成使用完攝像頭后,調(diào)用
Camera.stopPreview()停止預(yù)覽; - Release Camera - 調(diào)用
Camera.release()釋放攝像頭,其他應(yīng)用才能使用。
溫馨提示:如果應(yīng)用通常用來(lái)拍攝視頻,啟動(dòng)預(yù)覽視圖是使用 設(shè)置
setRecordingHint(boolean)為true。這個(gè)設(shè)置可以減少啟動(dòng)錄制的時(shí)間。
配置MediaRecorder
當(dāng)使用MediaRecorder錄制視頻時(shí),必須以指定的順序執(zhí)行配置步驟,然后調(diào)用MediaRecorder.prepare()檢查和使用配置。
private boolean prepareVideoRecorder(){
mCamera = getCameraInstance();
mMediaRecorder = new MediaRecorder();
// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
// Step 4: Set output file
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
}
以下MediaRecorder的視頻錄制參數(shù)已經(jīng)有了預(yù)設(shè)值,不過(guò)你也可以用下面的方法調(diào)整以適用自己的應(yīng)用:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
啟動(dòng)和停止MediaRecorder
當(dāng)使用MediaRecorder啟動(dòng)和停止錄制時(shí),必須遵從指定的順序:
- Camera.unlock()
- 如以上的代碼示例配置MediaRecorder
- MediaRecorder.start()啟動(dòng)錄制
- 錄制視頻
- MediaRecorder.stop()停止錄制
- MediaRecorder.release()釋放MediaRecorder
- Camera.lock()鎖定攝像頭
以下代碼演示了怎樣使用Camera和MediaRecorder通過(guò)一個(gè)按鈕正確的啟動(dòng)和停止視頻錄制
當(dāng)完成一個(gè)視頻錄制后,不要釋放Camera,否則預(yù)覽視圖也會(huì)停止
private boolean isRecording = false;
// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isRecording) {
// stop recording and release camera
mMediaRecorder.stop(); // stop the recording
releaseMediaRecorder(); // release the MediaRecorder object
mCamera.lock(); // take camera access back from MediaRecorder
// inform the user that recording has stopped
setCaptureButtonText("Capture");
isRecording = false;
} else {
// initialize video camera
if (prepareVideoRecorder()) {
// Camera is available and unlocked, MediaRecorder is prepared,
// now you can start recording
mMediaRecorder.start();
// inform the user that recording has started
setCaptureButtonText("Stop");
isRecording = true;
} else {
// prepare didn't work, release the camera
releaseMediaRecorder();
// inform user
}
}
}
}
);
釋放相機(jī)
一個(gè)設(shè)備上,攝像頭是所有應(yīng)用的共享資源,因此在自身的應(yīng)用處于onPause狀態(tài)時(shí),要立即釋放掉正在使用的Camera對(duì)象
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder(); // if you are using MediaRecorder, release it first
releaseCamera(); // release the camera immediately on pause event
}
private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
}