Camerax 打开指定摄像头id

安卓上开发摄像头预览应用,参考了谷歌的实现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()

你可能感兴趣的:(Camerax 打开指定摄像头id)