使用 MediaCodec 在 Android 上进行硬解码

要使用 MediaCodec 在 Android 上进行硬解码,并获取 RGBA 数据,你可以按照以下步骤进行操作:

创建 MediaExtractor 对象并设置要解码的 MP4 文件路径:

MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(filePath);

根据需要选择音频或视频轨道:

int trackCount = extractor.getTrackCount();
int trackIndex = -1;
for (int i = 0; i < trackCount; i++) {
 MediaFormat format = extractor.getTrackFormat(i);
 String mime = format.getString(MediaFormat.KEY_MIME);
 if (mime.startsWith("video/")) {
 trackIndex = i;
 break;
 }
}
if (trackIndex >= 0) {
 extractor.selectTrack(trackIndex);
}

创建 MediaCodec 对象并配置解码器:

MediaFormat format = extractor.getTrackFormat(trackIndex);
String mime = format.getString(MediaFormat.KEY_MIME);
MediaCodec codec = MediaCodec.createDecoderByType(mime);
codec.configure(format, null, null, 0);
codec.start();

循环解码并获取 RGBA 数据:

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
boolean isEOS = false;
while (!isEOS) {
 int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
 if (inputBufferIndex >= 0) {
 ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
 int sampleSize = extractor.readSampleData(inputBuffer, 0);
 if (sampleSize < 0) {
 codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
 isEOS = true;
 } else {
 long presentationTimeUs = extractor.getSampleTime();
 codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0);
 extractor.advance();
 }
 }
 int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, timeoutUs);
 if (outputBufferIndex >= 0) {
 ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
 // 解码后的数据位于 outputBuffer 中,根据需要进行 RGBA 数据的提取和处理
 // outputBuffer 中的数据格式可能是 YUV 或其他格式,需要根据解码器设置的输出格式进行相应的转换
 codec.releaseOutputBuffer(outputBufferIndex, false);
 } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
 // 解码器输出格式已更改,可以通过 codec.getOutputFormat() 获取新的格式
 }
}

在上述代码中,你需要根据解码器输出的数据格式进行相应的转换,以获取 RGBA 数据。具体的转换流程和代码取决于解码器输出的数据格式和你的需求。

需要注意的是,硬解码的支持和性能可能因设备、Android 版本和视频编码格式的不同而有所差异。确保你的设备支持硬解码,并且适当处理解码器的输入和输出缓冲区。

以下是完整代码:

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;
import java.nio.ByteBuffer;
public class MP4Decoder {
 private static final int TIMEOUT_US = 10000;
 private static final String TAG = "MP4Decoder";
 public interface FrameCallback {
 void onFrameDecoded(byte[] rgbaData, int width, int height);
 }
 public static void decodeMP4(String filePath, FrameCallback callback) {
 new Thread(() -> {
 MediaExtractor extractor = new MediaExtractor();
 try {
 extractor.setDataSource(filePath);
 int trackIndex = selectVideoTrack(extractor);
 if (trackIndex < 0) {
 return;
 }
 MediaFormat format = extractor.getTrackFormat(trackIndex);
 String mime = format.getString(MediaFormat.KEY_MIME);
 MediaCodec codec = MediaCodec.createDecoderByType(mime);
 codec.configure(format, null, null, 0);
 codec.start();
 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
 boolean isEOS = false;
 while (!isEOS) {
 int inputBufferIndex = codec.dequeueInputBuffer(TIMEOUT_US);
 if (inputBufferIndex >= 0) {
 ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
 int sampleSize = extractor.readSampleData(inputBuffer, 0);
 if (sampleSize < 0) {
 codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
 isEOS = true;
 } else {
 long presentationTimeUs = extractor.getSampleTime();
 codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0);
 extractor.advance();
 }
 }
 int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
 if (outputBufferIndex >= 0) {
 ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
 // 解码后的数据位于 outputBuffer 中,根据需要进行 RGBA 数据的提取和处理
 // outputBuffer 中的数据格式可能是 YUV 或其他格式,需要根据解码器设置的输出格式进行相应的转换
 byte[] rgbaData = convertToRGBA(outputBuffer, format);
 int width = format.getInteger(MediaFormat.KEY_WIDTH);
 int height = format.getInteger(MediaFormat.KEY_HEIGHT);
 callback.onFrameDecoded(rgbaData, width, height);
 codec.releaseOutputBuffer(outputBufferIndex, false);
 } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
 // 解码器输出格式已更改,可以通过 codec.getOutputFormat() 获取新的格式
 }
 }
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 extractor.release();
 }
 }).start();
 }
 private static int selectVideoTrack(MediaExtractor extractor) {
 int trackCount = extractor.getTrackCount();
 int trackIndex = -1;
 for (int i = 0; i < trackCount; i++) {
 MediaFormat format = extractor.getTrackFormat(i);
 String mime = format.getString(MediaFormat.KEY_MIME);
 if (mime.startsWith("video/")) {
 trackIndex = i;
 break;
 }
 }
 if (trackIndex >= 0) {
 extractor.selectTrack(trackIndex);
 }
 return trackIndex;
 }
 private static byte[] convertToRGBA(ByteBuffer buffer, MediaFormat format) {
 // 根据解码器设置的输出格式进行 RGBA 数据的提取和处理
 // 这里只是一个示例,实际的转换过程可能会更复杂,取决于输出格式和需求
 int width = format.getInteger(MediaFormat.KEY_WIDTH);
 int height = format.getInteger(MediaFormat.KEY_HEIGHT);
 int remaining = buffer.remaining();
 byte[] rgbaData = new byte[remaining];
 buffer.rewind();
 buffer.get(rgbaData);
 Log.i(TAG, "convertToRGBA: count:" + width + "; " + height + "; " + remaining);
 // todo: 进行 YUV 到 RGBA 的转换
 return rgbaData;
 }
}
作者:future_li原文地址:https://www.cnblogs.com/futureli/p/18153340

%s 个评论

要回复文章请先登录注册