片段中的 Android 相机预览

Posted

技术标签:

【中文标题】片段中的 Android 相机预览【英文标题】:Android Camera preview in a fragment 【发布时间】:2014-10-25 14:49:52 【问题描述】:

到目前为止,我有一个完整的工作代码,可以插入摄像头以查看前置摄像头的预览。

我现在要做的是让相机在Fragment 内工作。

完整代码:

MainActivity.java

public class MainActivity extends Activity 

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_main);

    getFragmentManager().beginTransaction().add(R.id.mainLayout, new CameraExtractionFragment()).commit();


CameraExtractionFragment.java

public class CameraExtractionFragment extends Fragment 

private CameraExtraction mCameraExtraction;
Camera mCamera;
int mNumberOfCameras;
int cameraId;
int rotation;

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    mCameraExtraction = new CameraExtraction(
            this.getActivity().getBaseContext(), 
            this.getActivity().getWindowManager().getDefaultDisplay().getRotation()
            );

    // Find the total number of cameras available
    mNumberOfCameras = Camera.getNumberOfCameras();

    // Find the ID of the rear-facing ("default") camera
    CameraInfo cameraInfo = new CameraInfo();
    for (int i = 0; i < mNumberOfCameras; i++) 
        Camera.getCameraInfo(i, cameraInfo);
        if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) 
            cameraId = i;
        
    


 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)               
     return mCameraExtraction;
 

 @Override
public void onResume() 
    super.onResume();

    // Use mCurrentCamera to select the camera desired to safely restore
    // the fragment after the camera has been changed
    mCamera = Camera.open(cameraId);
    mCameraExtraction.setCamera(mCamera);


@Override
public void onPause() 
    super.onPause();

    if (mCamera != null)
    
        mCamera.release();
    



// Modo en el que se pinta la cámara: encajada por dentro o saliendo los bordes por fuera.
public enum CameraViewMode 

    /**
     * Inner mode
     */
    Inner,
    /**
     * Outer mode 
     */
    Outer


CameraExtraction.java

public class CameraExtraction extends ViewGroup implements SurfaceHolder.Callback 

 private final String TAG = "CameraExtraction";

Camera mCamera;
SurfaceHolder mHolder;
SurfaceView mSurfaceView;
int mNumberOfCameras;
int cameraId;
Rect desiredSize;
CameraViewMode cameraViewMode;
boolean mSurfaceCreated = false;
List<Size> mSupportedPreviewSizes;
int rotation;
Size mPreviewSize;

public CameraExtraction(Context context, int rotation) 
    super(context);

    this.rotation = rotation;

    mSurfaceView = new SurfaceView(context);

    addView(mSurfaceView);

    // Install a SurfaceHolder.Callback so we get notified when the
    mHolder = mSurfaceView.getHolder();
    mHolder.addCallback(this);
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    cameraViewMode = CameraViewMode.Inner;


public void setCamera(Camera camera) 
    mCamera = camera;
    if (mCamera != null) 
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        if (mSurfaceCreated) requestLayout();
    


public void switchCamera(Camera camera) 
    setCamera(camera);
    try 
        camera.setPreviewDisplay(mHolder);
     catch (IOException exception) 
        Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
    


@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    if (mSurfaceView == null ||mSurfaceView.getHolder() == null) return;

    if (mSurfaceView.getHolder().getSurface() == null) 
        // preview surface does not exist
        return;
    

    final int width = resolveSize(getSuggestedMinimumWidth(),
            widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(),
            heightMeasureSpec);
    setMeasuredDimension(width, height);

    if (mSupportedPreviewSizes != null) 

        mPreviewSize = getNearestPreviewSize(mCamera.new Size(widthMeasureSpec,heightMeasureSpec));
    

    if (mCamera != null) 
      Camera.Parameters parameters = mCamera.getParameters();
      parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

      mCamera.setParameters(parameters);
    


@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) 
    if (getChildCount() > 0) 
        final View child = getChildAt(0);

        final int width = r - l;
        final int height = b - t;

        int previewWidth = width;
        int previewHeight = height;
        if (mPreviewSize != null) 
            previewWidth = mPreviewSize.width;
            previewHeight = mPreviewSize.height;
        

        // Center the child SurfaceView within the parent.
        if (width * previewHeight > height * previewWidth) 
            final int scaledChildWidth = previewWidth * height
                    / previewHeight;
            child.layout((width - scaledChildWidth) / 2, 0,
                    (width + scaledChildWidth) / 2, height);
         else 
            final int scaledChildHeight = previewHeight * width
                    / previewWidth;
            child.layout(0, (height - scaledChildHeight) / 2, width,
                    (height + scaledChildHeight) / 2);
        
           


@Override
public void surfaceCreated(SurfaceHolder holder) 
    mCamera = Camera.open(cameraId);        


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) 
    if (mSurfaceView == null || mSurfaceView.getHolder() == null) return;

            if (mSurfaceView.getHolder().getSurface() == null) 
                // preview surface does not exist
                return;
            

            // set preview size and make any resize, rotate or
            // reformatting changes here
            Camera.Parameters param = mCamera.getParameters();
            Point previewSize = new Point(640,480);

            Camera.Size size = getNearestPreviewSize(mCamera.new Size(previewSize.x,previewSize.y));
            param.setPreviewSize(size.width, size.height);
            mCamera.setParameters(param);
            rotation = setCameraDisplayOrientation(cameraId, mCamera);

            // start preview with new settings
            try 
                mCamera.setPreviewCallback(new Camera.PreviewCallback() 

                    @Override
                    public void onPreviewFrame(byte[] data, Camera camera) 
                        // TODO Auto-generated method stub

                    
                );
                mCamera.setPreviewDisplay(mSurfaceView.getHolder());
                mCamera.startPreview();

             catch (Exception e) 
                Log.d("androidControlSurfaceView",
                        "Error starting camera preview: " + e.getMessage());
                   


@Override
public void surfaceDestroyed(SurfaceHolder holder) 
    if (mCamera != null)
    
        mCamera.stopPreview();
        mCamera.release();
    



protected Rect getCameraViewSizeCompensated(Camera.Size cameraPreviewSize, Point hostViewSize) 
    Rect toReturn=null;

    float ratioWidth = hostViewSize.x / (float)cameraPreviewSize.width;
    float ratioHeight = hostViewSize.y / (float)cameraPreviewSize.height;

    switch (cameraViewMode)
    case Inner:
        if (ratioWidth < ratioHeight) 
            int newHeight = (int)(cameraPreviewSize.height*ratioWidth);
            int y = (hostViewSize.y - newHeight) / 2;
            toReturn = new Rect(0, y, hostViewSize.x, y+newHeight);
         else 
            int newWidth = (int)(cameraPreviewSize.width*ratioHeight);
            int x = (hostViewSize.x - newWidth) / 2;
            toReturn = new Rect(x, 0, x+newWidth,hostViewSize.y);
        
        break;
    case Outer:
        if (ratioWidth < ratioHeight) 
            int newWidth = (int)(cameraPreviewSize.width*ratioHeight);
            int x = (hostViewSize.x - newWidth) / 2;
            toReturn = new Rect(x, 0, x+newWidth,hostViewSize.y);
         else 
            int newHeight = (int)(cameraPreviewSize.height*ratioWidth);
            int y = (hostViewSize.y - newHeight) / 2;
            toReturn = new Rect(0, y, hostViewSize.x, y+newHeight);
        
        break;
    
    return toReturn;


private Camera.Size getNearestPreviewSize(Camera.Size size) 
  List<Camera.Size> availableSizes =  mCamera.getParameters().getSupportedPreviewSizes();
  if (availableSizes == null || availableSizes.size() <= 0) return null;

  Camera.Size toReturn = availableSizes.get(0);
  int distance = Math.abs(size.width*size.height - toReturn.width*toReturn.height);
  for (int a=1; a<availableSizes.size(); a++) 
      int temp = Math.abs(size.width*size.height - availableSizes.get(a).width*availableSizes.get(a).height);
      if (temp < distance) 
          distance = temp;
          toReturn = availableSizes.get(a);
      
  
  return toReturn;
 


public int setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) 

     CameraInfo info = new Camera.CameraInfo();
     Camera.getCameraInfo(cameraId, info);
     int degrees = 0;

     switch (rotation) 
         case Surface.ROTATION_0: degrees = 0; break;
         case Surface.ROTATION_90: degrees = 90; break;
         case Surface.ROTATION_180: degrees = 180; break;
         case Surface.ROTATION_270: degrees = 270; break;
     

     int result;
     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 
         result = (info.orientation + degrees) % 360;
         result = (360 - result) % 360;  // compensate the mirror
      else   // back-facing
         result = (info.orientation - degrees + 360) % 360;
     
     camera.setDisplayOrientation(result);
     return result/90;
 


但是当您运行应用程序时,我的设备中没有显示任何图像。只有一个白屏。请注意,正如我提到的,相机正在一个不包含片段的活动中工作

那么,为什么主要活动显示为白屏?

PS:Here你可以下载我的代码测试一下。

【问题讨论】:

你能把你的代码贴在这里吗?您的链接已失效且无法正常工作。 【参考方案1】:

首先 - 使用 FrameLayout 进行相机预览。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainLayout"
    android:layout_
    android:layout_ />

第二个 - 无需打开相机两次。你的surfaceCreated 方法。

@Override
public void surfaceCreated(SurfaceHolder holder) 
    try 
        if (mCamera != null) 
            mCamera.setPreviewDisplay(holder);
            mCamera.setPreviewCallback(new Camera.PreviewCallback() 
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) 
                
            );
        
     catch (IOException exception) 
        Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
    

第三个 - 无需释放相机两次。您是在 Fragment 中完成的,只需将其从 surfaceDestroyed 中删除即可。

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
        if (mCamera != null)
        
//          mCamera.stopPreview();
//          mCamera.release();
        
    

在你的片段中。

@Override
public void onPause() 
    super.onPause();

    if (mCamera != null)
    
        mCamera.stopPreview();
        mCamera.release();
    

你会在我看到的片段中看到你的相机预览。祝你好运!

【讨论】:

【参考方案2】:

您似乎在CameraExtractionFragmentCameraExtraction 中有属性cameraId,但它仅在CameraExtractionFragment 中分配了一个值。

您应该删除CameraExtraction.cameraId 并改用CameraExtractionFragment.cameraId

【讨论】:

以上是关于片段中的 Android 相机预览的主要内容,如果未能解决你的问题,请参考以下文章

如何在android中的相机预览上绘制矩形

如何设置 Open GLES2.0 与 Android 相机配合使用?

如何从Android片段中的相机获取图像

为啥Android模拟器中的相机预览会旋转90度?

Android中的相机,如何获得最佳尺寸,预览尺寸,图片尺寸,视图尺寸,图像失真

Android 相机预览应用中的 Nexus 5x 反向横向传感器修复