編輯:Android開發實例
前言
本章內容為Android開發者指南的 Framework Topics/Multimedia and Camera/Camera章節,譯為"攝像頭",版本為Android 4.0 r1,翻譯來自:"呆呆大蝦"。
攝像頭
譯者署名: 呆呆大蝦
譯者微博:http://weibo.com/popapa
版本:Android 4.0 r1
原文
http://developer.android.com/guide/topics/media/camera.html
在本文中
需要考慮的問題
概述
Manifest聲明
使用內置的攝像頭應用程序
捕獲圖像的intent
捕獲視頻的intent
接收攝像頭intent的結果
創建攝像頭應用程序
檢測攝像頭硬件
訪問攝像頭
檢查攝像頭feature
創建預覽類
將預覽畫面置入layout
捕獲圖像
捕獲視頻
釋放攝像頭
保存媒體文件
關鍵類
Camera
SurfaceView
MediaRecorder
Intent
參閱
Camera
MediaRecorder
數據存儲
Android框架包含了對多種攝像頭和攝像特性的支持,應用程序可以進行圖片和視頻的捕獲。本文討論了一種快速、簡便的捕獲圖像和視頻的方法,並簡述了一種更高級的可為用戶創建自定義攝像功能的方法。
需要考慮的問題
在讓應用程序使用Android設備的攝像頭之前,應該考慮一些期望如何使用此硬件的問題。
· 攝像頭需求 —— 攝像頭的使用對於應用程序是否確實如此重要,以至於在沒有攝像頭的設備上就不期望安裝此應用了?如果確實如此,應該在manifest中聲明攝像頭需求。
· 快速拍照還是自定義攝像 —— 應用程序如何使用攝像頭?僅僅是對快速拍照和視頻片段感興趣,還是要提供一種使用攝像頭的新方式?對於快速拍照和攝像而言,可以考慮使用內置的攝像頭應用。為了開發一種定制的攝像頭功能,請查看創建攝像頭應用一節。
· 存儲 —— 應用程序產生的圖像和視頻是否期望僅對自身可見,還是可以共享——以便相冊或其它媒體應用也能夠使用?當應用程序被卸載後,還期望圖像和視頻可用麼?請查看保存媒體文件一節來了解如何實現這些選項。
概述
通過Camera API 或攝像頭意圖Intent,Android框架為圖像和視頻捕獲提供支持。下面列出了有關的類:
Camera
此類是控制攝像頭的主要API。在創建攝像頭應用程序時,此類用於拍攝照片或視頻。
SurfaceView
此類用於向用戶提供攝像頭實時預覽功能。
MediaRecorder
此類用於從攝像頭錄制視頻。
Intent
動作類型為MediaStore.ACTION_IMAGE_CAPTURE 或MediaStore.ACTION_VIDEO_CAPTURE 的意圖,可在不直接使用Camera對象的情況下捕獲圖像和視頻。
Manifest聲明
開始開發攝像頭 API的應用之前,應該確保已經在manifest中正確聲明了對攝像頭的使用及其它相關的feature。
· Camera權限——應用程序必須對請求攝像頭的使用權限。
<uses-permission android:name="android.permission.CAMERA" />
注意:如果是通過意圖來使用攝像頭的,應用程序就不必請求本權限。
· Camera Feature——應用程序必須同時聲明對camera feature的使用,例如:
<uses-feature android:name="android.hardware.camera" />
關於攝像頭feature的清單,參閱manifest Feature參考。
在manifest中加入camera feature,將會使得Android Market在沒有攝像頭或不支持指定feature的設備上禁止安裝該應用程序。關於Android Market基於feature過濾的使用詳情,請參閱Android Market和基於Feature的過濾。
如果應用程序可能用到攝像頭或攝像頭feature,但卻不是必需的,則應在manifest中指定包含android:required屬性的feature,並將該屬性設為false:
<uses-feature android:name="android.hardware.camera" android:required="false" />
· 存儲權限——如果應用程序要把圖像或視頻保存到設備的外部存儲上(SD卡),則還必須在manifest中指定如下權限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
· 錄音權限——要用音頻捕獲來錄音,應用程序必須請求音頻捕獲權限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
使用內置的攝像頭應用程序
有一種快捷的方法可以讓應用程序不用額外編寫很多代碼就能實現拍照或攝像,這就是用意圖Intent來調用內置的Android攝像頭應用程序。攝像頭意圖(intent)會請求通過內置攝像應用來捕獲圖像或視頻,並把控制權返回給應用程序。本節展示了如何用這種方法來捕獲圖像。
通常按以下步驟來提交一個攝像頭 intent:
1. 構建一個攝像頭 Intent —— 用以下意圖類型之一,創建一個請求圖像或視頻的Intent :
o MediaStore.ACTION_IMAGE_CAPTURE —— 向內置攝像頭程序請求圖像的意圖活動類型。
o MediaStore.ACTION_VIDEO_CAPTURE —— 向內置攝像頭程序請求視頻的意圖活動類型。
2. 啟動攝像頭 Intent ——用startActivityForResult()方法執行攝像頭 intent。啟動完畢後攝像頭應用的用戶界面就會顯示在屏幕上,用戶就可以拍照或攝像了。
3. 接收Intent結果 —— 在應用程序中設置onActivityResult()方法,用於接收從攝像頭 intent返回的數據。當用戶拍攝完畢後(或者取消操作),系統會調用此方法。
捕獲圖像的intent
如果希望程序以最少的代碼實現拍照功能,利用攝像頭intent捕獲圖像是一條捷徑。圖像捕捉intent還可以包含以下附加信息:
· MediaStore.EXTRA_OUTPUT ——本設置需要一個Uri對象,用於指定存放圖片的路徑和文件名。本設置是可選項,但強烈建議使用。如果未指定本設置值,那麼攝像應用將會把所請求的圖片以默認文件名和路徑進行保存,並將數據置入intent的Intent.getData()部分返回。
以下例子演示了如何構建並執行一個圖像捕獲intent。此例中的getOutputMediaFileUri() 方法引自保存媒體文件中的例程代碼。.
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private Uri fileUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 創建拍照Intent並將控制權返回給調用的程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
// 創建保存圖片的文件
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
// 設置圖片文件名
// 啟動圖像捕獲Intent
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
startActivityForResult() 方法執行完畢後,用戶將看到內置攝像頭應用程序的界面。用戶拍照完畢(或取消操作)後,用戶界面返回應用程序,這時必須截獲onActivityResult()方法來接收intent的返回結果並執行後續操作。關於如何接收完整的intent,請參閱接收攝像頭 Intent的結果。
捕獲視頻的intent
如果希望程序以最少的代碼實現攝像功能,利用攝像頭 intent捕獲視頻是一條捷徑。視頻捕捉intent可以包含以下附帶信息:
· MediaStore.EXTRA_OUTPUT —— 本設置需要一個Uri,用於指定保存視頻的路徑和文件名。本設置是可選項,但強烈建議使用。如果未指定本設置值,那麼攝像應用將會把所請求的視頻以默認文件名和路徑進行保存,並將數據置入intent的Intent.getData()部分返回。
· MediaStore.EXTRA_VIDEO_QUALITY ——本值用0表示最低品質及最小的文件尺寸,用1表示最高品質和較大的文件尺寸。
· MediaStore.EXTRA_DURATION_LIMIT ——本值用於限制所捕獲視頻的長度,以秒為單位。
· MediaStore.EXTRA_SIZE_LIMIT —— 本值用於限制所捕獲視頻的文件尺寸,以字節為單位。
以下例子演示了如何構建並執行一個視頻捕獲intent。本例中的getOutputMediaFileUri()方法引自保存媒體文件中的例程代碼。
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
private Uri fileUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 創建新的Intent
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
// 創建保存視頻的文件
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
// 設置視頻文件名
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
// 設置視頻的品質為高
// 啟動視頻捕獲Intent
startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
}
startActivityForResult() 方法執行完畢後,用戶將看到一個改動過的攝像程序界面。用戶攝像完畢(或取消操作)後,用戶界面返回應用程序,這時必須監聽onActivityResult()方法來接收intent的返回結果並執行後續操作。關於如何接收完整的intent,請參閱下一節。
接收攝像頭 intent的結果
一旦已構建並運行了圖像或視頻的攝像頭intent,應用程序就必須進行設置,以接收intent返回的結果。本節展示了如何監聽攝像頭intent的回調方法,以便應用程序對捕獲到的圖片及視頻進行進一步的處理。
要接收intent的返回結果,必須覆蓋啟動intent的activity中的onActivityResult()方法。以下例子演示了如何覆蓋onActivityResult()來獲取上述章節例程中的圖像捕獲intent或視頻捕獲intent的結果。
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// 捕獲的圖像保存到Intent指定的fileUri
Toast.makeText(this, "Image saved to:\n" +
data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
// 用戶取消了圖像捕獲
} else {
// 圖像捕獲失敗,提示用戶
}
}
if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// 捕獲的視頻保存到Intent指定的fileUri
Toast.makeText(this, "Video saved to:\n" +
data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
// 用戶取消了視頻捕獲
} else {
// 視頻捕獲失敗,提示用戶
}
}
}
一旦activity接收到成功的結果,就說明捕獲到的圖像或視頻已保存到指定位置了,應用程序就可對其進行訪問。
創建攝像頭應用程序
有些開發人員可能需要自定義外觀的攝像頭用戶界面,或者需要提供特殊的功能。相比使用intent而言,創建定制的攝像activity需要編寫更多的代碼,不過也能向用戶提供更吸引人的使用感受。
通常按照以下步驟創建一個定制的攝像界面:
· 檢測並訪問攝像頭 —— 創建代碼以檢查攝像頭存在與否並請求訪問。
· 創建預覽類 —— 創建繼承自SurfaceView 並實現SurfaceHolder 接口的攝像預覽類。此類能預覽攝像的實時圖像。
· 建立預覽布局Preview Layout —— 一旦有了攝像預覽類,即可創建一個view layout,用於把預覽畫面與設計好的用戶界面控件融合在一起。
· 為捕獲設置偵聽器Listener —— 將用戶界面控件連接到listener,使其能響應用戶操作開始捕獲圖像或視頻,比如按下按鈕。
· 捕獲並保存文件 —— 建立捕獲圖片或視頻並保存到輸出文件的代碼。
· 釋放攝像頭 —— 攝像頭使用完畢後,應用程序必須正確地將其釋放,便於其它程序的使用。
攝像頭硬件是一個共享資源,必須對其進行細心的管理,因此需要使用它的應用程序之間不能發生沖突。下一節將會討論如何檢測攝像頭硬件、如何請求訪問攝像頭、使用完畢如何釋放。
警告:應用程序用完攝像頭後,請記得調用Camera.release()釋放Camera對象!如果某應用程序未能正確釋放攝像頭,所有後續訪問攝像頭的嘗試(包括此應用程序自身)都將會失敗,並可能導致程序被強行關閉。
檢測攝像頭硬件
如果應用程序未利用manifest聲明對攝像頭需求進行特別指明,則應該在運行時檢查一下攝像頭是否可用。可用PackageManager.hasSystemFeature()方法來進行這種檢查,代碼示例如下:
/** 檢查設備是否提供攝像頭 */
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// 攝像頭存在
return true;
} else {
// 攝像頭不存在
return false;
}
}
Android設備可能擁有多個攝像頭,比如向後的攝像頭用於拍照、向前的攝像頭用於攝像。Android 2.3 (API Level 9) 以上版本允許利用Camera.getNumberOfCameras()方法來檢查設備可用攝像頭的數量。
訪問攝像頭
如果在運行程序的設備上已經檢測到了攝像頭,則必須通過獲取一個Camera的實例來請求對其訪問(除非使用了用於訪問攝像頭的intent)。
可用Camera.open()方法來訪問主攝像頭,並確保捕獲全部的異常,示例代碼如下:
/** 安全獲取Camera對象實例的方法*/
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // 試圖獲取Camera實例
}
catch (Exception e){
// 攝像頭不可用(正被占用或不存在)
}
return c; // 不可用則返回null
}
警告: 每次使用Camera.open()時都要檢查異常。如果攝像頭被占用或者不存在,未檢查異常將會導致應用程序被系統強行關閉。
在運行Android 2.3 (API Level 9) 以上版本的設備上,可以用Camera.open(int)訪問指定的攝像頭。在擁有多於一個攝像頭的設備上,以上示例代碼將會訪問第一個也即朝後的那個攝像頭。
檢查攝像頭feature
一旦獲得了攝像頭的訪問權,就可以通過Camera.getParameters()方法來獲取更多信息,檢查返回的Camera.Parameters對象可查看攝像頭所支持的feature。如果正在使用API Level 9以上版本,可用Camera.getCameraInfo()來確定攝像頭朝前還是朝後以及圖像的方向。
創建預覽類
為了方便拍照或攝像,用戶必須能看到攝像頭所拍攝的畫面。攝像頭預覽類就是一種能夠顯示攝像頭實時數據的SurfaceView,用戶可以調整並捕獲圖片和視頻。
以下示例代碼演示了如何創建一個基本的攝像頭預覽類,它可被嵌入一個View布局中。為了捕捉view創建和銷毀時的回調事件,此類實現了SurfaceHolder.Callback,這在指定攝像頭預覽的輸入時需要用到。
/** 基本的攝像頭預覽類 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// 安裝一個SurfaceHolder.Callback,
// 這樣創建和銷毀底層surface時能夠獲得通知。
mHolder = getHolder();
mHolder.addCallback(this);
// 已過期的設置,但版本低於3.0的Android還需要
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// surface已被創建,現在把預覽畫面的位置通知攝像頭
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// 空代碼。注意在activity中釋放攝像頭預覽對象 }
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// 如果預覽無法更改或旋轉,注意此處的事件
// 確保在縮放或重排時停止預覽
if (mHolder.getSurface() == null){
// 預覽surface不存在
return;
}
// 更改時停止預覽
try {
mCamera.stopPreview();
} catch (Exception e){
// 忽略:試圖停止不存在的預覽
}
// 在此進行縮放、旋轉和重新組織格式
// 以新的設置啟動預覽
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
將預覽畫面置入layout
上節例程所述的攝像預覽類必須被放入一個activity的layout中,連同其它用戶界面控件一起,實現拍照或攝像功能。本節展示了如何為預覽創建一個簡單的layout和activity。
以下layout代碼提供了一個非常簡單的view,用於顯示一個攝像預覽畫面。在此例中,FrameLayout元素用於容納攝像預覽類。利用此類layout,可以把附加的圖片信息或控件疊加到實時預覽畫面上。
<?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>
在大多數設備上,缺省的攝像預覽方向是橫向的。此例中的layout指定了橫向(landscape)布局,下面的代碼還把應用程序的方向也改為了橫向。為了簡化攝像預覽畫面的刷新,應該在manifest中增加如下內容,把應用程序的預覽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>
注意: 攝像預覽畫面並不是一定要橫向顯示。自Android 2.2 (API Level 8) 開始,可以利用setDisplayOrientation() 方法來旋轉預覽畫面。為了讓預覽方向跟隨手機方向的變化而改變,可以在預覽類的surfaceChanged()方法中實現,先用Camera.stopPreview()停止預覽,改變方向後再用Camera.startPreview()開啟預覽。
在攝像view 的activity中,請把預覽類添加到上述的FrameLayout元素中。當攝像頭暫停使用或者關閉時,攝像activity還必須確保將其釋放。以下例子展示了如何修改攝像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);
// 創建Camera實例
mCamera = getCameraInstance();
// 創建Preview view並將其設為activity中的內容
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(id.camera_preview);
preview.addView(mPreview);
}
}
注意:上例中的getCameraInstance()方法引用了訪問攝像頭中的方法示例。
捕獲圖像
一旦創建了預覽類和顯示它的view layout,就可以開始在程序中捕獲圖片了。必須在程序代碼中為用戶界面控件設置listener,使其可響應用戶操作進行拍照。
可以通過Camera.takePicture()方法來獲取圖片,此方法用到三個參數並從攝像頭接收數據。如果要以JPEG的格式接收數據,必須實現Camera.PictureCallback接口,以接收圖片數據並寫入文件。以下代碼展示了Camera.PictureCallback接口的簡單例子,實現了從攝像頭接收圖片並保存。
private PictureCallback mPicture = 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());
}
}
};
通過調用Camera.takePicture()方法,觸發器捕獲了一張圖片。 以下例程展示了如何在按鈕View.OnClickListener的中調用此方法。
// 在Capture按鈕中加入listener
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// 從攝像頭獲取圖片
mCamera.takePicture(null, null, mPicture);
}
}
);
注意:下文例程中的mPicture 成員將會引用上述代碼。
警告:當應用程序使用完攝像頭之後,請記得調用Camera.release()釋放Camera 對象! 關於如何釋放攝像頭的詳情,請參閱釋放攝像頭。
捕獲視頻
Android框架的視頻捕捉需要對Camera對象進行仔細的管理,還要與MediaRecorder類一起協同工作。使用Camera錄制視頻時,必須管理好Camera.lock()與Camera.unlock()的調用,使得MediaRecorder能夠順利訪問攝像頭硬件,並且還要進行Camera.open()和Camera.release()調用。
注意:自Android 4.0 (API level 14) 開始,Camera.lock() 和 Camera.unlock() 調用由系統自動管理。
與用攝像頭拍照不同,視頻捕獲必需十分精確地按順序進行調用。必須按照特定的順序來執行,應用程序才能成功地准備並捕獲視頻,詳細步驟如下。
1. 打開攝像頭 —— 用Camera.open() 來獲得一個camera對象的實例。
2. 連接預覽 —— 用Camera.setPreviewDisplay()將camera連接到一個SurfaceView ,准備實時預覽。
3. 開始預覽 —— 調用 Camera.startPreview() 開始顯示實時攝像畫面。
4. 開始錄制視頻 —— 嚴格按照以下順序執行才能成功錄制視頻:
a. 解鎖Camera —— 調用Camera.unlock()解鎖,便於MediaRecorder 使用攝像頭。
b. 配置MediaRecorder —— 按照如下順序調用MediaRecorder 中的方法。詳情請參閱MediaRecorder 參考文檔。
1. setCamera() —— 用當前Camera實例將攝像頭用途設置為視頻捕捉。
2. setAudioSource() —— 用MediaRecorder.AudioSource.CAMCORDER設置音頻源。
3. setVideoSource() —— 用MediaRecorder.VideoSource.CAMERA設置視頻源。
4. 設置視頻輸出格式和編碼格式。對於Android 2.2 (API Level 8) 以上版本,使用MediaRecorder.setProfile 方法,並用CamcorderProfile.get()來獲取一個profile實例。對於Android prior to 2.2以上版本,必須設置視頻輸出格式和編碼參數:
i. setOutputFormat() —— 設置輸出格式,指定缺省設置或MediaRecorder.OutputFormat.MPEG_4。
ii. setAudioEncoder() —— 設置聲音編碼類型。指定缺省設置或MediaRecorder.AudioEncoder.AMR_NB。
iii. setVideoEncoder() —— 設置視頻編碼類型,指定缺省設置或者 MediaRecorder.VideoEncoder.MPEG_4_SP。
5. setOutputFile() —— 用getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()設置輸出文件,見保存媒體文件一節中的方法示例。
6. setPreviewDisplay() —— 用上面連接預覽中設置的對象來指定應用程序的SurfaceView 預覽layout元素。
警告: 必須按照如下順序調用MediaRecorder的下列配置方法,否則應用程序將會引發錯誤,錄像也將失敗。
c. 准備MediaRecorder —— 調用MediaRecorder.prepare()設置配置,准備好MediaRecorder 。
d. 啟動MediaRecorder —— 調用MediaRecorder.start()開始錄制視頻。
5. 停止錄制視頻 —— 按照順序調用以下方法,才能成功完成視頻錄制:
a. 停止MediaRecorder —— 調用MediaRecorder.stop()停止錄制視頻。
b. 重置MediaRecorder —— 這是可選步驟,調用MediaRecorder.reset()刪除recorder中的配置信息。
c. 釋放MediaRecorder —— 調用MediaRecorder.release()釋放MediaRecorder。
d. 鎖定攝像頭 —— 用Camera.lock()鎖定攝像頭,使得以後MediaRecorder session能夠使用它。自Android 4.0 (API level 14)開始,不再需要本調用了,除非MediaRecorder.prepare()調用失敗。
6. 停止預覽 —— activity使用完攝像頭後,應用Camera.stopPreview()停止預覽。
7. 釋放攝像頭 —— 使用Camera.release()釋放攝像頭,使其它應用程序可以使用它。
注意: 也可以不必先創建攝像頭預覽就使用MediaRecorder,並跳過本節開始的幾步。不過,因為用戶一般都希望在開始錄像前看到預覽畫面,這裡就不討論那類過程了。
配置MediaRecorder
在使用 MediaRecorder 類進行錄像時,必須先按照特定順序進行配置,然後調用MediaRecorder.prepare()方法檢查並執行這些配置。以下例程演示了如何為錄像正確配置並准備MediaRecorder 類。
private boolean prepareVideoRecorder(){
mCamera = getCameraInstance();
mMediaRecorder = new MediaRecorder();
// 第1步:解鎖並將攝像頭指向MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// 第2步:指定源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 第3步:指定CamcorderProfile(需要API Level 8以上版本)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
// 第4步:指定輸出文件
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// 第5步:指定預覽輸出
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// 第6步:根據以上配置准備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 Level 8) 之前的版本,則必須直接指定輸出格式和編碼格式,而不是使用CamcorderProfile。以下代碼演示了這種方式:
// 第3步:設置輸出格式和編碼格式(針對低於API Level 8版本)
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
MediaRecorder中以下有關視頻錄制的參數都給出了缺省值,當然也可以在應用程序中修改這些設置:
· setVideoEncodingBitRate()
· setVideoSize()
· setVideoFrameRate()
· setAudioEncodingBitRate()
· setAudioChannels()
· setAudioSamplingRate()
開始和停止MediaRecorder
使用MediaRecorder類開始和停止視頻錄制時,必須遵循以下特定順序。
1. 用Camera.unlock()解鎖攝像頭
2. 如上代碼所示配置MediaRecorder
3. 用MediaRecorder.start()開始錄制
4. 記錄視頻
5. 用MediaRecorder.stop()停止錄制
6. 用MediaRecorder.release()釋放media recorder
7. 用Camera.lock()鎖定攝像頭
以下例程演示了如何觸發按鈕並用camera和MediaRecorder類正確地開始和停止視頻錄制。
注意: 視頻錄制完畢後請不要釋放camera,否則預覽將會停止。
private boolean isRecording = false;
// 為Capture按鈕加入listener
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isRecording) {
// 停止錄像並釋放camera
mMediaRecorder.stop(); // 停止錄像
releaseMediaRecorder(); // 釋放MediaRecorder對象
mCamera.lock(); // 將控制權從MediaRecorder 交回camera
// 通知用戶錄像已停止
setCaptureButtonText("Capture");
isRecording = false;
} else {
// 初始化視頻camera
if (prepareVideoRecorder()) {
// Camera已可用並解鎖,MediaRecorder已就緒,
// 現在可以開始錄像
mMediaRecorder.start();
// 通知用戶錄像已開始
setCaptureButtonText("Stop");
isRecording = true;
} else {
// 准備未能完成,釋放camera
releaseMediaRecorder();
// 通知用戶
}
}
}
}
);
注意: 在上例中,prepareVideoRecorder() 方法引用了配置MediaRecorder。中的示例代碼。此方法實現了鎖定camera、配置和准備MediaRecorder 實例。
釋放攝像頭
攝像頭是設備上所有應用程序共享使用的資源。應用程序可以在獲得Camera實例後使用攝像頭,停止使用後請務必注意釋放攝像頭對象,應用程序暫停時(Activity.onPause())也是如此。如果某應用程序未能正確地釋放攝像頭,則所有後續訪問攝像頭的嘗試(包括該應用程序自身)都將會失敗,並可能導致應用程序被強行關閉。
用Camera.release()方法可以釋放Camera對象的實例,代碼示例如下。
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder(); // 如果正在使用MediaRecorder,首先需要釋放它。
releaseCamera(); // 在暫停事件中立即釋放攝像頭
}
private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // 清除recorder配置
mMediaRecorder.release(); // 釋放recorder對象
mMediaRecorder = null;
mCamera.lock(); // 為後續使用鎖定攝像頭
}
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // 為其它應用釋放攝像頭
mCamera = null;
}
}
}
警告:如果某應用程序未能正確釋放攝像頭,所有後續訪問攝像頭的嘗試(包括該應用程序自身)都將會失敗,並可能會導致應用程序被強行關閉。
保存媒體文件
諸如圖片和視頻這些由用戶創建的媒體文件,應該保存到設備外部存儲的目錄中(SD卡)去,以節省系統空間,並使用戶離開設備時也能訪問這些文件。設備上有很多可用於存儲媒體文件的目錄,但作為開發人員只應考慮兩個標准的位置:
· Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) —— 本方法返回標准的、共享的、系統建議的存放位置,用於存放圖片和視頻文件。本目錄是共享的(public),因此其它應用程序可以很容易地查找、讀取、修改、刪除存於此處的文件。即使應用程序被用戶卸載,存於此處的媒體文件也不會被刪除。為了避免與已有的圖片和視頻相沖突,應該在此目錄下為自己的媒體文件創建一個子目錄,如下代碼所示。本方法自Android 2.2 (API Level 8) 起啟用,更早API版本的也有類似的調用,請參閱保存共享文件。
· Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) —— 本方法返回一個標准的、用於存放圖片和視頻的位置,該存放位置與應用程序相關聯。如果應用程序被卸載,則存於此處的文件將會被刪除。對存於此處的文件不會增加訪問權限控制,其它應用程序也可以讀取、修改、刪除文件。
以下例程演示了如何為媒體文件創建一個File或Uri存放位置,通過Intent調用攝像頭時可以使用該文件,創建攝像應用時也可以使用它。
public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;
/** 為保存圖片或視頻創建文件Uri */
private static Uri getOutputMediaFileUri(int type){
return Uri.fromFile(getOutputMediaFile(type));
}
/** 為保存圖片或視頻創建File */
private static Uri getOutputMediaFile(int type){
// 安全起見,在使用前應該
// 用Environment.getExternalStorageState()檢查SD卡是否已裝入
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MyCameraApp");
// 如果期望圖片在應用程序卸載後還存在、且能被其它應用程序共享,
// 則此保存位置最合適
// 如果不存在的話,則創建存儲目錄
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
// 創建媒體文件名
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 Level 8) 版本啟用。如果目標設備使用較早期版本的Android,請用Environment.getExternalStorageDirectory() 代替。詳情請參閱保存共享文件。
關於在Android設備上保存文件的詳細信息,請參閱數據存儲。
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
前言 Content Provider為存儲數據和獲取數據提
輸入法編輯器(IME)是讓用戶輸入文本的控件。Android提供了一個可擴展的的輸入法的框架,它允許應用程序給用戶提供另外的輸入法,如軟鍵盤或語音輸入。這些輸入法
Android的允許通過添加不同種類的處理圖像效果。可以輕松地應用圖像處理技術來增加某些種類的圖像效果。這些影響可能是亮度,黑暗中,灰度轉換等Android提供了Bitm