安卓相机录制的视频倒置播放的问题

13

我使用以下代码录制了一段视频,录制效果很好,但播放视频时会倒置播放。

我在 mrec.prepare(); 之前尝试设置 mrec.setOrientationHint(180),但是这没有用。有什么提示吗?

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.app.Activity;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;

/**
 * @author SANA HASSAN
 */
public class CameraSurfaceView extends Activity {

    private Preview mPreview;
    private MediaRecorder mrec = new MediaRecorder();
    private int cameraId = 0;
    private Camera mCamera;


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

        requestWindowFeature(Window.FEATURE_NO_TITLE);

        mPreview = new Preview(this);
        setContentView(mPreview);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, 0, 0, "Start");
        menu.add(0, 1, 0, "Stop");
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case 0:
                try {
                    startRecording();
                } 
                catch (Exception e) {
                    e.printStackTrace();
                    mrec.release();
                }
                break;

            case 1: 
                mrec.stop();
                mrec.release();
                mrec = null;
                break;

            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    protected void startRecording() throws IOException  {

        mrec = new MediaRecorder();
        mrec.setCamera(mCamera);
        mCamera.unlock();
        File directory = new File(Environment.getExternalStorageDirectory()+"/NICUVideos");
        directory.mkdirs();
        mrec.setAudioSource( MediaRecorder.AudioSource.MIC);
        mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        mrec.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mrec.setOutputFile(Environment.getExternalStorageDirectory()+"/NICUVideos/"+System.currentTimeMillis()+".mp4"); 
        mrec.setPreviewDisplay(mPreview.getHolder().getSurface());
        mrec.setVideoSize(640, 480);

        Method[] methods = mrec.getClass().getMethods();
        for (Method method: methods){
            try{
                if(method.getName().equals("setAudioEncodingBitRate")){
                    method.invoke(mrec, 12200);
                }
                else if(method.getName().equals("setVideoEncodingBitRate")){
                    method.invoke(mrec, 800000);
                }
                else if(method.getName().equals("setAudioSamplingRate")){
                    method.invoke(mrec, 8000);
                }
                else if(method.getName().equals("setVideoFrameRate")){
                    method.invoke(mrec, 20);
                }
            }
            catch (IllegalArgumentException e) {
                e.printStackTrace();
            } 
            catch (IllegalAccessException e) {
                e.printStackTrace();
            } 
            catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mrec.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
        mrec.setMaxDuration(60000); // 60 seconds
        mrec.setMaxFileSize(10000000); // Approximately 10 megabytes
        mrec.prepare();
        mrec.start();
    }

    protected void stopRecording() {
        mrec.stop();
        mrec.release();
        mCamera.release();
    }

    class Preview extends SurfaceView implements SurfaceHolder.Callback {
        SurfaceHolder mHolder;
        Activity activity;

        Preview(Activity activity) {
            super(activity);
            mHolder = getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {

            Camera.CameraInfo info=new Camera.CameraInfo();
            for (int i=0; i < Camera.getNumberOfCameras(); i++) {
                Camera.getCameraInfo(i, info);
                if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    mCamera=Camera.open(i);
                    cameraId = i;
                }
            }

            try {
               mCamera.setPreviewDisplay(holder);
            } catch (IOException exception) {
                mCamera.release();
                mCamera = null;
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            setCameraDisplayOrientation(mCamera);
            mCamera.startPreview();
        }

        public void setCameraDisplayOrientation(Camera camera) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, info);

            int rotation = CameraSurfaceView.this.getWindowManager().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;
            }
            Log.d(Vars.TAG, "Result = "+result);
            camera.setDisplayOrientation(result);
        }
    }

}

这只发生在你的设备上吗?你尝试过在其他设备上吗? - Ron
你确定不是媒体播放器的设置问题吗?其他视频回放方向正确吗? - Ron
是的,我100%确定这不是媒体播放器设置的问题。我的手机是Atrix2,现在我不关心其他手机。 - Sana
10个回答

15

这个问题是由于 Android 只设置一些元数据而不实际旋转视频来处理旋转,而有些播放软件会忽略该设置。

文档中所述:

请注意,某些视频播放器可能会在播放时忽略视频中的组合矩阵。

您可以选择使用理解所设置元数据的不同播放软件,或者在录制后重新编码视频以使其正确方向。根据您的描述,哪种方案更好并不清楚。


7

在调用mrec.prepare();方法之前应该调用这个方法。

setOrientationHint(degrees);

链接

编辑: 尝试使用mCamera.setDisplayOrientation(degrees);

0 for landscape
90 for portrait
180 & 270 don't work very well and give weird results.

一些老旧的播放器和编码器无法解释此标志,这就是为什么视频会倒置播放的原因。


我已经试过了,但是没用,我在我的问题中已经说过了。 - Sana
我已经尝试了mCamera.setDisplayOrientation(90)来设置我的前置摄像头并且录制效果完美,但是当我进入/mnt/sdcard/folder/video.mp4文件夹并点击其中的文件时,它会以180度旋转的方式播放。 - Sana
已经尝试过了,我代码的最后一部分就是这么做的。请检查我的 setCameraDisplayOrientation 方法。 - Sana
我已经在setCameraDisplayOrientation方法中尝试过了,这个方法会自动处理前置摄像头的情况并设置mCamera.setDisplayOrientation(90),如果是后置摄像头则设置为mCamera.setDisplayOrientation(0) - Sana
API级别为10,即Android 2.3.3,我专门为Motorola Atrix2设备进行设计。 - Sana
显示剩余3条评论

2
我了解你的问题,视频使用媒体记录器(Media Recorder)相机(Camera)获取,因此您需要旋转媒体记录器(rotate Media Recorder)。使用以下代码应该可以解决您的问题。
/**
 * 
 * @param mMediaRecorder
 * @return
 */
public static MediaRecorder rotateBackVideo(MediaRecorder mMediaRecorder) {
    /**
     * Define Orientation of video in here,
     * if in portrait mode, use value = 90,
     * if in landscape mode, use value = 0
     */
    switch (CustomCamera.current_orientation) {
        case 0:
            mMediaRecorder.setOrientationHint(90);
            break;
        case 90:
            mMediaRecorder.setOrientationHint(180);
            break;
        case 180:
            mMediaRecorder.setOrientationHint(270);
            break;
        case 270:
            mMediaRecorder.setOrientationHint(0);
            break;
    }

    return mMediaRecorder;
}

prepare()方法之前应该添加:

// Step 5: Set the preview output
    /**
     * Define Orientation of image in here,
     * if in portrait mode, use value = 90,
     * if in landscape mode, use value = 0
     */
    CustomCamera.mMediaRecorder = Utils.rotateBackVideo(CustomCamera.mMediaRecorder);
    CustomCamera.mMediaRecorder.setPreviewDisplay(mCameraPreview.getHolder().getSurface());

谢谢你


2

我有同样的问题,我注意到相机预览方向角度和录制视频方向角度不同。因此,我使用以下方法来更改视频录制方向:

public static int getVideoOrientationAngle(Activity activity, int cameraId) { //The param cameraId is the number of the camera.
    int angle;
    Display display = activity.getWindowManager().getDefaultDisplay();
    int degrees = display.getRotation();
    android.hardware.Camera.CameraInfo info =
            new android.hardware.Camera.CameraInfo();
    android.hardware.Camera.getCameraInfo(cameraId, info);
    switch (degrees) {
        case Surface.ROTATION_0: 
            angle = 90; 
            break;
        case Surface.ROTATION_90:
            angle = 0;
            break;
        case Surface.ROTATION_180:
            angle = 270;
            break;
        case Surface.ROTATION_270:
            angle = 180;
            break;
        default:
            angle = 90;
            break;
    }
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
        angle = (angle + 180) % 360;

    return angle;
}

这是改变相机预览方向的方法:

 public static int setCameraDisplayOrientation(Activity activity,
                                              int cameraId, android.hardware.Camera camera) {
    android.hardware.Camera.CameraInfo info =
            new android.hardware.Camera.CameraInfo();
    android.hardware.Camera.getCameraInfo(cameraId, info);
    int rotation = activity.getWindowManager().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;
    }
    camera.setDisplayOrientation(result);
    return result;
}

请注意,了解相机是前置还是后置很重要。


我需要在哪里设置getVideoOrientationAngle的返回值? - Manish Gupta
mrec.setOrientationHint(...): - Alevittoria

1
使用OrientationEventListener并在设备旋转时跟踪旋转值此处为代码。此代码将旋转应用于相机,但对于录制,您需要将旋转应用于MediaRecorder。当您开始录制时,只需在mMediaRecorder.prepare()之前设置mMediaRecorder.setOrientationHint(rotation)即可解决我的问题。

0

这是自定义肖像相机的代码,将设置图片和视频的正确旋转:


private OrientationEventListener orientationEventListener;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    orientationEventListener = new OrientationEventListener(this) {
        @Override
        public void onOrientationChanged(int orientation) {
            if (orientation == ORIENTATION_UNKNOWN) return;

            flashButton.setRotation(-(orientation));
            cameraButton.setRotation(-(orientation));

            if (camera != null) {
                Parameters parameters = camera.getParameters();
                CameraInfo info = new CameraInfo();
                Camera.getCameraInfo(selectedCamera, info);
                orientation = (orientation + 45) / 90 * 90;
                int rotation = 0;
                if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
                    rotation = (info.orientation - orientation + 360) % 360;
                } else {  // back-facing camera
                    rotation = (info.orientation + orientation) % 360;
                }
                parameters.setRotation(rotation);
                if (!isRecording) {
                    mediaRecorder.setOrientationHint(rotation);
                }

                camera.setParameters(parameters);
            }
        }
    };
}


@Override
protected void onResume() {
    super.onResume();
    //...
    orientationEventListener.enable();
}

@Override
protected void onPause() {
    super.onPause();
    orientationEventListener.disable();
    //...
}

测试竖屏方向。记得在清单文件中添加以测试代码。我不知道是否适用于横屏。

<activity android:name=".activities.CameraActivity"
          android:screenOrientation="portrait">

这个程序可以处理前置和后置摄像头拍摄的照片,但只能处理后置摄像头拍摄的视频。目前我的代码无法处理前置摄像头拍摄的视频。如果我找到问题,我会发布相关代码。 - IgniteCoders
我不知道代码是否适用于横屏活动,如果不适用,你所需要做的就是将最终旋转角度增加或减少90度来修复它。 - IgniteCoders

0

最终我发现摩托罗拉手机在播放竖屏录制的视频时存在问题。

为了解决视频旋转的问题,我采用的最佳解决方案是将视频上传到服务器并使用命令 ffmpeg -i input.mp4 -c:v mpeg4 -c:a copy -c:s copy -vf "transpose=2" output.mp4 运行 ffmpeg

如果您认为还有其他方法,请告诉我。


0

如果要设置为竖屏模式,请使用mediaRecorder.setOrientationHint(90);角度与相机方向相同,可以使用myCamera.setDisplayOrientation(90)。


0

另一种解决方案是旋转您的活动,使其方向与传感器方向相同。也就是说,后置摄像头为横屏,前置摄像头为倒立竖屏。顺便说一下,这不会修复前置摄像头的镜像效果。另一个困难是您将不得不以符合活动方向的方式在这些旋转的活动中实现您的UI。


0

根据前置摄像头视频录制时相机显示方向所使用的度数,将您的MediaRecorder旋转如下

Display display = ((WindowManager)getContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
        parameters.setPreviewSize(height, width);
        if(display.getRotation() == Surface.ROTATION_0)
        {

            mCamera.setDisplayOrientation(90);
            mMediaRecorder.setOrientationHint(270);
        }

        if(display.getRotation() == Surface.ROTATION_90)
        {
            mMediaRecorder.setOrientationHint(180);
        }

        if(display.getRotation() == Surface.ROTATION_180)
        {
            mMediaRecorder.setOrientationHint(270);
        }

        if(display.getRotation() == Surface.ROTATION_270)
        {
            mCamera.setDisplayOrientation(180);
            mMediaRecorder.setOrientationHint(0);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接