解决方案在文章的最下方

android设备 除了前置和后置摄像头以外 ,有时会使用 type-c或者usb 连接AR眼镜 再连接到手机或者开发板等应用场景

在正常情况用手机调用的拍照底层函数takePicture是不会报错

但是在作为第三方摄像头接入以后用camera 1的api 调用拍照会报错

 mCamera = Camera.open()
 ...........初始化
 mCamera?.takePicture(null, null, Camera.PictureCallback { data, camera ->
                data?.let {
                 ......生成图片
                }
  })

 camera api会报以下错误

 java.lang.RuntimeException: takePicture failed
        at android.hardware.Camera.native_takePicture(Native Method)
        at android.hardware.Camera.takePicture(Camera.java:1497)
        at android.hardware.Camera.takePicture(Camera.java:1439)
        at com.afei.camerademo.camera.CameraProxy.takePicture(CameraProxy.java:213)
        at com.afei.camerademo.surfaceview.SurfaceCameraActivity.onClick(SurfaceCameraActivity.java:63)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

使用camera2的api则会报错

E/AndroidRuntime: FATAL EXCEPTION: RequestThread-0
    Process: com.afei.camerademo, PID: 12312
    java.lang.UnsupportedOperationException: Unknown error -38
        at android.hardware.camera2.legacy.LegacyExceptionUtils.throwOnError(LegacyExceptionUtils.java:77)
        at android.hardware.camera2.legacy.LegacyCameraDevice.setSurfaceOrientation(LegacyCameraDevice.java:789)
        at android.hardware.camera2.legacy.RequestThreadManager.configureOutputs(RequestThreadManager.java:392)
        at android.hardware.camera2.legacy.RequestThreadManager.-wrap0(Unknown Source:0)
        at android.hardware.camera2.legacy.RequestThreadManager$5.handleMessage(RequestThreadManager.java:713)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:164)
        at android.os.HandlerThread.run(HandlerThread.java:65)

用TextureView来代替拍照又报错

E/CameraClient: Invalid setDisplayOrientation degrees=215
   Process: com.afei.camerademo, PID: 12497
    java.lang.RuntimeException: set display orientation failed
        at android.hardware.Camera.setDisplayOrientation(Native Method)
        at com.afei.camerademo.camera.CameraProxy.setDisplayOrientation(CameraProxy.java:184)
        at com.afei.camerademo.camera.CameraProxy.openCamera(CameraProxy.java:55)
        at com.afei.camerademo.textureview.CameraTextureView$1.onSurfaceTextureAvailable(CameraTextureView.java:47)
        .....

从错误的日志中

只能勉强看到takePicture failed和RequestThread-0  error

问题太偏门了查了很多的文档也没有找到类似相关资料,读源码也找不到解决方案

怀疑是当用usb或者type-c 等AR设备的摄像头接入时

这种第三方既不属于前置摄像头也不属于后置摄像头,   它不属于系统进程

而属于另外一个应用进程,相机可以回传图像这没有问题,但是做一些拍照动作就没有权限

解决办法  用媒体类解锁第三方摄像头使用权限,我们在android 的api中寻找解决方案。

 

我们回到前面看at android.hardware.Camera.takePicture(Camera.java:1497) 1497的代码是注释是怎么描述

  /**
     * Triggers an asynchronous image capture. The camera service will initiate
     * a series of callbacks to the application as the image capture progresses.
     * The shutter callback occurs after the image is captured. This can be used
     * to trigger a sound to let the user know that image has been captured. The
     * raw callback occurs when the raw image data is available (NOTE: the data
     * will be null if there is no raw image callback buffer available or the
     * raw image callback buffer is not large enough to hold the raw image).
     * The postview callback occurs when a scaled, fully processed postview
     * image is available (NOTE: not all hardware supports this). The jpeg
     * callback occurs when the compressed image is available. If the
     * application does not need a particular callback, a null can be passed
     * instead of a callback method.
     *
     * <p>This method is only valid when preview is active (after
     * {@link #startPreview()}).  Preview will be stopped after the image is
     * taken; callers must call {@link #startPreview()} again if they want to
     * re-start preview or take more pictures. This should not be called between
     * {@link android.media.MediaRecorder#start()} and
     * {@link android.media.MediaRecorder#stop()}.
     *
     * <p>After calling this method, you must not call {@link #startPreview()}
     * or take another picture until the JPEG callback has returned.
     *
     * @param shutter   the callback for image capture moment, or null
     * @param raw       the callback for raw (uncompressed) image data, or null
     * @param postview  callback with postview image data, may be null
     * @param jpeg      the callback for JPEG image data, or null
     * @throws RuntimeException if starting picture capture fails; usually this
     *    would be because of a hardware or other low-level error, or because
     *    release() has been called on this Camera instance.
     */
翻译一下是
触发异步图像截图,摄像头服务将在图像截图过程中启动一系列回调
(shutter)快门回调发生在图像被截图后,这个可以触发一个声音,让用户知道已被截图.
这个(raw)原始回调发生在原始图像数据可用时(注意:如果没有可用的原始图像回调缓冲区或
原始图像回调缓冲区不够大,无法容纳原始图像)
(postview)当发生缩放动作时,会发生postview回调
(注意:并非所有硬件都支持此功能),(jpeg)当压缩图像可用时发生回调。如果应用程序不需要特定的回调,可以传递null而不是回调方法。

此方法仅在“预览”处于活动状态(在startPreview()方法之后
如果你想要拍更多图片,在图片生成以后必须再次调用startPreview)
也不能在 {@link android.media.MediaRecorder#start()} and {@link android.media.MediaRecorder#stop()}.方法之间

调用此方法后,不能调用{@link\#startPreview()}
或者拍摄另一张照片,直到(JPEG参数)中回调返回。

@param shutter图像捕获时刻的回调,或为null
@param raw原始(未压缩)图像数据的回调,或为null
@带有postview图像数据的postview回调参数可以为null
@param jpeg对jpeg图像数据的回调,或为null
@如果启动图片截图失败,则引发RuntimeException;通常是这样
可能是因为硬件或其他低版本错误,或者
已对此摄影机实例调用release()

这里我们看MediaRecorder.start怎么写的

MediaRecorder需要camera的unlock  方法 unlock api说明

     * 解锁一个摄像机进程并访问它
     * Unlocks the camera to allow another process to access it.
     * Normally, the camera is locked to the process with an active Camera
     * object until {@link #release()} is called.  To allow rapid handoff
     * between processes, you can call this method to release the camera
     * temporarily for another process to use; once the other process is done
     * you can call {@link #reconnect()} to reclaim the camera.
     *
     * <p>This must be done before calling
     * {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be
     * called after recording starts.
     *
     * <p>If you are not recording video, you probably do not need this method.
     *
     * @throws RuntimeException if the camera cannot be unlocked.


 

---------------------------------解决方案-----------------------------------

首先在startPreview 前调用unLock 解锁相机以允许其他进程访问它

var surfaceHolderCallback=object : SurfaceHolder.Callback {
        override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            mSurfaceHolder = holder!!
            mCamera?.apply {
                ..........
                startPreview()
                //添加unlock方法 解锁相机以允许其他进程访问它
                unlock()

            }
        }

        override fun surfaceDestroyed(holder: SurfaceHolder?) {
        }

        override fun surfaceCreated(holder: SurfaceHolder?) {
            ......

        }
    }

在拍照前调用创建MediaRecorder的媒体录像工具类的start方法来解锁camera来允许它被当前的应用调用拍照

​
mRecorder = MediaRecorder().apply {
            reset()
            setCamera(mCamera)
            // 设置音频源与视频源 这两项需要放在setOutputFormat之前
            setVideoSource(MediaRecorder.VideoSource.CAMERA)
            //设置输出格式
            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setVideoEncoder(MediaRecorder.VideoEncoder.H264)  //视频编码格式
            //设置最终出片分辨率
            setVideoSize(1920, 1080)
            setVideoFrameRate(30)
            setVideoEncodingBitRate(3 * 1024 * 1024)
//                setOrientationHint(90)
            //设置记录会话的最大持续时间(毫秒)
            setMaxDuration(30 * 1000)
        }
         path = Environment.getExternalStorageDirectory().path + File.separator + "temp.mp4"
        try {
            mRecorder.apply {
                stop()
                reset()
                release()
            }
        } catch (e: Exception) {
        }


mCamera?.takePicture(null, null, Camera.PictureCallback { data, camera ->
                data?.let {
                    //将data转成文件即可  
             
                    mCamera?.apply {
                        lock()
                        stopPreview()
                        release()
                    }


                }
            })

​

加上这些方法以后调用拍照就不会报错了?,为什么呢?MediaRecorder的.start方法是这样描述的 

“应用程序在使用这个方法以后。应用程序不需要再次锁定摄像头。但是,如果方法失败,应用程序仍应锁定摄像头” 原文:

     * Begins capturing and encoding data to the file specified with
     * setOutputFile(). Call this after prepare().
     *
     * <p>Since API level 13, if applications set a camera via
     * {@link #setCamera(Camera)}, the apps can use the camera after this method
     * call. The apps do not need to lock the camera again. However, if this
     * method fails, the apps should still lock the camera back. The apps should
     * not start another recording session during recording.
     *
     * @throws IllegalStateException if it is called before
     * prepare() or when the camera is already in use by another app.

拍照可以用了!?就是这么神奇!这个问题真的太偏门了,不知道其他设备遇到类似的错误能不能解决。分享此办法希望略尽绵力。

 

补充;使用这个方法以后拍摄的照片是缩放的,很模糊

val params = mCamera!!.parameters
val sizes = params.supportedPictureSizes
var size = sizes[0]
for (i in 0 until sizes.size) {
 if (sizes[i].width > size.width) size = sizes[i]
}

params.setPictureSize(size.width, size.height)
params.flashMode = Camera.Parameters.FLASH_MODE_AUTO
params.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
params.sceneMode = Camera.Parameters.SCENE_MODE_AUTO
params.whiteBalance = Camera.Parameters.WHITE_BALANCE_AUTO
params.exposureCompensation = 0
params.pictureFormat = ImageFormat.JPEG
params.jpegQuality = 100
params.setRotation(90)
mCamera?.parameters = params


//部分机型无法正确获取到params.supportedPictureSizes

可以强制使用1920*1080的分辨率
params.setPictureSize(1920, 1080)

 

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐