2013-12-09 70 views
11

我已經使用Android的MediaCodec API編寫了H264 Stream Encoder。我使用不同的處理器在大約十種不同的設備上進行了測試,它在所有這些設備上都能正常工作,Snapdragon 800處理器(Google Nexus 5和Sony Xperia Z1)除外。在這些設備上,我獲得了SPS和PPS以及第一個關鍵幀,但在此之後,mEncoder.dequeueOutputBuffer(mBufferInfo,0)僅返回MediaCodec.INFO_TRY_AGAIN_LATER。我已經嘗試過使用不同的超時,比特率,分辨率和其他配置選項,但無濟於事。結果總是一樣的。MediaCodec H264編碼器無法在Snapdragon 800設備上工作

我用下面的代碼來初始化編碼器:

 mBufferInfo = new MediaCodec.BufferInfo(); 
     encoder = MediaCodec.createEncoderByType("video/avc"); 
     MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480); 
     mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 768000); 
     mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); 
     mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mEncoderColorFormat); 
     mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); 
     encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 

在選擇的顏色格式是:

MediaCodecInfo.CodecCapabilities capabilities = mCodecInfo.getCapabilitiesForType(MIME_TYPE); 
      for (int i = 0; i < capabilities.colorFormats.length && selectedColorFormat == 0; i++) 
      { 
       int format = capabilities.colorFormats[i]; 
       switch (format) { 
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: 
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: 
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: 
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: 
        case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: 
        case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar: 
         selectedColorFormat = format; 
         break; 
        default: 
         LogHandler.e(LOG_TAG, "Unsupported color format " + format); 
         break; 
       } 
      } 

我做

  ByteBuffer[] inputBuffers = mEncoder.getInputBuffers(); 
     ByteBuffer[] outputBuffers = mEncoder.getOutputBuffers(); 

     int inputBufferIndex = mEncoder.dequeueInputBuffer(-1); 
     if (inputBufferIndex >= 0) 
     { 
      // fill inputBuffers[inputBufferIndex] with valid data 
      ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; 
      inputBuffer.clear(); 
      inputBuffer.put(rawFrame); 
      mEncoder.queueInputBuffer(inputBufferIndex, 0, rawFrame.length, 0, 0); 
      LogHandler.e(LOG_TAG, "Queue Buffer in " + inputBufferIndex); 
     } 

     while(true) 
     { 
      int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); 
      if (outputBufferIndex >= 0) 
      { 
       Log.d(LOG_TAG, "Queue Buffer out " + outputBufferIndex); 
       ByteBuffer buffer = outputBuffers[outputBufferIndex]; 
       if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) 
       { 
        // Config Bytes means SPS and PPS 
        Log.d(LOG_TAG, "Got config bytes"); 
       } 

       if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) 
       { 
        // Marks a Keyframe 
        Log.d(LOG_TAG, "Got Sync Frame"); 
       } 

       if (mBufferInfo.size != 0) 
       { 
        // adjust the ByteBuffer values to match BufferInfo (not needed?) 
        buffer.position(mBufferInfo.offset); 
        buffer.limit(mBufferInfo.offset + mBufferInfo.size); 

        int nalUnitLength = 0; 
        while((nalUnitLength = parseNextNalUnit(buffer)) != 0) 
        { 
         switch(mVideoData[0] & 0x0f) 
         { 
          // SPS 
          case 0x07: 
          { 
           Log.d(LOG_TAG, "Got SPS"); 
           break; 
          } 

          // PPS 
          case 0x08: 
          { 
           Log.d(LOG_TAG, "Got PPS"); 
           break; 
          } 

          // Key Frame 
          case 0x05: 
          { 
           Log.d(LOG_TAG, "Got Keyframe"); 
          } 

          //$FALL-THROUGH$ 
          default: 
          { 
           // Process Data 
           break; 
          } 
         } 
        } 
       } 

       mEncoder.releaseOutputBuffer(outputBufferIndex, false); 

       if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) 
       { 
        // Stream is marked as done, 
        // break out of while 
        Log.d(LOG_TAG, "Marked EOS"); 
        break; 
       } 
      } 
      else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) 
      { 
       outputBuffers = mEncoder.getOutputBuffers(); 
       Log.d(LOG_TAG, "Output Buffer changed " + outputBuffers); 
      } 
      else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) 
      { 
       MediaFormat newFormat = mEncoder.getOutputFormat(); 
       Log.d(LOG_TAG, "Media Format Changed " + newFormat); 
      } 
      else if(outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) 
      { 
       // No Data, break out 
       break; 
      } 
      else 
      { 
       // Unexpected State, ignore it 
       Log.d(LOG_TAG, "Unexpected State " + outputBufferIndex); 
      } 
     } 

由於獲取數據您的幫助!

+1

當輸出停止時,有多少輸入幀排隊? (我想確保它不是簡單的輸入。)有沒有什麼可疑的東西在logcat中看? (編解碼器傾向於噴灑Log.e,這可能很難說)。正在選擇什麼顏色格式? (QCOM格式?)「原始幀」的大小是否與輸入緩衝區的容量完全相同? (如果不是......爲什麼不呢?) – fadden

+0

@fadden我讓它運行了多長時間並不重要,但它似乎總是在輸入緩衝區中有5幀。它在創建時的輸出是:'I/OMXClient(11245):使用客戶端OMX mux。 I/ACodec(11245):setupVideoEncoder succeeded'選擇的顏色格式在兩種情況下都是'MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar'(如果我查詢所有格式,它只有兩個,上述和一個常數爲2130708361,如果選中則崩潰。)原始幀和輸入緩衝區不相同(原始幀大小始終較小,輸入緩衝區容量始終爲282624) – lowtraxx

+0

五個幀是典型的 - 聽起來像它不處理輸入,因此沒有輸出。我假設你在調用'encoder.start()'? YUV420SemiPlanar很好; 2130708361僅用於表面輸入。 YUV420緩衝區的大小應該是'width * height * 1.5',或460800字節,所以我對你的緩衝區大小有點困惑。你在日誌文件中看到你的「媒體格式改變」信息,如果有,它說什麼? – fadden

回答

20

您需要在調用queueInputBuffer時設置presentationTimeUs參數。 大多數編碼器忽略這一點,你可以編碼流沒有問題。用於Snapdragon 800設備的編碼器沒有。

此參數表示幀的錄製時間,因此需要增加您想要編碼的幀與前一幀之間的幀數。

如果參數集與前一幀中的值相同,則編碼器將其丟棄。 如果參數設置得太小(例如30 FPS記錄時爲100000),則編碼幀的質量下降。

+1

呵呵。演示時間戳不是H.264基本流的一部分,所以我的期望是該值只是簡單地通過了。我添加了一個新的FAQ條目(http://bigflake.com/mediacodec/#q8)。 – fadden

+0

您能否提供一個如何設置演示時間的例子? –

+0

調用Snapdragon 800設備的queueInputBuffer值時的TimeUs參數 – DreamCoder

0

encodeCodec.queueInputBuffer(inputBufferIndex,0,input.length,(System.currentTimeMillis() - startMs)* 1000,0);

+4

如果您正在接收實時輸入(例如來自攝像機),則使用當前時間沒有問題,但如果您使用其他來源(例如,以比實時快的速度轉碼視頻)。我也建議不要使用'System.currentTimeMillis()',因爲它會受到突然跳躍(向前和向後)的影響。單調的'System.nanoTime()'是一個更好的來源。 – fadden

+0

即使在轉碼的情況下,源內容的時間戳也將適用(基於源內容的fps)。編碼器需要知道時間戳才能夠管理速率控制。因此,如果您使用mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,FPS)配置了幀率,建議爲非實時編碼生成時間戳(N * 1000 * 1000/FPS)。 – peasea

相關問題