如何使用 MediaCodec 将位图编码为视频?

Posted

技术标签:

【中文标题】如何使用 MediaCodec 将位图编码为视频?【英文标题】:How to encode Bitmaps into a video using MediaCodec? 【发布时间】:2013-06-10 09:48:30 【问题描述】:

我想将我拥有的一组位图编码为 h264。这可以通过 MediaEncoder 实现吗?为了做到这一点,我编写了一些代码,但输出无法在我尝试过的任何媒体播放器中播放。以下是我主要从 *** 上找到的其他来源借用的一些代码。

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

我应该调整什么?

【问题讨论】:

【参考方案1】:

我已修改 abalta 提供的代码以实时接受位图(即您不需要将位图保存到光盘)。它还具有性能改进,因为您不需要从磁盘写入然后读取位图。我还增加了原始示例中的 TIMEOUT_USEC,它修复了我遇到的一些与超时相关的错误。

希望这对某人有所帮助。我花了很长时间尝试做到这一点,而不必将大型第三方库打包到我的应用程序(例如 ffmpeg)中,所以我非常感谢 abalta 的回答。

我正在使用 rxjava,因此您需要在应用的 build.gradle 依赖项中使用它:

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

如果您尝试写入外部存储,则需要清单中定义的外部存储权限:

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

并在系统设置应用中为您的应用手动切换权限,或将权限请求处理添加到您的活动中。

这是课程:

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;
    

用法类似于:

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();

如果您的录制被中断(前活动正在暂停),您可以中止,它会删除文件(因为无论如何它都会损坏)。或者,只需调用 stopEncoding ,它就会正确关闭文件,以免损坏:

bitmapToVideoEncoder.abortEncoding();

还有一个 getActiveBitmaps() 函数可以查看队列有多大(如果队列变大,您可能会耗尽内存)。这里还有一些代码可以有效地从视图创建位图,以便您可以将其排队(我的应用程序会定期截取屏幕截图并将它们编码成视频):

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()));

【讨论】:

如果 mNewFrameLatch 为空,那么我认为编码器没有成功启动。我更改了一些日志记录以帮助您调试。如果一切正常,您应该在 logcat 中看到“初始化完成。正在启动编码器...”和“编码器已启动”。如果您没有看到这些消息,则可能有一条错误消息可以为您提供有关问题的更多详细信息。确保您提供给 startEncoding 的 outputFile 是可写的。 锁存器的目的是允许您调用 stopEncoding(),即使在正在处理的队列中仍然可能有位图。它首先完成位图处理,然后完成视频。您是否偶然处理了非常少量的帧?或者您可能已经在内存中有一堆位图,所以您正在非常快速地调用 stopEncoding?如果是这样,可能会在它有机会初始化锁存器之前调用 stopEncoding()。 更正:闩锁用于使编码器线程阻塞并等待,以防您没有尽可能快地输入位图(当我使用此代码时就是这种情况)。如果您可以足够快地输入位图,则永远不需要初始化锁存器并导致崩溃。我修改了代码以对锁存器进行空检查。我还从它正在刷新队列的 stopEncoding() 中删除了一行;这是从我不应该拥有的 abortEncoding() 复制过来的。 酷!我很高兴它对你有用。最后一帧很好,我正在编码更长的视频,所以没有注意到。 感谢您的精彩课程。编码几秒钟后出现此错误:io.reactivex.exceptions.OnErrorNotImplementedException: Can't call getPixels() on a回收位图在 io.reactivex.internal.observers.EmptyCompletableObserver.onError(EmptyCom【参考方案2】:

MediaCodec 的输出是原始 H.264 基本流。我发现适用于 Linux 的 Totem 媒体播放器能够播放它们。

您真正想做的是将输出转换为 .mp4 文件。 (更新:)Android 4.3 (API 18) 引入了 MediaMuxer 类,它提供了一种将原始数据(加上可选的音频流)转换为 .mp4 文件的方法。

从 Android 4.3 开始,ByteBuffer 中的数据布局取决于设备。 (事实上​​,并不是所有的设备都支持COLOR_FormatYUV420Planar——有些人更喜欢半平面的变体。)在不知道你的设备的情况下我不能告诉你确切的布局,但我可以告诉你它需要未压缩的数据,所以传入压缩的 PNG 数据不起作用。

更新:)Android 4.3 还允许 Surface 输入到MediaCodec 编码器,因此可以记录任何可以使用 OpenGL ES 渲染的内容。有关示例,请参阅 EncodeAndMuxTest 示例 here。

【讨论】:

我可以将输出移动到 pc 并在那里将其编码为 mp4 吗?有什么方法可以为所有兼容的 Android 设备普遍确定正确的输入格式? 我相信您可以在设备外进行转换,但我没有具体的程序可以推荐。从 Android 4.2 开始,您可以查询编解码器以了解它支持的格式,但并非所有设备都以相同的方式处理格式(例如,某些设备对色度数据的开头有对齐限制)。 Android 4.3 添加了 Surface 输入和MediaMuxer 转换为 .mp4。答案已更新。 您需要查询编解码器以获取支持的格式列表,并使用其中一种。从 Android 4.3 开始,所有设备都必须支持 YUV420 平面或半平面。我从未见过来自ByteBuffer 的支持RGB 输入。您可以在bigflake.com/mediacodec/#EncodeDecodeTest 的缓冲区到缓冲区或缓冲区到表面测试中看到这是如何完成的。 任何对 4.3 以下的复用器解决方案感兴趣的人都可以在这里查看:***.com/a/23000620/727768【参考方案3】:

我使用以下步骤将位图转换为视频文件。

第 1 步:准备

我已经准备了这样的编码器。我使用 MediaMuxer 创建一个 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();
    

第 2 步:缓冲

我已经为缓冲创建了 runnable。

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();
    

第 3 步:编码

这是您错过的最重要的部分。在这一部分中,我在输出之前准备了输入缓冲区。当输入缓冲区排队时,输出缓冲区已准备好进行编码。

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

第 4 步:发布

最后,如果我们完成编码,则释放复用器和编码器。

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");
        
    

希望对你有帮助。

【讨论】:

感谢您的详细解答! 我对答案中未包含的一些方法感到有些困惑,例如computePresentationTime()。如果它可以帮助任何人,看起来这个答案中的代码部分基于这个开源代码:android.googlesource.com/platform/cts/+/kitkat-release/tests/… @RafaelLima,我知道。我想解释一下使用 MediaMuxer 的概念。 @abalta :我不了解 MediaCodec。 GitHub 上有没有演示代码? 嗨@ravi152,Github 上有一些演示。试试这些链接; github.com/taehwandev/MediaCodecExamplegithub.com/cedricfung/MediaCodecDemogithub.com/PhilLab/Android-MediaCodec-Examples/blob/master/…【参考方案4】:
    编码器输出为“raw”h264,所以可以设置文件扩展名为“h264”并用mplayer播放,即mplayer ./your_output.h264 还有一件事:你对编码器说帧将采用 COLOR_FormatYUV420Planar 颜色格式,但看起来你给了他 PNG 内容,所以输出文件可能包含颜色混乱。我认为您应该先将 PNG 转换为 yuv420(例如,https://code.google.com/p/libyuv/),然后再将其提供给编码器。

【讨论】:

是否支持其他格式?如果我想在其中放入原始位图字节,我会使用什么? 您可以在开发者文档developer.android.com/reference/android/media/… 中找到支持的格式。但在将编码器配置为使用某种颜色格式之前,您需要迭代所有编解码器并检查它们的功能(使用此developer.android.com/reference/android/media/…)。 如果编码器确实支持 COLOR_Format32bitARGB8888 或 COLOR_Format32bitBGRA8888 你可以使用 PNG 内容(虽然我从来没有遇到过这样的设备)。否则,图像应该从 PNG 转换为某种 yuv。如果转换不是时间关键过程,您可以编写自己的 java 代码,但您应该处理 yuv 内容(这里是起点 - fourcc.org/yuv.php)。

以上是关于如何使用 MediaCodec 将位图编码为视频?的主要内容,如果未能解决你的问题,请参考以下文章

Android 多媒体应用使用MediaCodec将摄像头采集的视频编码为h264

如何在 MediaCodec 编码器和 CameraX 之间共享 Surface

研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

使用 MediaCodec 和 Surface 进行 Android 编码