Android 多媒体框架
Android 多媒体开发涉及的内容非常广泛且深入,主要包括以下几个核心方面:
1. 媒体播放 (Playback):
- 核心组件: MediaPlayer, MediaCodec, SurfaceView, TextureView, ExoPlayer(Google推荐的强大开源库)
- 关键功能:
本地文件、资源文件、网络流(HTTP, RTSP等)的播放
播放控制(开始、暂停、停止、跳转、循环)
音频焦点管理(处理多个应用同时播放声音)
音频特效(如均衡器、响度控制)
显示视频
2. 媒体录制 (Recording):
- 核心组件: MediaRecorder, AudioRecord, Camera API, Camera2 API, CameraX(Jetpack库,简化相机开发)
- 关键功能:
音频录制(麦克风输入,支持不同格式/质量)
视频录制(使用相机,设置分辨率、帧率、比特率)
音频源选择(麦克风、通话音频等)
录像文件格式(MP4, 3GP等)
3. 媒体存储与访问 (Storage & Access):
- 核心机制: ContentProvider, MediaStore, FileProvider, Storage Access Framework
4. 媒体编解码与处理 (Codec & Processing):
- 核心组件
MediaCodec
,MediaFormat
,MediaExtractor
,MediaMuxer
,OpenGL ES
,NDK
,FFmpeg
- 关键功能 :
音视频编解码/转码
媒体容器复用解复用(MediaExtractor
/MediaMuxer
)
音视频处理(滤镜/变声)
能处理方案(NDK/OpenGL ES)
方库集成(FFmpeg)
5. 图像处理与显示:
6. 音频管理 (Audio Management)
MediaPlayer在Android多媒体中角色
文章主要介绍Android 的媒体播放内容,首先就先介绍开发者最常接触的MeidaPlayer。 对于App开发者或初学者来说可能会认为MeidaPlayer就是播放器。严格来说,MeidaPlayer 仅仅是Android提供给app调用的api
什么是播放器
我们先来了解下什么是播放器,MediaPlayer跟我常说的播放器有什么关系
"播放器"是一个广泛使用的概念,其核心含义是:一个能够读取、解码、渲染并输出音频或视频内容的软件或硬件系统。播放器的实现是比较复杂的,但不管怎么复杂,主要的核心功能都是以下几个方面
- 解析 媒体文件或网络流。
- 解封装 解析封装格式,提取对应的音视频数据,字幕等。
- 解码 压缩的音视频数据。
- 音频渲染: 将解码后的音频 PCM 数据送给系统的音频输出设备(扬声器、耳机)。
- 视频渲染(如果需要): 将解码后的视频帧发送到你提供的
Surface
(通常是SurfaceView
或TextureView
)。视频播放时,真正的渲染 发生在你设置的Surface
之上,但解码和帧同步等核心播放逻辑仍在底层引擎中进行。 - 管理网络 缓冲 、同步 音视频、处理 DRM 等复杂任务

MediaPlayer跟播放器的关系
MediaPlayer
是 "播放器" 的标准化接口和控制器, 而真正实现播放器功能的是位于native层的nuplayer。可以看到要实现整个播放功能涉及很多模块,关于这部分内容会在其他章节介绍

MediaPlayer使用
其实理解Android MediaPlayer最直接的方法是看google的官方文档 developer.android.google.cn/reference/a... 因为已经有大量的文章转载和翻译,所以这里暂时不做介绍和翻译,可能会在后续实现接口的过程中引用和参考里面的内容
学习MediaPlayer主要是两方面,一是熟悉api,二是状态机
核心使用步骤流程:
使用 MediaPlayer
需要遵循一个明确的生命周期状态机。主要步骤包括:
-
创建实例: 初始化
MediaPlayer
对象。iniMediaPlayer mediaPlayer = new MediaPlayer();
-
设置数据源: 告知
MediaPlayer
媒体内容的来源。这是关键的分水岭,决定了后续操作。-
本地文件:
gomediaPlayer.setDataSource("/sdcard/music.mp3"); // 需要文件读写权限 // 或者通过 FileDescriptor (更推荐用于res/raw) FileInputStream fis = new FileInputStream(new File("/sdcard/video.mp4")); mediaPlayer.setDataSource(fis.getFD()); fis.close(); // 注意关闭流
-
应用资源 (raw 或 assets):
ini// 1. res/raw 资源 (如 sound.mp3) Uri myUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.sound); mediaPlayer.setDataSource(context, myUri); // context 是有效的 Context 对象 // 2. assets 资源 (需使用 AssetFileDescriptor) AssetFileDescriptor afd = getAssets().openFd("sound.mp3"); mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); afd.close();
-
网络流媒体:
arduinomediaPlayer.setDataSource("https://example.com/stream.mp3"); // 需要 INTERNET 权限 // 注意:网络播放需要使用 prepareAsync()!
-
ContentProvider URI:
iniUri contentUri = ...; // e.g., from MediaStore mediaPlayer.setDataSource(context, contentUri);
-
-
准备播放: 让
MediaPlayer
解析媒体格式、分配资源、连接解码器。绝对不要在 UI 线程用同步prepare()
加载大文件或网络源!-
同步准备 (适用于小文件或res/raw):
scsstry { mediaPlayer.prepare(); // 阻塞调用,直到准备好或出错 } catch (IOException e) { e.printStackTrace(); // 处理异常 }
-
异步准备 (强烈推荐,尤其网络或大文件):
typescriptmediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { // 准备完成,可以开始播放了 mp.start(); } }); mediaPlayer.prepareAsync(); // 非阻塞,立即返回。准备完成会回调 onPrepared
-
-
(视频专属) 设置显示 Surface: 视频播放需要一个可视化的容器。
java// 通常在 SurfaceView 或 TextureView 的 SurfaceHolder.Callback 中设置 SurfaceHolder holder = surfaceView.getHolder(); holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { if (mediaPlayer != null) { mediaPlayer.setDisplay(holder); // 将 MediaPlayer 绑定到 SurfaceView // 如果已设置数据源但未prepare,可以在此时 prepareAsync() } } // ... surfaceChanged, surfaceDestroyed 略 });
- 也可以在
onPrepared
之后设置:mediaPlayer.setDisplay(holder);
- 也可以在
-
开始播放:
scssmediaPlayer.start(); // 播放或从暂停状态恢复
-
控制播放:
scssmediaPlayer.pause(); // 暂停播放 mediaPlayer.seekTo(msec); // 跳转到指定毫秒位置 (如 seekTo(30000) => 跳到30秒) mediaPlayer.stop(); // 停止播放。注意:stop()后必须重新 prepare() 才能再 start() boolean isPlaying = mediaPlayer.isPlaying(); // 检查是否正在播放 int currentPosition = mediaPlayer.getCurrentPosition(); // 获取当前位置(ms) int duration = mediaPlayer.getDuration(); // 获取总时长(ms), -1 表示未知(如直播流) mediaPlayer.setLooping(true); // 设置循环播放 mediaPlayer.setVolume(leftVolume, rightVolume); // 设置音量 (0.0f - 1.0f)
-
设置事件监听器 (重要!):
-
OnPreparedListener
: 异步准备完成时回调 (见步骤3)。 -
OnCompletionListener
: 媒体播放自然结束时回调。lessmediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { // 播放结束逻辑,如播放下一个、重置进度条、释放播放器等 } });
-
OnErrorListener
: 发生错误时回调。返回 true 表示错误已处理,不会触发默认错误行为;返回 false 会触发 OnCompletionListener。javamediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { Log.e("MediaPlayer", "Error! what=" + what + ", extra=" + extra); // 分析错误 (what: MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_IO 等; extra: 更具体错误码) // 尝试恢复(如release后重新初始化)或告知用户 return true; // 处理了错误 } });
-
OnBufferingUpdateListener
(网络流重要): 报告缓冲进度百分比。javamediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { // percent: 0-100, 表示缓冲完成的百分比 // 可以在 UI 上显示缓冲进度 } });
-
OnSeekCompleteListener
: seekTo()
操作完成时回调。
-
-
状态管理和重置:
-
reset()
: 将播放器重置回空闲(Idle
)状态(类似于刚创建时)。这会清除数据源和当前状态。通常在播放不同文件前调用。scssmediaPlayer.reset(); // 之后可以重新 setDataSource 和 prepare
-
-
释放资源 (绝对重要!!!): 当播放器不再需要时(例如在 Activity 的
onDestroy()
、Fragment 的onDestroyView()
中),必须 调用release()
释放它持有的所有资源(解码器、内存、网络连接等)。不释放会导致严重的内存泄漏甚至应用崩溃。typescript@Override protected void onDestroy() { super.onDestroy(); if (mediaPlayer != null) { mediaPlayer.release(); // 这是关键!!! mediaPlayer = null; // 防止后续误操作 } }
核心状态机(必须理解):
MediaPlayer
的内部状态决定了你能做什么操作。比如 setDataSource()
只能在 Idle
或 Initialized
状态之间转换,start()
只能在 Prepared
、Started
、Paused
、PlaybackCompleted
状态调用。

简单理解主要状态:
- Idle (空闲): 刚创建或
reset()
后。 - Initialized (已初始化): 成功调用
setDataSource()
后。 - Preparing (准备中):
prepareAsync()
调用后,准备完成前。 - Prepared (已准备):
prepare()
或prepareAsync()
成功完成 (onPrepared
回调)。 - Started (已开始):
start()
调用后。 - Paused (已暂停):
pause()
调用后(需处于Started
状态)。 - PlaybackCompleted (播放完成): 播放自然结束 (
onCompletion
回调),但looping=true
时会自动重新开始。 - Stopped (已停止):
stop()
调用后(需处于Prepared
、Started
、Paused
、PlaybackCompleted
状态)。必须重新prepare()
才能再次start()
。 - Error (错误): 发生错误时。唯一方法恢复是
reset()
。 - End (结束):
release()
调用后。对象完全失效
最佳实践与注意事项:
-
始终异步准备: 对除小型 res/raw 文件外的所有情况,优先使用
prepareAsync()
+OnPreparedListener
,避免阻塞 UI 线程导致 ANR。 -
及时释放资源:
release()
是强制性的且至关重要的!在任何可能不再需要播放器的地方(尤其是 Activity/Fragment 销毁时)调用它。 -
错误处理: 总是设置
OnErrorListener
以优雅处理故障。记录错误信息并向用户提供反馈。 -
网络播放:
- 必须
prepareAsync()
。 - 设置
OnBufferingUpdateListener
更新 UI。 - 处理网络错误和重试逻辑。
- 考虑缓冲策略(在
setDataSource
前可设置mediaPlayer.setBuffer(MediaPlayer.BUFFER_CACHE_ALL)
等模式)。
- 必须
-
视频播放: 确保在 Surface (通过
setDisplay
) 有效可用后再开始播放或准备。在surfaceDestroyed
回调中暂停播放或释放播放器。 -
权限: 访问网络 (
INTERNET
) 或本地文件 (READ_EXTERNAL_STORAGE
/WRITE_EXTERNAL_STORAGE
) 需要相应权限。 -
主线程限制: 虽然
MediaPlayer
方法本身可以从主线程调用,但涉及文件 IO 或网络的操作(如setDataSource
和prepare
)的实际工作可能会在后台线程进行(尤其是使用prepareAsync
时)。重要的是不要在主线程使用阻塞性的prepare()
。
下面是一个简单的播放本地视频的demo
Activity代码如下:
java
public class MainActivity extends Activity implements SurfaceHolder.Callback {
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View view = getWindow().getDecorView();
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE);
surfaceView = findViewById(R.id.sv2);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
// 初始化MediaPlayer
mediaPlayer = new MediaPlayer();
// 设置数据源:从assets中读取视频文件
AssetFileDescriptor afd = getAssets().openFd("waves.mp4");
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
// 设置显示在SurfaceView上
mediaPlayer.setDisplay(holder);
mediaPlayer.setLooping(true);
mediaPlayer.prepareAsync(); // 异步准备,避免阻塞UI线程
// 设置准备完成的监听
mediaPlayer.setOnPreparedListener(mp -> {
// 获取视频的宽高
int videoWidth = mp.getVideoWidth();
int videoHeight = mp.getVideoHeight();
// 根据视频宽高调整SurfaceView的布局参数(可选,这里全屏播放,可以不做调整)
// 开始播放
mp.start();
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 当Surface发生变化时,可以在此处调整
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 当Surface销毁时,释放MediaPlayer资源
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
@Override
protected void onPause() {
super.onPause();
// 在Activity暂停时暂停播放,并释放资源(也可以选择暂停而不释放)
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
xml文件如下
js
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<SurfaceView
android:id="@+id/sv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Android MediaPlayer 的局限性
虽然 MediaPlayer
是 Android 框架内置、易于上手的基础组件,适用于简单场景,但它存在一系列显著的限制,特别是在构建现代、高性能或功能丰富的媒体应用时会显得捉襟见肘:
-
对现代自适应流媒体 (ABR) 支持不足或不灵活:
-
MediaPlayer
对 DASH 和 HLS 这类主流自适应比特率流协议的支持是基础且黑盒的。开发者很难或无法:- 精细控制自适应逻辑: 无法自定义在不同网络条件下如何选择不同码率的轨道(视频、音频)。
- 访问轨道信息: 难以获取流中可用的音轨、视频轨、字幕轨及其属性列表。
- 手动切换轨道: 无法在播放过程中根据应用逻辑(如用户选择或特定条件)精确切换到指定的音轨、视频轨或字幕轨。
- 响应式调整策略: 对网络波动、缓冲预测的自适应策略不够透明或可定制。
-
-
功能相对有限和定制性差:
- 细粒度控制缺失: 缺乏对诸如精确下载管理(只下载特定部分)、边播边存、播放速度平滑过渡等高级功能的原生支持。
- 渲染和输出定制困难: 想要自定义视频渲染(如添加滤镜、水印、特效)或进行高级音频处理(如均衡器、音效),操作非常复杂且受限。它主要通过
SurfaceView
或TextureView
输出视频,对底层渲染管道控制很少。 - 拼接与编辑功能弱: 实现多个片段无缝拼接、复杂的剪辑播放(如播放列表不同片段的交叉淡化)等功能非常笨拙或无法实现。
- 可扩展性不足: 很难集成自定义的容器格式解析器 (Extractor)、数据源 (DataSource) 或渲染器 (Renderer)。
-
DRM (数字版权管理) 支持有限且复杂:
- 支持 Widevine 等 DRM 方案,但集成和使用通常较为繁琐。
- 对于需要高级 DRM 功能或需要支持多种 DRM 系统的复杂应用,其接口和流程不够友好和强大。
主流替代方案
鉴于 MediaPlayer
的局限性,以下替代方案在 Android 开发中得到了广泛应用和推荐:
-
ExoPlayer (官方推荐):
-
开发者: Google 开源。
-
定位:
MediaPlayer
的高性能、可扩展性、功能强大的现代替代品。是目前 Android 平台最主流的媒体播放库之一。 -
核心优势:
- 强大的流媒体支持: 对 DASH, HLS, SmoothStreaming 提供了一流的、透明且可定制的支持。开发者可以深度控制 ABR 策略、轨道选择等。
- 高度模块化: 采用组件化设计 (DataSource, Extractor, Renderer),开发者可以轻松替换或扩展各个部分(如自定义数据源、处理新格式、添加滤镜)。
- 丰富的功能集: 内置支持背景播放、广告插入、无缝拼接、边播边存、高级事件分析、DRM (Widevine, Clearkey, PlayReady)、自定义负载请求头等。
- 更好的性能与缓冲控制: 提供更多可配置的缓冲参数和更智能的缓冲算法,提升弱网体验。
- 更清晰现代的 API 和文档: 设计更符合现代开发习惯,文档和社区资源非常丰富。
- 持续更新: Google 和社区持续投入,快速适配新平台特性和媒体格式。
- 透明性: 开源且高度可定制,开发者可以深入了解其运行机制。
- 兼容性好: 在 API Level 16 (Android 4.1, Jelly Bean) 及以上的设备上运行良好。
-
缺点:
- 需要作为依赖库引入项目(增加 APK 尺寸)。
- 入门学习曲线相对
MediaPlayer
稍陡峭(但文档齐全)。 - 对于极其简单的本地音频播放需求(如按钮音效),可能显得有些"重"(但通常仍是更优选择)。
-
-
Jetpack Media3 (官方现代集成):
-
开发者: Google (Android Jetpack 组件之一)。
-
定位:
ExoPlayer
的现代化包装库,旨在统一媒体播放、媒体会话控制和 Cast (Chromecast) 集成的开发者体验。它代表了 Google 推荐的未来发展方向。 -
核心优势:
- 以 ExoPlayer 为核心引擎: 直接内置或依赖于
ExoPlayer
提供强大的播放能力,继承了其几乎所有优势。 - 简洁一致的 API (
Player
接口): 提供了MediaPlayer
和ExoPlayer
接口的抽象层 ,让开发者使用一套更现代化、更符合 Kotlin 习惯的 API 来操作不同的播放器实现(目前主要是ExoPlayer
)。 - 与系统深度集成: 简化了与
MediaSession
的集成,用于系统控件(通知栏、蓝牙设备、锁屏控件)、后台播放和与其他应用共享媒体控制权。同时也简化了 Chromecast 集成。 - 推荐实践: 代表了 Google 在媒体播放、媒体浏览器服务、会话控制等方面的最新最佳实践。
- 以 ExoPlayer 为核心引擎: 直接内置或依赖于
-
组件: 包含
media3-exoplayer
(播放核心),media3-ui
(预制 UI 控件),media3-session
(媒体会话),media3-cast
(Chromecast),media3-transformer
(媒体转换) 等库。 -
适用场景: 对于新项目或想要升级现有播放功能到最新标准、并且需要后台控制/投屏的应用,
Media3
是首选推荐。
-
-
其他第三方播放器 SDK:
- 如 VLC for Android 的 LibVLC: 开源,基于知名的 VLC 播放器核心,支持极其广泛的媒体格式和协议 。优势在于超强的兼容性(尤其在处理怪异格式时)。但集成文档、社区支持可能不如
ExoPlayer
/Media3
丰富,API 风格也不同。 - 商业 SDK: 市场上存在一些商业播放器 SDK,它们可能在特定功能(如特定的 DRM 方案支持、高级分析、专业级的播放性能优化、客户支持)上有所特长。选择商业 SDK 需要权衡成本和特定需求。
- 如 VLC for Android 的 LibVLC: 开源,基于知名的 VLC 播放器核心,支持极其广泛的媒体格式和协议 。优势在于超强的兼容性(尤其在处理怪异格式时)。但集成文档、社区支持可能不如