2013-12-09 22 views
11

Android'in MediaCodec API'sini kullanarak bir H264 Akış Kodlayıcısı yazdım. Farklı işlemcilerle yaklaşık on farklı cihazda test ettim ve Snapdragon 800 güçlendirilmiş olanlar (Google Nexus 5 ve Sony Xperia Z1) haricinde hepsinde çalıştı. Bu cihazlarda SPS ve PPS ile ilk Keyframe'i elde ederim, ancak bundan sonra mEncoder.dequeueOutputBuffer (mBufferInfo, 0) sadece MediaCodec.INFO_TRY_AGAIN_LATER değerini döndürür. Zaten farklı zaman aşımları, bit hızları, kararlar ve diğer yapılandırma seçenekleriyle denemedim. Sonuç her zaman aynıdır.MediaCodec H264 Enkoder Snapdragon 800 cihazlarda çalışmaz

 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); 

seçilen renk biçimidir:

Ben Encoder başlatmak için aşağıdaki kodu kullanabilirsiniz

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; 
       } 
      } 

Ve

  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); 
      } 
     } 

Teşekkür yaparak veri almak yardımın için!

+1

Çıkış durduğunda kaç tane giriş karesi sıraya alındı? (Sadece giriş için aç bırakılmadığından emin olmak istiyorum.) Logcat'ta şüpheli görünen bir şey var mı? (Codec'ler anlatmak zorlaştırabilir Log.e spreyleme eğilimindedir.) Hangi renk formatı seçilir? (QCOM formatı mı?) "Ham çerçevenizin" büyüklüğü, giriş arabelleğinin kapasitesi ile tam olarak aynı mı? (Değilse ... neden olmasın?) – fadden

+0

@fadden Ne kadar süre çalışmasına izin verdiğim önemli değil ama her zaman giriş arabelleklerinde 5 kareye sahip gibi görünüyor. Yaratılış üzerine çıktı: 'I/OMXClient (11245): İstemci tarafı OMX mux kullanımı. I/ACodec (11245): setupVideoEncoder başarılı oldu The Seçilen renk formatı her iki durumda da 'MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar' (Tüm biçimleri sorguladığımda, yalnızca iki tane var, yukarıda bahsedilen ve seçiliyse kilitlenen sabit bir 2130708361 olan .) Ham çerçeve ve giriş arabelleği aynı değildir (ham kare boyutu her zaman daha küçüktür ve giriş arabelleği kapasitesi her zaman 282624'tür) – lowtraxx

+0

Beş kare tipiktir - girişleri işlemiyor gibi değil, dolayısıyla çıkış yok. Ben encoder.start() 'diyorsun varsayalım? YUV420SemiPlanar iyidir; 2130708361 sadece Yüzey girişi için kullanılır. YUV420 arabelleğinin boyutu, genişlik * yükseklik * 1.5 'veya 460800 bayt olmalıdır, bu yüzden arabellek boyutunuz hakkında biraz kafam karışmış olur. Günlük dosyasında "Medya Formatı Değişti" mesajını görüyor musunuz, eğer öyleyse, ne diyor? – fadden

cevap

20

Aramanızdaki presentationTimeUs parametresini queueInputBuffer olarak ayarlamanız gerekir. Çoğu kodlayıcı bunu dikkate almaz ve sorunsuz bir şekilde akış için kodlayabilirsiniz. Snapdragon 800 cihazları için kullanılan kodlayıcı çalışmıyor.

Bu parametre çerçevenizin kayıt süresini gösterir ve bu nedenle kodlamak istediğiniz çerçeve ile önceki çerçeve arasındaki sayının artması gerekir.

Parametre kümesi önceki karedeki ile aynı değerdeyse, kodlayıcı onu düşürür. Parametre çok küçük bir değere ayarlanırsa (örneğin 30 FPS kaydıyla 100000), kodlanmış çerçevelerin kalitesi düşer.

+1

Huh. Sunum zamanı damgası, H.264 temel akışının bir parçası değil, bu yüzden beklentim, değerin basitçe geçtiğiydi. Yeni bir SSS girişi ekledim (http://bigflake.com/mediacodec/#q8). – fadden

+0

Sunum süresini nasıl ayarlayacağınıza dair bir örnek verebilir misiniz? –

+0

Aramanızdaki parametresi, Snapdragon 800 cihazları için queueInputBuffer değerine – DreamCoder

0

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

+4

olarak ayarlanmalıdır. Geçerli zamanın kullanılması, gerçek zamanlı giriş alıyorsanız (örn. Kameradan), ancak diğer kaynaklarla çalışıyorsanız (örneğin; Videoyu gerçek zamanlıdan daha hızlı kod dönüştürme). Ayrıca, System.currentTimeMillis() 'a karşı, ani sıçramalara (ileri ve geri) tabi olduğundan öneriyorum. Monotonik 'System.nanoTime()' daha iyi bir kaynaktır. – fadden

+0

Transcode olması durumunda bile, kaynak içeriğin zaman damgaları geçerli olacaktır (kaynak içeriğin fps'sine göre). Encoder, hız kontrolünü yönetebilmek için zaman damgalarını bilmelidir. Dolayısıyla, framerate öğesini mediaFormat.setInteger (MediaFormat.KEY_FRAME_RATE, FPS) ile yapılandırdıysanız, gerçek zamanlı olmayan kodlama için zaman damgalarını (N * 1000 * 1000/FPS) üretmeniz önerilir. – peasea