上一篇介紹了如何使用系統(tǒng)相機簡單、快速的進行拍照,本篇將介紹如何使用框架提供的API直接控制攝像機硬件。
你還在為開發(fā)中頻繁切換環(huán)境打包而煩惱嗎?快來試試 Environment Switcher 吧!使用它可以在app運行時一鍵切換環(huán)境,而且還支持其他貼心小功能,有了它媽媽再也不用擔心頻繁環(huán)境切換了。https://github.com/CodeXiaoMai/EnvironmentSwitcher
控制相機
直接控制設備相機比從現(xiàn)有相機應用程序中請求圖片或視頻要復雜很多。但是,如果我們要創(chuàng)建專門的相機應用程序或完全集成到應用程序的界面中,就不得不通過這種方式實現(xiàn)。
為應用程序創(chuàng)建自定義相機的一般步驟如下:
- 檢測和訪問攝像頭 - 檢查相機是否存在并請求訪問它。
- 創(chuàng)建一個預覽控件 - 創(chuàng)建一個類繼承 SurfaceView 并實現(xiàn)SurfaceHolder接口,這個控件用來預覽相機的實時圖像。
- 創(chuàng)建預覽布局 - 當相機預覽控件創(chuàng)建成功后,創(chuàng)建一個布局文件,其中包含預覽控件和所需的用戶界面控件。
- 為拍攝設置監(jiān)聽 - 響應用戶的操作(如按下按鈕)啟動圖像或視頻捕獲。
- 拍攝并保存文件 - 拍攝照片或視頻,并保存為文件。
- 釋放相機 - 相機使用完畢后,必須正確釋放以供其他應用程序使用。
注意:當我們的應用程序完成使用 Camera 后,一定要記住通過調用Camera.release()來釋放Camera對象!如果我們的應用程序未正確釋放相機,則所有后續(xù)嘗試訪問相機的操作(包括我們自己的應用程序)都將失敗,并可能導致我們的程序或其他應用程序關閉。
相機硬件是一個共享資源,必須小心管理,以便我們的應用程序不會與其他可能需要使用相機的應用程序發(fā)生沖突。下面將介紹如何檢測相機硬件,如何請求訪問相機,如何捕獲圖片或視頻以及如何在相機使用結束后釋放相機。
1. 檢測相機硬件
如果我們的應用程序沒有在 manifest 中聲明特別要求相機,則應檢查相機是否在運行時可用。要執(zhí)行此操作,我們可以使用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;
}
}
現(xiàn)在的Android設備基本都有多個攝像頭,例如用于攝影的后置攝像頭和用于視頻通話的前置攝像頭。 Android 2.3(API等級9)及更高版本的系統(tǒng)允許我們使用Camera.getNumberOfCameras()方法檢查設備上可用的攝像機數(shù)量。
2. 訪問相機
如果已經確定運行應用程序的設備有相機,則必須通過獲取Camera的實例來請求訪問它(除非我們使用 Intent 啟動相機)。
要訪問主攝像頭,應該使用Camera.open()方法,并確保捕獲任何異常,如以下代碼所示:
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
Camera camera = 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 camera ; // returns null if camera is unavailable
}
注意:使用Camera.open()時,必需檢查異常情況。如果相機正在使用或不存在,且沒有檢查異常將導致我們的應用程序被系統(tǒng)關閉。
在運行Android 2.3(API Level 9)或更高版本的設備上,我們可以使用Camera.open(int)訪問特定的攝像機。上面的示例代碼將訪問具有多個攝像頭的設備上的第一個后置攝像頭。
獲取攝像機的權限后,我們可以使用Camera.getParameters()方法獲取有關其功能的更多信息,并檢查返回的Camera.Parameters對象以獲得支持的功能。當使用API?? Level 9或更高版本時,可以使用Camera.getCameraInfo()來確定當前相機是在設備的正面還是背面,以及圖像的方向。
3. 創(chuàng)建相機預覽控件
為了讓用戶有效地拍照或錄像,他們必須能夠看到設備的相機所“看”到的內容。相機預覽控件是可以顯示來自相機的實時圖像數(shù)據(jù)的SurfaceView,因此用戶可以對圖片或視頻進行捕獲。
以下示例代碼演示了如何創(chuàng)建可以包含在View布局中的基本相機預覽類。該類實現(xiàn)了SurfaceHolder.Callback接口,以監(jiān)聽創(chuàng)建和銷毀視圖的回調事件。
public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback {
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
// 已棄用的設置,但在3.0之前的Android版本上需要此設置
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Surface 已創(chuàng)建,現(xiàn)在告訴相機在哪里繪制預覽。
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 如果允許預覽可以更改或旋轉,可以在這里處理這些事件
// 但是要確保在調整大小或重新格式化之前停止預覽。
if (mHolder.getSurface == null) {
return;
}
// 在進行更改之前停止預覽
try {
mCamera.stopPreview();
} catch (Exception e) {
// 忽略:試圖停止不存在的預覽
}
```
可以設置調整預覽為任意大小,旋轉或在這里重新格式化變更
如果要設置相機預覽的特定尺寸,必須使用 getSupportedPreviewSizes() 中的值。
不要在setPreviewSize() 方法中設置任意值。
```
// 用新設置開始預覽
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 不需要實現(xiàn)。但需要注意在我們的 Activity 中釋放相機預覽。
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
提示:隨著 Android 7.0(API級別24)及更高版本中的多窗口功能的引入,即使在調用 setDisplayOrientation()之后,我們也不能再假設預覽的寬高比與我們的 activity 相同。
4. 將預覽控件放置到布局中
現(xiàn)在預覽控件已經創(chuàng)建好了,我們要想拍攝照片或視頻,就必須將相機預覽控件(如上一節(jié)所示的示例)與其他用戶界面控件結合在一起放在 Activity 的布局中。本節(jié)介紹如何為預覽構建基本布局和活動。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<packageName.CameraPreview
android:id="@+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_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>
現(xiàn)在可以將創(chuàng)建好的布局設置到相機視圖的 Activity 中。此外還必須確保在暫停或關閉相機時釋放相機。
public class CameraActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPreview = (CameraPreview) findViewById(R.id.camera_preview);
// 創(chuàng)建一個 Camera 實例
mCamera = getCameraInstance();
if (mCamera != null) {
mPreview.setCamera(mCamera);
}
}
}
Ok,現(xiàn)在可以運行我們的程序,看看預覽效果了。
咦?!為什么所有的東西都是旋轉了90度呢?(并不是我故意把手機旋轉的)其實這是因為在大多數(shù)設備上,相機預覽的默認方向為橫向,所以我們看到預覽會感覺很別扭。在Android 2.2(API 8級)之前,相機預覽必須處于橫向模式,而從Android 2.2(API 8級)開始,可以使用 Camera.setDisplayOrientation()方法旋轉預覽圖像。為了在用戶更改手機的方向時更改預覽方向,在預覽類的 surfaceChanged()方法中,首先使用Camera.stopPreview()停止預覽,然后再次使用Camera.startPreview( ) 重新開始預覽。
既然原因找到了,那就解決一下吧。
因為Android 2.2(API 8級)之前,相機預覽必須處于橫向模式,所以我們可以通過將用來做相機預覽的 Activity 的方向更改為橫向,方法是將以下內容添加到清單中。
<activity android:name=".CameraActivity"
android:screenOrientation="landscape">
</activity>
而從Android 2.2(API 8級)開始,可以使用 Camera.setDisplayOrientation()方法旋轉預覽圖像。我們可以將Activity代碼修改為:
public class CameraActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPreview = (CameraPreview) findViewById(R.id.camera_preview);
// 創(chuàng)建一個 Camera 實例
mCamera = getCameraInstance();
if (mCamera != null) {
// 旋轉方向
mCamera.setDisplayOrientation(90);
mPreview.setCamera(mCamera);
}
}
}
Ok,我們再運行一下,看看效果。
哈哈,這次方向對了吧,可這只是預覽,你一定迫不及待的想要拍下屬于你自己的相機的第一張圖片吧,不要著急,接下來就拍照。
5. 拍攝照片或視頻
終于到了激動人心的時刻了,不管你激不激動,反正我是激動了。
現(xiàn)在預覽控件和一個視圖布局都已經創(chuàng)建好了,馬上就可以開始使用應用程序捕獲圖像和視頻了。在代碼中,我們必須設置用戶界面控件的監(jiān)聽器,以便響應用戶操作。
5.1 拍攝照片
要想拍攝圖片,我們可以使用Camera.takePicture()方法,該方法需要接收三個參數(shù)。為了以 JPEG 格式接收數(shù)據(jù),我們必須實現(xiàn)一個 Camera.PictureCallback 接口來接收圖像數(shù)據(jù)并將其寫入文件。下面的代碼就是一個實現(xiàn) Camera.PictureCallback 接口,來保存從相機接收到的圖像的示例 。
private PictureCallback mPictureCallBack = new PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
Log.d(TAG, "Error creating media file, check storage permissions: " +
e.getMessage());
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
};
如果想在拍照結束后處理圖片,如下:
File pictureFile = null;
try {
pictureFile = getOutPutMediaFile();
} catch (IOException e) {
e.printStackTrace();
}
if (pictureFile == null) {
CustomToast.show("圖片保存路徑不存在");
return;
}
Bitmap resource = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
// 拍出來的照片默認是橫向的
matrix.setRotate(90f);
// 裁剪
Bitmap bitmap = Bitmap.createBitmap(resource, startX, startY, newWidth, newHeight, matrix, false);
resource.recycle();
// 縮放 1.5 倍
float scale = 1.5f;
Matrix matrix2 = new Matrix();
matrix2.setScale(scale, scale);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix2, false);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(pictureFile);
fileOutputStream.write(byteArrayOutputStream.toByteArray());
fileOutputStream.close();
bitmap.recycle();
resource.recycle();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
public File getOutPutMediaFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageName = "JPEG_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
return File.createTempFile(
imageName,
".jpg",
storageDir
);
}
通過調用Camera.takePicture()方法捕獲圖像。以下示例代碼顯示了如何通過按鈕的 View.OnClickListener 調用此方法。
// 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) {
// get an image from the camera
mCamera.takePicture(null, null, mPictureCallBack);
}
}
);
這里 takePicture() 方法中的第一個參數(shù)傳入 null,效果是按下快門鍵的時候無任何聲音;而如果傳入一個 Camera.ShutterCallback 接口的實現(xiàn)類,就會默認有“咔擦”一聲,如下。
mCamera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, null, mPictureCallback);
再次警告:當我們的應用程序使用相機結束時,一定要記住通過調用Camera.release()來釋放Camera對象!有關如何釋放相機的具體操作,后面會介紹。
5.2 拍攝視頻
使用Android框架進行視頻捕獲需要仔細管理Camera對象并與 MediaRecorder 類進行協(xié)調。使用Camera錄制視頻時,除調用 Camera.open()和Camera.release()之外,還必須管理 Camera.lock()和Camera.unlock()的調用以允許MediaRecorder訪問攝像機硬件。
注意:從Android 4.0(API級別14)開始,Camera.lock()和 Camera.unlock()調用將自動進行管理。
與使用設備攝像頭拍攝照片不同,拍攝視頻需要非常特別的調用順序。我們必須按照特定的執(zhí)行順序才能成功捕獲視頻,具體步驟如下。
打開相機 - 使用Camera.open()獲取相機對象的實例。
連接預覽 - 使用Camera.setPreviewDisplay()將SurfaceView 連接到攝像機,準備實時攝像機圖像預覽。
開始預覽 - 調用Camera.startPreview()開始顯示實時攝像頭圖像。
-
開始錄制視頻 - 為了成功錄制視頻,必須完成以下步驟:
a. 解鎖相機 - 通過調用Camera.unlock()解鎖相機以供MediaRecorder使用。
b. 配置MediaRecorder - 按以下順序調用下面列出的MediaRecorder的方法。有關詳細信息,請參閱 MediaRecorder
參考文檔。- setCamera() - 使用應用程序當前的Camera實例,并將其設置為用于視頻捕獲。
- setAudioSource() - 使用MediaRecorder.AudioSource.CAMCORDER,設置音頻源。
- setVideoSource() - 設置視頻源,使用MediaRecorder.VideoSource.CAMERA。
- 設置視頻輸出格式和編碼 - 對于Android 2.2(API 8)及更高版本,可以使用MediaRecorder.setProfile方法,并使用CamcorderProfile.get()獲取配置文件實例。
- setOutputFile() - 設置輸出文件,使用getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()方法,后面會介紹。
- setPreviewDisplay() - 使用我們?yōu)檫B接預覽指定的相同對象,為應用程序指定 SurfaceView 預覽布局元素。
注意:我們必須按此順序調用這些MediaRecorder配置方法,否則應用程序將遇到錯誤,并且錄制將失敗。
c. **準備MediaRecorder **- 通過調用MediaRecorder.prepare(),為已提供的配置設置準備MediaRecorder。
d. 啟動MediaRecorder - 通過調用MediaRecorder.start()開始錄制視頻。 停止錄制視頻 - 按順序調用以下方法,以成功完成錄像:
a. 停止MediaRecorder - 通過調用MediaRecorder.stop()停止錄制視頻。
b. 重新設置MediaRecorder(可選) - 通過調用MediaRecorder.reset()從錄像機中刪除配置設置。
c. **釋放MediaRecorder ** - 通過調用MediaRecorder.release()來釋放MediaRecorder。
d. 鎖定相機 - 鎖定相機,以便將來的MediaRecorder會話可以通過調用Camera.lock()來使用它。從Android 4.0(API級別14)開始,除非MediaRecorder.prepare()調用失敗,否則不需要調用它。停止預覽 - 當我們的頁面完成使用相機后,要使用Camera.stopPreview()停止預覽。
釋放相機 - 釋放相機,以便其他應用程序可以通過調用Camera.release()來使用相機。
提示:可以先使用MediaRecorder創(chuàng)建攝像機預覽,并跳過此過程的前幾個步驟。然而,由于我們通常更喜歡在開始錄制之前便看到預覽,所以這里就不討論該過程。
技巧:如果我們的應用程序主要用于錄制視頻,可以在啟動預覽之前將setRecordingHint(boolean)設置為true。此設置可以幫助我們減少開始錄制所需的時間。
5.2.1 配置 MediaRecorder
使用MediaRecorder錄制視頻時,必須按照特定順序執(zhí)行配置步驟,然后調用MediaRecorder.prepare()方法來檢查和實現(xiàn)配置。
以下示例代碼演示如何正確配置和準備MediaRecorder進行視頻錄制。
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;
}
提示:這是假設我們的應用不支持 Android 2.2(API 8)之前的設備,因為 Android 2.2 之前的配置方法不同,具體方法可以到官網查看。
MediaRecorder的以下視頻錄制參數(shù)被賦予默認設置,我們可以通過調用以下方法調整應用程序的這些設置:
5.2.2 啟動和停止MediaRecorder
使用MediaRecorder類啟動和停止視頻錄制時,必須遵循以下列出的特定順序。
- 用Camera.unlock()解鎖相機
- 配置MediaRecorder,如上已節(jié)的代碼示例所示
- 使用MediaRecorder.start()開始錄制
- 錄制視頻
- 使用MediaRecorder.stop()停止錄制
- 用MediaRecorder.release()釋放媒體錄音機
- 使用Camera.lock()鎖定相機
以下示例代碼演示如何使用相機和MediaRecorder 連接按鈕以正確啟動和停止視頻錄制。
注意:和拍照不同的是,完成錄像時不要釋放相機,否則將停止預覽。
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
}
}
}
}
);
注意:在上面的示例中,prepareVideoRecorder()方法指的是配置MediaRecorder 中的示例代碼。此方法負責鎖定相機,配置和準備MediaRecorder實例。
6. 釋放相機
相機是設備上的所有應用程序共享的硬件資源。獲取相機實例后,我們的應用程序可以使用相機,當應用程序停止使用和應用程序暫停(Activity.onPause())時,必須特別注意釋放相機對象。如果我們的應用程序沒有正確地釋放相機,則所有后續(xù)訪問相機的嘗試(包括我們自己的應用程序)都將失敗,并可能導致我們的應用或其他應用程序關閉。
要釋放Camera對象的實例,應該使用Camera.release()方法,如下面的示例代碼所示。
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;
}
}
}
7. 保存文件
用戶創(chuàng)建的媒體文件(如圖片和視頻)應保存在設備的外部存儲目錄(SD卡)中,以節(jié)省系統(tǒng)空間,并允許用戶在沒有設備的情況下訪問這些文件。有許多可能的目錄位置來保存設備上的媒體文件,但是作為開發(fā)人員我們只能選擇兩個標準位置:
Environment.getExternalStoragePublicDirectory - 此方法返回的是Android系統(tǒng)推薦的用來保存圖片和視頻的標準的位置。該目錄是共享的(public),所以其他應用程序可以輕松地讀取,更改和刪除保存在此位置的文件。如果我們的應用程序被用戶卸載,保存到此位置的媒體文件將不會被刪除。為了避免干擾用戶現(xiàn)有的圖片和視頻,我們應該在該目錄中為我們的應用程序創(chuàng)建一個子目錄,如下面的代碼示例所示。
Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 此方法返回的是用于保存與應用程序相關聯(lián)的圖片和視頻的標準位置。如果我們的應用程序被卸載,則保存在此位置的所有文件都將被刪除。保存在該位置的文件其他應用程序也可以讀取,更改和刪除。
以下示例代碼演示如何為媒體文件創(chuàng)建一個文件或 Uri 位置,以便在使用Intent或調用設備的相機時使用。
public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;
/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
return Uri.fromFile(getOutputMediaFile(type));
}
/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MyCameraApp");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.
// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".mp4");
} else {
return null;
}
return mediaFile;
}
注意:Environment.getExternalStoragePublicDirectory()在Android 2.2(API 8級)或更高版本中可用。如果使用較早版本的Android定位設備,應改用Environment.getExternalStorageDirectory()。有關詳細信息,請參閱保存共享文件。
要使URI支持工作配置文件,首先將 file URI轉換為 content URI。然后,將 content URI添加到Intent的EXTRA_OUTPUT中。
相機功能
Android支持各種相機功能,我們的應用程序可對相機進行控制,如圖片格式,閃光模式,對焦設置等等。本節(jié)列出了常見的相機功能,并簡要的討論如何使用它們。通過Camera.Parameters對象可以訪問和設置大多數(shù)攝像頭功能。但是,有幾個重要的功能需要在Camera.Parameters中的簡單設置。以下部分將介紹這些功能:
- 測量和聚焦
- 面部識別
- 長時間視頻
有關如何使用通過Camera.Parameters控制的功能的信息,后面會進行詳細介紹。有關如何使用通過攝像機參數(shù)對象控制的功能的更多詳細信息,可以參考下面的功能列表中的鏈接到API參考文檔。
Feature | API Level | Description |
---|---|---|
人臉檢測 | 14 | 識別圖片中的人臉,并將其用于對焦,測光和白平衡 |
測量區(qū)域 | 14 | 指定圖像中的一個或多個區(qū)域以計算白平衡 |
聚焦區(qū)域 | 14 | 設置圖像中的一個或多個區(qū)域以用于焦點 |
白平衡鎖 | 14 | 停止或開始自動白平衡調整 |
曝光鎖 | 14 | 停止或開始自動曝光調整 |
視頻快照 | 14 | 拍攝視頻時拍攝照片(框架抓取) |
Time Lapse Video | 11 | 記錄設置延遲的幀以記錄時間延遲視頻 |
多個相機 | 9 | 支持設備上的多臺攝像機,包括前置攝像頭和后置攝像頭 |
聚焦距離 | 9 | 報告相機和對象之間的距離 |
縮放 | 8 | 設置圖像的縮放比例 |
曝光補償 | 8 | 增加或減少曝光量 |
GPS數(shù)據(jù) | 5 | 包含或省略地理位置數(shù)據(jù)與圖像 |
白平衡 | 5 | 設置白平衡模式,影響拍攝圖像中的顏色值 |
對焦模式 | 5 | 設置相機如何對焦于諸如自動,固定,宏或無限遠的主題 |
場景模式 | 5 | 對特定類型的攝影情況(例如夜間,海灘,雪景或燭光場景)應用預設模式 |
JPEG質量 | 5 | 設置JPEG圖像的壓縮級別,可增加或減少圖像輸出文件的質量和大小 |
閃光模式 | 5 | 打開,關閉閃光燈,或使用自動設置 |
顏色效果 | 5 | 對拍攝的圖像應用顏色效果,如黑白色,棕褐色調或陰性。 |
反捆扎 | 5 | 由于JPEG壓縮,降低了色彩漸變的效果 |
Picture Format | 1 | 指定圖片的文件格式 |
圖片尺寸 | 1 | 指定保存圖片的像素尺寸 |
注意:由于硬件差異和軟件實現(xiàn)不同,并非所有設備都支持這些功能。有關檢查應用程序運行的設備上功能的可用性的下一節(jié)會介紹。
檢查功能可用性
在Android設備上設置使用相機功能時,首先要了解的是,并非所有設備都支持所有相機功能。此外,支持特定功能的設備可能會將其支持到不同的級別或不同的選項。因此,我們要決定使用哪些相機功能以及支持到什么程序。做出決定后,我們應該先檢查設備硬件是否支持這些功能,如果功能不可用,給用戶提示。
我們可以通過獲取相機參數(shù)對象的實例并檢查相關方法來檢查相機功能的可用性。以下代碼示例顯示如何獲取Camera.Parameters對象并檢查相機是否支持自動對焦功能:
// 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
}
我們可以使用上面的方法來獲得大多數(shù)相機功能。 Camera.Parameters對象提供了一個getSupported ...(),is... Supported()或getMax ...()方法來確定是否(以及在什么程度上)功能被支持。如果我們的應用程序需要某些相機功能才能正常運行,則可以通過添加應用程序清單來要求它們。當我們聲明使用特定的相機功能(例如閃光燈和自動對焦)時,Google Play將應用程序限制為不支持這些功能的設備上。有關可以在應用程序清單中聲明的??攝像頭功能的列表,請參閱清單功能的 API Features Reference。
使用相機功能
大多數(shù)相機功能都是使用Camera.Parameters對象進行激活和控制的。首先我們可以通過獲取Camera對象的實例,調用getParameters()方法,更改返回的參數(shù)對象,然后將其設置回相機對象獲取的此對象,如以下示例代碼所示
// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
這種方法適用于幾乎所有的相機功能,大多數(shù)參數(shù)可以在獲取Camera對象的實例后隨時更改。在應用程序的相機預覽中,用戶對參數(shù)的更改通常是可見的。在軟件方面,參數(shù)更改可能需要幾幀才能實際生效,因為相機硬件處理新指令,然后發(fā)送更新的圖像數(shù)據(jù)。
重要提示:某些相機功能無法隨意更改。特別是更改相機預覽的大小或方向需要先停止預覽,更改預覽大小,然后重新啟動預覽。從Android 4.0(API級別14)開始,預覽方向可以更改,無需重新啟動預覽。
其他相機功能需要更多的代碼才能實現(xiàn),其中包括:
- 測量和聚焦
- 面部識別
- 長時間視頻
以下部分提供了如何實現(xiàn)這些功能的快速概述。
計量和重點領域
在某些攝影場景中,自動對焦和測光可能無法產生預期的效果。從Android 4.0(API 14級)開始,相機應用程序可以提供額外的控件,允許我們的應用或用戶指定圖像中用于確定焦點或光線級別設置的區(qū)域,并將這些值傳遞到相機硬件以用于捕獲圖像或視頻。
用于測光和對焦的區(qū)域與其他攝像機功能非常相似,我們可以通過Camera.Parameters對象中的方法來控制它們。以下代碼演示了為相機實例設置兩個測光區(qū)域:
// 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);
}
mCamera.setParameters(params);
Camera.Area對象包含兩個數(shù)據(jù)參數(shù):分別是用于指定相機視野內的區(qū)域的Rect對象和權重值,該值指示相機在光測量或對焦計算中應該給出該區(qū)域的重要程度。
Camera.Area 對象中的Rect字段描述了映射到2000 x 2000單位格網格的矩形。坐標-1000,-1000表示相機圖像的左上角,坐標1000,1000表示相機圖像的右下角,如下圖所示。
圖中紅線表示在相機預覽中指定Camera.Area的坐標系。藍色框顯示相機區(qū)域的位置和形狀,其中Rect值為333,333,667,667。
該坐標系的邊界總是對應于在相機預覽中可見的圖像的外邊緣,并且不隨著縮放級別而縮小或擴大。同樣地,使用Camera.setDisplayOrientation()的圖像預覽的旋轉不會重新映射坐標系。
面部檢測
對于包含人物的圖片,臉部通常是圖片中最重要的部分,并且應在捕獲圖像時用于確定焦點和白平衡。Android 4.0(API Level 14)框架提供用于識別人臉和使用臉部識別技術計算圖像設置的API。
注意:當臉部檢測功能正在運行時,setWhiteBalance(String),setFocusAreas(List <Camera.Area>)和setMeteringAreas(List <Camera.Area>)沒有任何作用。
使用相機應用程序中的人臉檢測功能需要幾個常規(guī)步驟:
- 檢查設備是否支持人臉檢測
- 創(chuàng)建一個面部檢測監(jiān)聽器
- 將面部檢測偵聽器添加到您的相機對象
- 預覽后啟動臉部檢測(每次重啟預覽后)
并不是所有設備都支持人臉檢測功能。您可以通過調用getMaxNumDetectedFaces()來檢查此功能是否受支持。此檢查的示例顯示在下面的startFaceDetection()示例方法中。
為了得到通知并對臉部的檢測做出響應,您的相機應用程序必須設置一個用于人臉檢測事件的偵聽器。為此,您必須創(chuàng)建一個監(jiān)聽器類,實現(xiàn)Camera.FaceDetectionListener接口,如下面的示例代碼所示。
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() );
}
}
}
創(chuàng)建此類之后,然后將其設置到應用程序的Camera對象中,如下面的示例代碼所示:
mCamera.setFaceDetectionListener(new MyFaceDetectionListener());
每次啟動(或重啟)相機預覽時,應用程序都必須啟動人臉檢測功能。創(chuàng)建一種開始面部檢測的方法,以便您可以根據(jù)需要調用它,如下面的示例代碼所示。
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();
}
}
每次啟動(或重新啟動)相機預覽時,都必須開始面部檢測。如果使用“創(chuàng)建預覽”類中顯示的預覽類,請在預覽類中將startFaceDetection()方法添加到surfaceCreated()和surfaceChanged()方法,如下面的示例代碼所示。
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
startFaceDetection(); // start face detection feature
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mHolder.getSurface() == null){
// preview surface does not exist
Log.d(TAG, "mHolder.getSurface() == null");
return;
}
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
}
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
startFaceDetection(); // re-start face detection feature
} catch (Exception e){
// ignore: tried to stop a non-existent preview
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
提示:調用startPreview()后,不要忘記調用這個方法。不要在相機應用程序的主要活動的onCreate()方法中嘗試開始人臉檢測,因為在應用程序的執(zhí)行過程中預覽不可用。
Time lapse video
時間延遲視頻允許用戶創(chuàng)建視頻剪輯,組合幾秒或幾分鐘之間拍攝的照片。此功能使用MediaRecorder記錄時間序列的圖像。
要使用MediaRecorder錄制時間延遲視頻,您必須配置錄像機對象,就像錄制正常視頻一樣,將每秒捕獲的幀數(shù)設置為低數(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
這些設置必須作為MediaRecorder的較大配置過程的一部分完成。有關完整配置代碼示例,請參閱Configuring MediaRecorder。配置完成后,您開始錄像,就像錄制正常視頻剪輯一樣。有關配置和運行MediaRecorder的更多信息,請參閱捕獲視頻。