Android:Camera

weixin_34292959發表於2013-11-21

Android Camera開發

    Android手機關於Camera的使用,一是拍照,二是攝像,由於Android提供了強大的元件功能,為此對於在Android手機系統上進行Camera的開發,我們可以使用兩類方法:一是藉助Intent和MediaStroe呼叫系統Camera App程式來實現拍照和攝像功能,二是根據Camera API自寫Camera程式。由於自寫Camera需要對Camera API瞭解很充分,而且對於通用的拍照和攝像應用只需要藉助系統Camera App程式就能滿足要求了,為此先從呼叫系統Camera App應用開始來對Android Camera做個簡單的使用小結。

一、 呼叫系統Camera App實現拍照和攝像功能
不是專門的Camera應用,一般用到Camera的需求就是獲取照片或者視訊,比如微博分享、隨手記等,對於在Symbian系統上通過簡單地呼叫系統自帶的Camera APP來實現該功能是做不到的,但是Android系統強大的元件特性,使得應用開發者只需通過Intent就可以方便的開啟系統自帶的Camera APP,並通過MediaStroe方便地獲取照片和視訊的檔案路徑。具體我們還是用程式碼來說話吧:
例1、 實現拍照
在選單或按鈕的選擇操作中呼叫如下程式碼,開啟系統自帶Camera APP,並傳遞一個拍照儲存的路徑給系統應用程式,具體如下:
imgPath = "/sdcard/test/img.jpg";
//必須確保資料夾路徑存在,否則拍照後無法完成回撥
File vFile = new File(imgPath);
if(!vFile.exists())
{
File vDirPath = vFile.getParentFile(); //new File(vFile.getParent());
vDirPath.mkdirs();
}
Uri uri = Uri.fromFile(vFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, SystemCapture);
上面我們使用的是startActivityForResult,所以最好需要過載void onActivityResult(int requestCode, int resultCode, Intent data)函式,不過因為當傳入檔案路徑的的情況下,data返回引數是null值,只要resultCode為RESULT_OK,則上述程式碼中/sdcard/test/img.jpg的圖片檔案就是最新的照片檔案。所以我們在這裡只需給出如下簡單的程式碼,將其顯示到ImageView中
if (resultCode == RESULT_OK)
{
iViewPic.setImageURI(Uri.fromFile(new File(imgPath)));
}
假設不傳引數MediaStore.EXTRA_OUTPUT的情況下,onActivityResult函式在resultCode為RESULT_OK的情況下,data返回的引數是經過實際拍攝照片經過縮放的影像資料,可以通過類似如下方法來列印縮放影像的尺寸
if (resultCode == RESULT_OK)
{
Bitmap bmp = (Bitmap)data.getExtras().get("data");
Log.d("Test", "bmp width:" + bmp.getWidth() + ", height:" + bmp.getHeight());
}
另外假如僅僅是呼叫系統照相機拍照,不關心拍照結果,則可以簡單使用如下程式碼
Intent intent = new Intent(); //呼叫照相機
intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
startActivity(intent);
備註:上面設定MediaStore.EXTRA_OUTPUT的方法,經過手機實測除了我們設定的路徑下有照片外,在手機儲存卡上也會儲存一份照片,預設目錄為sdcard/dcim/camera下面,我曾經嘗試著想如果每次返回可以取得/sdcard/dcim/camera下面的路徑就好了,但是目前看來沒辦法直接獲得,可以藉助MediaStroe每次去查詢最後一條照片記錄,應該也是可行的。
例2、 實現攝像
在攝像功能時,嘗試著設定MediaStore.EXTRA_OUTPUT以傳入類似拍照時的檔案路徑,結果在我的測試真機上,那個視訊檔案居然是一個0k的空檔案,最後通過類似如下程式碼實現
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//引數設定可以省略
startActivityForResult(intent, SystemVideoRecord);
在onActivityResult函式中進行如下程式碼呼叫
Uri videoUri = data.getData();
//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE };
Cursor cursor = managedQuery(videoUri, null, null, null, null);
cursor.moveToFirst();//這個必須加,否則下面讀取會報錯
int num = cursor.getCount();
String recordedVideoFilePath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA));
int recordedVideoFileSize = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));
iResultText.setText(recordedVideoFilePath);
Log.i("videoFilePath", recordedVideoFilePath);
Log.i("videoSize", ""+recordedVideoFileSize);
上面的返回引數data,也會因為使用者是否設定MediaStore.EXTRA_OUTPUT引數而改變,假設沒有通過EXTRA_OUTPUT設定路徑,data.getData返回的Uri為content://media/external/video/media/*,*個數字,代表具體的記錄號,通過managedQuery可以獲取到路徑,假如設定了EXTRA_OUTPUT的話(比如/sdcard/test.3gp),則data.getData返回的Uri則為file:///sdcard/test.3gp,但是該檔案居然是空白內容(不知道是不是跟手機有關,也沒有在其它手機上驗證過)。

二、 根據Camera API實現自己的拍照和攝像程式
通過上面對呼叫系統Camera App實現拍照和攝像功能的例子,我們發現雖然能夠滿足我們的需求,但是畢竟自由度降低了,而且拍照的介面就是系統的樣子,現在很多拍照程式,比如火爆的Camera 360軟體等,就需要根據SDK提供的Camera API來編寫自己的程式。
(1)準備工作
上面呼叫系統Camera App,我們壓根不需要任何許可權,但是這裡用Camera API,就必須在manifest內宣告使用許可權,通常由以下三項
<uses-permission android:name = "android.permission.CAMERA" />
<uses-feature android:name = "android.hardware.camera" />
<uses-feature android:name = "android.hardware.camera.autofocus" />
一般拍照和攝像的時候需要寫到sd卡上,所以還有一向許可權宣告如下
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
真做攝像功能時,需要音訊錄製和視訊錄製功能,所以又需要下面兩項許可權宣告
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
另外使用Camera API拍照或攝像,都需要用到預覽,預覽就要用到SurfaceView,為此Activity的佈局中必須有SurfaceView。
(2)拍照流程
上面簡單介紹了下準備工作,下面結合拍照過程中的需要用到的API對拍照流程做下簡單描述
1、在Activity的OnCreate函式中設定好SurfaceView,包括設定SurfaceHolder.Callback物件和SurfaceHolder物件的型別,具體如下
SurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);
SurfaceHolder mSurfaceHolder = mpreview.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
2、在SurfaceHolder.Callback的surfaceCreated函式中,使用Camera的Open函式開機攝像頭硬體,這個API在SDK 2.3之前,是沒有引數的,2.3以後支援多攝像頭,所以開啟前可以通過getNumberOfCameras先獲取攝像頭數目,再通過getCameraInfo得到需要開啟的攝像頭id,然後傳入Open函式開啟攝像頭,假如攝像頭開啟成功則返回一個Camera物件,否則就丟擲異常;
3、開啟成功的情況下,在SurfaceHolder.Callback的surfaceChanged函式中呼叫getParameters函式得到已開啟的攝像頭的配置引數Parameters物件,如果有需要就修改物件的引數,然後呼叫setParameters函式設定進去(SDK2.2以後,還可以通過Camera::setDisplayOrientation設定方向);
4、同樣在surfaceChanged函式中,通過Camera::setPreviewDisplay為攝像頭設定SurfaceHolder物件,設定成功後呼叫Camera::startPreview函式開啟預覽功能,上面3,4兩步的程式碼可以如下所示
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
//已經獲得Surface的width和height,設定Camera的引數
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(w, h);
List<Size> vSizeList = parameters.getSupportedPictureSizes();
for(int num = 0; num < vSizeList.size(); num++)
{
Size vSize = vSizeList.get(num);
}
if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
{
//如果是豎屏
parameters.set("orientation", "portrait");
//在2.2以上可以使用
//camera.setDisplayOrientation(90);
}
else
{
parameters.set("orientation", "landscape");
//在2.2以上可以使用
//camera.setDisplayOrientation(0);
}
camera.setParameters(parameters);
try {
//設定顯示
camera.setPreviewDisplay(holder);
} catch (IOException exception) {
camera.release();
camera = null;
}
//開始預覽
camera.startPreview();
}
5、假設要支援自動對焦功能,則在需要的情況下,或者在上述surfaceChanged呼叫完startPreview函式後,可以呼叫Camera::autoFocus函式來設定自動對焦回撥函式,該步是可選操作,有些裝置可能不支援,可以通過Camera::getFocusMode函式查詢。程式碼可以參考如下:
// 自動對焦
camera.autoFocus(new AutoFocusCallback()
{
@Override
public void onAutoFocus(boolean success, Camera camera)
{
if (success)
{
// success為true表示對焦成功,改變對焦狀態影像
ivFocus.setImageResource(R.drawable.focus2);
}
}
});
6、在需要拍照的時候,呼叫takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函式來完成拍照,這個函式中可以四個回撥介面,ShutterCallback是快門按下的回撥,在這裡我們可以設定播放“咔嚓”聲之類的操作,後面有三個PictureCallback介面,分別對應三份影像資料,分別是原始影像、縮放和壓縮影像和JPG影像,影像資料可以在PictureCallback介面的void onPictureTaken(byte[] data, Camera camera)中獲得,三份資料相應的三個回撥正好按照引數順序呼叫,通常我們只關心JPG影像資料,此時前面兩個PictureCallback介面引數可以直接傳null;
7、每次呼叫takePicture獲取影像後,攝像頭會停止預覽,假如需要繼續拍照,則我們需要在上面的PictureCallback的onPictureTaken函式末尾,再次掉喲更Camera::startPreview函式;
8、在不需要拍照的時候,我們需要主動呼叫Camera::stopPreview函式停止預覽功能,並且呼叫Camera::release函式釋放Camera,以便其他應用程式呼叫。SDK中建議放在Activity的Pause函式中,但是我覺得放在surfaceDestroyed函式中更好,示例程式碼如下
// 停止拍照時呼叫該方法
public void surfaceDestroyed(SurfaceHolder holder)
{
// 釋放手機攝像頭
camera.release();
}
以上就是自己實現拍照程式的的流程,一般還可以還可以獲取預覽幀的影像資料,可以分別通過Camera::setPreviewCallback和Camera::setOneShotPreviewCallback來設定每幀或下一幀影像資料的回撥,這裡就不做展開了。
(3)攝像流程
攝像流程也是需要預覽的,而且流程上與拍照流程在起始的1~4步流程和結束的8流程是一樣的,唯一不同的是6和7兩個步驟,至於5自動對焦本身就是可選的,在攝像流程也沒必要。
6、開啟視訊錄製,需要建立一個MediaRecorder物件,並呼叫Camera::unLock操作解鎖攝像頭,因為預設Camera都是鎖定的,只有解鎖後MediaRecorder等多媒體程式呼叫,並設定一些引數,然後呼叫MediaRecorder:: start開啟錄製具體可以參閱如下程式碼:
MediaRecorder mMediaRecorder = new MediaRecorder();
// Unlock the camera object before passing it to media recorder.
camera.unlock();
mMediaRecorder.setCamera(camera);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(mProfile);
mMediaRecorder.setMaxDuration(100000);//ms為單位
long dateTaken = System.currentTimeMillis();
Date date = new Date(dateTaken);
SimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.video_file_name_format));
String title = dateFormat.format(date);
String filename = title + ".3gp"; // Used when emailing.
String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
String filePath = cameraDirPath + "/" + filename;
File cameraDir = new File(cameraDirPath);
cameraDir.mkdirs();
mMediaRecorder.setOutputFile(filePath);
try {
mMediaRecorder.prepare();
mMediaRecorder.start(); // Recording is now started
} catch (RuntimeException e) {
Log.e(TAG, "Could not start media recorder. ", e);
return;
}
7、上面設定了最大間隔為100s,當100是視訊錄製結束,錄製就會被停止,如果沒有設時長和檔案大小限制,那麼通常需要呼叫MediaRecorder:: stop函式主動停止視訊的錄製,並將Camera物件通過lock函式繼續加鎖,示例程式碼如下
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
if(camera != null)
camera.lock();
之後的操作根據互動要麼重新錄製要麼就釋放Camera物件回到拍照流程的8步驟了。在這裡就不做贅述了。
使用和整理過程中,由於英文不太好,非常感謝網上的一篇SDK中文翻譯,連結地址如下
http://blog.csdn.net/raindrophust/article/details/6205038
另外Android開發,最佳借鑑,我覺得還是原始碼,Camera的很多引數和使用方法可以參照原始碼中Camera APP的原始碼,目錄為packages\apps\Camera。

參考連結:http://www.cnblogs.com/franksunny/archive/2011/11/17/2252926.html

Android Camera/攝像頭 相關知識總結貼:http://www.apkbus.com/forum.php?mod=viewthread&tid=143852

程式碼下載連結:http://www.apkbus.com/android-150798-1-1.html

相關文章