Android Camera2 API preview record take picture

Android Camera2 API预览 拍照和录像

code链接
git clone https://github.com/jzlhll/AndroidCam2Demo.git

原因

最近学习Camera2,发现Camera2的架构体系变化非常大。想要直接看懂google的android-Camera2Basic 和 android-Camera2Video,在架构体系上,做的不是很好,session,request写的比较绕。
于是,我使用了设计模式,让代码看上去更加有条理性。我想应该是入门最好的帖子了。

  • 优点1:一个APP,按照不同的场景,设计了3种状态,预览,拍照和录像;
  • 优点2:架构清晰,简单,很容易掌握到camera2的设计理念;
  • API:Camera2,MediaRecorder,TextureView/SurfaceView,ImageReader。
代码架构


View & Activity
使用了Material design风格,Layout中,传入一个自定义的CamSurfaceView或者CamTextureView:

<com.allan.androidcam2api.view.CamTextureView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="512dp"
    tools:ignore="MissingConstraints"
    tools:layout_editor_absoluteX="9dp"
    tools:layout_editor_absoluteY="0dp" />

在CamSurfaceView中初始化的时候,设置监听回调,getHolder().addCallback(this),在回调函数:

public interface MyCallback { //在Activity中注册CamView的监听
    void pleaseStart();

    void pleaseStop();
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    if (mCallback != null) mCallback.pleaseStart();
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    if (mCallback != null) mCallback.pleaseStop();
    return false;
}

类似的,CamTextureView setSurfaceTextureListener(this)并调用MyCallback来达到兼容的目的。
Activity设置callback略。

@Override
public void pleaseStart() {
    MyCameraManager.me.get().init(mView.getContext(), mView, mViewDecorator);
    MyCameraManager.me.get().addModChanged(this);
    MyCameraManager.me.get().openCamera();
}

@Override
public void pleaseStop() {
    MyCameraManager.me.get().closeCamera();
}

开始进行单例类MyCameraManager的初始化过程。


MyCameraManager & StateBase & Camera2
之所以Camera2不受大家欢迎的主要原因是,它的所有操作基本都是传入回调方法,等待回调。让代码绕来绕去。
经过我的设计以后,希望能减少大家的困惑。
首先我们来了解大致的流程:

1. 使用MyCameraManager保存cameraDevice对象;也就是getSystemService()拿到api cameraManager后,open传入CameraDevice.StateCallback回调回来以后,保存下来;
2. camera2比较绕的地方,是需要创建一个requestBuilder,给它添加addTarget surface(送显或者录制使用),可以多个;然后拿到request去创建session;
3. session创建成功的回调onConfigured()就可以保存下来,setRepeatingRequest进去,代表着某一类的显示已经成功。

那么,基于这种架构,我设计了一个抽象类+Handler消息处理机制来流转状态和控制显示模式。
首先,如上述流程图中,StateBase类所支持的能力:
1. 抽象了需要创建的surface个数:protected abstract void createSurfaces();
2. 抽象了需要贴入的surface:protected abstract void addTarget();
3. 抽象了显示模式:
protected int getTemplateType() {return CameraDevice.TEMPLATE_PREVIEW;}
4. 封装了createCaptureSession的动作,因为这一步不论是拍照模式录像模式等等都是一致的只是surface,和显示模式不一样;
5. 给createcaptureSession的回调构建抽象方法,子类创建这个回调用于处理具体业务 protected abstract CameraCaptureSession.StateCallback createStateCallback();


State+Handler模式

这种设计模式,其实是android framework的StateMachine机制。大概我的意图是这个方向,可能具体有些差异,不过能达到的目的是一致的控制状态的流转,防止太多同时的操作导致状态的异常

理解了google的demo以后,我知道了State有N种(实际中,2种即可):
1. 纯Preview的State;
2. Preview+Picture的State,注意它并不是拍照瞬间而是一直存在的,takePhoto可以看做一个额外的能力function;
3. Preview+Picture+Record的State, 注意它在进入录制状态就切换到这个State,而停止录制必须回调State2;
4. Preview+Record的State,略。
这个的区分,以Surface的个数和是否需要重新CreateSession为标准

回归到代码,第一步,openCamera() -> mCamHandler.sendEmptyMessage(OPEN);
打开以后得到回调后的mCameraDevice;紧接着,transmitModPicturePreview() ->则切换到preview+picture模式;

case MOD_PREVIEW_PIC: {
if (mCurrentSt == null) {
    openCameraFirst(msg); //达到了状态机,自动进入的目的
    return;
}

if (mCurrentSt.getId() == 0x011) {
    MyToast.toastNew(getContext(), mCamView, "Already in this mod");
    return;
}
notifyModChange("Picture&Preview");
mCurrentSt.closeSession(); //关闭session

mCurrentSt = new StatePicAndPreview(MyCameraManager.this); //创建某种State

try {
    mCurrentSt.createSession(new StatePicAndPreview.StateTakePicCb() {
        @Override
        public void onToken(String path) {
            CamLog.d("onToken in path" + path);
        }

        @Override
        public void onPreviewSuc() {
            CamLog.d("onPreviewSuc in myacmera");
        }

        @Override
        public void onPreviewErr() {
            CamLog.d("onPreviewErr in myacmera");
        }
    });
} catch (Exception e) {
    CamLog.e("start preview err0");
    e.printStackTrace();
}
}

这仅仅是创建了一种显示模式,而,拍照只是session下的一个动作。

case TAKE_PIC: {
if ((mCurrentSt.getId() & StateBase.PIC) == StateBase.PIC) {
     if (mCurrentSt instanceof StatePicAndPreview) {
        StatePicAndPreview spp = (StatePicAndPreview) mCurrentSt;
        TakePhotoFunc.ObjStruct objStruct = (TakePhotoFunc.ObjStruct) msg.obj;
       spp.takePicture(objStruct.dir, objStruct.name, objStruct.func);
    } else if (mCurrentSt instanceof StatePicAndRecAndPreview) { //TODO record pciture preview
        StatePicAndRecAndPreview sppr = (StatePicAndRecAndPreview) mCurrentSt;
        TakePhotoFunc.ObjStruct objStruct = (TakePhotoFunc.ObjStruct) msg.obj;
        sppr.takePicture(objStruct.dir, objStruct.name, objStruct.func);
                 }
 } else { //如果没有Picture属性则报错
      MyToast.toastNew(getContext(), mCamView, "No Picture Mod");
           return;
  }
}

只要当前State包含拍照的能力,即可调用它内部的方法。

阅读代码,关注几个transmit的Handler处理流程,并注意Start-Rec其实就是一种特殊的切换State模式即可理解我设计的状态机的流转了。下面一副low low的图,主要展示出几个状态的切换。他们内部具有的能力,在继承关系上可以体现出来。
下书就是类图,灰色,我没有实现。比如你的需求是录制中不需要拍照,则可以实现这个类StatePreviewRecord类。


录像
录像的过程,跟拍照在传统的Camera1的思想是不一致的。Camera1的时候我们使用MediaRecord的时候,没有设置过surface或者设置过surface,其实是framework进行过转换。这里我们可以显式地看出,addTarget的时候,将preview和record的surface都贴入了request。


其他
utils包里面,有几个重要的东西,android6.0以上Perssion申请的代码,这里引用了网上的东西,再此致谢;
Singleton设计,在我的其他文章中有讲;
里面穿插了很多的func接口,这是我写代码喜欢的一种方式,类似IOS上的block设计。在调用方法处比如takePicture(callback), 那么,可以很轻松的在callback中写处理逻辑。代码架构很清晰。


遗憾
有些旋转rotation,闪光灯没有详细写完;不过都留了TODO;

其实搭这个架构写代码没花太多时间,而在研究动态适配Camera Surface显示分辨率,比如对着一个圆进行浏览的时候,这个圆是否正。这方面百度和google都未能找到满意的答案,因为Camera2相关的介绍太少,基本都是基于google demo在复述。自我夸奖下,能重构到我这个地步,没看到~

很遗憾,在研究了很久很久好几天,setAspectRatio,通过设置可见区域onMesure()和setDefaultBufferSize对texture配置显示比例。都未能达到十分满意的效果。对size我的建议是,一般来说,应用需要找到一个合理的显示比例,固定下来。然后分辨率寻找一个16:9或者4:3定下来。

有兴趣的这部分可以继续,代码中已经留下来了setPreviewSize内很多的策略。

总体来说,如果对Camera2的使用,如果认为查看google demo不清晰的,可以看看我的代码,从架构上很好的剥离了复杂的逻辑。更容易理解其中的关键。

猜你喜欢

转载自blog.csdn.net/jzlhll123/article/details/80700189
今日推荐