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…

有人支持麼,好累啊
相關文章
相關標籤/搜索