2013-04-11 23 views
18

Nella mia app sto cercando di caricare alcuni video che l'utente ha scelto dalla galleria. Il problema è che di solito i file video di Android sono troppo grandi per essere caricati e quindi vogliamo comprimerli prima con un bitrate/una risoluzione più bassi.Compressione video su Android utilizzando la nuova libreria MediaCodec

Ho appena sentito parlare della nuova API MediaCodec che introduce con API 16 (ho tentato invariabilmente di farlo con ffmpeg).

Quello che sto facendo in questo momento è il seguente: Prima decodificare il video di input utilizzando un decodificatore video e configurarlo con il formato letto dal file di input. Successivamente, creo un codificatore video standard con alcuni parametri predefiniti e lo utilizzo per codificare il buffer di uscita del decodificatore. Quindi salvi il buffer di uscita dell'encoder in un file.

Tutto sembra buono - lo stesso numero di pacchetti vengono scritti e letti da ogni buffer di ingresso e uscita, ma il file finale non sembra un file video e non può essere aperto da qualsiasi lettore video.

Sembra che la decodifica sia ok, perché la provo visualizzandola su Surface. Per prima cosa configuro il decoder in modo che funzioni con Surface, e quando chiamiamo releaseOutputBuffer usiamo il flag di rendering e siamo in grado di vedere il video sullo schermo.

Ecco il codice che sto usando:

//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... 

e quelli sono il metodo che uso per codifica/decodifica:

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

Qualsiasi suggerimento su quello che sto facendo di sbagliato?

Non ho trovato troppo esempi per la codifica con MediaCodec solo pochi campioni di codice per la decodifica ... Grazie mille per l'aiuto

+1

Non è necessario MediaCodec. È pericoloso andare da soli, prendere questo: http://stackoverflow.com/a/23815402 –

+1

Come menzionato nei commenti- ho finito per usare ffmpeg – shem

+0

@sem: hai risolto questo problema? Sto lavorando allo stesso problema senza successo. Come @fadden, sono d'accordo sul fatto che dobbiamo formattare l'output con un 'MediaMuxer'. Per favore indicami un esempio che legge un file ('MediaExtractor') e scrive un video codificato di dimensioni diverse (sto usando il livello API 22, quindi un esempio asincrono sarebbe ancora meglio!) –

risposta

5

L'uscita di MediaCodec è un flusso elementare grezzo. È necessario comprimerlo in un formato di file video (eventualmente rimandando l'audio) prima che molti giocatori lo riconoscano. FWIW, ho scoperto che il Totem Movie Player basato su GStreamer per Linux riprodurrà file video/avc "grezzi".

Aggiornamento: Il modo per convertire H.264 a .mp4 su Android è con il MediaMuxer class, introdotto in Android 4.3 (API 18). Esistono couple of examples (EncodeAndMuxTest, CameraToMpegTest) che ne dimostrano l'uso.

+0

Il flusso raw è quello che ottengo dopo la decodifica, ecco perché sto anche codificando il flusso video (so che è senza il flusso audio). il file codificato dovrebbe essere un video normale, ho ragione? – shem

+0

I dati codificati che escono da un codificatore MediaCodec sono solo lo stream elementare video non elaborato. Guarda un dump esadecimale del file e confrontalo con il .mp4 che esce da MediaRecorder e vedrai subito differenze strutturali. – fadden

+0

Grazie. Quindi, se voglio comprimere un video decodificandolo e quindi codificandolo con un altro bit rate/size, come posso farlo con il framework MediaCodec? La codifica non è destinata alla trasformazione del flusso decodificato in un formato riproducibile? In caso contrario, come suggerisci che dovrei comprimere i video su Android? – shem

Problemi correlati