2015-09-29 196 views
0

我用下面的代码(original guide)解码H264视频流:MediaCodec崩溃

public void configure(Surface surface, int width, int height, ByteBuffer csd0) { 
     String VIDEO_FORMAT = "video/avc"; 
     if (mConfigured) { 
      throw new IllegalStateException("Decoder is already configured"); 
     } 
     MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height); 
     // little tricky here, csd-0 is required in order to configure the codec properly 
     // it is basically the first sample from encoder with flag: BUFFER_FLAG_CODEC_CONFIG 
     format.setByteBuffer("csd-0", csd0); 
     try { 
      mCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT); 
     } catch (IOException e) { 
      throw new RuntimeException("Failed to create codec", e); 
     } 
     mCodec.configure(format, surface, null, 0); 
     mCodec.start(); 
     mConfigured = true; 
    } 

    @SuppressWarnings("deprecation") 
    public void decodeSample(byte[] data, int offset, int size, long presentationTimeUs, int flags) { 
     if (mConfigured && mRunning) { 
      int index = mCodec.dequeueInputBuffer(mTimeoutUs); 
      if (index >= 0) { 
       ByteBuffer buffer; 
       // since API 21 we have new API to use 
       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 
        buffer = mCodec.getInputBuffers()[index]; 
        buffer.clear(); 
       } else { 
        buffer = mCodec.getInputBuffer(index); 
       } 
       if (buffer != null) { 
        buffer.put(data, offset, size); 
        mCodec.queueInputBuffer(index, 0, size, presentationTimeUs, flags); 
       } 
      } 
     } 
    } 

    @Override 
    public void run() { 
     try { 
      MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 
      while (mRunning) { 
       if (mConfigured) { 
        int index = mCodec.dequeueOutputBuffer(info, mTimeoutUs); 
        if (index >= 0) { 
         // setting true is telling system to render frame onto Surface 
         mCodec.releaseOutputBuffer(index, true); 
         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
          break; 
         } 
        } 
       } else { 
        // just waiting to be configured, then decode and render 
        try { 
         Thread.sleep(10); 
        } catch (InterruptedException ignore) { 
        } 
       } 
      } 
     } finally { 
      if (mConfigured) { 
       mCodec.stop(); 
       mCodec.release(); 
      } 
     } 
    } 

我可以同时在我的Nexus 6(API 22)和三星Galaxy核心运行这个(API 16)在低和中等质量。但是,当我切换到高质量(720p)时,在约30帧后它在三星上崩溃(但没有任何东西呈现在屏幕上)。

E/ACodec﹕ [OMX.qcom.video.decoder.avc] ERROR(0x8000100a) 
E/MediaCodec﹕ Codec reported an error. (omx error 0x8000100a, internalError -2147483648) 
[...] 
W/System.err﹕ java.lang.IllegalStateException 
W/System.err﹕ at android.media.MediaCodec.dequeueInputBuffer(Native Method) 
W/System.err﹕ at com.test.stream.VideoDecoder$Worker.decodeSample(VideoDecoder.java:95) 
W/System.err﹕ at com.test.stream.VideoDecoder.decodeSample(VideoDecoder.java:24) 
W/System.err﹕ at com.test.stream.VideoThread.run(VideoThread.java:160) 

上面的错误是第一个出现的错误,IllegalStateException后来抛出每帧。

我的问题是,这是一个设备特定的问题(因为:较旧的api /设备,较弱的功能等)还是实际上是错的? 以及我应该如何处理?

回答

2

对于我的Android h.264解码器,我将它与您的设置略有不同。我认为你使用更现代化的api级别。但对我来说,它看起来更像是这样的:

public void startDecoder() { 
    // Initilize codec 
    mediaCodec = MediaCodec.createDecoderByType("video/avc"); 
    mediaFormat = MediaFormat.createVideoFormat("video/avc", 0, 0); 
    bufferInfo = new MediaCodec.BufferInfo(); 

    // STOPS unit-tests from crashing here from mocked out android 
    if (mediaCodec != null) { 
     mediaCodec.configure(mediaFormat, targetSurface, null, 0); 
     mediaCodec.start(); 
     decoderThread = new Thread(this); 
     decoderThread.start(); 
    } 
} 

//解码线程是指这个类确实解码器/渲染循环:

public void run() { 
    //mediaCodec input + output dequeue timeouts 
    long kInputBufferTimeoutMs = 50; 
    long kOutputBufferTimeoutMs = 50; 

    while (running && mediaCodec != null) { 
     synchronized (mediaCodec) { 
      // stop if not running. 
      if (!running || mediaCodec == null) 
       break; 

      // Only push in new data if there is data available in the queue 
      if (naluSegmentQueue.size() > 0) { 
       int inputBufferIndex = mediaCodec.dequeueInputBuffer(kInputBufferTimeoutMs); 
       if (inputBufferIndex >= 0) { 
        NaluSegment segment = naluSegmentQueue.poll(); 
        codecInputBufferAvailable(segment, mediaCodec, inputBufferIndex); 
       } 
      } 

      // always check if output is available. 
      int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, kOutputBufferTimeoutMs); 
      if (outputBufferIndex >= 0) { 
       // Try and render first 
       codecOuputBufferAvailable(mediaCodec, outputBufferIndex, bufferInfo); 
      } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
       // Subsequent data will conform to new format. 
       // Can ignore if using getOutputFormat(outputBufferId) 
       mediaFormat = mediaCodec.getOutputFormat(); 
      } 
     } 
    } 
} 

将数据放入解码器包括参数。我懒得试图使用csd-0/1网络流可以改变格式描述,并且更容易让它动态获取。

private void codecInputBufferAvailable(NaluSegment segment, MediaCodec codec, int index) { 
    int flags = (segment.getType() == NaluType.SPS 
      || segment.getType() == NaluType.PPS 
      || segment.getType() == NaluType.SUPP_ENHANCEMENT) ? 
      MediaCodec.BUFFER_FLAG_CODEC_CONFIG : MediaCodec.BUFFER_FLAG_SYNC_FRAME; 

    ByteBuffer[] buffers = codec.getInputBuffers(); 
    ByteBuffer buffer = buffers[index]; 
    // Can throw buffer overflow exception when buffer sizes are too small. 
    try { 
     buffer.put(segment.getBuffer()); 
     codec.queueInputBuffer(index, 0, segment.getBufferSize(), 0, flags); 
    } catch(Exception e) { 
     Log.e(TAG, "Failed to push buffer to decoder"); 
    } 
} 

重要:buffer.put(segment.getBuffer()); getBuffer()在这里总是返回一个4字节的缓冲区。 android解码器不理解3个字节的最终单位。所以如果你有一个3字节的nal单元,将它变成4字节的长度为+1的魔术序列,并且0x00,0x00,0x00,0x01作为启动魔术序列,缓冲区的其余部分应该是& buffer [headerLength]。

注意这里的try-catch并没有给出编译器警告,但是如果你有一个非常大的有效负载和字节缓冲区太小,它可能会抛出缓冲区溢出异常

只要你解析出你的NAL单位,这应该适合你。但对于我的情况,我注意到NAL单位可以是3或4个字节的魔头。

/** 
* H264 is comprised of NALU segments. 
* 
* XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ 
* 
* Each segment is comprised of: 
* 
* XXXX -> Magic byte header (0x00, 0x00, 0x00, 0x01) NOTE: this can be either 3 of 4 bytes 
* Y  -> The Nalu Type 
* ZZZ... -> The Payload 
* 
* Notice there is no nalu length specified. To parse an nalu, you must 
* read until the next magic-byte-sequence AKA the next segment to figure 
* out the full nalu length 
**/ 
public static List<NaluSegment> parseNaluSegments(byte[] buffer) throws NaluBufferException { 
    List<NaluSegment> segmentList = new ArrayList<>(); 
    if (buffer.length < 6) { 
     return segmentList; 
    } 

    int lastStartingOffset = -1; 
    for (int i = 0; i < buffer.length - 10; ++i) { 
     **if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)** { 
      int naluType = (buffer[i+3] & 0x1F); 
      NaluSegment segment = new NaluSegment(naluType, 3, i); 

      **if (i > 0 && buffer[i-1] == 0x00)** { 
       // This is actually a 4 byte segment 
       int currentSegmentOffset = segment.getOffset(); 
       segment.setHeaderSize(4); 
       segment.setOffset(currentSegmentOffset - 1); 
      } 
... 

创建您自己的nalu段对象,并且不要忘记尾随的NAL。

我希望这会有所帮助。

+1

感谢您的广泛答复。你有没有遇到过任何限制,比如我提到的这个解决方案? – sadhi

+0

如果您打算进行自适应流媒体并将相同的媒体编解码器切换到更高质量的视频,您需要:media-codec.stop()并用新分辨率重新配置,或者我认为您可以使用:KeyMaxWidth和KeyMaxHeight以媒体格式支持更高质量的动态。 – redbrain