MediaPlayer的API使用及源码分析

MediaPlayer解读

MediaPlayer介绍

生命周期

音视频文件(或流)的播放控制是通过下图所示的状态机来管理的,图例说明:

MediaPlayer的状态
  • 椭圆代表的是播放器可能达到的状态
  • 弧线代表的是驱动状态改变的控制操作
    • 单箭头的弧线代表的是同步调用
    • 双箭头的弧线代表的是异步调用

权限相关

媒体播放需要的权限根据其实现的功能主要有以下三个:

  • 播放网络媒体需要INTERNET权限

      <uses-permission android:name="android.permission.INTERNET" />
  • 播放时保持屏幕常亮需要WAKE_LOCK权限

      <uses-permission android:name="android.permission.WAKE_LOCK" />
  • 实现播放缓存功能可能需要读写SD卡权限

      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

回调处理

MediaPlayer提供了比较多的回调监听处理,具体有:

  • setOnPreparedListener,播放器准备完毕
  • setOnVideoSizeChangedListener,播放器尺寸改变
  • setOnSeekCompleteListener,拖动完成
  • setOnCompletionListener,播放完成
  • setOnBufferingUpdateListener,缓存更新
  • setOnInfoListener,有可用信息或警告时
  • setOnErrorListener,发生错误时
  • MediaPlayer需要在拥有自己Looper的线程中创建

开发指南

Android多媒体框架有两个主要的类:MediaPlayer和AudioManager,前者主要负责音视频播放的基础API,后者主要管理设备上的音频资源和音频输出。

MediaPlayer的使用

可以用来播放以下三种类型:

播放raw资源:

1
2
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start();// 通过create得到的MediaPlayer实例无需prepare()

播放本机资源:

1
2
3
4
5
6
Uri myUri = ....; // 获取本机资源的Uri
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

播放网络资源:

1
2
3
4
5
6
String url = "http://........"; // 网络资源地址
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // 这里由于缓冲可能需要的时间比较久
mediaPlayer.start();

注意:播放的网络资源必须是可以逐步下载的。同时要捕获IllegalArgumentException和IOException因为请求的网络资源可能是不存在的。

异步执行

注意某些操作需要较长时间(例如prepare方法调用),切勿从应用的界面线程中调用它

避免界面线程挂起,使用其他线程来准备MediaPlayer,并在准备工作完成后通知主线程。框架提供prepareAsync()方法,会在后台开始准备媒体,并立即返回。当媒体准备就绪后,系统会调用MediaPlayer.OnPreparedListener的onPrepared()方法(通过setOnPreparedListener配置)。

状态管理

MediaPlayer具有内部状态,在编写代码时要始终注意,通常情况下状态是按如下节奏变化的:

Idle -> setDataSource() -> Initialized -> prepare()或prepareAsync() -> Prepared -> start(),pause()和seekTo() -> stop() -> Stopped.

释放MediaPlayer

MediaPlayer会占用宝贵的系统资源,不再使用时要及时进行资源的释放:

1
2
mediaPlayer.release();
mediaPlayer = null;

在Service中使用MediaPlayer

如果需要支持后台播放的功能,那么就要使用Service进行MediaPlayer的播放操作。官方建议使用MediaBrowserServiceCompat和MediaBrowserCompat来实现,它是一种C/S架构。通常是一个持有MediaPlayer的Service实例来进行播放的。

异步执行

即使是后台播放(Service通常和Activity一样默认运行在主线程的),Service中也不能进行耗时操作,也应采用异步执行并在准备完成后通知播放器进行播放。通常实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mediaPlayer = null;

public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mediaPlayer = ... // 初始化
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.prepareAsync(); // 异步prepare
}
}

/** 准备好后开始播放 */
public void onPrepared(MediaPlayer player) {
player.start();
}
}

处理异步错误

同步的播放操作在发生错误或异常后马上就会得到通知,但异步的情形是就需要我们自己在错误回调里面进行处理了,在Service里处理异步错误的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mediaPlayer;

public void initMediaPlayer() {
// 初始化媒体播放器
// 设置错误监听
mediaPlayer.setOnErrorListener(this);
}

@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 错误后的处理
// 播放器已经进入错误的状态,需要重置后才能继续使用
}
}

WakeLock处理

当设备休眠时,通常会关闭那些不必要的功能(包括CPU和WiFi硬件),所以后台播放需要请求Wakelock,MediaPlayer处理WakeLock很简单,代码如下:

mediaPlayer = new MediaPlayer();
// ... 此处其他初始化操作 ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

播放网络资源时,要防止WiFi被关,请求WiFi的WakeLock代码如下:

1
2
3
4
5
6
7
8
// 请求WiFi 的WakeLock
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

// 在合适的时机释放WakeLock
wifiLock.release();

注意:在不必要的时候就要及时释放WakeLock,毕竟WakeLock会减少设备电池的寿命。

数字版权管理(DRM)

从Android 8.0(API级别26)开始,MediaPlayer包含支持播放受DRM保护的资料的API,其并不提供MediaDrm的完整功能,当前可处理以下内容:

  • 受Widevine保护的本地媒体文件
  • 受Widevine保护的远程/流式传输媒体文件

下面的代码片段展示了同步使用DRM MediaPlayer的一般过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setDataSource();
setOnDrmConfigHelper(); // 可选项,用于个性化配置
prepare(); // 资源准备(实际使用时这里是异步调用的)
if (getDrmInfo() != null) { // 获取DRM信息,如果不为空就处理
prepareDrm(); // 准备drm信息(这个过程通常需要异步处理,
// 避免阻塞,通过OnDrmPreparedListener获取回调通知)
getKeyRequest(); // 获取不透明的密钥请求字节数组以发送到许可证服务器
provideKeyResponse(); // 将获取到的密钥响应通知到DRM引擎
}

// MediaPlayer is now ready to use
start();
// ...play/pause/resume...
stop();
releaseDrm();

异步设置DRM

可以创建和注册用于进行DRM准备的MediaPlayer.OnDrmInfoListener以及用于启动播放器的MediaPlayer.OnDrmPreparedListener,从而异步初始化DRM,与prepareAsync()结合使用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setOnPreparedListener();
setOnDrmInfoListener();
setDataSource();
prepareAsync();
// ...

// 如果是受版权保护的内容,在这个回调方法里面进行处理
onDrmInfo() {
prepareDrm();
getKeyRequest();
provideKeyResponse();
}

// 当prepareAsync()结束后,将收到onPrepared()回调通知,如果是一个DRM内容,
// onDrmInfo()会优先于onPrepared()被调用,所以这里可以开始播放了
onPrepared() {

start();
}

处理加密媒体

从Android 8.0(API级别26)开始,MediaPlayer开始支持为基本的流类型H.264和AAC解密通用加密方案(CENC)和HLS样本级加密媒体(METHOD=SAMPLE-AES),之前支持全分段加密媒体(METHOD=AES-128)。

从ContentResolver检索媒体

可以通过查询ContentResolver找到外部媒体来完成该操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// 查询失败,处理错误
} else if (!cursor.moveToFirst()) {
// 设备上没有媒体数据
} else {
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...处理功能入口...
} while (cursor.moveToNext());
}

要将其与MediaPlayer结合使用,您可以执行以下操作:

1
2
3
4
5
6
7
8
9
long id = /* 从某些地方检索 */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...准备媒体并启动...

源码分析

注意:此处分析的是android-5.1.1_r18,是比较老的版本。

MediaPlayer中大部分的功能使用C++实现,Java这边做的工作大部分是JNI的调用,这篇文章主要分析了常用的几个接口对应C++实现和Media Server。

本文来自于Media Player 源码分析

Media Server

Media Server整体的架构是C/S架构,C和S之间的通讯是IPC,具体来说是Binder。Media Server中大量的用到了Binder。整个架构将播放控制、视频、音频、相机等和多媒体有关的这些包装成不同的服务,通过IPC解耦。下图是Google关于Android中Media 引擎的架构做的关系图。

Media 架构

在系统 mediaserver 作为一个单独的进程,负责整个系统的音频视频编解码工作:

media server 进程

下面先从 Java 层的调用顺序开始看一下整个调用的流程。

MediaPlayer 调用流程

MediaPlayer中涉及到的主要函数都是通过JNI来完成的,MediaPlayer.java对应的是android_media_MediaPlayer.cpp。其中的对应关系如下,省略了一部分不是特别重要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static JNINativeMethod gMethods[] = {
{
"nativeSetDataSource",
"(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
"[Ljava/lang/String;)V",
(void *)android_media_MediaPlayer_setDataSourceAndHeaders
},
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth},
{"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_pause", "()V", (void *)android_media_MediaPlayer_pause},
{"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
{"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
{"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"_reset", "()V", (void *)android_media_MediaPlayer_reset},
{"native_init", "()V", (void *)android_media_MediaPlayer_native_init},
{"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup},
};

Java 这边的调用顺序通常是:

1
2
3
4
5
6
7
MediaPlayer mp = new MediaPlayer();
mp.setDataSource("/sdcard/test.mp3");
mp.prepare();
mp.start();
mp.pause();
mp.stop();
mp.release();

下面将按照这个顺序一步一步来分析。

构造函数

在MediaPlayer的构造函数中调用了native的android_media_MediaPlayer_native_setup 方法:

1
2
3
4
5
6
7
public MediaPlayer() {
...
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
}

setup方法中创建了MediaPlayer,同时也设置了回调函数。其中最后一行的setMediaPlayer将MediaPlayer的指针保存成一个Java对象,之后可以看到getMediaPlayer通过同样的方法获取到该对象的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
ALOGV("native_setup");
sp<MediaPlayer> mp = new MediaPlayer();
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
}
// create new listener and give it to MediaPlayer
sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
mp->setListener(listener);
// Stow our new C++ MediaPlayer in an opaque field in the Java object.
setMediaPlayer(env, thiz, mp);
}

static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
Mutex::Autolock l(sLock);
/* 由于指针的大小和 long 的大小是一样的,所以这里的 get 和 set 可以将
MediaPlayer 的指针取出或存入*/
sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);
// 判断 mediaplayer 是否为空指针,如果不是,这 sp 引用计数加1
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
// 如果之前存过 mediaplayer,检查之前存的是否为空,如果不为空,则引用计数减1
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
// 将新的 mediaplayer 指针存入 Java 对象中
env->SetLongField(thiz, fields.context, (jlong)player.get());
return old;
}

这里解释两点:

1. 由于指针的大小和long的大小是一样的,所以可以通过SetLongField和GetLongField来保存函数指针。

2. 由于shared_ptr和weak_ptr是C++11中才加入的,所以源码中实现了sp和wp作为智能指针来使用,这里可以看到代码中手动控制了引用计数。

现在一个mediaplayer对象就创建好了,其他的jni中对应的方法几乎都是调用mediaplayer来完成的。

设置数据源

setDataSource对应的是jni中的这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
// mediaplayer 为空
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
// 文件描述符为空
if (fileDescriptor == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("setDataSourceFD: fd %d", fd);
// 调用 mp->setDataSource(fd, offset, length) 为 mediaplayer 设置数据源
process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

可以看到函数开始先做了空指针的判断,之后调用了mp->setDataSource(fd, offset, length)方法来给mediaplayer设置数据源,同时process_media_player_call函数对setDataSource返回值做判断是否设置成功以及失败后抛出异常。

mediaplayer中的setDataSource函数分为三种,分别对应播放文件、网络和流三种情况,现在只看播放文件的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);
status_t err = UNKNOWN_ERROR;
// 获取 MediaPlayerService 接口
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
// 获取 MediaPlayer 接口
sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
// 设置数据源
if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
(NO_ERROR != player->setDataSource(fd, offset, length))) {
player.clear();
}
err = attachNewPlayer(player);
}
return err;
}

这个函数分为三个部分,逐个分析。

获取 MediaPlayerService 接口

这里先是调用了getMediaPlayerService方法获取到一个IMediaPlayerService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/*static*/const sp<IMediaPlayerService> &IMediaDeathNotifier::getMediaPlayerService()
{
ALOGV("getMediaPlayerService");
Mutex::Autolock _l(sServiceLock);
if (sMediaPlayerService == 0) {
// 获取 servicemanager
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
// 获取对应的 binder
binder = sm->getService(String16("media.player"));
if (binder != 0) {
break;
}
ALOGW("Media player service not published, waiting...");
usleep(500000); // 0.5 s
} while (true);
if (sDeathNotifier == NULL) {
sDeathNotifier = new DeathNotifier();
}
binder->linkToDeath(sDeathNotifier);
// asInterface 获取 binder 中的 remote 对象
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
}
ALOGE_IF(sMediaPlayerService == 0, "no media player service!?");
return sMediaPlayerService;
}

IServiceManager是IInterface类型,和用AIDL生成的接口相同,IInterface中包含了binder和remote两个东西。在ndk中对应的是BnInterface和BpInterface。在Java中,本地Client通过ServiceConnection中的onServiceConnected(ComponentName name, IBinder service)方法获得Service中在onBind时候返回的binder,同理,这里通过ServiceManager的getService获得和media.player这个Service通信用的binder。

到这里为止我们只拿到了media.player Service的binder,要想调用接口中的方法还需要通过asInterface方法来获得与之对应的IInterface接口。这里的interface_cast<IMediaPlayerService>(binder)方法就可以获得对应的接口,那么interface_cast是怎么做到的呢,其实很简单,利用了模板封装了asInterface的操作:

1
2
3
4
template<typename INTERFACE> inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}

现在我们就拿到了media.player Service的接口。

看到这里会有一个疑问,这个media.player的Service是什么时候启动的呢。我们根据media.player这个线索找到了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService());
}

int main(int argc __unused, char** argv)
{
...
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
// 就是这里啦
MediaPlayerService::instantiate();
// 照相机
CameraService::instantiate();
// 音频
AudioPolicyService::instantiate();
// 语音识别
SoundTriggerHwService::instantiate();
registerExtensions();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

其中MediaPlayerService位于MediaPlayerService.cpp中,而main函数位于main_mediaserver.cpp。还记得在本文最开始的那张图么,其中的mediaserver对应的就是这个。它是在开机的时候启动的,被写在了启动脚本中:

mediaserver 启动脚本

这样在系统启动的时候这个进程就开启了,同时里面的Service也就启动了。

获取 MediaPlayer 接口

service->create(this, mAudioSessionId)方法通过IPC的方式调用MediaPlayerService中的create方法获得IMediaPlayer,IMediaPlayer从名字就可以看出是一个IInterface,所以MediaPlayer也是通过IPC来调用的。先看这个方法做了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client, int audioSessionId)
{
pid_t pid = IPCThreadState::self()->getCallingPid();
int32_t connId = android_atomic_inc(&mNextConnId);
// MediaPlayerClient
sp<Client> c = new Client(
this, pid, connId, client, audioSessionId,
IPCThreadState::self()->getCallingUid());
ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid,
IPCThreadState::self()->getCallingUid());
wp<Client> w = c;
{
Mutex::Autolock lock(mLock);
// 管理 Client
mClients.add(w);
}
return c;
}

首先调用的时候传的参数是MediaPlayer本身,MediaPlayer除了继承IMediaDeathNotifier同时还继承了BnMediaPlayerClient,而BnMediaPlayerClient又继承了BnInterface<IMediaPlayerClient>,所以这里的参数列表是一个client引用。其中Client的实现也在MediaPlayerService.cpp 这个文件中,他的构造函数用来保存这些对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MediaPlayerService::Client::Client(
const sp<MediaPlayerService>& service, pid_t pid,
int32_t connId, const sp<IMediaPlayerClient>& client,
int audioSessionId, uid_t uid)
{
ALOGV("Client(%d) constructor", connId);
mPid = pid;
mConnId = connId;
mService = service;
mClient = client;
mLoop = false;
mStatus = NO_INIT;
mAudioSessionId = audioSessionId;
mUID = uid;
mRetransmitEndpointValid = false;
mAudioAttributes = NULL;
#if CALLBACK_ANTAGONIZER
ALOGD("create Antagonizer");
mAntagonizer = new Antagonizer(notify, this);
#endif
}

最后create方法返回一个MediaPlayer的接口,这个接口通过IPC用来调用Client中的函数。

设置数据源

经过了之前的折腾,我们先拿到了MediaPlayerService的接口,通过MediaPlayerService的接口又拿到了MediaPlayer的接口,接下来就要进行这个函数的最终目的设置数据源

同样是通过IPC调用了MediaPlayer的setDataSource,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
// 前面是一些输出要设置的数据源的信息的 log
...
// 获取播放器类型
player_type playerType = MediaPlayerFactory::getPlayerType(this, fd, offset, length);
// 创建播放器
sp<MediaPlayerBase> p = setDataSource_pre(playerType);
if (p == NULL) {
return NO_INIT;
}
// now set data source
setDataSource_post(p, p->setDataSource(fd, offset, length));
return mStatus;
}

首先是获取播放器类型,播放器类型定义在MediaPlayerInterface.h中:

1
2
3
4
5
6
7
8
9
10
11
enum player_type {
PV_PLAYER = 1,
SONIVOX_PLAYER = 2,
STAGEFRIGHT_PLAYER = 3,
NU_PLAYER = 4,
// Test players are available only in the 'test' and 'eng' builds.
// The shared library with the test player is passed passed as an
// argument to the 'test:' url in the setDataSource call.
TEST_PLAYER = 5,
};

下面说下这几种类型是做什么的,其中的每一个都对应一个工厂来创建对应的Player,由于PV_PLAYER已经被抛弃了,所以在5.1的源码里并没有出现它。

  1. PV_PLAYER 这个类型是Android最初采用的OpenCore,由于太臃肿已经被抛弃
  2. SONIVOX_PLAYER 用来处理midi相关
  3. NU_PLAYER 全能型,在安卓5.x上处于可选
  4. STAGEFRIGHT_PLAYER 安卓5.x之前的主力即awesome player,可以胜任除midi外全部的工作
  5. TEST_PLAYER 测试用

这些player都是由对应的factory创建的,对应的实现在MediaPlayerFactory.cpp中,其中的代码比较简单,这里就不分析了,主要是匹配不同类型对应不同的分数,然后选取分高的player创建。当前的主力是STAGEFRIGHT_PLAYER也就是awesome player,而NU_PLAYER是未来的主力,从Android M目前的源码中也可以看出代码中只剩下了NU_PLAYER和STAGEFRIGHT_PLAYER,其中NU_PLAYER负责网络和流的播放,STAGEFRIGHT_PLAYER负责有DRM和文件的播放。

这里我们就以目前的主力STAGEFRIGHT_PLAYER播放器继续往下分析。获取到播放器类型后就到了创建播放器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(player_type playerType)
{
...
// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) {
return p;
}
...
return p;
}
sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
// mPlayer 是当前已经有的 player,如果是刚创建这个对象,
// 那么 player 是 null 需要创建新的 player
// 如果创建过了了则对比下需要用到的 player 类型,避免重复创建
sp<MediaPlayerBase> p = mPlayer;
if ((p != NULL) && (p->playerType() != playerType)) {
ALOGV("delete player");
p.clear();
}
if (p == NULL) {
p = MediaPlayerFactory::createPlayer(playerType, this, notify);
}
...
return p;
}

这里从MediaPlayerFactory创建了对应的播放器,之后使用创建好的player设置数据源:

1
2
3
4
status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
ALOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
return mPlayer->setDataSource(dup(fd), offset, length);
}

这里的mPlayer就是AwesomePlayer,AwesomePlayer在StagefrightPlayer类的构造函数中创建:

1
2
3
4
StagefrightPlayer::StagefrightPlayer(): mPlayer(new AwesomePlayer) {
ALOGV("StagefrightPlayer");
mPlayer->setListener(this);
}

StagefrightPlayer的源码可以看到对象中的AwesomePlayer来完成。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
status_t AwesomePlayer::setDataSource(
int fd, int64_t offset, int64_t length) {
Mutex::Autolock autoLock(mLock);
// 重置播放器状态
reset_l();
// 封装成 DataSource
sp<DataSource> dataSource = new FileSource(fd, offset, length);
...
return setDataSource_l(dataSource);
}

status_t AwesomePlayer::setDataSource_l(const sp<DataSource> &dataSource) {
// 通过 datasource 创建分离器
sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
if (extractor == NULL) {
return UNKNOWN_ERROR;
}
if (extractor->getDrmFlag()) {
checkDrmStatus(dataSource);
}
// 为分离器设置数据源
return setDataSource_l(extractor);
}

这个分离器其实和ffpmeg中的demuxer一样,作用是将数据中的音频部分和视频部分分开,然后获取到对应的类型,调用对应的解码器进行解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
// Attempt to approximate overall stream bitrate by summing all
// tracks' individual bitrates, if not all of them advertise bitrate,
// we have to fail.
int64_t totalBitRate = 0;
mExtractor = extractor;
for (size_t i = 0; i < extractor->countTracks(); ++i) {
sp<MetaData> meta = extractor->getTrackMetaData(i);
int32_t bitrate;
if (!meta->findInt32(kKeyBitRate, &bitrate)) {
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
ALOGV("track of type '%s' does not publish bitrate", mime);
totalBitRate = -1;
break;
}
// 总的码率
totalBitRate += bitrate;
}
// metadata 就是数据中的元信息
sp<MetaData> fileMeta = mExtractor->getMetaData();
if (fileMeta != NULL) {
int64_t duration;
if (fileMeta->findInt64(kKeyDuration, &duration)) {
// 长度
mDurationUs = duration;
}
}
mBitrate = totalBitRate;
bool haveAudio = false;
bool haveVideo = false;
for (size_t i = 0; i < extractor->countTracks(); ++i) {
sp<MetaData> meta = extractor->getTrackMetaData(i);
const char *_mime;
CHECK(meta->findCString(kKeyMIMEType, &_mime));
String8 mime = String8(_mime);
// 视频
if (!haveVideo && !strncasecmp(mime.string(), "video/", 6)) {
setVideoSource(extractor->getTrack(i));
haveVideo = true;
...
// 音频
} else if (!haveAudio && !strncasecmp(mime.string(), "audio/", 6)) {
setAudioSource(extractor->getTrack(i));
haveAudio = true;
...
// ogg
if (!strcasecmp(mime.string(), MEDIA_MIMETYPE_AUDIO_VORBIS)) {
// Only do this for vorbis audio, none of the other audio
// formats even support this ringtone specific hack and
// retrieving the metadata on some extractors may turn out
// to be very expensive.
sp<MetaData> fileMeta = extractor->getMetaData();
int32_t loop;
if (fileMeta != NULL
&& fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
modifyFlags(AUTO_LOOPING, SET);
}
}
} else if (!strcasecmp(mime.string(), MEDIA_MIMETYPE_TEXT_3GPP)) {
addTextSource_l(i, extractor->getTrack(i));
}
}
...
return OK;
}

这里通过分离器来确定数据源中是音频还是视频,然后对player的videotrack和audiotrack进行相对应的设置。

到此为止setDataSource的流程就算是走完了,如果继续往下分析的话就到了视频的编解码的知识了。下面先用几张图来梳理一下这个过程(画的不是很规范)。

流程图:

setDataSource流程图

类图:

mediaplayer类图

类图中的Client是MediaPlayerService的内部类。MediaPlayerService通过IPC和MediaPlayerService通信获得Client,Client中包含了StagefrightPlayer,而StagefrightPlayer最终通过调用AwesomePlayer对应的方法。

Prepare

通过之前的经验我们可以很快的知道prepare应该对应的是AwesomePlayer中的prepare:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
status_t AwesomePlayer::prepare() {
...
return prepare_l();

}

status_t AwesomePlayer::prepare_l() {
...
status_t err = prepareAsync_l();
...
return mPrepareResult;
}

status_t AwesomePlayer::prepareAsync_l() {
if (mFlags & PREPARING) {
return UNKNOWN_ERROR; // async prepare already pending
}
if (!mQueueStarted) {
mQueue.start();
mQueueStarted = true;
}
modifyFlags(PREPARING, SET);
mAsyncPrepareEvent = new AwesomeEvent(
this, &AwesomePlayer::onPrepareAsyncEvent);
mQueue.postEvent(mAsyncPrepareEvent);
return OK;
}

mQueue是TimedEventQueue,TimedEventQueue和Handler很相似,使用pthread和队列来管理消息。在这里通过异步方式回调onPrepareAsyncEvent方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void AwesomePlayer::onPrepareAsyncEvent() {
Mutex::Autolock autoLock(mLock);
beginPrepareAsync_l();
}
void AwesomePlayer::beginPrepareAsync_l() {
// 取消 prepare
if (mFlags & PREPARE_CANCELLED) {
ALOGI("prepare was cancelled before doing anything");
abortPrepare(UNKNOWN_ERROR);
return;
}
// 网络媒体
if (mUri.size() > 0) {
status_t err = finishSetDataSource_l();
if (err != OK) {
abortPrepare(err);
return;
}
}
// 是否包含视频
if (mVideoTrack != NULL && mVideoSource == NULL) {
// 初始化视频解码器
status_t err = initVideoDecoder();
if (err != OK) {
abortPrepare(err);
return;
}
}
// 是否包含音频
if (mAudioTrack != NULL && mAudioSource == NULL) {
// 初始化音频解码器
status_t err = initAudioDecoder();
if (err != OK) {
abortPrepare(err);
return;
}
}
modifyFlags(PREPARING_CONNECTED, SET);
// 不同的播放源类型
if (isStreamingHTTP()) {
// 网络流媒体
postBufferingEvent_l();
} else {
// 本地媒体
finishAsyncPrepare_l();
}
}

void AwesomePlayer::finishAsyncPrepare_l() {
if (mIsAsyncPrepare) {
if (mVideoSource == NULL) {
notifyListener_l(MEDIA_SET_VIDEO_SIZE, 0, 0);
} else {
notifyVideoSize_l();
}
notifyListener_l(MEDIA_PREPARED);
}
// 设置 player 的状态
mPrepareResult = OK;
modifyFlags((PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED), CLEAR);
modifyFlags(PREPARED, SET);
mAsyncPrepareEvent = NULL;
// 同步线程之间的状态
mPreparedCondition.broadcast();
// mAudioTearDown 默认为 false,当暂停的时候通过回调方法将其改为 true
if (mAudioTearDown) {
if (mPrepareResult == OK) {
if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
seekTo_l(mAudioTearDownPosition);
}
if (mAudioTearDownWasPlaying) {
modifyFlags(CACHE_UNDERRUN, CLEAR);
play_l();
}
}
mAudioTearDown = false;
}
}

经过一番设置后,prepare的过程也算是完成了。这一部分主要是对player的状态进行设置,通过消息机制让让调用端也知道这边的player状态。

start

Java代码这边的start方法对应的是StagefrightPlayer中的start,其中又调用了player的play方法。

1
2
3
4
status_t StagefrightPlayer::start() {
ALOGV("start");
return mPlayer->play();
}

对应的是AwesomePlayer中的play方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
status_t AwesomePlayer::play() {
ATRACE_CALL();
Mutex::Autolock autoLock(mLock);
modifyFlags(CACHE_UNDERRUN, CLEAR);
return play_l();
}
status_t AwesomePlayer::play_l() {
modifyFlags(SEEK_PREVIEW, CLEAR);
// 如果正在播放,则什么也不做
if (mFlags & PLAYING) {
return OK;
}
mMediaRenderingStartGeneration = ++mStartGeneration;
// 如果没有之前没有调用 prepare,这里会帮你调用一次,顺便我还做了个实验,create 之后 直接 start 是可以播放的,并没有报错什么的。
if (!(mFlags & PREPARED)) {
status_t err = prepare_l();
if (err != OK) {
return err;
}
}
modifyFlags(PLAYING, SET);
modifyFlags(FIRST_FRAME, SET);
if (mDecryptHandle != NULL) {
int64_t position;
getPosition(&position);
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::START, position / 1000);
}
if (mAudioSource != NULL) {
if (mAudioPlayer == NULL) {
createAudioPlayer_l();
}
CHECK(!(mFlags & AUDIO_RUNNING));
if (mVideoSource == NULL) {
// We don't want to post an error notification at this point,
// the error returned from MediaPlayer::start() will suffice.
status_t err = startAudioPlayer_l(false /* sendErrorNotification */);
// 是否可以硬解音频
if ((err != OK) && mOffloadAudio) {
ALOGI("play_l() cannot create offload output, fallback to sw decode");
int64_t curTimeUs;
getPosition(&curTimeUs);
delete mAudioPlayer;
mAudioPlayer = NULL;
// if the player was started it will take care of stopping the source when destroyed
if (!(mFlags & AUDIOPLAYER_STARTED)) {
mAudioSource->stop();
}
modifyFlags((AUDIO_RUNNING | AUDIOPLAYER_STARTED), CLEAR);
mOffloadAudio = false;
mAudioSource = mOmxSource;
if (mAudioSource != NULL) {
err = mAudioSource->start();
if (err != OK) {
mAudioSource.clear();
} else {
mSeekNotificationSent = true;
if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
seekTo_l(curTimeUs);
}
createAudioPlayer_l();
// 播放音频
err = startAudioPlayer_l(false);
}
}
}
if (err != OK) {
delete mAudioPlayer;
mAudioPlayer = NULL;
modifyFlags((PLAYING | FIRST_FRAME), CLEAR);
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle, Playback::STOP, 0);
}
return err;
}
}
}
// timesource 用于视音频同步,通常来说是根据 audio 中的 timesource 为基准,
// 这里判断下是否有音频时间和音频播放器,如果没有,则采用系统的时间
if (mTimeSource == NULL && mAudioPlayer == NULL) {
mTimeSource = &mSystemTimeSource;
}
// 播放视频
if (mVideoSource != NULL) {
// Kick off video playback
postVideoEvent_l();
if (mAudioSource != NULL && mVideoSource != NULL) {
postVideoLagEvent_l();
}
}
// 流的结尾
if (mFlags & AT_EOS) {
// Legacy behaviour, if a stream finishes playing and then
// is started again, we play from the start...
seekTo_l(0);
}
uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted
| IMediaPlayerService::kBatteryDataTrackDecoder;
if ((mAudioSource != NULL) && (mAudioSource != mAudioTrack)) {
params |= IMediaPlayerService::kBatteryDataTrackAudio;
}
if (mVideoSource != NULL) {
params |= IMediaPlayerService::kBatteryDataTrackVideo;
}
addBatteryData(params);
if (isStreamingHTTP()) {
postBufferingEvent_l();
}
return OK;
}

视频的播放是通过不断的postVideoEvent_l来实现画面的播放,postVideoEvent_l会向队列中post一个videoEvent,这个event最终又会触发AwesomePlayer::onVideoEvent的方法,onVideoEvent方法中又会调用postVideoEvent_l,形成循环,最终实现视频的播放。startAudioPlayer_l最终会调用到AudioPlayer播放音频。

播放的过程到这里就结束了,接下来暂停和结束的部分就相对简单了。

pause

pause对应到AwesomePlayer中是pause_l这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
status_t AwesomePlayer::pause_l(bool at_eos) {
...
// 通知客户端播放器暂停
notifyListener_l(MEDIA_PAUSED);
mMediaRenderingStartGeneration = ++mStartGeneration;
// cancel 播放视频的队列
cancelPlayerEvents(true /* keepNotifications */);
if (mAudioPlayer != NULL && (mFlags & AUDIO_RUNNING)) {
// If we played the audio stream to completion we
// want to make sure that all samples remaining in the audio
// track's queue are played out.
mAudioPlayer->pause(at_eos /* playPendingSamples */);
// send us a reminder to tear down the AudioPlayer if paused for too long.
if (mOffloadAudio) {
postAudioTearDownEvent(kOffloadPauseMaxUs);
}
modifyFlags(AUDIO_RUNNING, CLEAR);
}
if (mFlags & TEXTPLAYER_INITIALIZED) {
mTextDriver->pause();
modifyFlags(TEXT_RUNNING, CLEAR);
}
modifyFlags(PLAYING, CLEAR);
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::PAUSE, 0);
}
...
return OK;
}

stop

然而stop和pause并没什么区别,代码中的注释也是挺有意思

1
2
3
4
5
6
7
8
9
status_t StagefrightPlayer::stop() {
ALOGV("stop");
return pause(); // what's the difference?
}
status_t StagefrightPlayer::pause() {
ALOGV("pause");

return mPlayer->pause();
}

release

MeidaPlayer对应jni中的android_media_MediaPlayer_release方法:

1
2
3
4
5
6
7
8
9
10
11
12
static void
android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
{
ALOGV("release");
decVideoSurfaceRef(env, thiz);
sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0);
if (mp != NULL) {
// this prevents native callbacks after the object is released
mp->setListener(0);
mp->disconnect();
}
}

setListener注释中说的很明白了,就是把listener置空。diconnect这里是断开了和mediaserver的连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
void MediaPlayer::disconnect()
{
ALOGV("disconnect");
sp<IMediaPlayer> p;
{
Mutex::Autolock _l(mLock);
p = mPlayer;
mPlayer.clear();
}
if (p != 0) {
p->disconnect();
}
}

总结

MediaPlayer整体上的流程就是这些,其中相对复杂的地方集中在编解码和不同player的使用上。编解码相关的主要是OMX的硬解和软解,player主要是不同的Android版本对不同的player优先使用不同。

参考

Media Playback

MediaPlayer

Media

深入理解 Android I


MediaPlayer的API使用及源码分析
https://blog.siantao.top/技术/计算机/软件/Android/多媒体/媒体播放/MediaPlayer的API使用及源码分析/
作者
玉水仙楊
发布于
2022年6月1日
许可协议