Camera 簡介
講解編解碼之前先對Camera進行簡單的介紹脸秽,本篇介紹完之后只能保證小白會用Camera預(yù)覽畫面,其他的Camera知識會后續(xù)講解臭胜。
考慮兼容性依然介紹Camera,目錄為android.hardware.Camera,可以看到從api21開始這個類已經(jīng)被標(biāo)記為過時呈础,谷歌大大推薦使用android.hardware.Camera2,但是Camera2要從api21才支持橱健,但現(xiàn)在大部分開發(fā)還必須以4.+為基礎(chǔ)進行開發(fā)而钞,所以也只能不聽google的堅持使用Camera了。
借助Camera可以利用設(shè)備的相機來預(yù)覽畫面拘荡,拍照和拍視頻臼节。要使用Camera需要在Manifest文件中添加Manifest.permission.CAMERA 權(quán)限同時如果要進行自動對焦,還需要特性聲明。
完整地權(quán)限和特性聲明設(shè)置為:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
//存儲權(quán)限也需要
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
注意:如果你只是想簡單的實現(xiàn)拍照和拍照視頻功能网缝,可以利用Intent打開系統(tǒng)提供的功能巨税。MediaStore.ACTION_IMAGE_CAPTURE 拍攝照片;MediaStore.ACTION_VIDEO_CAPTURE 拍攝視頻粉臊;
利用Camera想要拍照草添,需要如下使用步驟:
1 利用open(int)獲取Camera實例
2 利用getParameters()獲取默認設(shè)置,如果需要利用setParameters(Camera.Parameters)進行參數(shù)設(shè)置
3 利用setDisplayOrientation(int)函數(shù)設(shè)置正確的預(yù)覽方向
4 想要預(yù)覽扼仲,需要配合SurfaceView远寸,利用setPreviewDisplay(SurfaceHolder)設(shè)置SurfaceView的SurfaceHolder用于預(yù)覽。
5 調(diào)用startPreview()開始預(yù)覽犀盟,拍照之前必須已經(jīng)開始預(yù)覽
6 takePicture 拍攝照片
7 調(diào)用takePickture后預(yù)覽會停止而晒,想要繼續(xù)預(yù)覽需要調(diào)用startPreview()函數(shù)
8 調(diào)用stopPreview()停止預(yù)覽
9 調(diào)用release()釋放資源,為了節(jié)省資源在Activity.onPause是調(diào)用停止預(yù)覽阅畴,在onResume是開始預(yù)覽倡怎。
2 打開相機
檢查是否有相機
/** 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;
}
}
獲取相機數(shù)目:
Camera.getNumberOfCameras()
open或open(int)打開相機
open開啟默認相機(后置相機),open(int)開啟特定相機贱枣,打開相機是可能失敗的监署,所以一定要檢查相機是否打開成功,判斷Camera是否為null纽哥, mCamera = Camera.open(cameraID);
后置相機和前置相機id常量:CameraInfo.CAMERA_FACING_BACK钠乏, CameraInfo.CAMERA_FACING_FRONT
打開特定相機Camera.open(cameraid)。
Camera.getCameraInfo() 可以獲取CameraInfo春塌,可以知道相機是位于前面還是后面晓避。
public static class CameraInfo {
public static final int CAMERA_FACING_BACK = 0;
public static final int CAMERA_FACING_FRONT = 1;
/**
* 這個值就是標(biāo)明相機是前置還是后置
* CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
*/
public int facing;
public int orientation;
};
3 相機預(yù)覽方向設(shè)置
相機的方向(0,90,180,270)有四種只壳,預(yù)覽需要設(shè)置正確的方向和尺寸俏拱,預(yù)覽的圖片才不會變形,可以利用Camera.setDisplayOrientaion(int)設(shè)置相機的預(yù)覽方向吼句」兀可設(shè)置的參數(shù)有0,90惕艳,180搞隐,270,默認為0远搪,是指手機的左側(cè)為攝像頭頂部畫面劣纲,所以相機默認為橫屏,如果要豎屏預(yù)覽终娃,就需要設(shè)置90度味廊。
如果想讓相機跟隨設(shè)備方向變化蒸甜,改變預(yù)覽的方向,需要結(jié)合相機已經(jīng)旋轉(zhuǎn)的角度和屏幕旋轉(zhuǎn)的角度以及相機的前后(前置相機和后置相機預(yù)覽界面是不同的余佛,前置有鏡面效果)柠新,最好固定Activity的方向。
特別注意:
設(shè)置預(yù)覽角度辉巡,setDisplayOrientation本身只能改變預(yù)覽的角度previewFrameCallback以及拍攝出來的照片是不會發(fā)生改變的恨憎,拍攝出來的照片角度依舊不正常的,所以拍攝最后得到的照片需要自行處理(旋轉(zhuǎn))郊楣。
在布局發(fā)生改變時要重新設(shè)置相機預(yù)覽方向憔恳。
一般設(shè)置相機方向的通用方法:
public static int calculateCameraPreviewOrientation(Activity activity) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraID, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
最終算出來的值result是0,90,180,270中的一個,要獲取info.orientation的目的是不知道相機默認角度是多少净蚤。
4 設(shè)置預(yù)覽大小
相機的寬度和高度跟屏幕坐標(biāo)不一樣钥组,相機的寬高和手機屏幕的寬高是反過來的。例如如果設(shè)置展示預(yù)覽圖的SurfaceView的寬高比3:4今瀑,選取Camera預(yù)覽圖的尺寸時應(yīng)該是4:3(因為預(yù)覽默認對應(yīng)橫向程梦,如果要豎向顯示時,就是反過來的橘荠。)屿附。
設(shè)備上的相機支持的預(yù)覽大小是確定的,當(dāng)然也不是只有一種哥童,會有很多種挺份,我們只能從它支持的大小的列表中選取一個最接近我們需要比例的寬高。
為了保證相機預(yù)覽畫面不變形贮懈,預(yù)覽界面大小的設(shè)置必須和選取的相機支持的尺寸的寬高的比例相同匀泊。
舉個例子,獲取手機上支持的所有預(yù)覽尺寸:
Camera.Parameters parameters = camera.getParameters();
parameters.getSupportedPreviewSizes()
結(jié)果:
==支持的預(yù)覽尺寸=寬高= 176 144
==支持的預(yù)覽尺寸=寬高= 320 240
==支持的預(yù)覽尺寸=寬高= 352 288
==支持的預(yù)覽尺寸=寬高= 640 480
==支持的預(yù)覽尺寸=寬高= 1280 720
==支持的預(yù)覽尺寸=寬高= 1280 960
==支持的預(yù)覽尺寸=寬高= 176 144
==支持的預(yù)覽尺寸=寬高= 320 240
==支持的預(yù)覽尺寸=寬高= 352 288
==支持的預(yù)覽尺寸=寬高= 640 480
==支持的預(yù)覽尺寸=寬高= 1280 720
==支持的預(yù)覽尺寸=寬高= 1280 960
再次強調(diào):可以看到getSupportedPreviewSizes獲取了相機支持的所有預(yù)覽尺寸朵你,注意一點探赫,屏幕的寬高是按照豎屏獲取的,而getSupportedPreviewSizes獲得支持的尺寸是按照橫屏來說的撬呢,也就是說我們上面獲取的寬高實際上是反過來的,高是寬妆兑,寬是高魂拦,不知道大家理解沒。
例如選取上面獲取到的640:480,其實對應(yīng)到豎向屏幕上是480:640搁嗓。芯勘,如果我們選取640x480的尺寸,那么預(yù)覽Camera的SurfaceView的寬高比也必須為3:4,這樣預(yù)覽的畫面才不會變形腺逛,否則可能導(dǎo)致變形荷愕。
expectWidth和expectHeight 分別對應(yīng)期望的寬和高,是對應(yīng)相機的寬高,所以如果希望在view中看到的是640*480的預(yù)覽安疗,則期望寬高應(yīng)該寫入3:4的寬高抛杨,也就是把希望的對應(yīng)view的寬高反過來。
查找最合適尺寸的規(guī)則:
找出最合適的尺寸荐类,規(guī)則如下:
1.將尺寸按比例分組怖现,找出比例最接近屏幕比例的尺寸組
2.在比例最接近的尺寸組中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
3.如果沒有找到,則忽略2中第二個條件再找一遍玉罐,應(yīng)該是最合適的尺寸了
/**
* 找出最合適的尺寸屈嗤,規(guī)則如下:
* 1.將尺寸按比例分組,找出比例最接近屏幕比例的尺寸組
* 2.在比例最接近的尺寸組中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
* 3.如果沒有找到吊输,則忽略2中第二個條件再找一遍饶号,應(yīng)該是最合適的尺寸了
*/
private static Camera.Size findProperSize(Point surfaceSize, List<Camera.Size> sizeList) {
if (surfaceSize.x <= 0 || surfaceSize.y <= 0 || sizeList == null) {
return null;
}
int surfaceWidth = surfaceSize.x;
int surfaceHeight = surfaceSize.y;
List<List<Camera.Size>> ratioListList = new ArrayList<>();
for (Camera.Size size : sizeList) {
addRatioList(ratioListList, size);
}
final float surfaceRatio = (float) surfaceWidth / surfaceHeight;
List<Camera.Size> bestRatioList = null;
float ratioDiff = Float.MAX_VALUE;
for (List<Camera.Size> ratioList : ratioListList) {
float ratio = (float) ratioList.get(0).width / ratioList.get(0).height;
float newRatioDiff = Math.abs(ratio - surfaceRatio);
if (newRatioDiff < ratioDiff) {
bestRatioList = ratioList;
ratioDiff = newRatioDiff;
}
}
Camera.Size bestSize = null;
int diff = Integer.MAX_VALUE;
assert bestRatioList != null;
for (Camera.Size size : bestRatioList) {
int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
if (size.height >= surfaceHeight && newDiff < diff) {
bestSize = size;
diff = newDiff;
}
}
if (bestSize != null) {
return bestSize;
}
diff = Integer.MAX_VALUE;
for (Camera.Size size : bestRatioList) {
int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
if (newDiff < diff) {
bestSize = size;
diff = newDiff;
}
}
return bestSize;
}
private static void addRatioList(List<List<Camera.Size>> ratioListList, Camera.Size size) {
float ratio = (float) size.width / size.height;
for (List<Camera.Size> ratioList : ratioListList) {
float mine = (float) ratioList.get(0).width / ratioList.get(0).height;
if (ratio == mine) {
ratioList.add(size);
return;
}
}
List<Camera.Size> ratioList = new ArrayList<>();
ratioList.add(size);
ratioListList.add(ratioList);
}
設(shè)置SurfaceView的寬高為3:4的圖像。
設(shè)置SurfaceView的寬高為4:3的圖像季蚂,明顯變形了茫船。
5 設(shè)置拍攝圖片大小
調(diào)用camera的takePicture方法后,獲得拍照的圖像數(shù)據(jù)癣蟋,如何設(shè)置保存圖片的大小透硝,類似設(shè)置預(yù)覽大小,利用parameters.getSupportedPictureSizes()可以獲取支持的保存圖片的大小疯搅,圖片尺寸同樣只能從支持的列表中選取一個設(shè)置濒生。
picturesize和previewsize的寬高比也要保證一致,否則獲取的圖片會將preview時的圖像裁剪成picturesize的比例幔欧。
previewsize的分辨率罪治,只會影響預(yù)覽時的分辨率,不會影響獲取圖片的分辨率礁蔗,所以preview只是確定了圖像的取景最大范圍(所謂的取景范圍就是展示多大的畫面)觉义,最終圖片的分辨率是由picturesize來決定。
6 Camera設(shè)置幀率
/**
* 選擇合適的FPS
* @param parameters
* @param expectedThoudandFps 期望的FPS
* @return
*/
public static int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) {
List<int[]> supportedFps = parameters.getSupportedPreviewFpsRange();
for (int[] entry : supportedFps) {
if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) {
parameters.setPreviewFpsRange(entry[0], entry[1]);
return entry[0];
}
}
int[] temp = new int[2];
int guess;
parameters.getPreviewFpsRange(temp);
if (temp[0] == temp[1]) {
guess = temp[0];
} else {
guess = temp[1] / 2;
}
return guess;
}
getSupportedPreviewFpsRange函數(shù)可以獲取設(shè)備支持的幀率浴井,fps不是越高越好晒骇,F(xiàn)PS不宜過高,一般30fps足夠了磺浙。
7 如何讀取Camera的NV21數(shù)據(jù)和YUV數(shù)據(jù)
給camera對象設(shè)置一個 Camera.PreviewCallback,在這個回調(diào)中實現(xiàn)一個方法onPreviewFrame(byte[] data, Camera camera),就可以去Camera預(yù)覽圖片時的數(shù)據(jù)洪囤。
當(dāng)然如果設(shè)置了camera.setPreviewCallback(callback),onPreviewFrame這個方法會被一直調(diào)用,可以在攝像頭對焦成功后設(shè)置camera.setOneShotPreviewCallback(previewCallback)撕氧,這樣設(shè)置onPreviewFrame這個方法就會被調(diào)用一次,處理data數(shù)據(jù)瘤缩,bitmap來做相應(yīng)的處理就行了。這兩個方法都是系統(tǒng)自動配置緩沖區(qū)伦泥。
setPreviewFormat 函數(shù)可以設(shè)置預(yù)覽是onPreviewFrame返回數(shù)據(jù)的格式剥啤。
最常見的獲取的數(shù)據(jù)格式為NV21锦溪,如果不設(shè)置默認返回數(shù)據(jù)也是NV21編碼的數(shù)據(jù)。
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRecordingHint(true);
{
//設(shè)置獲取數(shù)據(jù)的格式
parameters.setPreviewFormat(ImageFormat.NV21);
//parameters.setPreviewFormat(ImageFormat.YV12);
//通過setPreviewCallback方法監(jiān)聽預(yù)覽的回調(diào):
byte[] imageByte;
Bitmap bitmap;
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
//這里面的Bytes的數(shù)據(jù)就是NV21格式的數(shù)據(jù),或者YV12的數(shù)據(jù)
Camera.Size previewSize = camera.getParameters().getPreviewSize();//獲取尺寸,格式轉(zhuǎn)換的時候要用到
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
previewSize.width,
previewSize.height,
null);
baos = new ByteArrayOutputStream();
yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);// 80--JPG圖片的質(zhì)量[0-100],100最高
imageByte = baos.toByteArray();
//將imageByte轉(zhuǎn)換成bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
bitmap = BitmapFactory.decodeByteArray(imageByte, 0, imageByte.length, options);
}
});
}
mCamera.setParameters(parameters);
注意府怯,onPreviewFrame()方法跟Camera.open()是運行于同一個線程刻诊,所以為了防止onPreviewFrame()會阻塞UI線程,將Camera.open()放置在子線程中運行富腊。
為什么要用到這個函數(shù)坏逢,因為如果調(diào)用takePicture別管怎么設(shè)置界面都會卡頓,如果通過onPreviewFrame的回調(diào)處理函數(shù)赘被,不會導(dǎo)致界面卡頓是整。
setPreviewCallbackWithBuffer 類似setPreiewCallback,一般配合setPreviewCallback使用
setPreviewCallbackWithBuffer (Camera.PreviewCallback cb) 要求指定一個字節(jié)數(shù)組作為緩沖區(qū)民假,用于預(yù)覽幀數(shù)據(jù)浮入,這樣能夠更好的管理預(yù)覽幀數(shù)據(jù)時使用的內(nèi)存。
setPreviewCallbackWithBuffer需要在startPreview()之前調(diào)用羊异,因為setPreviewCallbackWithBuffer使用時需要指定一個字節(jié)數(shù)組作為緩沖區(qū)事秀,用于預(yù)覽幀數(shù)據(jù),所以我們需要在setPreviewCallbackWithBuffer之前調(diào)用addCallbackBuffer野舶,這樣onPreviewFrame的data才有值易迹。然后需要在onPreviewFrame中調(diào)用,如果在onPreviewFrame中不調(diào)用平道,那么預(yù)覽幀數(shù)據(jù)就不會回調(diào)給onPreviewFrame睹欲。
代碼示例:
//通過setPreviewCallback方法監(jiān)聽預(yù)覽的回調(diào):
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
//這里面的Bytes的數(shù)據(jù)就是NV21格式的數(shù)據(jù),或者YUV_420_888的數(shù)據(jù)
}
});
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
});
}
mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
});
8 其他
camera的切換(前后攝像頭)
要先釋放前一個Camera,然后打開新相機一屋,打開預(yù)覽窘疮。
takePicture獲取圖片
調(diào)用takePicture后預(yù)覽會停止,需用重新調(diào)用startPreview才能再次開始預(yù)覽冀墨。預(yù)覽開始后闸衫,就可以通過Camera.takePicture()方法拍攝一張照片,返回的照片數(shù)據(jù)通過Callback接口獲取诽嘉。
takePicture()接口可以獲取三個類型的照片:
第一個蔚出,ShutterCallback接口,在拍攝瞬間瞬間被回調(diào)虫腋,通常用于播放“咔嚓”這樣的音效身冬;
第二個,PictureCallback接口岔乔,返回未經(jīng)壓縮的RAW類型照片;
第三個滚躯,PictureCallback接口雏门,返回經(jīng)過壓縮的JPEG類型照片嘿歌;
是否支持自動對焦
List modes = Parameters.getSupportedFocusModes();
modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
getSupportedFocusModes函數(shù)獲取所有的焦點模式,F(xiàn)OCUS_MODE_AUTO標(biāo)識自動對焦茁影,對焦方式還有FOCUS_MODE_CONTINUOUS_VIDEO使用視頻錄制宙帝,F(xiàn)OCUS_MODE_CONTINUOUS_PICTURE 用于拍照。