2013-12-09 19 views
11

Napisałem H264 Encoder Stream pomocą MediaCodec API Androida. Przetestowałem go na około dziesięciu różnych urządzeniach z różnymi procesorami i działał na nich wszystkich, z wyjątkiem zasilanych Snapdragonem 800 (Google Nexus 5 i Sony Xperia Z1). Na tych urządzeniach uzyskać SPS i PPS oraz pierwszą klatkę kluczową, ale po tym mEncoder.dequeueOutputBuffer (mBufferInfo, 0) zwraca tylko MediaCodec.INFO_TRY_AGAIN_LATER. Eksperymentowałem już z różnymi limitami czasu, bitraty, rozdzielczości i innymi opcjami konfiguracyjnymi, bezskutecznie. Wynik jest zawsze taki sam.MediaCodec H264 Enkoder nie działa na Snapdragon 800 urządzeń

używam następujący kod do zainicjowania Encoder:

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

wybranego formatu kolor jest:

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

I uzyskać dane wykonując

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

Thanks za pomoc!

+1

Ile wejściowe klatki są w kolejce w momencie gdy stragany wyjściowe? (Chcę się upewnić, że nie jest to po prostu głód w celu wprowadzenia.) Czy jest coś podejrzanie wyglądającego w logcat? (Kodeki mają tendencję do rozpylania Log.e, co może utrudniać określenie.) Jaki format koloru jest wybierany? (Czy jest to format QCOM?) Czy rozmiar "surowej ramki" jest dokładnie taki sam jak pojemność bufora wejściowego? (Jeśli nie ... dlaczego nie?) – fadden

+0

@fadden Nie ma znaczenia, jak długo pozwalam, aby działał, ale zawsze wydaje się mieć 5 ramek w buforze wejściowym. Jego wyjście podczas tworzenia to: 'I/OMXClient (11245): Używanie MUX-ów OMX po stronie klienta. I/ACodec (11245): setupVideoEncoder powiodło się "Wybrany format koloru jest w obu przypadkach" MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar' (Jeśli wyszukuję wszystkie formaty, ma tylko dwa, wyżej wymienione i taki, który ma stałą 2130708361, która ulega awarii, jeśli została wybrana .) Surowa ramka i bufor wejściowy nie są takie same (surowy rozmiar ramki jest zawsze mniejszy, a pojemność bufora wejściowego zawsze wynosi 282624). – lowtraxx

+0

Pięć ramek jest typowe - brzmi, jakby to nie było przetwarzanie danych wejściowych, a więc brak danych wyjściowych. Zakładam, że nazywasz 'encoder.start()'? YUV420SemiPlanar jest dobry; 2130708361 jest używany tylko do wprowadzania powierzchni. Rozmiar bufora YUV420 powinien wynosić "szerokość * wysokość * 1,5" lub 460800 bajtów, więc jestem nieco zdezorientowany z powodu rozmiaru bufora. Czy widzisz komunikat "Format zmieniono format multimediów" w pliku dziennika, a jeśli tak, to co on mówi? – fadden

Odpowiedz

20

trzeba ustawić parametr presentationTimeUs w wywołaniu queueInputBuffer. Większość koderów ignoruje to i można kodować strumieniowo bez problemów. Koder używany w urządzeniach Snapdragon 800 nie.

Parametr ten określa czas nagrywania ramki i dlatego musi wzrosnąć o liczbie nas między ramą, który chcesz kodować i poprzedniej ramki.

Jeśli zestaw parametrów ma taką samą wartość jak w poprzedniej ramce koder upuszcza. Jeśli parametr jest ustawiony na zbyt małą wartość (na przykład 100000 na nagranie 30 FPS), jakość kodowanych ramek spada.

+1

Huh. Znacznik czasu prezentacji nie jest częścią podstawowego strumienia H.264, więc oczekiwałem, że wartość została po prostu przekazana. Dodałem nowy wpis w FAQ (http://bigflake.com/mediacodec/#q8). – fadden

+0

Czy możesz podać przykładowy sposób ustawienia czasu prezentacji? –

+0

Parametr TimeUs w wywołaniu wartości queueInputBuffer dla urządzeń Snapdragon 800 – DreamCoder

0

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

+4

Korzystanie z aktualnego czasu jest w porządku, jeśli odbierasz w czasie rzeczywistym dane wejściowe (np. Z aparatu), ale będzie działać słabo, jeśli pracujesz z innymi źródłami (np. transkodować wideo szybciej niż w czasie rzeczywistym). Polecam również przeciwko 'System.currentTimeMillis()', ponieważ jest on poddawany nagłym skokom (do przodu i do tyłu). Monotoniczne 'System.nanoTime()' jest lepszym źródłem. – fadden

+0

Nawet w przypadku transkodowania będą stosowane znaczniki czasu zawartości źródłowej (w oparciu o fps treści źródłowej). Koder musi znać znaczniki czasu, aby móc zarządzać stopą. Tak więc, jeśli skonfigurowałeś framerate za pomocą mediaFormat.setInteger (MediaFormat.KEY_FRAME_RATE, FPS), zalecane jest generowanie znaczników czasu (N * 1000 * 1000/FPS) dla kodowania innego niż w czasie rzeczywistym. – peasea