使用MediaCodec流解码原始h264会导致黑色表面

时间:2016-10-10 14:27:16

标签: java android decode h.264

Hello Stack Overflow,

我目前正在编写一个框架来实现智能手机的体验。因此,图形内容在服务器(立体镜)上呈现,编码并发送到智能手机。我使用的是LG的Nexus 5x。 我写的应用程序最初由两个纹理视图和解码和显示帧的逻辑组成。 然而,Androids 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播放,没有任何问题。

我很抱歉大的代码块。 谢谢你的帮助!

2 个答案:

答案 0 :(得分:1)

所以唯一的问题是,dequeueOutputBuffers仍然可能返回-3,又称MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED,标记为已弃用。非常好。通过不处理此返回值,或更具体,使用常量值作为getOutputBuffer()的输入,编解码器抛出错误 - &gt;黑屏。

修改 哦,显然整个NAL的东西也是不需要的。即使API声明,也必须在开始之前提供SPS和PPS NALU。我在我的问题中标记了可以省略的部分。

答案 1 :(得分:0)

我在新的三星设备上看到类似的行为,并怀疑编解码器可能有同样的问题。将尝试你的修复,谢谢。

SPS / PPS的东西也只适用于像mp4这样的拳击容器。原始球员是带内球员。