一篇文章學會Android Camera2 Api,實現(xiàn)自定義相機入門溉潭。

前言

最近在做相機相關的項目净响,考慮到調用系統(tǒng)相機部分功能不能滿足項目需求,于是考慮自定相機這一塊喳瓣,由于之前沒有接觸過自定義相機這一塊馋贤,于是查閱了相關資料,結合自身實踐畏陕,做一個簡單的總結配乓。

相關參考資料

感謝下面文章提供的參考

切入正題

1)camera2包架構示意圖:

image

2) camera2包中的主要API結構圖:

image

3)主要API詳解:

  • CameraManager:攝像頭管理器。這是一個全新的系統(tǒng)管理器惠毁,專門用于檢測系統(tǒng)攝像頭犹芹、打開系統(tǒng)攝像頭。除此之外鞠绰,調用CameraManagergetCameraCharacteristics(String)方法即可獲取指定攝像頭的相關特性腰埂。
  • CameraCharacteristics:攝像頭特性。該對象通過CameraManager來獲取蜈膨,用于描述特定攝像頭所支持的各種特性屿笼。
  • CameraDevice:代表系統(tǒng)攝像頭。該類的功能類似于早期的Camera類翁巍。
  • CameraCaptureSession:這是一個非常重要的API驴一,當程序需要預覽、拍照時曙咽,都需要先通過該類的實例創(chuàng)建Session蛔趴。而且不管預覽還是拍照,也都是由該對象的方法進行控制的,其中控制預覽的方法為setRepeatingRequest()孝情;控制拍照的方法為capture()鱼蝉。
    為了監(jiān)聽CameraCaptureSession的創(chuàng)建過程,以及監(jiān)聽CameraCaptureSession的拍照過程箫荡,Camera v2 API為CameraCaptureSession提供了StateCallback魁亦、CaptureCallback等內部類。
  • CameraRequestCameraRequest.Builder:當程序調用setRepeatingRequest()方法進行預覽時羔挡,或調用capture()方法進行拍照時洁奈,都需要傳入CameraRequest參數(shù)。CameraRequest代表了一次捕獲請求绞灼,用于描述捕獲圖片的各種參數(shù)設置利术,比如對焦模式、曝光模式……總之低矮,程序需要對照片所做的各種控制印叁,都通過CameraRequest參數(shù)進行設置。CameraRequest.Builder則負責生成CameraRequest對象军掂。

4)自定義相機拍照大致流程:

image

1.

調用 CameraManageropenCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler) 方法打開指定攝像頭轮蜕。該方法的第一個參數(shù)cameraId代表要打開的攝像頭ID(攝像頭ID(通常0代表后置攝像頭,1代表前置攝像頭)蝗锥;第二個參數(shù)用于監(jiān)聽攝像頭的狀態(tài)跃洛;第三個參數(shù)代表執(zhí)行callbackHandler,如果程序希望直接在當前線程中執(zhí)行callback终议,則可將handler參數(shù)設為null汇竭。

2. 獲取CameraDevice對象

當攝像頭被打開之后,程序即可獲取CameraDevice—即根據(jù)攝像頭ID獲取了指定攝像頭設備穴张,然后調用CameraDevicecreateCaptureSession(List<Surface> outputs, CameraCaptureSession. StateCallback callback韩玩,Handler handler)方法來創(chuàng)建CameraCaptureSession。該方法的第一個參數(shù)是一個List集合陆馁,封裝了所有需要從該攝像頭獲取圖片的Surface,第二個參數(shù)用于監(jiān)聽CameraCaptureSession的創(chuàng)建過程合愈;第三個參數(shù)代表執(zhí)行callbackHandler叮贩,如果程序希望直接在當前線程中執(zhí)行callback,則可將handler參數(shù)設為null佛析。

3. 設置設置攝像頭模式

不管預覽還是拍照益老,程序都調用CameraDevicecreateCaptureRequest(int templateType)方法創(chuàng)建CaptureRequest.Builder,該方法支持TEMPLATE_PREVIEW(預覽)寸莫、TEMPLATE_RECORD(拍攝視頻)捺萌、TEMPLATE_STILL_CAPTURE(拍照)等參數(shù)。
通過第3步所調用方法返回的CaptureRequest.Builder設置拍照的各種參數(shù)膘茎,比如對焦模式桃纯、曝光模式等酷誓。
調用CaptureRequest.Builderbuild()方法即可得到CaptureRequest對象,接下來程序可通過CameraCaptureSessionsetRepeatingRequest()方法開始預覽态坦,或調用capture()方法拍照盐数。

5)案例代碼

配置靜態(tài)權限
6.0之后記得在代碼中添加相機和讀寫存儲卡的動態(tài)權限

    <!-- 相機 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.autocues"/> 
    <!-- 用于讀寫存儲卡 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

相機界面簡單布局
界面很簡單,就一個TextureView和一個拍照按鈕
activity_camera.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="net.ticp.uwonders.imagerecognize.activity.CameraActivity">

    <TextureView
        android:id="@+id/camera_texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/capture_ib"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="bottom|center"
        android:layout_marginBottom="10dp"
        android:background="@drawable/home_scan" />

</FrameLayout>

CameraActivity
代碼實現(xiàn)伞梯,項目中使用了 Butterknife 簡單注解
該文件注釋比較清楚玫氢,相信大家能夠看懂,就不分方法細講了

/**
 * @author Marlon
 * @desc Camera2Activity 基于Camera2 API 自定義相機
 * @date 2018/6/13
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Activity extends AppCompatActivity {

    @BindView(R.id.texture)
    AutoFitTextureView texture;
    @BindView(R.id.takephoto)
    ImageView takephoto;
    @BindView(R.id.change)
    Switch change;
    @BindView(R.id.auto)
    RadioButton auto;
    @BindView(R.id.open)
    RadioButton open;
    @BindView(R.id.close)
    RadioButton close;
    @BindView(R.id.flash_rg)
    RadioGroup flashRg;

    /*** 相機管理類*/
    CameraManager mCameraManager;

    /*** 指定攝像頭ID對應的Camera實體對象*/
    CameraDevice mCameraDevice;


    /**
     * 預覽尺寸
     */
    private Size mPreviewSize;
    private int mSurfaceWidth;
    private int mSurfaceHeight;

    /*** 打開攝像頭的ID{@link CameraDevice}.*/
    private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT;

    /*** 處理靜態(tài)圖像捕獲的ImageReader谜诫。{@link ImageReader}*/
    private ImageReader mImageReader;

    /*** 用于相機預覽的{@Link CameraCaptureSession}漾峡。*/
    private CameraCaptureSession mCaptureSession;

    /*** {@link CaptureRequest.Builder}用于相機預覽請求的構造器*/
    private CaptureRequest.Builder mPreviewRequestBuilder;

    /***預覽請求, 由上面的構建器構建出來*/
    private CaptureRequest mPreviewRequest;
    /**
     * 從屏幕旋轉圖片轉換方向。
     */
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    /***判斷是否支持閃關燈*/
    private boolean mFlashSupported;

    /*** 用于運行不應阻塞UI的任務的附加線程喻旷。*/
    private HandlerThread mBackgroundThread;

    /*** 用于在后臺運行任務的{@link Handler}生逸。*/
    private Handler mBackgroundHandler;

    /**
     * 文件存儲路徑
     */
    private File mFile;
    /**
     * 預覽請求構建器, 用來構建"預覽請求"(下面定義的)通過pipeline發(fā)送到Camera device
     * 這是{@link ImageReader}的回調對象。 當靜止圖像準備保存時掰邢,將會調用“onImageAvailable”牺陶。
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

    };

    /*** {@link CameraDevice.StateCallback}打開指定攝像頭回調{@link CameraDevice}*/
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            cameraDevice.close();
            cameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            cameraDevice.close();
            cameraDevice = null;
        }

    };

    /**
     * TextureView 生命周期響應
     */
    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override //創(chuàng)建
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            //當TextureView創(chuàng)建完成,打開指定攝像頭相機
            openCamera(width, height, mCameraId);
        }

        @Override //尺寸改變
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override //銷毀
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override //更新
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {

        }
    };
    private int CONTROL_AE_MODE;


    /**
     * 打開指定攝像頭ID的相機
     *
     * @param width
     * @param height
     * @param cameraId
     */
    private void openCamera(int width, int height, int cameraId) {
        if (ActivityCompat.checkSelfPermission(Camera2Activity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        try {
            mSurfaceWidth = width;
            mSurfaceHeight = height;
//            getCameraId(cameraId);
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId + "");
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 獲取設備方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int totalRotation = sensorToDeviceRotation(characteristics, rotation);
            boolean swapRotation = totalRotation == 90 || totalRotation == 270;
            int rotatedWidth = mSurfaceWidth;
            int rotatedHeight = mSurfaceHeight;
            if (swapRotation) {
                rotatedWidth = mSurfaceHeight;
                rotatedHeight = mSurfaceWidth;
            }
            // 獲取最佳的預覽尺寸
            mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
            if (swapRotation) {
                texture.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                texture.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
            }
            if (mImageReader == null) {
                // 創(chuàng)建一個ImageReader對象辣之,用于獲取攝像頭的圖像數(shù)據(jù),maxImages是ImageReader一次可以訪問的最大圖片數(shù)量
                mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                        ImageFormat.JPEG, 2);
                mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
            }
            //檢查是否支持閃光燈
            Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
            mFlashSupported = available == null ? false : available;
            mCameraManager.openCamera(mCameraId + "", mStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 設置相機閃關燈模式
     *
     * @param AE_MODE 閃關燈的模式
     * @throws CameraAccessException
     */
    private void setFlashMode(int AE_MODE) {
        if (mFlashSupported) {
            this.CONTROL_AE_MODE = AE_MODE;
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE);
            if (AE_MODE == CaptureRequest.CONTROL_AE_MODE_OFF) {
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
            }
        }

        // 構建上述的請求
        mPreviewRequest = mPreviewRequestBuilder.build();
        // 重復進行上面構建的請求, 用于顯示預覽
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 創(chuàng)建預覽對話
     */
    private void createCameraPreview() {
        try {
            // 獲取texture實例
            SurfaceTexture surfaceTexture = texture.getSurfaceTexture();
            assert surfaceTexture != null;
            //我們將默認緩沖區(qū)的大小配置為我們想要的相機預覽的大小掰伸。
            surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            // 用來開始預覽的輸出surface
            Surface surface = new Surface(surfaceTexture);
            //創(chuàng)建預覽請求構建器
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //將TextureView的Surface作為相機的預覽顯示輸出
            mPreviewRequestBuilder.addTarget(surface);
            //在這里,我們?yōu)橄鄼C預覽創(chuàng)建一個CameraCaptureSession怀估。
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // 相機關閉時, 直接返回
                            if (null == mCameraDevice) {
                                return;
                            }
                            //會話準備就緒后狮鸭,我們開始顯示預覽。
                            // 會話可行時, 將構建的會話賦給field
                            mCaptureSession = cameraCaptureSession;

                            //相機預覽應該連續(xù)自動對焦多搀。
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            //設置閃關燈模式
                            setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("預覽失敗了");

                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 拍照時調用方法
     */
    private void captureStillPicture() {
        try {
            if (mCameraDevice == null) {
                return;
            }
            // 創(chuàng)建作為拍照的CaptureRequest.Builder
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 將imageReader的surface作為CaptureRequest.Builder的目標
            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
/*            // 設置自動對焦模式
            mBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 設置自動曝光模式
            mBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);*/
            //設置為自動模式
//            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            setFlashMode(CONTROL_AE_MODE);
            // 停止連續(xù)取景
            mCaptureSession.stopRepeating();
            // 捕獲靜態(tài)圖像
            mCaptureSession.capture(mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                // 拍照完成時激發(fā)該方法
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                    //重新打開預覽
                    createCameraPreview();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取設備方向
     *
     * @param characteristics
     * @param deviceOrientation
     * @return
     */
    private static int sensorToDeviceRotation(CameraCharacteristics characteristics, int deviceOrientation) {
        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        deviceOrientation = ORIENTATIONS.get(deviceOrientation);
        return (sensorOrientation + deviceOrientation + 360) % 360;
    }

    /**
     * 獲取可用設備可用攝像頭列表
     */
    private void getCameraId(int ID) {
        try {
            for (String cameraId : mCameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == ID) {
                    continue;
                }
                mCameraId = Integer.valueOf(cameraId);
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 設置最佳尺寸
     *
     * @param sizes
     * @param width
     * @param height
     * @return
     */
    private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
        List<Size> collectorSizes = new ArrayList<>();
        for (Size option : sizes) {
            if (width > height) {
                if (option.getWidth() > width && option.getHeight() > height) {
                    collectorSizes.add(option);
                }
            } else {
                if (option.getHeight() > width && option.getWidth() > height) {
                    collectorSizes.add(option);
                }
            }
        }
        if (collectorSizes.size() > 0) {
            return Collections.min(collectorSizes, new Comparator<Size>() {
                @Override
                public int compare(Size s1, Size s2) {
                    return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
                }
            });
        }
        return sizes[0];
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera2);
        ButterKnife.bind(this);
        initView();
    }

    private void initView() {
        change.setOnCheckedChangeListener((buttonView, isChecked) -> {
            closeCamera();
            if (!isChecked) {
                //后置攝像頭
                mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            } else {
                //前置攝像頭
                mCameraId = CameraCharacteristics.LENS_FACING_BACK;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            }
        });

        flashRg.setOnCheckedChangeListener((group, checkedId) -> {
            switch (checkedId) {
                case R.id.auto:
                    //自動閃光燈
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    break;
                case R.id.open:
                    //開啟閃光燈
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
                    break;
                case R.id.close:
                    //關閉閃光燈
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_OFF);

                    break;
                default:
                    break;

            }
        });
        // 獲取CameraManager 相機設備管理器
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    }

    /**
     * 初試化拍照線程
     */
    public void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("Camera Background");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    public void stopBackgroundThread() {
        if (mBackgroundThread != null) {
            mBackgroundThread.quitSafely();
            try {
                mBackgroundThread.join();
                mBackgroundThread = null;
                mBackgroundHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Closes the current {@link CameraDevice}.
     * 關閉正在使用的相機
     */
    private void closeCamera() {
        // 關閉捕獲會話
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        // 關閉當前相機
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        // 關閉拍照處理器
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (texture.isAvailable()) {
            openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
        } else {
            texture.setSurfaceTextureListener(textureListener);
        }
        startBackgroundThread();
    }

    @Override
    protected void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @OnClick(R.id.takephoto)
    public void onViewClicked() {
        captureStillPicture();
    }

    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上顯示Toast的方法
     *
     * @param text The message to show
     */
    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上顯示Toast的方法
     *
     * @param text The message to show
     */
    private void showToast(final String text) {
        runOnUiThread(() -> Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show());
    }

    /**
     * Saves a JPEG {@link Image} into the specified {@link File}.
     * 保存圖片到自定目錄
     * 保存jpeg到指定的文件夾下, 開啟子線程執(zhí)行保存操作
     */
    private static class ImageSaver implements Runnable {

        /**
         * The JPEG image
         * 要保存的圖片
         */
        private final Image mImage;
        /**
         * The file we save the image into.
         * 圖片存儲的路徑
         */
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}

具體代碼請查看項目demo地址demo

總結

本文主要總結使用Android Camera2 API 自定義相機歧蕉,及實現(xiàn)閃光燈的打開,關閉康铭,自動惯退,前后攝像頭的切換等功能的集成等)。
demo中包含了使用Android Camera1/ Camera2 API 自定義相機及相關功能从藤,同時也包含了使用google框架 cameraView 自定義相機的demo催跪,歡迎大家瀏覽和Star!
筆者水平有限夷野,如有任何疑問懊蒸,請大神多多賜教!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末悯搔,一起剝皮案震驚了整個濱河市骑丸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖通危,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铸豁,死亡現(xiàn)場離奇詭異,居然都是意外死亡黄鳍,警方通過查閱死者的電腦和手機推姻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來框沟,“玉大人藏古,你說我怎么就攤上這事∪淘铮” “怎么了拧晕?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梅垄。 經(jīng)常有香客問我厂捞,道長,這世上最難降的妖魔是什么队丝? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任靡馁,我火速辦了婚禮,結果婚禮上机久,老公的妹妹穿的比我還像新娘臭墨。我一直安慰自己,他們只是感情好膘盖,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布胧弛。 她就那樣靜靜地躺著,像睡著了一般侠畔。 火紅的嫁衣襯著肌膚如雪结缚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天软棺,我揣著相機與錄音红竭,去河邊找鬼。 笑死喘落,一個胖子當著我的面吹牛德崭,可吹牛的內容都是我干的。 我是一名探鬼主播揖盘,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锌奴!你這毒婦竟也來了兽狭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箕慧,沒想到半個月后服球,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡颠焦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年斩熊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伐庭。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡粉渠,死狀恐怖,靈堂內的尸體忽然破棺而出圾另,到底是詐尸還是另有隱情霸株,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布集乔,位于F島的核電站去件,受9級特大地震影響,放射性物質發(fā)生泄漏扰路。R本人自食惡果不足惜尤溜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汗唱。 院中可真熱鬧宫莱,春花似錦、人聲如沸渡嚣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽识椰。三九已至绝葡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腹鹉,已是汗流浹背藏畅。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留功咒,地道東北人愉阎。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像力奋,于是被迫代替她去往敵國和親榜旦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容