android相机surfaceview方向

Posted

技术标签:

【中文标题】android相机surfaceview方向【英文标题】:android camera surfaceview orientation 【发布时间】:2011-07-06 16:35:26 【问题描述】:

好的,所以我有一个扩展 SurfaceView 并覆盖的类

surfaceChanged - 只需调用 startPreview surfaceCreated - 打开相机,编辑参数 *,设置 surfaceHolder surfaceDestroyed - 调用 stopPreview,释放相机

这一切都很好,因为当方向是纵向时:

从表面创建 *

m_camera = Camera.open();
Camera.Parameters p = m_camera.getParameters();

if (getResources().getConfiguration().orientation != 
    Configuration.ORIENTATION_LANDSCAPE)

    p.set("orientation", "portrait");

    // CameraApi is a wrapper to check for backwards compatibility  
    if (CameraApi.isSetRotationSupported())
    
         CameraApi.setRotation(p, 90);
    

但是,每次方向改变时,它都会调用 Camera.open()... 您可能知道这是一项相当昂贵的操作,导致转换不那么平滑。

当我将方向强制为横向时,预览效果很好。创建只被调用一次,因为预览是横向的,相机总是用户看到的。但是,我需要一种方法来设置纵向拍摄的实际照片的方向。但是,当我强制横向时,永远不会重新创建表面,并且当相机保持纵向时永远不会设置参数。

那么,我该如何(仅)执行以下操作之一?

    当方向改变时,在 onDestroy 和 onCreate 之间按住 m_camera,以便平滑过渡

    强制横向并以另一种方式检测方向变化...如果保持纵向,则旋转最终拍摄的照片。

另外,如果我不在基地,有人可以为我指出更好的方向吗?谢谢。

【问题讨论】:

+1 我也对此感兴趣。默认的谷歌相机应用程序执行得很好:它不会重新创建活动,但按钮和最后的图像预览很好地旋转以匹配横向/纵向方向。顺便说一句,在我看来,p.set("orientation", "portrait") 是一种隐藏的 API 用法,并且不受官方支持,不是吗? 我认为它实际上并没有做任何事情,哈哈。我首选的方法是强制景观。问题是我需要以另一种方式检测方向,因为这样就不会重新创建 cameraActivity。 啊,我明白你的想法了。因此,您将强制相机活动为横向,然后根据 真实 方向,只需旋转图片,对吗? This 可以帮助你。这不是一个坏主意,我可能会自己去实现它(-. 【参考方案1】:

我的实现方式:

private Camera mCamera;
private OrientationEventListener mOrientationEventListener;
private int mOrientation =  -1;

private static final int ORIENTATION_PORTRAIT_NORMAL =  1;
private static final int ORIENTATION_PORTRAIT_INVERTED =  2;
private static final int ORIENTATION_LANDSCAPE_NORMAL =  3;
private static final int ORIENTATION_LANDSCAPE_INVERTED =  4;

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    // force Landscape layout
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR | ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
   /*
   Your other initialization code here
   */


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

    if (mOrientationEventListener == null)             
        mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) 

            @Override
            public void onOrientationChanged(int orientation) 

                // determine our orientation based on sensor response
                int lastOrientation = mOrientation;

                if (orientation >= 315 || orientation < 45) 
                    if (mOrientation != ORIENTATION_PORTRAIT_NORMAL)                           
                        mOrientation = ORIENTATION_PORTRAIT_NORMAL;
                    
                
                else if (orientation < 315 && orientation >= 225) 
                    if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) 
                        mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
                                           
                
                else if (orientation < 225 && orientation >= 135) 
                    if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) 
                        mOrientation = ORIENTATION_PORTRAIT_INVERTED;
                                           
                
                else  // orientation <135 && orientation > 45
                    if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) 
                        mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
                                           
                   

                if (lastOrientation != mOrientation) 
                    changeRotation(mOrientation, lastOrientation);
                
            
        ;
    
    if (mOrientationEventListener.canDetectOrientation()) 
        mOrientationEventListener.enable();
    


@Override protected void onPause() 
    super.onPause();
    mOrientationEventListener.disable();


/**
 * Performs required action to accommodate new orientation
 * @param orientation
 * @param lastOrientation
 */
private void changeRotation(int orientation, int lastOrientation) 
    switch (orientation) 
        case ORIENTATION_PORTRAIT_NORMAL:
            mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 270));
            mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 270));
            Log.v("CameraActivity", "Orientation = 90");
            break;
        case ORIENTATION_LANDSCAPE_NORMAL:
            mSnapButton.setImageResource(android.R.drawable.ic_menu_camera);
            mBackButton.setImageResource(android.R.drawable.ic_menu_revert);
            Log.v("CameraActivity", "Orientation = 0");
            break;
        case ORIENTATION_PORTRAIT_INVERTED:
            mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 90));
            mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 90));
            Log.v("CameraActivity", "Orientation = 270");
            break;
        case ORIENTATION_LANDSCAPE_INVERTED:
            mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 180));
            mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 180));      
            Log.v("CameraActivity", "Orientation = 180");
            break;
    


    /**
 * Rotates given Drawable
 * @param drawableId    Drawable Id to rotate
 * @param degrees       Rotate drawable by Degrees
 * @return              Rotated Drawable
 */
private Drawable getRotatedImage(int drawableId, int degrees) 
    Bitmap original = BitmapFactory.decodeResource(getResources(), drawableId);
    Matrix matrix = new Matrix();
    matrix.postRotate(degrees);

    Bitmap rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true);
    return new BitmapDrawable(rotated);

然后在您的 PictureCallback 中设置元数据以指示旋转级别:

    private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() 

    @Override
    public void onPictureTaken(byte[] data, Camera camera) 
        try 
            // Populate image metadata

            ContentValues image = new ContentValues();
            // additional picture metadata
            image.put(Media.DISPLAY_NAME, [picture name]);
            image.put(Media.MIME_TYPE, "image/jpg");
            image.put(Media.TITLE, [picture title]);
            image.put(Media.DESCRIPTION, [picture description]);
            image.put(Media.DATE_ADDED, [some time]);
            image.put(Media.DATE_TAKEN, [some time]);
            image.put(Media.DATE_MODIFIED, [some time]);

            // do not rotate image, just put rotation info in
            switch (mOrientation) 
                case ORIENTATION_PORTRAIT_NORMAL:
                    image.put(Media.ORIENTATION, 90);
                    break;
                case ORIENTATION_LANDSCAPE_NORMAL:
                    image.put(Media.ORIENTATION, 0);
                    break;
                case ORIENTATION_PORTRAIT_INVERTED:
                    image.put(Media.ORIENTATION, 270);
                    break;
                case ORIENTATION_LANDSCAPE_INVERTED:
                    image.put(Media.ORIENTATION, 180);
                    break;
            

            // store the picture
            Uri uri = getContentResolver().insert(
                    Media.EXTERNAL_CONTENT_URI, image);

            try 
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
                        data.length);
                OutputStream out = getContentResolver().openOutputStream(
                        uri);
                boolean success = bitmap.compress(
                        Bitmap.CompressFormat.JPEG, 75, out);
                out.close();
                if (!success) 
                    finish(); // image output failed without any error,
                                // silently finish
                

             catch (Exception e) 
                e.printStackTrace();
                // handle exceptions
            

            mResultIntent = new Intent();
            mResultIntent.setData(uri);
         catch (Exception e) 
            e.printStackTrace();
        

        finish();
    
;

希望对你有帮助。

更新现在,当基于横向的设备出现时,需要在 OrientationEventListener 中对其进行额外检查。

Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();                                        
if (display.getOrientation() == Surface.ROTATION_0)  
    // landscape oriented devices
 else  
    // portrait oriented device

完整代码(LC有点浪费,但很容易演示该方法)

@Override
public void onOrientationChanged(int orientation) 

    // determine our orientation based on sensor response
    int lastOrientation = mOrientation;

    Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();                                        

    if (display.getOrientation() == Surface.ROTATION_0)    // landscape oriented devices
        if (orientation >= 315 || orientation < 45) 
            if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL)                          
                mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
            
         else if (orientation < 315 && orientation >= 225) 
            if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) 
                mOrientation = ORIENTATION_PORTRAIT_INVERTED;
                                   
         else if (orientation < 225 && orientation >= 135) 
            if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) 
                mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
                                   
         else if (orientation <135 && orientation > 45)  
            if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) 
                mOrientation = ORIENTATION_PORTRAIT_NORMAL;
                                   
                               
     else   // portrait oriented devices
        if (orientation >= 315 || orientation < 45) 
            if (mOrientation != ORIENTATION_PORTRAIT_NORMAL)                           
                mOrientation = ORIENTATION_PORTRAIT_NORMAL;
            
         else if (orientation < 315 && orientation >= 225) 
            if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) 
                mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
                                   
         else if (orientation < 225 && orientation >= 135) 
            if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) 
                mOrientation = ORIENTATION_PORTRAIT_INVERTED;
                                   
         else if (orientation <135 && orientation > 45)  
            if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) 
                mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
                                   
        
    

    if (lastOrientation != mOrientation) 
        changeRotation(mOrientation, lastOrientation);
    

【讨论】:

我最终使用了默认的相机活动,但是这看起来不错,我可能会在其他时间回来。谢谢。 欢迎您。老实说,我实现了自己的摄像头活动,因为与不同安卓手机上默认摄像头活动的所有不一致,我觉得做各种黑客来绕过它们是不对的。 @Audrius 我用按钮创建了简单的自定义摄像头,但我需要在设备的 4 侧旋转摄像头...怎么做? 永远不要使用mOrientationEventListene,它的更新频率比你的应用程序方向变化的频率高……最好使用DisplayManager.DisplayListener【参考方案2】:

您是否考虑过使用 API 文档中提供的标准方法,您可以在 surfaceChanged 上调用该方法?您可以将度数存储在全局变量中,以便以后在保存图片时使用。也可以对你的相机变量做一个简单的空检查,所以你不会在surfaceCreated中再次创建它。

public void setCameraDisplayOrientation() 
        
     if (mCamera == null)
     
         Log.d(TAG,"setCameraDisplayOrientation - camera null");
         return;             
     

     Camera.CameraInfo info = new Camera.CameraInfo();
     Camera.getCameraInfo(CAM_ID, info);

     WindowManager winManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     int rotation = winManager.getDefaultDisplay().getRotation();

     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;
     
     mCamera.setDisplayOrientation(result);

【讨论】:

如果你继续实现它,请注意,CAM_ID 是一个全局变量,我在别处设置。 在surfaceCreaeted/surfaceChanged或onResume中我们必须在哪里调用setCameraDisplayOrientation() 我不知道如何使用“mCamera”和“CAM_ID”。我该如何实现它们? 此代码在设置方向修复纵向或横向时不起作用【参考方案3】:

正如您从其他答案中看到的,这段代码变得非常复杂。您可能希望使用库来帮助您提供此功能,例如,CWAC-Camera 支持 OS 2.3 及更高版本(希望您现在可以放弃 OS 2.1 和 OS 2.2 支持):https://github.com/commonsguy/cwac-camera

CWAC-Camera 支持将相机预览锁定为横向,并会自动将图像旋转到适合您的校正方向。如果您想了解所有需要解决的设备特定问题,请浏览project issues,这是 IMO 尝试使用库而不是维护所有这些代码并自行测试的更多原因。

【讨论】:

以上是关于android相机surfaceview方向的主要内容,如果未能解决你的问题,请参考以下文章

Android:在方向更改时保留相机预览,而其他视图可以旋转

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

相机中使用的Android模糊surfaceview

Android相机教程(使用surfaceview)

如何在surfaceview中将Android相机更改为人像?

android自定义相机拉伸surfaceview