
Android MediaPlayer/MediaRecorder
在Android多媒体开发中,MediaPlayer和MediaRecorder是最核心的两个API:前者负责音视频的播放,后者负责音视频的录制。
这两个组件看似上层接口简单,但其底层串联了Android多媒体框架的核心模块(编解码、数据采集/输出、封装/解封装、硬件交互等)。
本文将从架构分层、状态机模型、底层组件、核心流程等维度,全面解析MediaPlayer和MediaRecorder的实现原理。
Android多媒体框架
要理解MediaPlayer/MediaRecorder的底层逻辑,首先需掌握Android多媒体框架的分层设计。
整个框架从上层到下层分为5层,各层职责清晰且通过标准化接口交互:
| 层级 | 核心组件/作用 |
|---|---|
| 应用层 | MediaPlayer、MediaRecorder、ExoPlayer(第三方)等面向开发者的API |
| 框架层 | 基于Java的MediaPlayer/Recorder核心逻辑、JNI桥接、MediaCodec封装等 |
| 原生层(Native) | C/C++实现的核心逻辑:Stagefright框架、nuPlayer、MediaExtractor/Muxer、MediaCodec原生接口 |
| 硬件抽象层(HAL) | 音频HAL(Audio HAL)、视频编解码HAL(Codec HAL)、相机HAL(Camera HAL)等,屏蔽硬件差异 |
| 内核层 | 音频驱动、视频驱动、摄像头驱动、存储驱动等,与硬件直接交互 |
核心依赖:
- Stagefright:Android原生的多媒体处理框架,是MediaPlayer/Recorder的核心实现载体;
- OpenMAX IL:跨平台的多媒体编解码标准接口,连接原生层与HAL层,适配不同厂商的硬件编解码器;
- AudioFlinger:Android音频系统的核心,负责音频数据的混音、路由和输出/采集。
MediaPlayer
1 MediaPlayer的核心特性与状态机
MediaPlayer的上层设计严格遵循状态机模型(开发中最易出错的点),所有操作必须符合状态转换规则,否则会抛出IllegalStateException。其核心状态及转换关系如下:
Idle(空闲)→ Initialized(已初始化)→ Prepared(已准备)→ Started(播放中)
↑ ↓ ↓
Error(错误)← Stopped(已停止)← Paused(暂停)←
↑ ↓
Released(已释放)← End(播放完成)←─────────────
关键规则:
- 刚创建的MediaPlayer处于Idle状态,调用
setDataSource()后进入Initialized; - 必须通过
prepare()(同步)或prepareAsync()(异步)进入Prepared状态,才能调用start(); - 播放完成后进入End状态,需调用
seekTo()或start()重新激活; - 任何状态下发生错误都会进入Error状态,需调用
reset()恢复Idle。
2 从Java层到Native层的调用链路
MediaPlayer的Java层只是"壳",核心逻辑在Native层实现,调用链路如下:
- Java层 :
android.media.MediaPlayer,暴露setDataSource()、prepare()、start()等API; - JNI层 :
android_media_MediaPlayer.cpp,将Java方法映射为Native方法(如native_setup()、native_setDataSource()); - Native层核心 :
MediaPlayer.cpp(Native层MediaPlayer封装),通过IMediaPlayer接口与多媒体服务(MediaServer)通信; - 多媒体服务层 :MediaServer进程中的
StagefrightPlayer/nuPlayer(播放引擎),负责实际的解封装、解码、渲染。
注:Android 4.1后主推nuPlayer(支持流媒体、自适应播放),替代旧的StagefrightPlayer。
3 底层核心组件解析
MediaPlayer的播放能力依赖以下核心组件的协同工作:
(1)MediaExtractor:解封装器
作用:将封装格式(MP4、MP3、FLV等)的音视频文件解封装,分离出音频流、视频流(裸流)。
- 支持的封装格式由底层解析器(如MP4Extractor、MP3Extractor)决定;
- 核心接口:
setDataSource()(设置数据源)、getTrackCount()(获取流数量)、selectTrack()(选择要解析的流)、readSampleData()(读取裸流数据)。
(2)MediaCodec:编解码器
作用:将MediaExtractor分离出的裸流(如H.264、AAC)解码为原始数据(YUV视频帧、PCM音频帧)。
- 两种解码模式:
- 软解码:基于CPU的软件解码(如FFmpeg),兼容性好但性能低;
- 硬解码:基于硬件编解码器(如高通QCOM、联发科MTK的Codec HAL),性能高、功耗低,通过OpenMAX IL调用;
- 核心流程:
configure()(配置解码参数)→start()→queueInputBuffer()(送入待解码数据)→dequeueOutputBuffer()(取出解码后数据)。
(3)AudioTrack/AudioFlinger:音频输出
- AudioTrack:接收MediaCodec解码后的PCM音频数据,将数据写入AudioFlinger;
- AudioFlinger:Android音频系统的核心进程,负责音频混音(多应用音频叠加)、音量控制、路由(扬声器/耳机/蓝牙),最终将数据通过Audio HAL输出到硬件。
(4)Surface:视频渲染
解码后的YUV视频帧通过Surface传递给显示系统:
- Surface对应底层的
ANativeWindow,连接到SurfaceFlinger(Android显示系统核心); - 视频帧经SurfaceFlinger合成后,通过Display HAL输出到屏幕;
- 支持硬件渲染(Overlay)和软件渲染(Canvas),硬件渲染性能更优。
(5)音视频同步模块
播放的核心难点是音视频同步,MediaPlayer通过PTS/DTS时钟同步实现:
- PTS(Presentation Time Stamp):显示时间戳,指示帧应播放的时间;
- DTS(Decoding Time Stamp):解码时间戳,指示帧应解码的时间;
- 核心策略:以音频时钟为基准(音频采样率固定,时钟更稳定),调整视频帧的渲染时间,确保音视频时序一致。
4 MediaPlayer播放流程的底层执行步骤
以本地MP4文件播放为例,底层完整流程如下:
-
初始化阶段:
- Java层调用
new MediaPlayer(),触发JNI层native_setup(),创建Native层MediaPlayer实例; - 绑定MediaServer的播放服务,初始化音视频输出通道(AudioTrack/Surface)。
- Java层调用
-
数据源配置:
- 调用
setDataSource(),Native层通过MediaExtractor打开文件,解析封装格式,分离音视频流。
- 调用
-
准备阶段:
- 调用
prepare(),初始化MediaCodec(根据流的编码格式选择软/硬解码器); - 配置AudioTrack(匹配音频采样率、声道数、位深),建立与AudioFlinger的连接;
- 若为视频,绑定Surface,初始化渲染缓冲区。
- 调用
-
播放阶段:
- 调用
start(),MediaExtractor循环读取音视频裸流数据,送入MediaCodec解码; - 解码后的PCM音频数据写入AudioTrack,由AudioFlinger混音后输出到扬声器;
- 解码后的YUV视频帧送入Surface,由SurfaceFlinger合成后显示;
- 同步模块实时调整视频渲染时间,保证音视频同步。
- 调用
-
停止/释放阶段:
- 调用
stop(),停止数据读取和解码,清空缓冲区; - 调用
release(),释放MediaExtractor、MediaCodec、AudioTrack、Surface等资源,断开与MediaServer的连接。
- 调用
MediaRecorder
1 MediaRecorder的状态机模型
与MediaPlayer类似,MediaRecorder也有严格的状态机,核心状态转换如下:
Idle(空闲)→ Initialized(已初始化)→ DataSourceConfigured(数据源配置完成)→ Prepared(已准备)→ Recording(录制中)
↑ ↑ ↓ ↓
Error(错误)←────────────── Stopped(已停止)←────────── Paused(暂停)←
↑
Released(已释放)←───────────────────────────────────────────────
关键规则:
- 初始化后需先设置音/视频源(
setAudioSource()/setVideoSource()),进入Initialized; - 配置输出格式(
setOutputFormat())和编码格式(setAudioEncoder()/setVideoEncoder())后,进入DataSourceConfigured; - 调用
prepare()进入Prepared后,才能调用start()开始录制; - 录制中可调用
pause()暂停,resume()恢复,最终需调用stop()停止录制。
2 从Java层到Native层的调用链路
MediaRecorder的调用链路与MediaPlayer类似,但核心组件不同:
- Java层 :
android.media.MediaRecorder,暴露setAudioSource()、setOutputFormat()、start()等API; - JNI层 :
android_media_MediaRecorder.cpp,映射Java方法到Native方法(如native_setup()、native_setAudioSource()); - Native层核心 :
MediaRecorder.cpp,通过IMediaRecorder接口与MediaServer的录制服务通信; - 多媒体服务层 :MediaServer中的
MediaRecorderBase,整合音频采集、视频采集、编码、封装逻辑。
3 底层核心组件解析
MediaRecorder的录制能力依赖以下核心组件:
(1)AudioRecord/AudioFlinger:音频采集
- AudioRecord:从麦克风采集原始PCM音频数据,需配置采样率(如44.1kHz)、声道数(单声道/立体声)、位深(16bit);
- AudioFlinger:管理音频输入设备,负责麦克风数据的采集和传输,通过Audio HAL与硬件麦克风交互。
(2)Camera/Camera HAL:视频采集
- 调用Camera API(Android 5.0后为Camera2)获取摄像头数据,原始数据格式为YUV(如NV21);
- 通过Camera HAL与硬件摄像头交互,配置分辨率、帧率等参数。
(3)MediaCodec:编码器
作用:将采集的原始音视频数据(PCM/YUV)编码为压缩格式(如AAC、H.264)。
- 硬编码:通过Codec HAL调用硬件编码器(主流方案,性能高);
- 软编码:基于CPU的软件编码(兼容性好,功耗高);
- 核心流程:
configure()(配置编码参数)→start()→queueInputBuffer()(送入原始数据)→dequeueOutputBuffer()(取出编码后数据)。
(4)MediaMuxer:封装器
作用:将编码后的音频流和视频流封装为标准格式(MP4、3GP、WebM等),写入文件或网络流。
- 核心接口:
addTrack()(添加音视频轨道)、writeSampleData()(写入编码后数据)、stop()(完成封装)。
4 MediaRecorder录制流程的底层执行步骤
以录制MP4格式的音视频为例,底层完整流程如下:
-
初始化阶段:
- Java层调用
new MediaRecorder(),触发JNI层native_setup(),创建Native层MediaRecorder实例; - 绑定MediaServer的录制服务,初始化音视频采集通道。
- Java层调用
-
数据源配置:
- 调用
setAudioSource(MIC),初始化AudioRecord,建立与AudioFlinger的连接,配置音频采集参数; - 调用
setVideoSource(CAMERA),初始化Camera,配置视频分辨率、帧率等参数; - 调用
setOutputFormat(MP4)和setAudioEncoder(AAC)/setVideoEncoder(H264),配置封装格式和编码参数。
- 调用
-
准备阶段:
- 调用
prepare(),初始化MediaCodec(根据编码格式选择软/硬编码器); - 初始化MediaMuxer,创建输出文件,添加音视频轨道;
- 启动AudioRecord和Camera,准备采集数据。
- 调用
-
录制阶段:
- 调用
start(),AudioRecord从麦克风采集PCM数据,Camera采集YUV数据; - 原始数据送入MediaCodec编码为AAC/H.264压缩数据;
- 编码后的数据通过MediaMuxer写入MP4文件,同时记录PTS时间戳(保证音视频同步);
- 录制中可调用
pause()暂停采集/编码,resume()恢复。
- 调用
-
停止/释放阶段:
- 调用
stop(),停止音视频采集、编码,MediaMuxer完成文件封装(写入moov原子,保证文件可播放); - 调用
release(),释放AudioRecord、Camera、MediaCodec、MediaMuxer等资源,断开与MediaServer的连接。
- 调用
MediaPlayer与MediaRecorder的共性与差异
1 共性
- 分层设计:均采用"Java层封装 + JNI桥接 + Native层核心 + HAL层硬件交互"的架构,屏蔽硬件差异;
- 依赖核心服务:均基于MediaServer进程提供的多媒体服务,避免应用层直接操作硬件;
- 依赖MediaCodec:均使用MediaCodec进行编解码(MediaPlayer解码,MediaRecorder编码);
- 音频依赖AudioFlinger:播放时通过AudioFlinger输出音频,录制时通过AudioFlinger采集音频;
- 状态机约束:均有严格的状态转换规则,违规操作会抛出异常。
2 差异
| 维度 | MediaPlayer | MediaRecorder |
|---|---|---|
| 数据流方向 | 存储/网络 → 解封装 → 解码 → 输出 | 采集(麦克风/摄像头)→ 编码 → 封装 → 存储/网络 |
| 核心数据处理 | 解封装(MediaExtractor)+ 解码 | 采集 + 编码 + 封装(MediaMuxer) |
| 音频组件 | AudioTrack(输出PCM) | AudioRecord(采集PCM) |
| 视频组件 | Surface(渲染YUV) | Camera(采集YUV) |
| 同步机制 | 基于PTS/DTS的音视频同步(播放端) | 基于PTS的音视频同步(录制端) |
底层核心技术补充
1 OpenMAX IL:硬件编解码的标准接口
OpenMAX IL(Open Media Acceleration Integration Layer)是Khronos集团制定的多媒体编解码接口标准,Android通过它连接Native层和Codec HAL:
- 原生层通过OpenMAX IL的
OMX_Core加载硬件编解码器; - 编解码器厂商(如高通、联发科)实现OpenMAX IL接口,适配自家硬件;
- Android通过OpenMAX IL屏蔽不同厂商的硬件差异,保证上层API的统一性。
2 硬件加速的实现
MediaPlayer/MediaRecorder的硬件加速核心是硬件编解码:
- 硬解码:MediaCodec调用硬件解码器,解码过程由GPU/专用解码芯片完成,CPU占用率低;
- 硬编码:MediaCodec调用硬件编码器,编码过程由专用编码芯片完成,支持高分辨率、高帧率录制;
- 开发者可通过
MediaCodecInfo判断设备是否支持指定格式的硬编解码。
3 异常处理机制
底层通过回调和错误码向上层传递异常:
- MediaPlayer的
OnErrorListener、OnInfoListener; - MediaRecorder的
OnErrorListener、OnInfoListener; - 常见错误:数据源不存在(MEDIA_ERROR_UNKNOWN)、编解码器不支持(MEDIA_ERROR_CODEC_NOT_SUPPORTED)、状态违规(ILLEGAL_STATE_EXCEPTION)。
开发实践中的注意事项
- 严格遵守状态机:避免在错误状态调用API(如Prepared前调用start()),建议封装状态管理工具类;
- 资源及时释放 :MediaPlayer/Recorder持有硬件资源(麦克风、摄像头、音频设备),使用后必须调用
release(),否则会导致资源泄漏; - 硬编解码兼容性:不同设备支持的编解码格式不同,建议先检测设备支持的格式,降级使用软编解码;
- 异步操作处理 :
prepareAsync()、setDataSource()(网络数据源)为异步操作,需通过监听器处理回调,避免主线程阻塞; - 性能优化:播放大文件时使用缓存策略,录制时控制分辨率/帧率(如1080P/30fps),避免占用过多内存和存储。
总结
MediaPlayer和MediaRecorder是Android多媒体框架的上层抽象,其底层串联了解封装/封装、编解码、音视频采集/输出、硬件交互等核心能力:
- MediaPlayer的核心是"解封装→解码→输出",依赖MediaExtractor、MediaCodec、AudioTrack/Surface;
- MediaRecorder的核心是"采集→编码→封装",依赖AudioRecord/Camera、MediaCodec、MediaMuxer。
