2013-12-09 15 views
11

Ho scritto un codificatore di flusso H264 utilizzando l'API MediaCodec di Android. L'ho provato su una decina di dispositivi diversi con processori diversi e ha funzionato su tutti, tranne su quelli alimentati con Snapdragon 800 (Google Nexus 5 e Sony Xperia Z1). Su quei dispositivi ottengo SPS e PPS e il primo Keyframe, ma successivamente mEncoder.dequeueOutputBuffer (mBufferInfo, 0) restituisce solo MediaCodec.INFO_TRY_AGAIN_LATER. Ho già sperimentato diversi timeout, bitrate, risoluzioni e altre opzioni di configurazione, senza alcun risultato. Il risultato è sempre lo stesso.MediaCodec H264 Encoder non funziona su dispositivi Snapdragon 800

Io uso il seguente codice per inizializzare il codificatore:

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

in cui il formato del colore selezionato è:

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

e ottengo i dati facendo

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

Grazie per il tuo aiuto!

+1

Quanti frame di input vengono messi in coda nel punto in cui l'output si blocca? (Voglio assicurarmi che non stia semplicemente morendo di fame per input.) C'è qualcosa di sospetto in cerca di logcat? (I codec tendono a spruzzare Log.e, il che può rendere difficile dirlo). Quale formato di colore viene selezionato? (È il formato QCOM?) La dimensione della "cornice grezza" è esattamente uguale alla capacità del buffer di input? (Se no ... perché no?) – fadden

+0

@fadden Non importa per quanto tempo lo lascio girare ma sembra sempre avere 5 frame nei buffer di input. Il suo output al momento della creazione è: 'I/OMXClient (11245): utilizzo di mux OMX lato client. I/ACodec (11245): setupVideoEncoder riuscito' Il formato colore selezionato è in entrambi i casi 'MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar' (Se interrogo tutti i formati ne ha solo due, il sopra e uno che ha una costante 2130708361 che si arresta se selezionato .) Il frame non elaborato e il buffer di input non sono gli stessi (la dimensione raw del frame è sempre minore e la capacità del buffer di input è sempre 282624) – lowtraxx

+0

Cinque frame è tipico - sembra che non sia l'input di elaborazione, quindi nessun output. Suppongo che tu stia chiamando 'encoder.start()'? YUV420SemiPlanar è buono; 2130708361 è utilizzato solo per l'input di superficie. La dimensione di un buffer YUV420 dovrebbe essere "width * height * 1.5', o 460800 byte, quindi sono un po 'confuso riguardo la dimensione del buffer. Vedete il vostro messaggio "Media Format Changed" nel file di log e, in caso affermativo, cosa dice? – fadden

risposta

20

È necessario impostare il parametro presentationTimeUs nella chiamata a queueInputBuffer. La maggior parte degli encoder lo ignorano e puoi codificare per lo streaming senza problemi. Il codificatore utilizzato per i dispositivi Snapdragon 800 no.

Questo parametro rappresenta il tempo di registrazione del frame e deve pertanto aumentare del numero di noi tra il frame che si desidera codificare e il frame precedente.

Se il set di parametri ha lo stesso valore del fotogramma precedente, l'encoder lo rilascia. Se il parametro è impostato su un valore troppo piccolo (ad esempio 100000 su una registrazione a 30 FPS), la qualità dei fotogrammi codificati diminuisce.

+1

Huh. Il timestamp della presentazione non fa parte del flusso elementare H.264, quindi la mia aspettativa era che il valore fosse semplicemente passato. Ho aggiunto una nuova voce FAQ (http://bigflake.com/mediacodec/#q8). – fadden

+0

Potresti fornire un esempio di come impostare il tempo di presentazione? –

+0

Parametro TimeUs nella chiamata al valore queueInputBuffer per i dispositivi Snapdragon 800 – DreamCoder

0

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

+4

L'utilizzo del tempo corrente va bene se si riceve input in tempo reale (ad esempio dalla telecamera), ma funzionerà male se si lavora con altre fonti (ad es. transcodifica video più veloce del tempo reale). Raccomando anche contro 'System.currentTimeMillis()', in quanto soggetto a salti improvvisi (in avanti e all'indietro). Il monotonico 'System.nanoTime()' è una fonte migliore. – fadden

+0

Anche in caso di transcodifica, i timestamp del contenuto sorgente saranno applicabili (in base ai fps del contenuto sorgente). L'encoder deve conoscere i timestamp per essere in grado di gestire il controllo della velocità. Quindi, se hai configurato il framerate con mediaFormat.setInteger (MediaFormat.KEY_FRAME_RATE, FPS), è consigliabile generare i timestamp (N * 1000 * 1000/FPS) per la codifica non in tempo reale. – peasea

Problemi correlati