Android使用MediaCodec和OpenGL對多段視頻畫面進行裁剪和拼接

太久沒寫博客了,由於工作,過年還有孩子出生搞得自己焦頭爛額,現在有些時間了就搞點東西。發現瀏覽量突破10萬了,也是挺高興的,雖然很多東西寫的不好,可也看到了自己的進步,也是前年到現在的累積。剛開始我只是學習視頻解碼,渲染和視頻編碼,慢慢的也開始搞音頻了,本來沒想過搞視頻編輯這一塊的,慢慢的做着做着就接觸到了,也沒想到會搞成一個系列,等完成了再好好整理一下,廢話不多說開始說正題。

轉gif由於文件大小問題降低了幀率所以看起來卡其實不卡

這篇文章只講畫面部分,音頻部分在我以前的文章有android 使用MediaCodec和lamemp3對多段音頻進行截取和拼接,以後再寫一篇相結合的文章,真正實現一個有畫面有聲音的視頻裁剪和合並。做音頻拼接知道有些參數要一樣纔可以進行拼接,視頻也一樣,主要是幀速率和分辨率要一樣,因爲用到opengl,所以分辨率這一塊可以通過計算解決,而幀速率可以進行丟幀處理,選最小的幀速率爲參數,對大幀速率的視頻進行丟幀。

首先裁剪視頻,裁剪視頻用的是我的這篇文章android 簡單的視頻編輯功能

主要用到這些參數,這是純畫面的,先不處理聲音

已經加入了音頻

    //視頻路徑
    private String videoFile;
    //裁剪的座標長寬
    private int cropLeft;
    private int cropTop;
    private int cropWidth;
    private int cropHeight;
    //開始結束時間
    private long startTime;
    private long endTime;
    //幀時間
    private long frameTime;

得到複數的視頻開始統一參數

//這裏遍歷數據,選擇最小的參數用來初始化編碼器
        cropWidth = decoderList.get(0).getCropWidth();
        cropHeight = decoderList.get(0).getCropHeight();
        int frameRate = decoderList.get(0).getFrameRate();
        frameTime = decoderList.get(0).getFrameTime();
        //寬高選擇方案
        //以最小寬高來顯示還是根據寬高比計算最小顯示分辨率
        //我這選用根據寬高比計算最小顯示分辨率
        float sh = cropWidth*1.0f/cropHeight;
        if(decoderList.size() != 1) {
            for (VideoDecoder holder : decoderList) {
                float vh = holder.getCropWidth()*1.0f/holder.getCropHeight();
                if( sh < vh){
                    cropWidth = Math.min(cropWidth,holder.getCropWidth());
                    cropHeight = (int) (cropWidth*vh);
                    //寬高不能是奇數
                    if(cropHeight%2 != 0){
                        cropHeight = cropHeight - 1;
                    }
                }
                //cropWidth = Math.min(cropWidth,holder.getCropWidth());
                //cropHeight = Math.min(cropHeight,holder.getCropHeight());
                //選最小幀速率,對幀素率大的進行丟幀處理
                frameRate = Math.min(frameRate, holder.getFrameRate());
                //幀時間,幀速率越小,幀時間越大
                frameTime = Math.max(frameTime, holder.getFrameTime());
            }
        }
        //防止MediaFormat取不到幀速率
        if(frameRate < 1){
            frameRate = (int) (1000/(frameTime/1000));
        }
        //編碼參數
        int BIT_RATE = cropWidth*cropHeight*2*8;
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(VIDEO, cropWidth, cropHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

初始化編碼器

        //創建編碼器
        try {
            videoEncode = MediaCodec.createEncoderByType(VIDEO);
        } catch (IOException e) {
            e.printStackTrace();
        }
        videoEncode.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

將編碼器生成的Surface傳給opengl來獲取opengl的顯示畫面

        //將編碼器生成的Surface傳給opengl來獲取opengl的顯示畫面
        Surface surface = videoEncode.createInputSurface();
        videoEncode.start();
        //初始化opengl
        eglUtils = new EGLUtils();
        eglUtils.initEGL(surface);
        framebuffer = new GLFramebuffer();
        framebuffer.initFramebuffer(cropWidth,cropHeight);
        surfaceTexture = framebuffer.getSurfaceTexture();
        surfaceTexture.setDefaultBufferSize(cropWidth,cropHeight);

解碼開始前要計算顯示的畫面

            //計算裁剪的顯示畫面
            float f = decoder.getCropLeft()*1.0f/decoder.getVideoWidth();
            float t = 1.0f - decoder.getCropTop()*1.0f/decoder.getVideoHeight();
            float r = (decoder.getCropLeft()+decoder.getCropWidth())*1.0f/decoder.getVideoWidth();
            float b = 1.0f - (decoder.getCropTop()+decoder.getCropHeight())*1.0f/decoder.getVideoHeight();
            float[] textureVertexData = {
                    r, b,
                    f, b,
                    r, t,
                    f, t
            };
            //將要顯示的畫面傳給opengl
            framebuffer.setVertexDat(textureVertexData);
            framebuffer.setRect(decoder.getCropWidth(),decoder.getCropHeight());

創建解碼器

        //創建解碼器
        try {
            videoDecoder = MediaCodec.createDecoderByType(getMime());
            videoDecoder.configure(format, surface, null, 0);
            videoDecoder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

根據時間進行解碼

            int outIndex = videoDecoder.dequeueOutputBuffer(info, 50000);
                if(outIndex >= 0){
                    //根據時間進行解碼
                    if(info.presentationTimeUs >= videoHolder.getStartTime() &&
                            info.presentationTimeUs <= videoHolder.getEndTime()){
                        //解碼一幀完成後回調
                        if(decoderListener != null){
                            decoderListener.onDecoder(info.presentationTimeUs,videoHolder);
                        }
                    }
                    videoDecoder.releaseOutputBuffer(outIndex, true);
                    //判斷是否裁剪內部分解碼完成
                    if(info.presentationTimeUs >= videoHolder.getEndTime()){
                        break;
                    }
                }

解碼出的圖像進行處理

        surfaceTexture.updateTexImage();
        surfaceTexture.getTransformMatrix(mSTMatrix);

        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(0f,0f,0f,0f);
        GLES20.glViewport(rect.left, rect.top, rect.right, rect.bottom);

        GLES20.glUseProgram(programId);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffers[0]);
        GLES20.glEnableVertexAttribArray(aPositionHandle);
        GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
                12, 0);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffers[1]);
        GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
        GLES20.glVertexAttribPointer(aTextureCoordHandle, 2, GLES20.GL_FLOAT, false, 8, 0);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
        GLES20.glUniform1i(uTextureSamplerHandle,0);
        GLES20.glUniformMatrix4fv(uSTMMatrixHandle, 1, false, mSTMatrix, 0);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

進行編碼

ByteBuffer encodedData = videoEncode.getOutputBuffer(inputIndex);
                    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                        info.size = 0;
                    }
                    if (info.size != 0) {
                        encodedData.position(info.offset);
                        encodedData.limit(info.offset + info.size);
                        info.presentationTimeUs = timeUs;
                        //填充數據
                        mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info);
                        Log.d("==============","timeUs = "+timeUs);
                        timeUs = timeUs+frameTime;
                        //結合presentationTimeUs - videoHolder.getStartTime() < startFrameTime
                        //對幀速率大的進行丟幀
                        startFrameTime = startFrameTime + frameTime;
                    }
                    videoEncode.releaseOutputBuffer(inputIndex, false);

詳細的可以看我的demo

VideoMerge

現在沒加進度條,處理的進度就看log打印,等整理好以後加上音頻和進度條,成爲一個可以看的demo

2019/3/27更新

因爲音頻要轉碼成MP3,再轉成視頻所以效率不太好,可是也找不到什麼好方法將採樣率,比特率和聲道變一樣的方法

在整合音頻時代碼做了大改動,畫面核心代碼沒變只是放到其他類和函數內了,所以就不改博客了,可以下載demo,裏面我也做了註釋

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