如何在 SurfaceView 上显示相机预览?

Posted

技术标签:

【中文标题】如何在 SurfaceView 上显示相机预览?【英文标题】:How to show the camera preview on a SurfaceView? 【发布时间】:2017-02-15 01:03:00 【问题描述】:

我正在尝试在 SurfaceView 上打开相机硬件。在布局中,我创建了一个 SurfaceView 并打开相机,如下面的代码所示。当我运行代码时,CameraAvailableCB 中的 toast 出现并显示“onCameraAvailable”,但 SurfaceView 上没有出现任何内容。

如何在 SurfaceView 上显示相机显示?

代码

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.mBtnCapture = (Button) findViewById(R.id.actMain_btn_capture);
    this.mSurfaceView = (SurfaceView) findViewById(R.id.actMain_surfaceView);
    this.mSurfaceHolder = this.mSurfaceView.getHolder();
    this.mSurfaceHolder.addCallback(this);
    this.mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);

    try 
        cameraIDsList = this.mCameraManager.getCameraIdList();

        for (String id : cameraIDsList) 
            Log.v(TAG, "CameraID: " + id);
        

     catch (CameraAccessException e) 
        e.printStackTrace();
    

    cameraStateCB = new CameraDevice.StateCallback() 
        @Override
        public void onOpened(CameraDevice camera) 
            Toast.makeText(getApplicationContext(), "onOpened", Toast.LENGTH_SHORT).show();

            //requesting permission
            int permissionCheck = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
            if (permissionCheck != PackageManager.PERMISSION_GRANTED) 

                if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA)) 

                 else 
                    ActivityCompat.requestPermissions(activity, new String[]Manifest.permission.CAMERA, MY_PERMISSIONS_REQUEST_CAMERA);
                    Toast.makeText(getApplicationContext(), "request permission", Toast.LENGTH_SHORT).show();
                

             else 
                Toast.makeText(getApplicationContext(), "PERMISSION_ALREADY_GRANTED", Toast.LENGTH_SHORT).show();
            

            //opening the camera
            try 
                mCameraManager.openCamera(cameraIDsList[1], cameraStateCB, new Handler());

             catch (CameraAccessException e) 
                e.printStackTrace();
            
        

        @Override
        public void onDisconnected(CameraDevice camera) 
            Toast.makeText(getApplicationContext(), "onDisconnected", Toast.LENGTH_SHORT).show();
        

        @Override
        public void onError(CameraDevice camera, int error) 
            Toast.makeText(getApplicationContext(), "onError", Toast.LENGTH_SHORT).show();
        
    ;

    CameraManager.AvailabilityCallback cameraAvailableCB = new CameraManager.AvailabilityCallback() 
        @Override
        public void onCameraAvailable(String cameraId) 
            super.onCameraAvailable(cameraId);

            Toast.makeText(getApplicationContext(), "onCameraAvailable", Toast.LENGTH_SHORT).show();
        

        @Override
        public void onCameraUnavailable(String cameraId) 
            super.onCameraUnavailable(cameraId);

            Toast.makeText(getApplicationContext(), "onCameraUnavailable", Toast.LENGTH_SHORT).show();
        
    ;
    this.mCameraManager.registerAvailabilityCallback(cameraAvailableCB, new Handler());




@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) 
        case MY_PERMISSIONS_REQUEST_CAMERA:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                // Open Camera
                break;
    


@Override
public void surfaceCreated(SurfaceHolder holder) 
    Log.w(TAG, "surfaceCreated");


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
    Log.w(TAG, "surfaceChanged");


@Override
public void surfaceDestroyed(SurfaceHolder holder) 
    Log.w(TAG, "surfaceDestroyed");

【问题讨论】:

那么,“onOpened”toast 也没有出现? @AlexCohn 有时会出现 【参考方案1】:

要使用 Camera2 API 从相机显示预览,您应该执行以下步骤:

    获取使用相机设备的权限 使用 CameraManager 打开与相机的连接 已准备好 Surface 以供预览 使用打开的相机设备和所需的表面(它不仅可以包括预览表面)创建 CaptureSession 创建 CaptureSession 后,您需要创建和配置 CaptureRequest 并将其提交给 CaptureSession

需要注意,准备表面和打开与相机的连接是独立的过程,因此您需要确保它们都在创建 CaptureSession 之前完成。

这是在屏幕上显示相机预览的 Activity 示例:

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Handler.Callback 
    static final String TAG = "CamTest";
    static final int MY_PERMISSIONS_REQUEST_CAMERA = 1242;
    private static final int MSG_CAMERA_OPENED = 1;
    private static final int MSG_SURFACE_READY = 2;
    private final Handler mHandler = new Handler(this);
    SurfaceView mSurfaceView;
    SurfaceHolder mSurfaceHolder;
    CameraManager mCameraManager;
    String[] mCameraIDsList;
    CameraDevice.StateCallback mCameraStateCB;
    CameraDevice mCameraDevice;
    CameraCaptureSession mCaptureSession;
    boolean mSurfaceCreated = true;
    boolean mIsCameraConfigured = false;
    private Surface mCameraSurface = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceViewPreview);
        this.mSurfaceHolder = this.mSurfaceView.getHolder();
        this.mSurfaceHolder.addCallback(this);
        this.mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);

        try 
            mCameraIDsList = this.mCameraManager.getCameraIdList();
            for (String id : mCameraIDsList) 
                Log.v(TAG, "CameraID: " + id);
            
         catch (CameraAccessException e) 
            e.printStackTrace();
        

        mCameraStateCB = new CameraDevice.StateCallback() 
            @Override
            public void onOpened(CameraDevice camera) 
                Toast.makeText(getApplicationContext(), "onOpened", Toast.LENGTH_SHORT).show();

                mCameraDevice = camera;
                mHandler.sendEmptyMessage(MSG_CAMERA_OPENED);
            

            @Override
            public void onDisconnected(CameraDevice camera) 
                Toast.makeText(getApplicationContext(), "onDisconnected", Toast.LENGTH_SHORT).show();
            

            @Override
            public void onError(CameraDevice camera, int error) 
                Toast.makeText(getApplicationContext(), "onError", Toast.LENGTH_SHORT).show();
            
        ;
    

    @Override
    protected void onStart() 
        super.onStart();

        //requesting permission
        int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) 
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) 

             else 
                ActivityCompat.requestPermissions(this, new String[]Manifest.permission.CAMERA, MY_PERMISSIONS_REQUEST_CAMERA);
                Toast.makeText(getApplicationContext(), "request permission", Toast.LENGTH_SHORT).show();
            
         else 
            Toast.makeText(getApplicationContext(), "PERMISSION_ALREADY_GRANTED", Toast.LENGTH_SHORT).show();
            try 
                mCameraManager.openCamera(mCameraIDsList[1], mCameraStateCB, new Handler());
             catch (CameraAccessException e) 
                e.printStackTrace();
            
        
    

    @Override
    protected void onStop() 
        super.onStop();
        try 
            if (mCaptureSession != null) 
                mCaptureSession.stopRepeating();
                mCaptureSession.close();
                mCaptureSession = null;
            

            mIsCameraConfigured = false;
         catch (final CameraAccessException e) 
            // Doesn't matter, cloising device anyway
            e.printStackTrace();
         catch (final IllegalStateException e2) 
            // Doesn't matter, cloising device anyway
            e2.printStackTrace();
         finally 
            if (mCameraDevice != null) 
                mCameraDevice.close();
                mCameraDevice = null;
                mCaptureSession = null;
            
        
    

    @Override
    public boolean handleMessage(Message msg) 
        switch (msg.what) 
            case MSG_CAMERA_OPENED:
            case MSG_SURFACE_READY:
                // if both surface is created and camera device is opened
                // - ready to set up preview and other things
                if (mSurfaceCreated && (mCameraDevice != null)
                        && !mIsCameraConfigured) 
                    configureCamera();
                
                break;
        

        return true;
    

    private void configureCamera() 
        // prepare list of surfaces to be used in capture requests
        List<Surface> sfl = new ArrayList<Surface>();

        sfl.add(mCameraSurface); // surface for viewfinder preview

        // configure camera with all the surfaces to be ever used
        try 
            mCameraDevice.createCaptureSession(sfl,
                    new CaptureSessionListener(), null);
         catch (CameraAccessException e) 
            e.printStackTrace();
        

        mIsCameraConfigured = true;
    


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) 
            case MY_PERMISSIONS_REQUEST_CAMERA:
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)
                    try 
                        mCameraManager.openCamera(mCameraIDsList[1], mCameraStateCB, new Handler());
                     catch (CameraAccessException e) 
                        e.printStackTrace();
                    
                break;
        
    

    @Override
    public void surfaceCreated(SurfaceHolder holder) 
        mCameraSurface = holder.getSurface();
    

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
        mCameraSurface = holder.getSurface();
        mSurfaceCreated = true;
        mHandler.sendEmptyMessage(MSG_SURFACE_READY);
    

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
        mSurfaceCreated = false;
    

    private class CaptureSessionListener extends
            CameraCaptureSession.StateCallback 
        @Override
        public void onConfigureFailed(final CameraCaptureSession session) 
            Log.d(TAG, "CaptureSessionConfigure failed");
        

        @Override
        public void onConfigured(final CameraCaptureSession session) 
            Log.d(TAG, "CaptureSessionConfigure onConfigured");
            mCaptureSession = session;

            try 
                CaptureRequest.Builder previewRequestBuilder = mCameraDevice
                        .createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                previewRequestBuilder.addTarget(mCameraSurface);
                mCaptureSession.setRepeatingRequest(previewRequestBuilder.build(),
                        null, null);
             catch (CameraAccessException e) 
                Log.d(TAG, "setting up preview failed");
                e.printStackTrace();
            
        
    

【讨论】:

kotin 有这个吗? @MosesLiaoGZ 对不起,没有 谢谢,我猜...但是这有点荒谬,需要像这样的大量代码来显示一个该死的相机预览... @BadCash 虽然这是执行看似简单的任务的大量代码,但谷歌制作的软件包可以为您轻松完成。看看CameraView @HamzaAbdullah 尝试将 'mCameraIDsList[1]' 更改为 'mCameraIDsList[0]'【参考方案2】:

有了 CameraX,现在很容易。

    // Create a preview use case instance
val preview = Preview.Builder().build()

// Bind the preview use case and other needed user cases to a lifecycle
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis, imageCapture)

// Create a surfaceProvider using the bound camera's cameraInfo
val surfaceProvider = previewView.createSurfaceProvider(camera.cameraInfo)

// Attach the surfaceProvider to the preview use case to start preview
preview.setSurfaceProvider(surfaceProvider)


<androidx.camera.view.PreviewView
    android:layout_
    android:layout_
    app:scaleType="fitEnd" />

https://medium.com/androiddevelopers/display-a-camera-preview-with-previewview-86562433d86c

【讨论】:

问题要求在 SurfaceView 上渲染,而不是 PreviewView。

以上是关于如何在 SurfaceView 上显示相机预览?的主要内容,如果未能解决你的问题,请参考以下文章

使SurfaceView大于屏幕(将相机预览适配到大于显示的SurfaceView)

使SurfaceView大于屏幕(将相机预览适配到大于显示的SurfaceView)

如何在Android中修复相机预览(surfaceview)的正确纵横比?

相机中使用的Android模糊surfaceview

尝试在 SurfaceView 中设置时拉伸相机预览

是否可以裁剪相机预览?