2013-12-09 9 views
11

Ich habe einen H264 Stream Encoder mit der MediaCodec API von Android geschrieben. Ich habe es auf ungefähr zehn verschiedenen Geräten mit verschiedenen Prozessoren getestet und es hat an allen von ihnen funktioniert, außer bei Snapdragon 800 (Google Nexus 5 und Sony Xperia Z1). Auf diesen Geräten bekomme ich SPS und PPS und das erste Keyframe, aber danach gibt mEncoder.dequeueOutputBuffer (mBufferInfo, 0) nur MediaCodec.INFO_TRY_AGAIN_LATER zurück. Ich habe bereits mit verschiedenen Timeouts, Bitraten, Auflösungen und anderen Konfigurationsoptionen experimentiert, ohne Erfolg. Das Ergebnis ist immer das Gleiche.MediaCodec H264 Encoder funktioniert nicht auf Snapdragon 800 Geräten

Ich verwende den folgenden Code, um den Encoder zu initialisieren:

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

, wo das ausgewählte Farbformat ist:

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

Und ich die Daten erhalten, indem

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

Dank für deine Hilfe!

+1

Wie viele Eingaberahmen werden zu dem Zeitpunkt eingereiht, an dem der Ausgang blockiert? (Ich möchte sicherstellen, dass es nicht nur nach Eingabe hungert.) Gibt es etwas Verdächtiges im Logcat? (Codecs neigen dazu, Log.e zu sprühen, was es schwierig machen kann, zu sagen.) Welches Farbformat wird ausgewählt? (Ist es das QCOM-Format?) Ist die Größe Ihres "rohen Rahmens" genau so groß wie die Kapazität des Eingangspuffers? (Wenn nicht ... warum nicht?) – fadden

+0

@fadden Es spielt keine Rolle, wie lange ich es laufen lasse, aber es scheint immer 5 Rahmen in den Eingabepuffern zu haben. Seine Ausgabe beim Erstellen lautet: 'I/OMXClient (11245): Verwendung des clientseitigen OMX mux. I/ACodec (11245): setupVideoEncoder erfolgreich Das ausgewählte Farbformat ist in beiden Fällen 'MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar' (Wenn ich alle Formate abfrage, hat es nur zwei, das oben genannte und eins, das eine Konstante 2130708361 hat, die abstürzt, falls ausgewählt .) Der Raw-Frame und der Input-Buffer sind nicht identisch (Raw-Frame-Größe ist immer kleiner und die Input-Buffer-Kapazität ist immer 282624) – lowtraxx

+0

Fünf Frames ist typisch - klingt wie es keine Eingabe verarbeitet, daher keine Ausgabe. Ich nehme an, du nennst 'encoder.start()'? YUV420SemiPlanar ist gut; 2130708361 wird nur für den Oberflächeneingang verwendet. Die Größe eines YUV420-Puffers sollte "width * height * 1,5" oder 460800 Bytes sein, daher bin ich etwas verwirrt über Ihre Puffergröße. Wird die Meldung "Medienformat geändert" in der Protokolldatei angezeigt, und wenn ja, was heißt es? – fadden

Antwort

20

Sie müssen den Parameter presentationTimeUs in Ihrem Aufruf von queueInputBuffer setzen. Die meisten Encoder ignorieren dies und Sie können für das Streaming ohne Probleme codieren. Der für Snapdragon 800-Geräte verwendete Encoder funktioniert nicht.

Dieser Parameter repräsentiert die Aufnahmezeit Ihres Frames und muss daher um die Anzahl von uns zwischen dem zu codierenden Frame und dem vorherigen Frame erhöht werden.

Wenn der Parametersatz den gleichen Wert wie im vorherigen Frame hat, fällt der Encoder ab. Wenn der Parameter auf einen zu kleinen Wert eingestellt ist (z. B. 100000 bei einer Aufnahme mit 30 FPS), sinkt die Qualität der codierten Bilder.

+1

Huh. Der Präsentationszeitstempel ist nicht Teil des H.264-Elementarstroms. Daher wurde erwartet, dass der Wert einfach übergeben wurde. Ich habe einen neuen FAQ-Eintrag hinzugefügt (http://bigflake.com/mediacodec/#q8). – fadden

+0

Könnten Sie bitte ein Beispiel dafür geben, wie Sie die Präsentationszeit einstellen? –

+0

TimeUs-Parameter in Ihrem Aufruf des queueInputBuffer-Werts für Snapdragon 800-Geräte – DreamCoder

0

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

+4

Die aktuelle Zeit ist in Ordnung, wenn Sie Echtzeit-Eingaben (z. B. von der Kamera) erhalten, funktioniert aber schlecht, wenn Sie mit anderen Quellen arbeiten (z Transcodieren von Videos schneller als in Echtzeit). Ich würde auch gegen 'System.currentTimeMillis()' empfehlen, da es plötzlichen Sprüngen (vorwärts und rückwärts) unterworfen ist. Die monotonische 'System.nanoTime()' ist eine bessere Quelle. – fadden

+0

Auch bei der Transcodierung gelten die Zeitstempel des Quellinhalts (basierend auf den fps der Quellinhalte). Encoder muss die Zeitstempel kennen, um die Ratensteuerung verwalten zu können. Wenn Sie also die Bildrate mit mediaFormat.setInteger (MediaFormat.KEY_FRAME_RATE, FPS) konfiguriert haben, sollten Sie die Zeitstempel (N * 1000 * 1000/FPS) für Nicht-Echtzeit-Kodierung erstellen. – peasea

Verwandte Themen