1 MediaRecorder 录制视频步骤

这篇文章介绍了Android录音MediaRecorder使用和SoundPool的使用 主要讲解了音频的录制和播放,这篇AudioRecord和AudioTrack更加底层的音频录制和播放今天这篇文章讲解录制视频,其实前面已经讲过了利用MediaCodec录制视频但考虑到简单开发用的比较多的还是MediaRecorder所以继续讲解视频的录制。
如何利用MediaRecorder录制视频?
在Camera的文档中写的很清楚:https://developer.android.google.cn/guide/topics/media/camera#capture-video
Capturing Videos。
不像利用Camera进行拍照,录制视频需要一个严格的调用顺序,你必须遵从下面的顺序才能很好地利用Camera在你的应用中录制出视频。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
具体步骤:

  • 1打开Camera,利用Camera.open()函数打开相机。
  • 2设置预览,把Camera和SurfaceView连接,利用Camera.setPreviewDisplay()设置预览的SurfaceHolder。
  • 3开始预览,调用Camera.startPreview()开始预览
  • 4开始录制视频(严格按照下面的顺序进行操作):
    • a . unLock Camera,解锁相机,利用函数Camera.unlock(),这样Camera才能被MediaRecodrder使用,否则会报错。
    • b. 配置MediaRecorder,生成MediaRecorder实例
      • (1)利用setCamera()设置Camera被视频录制使用,
      • (2)setAudioSource(),设置音频来自哪里(从哪里获取音频),MediaRecorder.AudioSource.CAMCORDER
      • (3)setVideoSource() ,设置视频来源(从哪里获取视频),MediaRecorder.VideoSource.CAMERA
      • (4)设置输出视频文件的格式和编码,从Android 2.2及以上版本开始,利用MediaRecorder.setProfile方法进行设置,利用CamcorderProfile.get()可以获取CamcorderProfile对象,内部包含了系统封装好的一些配置。
        至少需要设置的参数:
        setOutputFormat() 设置输出格式,一般设置为默认或者MediaRecorder.OutputFormat.MPEG_4.
        setAudioEncoder() 设置编码类型一般为默认或者MediaRecorder.AudioEncoder.AMR_NB.
        setVideoEncoder() 设置视频编码类型一般是默认或者MediaRecorder.VideoEncoder.MPEG_4_SP.
      • (5)setOutputFile() 设置输出文件的存放位置。
      • (6)setPreviewDisplay() ,设置预览的SurfaceHolder。
    • c 准备,调用MediaRecorder.prepare() 应用上面的配置。
    • d 开始视频录制,MediaRecorder.start()
  • 5 停止录制
    • a 停止MediaRecorder,停止录制
    • b 重置 MediaRecorder.reset(),移除设置的参数
    • c 释放MediaRecorder
    • d Lock Camera,Camera.lock(),从Android 4.0 开始,lock操作不是必须的,除非MediaRecorder.prepare()失败。
  • 6 Camera 停止预览,Camera.stopPreview().
  • 7 释放Camera,Camera.release()。

代码示例:来自developer

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mMediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mMediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mMediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

//如果是Android 2.2 以前,不能直接使用CamcorderProfile,需要手动设置如下参数:
    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

2 Camera的lock和 unlock

录制视频之前需要调用unlock,释放MediaRecorder时需要调用lock。

/**源码中的unlock注释:

  • Unlocks the camera to allow another process to access it.
  • Normally, the camera is locked to the process with an active Camera
  • object until {@link #release()} is called. To allow rapid handoff
  • between processes, you can call this method to release the camera
  • temporarily for another process to use; once the other process is done
  • you can call {@link #reconnect()} to reclaim the camera.
  • This must be done before calling

  • {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be
  • called after recording starts.
  • If you are not recording video, you probably do not need this method.

  • @throws RuntimeException if the camera cannot be unlocked.
    public native final void unlock();

unlock的作用是Camera属于硬件设备,通常情况下Camera被一个使用Camera的进程锁定,是不允许其他进程使用的。调用unlock后Camera可以允许其他进行使用它,否则如果正被其他进程使用会发生错误,经过试验就是不被其它进程使用不调用这个函数也会发生莫名错误,其他进程使用完之后可以调用reconnect释放Camera。unLock必须在你调用MediaRecorder.setCamera之前调用。注意如果你不是要录制视频,只是简单地预览不需要调用这个函数。

/**源码lock函数注释

  • Re-locks the camera to prevent other processes from accessing it.
  • Camera objects are locked by default unless {@link #unlock()} is
  • called. Normally {@link #reconnect()} is used instead.
  • Since API level 14, camera is automatically locked for applications in

  • {@link android.media.MediaRecorder#start()}. Applications can use the
  • camera (ex: zoom) after recording starts. There is no need to call this
  • after recording starts or stops.
  • If you are not recording video, you probably do not need this method.

  • @throws RuntimeException if the camera cannot be re-locked (for
  • example, if the camera is still in use by another process).
    

*/
public native final void lock();

Lock函数再次禁止其他进程使用Camera,听起来有点像reconnect,的确它两个功能一样,通常使用reconnect函数。
从api14 开始,录制视频时MediaRecorder调用start函数时Camera 会自动的调用Lock,所以再开始录制视频之前或者录制视频结束之后不需要手动的调用lock函数。
注意:lock和unLock 都是只有在录制视频时才会使用,其他情况用不到这两个函数。

3 setAudioSource,setVideoSource

设置音频和视频的来源

设置音频来源:
在AudioSource类中包括:

  • MediaRecorder.AudioSource.DEFAULT: 默认音频源
  • MediaRecorder.AudioSource.MIC:主麦克风。
  • MediaRecorder.AudioSource.VOICE_CALL:来源为语音拨出的语音与对方说话的声音
  • MediaRecorder.AudioSource.VOICE_COMMUNICATION:摄像头旁边的麦克风
  • MediaRecorder.AudioSource.VOICE_DOWNLINK:下行声音
  • MediaRecorder.AudioSource.VOICE_RECOGNITION:语音识别
  • MediaRecorder.AudioSource.VOICE_UPLINK:上行声音
    一般使用默认或者主麦克风或者摄像头旁边的麦克风。
    设置视频来源:
  • VideoSource.DEFAULT:默认
  • VideoSource.CAMERA:摄像头
  • VideoSource.SURFACE:Surface作为来源
    在录制视频的步骤中有一步是调用setCamera设置Camera,这一步相当于设置来源是摄像头,下面就需要使用VideoSource.CAMERA作为视频源,还可以使用MediaRecorder的getSurface播放视频,代替setCamera,这时的视频来源就是Surface。

4 CamcorderProfile 和 参数设置

/**CamcorderProfile类源码中的注释
Retrieves the
predefined camcorder profile settings for camcorder applications.
These settings are read-only.
The compressed output from a recording session with a given
CamcorderProfile contains two tracks: one for audio and one for video.
Each profile specifies the following set of parameters:
The file output format
Video codec format
Video bit rate in bits per second
Video frame rate in frames per second
Video frame width and height,
Audio codec format
Audio bit rate in bits per second,
Audio sample rate
Number of audio channels for recording.
public class CamcorderProfile

CamcorderProfile获取方法:利用get方法

CamcorderProfile get(int cameraId, int quality)
CamcorderProfile get(int quality)
参数说明
**cameraId:**摄像头id,分为前置摄像头或者后置摄像头。
**quality:**质量情况

CamcorderProfile保存音视频配置信息,Android系统利用CamcorderProfile设定好的配置信息,可以通过选择不同质量等级,得到不同的配置信息。
CamcorderProfile 管理的配置信息包括:
fileFormat:文件输出格式
videoCodec:视频编解码格式
videoBitRate:视频比特率,以位/秒为单位
videoFrameRate:视频帧速率(以每秒帧数为单位)
videoFrameWidth,videoFrameHeight:视频帧宽和高度,
audioCodec:音频编解码格式
audioBitRate:音频比特率,以位/秒为单位,录制的音频通道数。
audioSampleRate:音频采样率
audioChannels:音频声道数

如何利用get函数获取不同质量的上面的参数呢?

get()函数中有一个quality的参数,这个参数的取值为:
QUALITY_LOW
QUALITY_HIGH
QUALITY_QCIF
QUALITY_CIF
QUALITY_480P
QUALITY_720P
QUALITY_1080P
QUALITY_2160P
QUALITY_TIME_LAPSE_LOW
QUALITY_TIME_LAPSE_HIGH
QUALITY_TIME_LAPSE_QCIF
QUALITY_TIME_LAPSE_CIF
QUALITY_TIME_LAPSE_480P
QUALITY_TIME_LAPSE_720P
QUALITY_TIME_LAPSE_1080P
QUALITY_TIME_LAPSE_2160P
QUALITY_HIGH_SPEED_LOW
QUALITY_HIGH_SPEED_HIGH
QUALITY_HIGH_SPEED_480P
QUALITY_HIGH_SPEED_720P
QUALITY_HIGH_SPEED_1080P
QUALITY_HIGH_SPEED_2160P

从命名可以看到主要分为三类:
第一类QUALITY_LOW,QUALITY_HIGH,QUALITY_QCIF,QUALITY_CIF,QUALITY_480P,QUALITY_720P,QUALITY_1080P,QUALITY_2160P,直接获取。
其中QUALITY_LOW,QUALITY_HIGH是一定存在的,不会出现错误,但是其他特性质量不一定存在,如果设置不存在的quality则无法获取CamcorderProfile,可以利用hasProfile(int, int)函数判断是否存在。QUALITY_LOW,QUALITY_HIGH在不同的手机上的值不一样要注意区分。

第二类是时间流逝质量(比特率)水平包括QUALITY_TIME_LAPSE_LOW,QUALITY_TIME_LAPSE_HIGH,
QUALITY_TIME_LAPSE_QCIF,QUALITY_TIME_LAPSE_CIF,QUALITY_TIME_LAPSE_480P,QUALITY_TIME_LAPSE_720P,QUALITY_TIME_LAPSE_1080P,QUALITY_TIME_LAPSE_2160P
QUALITY_TIME_LAPSE_LOW,QUALITY_TIME_LAPSE_HIGH是一定存在的其他需要进行判断使用。

第三类是高速(高帧率)质量包括QUALITY_HIGH_SPEED_LOW,QUALITY_HIGH_SPEED_HIGH,QUALITY_HIGH_SPEED_480P,QUALITY_HIGH_SPEED_720P,QUALITY_HIGH_SPEED_1080P,
QUALITY_HIGH_SPEED_2160P。
QUALITY_HIGH_SPEED_LOW and QUALITY_HIGH_SPEED_HIGH确定存在其他需要判断使用。

CamcorderProfile 设置参数方式

/**setProfile 源码注释

  • Uses the settings from a CamcorderProfile object for recording. This method should
  • be called after the video AND audio sources are set, and before setOutputFile().
  • If a time lapse CamcorderProfile is used, audio related source or recording
  • parameters are ignored.
  • @param profile the CamcorderProfile to use
  • @see android.media.CamcorderProfile
    */
    public void setProfile(CamcorderProfile profile)

利用setProfile设置参数必须在设置了video和audio Source之后调用,在setOutputFile之前设置。如果时间流逝的CamcorderProfile 被使用,audio相关的源或者参数设置将被忽略。
注意:如果调用了setProfile函数,setOutputFormat,setAudioEncoder,setVideoEncoder,不能再调用设置。

CamcorderProfile mCamcorderProfile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK,
         CamcorderProfile.QUALITY_HIGH);
 mMediaRecorder.setProfile(mCamcorderProfile);

如果使用其他类型质量参数就需要进行判断:

CamcorderProfile profile = null;
        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        }
        if (profile != null) {
            mMediaRecorder.setProfile(profile);
        }

手动设置参数

通过profile可以一步到位配置各个参数,但是缺点是没办法设置自己想要的视频清晰度,因为视频清晰度是根据码率和分辨率决定的,而每个profile已经固定了码率和分辨率,所以无法进行调整。
注意:
帧率通过camera获取支持的帧率,视频尺寸的大小,可以根据需要的质量先获取系统720p的profile,然后取profile.videoFrameWidth; profile.videoFrameHeight作为输出宽高。

setOutputFormat():设置输出格式。
系统包括:
DEFAULT:MP4
THREE_GPP:3gp
MPEG_4:.mp4,m4a
RAW_AMR:.arm
AAC_ADTS :aac

视频音频编码格式:
视频编码格式MediaRecorder.VideoEncoder
DEFAULT,H263,H264,MPEG_4_SP,VP8
音频编码格式MediaRecorder.AudioEncoder
default,AAC,HE_AAC,AAC_ELD,AMR_NB,AMR_WB,VORBIS

setVideoFrameRate:设置帧率,24fps已经很流畅,帧率不能太低,也不能太高。
setVideoEncodingBitRate:设置码率,配合视频大小可以调节视频的质量
setVideoSize:设置视频大小
setAudioEncodingBitRate:设置音频采样率
setAudioChannels:设置声道数

其他设置:
setOrientationHint()对输出的文件设置方向,一般和Camera的setDisplayOrientation角度相同。
特别注意:setOrientationHint并没有对视频的帧进行旋转操作,所以真实生成的视频时没有旋转的,它会增加一个Matrix举证,当output为OutputFormat.THREE_GPP 或者 OutputFormat.MPEG_4时视频播放时,播放器能够自动识别。
setMaxDuration:录制的最长时间
setOutputFile:设置输出的文件
setPreviewDisplay:设置Surface预览视频
setMaxFileSize:设置录制文件的最大值。

5 代码示例

public class Main20Activity extends AppCompatActivity implements SurfaceHolder.Callback {

    private static int mOrientation = 0;
    private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Camera mCamera;
    private boolean havePermission = false;
    private Button mStart;
    private Button mStop;
    private MediaRecorder mMediaRecorder;
    private Camera.Size mSize;

    String srcPath = Environment.getExternalStorageDirectory()
            .getPath() + "/mediarecorder/";
    String srcName = "video.mp4";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main20);
        mStart = findViewById(R.id.startrecord);
        mStop = findViewById(R.id.stoprecord);
        mStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                initMediaRecord();
            }
        });

        mStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mMediaRecorder != null ){
                    mMediaRecorder.stop();
                    mMediaRecorder.release();
                    mCamera.lock();
                }
            }
        });
       
        // Android 6.0相机动态权限检查,省略了
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
                == PackageManager.PERMISSION_GRANTED) {
            havePermission = true;
            init();
        } else {
        havePermission = false;
            ActivityCompat.requestPermissions(this,
                    new String[]{
                            Manifest.permission.CAMERA,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.RECORD_AUDIO
                    }, 100);
        }
    }

    public void initMediaRecord(){
        //创建MediaRecorder
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.reset();
        mCamera.unlock();
        //创建录音文件
        File mRecorderFile = new File(srcPath+srcName);
        try {
            if (!mRecorderFile.getParentFile().exists()) mRecorderFile.getParentFile().mkdirs();
            mRecorderFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaRecorder.setCamera(mCamera);
        mMediaRecorder.setOrientationHint(mOrientation);
        //从麦克风采集
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        CamcorderProfile mCamcorderProfile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK, CamcorderProfile.QUALITY_HIGH);
        System.out.println("============mCamcorderProfile============"+mCamcorderProfile.videoFrameWidth+"   "+mCamcorderProfile.videoFrameHeight);
        mMediaRecorder.setProfile(mCamcorderProfile);
        //使用CamcorderProfile做配置的话,输出格式,音频编码,视频编码 不要写,否则会报错(崩溃)
        /*mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);*/

        //设置录制视频的大小,其实Camera也必须要和这个比例相同,此处为了简单不做处理
        mMediaRecorder.setVideoSize(mCamcorderProfile.videoFrameWidth,mCamcorderProfile.videoFrameHeight);
        //提高帧频率,录像模糊,花屏,绿屏可写上调试
        mMediaRecorder.setVideoEncodingBitRate(mCamcorderProfile.videoFrameWidth*mCamcorderProfile.videoFrameHeight*24*16);
        mMediaRecorder.setVideoFrameRate(24);
        //所有android系统都支持的适中采样的频率
        mMediaRecorder.setAudioSamplingRate(44100);
        //设置文件录音的位置
        mMediaRecorder.setOutputFile(mRecorderFile.getAbsolutePath());
        mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
        //开始录音
        try {
            mMediaRecorder.prepare();
            mMediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

     public void init(){
        if(mSurfaceView == null){
            mSurfaceView = findViewById(R.id.surfaceview);
            mSurfaceView.setCustomEvent(new CustomSurfaceView.ONTouchEvent() {
                @Override
                public void onTouchEvent(MotionEvent event) {
                    handleFocus(event, mCamera);
                }
            });
            mSurfaceHolder = mSurfaceView.getHolder();
            mSurfaceHolder.addCallback(this);
            WindowManager wm = (WindowManager) Main23Activity.this.getSystemService(Context.WINDOW_SERVICE);
            int width = wm.getDefaultDisplay().getWidth();
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mSurfaceView.getLayoutParams();
            layoutParams.width = width;
            layoutParams.height = width*4/3;
            useWidth = width;
            useHeight = width*4/3;
            mSurfaceView.setLayoutParams(layoutParams);
        }

    }

    private void initCamera() {
        if (mCamera != null){
            releaseCamera();
            System.out.println("===================releaseCamera=============");
        }
        mCamera = Camera.open(mCameraID);
        System.out.println("===================openCamera=============");
        if (mCamera != null){
            try {
                mCamera.setPreviewDisplay(mSurfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setRecordingHint(true);
            {
                //设置获取数据
                parameters.setPreviewFormat(ImageFormat.NV21);
                //parameters.setPreviewFormat(ImageFormat.YUV_420_888);

                //通过setPreviewCallback方法监听预览的回调:
                mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(byte[] bytes, Camera camera) {
                        //这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据


                    }
                });
            }

            if(mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }

            mCamera.setParameters(parameters);

            calculateCameraPreviewOrientation(this);
            Camera.Size tempSize = setPreviewSize(mCamera, useHeight,useWidth);
            {
                //此处可以处理,获取到tempSize,如果tempSize和设置的SurfaceView的宽高冲突,重新设置SurfaceView的宽高
            }

            setPictureSize(mCamera,  useHeight,useWidth);
            mCamera.setDisplayOrientation(mOrientation);
            int degree = calculateCameraPreviewOrientation(Main23Activity.this);
            mCamera.setDisplayOrientation(degree);
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
     
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //当SurfaceView变化时也需要做相应操作,这里未做相应操作
           if (havePermission){
            initCamera()}
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
    }

    private void setPictureSize(Camera camera ,int expectWidth,int expectHeight){
        Camera.Parameters parameters = camera.getParameters();
        Point point = new Point(expectWidth, expectHeight);
        Camera.Size size = findProperSize(point,parameters.getSupportedPreviewSizes());
        parameters.setPictureSize(size.width, size.height);
        camera.setParameters(parameters);
    }

    private Camera.Size setPreviewSize(Camera camera, int expectWidth, int expectHeight) {
        Camera.Parameters parameters = camera.getParameters();
        Point point = new Point(expectWidth, expectHeight);
        Camera.Size size = findProperSize(point,parameters.getSupportedPictureSizes());
        parameters.setPictureSize(size.width, size.height);
        camera.setParameters(parameters);
        return size;
    }

    /**
     * 找出最合适的尺寸,规则如下:
     * 1.将尺寸按比例分组,找出比例最接近屏幕比例的尺寸组
     * 2.在比例最接近的尺寸组中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
     * 3.如果没有找到,则忽略2中第二个条件再找一遍,应该是最合适的尺寸了
     */
    private static Camera.Size findProperSize(Point surfaceSize, List<Camera.Size> sizeList) {
        if (surfaceSize.x <= 0 || surfaceSize.y <= 0 || sizeList == null) {
            return null;
        }

        int surfaceWidth = surfaceSize.x;
        int surfaceHeight = surfaceSize.y;

        List<List<Camera.Size>> ratioListList = new ArrayList<>();
        for (Camera.Size size : sizeList) {
            addRatioList(ratioListList, size);
        }

        final float surfaceRatio = (float) surfaceWidth / surfaceHeight;
        List<Camera.Size> bestRatioList = null;
        float ratioDiff = Float.MAX_VALUE;
        for (List<Camera.Size> ratioList : ratioListList) {
            float ratio = (float) ratioList.get(0).width / ratioList.get(0).height;
            float newRatioDiff = Math.abs(ratio - surfaceRatio);
            if (newRatioDiff < ratioDiff) {
                bestRatioList = ratioList;
                ratioDiff = newRatioDiff;
            }
        }

        Camera.Size bestSize = null;
        int diff = Integer.MAX_VALUE;
        assert bestRatioList != null;
        for (Camera.Size size : bestRatioList) {
            int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
            if (size.height >= surfaceHeight && newDiff < diff) {
                bestSize = size;
                diff = newDiff;
            }
        }

        if (bestSize != null) {
            return bestSize;
        }

        diff = Integer.MAX_VALUE;
        for (Camera.Size size : bestRatioList) {
            int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
            if (newDiff < diff) {
                bestSize = size;
                diff = newDiff;
            }
        }

        return bestSize;
    }

    private static void addRatioList(List<List<Camera.Size>> ratioListList, Camera.Size size) {
        float ratio = (float) size.width / size.height;
        for (List<Camera.Size> ratioList : ratioListList) {
            float mine = (float) ratioList.get(0).width / ratioList.get(0).height;
            if (ratio == mine) {
                ratioList.add(size);
                return;
            }
        }

        List<Camera.Size> ratioList = new ArrayList<>();
        ratioList.add(size);
        ratioListList.add(ratioList);
    }
    /**
     * 排序
     * @param list
     */
    private static void sortList(List<Camera.Size> list) {
        Collections.sort(list, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size pre, Camera.Size after) {
                if (pre.width > after.width) {
                    return 1;
                } else if (pre.width < after.width) {
                    return -1;
                }
                return 0;
            }
        });
    }

    /**
     * 设置预览角度,setDisplayOrientation本身只能改变预览的角度
     * previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的
     * 拍摄的照片需要自行处理
     * 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。
     * @param activity
     */
    public static int calculateCameraPreviewOrientation(Activity activity) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraID, 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;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        mOrientation = result;
        System.out.println("=========orienttaion============="+result);
        return result;
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (havePermission && mCamera != null)
            mCamera.startPreview();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (havePermission && mCamera != null)
            mCamera.stopPreview();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            // 相机权限
            case 100:
                havePermission = true;
                init();
                break;
        }
    }

}

录制视频:
在这里插入图片描述
播放录制视频:
在这里插入图片描述
可以看到播放录制的视频时发生了旋转,如何让播放器知道制品旋转了,然后按正常的方向播放呢?根据前面讲解setOrientationHint的知识,只需要改变输出格式为OutputFormat.THREE_GPP 或者 OutputFormat.MPEG_4中的一种,DEFAULT就是默认的mp4格式也满足这个条件,所以修改代码。

 mMediaRecorder.setCamera(mCamera);
 mMediaRecorder.setOrientationHint(mOrientation);
 //从麦克风采集
 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
 CamcorderProfile mCamcorderProfile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK, CamcorderProfile.QUALITY_HIGH);
 System.out.println("============mCamcorderProfile============"+mCamcorderProfile.videoFrameWidth+"   "+mCamcorderProfile.videoFrameHeight);
/* mMediaRecorder.setProfile(mCamcorderProfile);*/
 //使用CamcorderProfile做配置的话,输出格式,音频编码,视频编码 不要写,否则会报错(崩溃)
 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

 //设置录制视频的大小,其实Camera也必须要和这个比例相同,此处为了简单不做处理
 mMediaRecorder.setVideoSize(mCamcorderProfile.videoFrameWidth,mCamcorderProfile.videoFrameHeight);
 //提高帧频率,录像模糊,花屏,绿屏可写上调试
 mMediaRecorder.setVideoEncodingBitRate(mCamcorderProfile.videoFrameWidth*mCamcorderProfile.videoFrameHeight*24*16);
 mMediaRecorder.setVideoFrameRate(24);

在这里插入图片描述
以上代码只是为了展示效果,想要使用需要优化,谢谢。

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐