web-dev-qa-db-fra.com

Compression vidéo sur Android utilisant la nouvelle bibliothèque MediaCodec

Dans mon application, j'essaie de télécharger des vidéos que l'utilisateur a choisies dans la galerie. Le problème est que généralement les fichiers vidéo Android Android sont trop gros pour être téléchargés et donc - nous voulons les compresser d'abord par un débit/résolution inférieur.

Je viens d'entendre parler de la nouvelle MediaCodec api qui introduit avec API 16 (j'ai perversement essayé de le faire avec ffmpeg).

Ce que je fais en ce moment est le suivant: Décodez d'abord la vidéo d'entrée à l'aide d'un décodeur vidéo et configurez-la avec le format qui a été lu à partir du fichier d'entrée. Ensuite, je crée un encodeur vidéo standard avec des paramètres prédéfinis et je l'utilise pour encoder le tampon de sortie du décodeur. Ensuite, j'enregistre le tampon de sortie de l'encodeur dans un fichier.

Tout semble bon - le même nombre de paquets sont écrits et lus à partir de chaque tampon d'entrée et de sortie, mais le fichier final ne ressemble pas à un fichier vidéo et ne peut être ouvert par aucun lecteur vidéo.

On dirait que le décodage est correct, car je le teste en l'affichant sur Surface. Je configure d'abord le décodeur pour qu'il fonctionne avec une surface, et lorsque nous appelons releaseOutputBuffer, nous utilisons l'indicateur de rendu et nous pouvons voir la vidéo à l'écran.

Voici le code que j'utilise:

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

et ce sont la méthode que j'utilise pour décoder/encoder:

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

Une suggestion sur ce que je fais mal?

Je n'ai pas trouvé trop d'exemples d'encodage avec MediaCodec juste quelques exemples de code pour le décodage ... Merci beaucoup pour l'aide

23
shem

La sortie de MediaCodec est un flux élémentaire brut. Vous devez l'empaqueter dans un format de fichier vidéo (éventuellement en muxant l'audio) avant que de nombreux joueurs ne le reconnaissent. FWIW, j'ai constaté que le lecteur de film Totem basé sur GStreamer pour Linux lira les fichiers vidéo/avc "bruts".

Mise à jour: La façon de convertir H.264 en .mp4 sur Android est avec la classe MediaMuxer , introduit dans Android 4.3 (API 18). Il existe quelques exemples (EncodeAndMuxTest, CameraToMpegTest) qui démontrent son utilisation.

5
fadden