簡體   English   中英

用MediaCodec流解碼原始h264會導致黑色表面

[英]Decoding raw h264 with MediaCodec stream results in black surface

你好堆棧溢出,

我目前正在編寫一個框架,以通過智能手機獲得虛擬現實體驗。 因此,圖形內容將在服務器(立體鏡)上呈現,編碼並發送到智能手機。 我使用的是LG的Nexus 5x。 我正在編寫的應用程序最初由兩個紋理視圖以及用於解碼和顯示幀的邏輯組成。 但是,Android的MediaCodec類在每次嘗試時都會崩潰,因此我嘗試根據我之前編寫的工作代碼創建一個只有一個表面的最小工作示例。 但是,盡管MediaCodec不再拋出CodecException了,但表面仍然保持黑色。

public class MainActivity extends Activity implements SurfaceHolder.Callback
{
private DisplayThread displayThread = null;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    SurfaceView sv = new SurfaceView(this);
    sv.getHolder().addCallback(this);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    setContentView(sv);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
    if (displayThread == null)
    {
        displayThread = new DisplayThread(holder.getSurface());
        displayThread.start();
    }
}

private class DisplayThread extends Thread
{
    private MediaCodec codec;
    private Surface surface;
    private UdpReceiver m_renderSock;


    public DisplayThread(Surface surface)
    {
        this.surface = surface;
    }

    @Override
    public void run()
    {
        m_renderSock = new UdpReceiver(9091);

        //Configuring Media Decoder
        try {
            codec = MediaCodec.createDecoderByType("video/avc");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }

        MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280,720);

        codec.configure(format, surface, null, 0);
        codec.start();


        while(!Thread.interrupted())
        {
            int frameSize = 0;
            byte[] frameData = m_renderSock.receive();

            if(frameData.length == 1) // Just for the moment, to cope with the first pakets get lost because of missing ARP, see http://stackoverflow.com/questions/11812731/first-udp-message-to-a-specific-remote-ip-gets-lost
                continue;

            /*Edit: This part may be left out*/
            int NAL_START = 1;
            //103, 104 -> SPS, PPS  | 101 -> Data
            int id = 0;
            int dataOffset = 0;

            //Later on this will be serversided, but for now... 
            //Separate the SPSPPS from the Data
            for(int i = 0; i < frameData.length - 4; i++)
            {
                id = frameData[i] << 24 |frameData[i+1] << 16 | frameData[i+2] << 8
                        | frameData[i+3];

                if(id == NAL_START) {
                    if(frameData[i+4] == 101)
                    {
                        dataOffset = i;
                    }
                }
            }


            byte[] SPSPPS = Arrays.copyOfRange(frameData, 0, dataOffset);
            byte[] data = Arrays.copyOfRange(frameData, dataOffset, frameData.length);

            if(SPSPPS.length != 0) {
                int inIndex = codec.dequeueInputBuffer(100000);

                if(inIndex >= 0)
                {
                    ByteBuffer input = codec.getInputBuffer(inIndex);
                    input.clear();
                    input.put(SPSPPS);
                    codec.queueInputBuffer(inIndex, 0, SPSPPS.length, 16, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                }
            }
            /*Edit end*/

            int inIndex = codec.dequeueInputBuffer(10000);
            if(inIndex >= 0)
            {
                ByteBuffer inputBuffer = codec.getInputBuffer(inIndex);
                inputBuffer.clear();
                //inputBuffer.put(data);
                inputBuffer.put(frameData);
                //codec.queueInputBuffer(inIndex, 0, data.length, 16, 0);
                codec.queueInputBuffer(inIndex, 0, frameData.length, 16, 0);
            }

            BufferInfo buffInfo = new MediaCodec.BufferInfo();
            int outIndex = codec.dequeueOutputBuffer(buffInfo, 10000);

            switch(outIndex)
            {
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    break;
                case -3: //This solves it
                    break;
                default:
                    ByteBuffer buffer = codec.getOutputBuffer(outIndex);
                    codec.releaseOutputBuffer(outIndex, true);
            }


        }
    }
}

因此,基本上這段代碼在過去一直有效。 但是那時,媒體編解碼器API的ByteBuffer[]用於輸入和輸出緩沖區。 另外,也不需要將SPSPPS數據與幀數據分開(至少我沒有這樣做,並且它起作用了,也可能是因為Nvcuvenc將每個NALU分開了)。

我檢查了兩個緩沖區的內容,這是結果:

SPSPPS: 
0 0 0 1 103 100 0 32 -84 43 64 40 2 -35 -128 -120 0 0 31 64 0 14 -90 4 120 -31 -107 
0 0 1 104 -18 60 -80

Data:
0 0 0 1 101 -72 4 95 ...

對我來說,這看起來是正確的。 h264流是使用Nvidias NVenc API創建的,如果保存到磁盤,則可以在VLC上播放而沒有任何問題。

我為大型代碼塊感到抱歉。 謝謝你的幫助!

因此,唯一的問題是, dequeueOutputBuffers仍可能返回-3,即MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ,它被標記為已棄用。 非常好。 通過不處理此返回值,或更具體地說,將常量值用作getOutputBuffer()輸入,編解碼器將引發錯誤->黑屏。

編輯:哦,顯然也不需要整個NAL。 即使API指出,也必須在啟動之前提供SPS和PPS NALU。 我標記了可以在我的問題中忽略的部分。

我在新的三星設備上看到類似的行為,並懷疑編解碼器可能有相同的問題。 會嘗試您的修復,謝謝。

同樣,僅對mp4之類的包裝箱來說,SPS / PPS物品是必需的。 原始播放器是帶內的。

暫無
暫無

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

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