安卓上开发摄像头预览应用,参考了谷歌的实现CameraX基本使用;具体使用可查看源码
复制一下关键代码:
private lateinit var viewFinder: PreviewView
private var camera: Camera? =null
private var cameraProvider: ProcessCameraProvider? =null
private var preview: Preview? =null
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
/** Initialize CameraX, and prepare to bind the camera use cases */
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// Select lensFacing depending on the available cameras
lensFacing =when {
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else ->throw IllegalStateException("Back and front camera are unavailable")
}
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}
/** Declare and bind preview, capture and analysis use cases */
@SuppressLint("RestrictedApi")
private fun bindCameraUseCases() {
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it)}
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
val rotation =viewFinder.display.rotation
val cameraProvider =cameraProvider
?:throw IllegalStateException("Camera initialization failed.")
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
}catch (exc: Exception) {
}
}
/** Returns true if the device has an available back camera. False otherwise */
private fun hasBackCamera(): Boolean {
return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?:false
}
/** Returns true if the device has an available front camera. False otherwise */
private fun hasFrontCamera(): Boolean {
return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?:false
}
其中选择预览的摄像头是通过
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
这个是CameraX 中默认的摄像头选择过滤的一个类,实现如下:
public Builder requireLensFacing(@LensFacing int lensFacing) {
mCameraFilterSet.add(new LensFacingCameraFilter(lensFacing));
return this;
}
继续看LensFacingCameraFilter类的实现,
public class LensFacingCameraFilterimplements CameraFilter {
@CameraSelector.LensFacing
private int mLensFacing;
public LensFacingCameraFilter(@CameraSelector.LensFacing int lensFacing) {
mLensFacing = lensFacing;
}
@NonNull
@Override
public List filter(@NonNull List cameraInfos) {
List result =new ArrayList();
for (CameraInfo cameraInfo : cameraInfos) {
Preconditions.checkArgument(cameraInfoinstanceof CameraInfoInternal,
"The camera info doesn't contain internal implementation.");
//拿到摄像头的前置或后置信息,和mLensFacing比较,相同的摄像头加入result返回
//mLensFacing就是requireLensFacing()传入的要打开前置还是后置
Integer lensFacing = ((CameraInfoInternal) cameraInfo).getLensFacing();
if (lensFacing !=null && lensFacing ==mLensFacing) {
result.add(cameraInfo);
}
}
return result;
}
}
主要是重写的filter()方法,其中的过滤方式是拿到 前后置和你设置的前后置一样的摄像头列表。
/** A camera on the device facing the same direction as the device's screen. */
public static final int LENS_FACING_FRONT =0;
/** A camera on the device facing the opposite direction as the device's screen. */
public static final int LENS_FACING_BACK =1;
当有多个摄像头都是前置或者后置是,默认就会打开列表的第一个;
先看下CameraSelector中的filter方法,
public List filter(@NonNull List cameraInfos) {
List input =new ArrayList(cameraInfos);
List output =new ArrayList(cameraInfos);
for (CameraFilter filter :mCameraFilterSet) {
//在这里调用了CameraFilter中的过滤方法filter,默认的是根据前后置来过滤,
//就是LensFacingCameraFilter类中filter的实现
output = filter.filter(Collections.unmodifiableList(output));
if (output.isEmpty()) {
throw new IllegalArgumentException("No available camera can be found.");
}else if (!input.containsAll(output)) {
throw new IllegalArgumentException("The output isn't contained in the input.");
}
input.retainAll(output);
}
return output;
}
在绑定摄像头时,cameraProvider.bindToLifecycle()里的实现;
public CamerabindToLifecycle(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@Nullable ViewPort viewPort,
@NonNull UseCase... useCases) {
...
CameraSelector.Builder selectorBuilder =
CameraSelector.Builder.fromSelector(cameraSelector);
// Append the camera filter required internally if there's any.
for (UseCase useCase : useCases) {
CameraSelector selector = useCase.getCurrentConfig().getCameraSelector(null);
if (selector !=null) {
for (CameraFilter filter : selector.getCameraFilterSet()) {
selectorBuilder.addCameraFilter(filter);
}
}
}
CameraSelector modifiedSelector = selectorBuilder.build();
/**在modifiedSelector.filter方法中会根据LensFacingCameraFilter的过滤规则把所有符合前置或后置的摄像头过滤出来*/
LinkedHashSet cameraInternals =
modifiedSelector.filter(mCameraX.getCameraRepository().getCameras());
...
// Try to get the camera before binding to the use case, and throw IllegalArgumentException
// if the camera not found.
//在这里 new CameraUseCaseAdapter()的时候会把都是前置或者后置的摄像头列表传进入,然后多个前置或后置时默认绑定第一个摄像头,无法指定第几个前置
if (lifecycleCameraToBind ==null) {
lifecycleCameraToBind =
mLifecycleCameraRepository.createLifecycleCamera(lifecycleOwner,
new CameraUseCaseAdapter(cameraInternals,
mCameraX.getCameraDeviceSurfaceManager(),
mCameraX.getDefaultConfigFactory()));
}
}
在CameraUseCaseAdapter初始化时默认选择列表的第一个摄像头:mCameraInternal = cameras.iterator().next();
public CameraUseCaseAdapter(@NonNull LinkedHashSet cameras,
@NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
@NonNull UseCaseConfigFactory useCaseConfigFactory) {
mCameraInternal = cameras.iterator().next();
mCameraInternals =new LinkedHashSet<>(cameras);
mId =new CameraId(mCameraInternals);
mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
mUseCaseConfigFactory = useCaseConfigFactory;
}
一开始使用CameraX,机器只有一个前置或后置时,通过前置或后置来选择摄像头没有问题;
后面遇到插入USB摄像头(我遇到的情况是插入多个USB摄像头时,和本地摄像头都被识别为前置摄像头)或者有多个后置或前置时,就只能打开默认的摄像头,没办法打开指定的摄像头了。
于是自定义自己的CameraFilter,通过摄像头的来过滤要打开的摄像头。
实现如下:
@ExperimentalCameraFilter
class MyCameraFilter(private val mId: String) : CameraFilter {
private val TAG ="CameraIdCameraFilter"
@SuppressLint("RestrictedApi")
override fun filter(cameraInfos: MutableList): MutableList {
val result = ArrayList()
cameraInfos.forEach {
Preconditions.checkArgument(
it is CameraInfoInternal,
"The camera info doesn't contain internal implementation."
)
it as CameraInfoInternal
val id =it.cameraId
//传入的mId通过CameraManager.cameraIdList获取就可以了
//但是有一个问题:外接的USB摄像头id在机器重启后,原来的摄像头对应的id会发生变化,
//这个时候想打开确定的摄像头的话,可以通过USB摄像头的pid,vid找到对应的摄像头对应
//的id,这个不同机型都不太一样,直接找系统工程师确认吧
if (id==mId) {
result.add(it)
}
}
return result
}
}
使用时初始化cameraSelector时改一下即可,其他不变:
private var mCameraId =0
val cameraSelector = CameraSelector.Builder()
.addCameraFilter(MyCameraFilter("$mCameraId")).build()