前言
不管在Camera1或者Camera2在適配不同手機/不同使用場景的情況下都需要計算攝像頭里提供的分辨率列表中最合適的那一個分辨率.所以在需要大量機型適配的app,是不建議不經過計算直接自定義分辨率設置到預覽或者拍照照片中,有概率會因為攝像頭不支持你輸入的自定義分辨率導致報錯或者打不開攝像頭.
如果你的確有需求要自定義分辨率,那么使用場景只有一個那就是你是在開發Android設備,並且你輸入的自定義分辨率確定在這個設備上不會報錯.
目前本人總結的2個分辨率計算方法有2個:
- 求最佳比例正方形分辨率
- 求最滿足寬度的情況下,在找到最接近高度的分辨率.
下面我就來解釋這個2個計算方法.
求最佳比例正方形分辨率
較為歪門邪道的方法,核心就是TextureView的寬高比與攝像頭的高寬比做差值比較,注意這里一個是寬高一個是高寬,求出來的結果就是在指定指定比例最接近正方形的分辨率
優點:因為是正方形的分辨率,所以在預覽的時候不管是什么尺寸的TextureView的都能顯示的不會變形.所以比較適合在小尺寸TextureView上
缺點:在預覽的時候其實無法完全顯示完整(正方形不管怎么樣都有可能上下或者左右超出View的大小),所以TextureView會自動忽略四周部分,只顯示最中間的部分.這樣拍照的時候就會發現預覽與實際照片顯示范圍不一致.
/** * 獲取匹配的大小 這里是Camera2獲取分辨率數組的方式,Camera1獲取不同,計算一樣 * @return */ private Size getMatchingSize(){ Size selectSize = null; float selectProportion = 0; try { float viewProportion = (float)mTextureView.getWidth() / (float)mTextureView.getHeight();//計算View的寬高比 CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); for (int i = 0; i < sizes.length; i++){ Size itemSize = sizes[i]; float itemSizeProportion = (float)itemSize.getHeight() / (float)itemSize.getWidth();//計算當前分辨率的高寬比 float differenceProportion = Math.abs(viewProportion - itemSizeProportion);//求絕對值 Log.e(TAG, "相減差值比例="+differenceProportion ); if (i == 0){ selectSize = itemSize; selectProportion = differenceProportion; continue; } if (differenceProportion <= selectProportion){ //判斷差值是不是比之前的選擇的差值更小 if (differenceProportion == selectProportion){ //如果差值與之前選擇的差值一樣 if (selectSize.getWidth() + selectSize.getHeight() < itemSize.getWidth() + itemSize.getHeight()){//選擇分辨率更大的Size selectSize = itemSize; selectProportion = differenceProportion; } }else { selectSize = itemSize; selectProportion = differenceProportion; } } } } catch (CameraAccessException e) { e.printStackTrace(); } Log.e(TAG, "getMatchingSize: 選擇的比例是="+selectProportion); Log.e(TAG, "getMatchingSize: 選擇的尺寸是 寬度="+selectSize.getWidth()+"高度="+selectSize.getHeight()); return selectSize; }
求最滿足寬度的情況下,在找到最接近高度的分辨率
這個是最正常的算法了,核心就是找到與屏幕寬度最接近的分辨率,然后在找最接近屏幕高度的分辨率.這里是屏幕寬度是最高優先級的,其次在滿足高度.
優點:預覽圖像與拍照照片的效果完全一致.
缺點:
- 只能滿足全屏幕預覽的拍照情況下,各種奇葩自定義大小的TextureView你基本上不可能找到滿足的寬度的分辨率.
- 因為需要讓TextureView的高度跟隨分辨率高度,所以預覽的上面或者下面可能會有需要留出空白區域的情況.(可以用黑色背景View填充),全屏預覽的時候可以忽略這個情況,因為基本上手機的攝像頭都會有一個分辨率剛好與屏幕分辨率一致.但是不排除個別奇葩手機
在看代碼前這里說明一個重要知識!攝像頭分辨率的寬度和高度與屏幕分辨率的寬度和高度的對應
1.攝像頭分辨率的寬度和高度其實是手機橫屏下的才是正確方向.如下圖所示
2.屏幕的分辨率的寬度和高度依然是手機豎屏下的高度和寬度.
代碼部分
private Size getMatchingSize2(){ Size selectSize = null; try { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //因為我這里是將預覽鋪滿屏幕,所以直接獲取屏幕分辨率 int deviceWidth = displayMetrics.widthPixels; //屏幕分辨率寬 int deviceHeigh = displayMetrics.heightPixels; //屏幕分辨率高 Log.e(TAG, "getMatchingSize2: 屏幕密度寬度="+deviceWidth); Log.e(TAG, "getMatchingSize2: 屏幕密度高度="+deviceHeigh ); /** * 循環40次,讓寬度范圍從最小逐步增加,找到最符合屏幕寬度的分辨率, * 你要是不放心那就增加循環,肯定會找到一個分辨率,不會出現此方法返回一個null的Size的情況 * ,但是循環越大后獲取的分辨率就越不匹配 */ for (int j = 1; j < 41; j++) { for (int i = 0; i < sizes.length; i++) { //遍歷所有Size Size itemSize = sizes[i]; Log.e(TAG,"當前itemSize 寬="+itemSize.getWidth()+"高="+itemSize.getHeight()); //判斷當前Size高度小於屏幕寬度+j*5 && 判斷當前Size高度大於屏幕寬度-j*5 if (itemSize.getHeight() < (deviceWidth + j*5) && itemSize.getHeight() > (deviceWidth - j*5)) { if (selectSize != null){ //如果之前已經找到一個匹配的寬度 if (Math.abs(deviceHeigh-itemSize.getWidth()) < Math.abs(deviceHeigh - selectSize.getWidth())){ //求絕對值算出最接近設備高度的尺寸 selectSize = itemSize; continue; } }else { selectSize = itemSize; } } } /** * 因為需要滿足寬度,所以原則上如果沒有必要,首次找到最接近寬度屏幕的攝像頭高度后就跳出循環 */ if(selectSize != null){
Log.e(TAG, "getMatchingSize2: 選擇的分辨率寬度="+selectSize.getWidth());
Log.e(TAG, "getMatchingSize2: 選擇的分辨率高度="+selectSize.getHeight()); return selectSize; } } } catch (CameraAccessException e) { e.printStackTrace(); } Log.e(TAG, "getMatchingSize2: 選擇的分辨率寬度="+selectSize.getWidth()); Log.e(TAG, "getMatchingSize2: 選擇的分辨率高度="+selectSize.getHeight()); return selectSize; }