1. MediaRecorder.prepare方法深度解析
在Android多媒体开发中,MediaRecorder是处理音视频录制的核心类,而prepare()方法则是整个录制流程中的关键环节。这个看似简单的方法调用背后,隐藏着复杂的系统级交互和状态管理机制。
1.1 prepare方法的核心作用
prepare()方法的主要职责是将MediaRecorder对象从"初始状态"过渡到"准备就绪状态"。这个过程中会完成以下关键操作:
- 参数校验与绑定:验证setAudioSource/setVideoSource等设置的参数有效性
- 原生层对象创建:通过JNI调用创建底层media_recorder对象
- 文件描述符绑定:建立与输出文件的关联(setOutputFile)
- 编码器初始化:根据setAudioEncoder/setVideoEncoder配置初始化编码器
- 缓冲区分配:为音频/视频数据流分配环形缓冲区
重要提示:prepare()必须在配置完所有参数后调用,且必须在start()之前调用。错误的调用顺序会导致IllegalStateException。
1.2 典型调用时序分析
标准的MediaRecorder使用流程如下:
java复制MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(PATH_NAME);
recorder.prepare(); // 关键转折点
recorder.start(); // 开始录制
这个看似线性的流程在Framework层实际上经历了复杂的异步处理:
- Java层prepare()调用通过JNI进入native层
- 创建MediaRecorderClient对象(libmedia服务)
- 与MediaPlayerService建立IPC连接
- 初始化StagefrightRecorder组件
- 配置OMX编码器组件
- 返回准备状态到应用层
2. 框架层调用流程详解
2.1 Java到Native的跨越
当Java层的prepare()被调用时,实际执行的是android_media_MediaRecorder.cpp中的android_media_MediaRecorder_prepare():
cpp复制// frameworks/base/media/jni/android_media_MediaRecorder.cpp
static void android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) {
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
process_media_recorder_call(env, mr->prepare(), "java/lang/RuntimeException", "prepare failed.");
}
这个JNI桥接层做了三件关键事情:
- 通过env->GetIntField获取Java对象关联的native对象指针
- 调用native MediaRecorder的prepare()方法
- 检查返回状态并处理异常
2.2 MediaService的IPC交互
native层的prepare()实现位于libmedia中:
cpp复制// frameworks/av/media/libmedia/mediarecorder.cpp
status_t MediaRecorder::prepare() {
ALOGV("prepare");
Mutex::Autolock lock(mLock);
return prepare_l();
}
prepare_l()的私有方法会通过Binder调用MediaPlayerService:
cpp复制status_t MediaRecorder::prepare_l() {
if (mMediaRecorder == NULL) {
return NO_INIT;
}
return mMediaRecorder->prepare();
}
这里mMediaRecorder是IMediaRecorder接口,实际对应的是MediaRecorderClient对象。这个跨进程调用会进入MediaPlayerService:
cpp复制// frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp
status_t MediaRecorderClient::prepare() {
ALOGV("prepare");
return mRecorder->prepare();
}
2.3 StagefrightRecorder的初始化
最终调用落到StagefrightRecorder组件:
cpp复制// frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::prepare() {
// 检查状态机
if (mCurrentState != INITIALIZED) {
return INVALID_OPERATION;
}
// 创建MediaWriter
status_t status = setupMediaSource();
if (status != OK) return status;
// 初始化编码器
status = setupVideoEncoder();
if (status != OK) return status;
status = setupAudioEncoder();
if (status != OK) return status;
mCurrentState = PREPARED;
return OK;
}
这个过程中有几个关键点值得注意:
- 状态机验证:必须处于INITIALIZED状态才能prepare
- MediaSource创建:根据输入源(摄像头/麦克风)创建数据管道
- 编码器懒加载:视频和音频编码器是分别初始化的
- 状态转换:成功后才切换到PREPARED状态
3. 实战中的关键问题与解决方案
3.1 典型异常处理
在实际开发中,prepare()调用可能抛出多种异常:
-
IllegalStateException:
- 原因:状态机错误(如重复prepare)
- 解决方案:检查调用顺序,确保状态转换正确
-
IOException:
- 原因:输出文件无法访问
- 解决方案:检查文件路径权限(Android 10+需使用MediaStore)
-
RuntimeException:
- 原因:底层服务异常(如编码器不支持)
- 解决方案:检查设备支持的编码格式
典型错误处理代码示例:
java复制try {
recorder.prepare();
} catch (IllegalStateException e) {
Log.e(TAG, "prepare() called in invalid state", e);
resetRecorder();
} catch (IOException e) {
Log.e(TAG, "Failed to prepare recorder", e);
checkStoragePermissions();
} catch (RuntimeException e) {
Log.e(TAG, "Device doesn't support required codecs", e);
fallbackToBasicConfig();
}
3.2 性能优化技巧
通过分析prepare()的调用流程,我们可以提取几个优化点:
-
延迟初始化:
java复制// 不好的做法:立即prepare setupRecorder(); prepare(); // 此时用户可能还未点击录制 // 优化方案:按需prepare fun startRecording() { if (!isPrepared) { prepare() } start() } -
编码器预热:
java复制// 在应用启动时预加载编码器 fun warmUpEncoder() { val dummy = MediaRecorder() dummy.setAudioSource(MediaRecorder.AudioSource.DEFAULT) dummy.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT) try { dummy.prepare() // 触发编码器加载 } catch (e: Exception) { // 忽略测试异常 } finally { dummy.release() } } -
参数缓存:
java复制// 复用已验证的参数组合 object RecorderPresets { val HD_VIDEO = object : RecorderConfig { override fun apply(recorder: MediaRecorder) { recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264) recorder.setVideoSize(1280, 720) recorder.setVideoFrameRate(30) } } } fun setupWithPreset(preset: RecorderConfig) { preset.apply(recorder) prepare() // 使用已验证的参数组合 }
4. 高级应用场景剖析
4.1 低延迟录制优化
对于直播等低延迟场景,传统的prepare()流程可能引入不可接受的延迟。我们可以通过以下方式优化:
-
后台预加载:
java复制// 在后台线程提前prepare val handlerThread = HandlerThread("RecorderPreloader").apply { start() } val handler = Handler(handlerThread.looper) fun preloadRecorder() { handler.post { val tempRecorder = createRecorder() try { tempRecorder.prepare() // 后台预热 cachePreparedState() } catch (e: Exception) { logError(e) } } } -
Surface传输优化:
当使用Surface作为视频输入时,可以绕过部分初始化步骤:java复制// 使用SurfaceInput时的高效prepare val surface = camera.createCaptureSessionSurface() recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE) recorder.setInputSurface(surface) recorder.prepare() // 此场景下prepare更快
4.2 动态配置切换
某些应用需要运行时切换配置(如分辨率调整),传统做法是:
- 调用reset()
- 重新设置所有参数
- 再次prepare()
这种方案会导致录制中断。替代方案是:
java复制fun dynamicReconfig(newConfig: Config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API 26+支持暂停录制
recorder.pause()
applyNewConfig(newConfig)
recorder.resume()
} else {
// 旧版本使用双Recorder切换
val newRecorder = createRecorder(newConfig)
newRecorder.prepare()
// 精确控制切换时机
val lastTimestamp = recorder.getMaxAmplitude()
recorder.stop()
newRecorder.start()
syncTimestamps(lastTimestamp)
}
}
5. 跨版本兼容性处理
5.1 Android版本差异
prepare()的行为在不同Android版本上有显著差异:
| 版本 | 关键变化 | 适配建议 |
|---|---|---|
| 4.4- | 无严格状态检查 | 需自行维护状态机 |
| 5.0+ | 引入更严格的状态验证 | 确保调用顺序正确 |
| 8.0+ | 后台执行限制 | 需使用前台服务 |
| 10+ | 存储访问限制 | 使用MediaStore API |
| 12+ | 麦克风访问提示 | 需处理运行时权限 |
典型版本适配代码:
java复制fun safePrepare(recorder: MediaRecorder) {
try {
recorder.prepare()
} catch (e: Exception) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& e is IllegalStateException) {
// Android 7.0+对状态机更严格
recoverFromInvalidState()
}
throw e
}
}
5.2 厂商定制ROM问题
某些厂商ROM修改了MediaRecorder行为:
- 华为EMUI:prepare()可能阻塞较长时间(需异步调用)
- 小米MIUI:后台prepare()可能被终止(需加锁保持)
- 三星OneUI:某些编码组合不支持(需fallback处理)
厂商适配方案示例:
java复制fun manufacturerAwarePrepare() {
if (isHuaweiDevice()) {
handler.postDelayed({
tryPrepare()
}, 200) // 延迟避免EMUI阻塞主线程
} else {
tryPrepare()
}
}
private fun tryPrepare() {
synchronized(lock) {
recorder.prepare()
}
}
6. 调试与性能分析技巧
6.1 日志分析策略
通过adb logcat可以获取prepare()的详细日志:
bash复制adb logcat -v threadtime MediaRecorder:V *:S
关键日志标记:
MediaRecorderJNI: Java到Native的转换日志StagefrightRecorder: 核心组件初始化日志OMXCodec: 编码器加载日志CameraSource: 摄像头输入源日志
6.2 性能测量方案
测量prepare()耗时的推荐方法:
java复制fun measurePrepareTime(): Long {
val start = System.nanoTime()
recorder.prepare()
val elapsed = System.nanoTime() - start
Debug.logMetric("prepare_time_ms", elapsed / 1_000_000)
return elapsed
}
不同设备的典型prepare时间参考:
| 设备类型 | 平均时间(ms) | 主要影响因素 |
|---|---|---|
| 旗舰机 | 50-100 | 编码器加载 |
| 中端机 | 100-300 | CPU性能 |
| 低端机 | 300-800 | IO速度 |
6.3 内存泄漏检测
MediaRecorder使用不当会导致内存泄漏:
检测方案:
java复制class RecorderHolder : AutoCloseable {
private val recorder = MediaRecorder()
override fun close() {
recorder.release()
}
}
// 测试代码
val weakRef = WeakReference(RecorderHolder().apply {
recorder.prepare()
})
System.gc()
assertNull(weakRef.get()) // 如果非null说明泄漏
常见泄漏场景:
- 未调用release()直接丢弃MediaRecorder实例
- 在Activity中prepare()但未处理生命周期
- 异常路径下未正确清理
