Android 使用FFmpeg 裁剪出正方形视频

到目前为止Android中还不能直接录制正方形的视频,虽然不能直接录但是我们也有一些方式来处理录制后的视频,之前我写过一篇文章 Android自定义Camera(一), 可以先了解一下如何做一个简单的自定义相机demo, 那录制视频也要开启相机预览, 有以下几个步骤和需要注意的地方:

1.获取相机实例

/**
     * 获取Camera实例
     *
     * @return
     */
private Camera getCamera(int id) {
    Camera camera = null;
    try {
        camera = Camera.open(id);
    } catch(Exception e) {

}
    return camera;
}```

#2.开启预览 
要注意,开启预览要在Activity的onResume方法里面开启,然后在onPause方法里面释放相机资源,举一个简单的例子,如果你在预览的时候按下了home键,此时再次打开程序,如果你是在Oncreate方法里面开启相机,那么再次打开预览界面应该会卡住。

/**
* 预览相机
*/
private void startPreview(Camera camera, SurfaceHolder holder) {
try {
setupCamera(camera);
camera.setPreviewDisplay(holder);
//获取相机预览角度, 后面录制视频需要用
recorderRotation = CameraUtil.getInstance().getRecorderRotation(mCameraId);
CameraUtil.getInstance().setCameraDisplayOrientation(this, mCameraId, camera);
camera.startPreview();
} catch(IOException e) {
e.printStackTrace();
}
}

/**
* 设置
*/
private void setupCamera(Camera camera) {
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();

    List < String > focusModes = parameters.getSupportedFocusModes();
    if (focusModes != null && focusModes.size() > 0) {
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            //设置自动对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
    }

    List < Camera.Size > videoSiezes = null;
    if (parameters != null) {
        //获取相机所有支持尺寸
        videoSiezes = parameters.getSupportedVideoSizes();
        for (Camera.Size size: videoSiezes) {}
    }

    if (videoSiezes != null && videoSiezes.size() > 0) {
        //拿到一个预览宽度最小为720像素的预览值 
        Camera.Size videoSize = CameraUtil.getInstance().getPropVideoSize(videoSiezes, 720);
        video_width = videoSize.width;
        video_height = videoSize.height;
        LogUtils.i("video_width===" + video_width);
        LogUtils.i("video_height===" + video_height);
    }

    //这里第三个参数为最小尺寸 getPropPreviewSize方法会对从最小尺寸开始升序排列 取出所有支持尺寸的最小尺寸
    Camera.Size previewSize = CameraUtil.getInstance().getPropPreviewSize(parameters.getSupportedPreviewSizes(), video_width);
    parameters.setPreviewSize(previewSize.width, previewSize.height);

    Camera.Size pictrueSize = CameraUtil.getInstance().getPropPictureSize(parameters.getSupportedPictureSizes(), video_width);
    parameters.setPictureSize(pictrueSize.width, pictrueSize.height);

    camera.setParameters(parameters);

    /**
         * 设置surfaceView的尺寸 因为camera默认是横屏,所以取得支持尺寸也都是横屏的尺寸
         * 我们在startPreview方法里面把它矫正了过来,但是这里我们设置设置surfaceView的尺寸的时候要注意 previewSize.height<previewSize.width
         * previewSize.width才是surfaceView的高度
         * 一般相机都是屏幕的宽度 这里设置为屏幕宽度 高度自适应 你也可以设置自己想要的大小
         */
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(screenWidth, (screenWidth * video_width) / video_height);
    //这里当然可以设置拍照位置 比如居中 我这里就置顶了
    surfaceView.setLayoutParams(params);

    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(screenWidth, screenheight - screenWidth);
    layoutParams.addRule(RelativeLayout.BELOW, surfaceView.getId());
    layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
    bottomLayout.setLayoutParams(layoutParams);
}

}```

3.开始录制

这里要注意的是MediaRecorder的相关方法的调用顺序时候不能乱的 * *,详细可以看官网api说明

接下来开启录制:

protected void start() {
    try {
        pathName = System.currentTimeMillis() + "";
        //视频存储路径
        file = new File(MyApplication.getInstance().getTempPath() + File.separator + pathName + AppConfig.MP4);

        //如果没有要创建
        BitmapUtils.makeDir(file);

        //初始化一个MediaRecorder
        if (mediaRecorder == null) {
            mediaRecorder = new MediaRecorder();
        } else {
            mediaRecorder.reset();
        }

        mCamera.unlock();
        mediaRecorder.setCamera(mCamera);
        //设置视频输出的方向 很多设备在播放的时候需要设个参数 这算是一个文件属性
        mediaRecorder.setOrientationHint(recorderRotation);

        //视频源类型
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setAudioChannels(2);
        // 设置视频图像的录入源
        // 设置录入媒体的输出格式
        //            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        // 设置音频的编码格式
        //            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        // 设置视频的编码格式
        //            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
        }
        /*else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
                profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
            } */
        else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW);
        }

        if (profile != null) {
            profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
            profile.audioChannels = 1;
            profile.audioSampleRate = 16000;

            profile.videoCodec = MediaRecorder.VideoEncoder.H264;
            mediaRecorder.setProfile(profile);
        }

        //视频尺寸
        mediaRecorder.setVideoSize(video_width, video_height);

        //数值越大 视频质量越高
        mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024);

        // 设置视频的采样率,每秒帧数
        //            mediaRecorder.setVideoFrameRate(5);
        // 设置录制视频文件的输出路径
        mediaRecorder.setOutputFile(file.getAbsolutePath());
        mediaRecorder.setMaxDuration(2000);

        // 设置捕获视频图像的预览界面
        mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface());

        mediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {

            @Override public void onError(MediaRecorder mr, int what, int extra) {
                // 发生错误,停止录制
                if (mediaRecorder != null) {
                    mediaRecorder.stop();
                    mediaRecorder.release();
                    mediaRecorder = null;
                    LogUtils.i("Error");
                }
            }
        });

        mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {@Override public void onInfo(MediaRecorder mr, int what, int extra) {
                //录制完成
            }
        });

        // 准备、开始
        mediaRecorder.prepare();
        mediaRecorder.start();

        new Thread(new Runnable() {@Override public void run() {
                for (int i = 0; i < PROGRESS_MAX; i++) {
                    try {
                        Thread.currentThread().sleep(20);

                        Message message = new Message();
                        message.what = 1;
                        message.obj = i;
                        handler.sendMessage(message);
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    } catch(Exception e) {
        e.printStackTrace();
    }
}

4.录制成功后接下来的重点来了

使用ffmpeg对视频进行裁剪正方形,ffmpeg使用Shell命令的方式进行视频操作,执行效率也是非常好,那首先你要集成FFmpeg到Android项目里面,这里我下载好了一个library, 直接引入到项目即可,感兴趣的伙伴可以自己编译一个库,这里我把命令贴出来解释一下,

ffmpeg - threads 4 - y - i / storage / emulated / 0 / CustomCamera / temp / 1476598263062.mp4 - metadata: s: v rotate = "0" - vf transpose = 1, crop = width: height: x: y - preset ultrafast - tune zerolatency - r 25 - vcodec libx264 - acodec copy / storage / emulated / 0 / CustomCamera / VIDEO / 1476598263062.mp4

-y: 如果文件存在那么覆盖掉

-i:输入

metadata: s: v rotate = "0": 重新编码除去rotate, 因为默认录制出来的是横屏视频,这里重新编码

transpose = 1:0 = 90CounterCLockwise and Vertical Flip(
default)逆时针旋转90度并且垂直翻转,下面类推1 = 90Clockwise 2 = 90CounterClockwise 3 = 90Clockwise and Vertical Flip

crop = width: height: x: y,其中width和height表示裁剪后的尺寸,x: y表示裁剪区域的左上角坐标

-preset ultrafast - tune zerolatency: 加快效率

r 25:帧率

-vcodec libx264 - acodec:编码方式libx264如果想知道更多关于ffmpeg的东西可以访问官网https: //ffmpeg.org/
再贴一下我的代码调用:

 try {
     fc.compress_clipVideo(file.getAbsolutePath(), file2.getAbsolutePath(), mCameraId, video_width, video_height, 0, 0, new ShellUtils.ShellCallback() {

         @Override public void shellOut(String shellLine) {}

         @Override public void processComplete(int exitValue) {
             dialog.dismiss();
             if (exitValue != 0) {
                 ToastFactory.showLongToast(context, getResources().getString(R.string.state_compress_error));
                 mHandler.sendEmptyMessage(R.string.state_compress_error);
             } else {
                 mHandler.sendEmptyMessage(R.string.state_compress_end);
             }
         }
     });

 } catch(Exception e) {
     e.printStackTrace();
 }

FFmpeg编译出来的Android库还是很大的,大约15M左右,无疑增加了apk的大小,如果你的产品方向是视频图片gif等等的格式转换类似的功能可以考虑使用 * *

如果你对这样的功能感兴趣可以详细查看源码:

github源码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容