音視頻系列文章
Android 音視頻開發(fā)(一) -- 使用AudioRecord 錄制PCM(錄音)亚情;AudioTrack播放音頻
工程: 音視頻Demo
Camera1 在 API 21 的時候已經被棄用了,雖然現(xiàn)在google 都推薦 使用 Camerax 來實現(xiàn)相機的一些功能椅您,但這不妨礙我們學習 Camera1 和 Camera2,對此有基礎了解生闲,為后續(xù)學習 Camera2 和 Camerax 做鋪墊
在這篇文章中媳溺,你講學習到:
- 實現(xiàn)相機的開啟與預覽
- 相機預覽方向的矯正
- 實現(xiàn)拍照工鞥,并矯正拍照圖片
效果如下:
一. 相機的開啟與預覽
首先碍讯,先申請權限:
<uses-permission android:name="android.permission.CAMERA" />
<!-- 支持相機才能運行 -->
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
1.1. 獲取相機個數(shù)
一般手機中悬蔽,都有前置攝像頭和后置攝像頭,我們可以根據 Camera 的 getNumberOfCameras() 方法捉兴,來獲取這些信息蝎困。比如:
//獲取相機個數(shù)
int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
//獲取相機信息
Camera.getCameraInfo(i, info);
//前置攝像頭
if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
mFrontCameraId = i;
mFrontCameraInfo = info;
} else if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing) {
mBackCameraId = i;
mBackCameraInfo = info;
}
}
可以看到,通過 Camera.getCameraInfo(i, info) 就可以拿到當前的 CameraInfo 的信息倍啥,里面有個參數(shù)我們需要注意一下禾乘,就是 facing,它表示當前攝像機面對的方向虽缕,理解為前置和后置盖袭,然后我們把這些信息也保存起來。
1.2 打開攝像頭
接著彼宠,我們可以使用 Camera.open(cameraid) 去打開攝像頭
//根據 cameraId 打開不同攝像頭
mCamera = Camera.open(cameraId);
打開我們的攝像頭之后鳄虱,可以對它進行一些配置,比如設置預覽方向等凭峡,這個話題我們等到下面出現(xiàn)了再說拙已。
1.3 配置攝像頭屬性
在開啟相機預覽之前,我們需要對相機進行一些參數(shù)配置摧冀,比如聚焦倍踪,預覽尺寸等;這里我使用的是 SurfaceView索昂,所以等SurfaceView 創(chuàng)建好之后建车,可以對它進行一些參數(shù)的設置:
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
startPreview(width, height);
}
# startPreview
private void startPreview(int width, int height) {
//配置camera參數(shù)
initPreviewParams(width, height);
//設置預覽 SurfaceHolder
Camera camera = mCamera;
if (camera != null) {
try {
camera.setPreviewDisplay(mSurfaceView.getHolder());
} catch (IOException e) {
e.printStackTrace();
}
}
//開始顯示
camera.startPreview();
}
在Camra 中,我們可以通過 camera.getParameters() 拿到相機默認的參數(shù)椒惨,如果要配置自己的參數(shù)缤至,可以使用 camera.setParameters(parameters) 去設置,不過這個比較比較好使康谆,所以相機的配置開啟這些领斥,可以使用 HandlerThread 去開啟,這里就不增加多余代碼了沃暗。
initPreviewParams 的完整代碼如下:
private void initPreviewParams(int shortSize, int longSize) {
Camera camera = mCamera;
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
//獲取手機支持的尺寸
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size bestSize = getBestSize(shortSize, longSize, sizes);
//設置預覽大小
parameters.setPreviewSize(bestSize.width, bestSize.height);
//設置圖片大小月洛,拍照
parameters.setPictureSize(bestSize.width, bestSize.height);
//設置格式,所有的相機都支持 NV21格式
parameters.setPreviewFormat(ImageFormat.NV21);
//設置聚焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
camera.setParameters(parameters);
}
}
1.3.1 相機預覽大小
首先,應該根據自己UI的大小去設置相機預覽的大小孽锥,如果你的控件為 200x200嚼黔,但相機的數(shù)據為 1920x1080 细层,這樣填充過去,畫面肯定是會被拉伸的唬涧。
所以今艺,可以通過
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes()
拿到手機相機支持的所有尺寸;所以爵卒,我們需要找到比例相同虚缎,或者近似的大小,跟UI配合钓株,這樣畫面才不會拉伸实牡,注意相機的 width > height,所以獲取一個最佳的預覽尺寸可以這樣寫:
/**
* 獲取預覽最后尺寸
*/
private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
Camera.Size bestSize = null;
float uiRatio = (float) longSize / shortSize;
float minRatio = uiRatio;
for (Camera.Size previewSize : sizes) {
float cameraRatio = (float) previewSize.width / previewSize.height;
//如果找不到比例相同的轴合,找一個最近的,防止預覽變形
float offset = Math.abs(cameraRatio - minRatio);
if (offset < minRatio) {
minRatio = offset;
bestSize = previewSize;
}
//比例相同
if (uiRatio == cameraRatio) {
bestSize = previewSize;
break;
}
}
return bestSize;
}
當 UI 的比例跟相機支持的比例相同创坞,直接返回,否則則找近似的受葛。
接著調用
效果如下:
咦题涨,發(fā)現(xiàn)預覽的方向是反的;這個時候就需要使用 setDisplayOrientation() 去設置預覽方向了
二. 調整預覽方向
首先总滩,在調整預覽方向錢纲堵,我們需要先了解一些知識。
- 屏幕坐標: Android 坐標系中闰渔,在 (0,0) 坐標那席函,向右為 x 軸,向下為 y 軸冈涧。
- 自然方向: 設置的自然方向茂附,比如手機默認就是豎直是自然方向,平板的話督弓,橫向就是自然方向
- 圖片傳感器方向: 手機的圖片數(shù)據都來自攝像頭硬件傳感器营曼,這個傳感器有個默認的方向,一般是手機是橫向的愚隧,這就跟手機的自然方向成 90° 關系了蒂阱。
所以,我們要做的就是奸攻,就是把傳感器拿到的圖片蒜危,進行一個角度的變化虱痕,使圖像能跟自然方向一致:
圖片來源
所以睹耐,我們的方向調整可以這樣寫:
private void adjustCameraOrientation(Camera.CameraInfo info) {
//判斷當前的橫豎屏
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int degress = 0;
//獲取手機的方向
switch (rotation) {
case Surface.ROTATION_0:
degress = 0;
break;
case Surface.ROTATION_90:
degress = 90;
break;
case Surface.ROTATION_180:
degress = 180;
break;
case Surface.ROTATION_270:
degress = 270;
break;
}
int result = 0;
//后置攝像頭
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
result = (info.orientation - degress + 360) % 360;
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//先鏡像
result = (info.orientation + degress) % 360;
result = (360 - result) % 360;
}
mCamera.setDisplayOrientation(result);
}
最后再看一下:
三. 切換攝像頭
現(xiàn)在用到的都是后置攝像頭,切換也比較簡單部翘,首先先釋放相機支援硝训,然后再從配置參數(shù),預覽再來一遍即可:
//關閉攝像頭
closeCamera();
mCameraID = mCameraID == mFrontCameraId ? mBackCameraId : mFrontCameraId;
//打開相機
openCamera(mCameraID);
//開啟預覽
startPreview(mSurfaceView.getWidth(), mSurfaceView.getHeight());
#closeCamera
private void closeCamera() {
//停止預覽
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
四. 拍照及調整圖片方向
Camera 的拍照也比較簡單,使用 takePicture(ShutterCallback shutter, PictureCallback raw窖梁, PictureCallback jpeg) 方法即可赘风,它的三個參數(shù)如下:
- ShutterCallback :拍照瞬間調用,如果空回調纵刘,則由聲音邀窃,傳 null ,則沒效果
- PictureCallback :圖片的原始數(shù)據假哎,即沒處理過的
- PictureCallback : 圖片的 JPEG 數(shù)據
拿到 byte 數(shù)據后瞬捕,轉換成bitmap即可,如下:
Camera camera = mCamera;
camera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
new SavePicAsyncTask(data).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
這里的圖片保存舵抹,用一個 AsyncTask 來保存:
/**
* 保存圖片
*/
class SavePicAsyncTask extends AsyncTask<Void, Void, File> {
byte[] data;
File file;
public SavePicAsyncTask(byte[] data) {
this.data = data;
File dir = new File(Constants.PATH);
if (!dir.exists()) {
dir.mkdirs();
}
String name = "test.jpg";
file = new File(dir, name);
}
@Override
protected File doInBackground(Void... voids) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if (bitmap == null) {
return null;
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
//保存之前先調整方向
Camera.CameraInfo info = mCameraID == mFrontCameraId ? mFrontCameraInfo : mBackCameraInfo;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
bitmap = BitmapUtils.rotate(bitmap, 90);
} else {
bitmap = BitmapUtils.rotate(bitmap, 270);
}
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.close(fos);
}
return file;
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if (file != null) {
Toast.makeText(Camera1Activity.this, "圖片保存成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(Camera1Activity.this, "圖片保存失敗", Toast.LENGTH_SHORT).show();
}
}
}
#BitmapUtils#rotate
public static Bitmap rotate(Bitmap bitmap,float degress){
Matrix matrix = new Matrix();
matrix.postRotate(degress);
return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
}
當拿到 byte[] 數(shù)據時肪虎,使用 BitmapFactory.decodeByteArray 解析 bitmap ,但此時的圖片也是不對的惧蛹,需要對它進行一個旋轉扇救,如上所示,這樣香嗓,我們的拍照就也完成了迅腔。