如何使用 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