通過camera CameraCallbacks預覽回調數據保存視頻

前言:因爲camera 1 使用MediaRecorder錄製視頻時,相機的預覽回調函數(onPreviewFrame(byte[] data, Camera camera)

)就不會執行。使用camera 2 我也遇到同樣的問題,雖然有的文章說camera 2沒有這樣的問題,但是本人沒有做到。但是項目需要處理相機預覽回調數據,同時錄製視頻。所以爲了讓相機預覽回調和錄製視頻同時進行,只能通過將相機預覽回調數據保存爲視頻,以達目的。

首先,通過預覽回調數據錄製視頻的代碼參照:https://www.cnblogs.com/CoderTian/p/6224605.html,文章中有完整代碼,運行起來沒有問題,視頻保存爲H264格式的文件,同時本人測試時發現保存的視頻是橫屏的,在PC上播放時視頻爲逆時針旋轉90度的畫面。

爲了將視頻格式保存爲mp4格式,同時將視頻的畫面調正,對以上文章中的代碼稍作修改。

一、將視頻調正。

先補充相機預覽數據格式知識,相機預覽回調函數onPreviewFrame(byte[] data, Camera camera)中的data返回的格式一般是NV21,是屬於YUV420格式。以寬高分別爲8和6爲例,data的數組長度爲8*6*3/2,存儲方式如下圖:

nv21formatdata

因爲視頻編碼器MediaCodec使用的顏色格式是YUV420SP,需要將NV21格式轉化爲NV12(YUV420SP),以便視頻編碼。NV12格式和NV21類似,Y存儲方式一樣,不同就是V和U存儲位置對調,比如上圖的V0和U0對調,V1和U1對調,以此類推。以上文章中使用的方法是NV21ToNV12(),稍微簡化成如下函數,

private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
    if (nv21 == null || nv12 == null) return;
    int framesize = width * height;
    // for y nv21 and nv12 is equal
    // reference to https://blog.csdn.net/baidu_31872269/article/details/70315193
    System.arraycopy(nv21, 0, nv12, 0, framesize);
    // remaining 1/2 uv data
    for (int j = 0; j < framesize / 2; j += 2) {
        nv12[framesize + j - 1] = nv21[j + framesize];
        nv12[framesize + j] = nv21[j + framesize - 1];
    }
}

重點來了,如何修改代碼,讓視頻調正。

1、修改視頻格式的寬和高,代碼如下

// mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width);


參數中width > height,即視頻的寬度是比高度大的。比如1280x720,640x480。

2、調用NV21轉NV12(YUV420SP)格式轉化函數並旋轉90度。

網上YUV420SP順時針旋轉函數很多,直接用就行。但是爲了節省轉化時間,我寫了個函數,直接將NV21轉化成NV12並順時針旋轉90度。函數如下:

    private void NV21ToNV12Rotate90(byte[] nv21, byte[] nv12, int width, int height) {
        if (nv21 == null || nv12 == null) return;
        int framesize = width * height;
        //旋轉Y, add by zzh, rotate 90
        int k = 0;
        int row = width;
        for (int i = width; i > 0; i--) {
            for (int j = height; j > 0; j--) {
                nv12[k++] = nv21[width * j - row];
            }
            row--;
        }
        // uv 數據作爲一個整體所在列,從最後一列開始賦值
        int uvColIndex = height;
        // uv data count
        for (int i = framesize; i < framesize * 3 / 2;) {
            // after rotate uv data rows
            for (int j = 0; j < width / 2; j++) {
                // 從最後一列開始賦值
                nv12[framesize + uvColIndex - 2 + (j * height)] = nv21[i + 1];
                nv12[framesize + uvColIndex - 1 + (j * height)] = nv21[i];
                i +=2;
            }
            uvColIndex -= 2;
        }
    }

用圖片展示8*6從NV21格式轉化成NV12並順時針旋轉90度後的格式如下圖:

NV21轉NV12旋轉90度

二、將H264轉成mp4格式。

1、先在Android studio app的build.gradle中加入mp4parser包的引用

dependencies {
    // ...
    // for h264 video to mp4 video
    implementation 'com.googlecode.mp4parser:isoparser:1.1.21'
}

2、在AvcEncoder.StopThread()中調用h264轉mp4函數,最好起一個線程執行,防止阻塞UI。

    // add by zzh for h264 to mp4
    private void h264ToMp4() {
        try {
            File file = new File(path);
            if (!file.exists()) {
                Log.e(TAG, "h264ToMp4 error, h264 file not exists");
                return;
            }

            H264TrackImpl h264Track = new H264TrackImpl(new FileDataSourceImpl(path));
            Movie movie = new Movie();
            movie.addTrack(h264Track);
            Container mp4file = new DefaultMp4Builder().build(movie);
            FileChannel fc = new FileOutputStream(new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/test1.mp4")).getChannel();
            mp4file.writeContainer(fc);
            fc.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章