概述
Android MediaCodec是Android平台提供的底层音视频编解码API,它为开发者提供了直接访问设备硬件编解码器的能力。通过MediaCodec,我们可以高效地处理音频和视频数据的编码与解码操作,实现高性能的多媒体应用。
本文将深入探讨MediaCodec的核心概念、使用方法以及在实际开发中的最佳实践。
MediaCodec架构简介
基本工作原理
MediaCodec采用异步的、基于缓冲区的处理模式。其核心架构包括:
- 输入缓冲区队列(Input Buffer Queue):存放待处理的原始数据
- 输出缓冲区队列(Output Buffer Queue):存放处理后的数据
- 编解码引擎:执行实际的编解码操作
- 回调机制:通知应用程序缓冲区状态变化
状态管理
MediaCodec具有明确的状态机制:
Uninitialized → Configured → Executing → Released
↓ ↓ ↓
Error ←——————————————————————————
视频解码实现
创建和配置解码器
public class VideoDecoder {
private MediaCodec decoder;
private Surface surface;
public void initDecoder(String mimeType, int width, int height, Surface outputSurface) {
try {
// 创建解码器实例
decoder = MediaCodec.createDecoderByType(mimeType);
// 配置MediaFormat
MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
// 设置输出Surface
this.surface = outputSurface;
// 配置解码器
decoder.configure(format, surface, null, 0);
decoder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
异步解码处理
public void startDecoding() {
decoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
// 处理输入缓冲区
ByteBuffer inputBuffer = codec.getInputBuffer(index);
// 从数据源读取数据到inputBuffer
int sampleSize = readSampleData(inputBuffer);
if (sampleSize > 0) {
// 提交输入数据
codec.queueInputBuffer(index, 0, sampleSize,
getCurrentTimestamp(), 0);
} else {
// 数据结束
codec.queueInputBuffer(index, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index,
MediaCodec.BufferInfo info) {
// 处理输出缓冲区
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// 解码完成
handleDecodingComplete();
}
// 释放输出缓冲区(渲染到Surface)
codec.releaseOutputBuffer(index, true);
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
// 错误处理
handleError(e);
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
// 输出格式变化处理
handleFormatChange(format);
}
});
}
视频编码实现
编码器初始化
public class VideoEncoder {
private MediaCodec encoder;
private Surface inputSurface;
private MediaMuxer muxer;
public void initEncoder(String outputPath, int width, int height, int bitRate) {
try {
// 创建编码器
encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
// 配置编码参数
MediaFormat format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
// 配置编码器
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// 获取输入Surface
inputSurface = encoder.createInputSurface();
// 创建MediaMuxer用于输出
muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
encoder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
编码数据处理
private int videoTrackIndex = -1;
private boolean muxerStarted = false;
public void startEncoding() {
encoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
// 对于Surface输入,这个回调通常不使用
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index,
MediaCodec.BufferInfo info) {
ByteBuffer outputBuffer = codec.getOutputBuffer(index);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// 配置数据,通常是SPS/PPS
info.size = 0;
}
if (info.size > 0) {
if (!muxerStarted) {
throw new RuntimeException("Muxer hasn't started");
}
// 调整ByteBuffer位置
outputBuffer.position(info.offset);
outputBuffer.limit(info.offset + info.size);
// 写入媒体数据
muxer.writeSampleData(videoTrackIndex, outputBuffer, info);
}
// 释放输出缓冲区
codec.releaseOutputBuffer(index, false);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// 编码完成
handleEncodingComplete();
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
handleError(e);
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
if (muxerStarted) {
throw new RuntimeException("Format changed twice");
}
// 添加轨道到muxer
videoTrackIndex = muxer.addTrack(format);
muxer.start();
muxerStarted = true;
}
});
}
音频编解码
音频解码示例
public class AudioDecoder {
private MediaCodec audioDecoder;
private MediaExtractor extractor;
public void initAudioDecoder(String filePath) {
try {
extractor = new MediaExtractor();
extractor.setDataSource(filePath);
// 找到音频轨道
int audioTrack = selectAudioTrack(extractor);
if (audioTrack >= 0) {
extractor.selectTrack(audioTrack);
MediaFormat format = extractor.getTrackFormat(audioTrack);
String mimeType = format.getString(MediaFormat.KEY_MIME);
audioDecoder = MediaCodec.createDecoderByType(mimeType);
audioDecoder.configure(format, null, null, 0);
audioDecoder.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private int selectAudioTrack(MediaExtractor extractor) {
int trackCount = extractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mimeType = format.getString(MediaFormat.KEY_MIME);
if (mimeType.startsWith("audio/")) {
return i;
}
}
return -1;
}
}
音频编码示例
public void initAudioEncoder(int sampleRate, int channelCount, int bitRate) {
try {
MediaFormat format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);
format.setInteger(MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
性能优化策略
1. 缓冲区管理
// 合理设置缓冲区大小
private static final int BUFFER_SIZE = 64 * 1024; // 64KB
// 重用ByteBuffer避免频繁分配
private ByteBuffer reusableBuffer = ByteBuffer.allocate(BUFFER_SIZE);
2. 线程优化
// 使用专用线程处理编解码
private HandlerThread codecThread;
private Handler codecHandler;
private void initCodecThread() {
codecThread = new HandlerThread("CodecThread");
codecThread.start();
codecHandler = new Handler(codecThread.getLooper());
}
3. 内存管理
// 及时释放资源
private void releaseResources() {
if (decoder != null) {
decoder.stop();
decoder.release();
decoder = null;
}
if (surface != null) {
surface.release();
surface = null;
}
}
错误处理与调试
常见错误类型
private void handleCodecError(MediaCodec.CodecException e) {
if (e.isTransient()) {
// 暂时性错误,可以重试
Log.w(TAG, "Transient codec error", e);
retryOperation();
} else if (e.isRecoverable()) {
// 可恢复错误,重新配置编解码器
Log.w(TAG, "Recoverable codec error", e);
reconfigureCodec();
} else {
// 致命错误,需要完全重建
Log.e(TAG, "Fatal codec error", e);
recreateCodec();
}
}
调试技巧
- 日志记录:详细记录缓冲区状态和时间戳
- 性能监控:监控帧率、码率和延迟
- 内存检查:使用Profiler检查内存泄漏
最佳实践
1. 选择合适的编解码器
// 检查硬件编解码器支持
private boolean isHardwareAccelerated(String codecName) {
return !codecName.startsWith("OMX.google.");
}
// 优先选择硬件编解码器
private MediaCodec createOptimalDecoder(String mimeType) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
if (codecInfo.isEncoder()) continue;
String[] types = codecInfo.getSupportedTypes();
for (String type : types) {
if (type.equals(mimeType) && isHardwareAccelerated(codecInfo.getName())) {
try {
return MediaCodec.createByCodecName(codecInfo.getName());
} catch (IOException e) {
continue;
}
}
}
}
// 回退到默认编解码器
try {
return MediaCodec.createDecoderByType(mimeType);
} catch (IOException e) {
return null;
}
}
2. 配置参数优化
private MediaFormat createOptimalVideoFormat(int width, int height, int bitRate) {
MediaFormat format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
// 设置关键参数
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
// 设置编码质量
format.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
// 设置颜色格式
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
return format;
}
3. 同步和时间戳管理
private long generatePresentationTime() {
return System.nanoTime() / 1000; // 微秒时间戳
}
private void adjustTimestamp(MediaCodec.BufferInfo info, long baseTime) {
info.presentationTimeUs = info.presentationTimeUs - baseTime;
}
总结
Android MediaCodec是一个强大而灵活的音视频编解码框架,通过合理使用其API可以实现高性能的多媒体应用。关键要点包括:
- 理解异步处理模式:正确处理回调和缓冲区管理
- 优化性能:选择硬件编解码器,合理配置参数
- 错误处理:实现健壮的错误恢复机制
- 资源管理:及时释放编解码器和相关资源
- 线程安全:在合适的线程中执行编解码操作
通过遵循这些最佳实践,开发者可以充分发挥MediaCodec的能力,构建稳定、高效的音视频应用。
1016

被折叠的 条评论
为什么被折叠?



