簡體   English   中英

通過MediaCodec直接對h264視頻進行轉碼而無需紋理渲染

[英]Transcode h264 video by MediaCodec directly withou texture render

我正在使用MediaCodec做代碼轉換器。

我創建了兩個mediacodec實例,一個用於解碼,另一個用於編碼。 我正在嘗試將解碼器outputBuffer直接發送到編碼器inputBuffer中。

在編譯和執行時似乎沒有問題,而且運行速度很快。 但是輸出的視頻文件有問題,我檢查了輸出視頻的元數據,它們沒問題:比特率,幀率,分辨率...只有視頻中的圖像是這樣的: 屏幕截圖

我以為它出了點問題,但是我無法弄清楚...

我搜索了庫和文檔,然后使用Texture表面找到了一些示例代碼,以渲染解碼器輸出數據並將數據傳輸到編碼器中。 但是我認為這對我來說不是必需的。 因為我不需要編輯視頻圖像,所以我只需要做的就是更改比特率和分辨率,以減小文件的大小。

這是我項目中的核心代碼:

private void decodeCore() {
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    int frameCount = 0;
    while (mDecodeRunning) {

        int inputBufferId = mDecoder.dequeueInputBuffer(50);
        if (inputBufferId >= 0) {
            // fill inputBuffers[inputBufferId] with valid data
            int sampleSize = mExtractor.readSampleData(mDecodeInputBuffers[inputBufferId], 0);
            if (sampleSize >= 0) {
                long time = mExtractor.getSampleTime();
                mDecoder.queueInputBuffer(inputBufferId, 0, sampleSize, time, 0);
            } else {
                mDecoder.queueInputBuffer(inputBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            }

            mExtractor.advance();
        }

        int outputBufferId = mDecoder.dequeueOutputBuffer(bufferInfo, 50);
        if (outputBufferId >= 0) {

            FrameData data = mFrameDataQueue.obtain();
            //wait until queue has space to push data
            while (data == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data = mFrameDataQueue.obtain();
            }

            data.data.clear();
            data.size = 0;
            data.offset = 0;
            data.flag = 0;
            data.frameTimeInUs = bufferInfo.presentationTimeUs;

            // outputBuffers[outputBufferId] is ready to be processed or rendered.
            if (bufferInfo.size > 0) {
                ByteBuffer buffer = mDecodeOutputBuffers[outputBufferId];

                buffer.position(bufferInfo.offset);
                buffer.limit(bufferInfo.offset + bufferInfo.size);

                data.data.put(buffer);
                data.data.flip();

                data.size = bufferInfo.size;
                data.frameIndex = frameCount++;

            }

            data.flag = bufferInfo.flags;

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d("bingbing_transcode", "decode over! frames:" + (frameCount - 1));
                mDecodeRunning = false;
            }

            mFrameDataQueue.pushToQueue(data);
            mDecoder.releaseOutputBuffer(outputBufferId, false);
            Log.d("bingbing_transcode", "decode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size);
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            mDecodeOutputBuffers = mDecoder.getOutputBuffers();
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // Subsequent data will conform to new format.
            mDecodeOutputVideoFormat = mDecoder.getOutputFormat();
            configureAndStartEncoder();
        }
    }

    mDecoder.stop();
    mDecoder.release();
}

private void encodeCore() {
    int trackIndex = 0;
    boolean muxerStarted = false;
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    int frameCount = 0;
    while (mEncodeRunning) {
        int inputBufferId = mEncoder.dequeueInputBuffer(50);
        if (inputBufferId >= 0) {
            FrameData data = mFrameDataQueue.pollFromQueue();
            //wait until queue has space to push data
            while (data == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data = mFrameDataQueue.obtain();
            }

            if (data.size > 0) {
                ByteBuffer inputBuffer = mEncodeInputBuffers[inputBufferId];
                inputBuffer.clear();
                inputBuffer.put(data.data);
                inputBuffer.flip();
            }
            mEncoder.queueInputBuffer(inputBufferId, 0, data.size, data.frameTimeInUs, data.flag);
            mFrameDataQueue.recycle(data);
        }

        int outputBufferId = mEncoder.dequeueOutputBuffer(bufferInfo, 50);
        if (outputBufferId >= 0) {
            // outputBuffers[outputBufferId] is ready to be processed or rendered.
            ByteBuffer encodedData = mEncodeOutputBuffers[outputBufferId];

            if (bufferInfo.size > 0) {
                if (encodedData == null) {
                    throw new RuntimeException("encoderOutputBuffer " + outputBufferId + " was null");
                }

                if (!muxerStarted) {
                    throw new RuntimeException("muxer hasn't started");
                }

                frameCount++;
            }
            // adjust the ByteBuffer values to match BufferInfo (not needed?)
            encodedData.position(bufferInfo.offset);
            encodedData.limit(bufferInfo.offset + bufferInfo.size);

            mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo);

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d("bingbing_transcode", "encode over! frames:" + (frameCount - 1));
                mEncodeRunning = false;
            }

            mEncoder.releaseOutputBuffer(outputBufferId, false);
            Log.d("bingbing_transcode", "encode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size);

        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            mEncodeOutputBuffers = mEncoder.getOutputBuffers();
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // should happen before receiving buffers, and should only happen once
            if (muxerStarted) {
                throw new RuntimeException("format changed twice");
            }

            MediaFormat newFormat = mEncoder.getOutputFormat();
            Log.d("bingbing_transcode", "encoder output format changed: " + newFormat);

            // now that we have the Magic Goodies, start the muxer
            trackIndex = mMuxer.addTrack(newFormat);
            mMuxer.start();
            muxerStarted = true;
            mEncodeOutputVideoFormat = newFormat;
        }
    }


    mEncoder.stop();
    mEncoder.release();
    if (muxerStarted) {
        mMuxer.stop();
        mMuxer.release();
    }
}

這兩個函數在兩個不同的線程中運行。

FrameData是幀字節緩沖區和幀當前時間以及所需內容的簡單存儲

使用字節緩沖區輸入時,有一些未定義的有關輸入數據布局的細節。 當寬度不是16的倍數時,某些編碼器希望將輸入數據行的長度填充為16的倍數,而其他編碼器將假定線長等於寬度,而沒有額外的填充。

Android CTS測試(定義了人們在所有設備上期望的行為)用於從字節緩沖區輸入進行編碼的目的是故意僅測試分辨率為16的倍數,因為他們知道不同的硬件供應商會以不同的方式執行此操作,並且他們不想強制執行任何特殊處理。

通常,您不能假定解碼器的輸出將使用與編碼器消耗的行大小相似的行大小。 解碼器可以自由地(並且有些實際上可以)返回比實際內容大小大得多的寬度,並使用crop_left / crop_right字段來指示實際上打算將其可視的哪些部分。 因此,如果解碼器這樣做了,則除非考慮解碼器和編碼器實際使用的行大小,否則除非將數據逐行復制,否則無法將數據直接從解碼器傳遞到編碼器。

此外,您甚至無法假設解碼器使用與編碼器類似的像素格式。 許多高通設備將特殊的平鋪像素格式用於解碼器輸出,而編碼器輸入是常規平面數據。 在這些情況下,您必須實現一個相當復雜的邏輯來對數據進行混洗,然后才能將其輸入編碼器。

使用紋理表面作為中間隱藏所有這些細節。 在您的用例中,聽起來可能並非完全必要,但是它確實隱藏了解碼器和編碼器之間緩沖區格式的所有變化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM