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

我想将我拥有的一组位图编码为 h264。这可以通过 MediaEncoder 实现吗?为了做到这一点,我编写了一些代码,但输出无法在我尝试过的任何媒体播放器中播放。以下是我主要从 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);
}

我应该调整什么?

【问题讨论】:

    标签: android bitmap h.264 android-4.2-jelly-bean video-encoding


    【解决方案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 以下的复用器解决方案感兴趣的人都可以在这里查看:stackoverflow.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");
            }
        }
    

    希望对你有帮助。

    【讨论】:

    【解决方案4】:
    1. 编码器输出为“raw”h264,所以可以设置文件扩展名为“h264”并用mplayer播放,即mplayer ./your_output.h264
    2. 还有一件事:你对编码器说帧将采用 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)。
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 2016-03-12
    • 2014-06-12
    • 1970-01-01
    • 2013-11-18
    • 2014-10-06
    • 2018-07-16
    • 2014-07-21
    相关资源
    最近更新 更多