2016-10-10 44 views
0

您好堆栈溢出,使用MediaCodec流解码原始h264导致黑色表面

我目前正在编写一个框架以实现与智能手机的vr体验。因此图形内容在服务器(立体镜)上呈现,编码并发送到智能手机。我使用的是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播放。

我很抱歉代码很大。 感谢您的帮助!

回答

1

所以唯一的问题是,那dequeueOutputBuffers仍然可能会返回-3,又名MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED,它被标记为已弃用。非常好。通过不处理这个返回值,或者更具体地说,使用常量值作为getOutputBuffer()的输入,编解码器会抛出一个错误 - >黑屏。

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

0

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

此外SPS/PPS的东西只有拳击容器像mp4所必需的东西。原始球员是带内的。

相关问题