Android利用MediaCodec组件实现音视频编解码功能
作者:天天进步2015
Android MediaCodec是Android平台提供的底层音视频编解码API,它为开发者提供了直接访问设备硬件编解码器的能力,本文将深入探讨MediaCodec的核心概念、使用方法以及在实际开发中的最佳实践
概述
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的能力,构建稳定、高效的音视频应用。
以上就是Android利用MediaCodec组件实现音视频编解码功能的详细内容,更多关于Android音视频解码的资料请关注脚本之家其它相关文章!