CameraView1.x阅读笔记

1,406 阅读9分钟

前言

最近阅读了使用很久的第三方相机库:natario1/CameraView。笔者用得比较多的是1.x版本,本文主要记录库的大体结构与部分类关于拍照的相关职责

ps:这个库前几年热度还是比较高的,当时只适配了Camera1,现在已经出到2.x版本了,API变动比较大。

本文阅读的代码版本:natario1/CameraView at v1.6.0

功能介绍

CameraView主要是基于Camera1封装的,同时也预留了Camera2的实现接口。

  • 从功能上涵盖了包括
    • 相机的拍照/录像
    • 摄像头切换(前置/后置)
    • 闪光灯
    • 缩放
    • 对焦
    • 白平衡
    • 曝光补偿
    • HDR
    等功能
  • 封装了预览的SurfaceView/TextureView的兼容逻辑,区分场景选择使用SurfaceView/TextureView,并对其统一出口。还有预览的方向问题处理。
  • 对于预览帧的输出管理。
  • 对于手势的封装,如单击、长按、手指捏合、上下/左右滑动等。分别对于不同手势作出不同操作,譬如手指捏合或左右滑动调整缩放比。
  • 网格的拍照辅助试图。

总结就是CameraView在实现Camera1的基础功能外,整合了一些相机常用视图相关的功能。

大体结构

image.png 粗略整理了大体的类图。

  • CameraView:一个View作为外部的入口,封装了相机相关功能设置入口、相机事件回调、触摸事件的分发。
  • CameraController:抽象类,主要负责相机API的封装。对相机开启/关闭的生命周期、接收CameraPreview预览视图回调等作管理,有两个实现类Camera1、Camera2。
    • Camera1:Camera1 API的实现。
    • Camera2:Camera2 API的实现。ps:空方法,未实现
  • CameraPreview:抽象类,负责封装SurfaceView/TextureView的通用部分,如Surface有效的回调、获取Surface以及将View添加到CameraView(父类)。
    • SurfaceCameraPreview:实现预览视图是SurfaceView的操作。
    • TextureCameraPreview:实现预览视图是TextureView的操作。
  • GestureLayout:抽象类,一个FrameLayout。其子类负责各个手势操作的触摸事件处理,以及对应的UI显示。
    • GestureAction:规定手势所触发的不同操作,如对焦、缩放、拍照等。
    • Gesture:规定不同手势可触发的GestureActon,如单击可进行对焦或拍照等。
    • 两者均服务于GestureLayout。
  • FrameProcessor:预览帧回调。
  • CameraListener:对外一些相机相关的事件监听,譬如相机的开启关闭、拍照数据回调等。
  • CameraCallbacks:充当CameraController与CameraView之间的异步通信,属于内部的相机相关的事件回调,CameraListener、FrameProcessor的事件会从这里进行触发。
  • SizeSelectors:筛选分辨率的工具类。
  • Control:可简单理解为库中的通用枚举。以Facing为例:
    • BACK为后置,FRONT为前置。
  • CameraOptions:存储相关选项,譬如白平衡、闪光灯、前置/后置摄像头等。负责将不同API转换成通用的枚举。
  • Mapper:负责将通用枚举转换成不同的API对应的设置。
  • FrameManger:预览帧的管理类,以Frame对象为单位,负责创建和缓存Frame对象。Frame对象可视为一次预览帧。

入口 - CameraView

CameraView本质上是一个FrameLayout,以一个自定义View作为与外部的交互的入口。提供大量自定义属性方便开发者在xml中定义。

自定义属性

/* CameraView.java */
// Self managed
int jpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, DEFAULT_JPEG_QUALITY);
boolean cropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, DEFAULT_CROP_OUTPUT);
boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS);

// Camera controller params
Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT.value()));
Flash flash = Flash.fromValue(a.getInteger(R.styleable.CameraView_cameraFlash, Flash.DEFAULT.value()));
Grid grid = Grid.fromValue(a.getInteger(R.styleable.CameraView_cameraGrid, Grid.DEFAULT.value()));
WhiteBalance whiteBalance = WhiteBalance.fromValue(a.getInteger(R.styleable.CameraView_cameraWhiteBalance, WhiteBalance.DEFAULT.value()));
VideoQuality videoQuality = VideoQuality.fromValue(a.getInteger(R.styleable.CameraView_cameraVideoQuality, VideoQuality.DEFAULT.value()));
SessionType sessionType = SessionType.fromValue(a.getInteger(R.styleable.CameraView_cameraSessionType, SessionType.DEFAULT.value()));
Hdr hdr = Hdr.fromValue(a.getInteger(R.styleable.CameraView_cameraHdr, Hdr.DEFAULT.value()));
Audio audio = Audio.fromValue(a.getInteger(R.styleable.CameraView_cameraAudio, Audio.DEFAULT.value()));
VideoCodec codec = VideoCodec.fromValue(a.getInteger(R.styleable.CameraView_cameraVideoCodec, VideoCodec.DEFAULT.value()));
long videoMaxSize = (long) a.getFloat(R.styleable.CameraView_cameraVideoMaxSize, 0);
int videoMaxDuration = a.getInteger(R.styleable.CameraView_cameraVideoMaxDuration, 0);

// Size selectors
List<SizeSelector> constraints = new ArrayList<>(3);
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinWidth)) {
    constraints.add(SizeSelectors.minWidth(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxWidth)) {
    constraints.add(SizeSelectors.maxWidth(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinHeight)) {
    constraints.add(SizeSelectors.minHeight(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxHeight)) {
    constraints.add(SizeSelectors.maxHeight(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinArea)) {
    constraints.add(SizeSelectors.minArea(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxArea)) {
    constraints.add(SizeSelectors.maxArea(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeAspectRatio)) {
    //noinspection ConstantConditions
    constraints.add(SizeSelectors.aspectRatio(AspectRatio.parse(a.getString(R.styleable.CameraView_cameraPictureSizeAspectRatio)), 0));
}
if (a.getBoolean(R.styleable.CameraView_cameraPictureSizeSmallest, false)) constraints.add(SizeSelectors.smallest());
if (a.getBoolean(R.styleable.CameraView_cameraPictureSizeBiggest, false)) constraints.add(SizeSelectors.biggest());
SizeSelector selector = !constraints.isEmpty() ?
        SizeSelectors.and(constraints.toArray(new SizeSelector[0])) :
        SizeSelectors.biggest();

// Gestures
GestureAction tapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureTap, GestureAction.DEFAULT_TAP.value()));
GestureAction longTapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureLongTap, GestureAction.DEFAULT_LONG_TAP.value()));
GestureAction pinchGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGesturePinch, GestureAction.DEFAULT_PINCH.value()));
GestureAction scrollHorizontalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollHorizontal, GestureAction.DEFAULT_SCROLL_HORIZONTAL.value()));
GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value()));

截取的是构造时读取的xml属性,其中包括:

  • 一些上述提到的Control设置。ps:这里需要注意的是SessionType DEFAULT = PICTURE,即默认是拍照的。
  • 关于图片分辨率的设置,如最大最小宽高、最大最小面积以及宽高比等。最终都会整合到一个SizeSelector中。
  • 关于手势所触发的事件,如上述提到的单击事件是触发对焦,还是拍照。

详细的属性介绍可查看CameraView/README.md

在提供xml自定义属性的同时,也对应公开setter方法在Java代码中使用。

初始化CameraController

/* CameraView.java */
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
    return new Camera1(callbacks);
}

属性初始化完成后会先初始化CameraController,这个instantiateCameraController本来应该可以根据某些条件选择使用Camera1还是Camera2的策略。由于1.x版本只实现了Camera1所以这里就直接返回了。

初始化CameraController后,会将上述的属性设置到CameraController中,用于后续的相机设置。

初始化CameraPreview

/* CameraView.java */
protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
    // TextureView is not supported without hardware acceleration.
    LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated());
    return isHardwareAccelerated() ?
            new TextureCameraPreview(context, container, null) :
            new SurfaceCameraPreview(context, container, null);
}

void instantiatePreview() {
    mCameraPreview = instantiatePreview(getContext(), this);
    mCameraController.setPreview(mCameraPreview);
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mCameraPreview == null) {
        // isHardwareAccelerated will return the real value only after we are
        // attached. That's why we instantiate the preview here.
        instantiatePreview();
    }
    if (!isInEditMode()) {
        mOrientationHelper.enable(getContext());
    }
}

CameraPreview即用来作预览的View,初始化是在onAttachedToWindow的,在CameraPreview构造的时候需要添加到CameraView上,这个后面会讲到。同时也是确保isHardwareAccelerated()返回的正确性。

  • onAttachedToWindow时会调用instantiatePreview()。
  • instantiatePreview()调用instantiatePreview(Context context, ViewGroup container),内部会通过View#isHardwareAccelerated()是否支持硬件加速来判断使用的是TextureView还是SurfaceView。这里也是一种策略模式。
  • 在CameraPreview创建后,需要引用到CameraController,方便Surface的事件监听。

预览 - CameraPreview

CameraPreview本身不是一个View,它只负责将View创建出来添加CameraView,并对Surface相关的事情进行管理。

这里关注的是几个抽象方法,还有一个SurfaceCallback。

/* CameraPreview.java */
CameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
    mView = onCreateView(context, parent);
    mSurfaceCallback = callback;
}

@NonNull
protected abstract T onCreateView(Context context, ViewGroup parent);

abstract Surface getSurface();

abstract Class<Output> getOutputClass();

abstract Output getOutput();
  • onCreateView:创建一个预览的视图,TextureView/SurfaceView,并添加到parent,也就是CameraView中。该方法在构造方法时触发,也就是在CameraView的onAttachedToWindow时。
  • getOutputClass、getOutput:TextureView就是SurfaceTexture,SurfaceView就是SurfaceHolder。
/* CameraPreview.java */
interface SurfaceCallback {
    void onSurfaceAvailable();
    void onSurfaceChanged();
}

SurfaceCallback用于与CameraController进行通信,回调Surface的事件。

/* CameraPreview.java */
// As far as I can see, these are the view/surface dimensions.
// This live in the 'View' orientation.
private int mSurfaceWidth;
private int mSurfaceHeight;

// As far as I can see, these are the actual preview dimensions, as set in CameraParameters.
private int mDesiredWidth;
private int mDesiredHeight;
  • surfaceWidth/surfaceHeight记录的是Surface事件回调的宽高
  • desiredWidth/desiredHeight记录的是预览分辨率的宽高

区分开两者是因为有可能根据预览分辨率对视图作一些变换或者是设置。

子类TextureCameraPreview

这里以TextureView的情况为例

/* TextureCameraPreview.java */
@NonNull
@Override
protected TextureView onCreateView(Context context, ViewGroup parent) {
    View root = LayoutInflater.from(context).inflate(R.layout.cameraview_texture_view, parent, false);
    parent.addView(root, 0);
    TextureView texture = root.findViewById(R.id.texture_view);
    texture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {

        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            onSurfaceAvailable(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            onSurfaceSizeChanged(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            onSurfaceDestroyed();
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    });
    return texture;
}

onCreateView时会实例一个TextureView,并注册SurfaceTextureListener监听。

/* CameraPreview.java */
protected final void onSurfaceAvailable(int width, int height) {
    LOG.i("onSurfaceAvailable:", "w=", width, "h=", height);
    mSurfaceWidth = width;
    mSurfaceHeight = height;
    crop();
    mSurfaceCallback.onSurfaceAvailable();
}

onSurfaceAvailable是定义在父类的,这里就是记录surfaceWidth/surfaceHeight,crop()是对View进行缩放,这个其实只对TextureView有效。最后会通过SurfaceCallback通知CameraController已经SurfaceAvailable了。

/* CameraPreview.java */
void setDesiredSize(int width, int height) {
    LOG.i("setDesiredSize:", "desiredW=", width, "desiredH=", height);
    this.mDesiredWidth = width;
    this.mDesiredHeight = height;
    crop();
}

/* TextureCameraPreview.java */
@TargetApi(15)
@Override
void setDesiredSize(int width, int height) {
    super.setDesiredSize(width, height);
    if (getView().getSurfaceTexture() != null) {
        getView().getSurfaceTexture().setDefaultBufferSize(width, height);
    }
}

在Camera预览初始化过程中,可以调用setDesiredSize(int width, int height),这个方法就是记录desiredWidth/desiredHeight,同时crop()根据surfaceWidth/surfaceHeight和desiredWidth/desiredHeight对View进行缩放。调用SurfaceTexture#setDefaultBufferSize修正大小。

相机 - CameraController

抽象类CameraController主要的职能是对于Camera生命周期的管理,这里以start为例。外部通过CameraView#start,会调用CameraController#start。

/* CameraView.java */
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void start() {
    if (!isEnabled()) return;

    if (checkPermissions(getSessionType(), getAudio())) {
        // Update display orientation for current CameraController
        mOrientationHelper.enable(getContext());
        mCameraController.setDisplayOffset(mOrientationHelper.getDisplayOffset());
        mCameraController.start();
    }
}

/* Camera1.java */
// Starts the preview asynchronously.
final void start() {
    LOG.i("Start:", "posting runnable. State:", ss());
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            LOG.i("Start:", "executing. State:", ss());
            if (mState >= STATE_STARTING) return;
            mState = STATE_STARTING;
            LOG.i("Start:", "about to call onStart()", ss());
            onStart();
            LOG.i("Start:", "returned from onStart().", "Dispatching.", ss());
            mState = STATE_STARTED;
            mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions);
        }
    });
}

@WorkerThread
abstract void onStart();

CameraController#start()实际上只是进行了mState的状态更新,实际的相机操作会在onStart进行,也就是交由子类进行。

ps:这里的mHandler是一个HandlerThread的Handler。

来看看Camera1#onStart的是实现

/* Camera1.java */
@WorkerThread
@Override
void onStart() {
    if (isCameraAvailable()) {
        LOG.w("onStart:", "Camera not available. Should not happen.");
        onStop(); // Should not happen.
    }
    if (collectCameraId()) {
        try {
            mCamera = Camera.open(mCameraId);
        } catch (Exception e) {
            LOG.e("onStart:", "Failed to connect. Maybe in use by another app?");
            throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT);
        }
        mCamera.setErrorCallback(this);

        // Set parameters that might have been set before the camera was opened.
        LOG.i("onStart:", "Applying default parameters.");
        Camera.Parameters params = mCamera.getParameters();
        mExtraProperties = new ExtraProperties(params);
        mCameraOptions = new CameraOptions(params, shouldFlipSizes());
        applyDefaultFocus(params);
        mergeFlash(params, Flash.DEFAULT);
        mergeLocation(params, null);
        mergeWhiteBalance(params, WhiteBalance.DEFAULT);
        mergeHdr(params, Hdr.DEFAULT);
        mergePlaySound(mPlaySounds);
        params.setRecordingHint(mSessionType == SessionType.VIDEO);
        mCamera.setParameters(params);

        // Try starting preview.
        mCamera.setDisplayOrientation(computeSensorToViewOffset()); // <- not allowed during preview
        if (shouldBindToSurface()) bindToSurface();
        LOG.i("onStart:", "Ended");
    }
}

这里主要做了几件事:

  • 选择摄像头,Camera1只能选择前置或后置。
  • 调用Camera.open开启摄像头。
  • 以及对于之前设置的各种属性往Camera.Parameters中设置。

最后有一个shouldBindToSurface,也有通过生命周期状态判断是否需要绑定预览画面。

/* Camera1.java */
private boolean shouldBindToSurface() {
    return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsBound;
}

private boolean isCameraAvailable() {
    switch (mState) {
        // If we are stopped, don't.
        case STATE_STOPPED:
            return false;
        // If we are going to be closed, don't act on camera.
        // Even if mCamera != null, it might have been released.
        case STATE_STOPPING:
            return false;
        // If we are started, mCamera should never be null.
        case STATE_STARTED:
            return true;
        // If we are starting, theoretically we could act.
        // Just check that camera is available.
        case STATE_STARTING:
            return mCamera != null;
    }
    return false;
}

其余关于Camera1相机相关的操作都封装在Camera1.java中了,这里不多赘述,可以根据需要阅读。

对焦

以对接为例看看整个大致的交互流程

  • CameraView#startAutoFocus

    /* CameraView.java */
    public void startAutoFocus(float x, float y) {
        if (x < 0 || x > getWidth()) throw new IllegalArgumentException("x should be >= 0 and <= getWidth()");
        if (y < 0 || y > getHeight()) throw new IllegalArgumentException("y should be >= 0 and <= getHeight()");
        mCameraController.startAutoFocus(null, new PointF(x, y));
    }
    

    外部调用CameraView#startAutoFocus,实际调用的是CameraController#startAutoFocus。

  • Camera1#startAutoFocus

    /* Camera1.java */
    @Override
    void startAutoFocus(@Nullable final Gesture gesture, final PointF point) {
        // Must get width and height from the UI thread.
        int viewWidth = 0, viewHeight = 0;
        if (mPreview != null && mPreview.isReady()) {
            viewWidth = mPreview.getView().getWidth();
            viewHeight = mPreview.getView().getHeight();
        }
        final int viewWidthF = viewWidth;
        final int viewHeightF = viewHeight;
        // Schedule.
        schedule(null, true, new Runnable() {
            @Override
            public void run() {
                if (!mCameraOptions.isAutoFocusSupported()) return;
                final PointF p = new PointF(point.x, point.y); // copy.
                List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y,
                        viewWidthF, viewHeightF, computeSensorToViewOffset());
                List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1);
    
                // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().
                Camera.Parameters params = mCamera.getParameters();
                int maxAF = params.getMaxNumFocusAreas();
                int maxAE = params.getMaxNumMeteringAreas();
                if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1);
                if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1);
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                mCamera.setParameters(params);
                mCameraCallbacks.dispatchOnFocusStart(gesture, p);
                // TODO this is not guaranteed to be called... Fix.
                mCamera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {
                        // TODO lock auto exposure and white balance for a while
                        mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p);
                        mHandler.get().removeCallbacks(mPostFocusResetRunnable);
                        mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay);
                    }
                });
            }
        });
    }
    

    该方法做了几件事情:

    • 判断是否支持自动对焦,mCameraOptions.isAutoFocusSupported()里的值在Camera1#onStart方法s时赋值,数据来源于Camera.Parameters。
    • 根据对焦的位置,计算并设置FocusArea、MeteringArea。
    • 设置对焦,并通过CameraCallbacks回调对焦开始事件
    • 监听AutoFocusCallback,并在onAutoFocus时通过CameraCallbacks回调对焦结束事件。ps:mPostFocusResetRunnable做的事情是恢复对焦前Camera.Parameters的相关设置。

选择摄像头

再来看一下摄像头选择,这里就用到了Mapper。mFacing是一个通用枚举,默认是Facing.BACK。Mapper的作用是将其转换成Camera1的枚举,即Camera.CameraInfo.CAMERA_FACING_BACK

/* Camera1.java */
private boolean collectCameraId() {
    int internalFacing = mMapper.map(mFacing);
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
        Camera.getCameraInfo(i, cameraInfo);
        if (cameraInfo.facing == internalFacing) {
            mSensorOffset = cameraInfo.orientation;
            mCameraId = i;
            return true;
        }
    }
    return false;
}

尺寸选择工具 - SizeSelectors

SizeSelectors实际上可以理解为语法糖,可以类比成Kotlin的一些操作符,只不过它只服务于Size类。它用于按规则筛选出合适的Size。

以biggest为例,顾名思义是选择最大的。

/* SizeSelectors.java */
public static SizeSelector biggest() {
    return new SizeSelector() {
        @NonNull
        @Override
        public List<Size> select(@NonNull List<Size> source) {
            Collections.sort(source);
            Collections.reverse(source);
            return source;
        }
    };
}

这里就是简单的先排序(从小到大),再倒序,取第一个即为最大。ps:注意这里的Size是自定义类com.otaliastudios.cameraview.Size

Size的比较器:

/* Size.java */
@Override
public int compareTo(@NonNull Size another) {
    return mWidth * mHeight - another.mWidth * another.mHeight;
}

面积较大的,Size就较大。

也有逻辑运算的,如AndSelector

/* SizeSelectors.java */
private static class AndSelector implements SizeSelector {

    private SizeSelector[] values;

    private AndSelector(@NonNull SizeSelector... values) {
        this.values = values;
    }

    @Override
    @NonNull
    public List<Size> select(@NonNull List<Size> source) {
        List<Size> temp = source;
        for (SizeSelector selector : values) {
            temp = selector.select(temp);
        }
        return temp;
    }
}

将前一个SizeSelector输出的结果作为下一个SizeSelector的输入。

当然,在特殊逻辑下,也可以自定义自己的SizeSelector,通过CameraView设置。

预览帧管理 - FrameManager

正如前面所讲,FrameManager会以Frame对象为单位管理预览帧。内部维护着一个缓冲池是一个LinkedBlockingQueue,默认大小是2,在CameraController构造时会初始化FrameManager。

/* FrameManager.java */
private LinkedBlockingQueue<Frame> mQueue;

当预览帧回调时,这里以Camera1为例

/* Camera1.java */
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    Frame frame = mFrameManager.getFrame(data,
            System.currentTimeMillis(),
            computeSensorToOutputOffset(),
            mPreviewSize,
            mPreviewFormat);
    mCameraCallbacks.dispatchFrame(frame);
}

调用FrameManager#getFrame将回调的byte[]包装成Frame对象,再回调出去。

/* FrameManager.java */
Frame getFrame(byte[] data, long time, int rotation, Size previewSize, int previewFormat) {
    Frame frame = mQueue.poll();
    if (frame == null) frame = new Frame(this);
    frame.set(data, time, rotation, previewSize, previewFormat);
    return frame;
}

这里的如果Queue为空,则会创建一个新的Frame。

Frame被再次添加到Queue是在其release时,调用FrameManager#onFrameRelease。

/* FrameManager.java */
void onFrameReleased(Frame frame) {
    byte[] buffer = frame.getData();
    boolean willRecycle = mQueue.offer(frame);
    if (!willRecycle) {
        frame.releaseManager();
    }
    if (buffer != null && mCallback != null) {
        int currSize = buffer.length;
        int reqSize = mBufferSize;
        if (currSize == reqSize) {
            mCallback.onBufferAvailable(buffer);
        }
    }
}

当Frame在CameraCallbacks回调给CameraView时

/* CameraView.java */
@Override
public void dispatchFrame(final Frame frame) {
    if (mFrameProcessors.isEmpty()) {
        // Mark as released. This instance will be reused.
        frame.release();
    } else {
        mLogger.v("dispatchFrame:", frame.getTime(), "processors:", mFrameProcessors.size());
        mFrameProcessorsHandler.post(new Runnable() {
            @Override
            public void run() {
                for (FrameProcessor processor : mFrameProcessors) {
                    processor.process(frame);
                }
                frame.release();
            }
        });
    }
}

这里Frame会被抛到一个新的线程回调出去,当会执行完后就会调用Frame#release。这样Frame从使用到回收的流程就串联起来了。

最后

以上就是库的大体结构与部分类关于拍照的相关职责了。其实可以看到整体的设计可能由于在Camera2没有实现的情况下,并没有过多的考虑Camera2,所以该库在2.x版本后也进行了不小的重构。笔者在使用这个1.x版本时偶尔也会遇到崩溃的情况,一些小问题也需要自己根据实际情况对代码进行小改动。写这篇文章主要是想记录自己阅读源码后所理解的东西,希望也可以帮到有需要的同学。