Compressione video su Android utilizzando la nuova libreria MediaCodec

Nella mia app sto provando a 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.

Ho appena sentito parlare della nuova API MediaCodec introdotta con API 16 (ho provato a farlo con ffmpeg).

Quello che sto facendo ora è il seguente: decodificare prima il video in ingresso usando 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 a posto – lo stesso numero di pacchetti viene scritto e letto da ciascun buffer di input e output, ma il file finale non ha l’aspetto di un file video e non può essere aperto da nessun video player.

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 decodificare / codificare:

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

Qualche suggerimento su cosa sto facendo male?

Non ho trovato troppi esempi per codificare con MediaCodec solo qualche codice di esempio per la decodifica … Grazie mille per l’aiuto

L’output di MediaCodec è un stream elementare non elaborato. È 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 in .mp4 su Android è con la class MediaMuxer , introdotta in Android 4.3 (API 18). Esistono un paio di esempi (EncodeAndMuxTest, CameraToMpegTest) che ne dimostrano l’uso.

Mi sono imbattuto in questa libreria che potrebbe aiutarti a creare il contenitore corretto in modo che i lettori video lo riconoscano: http://code.google.com/p/mp4parser/

Utilizzare la libreria SiliCompressor per risolverlo. Comprimi i video e le immagini, pur mantenendo la qualità dei media.