2015-09-29 192 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