web-dev-qa-db-fra.com

Comment encoder des bitmaps dans une vidéo en utilisant MediaCodec?

Je voudrais encoder un ensemble de bitmaps que j'ai dans un h264. Est-ce possible via MediaEncoder? J'ai écrit du code pour le faire, mais la sortie ne peut être lue dans aucun lecteur multimédia que j'ai essayé. Voici une partie du code que j'ai principalement emprunté à d'autres sources que j'ai trouvées sur Stackoverflow.

mMediaCodec = MediaCodec.createEncoderByType("video/avc");
mMediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
mMediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mMediaCodec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
mInputBuffers = mMediaCodec.getInputBuffers();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); // image is the bitmap
byte[] input = byteArrayOutputStream.toByteArray();

int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = mInputBuffers[inputBufferIndex];
    inputBuffer.clear();
    inputBuffer.put(input);
    mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}

Que dois-je régler?

25
BVB

La sortie de MediaCodec est un flux élémentaire H.264 brut. J'ai trouvé que le lecteur multimédia Totem pour Linux est capable de les lire.

Ce que vous voulez vraiment faire, c'est convertir la sortie en un fichier .mp4. ( Mise à jour: ) Android 4.3 (API 18) a introduit la classe MediaMuxer , qui fournit un moyen de convertir les données brutes (plus un flux audio en option) en un fichier .mp4.

La disposition des données dans le ByteBuffer est, à partir de Android 4.3, dépendante de l'appareil. (En fait, tous les appareils ne prennent pas en charge COLOR_FormatYUV420Planar - certains préfèrent une variante semi-planaire.) Je ne peux pas vous dire la disposition exacte sans connaître votre appareil, mais je peux vous dire qu'il veut des données non compressées, donc passer des données PNG compressées ne fonctionnera pas.

( Mise à jour: ) Android 4.3 permet également la saisie de Surface aux encodeurs MediaCodec, donc tout ce que vous pouvez le rendu avec OpenGL ES peut être enregistré. Pour un exemple, voir l'exemple EncodeAndMuxTest ici .

15
fadden

J'ai modifié le code fourni par abalta pour accepter les bitmaps en temps réel (c'est-à-dire que vous n'avez pas déjà besoin d'avoir les bitmaps enregistrés sur le disque). Il a également une amélioration des performances car vous n'avez pas besoin d'écrire puis de lire les bitmaps du disque. J'ai également augmenté le TIMEOUT_USEC de l'exemple d'origine qui a corrigé certaines erreurs liées au délai d'attente que je rencontrais.

J'espère que cela aide quelqu'un. J'ai passé beaucoup de temps à essayer de le faire sans avoir à intégrer une grande bibliothèque tierce dans mon application (ex ffmpeg), donc j'apprécie vraiment la réponse d'Avalta.

J'utilise rxjava, vous en aurez donc besoin dans les dépendances build.gradle de votre application:

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

Si vous essayez d'écrire sur un stockage externe, vous aurez besoin de l'autorisation de stockage externe définie dans votre manifeste:

<uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />

et activer ou désactiver manuellement l'autorisation dans l'application Paramètres système de votre application, ou ajouter la gestion des demandes d'autorisation pour celle-ci à votre activité.

Et voici la classe:

import Android.graphics.Bitmap;
import Android.media.MediaCodec;
import Android.media.MediaCodecInfo;
import Android.media.MediaCodecList;
import Android.media.MediaFormat;
import Android.media.MediaMuxer;
import Android.util.Log;

import Java.io.File;
import Java.io.IOException;
import Java.nio.ByteBuffer;
import Java.util.Queue;
import Java.util.concurrent.ConcurrentLinkedQueue;
import Java.util.concurrent.CountDownLatch;

import io.reactivex.Completable;
import io.reactivex.Android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

public class BitmapToVideoEncoder {
    private static final String TAG = BitmapToVideoEncoder.class.getSimpleName();

    private IBitmapToVideoEncoderCallback mCallback;
    private File mOutputFile;
    private Queue<Bitmap> mEncodeQueue = new ConcurrentLinkedQueue();
    private MediaCodec mediaCodec;
    private MediaMuxer mediaMuxer;

    private Object mFrameSync = new Object();
    private CountDownLatch mNewFrameLatch;

    private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
    private static int mWidth;
    private static int mHeight;
    private static final int BIT_RATE = 16000000;
    private static final int FRAME_RATE = 30; // Frames per second

    private static final int I_FRAME_INTERVAL = 1;

    private int mGenerateIndex = 0;
    private int mTrackIndex;
    private boolean mNoMoreFrames = false;
    private boolean mAbort = false;

    public interface IBitmapToVideoEncoderCallback {
        void onEncodingComplete(File outputFile);
    }

    public BitmapToVideoEncoder(IBitmapToVideoEncoderCallback callback) {
        mCallback = callback;
    }

    public boolean isEncodingStarted() {
        return (mediaCodec != null) && (mediaMuxer != null) && !mNoMoreFrames && !mAbort;
    }

    public int getActiveBitmaps() {
        return mEncodeQueue.size();
    }

    public void startEncoding(int width, int height, File outputFile) {
        mWidth = width;
        mHeight = height;
        mOutputFile = outputFile;

        String outputFileString;
        try {
            outputFileString = outputFile.getCanonicalPath();
        } catch (IOException e) {
            Log.e(TAG, "Unable to get path for " + outputFile);
            return;
        }

        MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
        if (codecInfo == null) {
            Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
            return;
        }
        Log.d(TAG, "found codec: " + codecInfo.getName());
        int colorFormat;
        try {
            colorFormat = selectColorFormat(codecInfo, MIME_TYPE);
        } catch (Exception e) {
            colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
        }

        try {
            mediaCodec = MediaCodec.createByCodecName(codecInfo.getName());
        } catch (IOException e) {
            Log.e(TAG, "Unable to create MediaCodec " + e.getMessage());
            return;
        }

        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL);
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();
        try {
            mediaMuxer = new MediaMuxer(outputFileString, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        } catch (IOException e) {
            Log.e(TAG,"MediaMuxer creation failed. " + e.getMessage());
            return;
        }

        Log.d(TAG, "Initialization complete. Starting encoder...");

        Completable.fromAction(() -> encode())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe();
    }

    public void stopEncoding() {
        if (mediaCodec == null || mediaMuxer == null) {
            Log.d(TAG, "Failed to stop encoding since it never started");
            return;
        }
        Log.d(TAG, "Stopping encoding");

        mNoMoreFrames = true;

        synchronized (mFrameSync) {
            if ((mNewFrameLatch != null) && (mNewFrameLatch.getCount() > 0)) {
                mNewFrameLatch.countDown();
            }
        }
    }

    public void abortEncoding() {
        if (mediaCodec == null || mediaMuxer == null) {
            Log.d(TAG, "Failed to abort encoding since it never started");
            return;
        }
        Log.d(TAG, "Aborting encoding");

        mNoMoreFrames = true;
        mAbort = true;
        mEncodeQueue = new ConcurrentLinkedQueue(); // Drop all frames

        synchronized (mFrameSync) {
            if ((mNewFrameLatch != null) && (mNewFrameLatch.getCount() > 0)) {
                mNewFrameLatch.countDown();
            }
        }
    }

    public void queueFrame(Bitmap bitmap) {
        if (mediaCodec == null || mediaMuxer == null) {
            Log.d(TAG, "Failed to queue frame. Encoding not started");
            return;
        }

        Log.d(TAG, "Queueing frame");
        mEncodeQueue.add(bitmap);

        synchronized (mFrameSync) {
            if ((mNewFrameLatch != null) && (mNewFrameLatch.getCount() > 0)) {
                mNewFrameLatch.countDown();
            }
        }
    }

    private void encode() {

        Log.d(TAG, "Encoder started");

        while(true) {
            if (mNoMoreFrames && (mEncodeQueue.size() ==  0)) break;

            Bitmap bitmap = mEncodeQueue.poll();
            if (bitmap ==  null) {
                synchronized (mFrameSync) {
                    mNewFrameLatch = new CountDownLatch(1);
                }

                try {
                    mNewFrameLatch.await();
                } catch (InterruptedException e) {}

                bitmap = mEncodeQueue.poll();
            }

            if (bitmap == null) continue;

            byte[] byteConvertFrame = getNV21(bitmap.getWidth(), bitmap.getHeight(), bitmap);

            long TIMEOUT_USEC = 500000;
            int inputBufIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
            long ptsUsec = computePresentationTime(mGenerateIndex, FRAME_RATE);
            if (inputBufIndex >= 0) {
                final ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufIndex);
                inputBuffer.clear();
                inputBuffer.put(byteConvertFrame);
                mediaCodec.queueInputBuffer(inputBufIndex, 0, byteConvertFrame.length, ptsUsec, 0);
                mGenerateIndex++;
            }
            MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
            int encoderStatus = mediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
            if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // no output available yet
                Log.e(TAG, "No output from encoder available");
            } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // not expected for an encoder
                MediaFormat newFormat = mediaCodec.getOutputFormat();
                mTrackIndex = mediaMuxer.addTrack(newFormat);
                mediaMuxer.start();
            } else if (encoderStatus < 0) {
                Log.e(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
            } else if (mBufferInfo.size != 0) {
                ByteBuffer encodedData = mediaCodec.getOutputBuffer(encoderStatus);
                if (encodedData == null) {
                    Log.e(TAG, "encoderOutputBuffer " + encoderStatus + " was null");
                } else {
                    encodedData.position(mBufferInfo.offset);
                    encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
                    mediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
                    mediaCodec.releaseOutputBuffer(encoderStatus, false);
                }
            }
        }

        release();

        if (mAbort) {
            mOutputFile.delete();
        } else {
            mCallback.onEncodingComplete(mOutputFile);
        }
    }

    private void release() {
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec = null;
            Log.d(TAG,"RELEASE CODEC");
        }
        if (mediaMuxer != null) {
            mediaMuxer.stop();
            mediaMuxer.release();
            mediaMuxer = null;
            Log.d(TAG,"RELEASE MUXER");
        }
    }

    private static MediaCodecInfo selectCodec(String mimeType) {
        int numCodecs = MediaCodecList.getCodecCount();
        for (int i = 0; i < numCodecs; i++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            if (!codecInfo.isEncoder()) {
                continue;
            }
            String[] types = codecInfo.getSupportedTypes();
            for (int j = 0; j < types.length; j++) {
                if (types[j].equalsIgnoreCase(mimeType)) {
                    return codecInfo;
                }
            }
        }
        return null;
    }

    private static int selectColorFormat(MediaCodecInfo codecInfo,
                                         String mimeType) {
        MediaCodecInfo.CodecCapabilities capabilities = codecInfo
                .getCapabilitiesForType(mimeType);
        for (int i = 0; i < capabilities.colorFormats.length; i++) {
            int colorFormat = capabilities.colorFormats[i];
            if (isRecognizedFormat(colorFormat)) {
                return colorFormat;
            }
        }
        return 0; // not reached
    }

    private static boolean isRecognizedFormat(int colorFormat) {
        switch (colorFormat) {
            // these are the formats we know how to handle for
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                return true;
            default:
                return false;
        }
    }

    private byte[] getNV21(int inputWidth, int inputHeight, Bitmap scaled) {

        int[] argb = new int[inputWidth * inputHeight];

        scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);

        byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];
        encodeYUV420SP(yuv, argb, inputWidth, inputHeight);

        scaled.recycle();

        return yuv;
    }

    private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
        final int frameSize = width * height;

        int yIndex = 0;
        int uvIndex = frameSize;

        int a, R, G, B, Y, U, V;
        int index = 0;
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {

                a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
                R = (argb[index] & 0xff0000) >> 16;
                G = (argb[index] & 0xff00) >> 8;
                B = (argb[index] & 0xff) >> 0;


                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;


                yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
                if (j % 2 == 0 && index % 2 == 0) {
                    yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
                    yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));

                }

                index++;
            }
        }
    }

    private long computePresentationTime(long frameIndex, int framerate) {
        return 132 + frameIndex * 1000000 / framerate;
    }
}

L'utilisation est quelque chose comme:

BitmapToVideoEncoder bitmapToVideoEncoder = new BitmapToVideoEncoder(new IBitmapToVideoEncoderCallback() {
    @Override
    public void onEncodingComplete(File outputFile) {
        Toast.makeText(this,  "Encoding complete!", Toast.LENGTH_LONG).show();
    }
});

bitmapToVideoEncoder.startEncoding(getWidth(), getHeight(), new File("some_path"));
bitmapToVideoEncoder.queueFrame(bitmap1);
bitmapToVideoEncoder.queueFrame(bitmap2);
bitmapToVideoEncoder.queueFrame(bitmap3);
bitmapToVideoEncoder.queueFrame(bitmap4);
bitmapToVideoEncoder.queueFrame(bitmap5);
bitmapToVideoEncoder.stopEncoding();

Et si votre enregistrement est interrompu (par exemple, l'activité est en pause), vous pouvez abandonner et il supprimera le fichier (car il serait de toute façon corrompu). Sinon, il suffit d'appeler stopEncoding et il fermera correctement le fichier afin qu'il ne soit pas corrompu:

bitmapToVideoEncoder.abortEncoding();

Il existe également une fonction getActiveBitmaps () pour voir la taille de la file d'attente (si la file d'attente devient trop grande, vous pouvez manquer de mémoire). Voici également du code pour créer efficacement une image bitmap à partir d'une vue afin que vous puissiez la mettre en file d'attente (mon application prend des captures d'écran périodiques et les encode dans une vidéo):

View view = some_view;
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
        Bitmap.Config.ARGB_8888);

// Create a handler thread to offload the processing of the image.
final HandlerThread handlerThread = new HandlerThread("PixelCopier");
handlerThread.start();

PixelCopy.request(view, bitmap, (copyResult) -> {
    bitmapToVideoEncoder.queueFrame(bitmap);
}, new Handler(handlerThread.getLooper()));
13
OldSchool4664

J'ai utilisé les étapes suivantes pour convertir mes bitmaps en fichier vidéo.

Étape 1: Préparation

J'ai préparé un encodeur comme celui-ci. J'utilise MediaMuxer pour créer un fichier mp4.

    private void prepareEncoder() {
    try {
        mBufferInfo = new MediaCodec.BufferInfo();

        mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
        mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Lollipop) {
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        }else{
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        }
        //2130708361, 2135033992, 21
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);

        final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, 1);
        audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
        audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);

        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();

        mediaCodecForAudio = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
        mediaCodecForAudio.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodecForAudio.start();

        try {
            String outputPath = new File(Environment.getExternalStorageDirectory(),
                    "test.mp4").toString();
            mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        } catch (IOException ioe) {
            throw new RuntimeException("MediaMuxer creation failed", ioe);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Étape 2: mise en mémoire tampon

J'ai créé runnable pour la mise en mémoire tampon.

private void bufferEncoder() {
        runnable = new Runnable() {
            @Override
            public void run() {
                prepareEncoder();
                try {
                    while (mRunning) {
                        encode();
                    }
                    encode();
                } finally {
                    release();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }

Étape 3: encodage

C'est la partie la plus importante que vous avez ratée. Dans cette partie, j'ai préparé un tampon d'entrée avant la sortie. Lorsque les tampons d'entrée sont mis en file d'attente, les tampons de sortie sont prêts à être codés.

public void encode() {
            while (true) {
                if (!mRunning) {
                    break;
                }
                int inputBufIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
                long ptsUsec = computePresentationTime(generateIndex);
                if (inputBufIndex >= 0) {
                    Bitmap image = loadBitmapFromView(captureImageView);
                    image = Bitmap.createScaledBitmap(image, WIDTH, HEIGHT, false);
                    byte[] input = getNV21(WIDTH, HEIGHT, image);
                    final ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufIndex);
                    inputBuffer.clear();
                    inputBuffer.put(input);
                    mediaCodec.queueInputBuffer(inputBufIndex, 0, input.length, ptsUsec, 0);
                    generateIndex++;
                }
                int encoderStatus = mediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // no output available yet
                    Log.d("CODEC", "no output from encoder available");
                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    // not expected for an encoder
                    MediaFormat newFormat = mediaCodec.getOutputFormat();
                    mTrackIndex = mediaMuxer.addTrack(newFormat);
                    mediaMuxer.start();
                } else if (encoderStatus < 0) {
                    Log.i("CODEC", "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
                } else if (mBufferInfo.size != 0) {
                    ByteBuffer encodedData = mediaCodec.getOutputBuffer(encoderStatus);
                    if (encodedData == null) {
                        Log.i("CODEC", "encoderOutputBuffer " + encoderStatus + " was null");
                    } else {
                        encodedData.position(mBufferInfo.offset);
                        encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
                        mediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
                        mediaCodec.releaseOutputBuffer(encoderStatus, false);
                    }
                }
            }
        }
    }

Étape 4: libération

Enfin, si nous avons terminé l'encodage, libérez le multiplexeur et l'encodeur.

private void release() {
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec = null;
            Log.i("CODEC", "RELEASE CODEC");
        }
        if (mediaMuxer != null) {
            mediaMuxer.stop();
            mediaMuxer.release();
            mediaMuxer = null;
            Log.i("CODEC", "RELEASE MUXER");
        }
    }

J'espère que cela vous aidera.

6
abalta
  1. La sortie des encodeurs est "brute" h264, vous pouvez donc définir l'extension du nom de fichier sur "h264" et la lire avec mplayer, c'est-à-dire mplayer ./your_output.h264
  2. Encore une chose: vous dites à l'encodeur que le cadre sera au format de couleur COLOR_FormatYUV420Planar mais il semble que vous lui donniez du contenu PNG, donc le fichier de sortie contiendra probablement un désordre de couleur. Je pense que vous devez convertir PNG en yuv420 (avec cela, par exemple, https://code.google.com/p/libyuv/ ) avant de le charger dans l'encodeur.
5
user2399321