日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

編碼分為軟編和硬編,毫無疑問,能用硬編就用硬編,而Android/ target=_blank class=infotextkey>安卓硬編,繞不開MediaCodec。

MediaCodec

關于MediaCodec,官方文檔有著詳細的解答,這里就不贅述了。

android 音視頻硬編解碼

 

視頻硬編碼

我這里需要將相機實時預覽的YUV數據,編碼為H.264格式的數據,在開始編碼之前,首先要

        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, width, height);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        try {
            mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

接下來就可以傳入數據進行編碼了

    private void encodeBuffer(@NonNull byte[] buffer, long pts) {
        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_S);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
            inputBuffer.clear();
            inputBuffer.put(buffer);
            mMediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, pts, 0);
        }
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
        while (outputBufferIndex >= 0) {
            ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                bufferInfo.size = 0;
            }
            if (bufferInfo.size > 0) {
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                bufferInfo.presentationTimeUs = pts;
                // todo 編碼后的數據,可做回調處理...
            }
            mMediaCodec.releaseoutputBuffer(outputBufferIndex, false);
            bufferInfo = new MediaCodec.BufferInfo();
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
        }
    }

音頻硬編碼

同時,將麥克風錄制的PCM數據,編碼為AAC格式的數據,同理,在開始編碼之前

        MediaFormat mediaFormat = MediaFormat.createAudioFormat(MIMETYPE_AUDIO_AAC, sampleRateInHz, channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, AudioRecord.getMinBufferSize(DEFAULT_SAMPLE_RATE_IN_HZ, DEFAULT_CHANNEL_CONFIG, DEFAULT_ENCODING) * 3);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2);
        try {
            mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

同理,接下來就可以傳入數據進行編碼了

    private void encodeBuffer(@NonNull byte[] buffer, long pts) {
        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_S);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
            inputBuffer.clear();
            inputBuffer.limit(buffer.length);
            inputBuffer.put(buffer);
            mMediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, pts, 0);
        }
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
        while (outputBufferIndex >= 0) {
            ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                bufferInfo.size = 0;
            }
            if (bufferInfo.size > 0) {
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                bufferInfo.presentationTimeUs = pts;
                // todo 編碼后的數據,可做回調處理...
            }
            mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
            bufferInfo = new MediaCodec.BufferInfo();
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
        }
    }

可以發現,音視頻編碼流程是一樣的,通過上面的操作,看起來數據的編碼流程已經完成,接下來,就是解碼了,同樣的,解碼也要用到的MediaCodec

視頻硬解碼

解碼之前

        try {
            mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
            mMediaCodec.configure(mediaFormat, surface, null, 0);
            mMediaCodec.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

接下來便是解碼已經編碼好的H.264幀數據并在Surface中進行渲染

    public void decodeAndRenderV(byte[] in, int offset, int length, long pts) {
        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
            inputBuffer.clear();
            inputBuffer.put(in, offset, length);
            mMediaCodec.queueInputBuffer(inputBufferIndex, 0, length, pts, 0);
        }

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
        while (outputBufferIndex >= 0) {
            mMediaCodec.releaseOutputBuffer(outputBufferIndex, true);
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
        }
    }

如果想完整保存每一幀的YUV數據呢?

音頻硬解碼

同樣的,首先初始化操作

        try {
            mMediaCodec = MediaCodec.createDecoderByType(MIMETYPE_AUDIO_AAC);
            MediaFormat mediaFormat = new MediaFormat();
            mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_AUDIO_AAC);
            mMediaCodec.configure(mediaFormat, null, null, 0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

最后,解碼音頻幀數據并進行播放(需要先準備好AudioTrack)

    public void decodeAndRenderA(byte[] in, int offset, int length, long pts) {
        int inputBufIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
        if (inputBufIndex >= 0) {
            ByteBuffer dstBuf = mMediaCodec.getInputBuffer(inputBufIndex);
            dstBuf.clear();
            dstBuf.put(in, offset, length);
            mMediaCodec.queueInputBuffer(inputBufIndex, 0, length, pts, 0);
        }
        ByteBuffer outputBuffer;
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(info, TIMEOUT_US);
        while (outputBufferIndex >= 0) {
            outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
            byte[] outData = new byte[info.size];
            outputBuffer.get(outData);
            outputBuffer.clear();
            if (mAudioTrack != null) {
                mAudioTrack.write(outData, 0, info.size);
            }
            mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(info, TIMEOUT_US);
        }
    }

坑的開始

重點來了,通過上面對MediaCodec的使用,可以實現規范的音視頻數據幀的硬編解碼,然而,在實際的應用中會發現,無論是推流還是收流,都存在不少的坑,比如

編碼后的圖像呈黑白色

ffmpeg推視頻流報non-existing PPS 0 referenced錯誤

帶ADTS數據頭的數據幀用ffmpeg推流成功后,再推視頻流會一直返回錯誤碼-1094995529

AAC硬解碼總是報IllegalStateException異常

解碼AAC幀數據硬解碼調用dequeueOutputBuffer時,總是返回-1

拉流得到的數據頭有變更

這里只列舉了印象比較深的幾個坑,其它的就不一一列舉了,而在解決這各種問題之前,還必須要掌握SPS、PPS以及ADTS的相關知識

SPS和PPS

MediaCodec同步方式H.264編碼獲取SPS和PPS

        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
        if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat mediaFormat = mMediaCodec.getOutputFormat();
            ByteBuffer spsb = mediaFormat.getByteBuffer("csd-0");
            byte[] sps = new byte[spsb.remaining()];
            spsb.get(sps, 0, sps.length);
            ByteBuffer ppsb = mediaFormat.getByteBuffer("csd-1");
            byte[] pps = new byte[ppsb.remaining()];
            ppsb.get(pps, 0, pps.length);
            byte[] sps_pps = new byte[sps.length + pps.length];
            System.arraycopy(sps, 0, sps_pps, 0, sps.length);
            System.arraycopy(pps, 0, sps_pps, sps.length, pps.length);
        }

相對應的,H.264解碼如何獲取SPS和PPS信息呢?

通常,H.264編碼幀數據都有一個起始碼,起始碼由三個字節的00 00 01或者四個字節的00 00 00 01組成,起始碼之后下一個字節便是幀數據類型Code

android 音視頻硬編解碼

 

nalu type = code & 0x1F

由于設備廠商的不同,起始碼緊接的code可能為十進制,也可能為十六進制,我在這里就踩過坑

在解碼AAC幀數據前,也需要設置MediaCodec的SPS

            int sampleIndex = 4;
            int chanCfgIndex = 2;
            int profileIndex = 1;
            byte[] adtsAudioHeader = new byte[2];
            adtsAudioHeader[0] = (byte) (((profileIndex + 1) << 3) | (sampleIndex >> 1));
            adtsAudioHeader[1] = (byte) ((byte) ((sampleIndex << 7) & 0x80) | (chanCfgIndex << 3));
            ByteBuffer byteBuffer = ByteBuffer.allocate(adtsAudioHeader.length);
            byteBuffer.put(adtsAudioHeader);
            byteBuffer.flip();
            mediaFormat.setByteBuffer("csd-0", byteBuffer);

當然,這種方法不夠靈活,可以變更為在剛開始接收帶ADTS數據頭的數據幀時再設置

分享到:
標簽:android
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定