Android 音視訊開發【視訊篇】【二】視訊採集 | 8月更文挑戰

語言: CN / TW / HK

這是我參與8月更文挑戰的第2天,活動詳情檢視:8月更文挑戰

上一章介紹了RGBYUV格式,本章將介紹如何採集YUV格式的資料

一、視訊相關概念

1.1 解析度

用以橫向縱向的畫素點個數來衡量一副影象的大小,比如1080*960,其中1080表示橫向的畫素點個數為1080個,960表示縱向的畫素點個數為960

1.2 幀率

1s內有多少個副影象,比如30fps,表示1s內有30副影象,即30幀,而60fps,表示1s內有60幀,更高的還有90fps120fps,幀率越高,相對來說,畫面就會更流暢

根據人眼的視覺暫留特性,24fps時,人眼就會認為是流暢的,而更高的幀率,會給人一種身臨其境的感覺

當然,也不是幀率越高越好,需要考慮顯示器的重新整理率,幀率過高可能反而浪費不必要的資源

1.3 位元速率/位元率

位元速率,也即位元率,單位是bps,表示視訊的位元數,計算公式是:

$$ 位元速率 = 檔案大小(kb) / 時長(s) $$

二、視訊採集

採集視訊,在手機中可以通過攝像頭進行採集,那麼,在Android也提供了很多對攝像頭進行操作的類

  • Camera

    操作相對簡單,提供對攝像頭的很多的操作方法,比如開啟、關閉攝像頭、變焦、拍照等,不過谷歌已經不建議使用,而是建議使用CameraX,或者是Camera2

  • Camera2

    Camera2相對於Camera增加了很多特性,比如對Api的架構進行了很大的優化,比Camera更先進,還可以獲取每幀的資訊,等等,還有很多特性,不過,Camera2相對於Camera,使用上變得更加複雜

  • CameraX

    JetPack家族的一員,CameraX是在Camea2之後新出的一個相機框架,可能是谷歌覺得雖然Camera2功能強大,但是使用起來確實麻煩了很多,所有為了在保證功能的前提的下,操作變得更簡潔,所有推出了CameraX

雖然谷歌建議我們使用CameraX或者是Camera2,但是視訊系列的相關文章,還是以Camera,不為別的,就是為了簡單,哈哈

2.1 Camera

關於相機的使用,可以放入子執行緒去使用

首先,第一步,當然是開啟相機

開啟相機

java camera = Camera.open(id);

開啟相機,使用的是Camera類的靜態方法open(),該方法需要傳入一個int型別的id,有兩個值可選

  • Camera.CameraInfo.CAMERA_FACING_BACK

    後攝

  • Camera.CameraInfo.CAMERA_FACING_FRONT

    前攝

在獲取camera例項後,此時攝像頭並沒有正在的開啟

接下來需要對camera做一些引數配置,比如預覽尺寸

獲取相機引數

java Camera.Parameters parameters = camera.getParameters();

camera進行引數設定,先得呼叫該方法,然後下面就會對parameters進行一些引數設定

獲取全部預覽尺寸

java List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();

通過呼叫parameters.getSupportedPreviewSizes()獲取當前相機所支援的預覽尺寸,因為我們傳入相機的預覽尺寸必須得是相機所支援的

一般來說,我們通常希望相機的尺寸能夠和螢幕一樣大,或者是和螢幕同等比例,這樣,顯示出來的效果也就比較好,所有下面通過一個演算法,找到最佳的尺寸

java /** * 尋找最合適的尺寸 */ public static Camera.Size findTheBestSize(List<Camera.Size> sizeList, int screenW, int screenH) {    if (sizeList == null || sizeList.isEmpty()) {        throw new IllegalArgumentException();   }    Camera.Size bestSize = sizeList.get(0);    for (Camera.Size size : sizeList) {        int width = size.height;        int height = size.width;        float ratioW = (float) width / screenW;        float ratioH = (float) height / screenH;        if (ratioW == ratioH) {            bestSize = size;            break;       }   }    return bestSize; }

一個引數,直接傳入相機所支援的預覽尺寸,後面兩個引數是螢幕的尺寸

該演算法的目的就是遍歷每一個預覽尺寸,但預覽尺寸的比例和螢幕的尺寸比例一致時,就可以返回,而如果當遍歷完,還是沒找到合適的尺寸,就返回第一個相機所支援的尺寸

通過此方法,就獲取到了一個最佳的尺寸

設定預覽尺寸

java parameters.setPreviewSize(width, height);

本章的目的是取樣視訊資料,所以我們得設定相機預覽資料的回撥格式

設定預覽格式

java parameters.setPreviewFormat(ImageFormat.NV21);

相機設定引數

java camera.setParameters(parameters);

在對parameters做了一系列的設定後,我們就可以將其設定給camera

設定旋轉角度

java camera.setDisplayOrientation(CameraUtils.getDisplayOrientation(activity, id));

設定旋轉角度,使用的是CameraUtils.getDisplayOrientation(activity, id)方法

java /** * 獲取攝像頭旋轉角度 */ public static int getDisplayOrientation(Activity activity, int facing) {    Camera.CameraInfo info = new Camera.CameraInfo();    Camera.getCameraInfo(facing, 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;   }    return result; }

設定資料快取和回撥

之前設定了預覽資料的回撥格式為NV21,它是一種YUV420的格式

camera提供了addCallbackBuffersetPreviewCallbackWithBuffer的方法,用於接收預覽資料和設定回撥,它能夠減少物件的建立

java camera.addCallbackBuffer(nv21Data); camera.setPreviewCallbackWithBuffer(this);

設定回撥後,我們需要重寫onPreviewFrame方法

java @Override public void onPreviewFrame(byte[] data, Camera camera) {    Log.d(TAG, "onPreviewFrame: " + nv21Data.length);    camera.addCallbackBuffer(nv21Data);    if (fos == null) {        return;   }    try {        fos.write(nv21Data);   } catch (IOException e) {        e.printStackTrace();   } }

在該方法中,每次回撥預覽資料,都會先呼叫addCallbackBuffer方法,該方法會複用對應的物件

接著後面將資料寫入到檔案中

設定SurfaceTexture,開啟預覽

當然,上面還沒結束,還需要設定SurfaceTexture才能正常開啟預覽,也就才能正常獲取預覽回撥資料

java try {    camera.setPreviewTexture(new SurfaceTexture(0)); } catch (IOException e) {    e.printStackTrace(); } camera.startPreview();

這裡設定的是一個new出來的surfaceTexture,產生的效果是無預覽取樣視訊

你也可以使用SurfaceView或者TextureView顯示預覽畫面

停止預覽,釋放資源

當我們停止錄製時,需要停止預覽並釋放資源

java private void release() {    if (camera != null) {        camera.stopPreview();        camera.release();        camera = null;   }    if (fos != null) {        try {            fos.close();            fos = null;       } catch (IOException e) {            e.printStackTrace();       }   } }

三、GitHub

YuvRecord

YuvActivity