Android 拍摄(横 \ 竖屏)视频的懒人之路

hello,你们吼,我是那个爱猫的老司机,爱好是掀桌子的话唠程序猿。回想起刚开始码文章的时候,没想到内向的本身也能够撸出那么多文字,真是挖坑不止,且行且珍惜啊。有猜到今天聊的主角是谁吗?猜到是否是要送红包呢?

 
请捂着你的良心说话,对于贫穷的做者(我)不是应该打赏么 ̄へ ̄!,接下来工做又要忙起来了,更新应该是放缓了呢╮(╯_╰)╭,好伤心。
javascript


例牌飘过: github.com/CarGuo 请(bu yao)无视。

 想想,咱们聊过AudioReordAudioTrackMediaPlayer,那多媒体四大金刚,就剩下了MediaRecorder了(SoundPool?我这里信号很差···)。其实MediaRecorder我的用的也很少,好久前用它在拍摄视频上确实趟过无视次坑,那今天就聊它吧,把它聊到躺下(ノQ益Q)ノ彡┻━┻。java

MediaRecorder

 通常用在多媒体录制上面,固然若是你只是简单的想录制音频,用它最合适不过,不过若是你想更多样化的录制这里推荐《Android MP3录制,波形显示,音频权限兼容与播放》。今天的主题是录制视频,用的仍是老式通用的Camera,不是新的camera2(这就尴尬了.....((/- -)/),反正我的秉承能用是王道的作法(懒)。以前也尝试过FFMPEG的录制合成音频,大小和效果也不错,只是有时候的兼容性确实有些问题,最主要仍是资料很少,很差改啊 ̄へ ̄(懒)。git

 既然是录制视频,那么少不了Camera,这货也是让人又爱又恨(哪里有爱了┑( ̄Д  ̄)┍?),也许是由于Android碎片化的缘由,因此用起来也是坑坑洼洼的,接下来就让咱们结束废话吧:github

  • 一、SurfaceView用于承载画面。
  • 二、初始化相机Camera
  • 三、初始化重力旋转用于横竖屏。
  • 四、配置闪光灯和旋转摄像头功能。
  • 五、配置MediaRecorder的录制参数后开始录制。
  • 六、结束录制预览视频。

一、SurfaceView显示画面

 
 旧项目用的都是SurfaceView,此次就就它吧。这里咱们须要首先是implements SurfaceHolder.Callback,这样咱们才能在surface建立的时候初始化相机渲染画面,在画面销毁的时候销毁相机(画面都没有要初始化相机何用)。api

SurfaceHolder holder = cameraShowView.getHolder();
holder.addCallback(this);
// setType必须设置,要不出错.
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

···此处略过无数只草泥马

@Override
public void surfaceCreated(SurfaceHolder holder) {
    surfaceHolder = holder;
    //更具当前的相机类型(前,后)初始化相机,闪光灯不启动
    initCamera(cameraType, false);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    surfaceHolder = holder;
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    //结束录制
    endRecord();
    //是否相机
    releaseCamera();
}复制代码

 

二、初始化Camera

 
 除了有点坑外,流程上仍是比较简单的:ide

  • 释放已经初始化过的相机。
  • 根据当前摄像头类型打开相机。
  • 配置相机参数:预览大小,对焦,闪光灯,竖屏显示。
  • 设置显示画面的surface
  • 开始绘制
if (camera != null) {
    //若是已经初始化过,就先释放
    releaseCamera();
}

try {
    //根据先后摄像头打开摄像头
    camera = Camera.open(type);
    if (camera == null) {
        //拿不到多是没权限
        showCameraPermission();
        return;
    }
    camera.lock();

    //Point screen = new Point(getScreenWidth(this), getScreenHeight(this));
    //如今不用获取最高的显示效果
    //Point show = getBestCameraShow(camera.getParameters(), screen);

    Camera.Parameters parameters = camera.getParameters();
    if (type == 0) {
        //基本是都支持这个比例
        parameters.setPreviewSize(SIZE_1, SIZE_2);
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//1连续对焦
        camera.cancelAutoFocus();// 2若是要实现连续的自动对焦,这一句必须加上
    }
    camera.setParameters(parameters);
    FlashLogic(camera.getParameters(), flashType, flashDo);
    if (cameraType == 1) {
        frontCameraRotate();
        camera.setDisplayOrientation(frontRotate);
    } else {
        camera.setDisplayOrientation(90);
    }
    camera.setPreviewDisplay(surfaceHolder);
    camera.startPreview();
    camera.unlock();
} catch (Exception e) {
    e.printStackTrace();
    releaseCamera();
}复制代码

 这里须要注意坑(画面变形)问题,那就是你配置的相机分辨率画面,在录制的时候可能会由于和录制的分辨率画面不一致,致使开始录制的时候画面奇怪的突变,因此CameraMediaRecorder的分辨率最好一致。性能

 问题又来了CameraMediaRecorder不是什么分辨率都支持的,他们分别都有对应的接口:getSupportedPreviewSizesCamcorderProfile等来获取对应支持的分辨率的,路迢迢啊。
 
 通过轮番的尝试,还有上传对大小要求,因此最终选择写死,对,写死了640 * 480这样的大小,这个分辨率基本都支持(不支持那手机的尊严何在( ‵o′)凸),对于十来秒的视频,这个分辨率的尺寸还算能够(若是对画质有须要能够另外配置,若是FFMPEG压缩性能堪忧啊)。测试

 那么问题又来了(哪来那么多问题),可是手机屏幕大部分状况下是16:9,而这个分辨率明显是4:3(万恶的需求啊(ノQ益Q)ノ彡┻━┻)。这时候由于Surface的最外层是FrameLayout(搞不懂为何超出屏幕的时候RelativeLayout有时候会有问题),我的的作法是调整surface的比例。若是是不充满屏幕高度的,就经过屏幕宽度比例算出surface的高度;若是充满屏幕高度,就算出surface的宽度。动画

 如此以来,不变形啦,在点击录制的瞬间也不跳动啦,惟一有点小问题的就是充满高度的时候,画面是超过了屏幕宽度的一点的,因此可能录到了什么不想录制的♂,可是恰好没看到︿( ̄︶ ̄)︿。this

int screenWidth = getScreenWidth(this);
int screenHeight = getScreenHeight(this);
//根据比例设置surface的宽度
setViewSize(cameraShowView, screenWidth * SIZE_1 / SIZE_2, screenHeight);复制代码

三、重力感应旋转

 
 当时看到IOS微博的视频录制是能够支持横竖屏录制,以为挺有意思的,这里用的是OrientationEventListener,具体的以前IJKPlayer视频文章里已经说过(懒),有兴趣的能够去看看。咱们是在画面旋转的时候把对应的logo用属性动画也旋转了,而后获得当前的旋转角度,告诉MediaRecorder,拍摄出来的视频元信息里就带有了角度信息,播放的时候画面会就旋转为横屏或者竖屏啦。

orientationEventListener = new OrientationEventListener(this) {
    @Override
    public void onOrientationChanged(int rotation) {
        if (!flagRecord) {
            if (((rotation >= 0) && (rotation <= 30)) || (rotation >= 330)) {
                // 竖屏拍摄
                if (rotationFlag != 0) {
                    //旋转logo
                    rotationAnimation(rotationFlag, 0);
                    //这是竖屏视频须要的角度
                    rotationRecord = 90;
                    //这是记录当前角度的flag
                    rotationFlag = 0;
                }
            } else if (((rotation >= 230) && (rotation <= 310))) {
                // 横屏拍摄
                if (rotationFlag != 90) {
                    //旋转logo
                    rotationAnimation(rotationFlag, 90); 
                    //这是正横屏视频须要的角度
                    rotationRecord = 0;
                    //这是记录当前角度的flag
                    rotationFlag = 90;
                }
            } else if (rotation > 30 && rotation < 95) {
                // 反横屏拍摄
                if (rotationFlag != 270) {
                    //旋转logo
                    rotationAnimation(rotationFlag, 270);
                    //这是反横屏视频须要的角度
                    rotationRecord = 180;
                    //这是记录当前角度的flag
                    rotationFlag = 270;
                }
            }
            //倒过来就算了,你又不是小米MIX
        }
    }
};
orientationEventListener.enable();复制代码

前置摄像头

 此处有,还不止一个,若是你还须要支持前置摄像头(能说不吗?),直接使用上面的rotationRecord去配置MediaRecorder是会有问题的。

 首先说Camera,若是测试说你的前置Camera在某些手机上画面角度不对,这时候你能够偷偷把手机砸了,由于这是兼容问题。若是你没有勇气砸手机,看下面。

 传说中,只要拿下面的frontRotate去配置Camera就正常显示啦,伟人说的!而其中的frontOri,咱们是用到配置后面MediaRecorder,具体看代码的,这是调出来的结果(。・・)ノ。

/** * 旋转前置摄像头为正的 */
private void frontCameraRotate() {
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(1, info);
    int degrees = getDisplayRotation(this);
    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;
    }
    frontOri = info.orientation;
    frontRotate = result;
}

/** * 获取旋转角度 */
private int getDisplayRotation(Activity activity) {
    int rotation = activity.getWindowManager().getDefaultDisplay()
            .getRotation();
    switch (rotation) {
        case Surface.ROTATION_0:
            return 0;
        case Surface.ROTATION_90:
            return 90;
        case Surface.ROTATION_180:
            return 180;
        case Surface.ROTATION_270:
            return 270;
    }
    return 0;
}

···此处无数字草泥马
//配置录制角度
int frontRotation;
if (rotationRecord == 180) {
    //反向横屏的前置角度
    frontRotation = 180;
} else {
    //竖屏和正向横屏的前置角度
    //录制下来的视屏选择角度,此处为前置
    frontRotation = (rotationRecord == 0) ? 270 - frontOri : frontOri; 
}
//根据先后摄像头给角度
recorder.setOrientationHint((cameraType == 1) ? frontRotation : rotationRecord);复制代码

四、闪光灯和旋转摄像头

 闪光灯的打开关闭遇到过一个问题,就是有的手机尚未开启录制,一配置打开它就亮了。(砸手机)最后解决的是在配置的时候标志类型,设置好MediaRecorder以后拍摄才开始闪光灯。(其余的什么一闪一闪的模式就算了吧= =)

 至于旋转切换相机,主要仍是针对前置camera须要作如上面所说的画面预览旋转。

/** * 闪光灯逻辑 * * @param p 相机参数 * @param type 打开仍是关闭 * @param isOn 是否当即启动 */
private void FlashLogic(Camera.Parameters p, int type, boolean isOn) {
    flashType = type;
    if (type == 0) {
        if (isOn) {
            p.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            camera.setParameters(p);
        }
        videoFlashLight.setImageResource(R.drawable.flash_off);
    } else {
        if (isOn) {
            p.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            camera.setParameters(p);
        }
        videoFlashLight.setImageResource(R.drawable.flash);
    }
    if (cameraFlag == 0) {
        videoFlashLight.setVisibility(View.GONE);
    } else {
        videoFlashLight.setVisibility(View.VISIBLE);
    }
}

/** * 切换摄像头 */
public void switchCamera() {
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    int cameraCount = Camera.getNumberOfCameras();//获得摄像头的个数0或者1;

    try {
        for (int i = 0; i < cameraCount; i++) {
            Camera.getCameraInfo(i, cameraInfo);//获得每个摄像头的信息
            if (cameraFlag == 1) {
                //后置到前置
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//表明摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置
                    frontCameraRotate();//前置旋转摄像头度数
                    switchCameraLogic(i, 0, frontRotate);
                    break;
                }
            } else {
                //前置到后置
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {//表明摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置
                    switchCameraLogic(i, 1, 90);
                    break;
                }
            }
        }
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}复制代码

 

五、配置MediaRecorder的录制参数、生成视频。

 这里最坑的就是MediaRecorder的配置参数是有先后关系的,先生小孩后再洞房这种绿色模式是不行的,具体顺序参照下方代码,码率和帧数都是配置相对较小,适合拍摄上传。此处还须要注意,若是应用没有获取到录音权限,在录制的时候是会走catch里面的。

 中止录制相对就简单了,只要顺序正常便可,以后就能够把视频传到VideoView快速实现预览啦。做为谷歌亲儿子,VideoView自带对setOrientationHint的角度解析,只要根据视频大小配置好界面显示的效果便可。比起 以前本人撸的播放器 ,儿子仍是本身的亲┑( ̄Д  ̄)┍,若是需求不高用起来仍是能够闭着眼睛的用的。(以前还有小伙伴本身用MediaPlayer播放呢)

//开始
private boolean startRecord() {

    //懒人模式,根据闪光灯和摄像头先后从新初始化一遍,开期闪光灯工做模式
    initCamera(cameraType, true);

    if (recorder == null) {
        recorder = new MediaRecorder();
    }
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
            || camera == null || recorder == null) {
        camera = null;
        recorder = null;
        //仍是没权限啊
        showCameraPermission();
        return false;
    }

    try {

        recorder.setCamera(camera);
        // 这两项须要放在setOutputFormat以前
        recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        // Set output file format,输出格式
        recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

        //必须在setEncoder以前
        recorder.setVideoFrameRate(15);  //帧数 一分钟帧,15帧就够了
        recorder.setVideoSize(SIZE_1, SIZE_2);//这个大小就够了

        // 这两项须要放在setOutputFormat以后
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

        recorder.setVideoEncodingBitRate(3 * SIZE_1 * SIZE_2);//第一个数字越大,清晰度就越高,考虑文件大小的缘故,就调整为1
        int frontRotation;
        if (rotationRecord == 180) {
            //反向的前置
            frontRotation = 180;
        } else {
            //正向的前置
            frontRotation = (rotationRecord == 0) ? 270 - frontOri : frontOri; //录制下来的视屏选择角度,此处为前置
        }
        recorder.setOrientationHint((cameraType == 1) ? frontRotation : rotationRecord);
        //把摄像头的画面给它
        recorder.setPreviewDisplay(surfaceHolder.getSurface());
        //建立好视频文件用来保存
        videoDir();
        if (videoFile != null) {
            //设置建立好的输入路径
            recorder.setOutputFile(videoFile.getPath());
            recorder.prepare();
            recorder.start();
            //不能旋转啦
            orientationEventListener.disable();
            flagRecord = true;
        }
    } catch (Exception e) {
        //通常没有录制权限或者录制参数出现问题都走这里
        e.printStackTrace();
        //仍是没权限啊
        recorder.reset();
        recorder.release();
        recorder = null;
        showCameraPermission();
        FileUtils.deleteFile(videoFile.getPath());
        return false;
    }
    return true;

}

//结束录制
private void endRecord() {
    //反正屡次进入,好比surface的destroy和界面onPause
    if (!flagRecord) {
        return;
    }
    flagRecord = false;
    try {
        if (recorder != null) {
            recorder.stop();
            recorder.reset();
            recorder.release();
            orientationEventListener.enable();
            recorder = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    videoTime.stop();
    videoTime.setBase(SystemClock.elapsedRealtime());
    Intent intent = new Intent(this, PlayActivity.class);
    intent.putExtra(PlayActivity.DATA, videoFile.getAbsolutePath());
    startActivityForResult(intent, 2222);
    overridePendingTransition(R.anim.fab_in, R.anim.fab_out);
}复制代码
最后

 
 总的来讲,录制视频仍是蛮简单的,主要仍是视频的角度问题须要考虑:

  • Camera的前置摄像头角度注意。
  • Android自己默认的是横屏录制效果,因此须要配置横屏和竖屏的录制角度。
  • MediaRecorder参数的配置顺序。
  • CameraMediaRecorder的分辨率和拉伸问题。
  • 闪光灯要在开始录制的时候才开启。
  • 初始化摄像头和释放摄像头须要在surface的surfaceCreatedsurfaceDestroyed
  • 注意锁屏、退到后台、onPuase的是会走surface的surfaceDestroyed
  • 若是是要一次性上传很长很长的拍摄视频,推荐仍是找FFMPEG的录制方式吧,毕经录制好了再压缩的作法很费时。 
  • 告诉IOS,让他支持视频元信息的角度旋转播放。(不支持?网上那么多视频有角度信息,难道歪着看?)
  • 测试若是说前置画面拍摄出来的视频左右翻转,用本机拍一个前置视频或者照片给他看,否则你只能接FFMPEG了。  

<( ̄︶ ̄)↗知道你想说什么,DMEO在这里 : github.com/CarGuo/Vide…

有人支持么,好累啊
相关文章
相关标签/搜索