如何在 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)