2016-11-11 6 views
0

Ich mache einen Transcoder mit MediaCodec.Transcode h264 Video von MediaCodec direkt ohne Textur Render

Ich habe zwei Mediacodec Instanz erstellt, eine ist für die Decodierung und eine andere ist für die Codierung. Ich versuche Decodierer outputBuffer direkt in Encoder inputBuffer zu senden.

Es scheint kein Problem beim Kompilieren und Ausführen.Und es läuft schnell. Aber die Ausgabevideodatei hat etwas wrong.I die Metadaten des Video-Ausgang überprüft und sie sind alle richtig: Bitrate, Framerate, Auflösung ... Nur die Bilder in dem Video, wie das ist falsch: screen shot

Ich dachte, es hat etwas falsch, aber ich kann es nicht herausfinden ...

Ich suchte Bibliotheken und Dokumente, und ich fand einige Beispielcodes mit Texture Oberfläche, um den Decoder Ausgabedaten zu rendern und die Daten in den Encoder zu übertragen. Aber ich dachte, das sollte nicht notwendig für mich sein. Weil ich Bilder des Videos nicht bearbeiten muss. Was ich nur tun muss, ist, die Bitrate und Auflösung zu ändern, um die Dateigröße kleiner zu machen.

hier ist der Kern-Code in meinem Projekt:

private void decodeCore() { 
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
    int frameCount = 0; 
    while (mDecodeRunning) { 

     int inputBufferId = mDecoder.dequeueInputBuffer(50); 
     if (inputBufferId >= 0) { 
      // fill inputBuffers[inputBufferId] with valid data 
      int sampleSize = mExtractor.readSampleData(mDecodeInputBuffers[inputBufferId], 0); 
      if (sampleSize >= 0) { 
       long time = mExtractor.getSampleTime(); 
       mDecoder.queueInputBuffer(inputBufferId, 0, sampleSize, time, 0); 
      } else { 
       mDecoder.queueInputBuffer(inputBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      } 

      mExtractor.advance(); 
     } 

     int outputBufferId = mDecoder.dequeueOutputBuffer(bufferInfo, 50); 
     if (outputBufferId >= 0) { 

      FrameData data = mFrameDataQueue.obtain(); 
      //wait until queue has space to push data 
      while (data == null) { 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       data = mFrameDataQueue.obtain(); 
      } 

      data.data.clear(); 
      data.size = 0; 
      data.offset = 0; 
      data.flag = 0; 
      data.frameTimeInUs = bufferInfo.presentationTimeUs; 

      // outputBuffers[outputBufferId] is ready to be processed or rendered. 
      if (bufferInfo.size > 0) { 
       ByteBuffer buffer = mDecodeOutputBuffers[outputBufferId]; 

       buffer.position(bufferInfo.offset); 
       buffer.limit(bufferInfo.offset + bufferInfo.size); 

       data.data.put(buffer); 
       data.data.flip(); 

       data.size = bufferInfo.size; 
       data.frameIndex = frameCount++; 

      } 

      data.flag = bufferInfo.flags; 

      if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
       Log.d("bingbing_transcode", "decode over! frames:" + (frameCount - 1)); 
       mDecodeRunning = false; 
      } 

      mFrameDataQueue.pushToQueue(data); 
      mDecoder.releaseOutputBuffer(outputBufferId, false); 
      Log.d("bingbing_transcode", "decode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
      mDecodeOutputBuffers = mDecoder.getOutputBuffers(); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
      // Subsequent data will conform to new format. 
      mDecodeOutputVideoFormat = mDecoder.getOutputFormat(); 
      configureAndStartEncoder(); 
     } 
    } 

    mDecoder.stop(); 
    mDecoder.release(); 
} 

private void encodeCore() { 
    int trackIndex = 0; 
    boolean muxerStarted = false; 
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
    int frameCount = 0; 
    while (mEncodeRunning) { 
     int inputBufferId = mEncoder.dequeueInputBuffer(50); 
     if (inputBufferId >= 0) { 
      FrameData data = mFrameDataQueue.pollFromQueue(); 
      //wait until queue has space to push data 
      while (data == null) { 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       data = mFrameDataQueue.obtain(); 
      } 

      if (data.size > 0) { 
       ByteBuffer inputBuffer = mEncodeInputBuffers[inputBufferId]; 
       inputBuffer.clear(); 
       inputBuffer.put(data.data); 
       inputBuffer.flip(); 
      } 
      mEncoder.queueInputBuffer(inputBufferId, 0, data.size, data.frameTimeInUs, data.flag); 
      mFrameDataQueue.recycle(data); 
     } 

     int outputBufferId = mEncoder.dequeueOutputBuffer(bufferInfo, 50); 
     if (outputBufferId >= 0) { 
      // outputBuffers[outputBufferId] is ready to be processed or rendered. 
      ByteBuffer encodedData = mEncodeOutputBuffers[outputBufferId]; 

      if (bufferInfo.size > 0) { 
       if (encodedData == null) { 
        throw new RuntimeException("encoderOutputBuffer " + outputBufferId + " was null"); 
       } 

       if (!muxerStarted) { 
        throw new RuntimeException("muxer hasn't started"); 
       } 

       frameCount++; 
      } 
      // adjust the ByteBuffer values to match BufferInfo (not needed?) 
      encodedData.position(bufferInfo.offset); 
      encodedData.limit(bufferInfo.offset + bufferInfo.size); 

      mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo); 

      if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
       Log.d("bingbing_transcode", "encode over! frames:" + (frameCount - 1)); 
       mEncodeRunning = false; 
      } 

      mEncoder.releaseOutputBuffer(outputBufferId, false); 
      Log.d("bingbing_transcode", "encode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size); 

     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
      mEncodeOutputBuffers = mEncoder.getOutputBuffers(); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
      // should happen before receiving buffers, and should only happen once 
      if (muxerStarted) { 
       throw new RuntimeException("format changed twice"); 
      } 

      MediaFormat newFormat = mEncoder.getOutputFormat(); 
      Log.d("bingbing_transcode", "encoder output format changed: " + newFormat); 

      // now that we have the Magic Goodies, start the muxer 
      trackIndex = mMuxer.addTrack(newFormat); 
      mMuxer.start(); 
      muxerStarted = true; 
      mEncodeOutputVideoFormat = newFormat; 
     } 
    } 


    mEncoder.stop(); 
    mEncoder.release(); 
    if (muxerStarted) { 
     mMuxer.stop(); 
     mMuxer.release(); 
    } 
} 

diese beiden in zwei verschiedenen Threads ausgeführt Funktionen.

Framedata ist eine einfache Lagerung des Rahmens ByteBuffer und Rahmen vorhanden Zeit und etwas brauchte

Antwort

0

Wenn ByteBuffer Eingang verwenden, gibt es ein paar Details, die über die Eingangsdaten das Layout nicht definiert sind. Wenn die Breite kein Vielfaches von 16 ist, möchten einige Encoder die Eingabedatenzeilenlänge auf ein Vielfaches von 16 aufgefüllt haben, während andere eine Zeilenlänge gleich der Breite ohne zusätzliche Auffüllung annehmen. Die Android-CTS-Tests (die definieren, welches Verhalten man auf allen Geräten erwarten kann) für das Codieren von Byte-Puffereingaben verwenden absichtlich nur Auflösungen, die ein Vielfaches von 16 sind, da sie unterschiedliche Hardwarehersteller wissen, die dies anders machen. Sie möchten eine bestimmte Handhabung erzwingen.

Sie können im Allgemeinen nicht davon ausgehen, dass der Decoder-Ausgang eine ähnliche Zeilengröße verwenden würde wie der Encoder. Der Dekodierer kann (und tut dies tatsächlich) eine deutlich größere Breite als die tatsächliche Inhaltsgröße zurückgeben und die Felder crop_left/crop_right verwenden, um anzugeben, welche Teile davon tatsächlich sichtbar sein sollen. Wenn der Decoder dies getan hat, können Sie die Daten nicht direkt vom Decoder zum Encoder weiterleiten, es sei denn, Sie kopieren ihn Zeile für Zeile, wobei Sie die tatsächlichen vom Decoder und Encoder verwendeten Zeilengrößen berücksichtigen.

Außerdem können Sie nicht einmal davon ausgehen, dass der Decoder ein ähnliches Pixelformat wie der Encoder verwendet. Viele Qualcomm-Geräte verwenden ein spezielles gekacheltes Pixelformat für die Decoderausgabe, während der Encodereingang normale planare Daten sind. In diesen Fällen müssen Sie eine ziemlich komplexe Logik implementieren, um die Daten zu entschlüsseln, bevor Sie sie in den Encoder einspeisen können.

Die Verwendung einer Texturoberfläche als Zwischenprodukt verbirgt all diese Details. Für Ihren Anwendungsfall klingt es vielleicht nicht völlig notwendig, aber es verdeckt alle Variationen der Pufferformate zwischen Decoder und Encoder.

Verwandte Themen