2013-04-11 12 views
18

In meiner App versuche ich, einige Videos hochzuladen, die der Benutzer aus der Galerie ausgewählt hat. Das Problem ist, dass die Androiden-Videodateien normalerweise zu groß sind, um hochgeladen zu werden. Daher möchten wir sie zuerst mit einer niedrigeren Bitrate/Auflösung komprimieren.Videokomprimierung auf Android mit neuer MediaCodec-Bibliothek

Ich habe gerade von der neuen MediaCodec API gehört, die mit API 16 eingeführt wird (ich versuchte es vorher mit ffmpeg).

Was ich gerade mache, ist folgendes: Entschlüsseln Sie zunächst das Eingangsvideo mit einem Video-Decoder und konfigurieren Sie es mit dem Format, das aus der Eingangsdatei gelesen wurde. Als nächstes erstelle ich einen Standard-Videoencoder mit einigen vordefinierten Parametern und benutze ihn zum Codieren des Decoder-Ausgabepuffers. Dann speichere ich den Encoder-Ausgabepuffer in eine Datei.

Alles sieht gut aus - aus jedem Ein- und Ausgabepuffer wird die gleiche Anzahl von Paketen geschrieben und gelesen, aber die finale Datei sieht nicht wie eine Videodatei aus und kann von keinem Videoplayer geöffnet werden.

Sieht aus wie die Dekodierung ist in Ordnung, weil ich es durch die Anzeige auf Surface testen. Ich konfiguriere zuerst den Decoder, um mit einem Surface zu arbeiten, und wenn wir releaseOutputBuffer aufrufen, benutzen wir das Render-Flag, und wir können das Video auf dem Bildschirm sehen.

Hier ist der Code, ich verwende:

//init decoder 
    MediaCodec decoder = MediaCodec.createDecoderByType(mime); 
    decoder.configure(format, null , null , 0); 
    decoder.start(); 
    ByteBuffer[] codecInputBuffers = decoder.getInputBuffers(); 
    ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers(); 

    //init encoder 
    MediaCodec encoder = MediaCodec.createEncoderByType(mime); 
    int width = format.getInteger(MediaFormat.KEY_WIDTH); 
    int height = format.getInteger(MediaFormat.KEY_HEIGHT); 
    MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height); 
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 400000); 
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25); 
    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); 
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); 
    encoder.configure(mediaFormat, null , null , MediaCodec.CONFIGURE_FLAG_ENCODE); 
    encoder.start(); 
    ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers(); 
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); 

    extractor.selectTrack(0); 

    boolean sawInputEOS = false; 
    boolean sawOutputEOS = false; 
    boolean sawOutputEOS2 = false; 
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 
    BufferInfo encoderInfo = new MediaCodec.BufferInfo(); 

    while (!sawInputEOS || !sawOutputEOS || !sawOutputEOS2) { 
     if (!sawInputEOS) { 
      sawInputEOS = decodeInput(extractor, decoder, codecInputBuffers); 
     } 

     if (!sawOutputEOS) { 
      int outputBufIndex = decoder.dequeueOutputBuffer(info, 0); 
      if (outputBufIndex >= 0) { 
       sawOutputEOS = decodeEncode(extractor, decoder, encoder, codecOutputBuffers, encoderInputBuffers, info, outputBufIndex); 
      } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
       Log.d(LOG_TAG, "decoding INFO_OUTPUT_BUFFERS_CHANGED"); 
       codecOutputBuffers = decoder.getOutputBuffers(); 
      } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
       final MediaFormat oformat = decoder.getOutputFormat(); 
       Log.d(LOG_TAG, "decoding Output format has changed to " + oformat); 
      } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { 
       Log.d(LOG_TAG, "decoding dequeueOutputBuffer timed out!"); 
      } 
     } 

     if (!sawOutputEOS2) { 
      int encodingOutputBufferIndex = encoder.dequeueOutputBuffer(encoderInfo, 0); 
      if (encodingOutputBufferIndex >= 0) { 
       sawOutputEOS2 = encodeOuput(outputStream, encoder, encoderOutputBuffers, encoderInfo, encodingOutputBufferIndex); 
      } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
       Log.d(LOG_TAG, "encoding INFO_OUTPUT_BUFFERS_CHANGED"); 
       encoderOutputBuffers = encoder.getOutputBuffers(); 
      } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
       final MediaFormat oformat = encoder.getOutputFormat(); 
       Log.d(LOG_TAG, "encoding Output format has changed to " + oformat); 
      } else if (encodingOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { 
       Log.d(LOG_TAG, "encoding dequeueOutputBuffer timed out!"); 
      } 
     } 
    } 
      //clear some stuff here... 

und das ist die Methode, die ich für dekodieren/kodieren verwenden:

private boolean decodeInput(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecInputBuffers) { 
     boolean sawInputEOS = false; 
     int inputBufIndex = decoder.dequeueInputBuffer(0); 
     if (inputBufIndex >= 0) { 
      ByteBuffer dstBuf = codecInputBuffers[inputBufIndex]; 
      input1count++; 

      int sampleSize = extractor.readSampleData(dstBuf, 0); 
      long presentationTimeUs = 0; 
      if (sampleSize < 0) { 
       sawInputEOS = true; 
       sampleSize = 0; 
       Log.d(LOG_TAG, "done decoding input: #" + input1count); 
      } else { 
       presentationTimeUs = extractor.getSampleTime(); 
      } 

      decoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 
      if (!sawInputEOS) { 
       extractor.advance(); 
      } 
     } 
     return sawInputEOS; 
    } 
    private boolean decodeOutputToFile(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecOutputBuffers, 
      MediaCodec.BufferInfo info, int outputBufIndex, OutputStream output) throws IOException { 
     boolean sawOutputEOS = false; 

     ByteBuffer buf = codecOutputBuffers[outputBufIndex]; 
     if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
      sawOutputEOS = true; 
      Log.d(LOG_TAG, "done decoding output: #" + output1count); 
     } 

     if (info.size > 0) { 
      output1count++; 
      byte[] outData = new byte[info.size]; 
      buf.get(outData); 
      output.write(outData, 0, outData.length); 
     } else { 
      Log.d(LOG_TAG, "no data available " + info.size); 
     } 
     buf.clear(); 
     decoder.releaseOutputBuffer(outputBufIndex, false); 
     return sawOutputEOS; 
    } 

    private boolean encodeInputFromFile(MediaCodec encoder, ByteBuffer[] encoderInputBuffers, MediaCodec.BufferInfo info, FileChannel channel) throws IOException { 
      boolean sawInputEOS = false; 
      int inputBufIndex = encoder.dequeueInputBuffer(0); 
      if (inputBufIndex >= 0) { 
       ByteBuffer dstBuf = encoderInputBuffers[inputBufIndex]; 
       input1count++; 

       int sampleSize = channel.read(dstBuf); 
       if (sampleSize < 0) { 
        sawInputEOS = true; 
        sampleSize = 0; 
        Log.d(LOG_TAG, "done encoding input: #" + input1count); 
       } 

       encoder.queueInputBuffer(inputBufIndex, 0, sampleSize, channel.position(), sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 
      } 
      return sawInputEOS; 
    } 

Jeder Vorschlag auf, was ich falsch mache?

ich zu viel Beispiele für die Codierung mit MediaCodec nicht nur ein paar Proben Code zur Dekodierung ... Vielen Dank für die Hilfe

+1

Sie benötigen keinen MediaCodec. Es ist gefährlich, alleine zu gehen, nehmen Sie dies: http://StackOverflow.com/a/23815402 –

+1

Wie in den Kommentaren erwähnt - ich endete mit ffmpeg – shem

+0

@shem: Haben Sie dieses Problem lösen? Ich arbeite an demselben Problem ohne Erfolg. Wie @fadden stimme ich zu, dass wir die Ausgabe mit einem 'MediaMuxer' formatieren müssen. Bitte zeigen Sie mir ein Beispiel, das eine Datei ('MediaExtractor') liest und ein kodiertes Video einer anderen Größe schreibt (ich benutze API Level 22, also wäre ein asynchrones Beispiel noch besser!) –

Antwort

5

Der Ausgang des MediaCodec ist ein roher Elementarstrom finden. Sie müssen es in ein Video-Datei-Format verpacken (möglicherweise muxen das Audio wieder in), bevor viele Spieler es erkennen. FWIW, ich habe herausgefunden, dass der GStreamer-basierte Totem Movie Player für Linux "rohe" Video/AVC-Dateien abspielen wird.

Update: Die Art und Weise H.264 zu konvertieren Mp4 auf Android mit den MediaMuxer class ist, eingeführt in Android 4.3 (API 18). Es gibt einen couple of examples (EncodeAndMuxTest, CameraToMpegTest), der seine Verwendung demonstriert.

+0

Der rohe Stream ist, was ich nach der Decodierung bekommen, deshalb bin ich auch die Codierung des Videostreams (ich weiß, es ist ohne den Sound-Stream). die verschlüsselte Datei sollte ein normales Video sein, habe ich recht? – shem

+0

Die codierten Daten, die aus einem MediaCodec-Encoder kommen, sind nur der rohe Videoelementarstrom. Sehen Sie sich einen hexadezimalen Dump der Datei an und vergleichen Sie ihn mit der .mp4, die aus MediaRecorder kommt, und Sie werden sofort strukturelle Unterschiede sehen. – fadden

+0

Danke. Wenn ich also ein Video komprimieren möchte, indem ich es dekodiere und es dann mit einer anderen Bitrate/Größe codiere, wie kann ich das mit dem MediaCodec-Framework tun? Ist die Codierung nicht dafür gedacht, den dekodierten Stream in ein abspielbares Format umzuwandeln? Wenn nicht, wie schlägst du vor, ich sollte Videos auf Android komprimieren? – shem

Verwandte Themen