一瞎饲、簡述
1伯诬、camera2的發(fā)展是由于手機廠商在添加過多的攝像頭,而google的camera1只是針對單攝像頭的所以對于多攝像頭的處理不是很友好织盼。
2杨何、由于手機主板需要占據(jù)手機大部分的空間,而手機的攝像頭也是長方形的悔政,所以手機廠商的后置攝像頭的放置一般都是逆時針旋轉(zhuǎn)90度的晚吞。
二、學習camera2需要了解哪些api的使用谋国?
CameraManager//獲取相機的一些參數(shù)信息和打開相機的管理類
CameraCaptureSession//用來向相機設(shè)備發(fā)送獲取圖像的請求
ImageReader//獲取屏幕渲染數(shù)據(jù) 可以搭配MediaProjectionManager錄屏一起使用
三槽地、學習camera2遇到的一些問題
1:用戶在使用手機在拍照的時候不會是完全豎直使用的,那在用戶點擊拍照的時候怎么處理手機旋轉(zhuǎn)帶來的問題?
2:前置攝像頭就像是鏡子捌蚊,所以拍照出來的數(shù)據(jù)是鏡像的我們需要怎么處理集畅?
3:用戶切換前后攝像頭代碼應該怎么做?
? ?? 帶著以上3個問題開始代碼之旅
四缅糟、問題處理
- 手機旋轉(zhuǎn)我們可以做監(jiān)聽處理挺智,系統(tǒng)提供了OrientationEventListener類
val mOrEventListener: OrientationEventListener =
object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
phoneDegree = 0;
} else if ((orientation > 45) && (orientation <= 135)) {
phoneDegree = 90;
} else if ((orientation > 135) && (orientation <= 225)) {
phoneDegree = 180;
} else if ((orientation > 225) && (orientation <= 315)) {
phoneDegree = 270;
}
}
}
mOrEventListener.enable()
我們將獲取的旋轉(zhuǎn)角度保存為全局變量,此時只是獲取了旋轉(zhuǎn)角度窗宦,還有攝像頭對應得角度我們還需要再處理赦颇,上代碼
val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
mCameraId是對應得前置還是后置的攝像頭,mOrientation 就是相機需要的旋轉(zhuǎn)角度
- 對應得前置攝像頭的處理方案
(mOrientation-phoneDegree + 360)% 360
- 對應得后置攝像頭的處理方案
(mOrientation+phoneDegree )% 360
至此第一個問題可以解決了!8昂媒怯!
- 鏡像處理應該大家都做過很簡單的一個沿著x翻轉(zhuǎn)就可以了,這里我們用的是Matrix
matrix.postScale(-1F, 1F);//利用matrix 對矩陣進行轉(zhuǎn)換髓窜,x軸鏡像
至此第二個問題可以解決了I劝!寄纵!
- 第三個不是技術(shù)問題鳖敷,是攝像頭在使用過程中不支持動態(tài)更改配置,如果需要更改的話必須重新初始化所有配置操作程拭。所以封裝好代碼按步驟初始化吧定踱。這類需要提的幾點就是,對于camera如果已經(jīng)打開過了我們需要release
captureSession!!.close()
cameraDevice!!.close()
對于ImageReader類我們已經(jīng)創(chuàng)建過的需要close
imageReader!!.close()
至此以上遇到的一些問題都解決了2负N荻帧!沒有可以難倒我們的了山宾。接下來就是看看代碼如何處理吧
(代碼主要是kotlin寫的,初學者鳍徽,希望寫的不對的大家可以指教W拭獭!=准馈1炼拧)
下面貼一下完整的代碼。
class Camera2Manager(var context: Context, var captureTexture: SurfaceTexture) {
private val tag = Camera2Manager::class.java.simpleName
private var handlerThread = HandlerThread("Camera2Manager")
private var handler: Handler? = null
private var mCameraId = "0"http://初始為后置攝像頭
private var cameraManager: CameraManager? = null//獲取相機的一些參數(shù)信息和打開相機的管理類
private var captureSession: CameraCaptureSession? = null//用來向相機設(shè)備發(fā)送獲取圖像的請求
private var imageReader: ImageReader? = null//獲取屏幕渲染數(shù)據(jù) 可以搭配MediaProjectionManager錄屏一起使用
private var previewWidth = 0//圖片預覽寬
private var previewHeight = 0//圖片預覽高
private var cameraDevice: CameraDevice? = null
private var captureCallBack: CaptureCallBack? = null
private var isFrontCamera: Boolean = false//前置攝像頭的判斷
private var phoneDegree = 0;//手機的旋轉(zhuǎn)角度
init {
handlerThread.start()//啟動工作線程
handler=Handler(handlerThread.looper)
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
mCameraId = getCamera()[0]
initDefaultPreviewSize()
startOrientationListener()
}
/**
* 主要是監(jiān)聽手機旋轉(zhuǎn)角度來保證所拍攝的照片始終是正向的
*/
private fun startOrientationListener() {
val mOrEventListener: OrientationEventListener =
object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
phoneDegree = 0;
} else if ((orientation > 45) && (orientation <= 135)) {
phoneDegree = 90;
} else if ((orientation > 135) && (orientation <= 225)) {
phoneDegree = 180;
} else if ((orientation > 225) && (orientation <= 315)) {
phoneDegree = 270;
}
}
}
mOrEventListener.enable()
}
/**
* 獲取CameraId 一臺設(shè)備會有多個攝像頭
*/
private fun getCamera(): Array<out String> {
return cameraManager!!.cameraIdList
}
/**
* 獲取當前camera的支持預覽大小 可以供用戶選擇分辨率不過一般給與最大分辨率拍攝
*/
private fun getPreviewSize(): Array<out Size>? {
var characteristics = cameraManager?.getCameraCharacteristics(mCameraId)
val configs = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
return configs!!.getOutputSizes(SurfaceTexture::class.java)
}
/**
* 根據(jù)cameraId打開指定的攝像頭
*/
fun openCamera() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
return
}
createImageReader()
cameraManager?.openCamera(mCameraId, cameraStateCallback, handler)
}
/**
* 打開Camera的狀態(tài)回調(diào)
*/
private var cameraStateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
createCaptureSession()
}
override fun onDisconnected(device: CameraDevice) {
}
override fun onError(device: CameraDevice, p1: Int) {
}
}
/**
* 創(chuàng)建CameraCaptureSession的狀態(tài)回調(diào)
*/
private var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
captureSession = session
var captureSessionRequest =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureSessionRequest.addTarget(Surface(captureTexture))
// 設(shè)置自動對焦模式
captureSessionRequest?.set(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 設(shè)置自動曝光模式
captureSessionRequest?.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
var request = captureSessionRequest.build()
captureSession!!.setRepeatingRequest(request, null, handler)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
}
}
/**
* ImageReader獲取到數(shù)據(jù)時的回調(diào)
*/
private var imageAvailableListener = ImageReader.OnImageAvailableListener {
var image = imageReader!!.acquireLatestImage()//點擊拍照的時候會獲取當前的一幀數(shù)據(jù)
var buffer = image.planes[0].buffer
var length = buffer.remaining()
var bytes = ByteArray(length)
buffer.get(bytes)
image.close()
try {
var bmp = BitmapFactory.decodeByteArray(bytes, 0, length, null)
var matrix = Matrix()
val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
if (isFrontCamera) {
matrix.postRotate((mOrientation-phoneDegree + 360)% 360 * 1.0F)
matrix.postScale(-1F, 1F);//利用matrix 對矩陣進行轉(zhuǎn)換濒募,x軸鏡像
} else {
matrix.postRotate((mOrientation+phoneDegree )% 360 * 1.0F)
}
var bitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, false)
if (captureCallBack != null) {
captureCallBack!!.onSucceed(bitmap)
}
} catch (e: Exception) {
if (captureCallBack != null) {
captureCallBack!!.onFailed(Throwable(e.localizedMessage))
}
}
}
/**
* 更改預覽鞭盟,本質(zhì)上是重新創(chuàng)建CameraCaptureSession和ImageReader
*/
fun changePreviewSize(width: Int, height: Int) {
previewWidth = width
previewHeight = height
if (cameraDevice != null) {
createImageReader()
createCaptureSession()
}
}
/**
* 創(chuàng)建CameraCaptureSession
*/
private fun createCaptureSession() {
captureTexture!!.setDefaultBufferSize(previewWidth, previewHeight);//設(shè)置SurfaceTexture緩沖區(qū)大小
if (captureSession != null) {
captureSession!!.close()
captureSession = null
}
cameraDevice!!.createCaptureSession(
listOf(
Surface(captureTexture),
(imageReader!!.surface)
), captureSessionCallback, handler
)
}
/**
* 創(chuàng)建ImageReader
*/
private fun createImageReader() {
if (imageReader != null) {
imageReader!!.close()
imageReader = null
}
imageReader = ImageReader.newInstance(previewWidth, previewHeight, ImageFormat.JPEG, 2)
imageReader!!.setOnImageAvailableListener(imageAvailableListener, handler)
}
/**
* 重新打開一個相機 打開之前要先做一下清理
* 每次切換攝像頭都需要重新創(chuàng)建
*/
fun openCamera(cameraId: String) {
if (cameraDevice != null) {
releaseCamera()
}
//可以根據(jù)下面的代碼判斷是否是前置攝像頭或者后置
// val characteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
// if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)//前置
// if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK)//后置
mCameraId = cameraId
isFrontCamera = when (cameraId) {
"0" -> false
else -> true
}
initDefaultPreviewSize()
openCamera()
}
/**
* 獲取默認的預覽尺寸 取第一個尺寸的默認最大
*/
private fun initDefaultPreviewSize() {
var previewSize = getPreviewSize()
for (i in 0 until previewSize?.size!!) {
var width = previewSize[i].width
var height = previewSize[i].height
Log.e("我的預覽尺寸$i", "$width ---- $height")
}
var size = previewSize!![0]
previewWidth = size.width
previewHeight = size.height
}
/**
* 拍照
*/
fun capturePic(captureCallBack: CaptureCallBack) {
if (cameraDevice == null) {
return
}
this.captureCallBack = captureCallBack
try {
var requestBuilder =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
requestBuilder.addTarget(imageReader!!.surface)
var request = requestBuilder.build()
captureSession!!.capture(request, null, handler)
} catch (e: Exception) {
if (captureCallBack != null) {
captureCallBack.onFailed(Throwable(e.localizedMessage))
}
}
}
/**
* 釋放相機
*/
fun releaseCamera() {
captureSession!!.close()
cameraDevice!!.close()
}
/**
* 釋放handlerThread
*/
fun destroy() {
handlerThread.quit()
}
}
至此暫時結(jié)束簡單的處理,接下來有時間會寫更詳細的處理瑰剃,還有camerax的使用之類的齿诉。
簡書不一定會常用,以后也會不定期更新一些關(guān)于音視頻方面的東西,希望跟志同道合的朋友一起學習進步粤剧。
下一篇會寫android端p2p的投屏軟件歇竟,喜歡的朋友可以關(guān)注下一起進步。
追加一些自動對焦的處理抵恋,引用下別人的文章Android Camera2之 手動點擊區(qū)域?qū)?/a>