第二章 常用的系统播放器 MediaPlayer
第二章 常用的系统播放器 MediaPlayer
此为该系列第二章
目录:https://heyanle.com/archives/androidyin-shi-pin-kai-fa
2.1 状态图及生命周期
- MediaPlayer 的状态图
其中单箭头为同步方法调用,双箭头为异步方法调用
-
Idle 状态及 End 状态
Idle 状态可以理解为空闲状态,当创建实例或者调用 reset 后,就处于该状态。调用 release 后,就会变成 End 状态,在这两种状态之间就是 MediaPlayer 的生命周期
-
Error 状态
当在 Idle 状态调用各种方法,如:getCurrentPosition、getVideoHeight、getDuration、getVideoWidth、setAudioStreamType(int)、setLooping(boolean)、setVolume(float, float)、pause、start、stop、seekTo(int)、prepare、prepareAsync 这些方法时会出错,用户提供的回调函数 OnErrorListener 将会被触发。MediaPlayer 将来到 Error 状态,因此当 MediaPlayer 不再使用时,需要调用 release 方法释放。
当 MediaPlayer 处于 End 状态时,它将不能再被使用,这是不能再回到其他状态,此次生命周期已经终止。
在播放过程中,如果发生错误(如输入数据流超时)时,也会来到 Error 状态,此时用户的 OnErrorListener 将会被调用。我们可以在回调里获取错误信息并调用 reset 方法回到 Idle 状态后重新播放。
-
Initialized 状态
当在 Idle 状态调用 setDataSource 的各种重载方法设定数据源时,会变为 Initialized 状态。如果在 非 Idle 状态调用,则会抛出 IllegalStateException 异常。
-
Prepared 状态
有两种方式,同步与异步,同步状态多用于本地文件,在调用 prepare 后将会从 Initialized 状态变为 Prepared 状态,该方法为同步方法,会阻塞到准备完毕。异步一般为网络视频,需要缓冲,调用 prepareAsync 将会从 Initialized 状态变为 Preparing 状态,此时方法会直接返回,当准备完毕后会变为 Prepared 状态。
用户可以设置 Prepared 状态的回调。但是没有 Preparing 状态的回调,该状态只是临时状态(如果准备成功会进入 Prepared ,失败会进入 Error,不会长时间停留在该状态),并不作为一个严格意义上的生命周期状态。
-
Started 状态
在 MediaPlayer 进入 Prepared 状态后,应用层可以设置一些属性,如音视频的音频、screenOnWhilePlaying、Looping 等。在播放控制开始之前,必须调用 start 函数并成功返回。在 Started 状态下,用户设定的 OnBufferingUpdate 回调将会被调用。该回调使应用能保持跟踪音视频流的 buffering status,如果 MediaPlayer 已经处于 Started 状态,在调用 start 方法没有任何作用。
-
Paused 状态
MediaPlayer 在播放控制时可以使 Paused 和 Stopped 状态的,且当前的播放进度可以调整。当调用 pause 方法时,状态将变为 Pause 状态。但是反之为异步过程,在调用 start 后,将会调用 playback 恢复之前暂停的位置,然后再开始播放。
-
Stopped 状态
当调用 stop 方法时,无论处于 Started、Paused、Prepared 或 PlaybackCompleted 中的哪种状态都会进入 Stop 状态,此时将无法 playback,只能重新调用 Prepared 或 Preparing 方法重新回到 Prepared 状态
-
PlaybackCompleted 状态
当播放完毕后,如果当前 Looping 设置为 false,则会进入 PlaybackCompleted 状态,此时调用 start 方法,则会回到 Started 状态并重新播放。如果我们设置 Looping 为 true,则不会进入 PlaybackCompleted 状态,直接在 Started 状态就从头播放。用户可以设置 OnCompletionListener 回调在进入 PlaybackCompleted 状态时触发。
2.2 从创建到 setDataSource 过程
2.2.1 从创建到 setDisplay 过程
类加载时通过 native_init 加载 native 代码
在 create 方法中使用 new 创建实例最终调用构造方法
(在最新的源码中没看到,估计是不需要了)在构造方法中调用 ServiceManager 的 getService 方法获取 AppOpsService(书中这里说是获取 MediaPlayerService,但是给出的源码是获取 AppOpsService),这里使用 Binder 来进行 IPC。
调用 native_setup 方法创建播放器(这里需要传入一个 MediaPlayer 对象的 WeakRefrence,书中说是软引用,我笑了),接着调用 setDataSource 把 URL 传入底层
通过 setDisplay 传入 SurfaceHolder
2.2.2 创建过程
这里主要看 create 方法:
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
// 实例化
MediaPlayer mp = new MediaPlayer(audioSessionId);
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
// 设置 音频处理参数
mp.setAudioAttributes(aa);
// 设置音频处理 Session ID
mp.native_setAudioSessionId(audioSessionId);
// setDataSource
mp.setDataSource(context, uri);
if (holder != null) {
// setDisplay
mp.setDisplay(holder);
}
// prepare
mp.prepare();
return mp;
} catch (IOException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (IllegalArgumentException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (SecurityException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
}
return null;
}
主要是实例化对象,音频处理与 setDataSource 和 prepare 方法。当调用完 create 方法后,直接变为 Prepared 状态,此时可以调用 start 开始播放。
之后来看看构造方法(这里为我自己分析的 安卓 12 的源码,与书本有出入)
private MediaPlayer(int sessionId) {
super(new AudioAttributes.Builder().build(),
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
// 获取时间提供者(用于时间?)
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
AttributionSource attributionSource = AttributionSource.myAttributionSource();
// set the package name to empty if it was null
if (attributionSource.getPackageName() == null) {
attributionSource = attributionSource.withPackageName("");
}
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
// 调用 native 层的 setup 方法创建 native 层的 mediaplayer,这里传入弱引用与 sourceState 的 序列化
native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
}
baseRegisterPlayer(sessionId);
}
接下来来到 native 层,首先先看看 MediaPlayer 的 static 代码块:
static {
System.loadLibrary("media_jni");
native_init();
}
首先是连接上 android_media_MediaPlayer.cpp 文件,然后调用 native_init 方法,现在来看看该 native 方法:
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaPlayer, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
jclass clazz;
// 获取 MediaPlayer.class 对象对应的结构体
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}
// 获取 MediaPlayer 中 mNativeContext 变量的 id,为 Long 类型
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
// 获取 static postEventFromNative 方法 ID
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
if (fields.surface_texture == NULL) {
return;
}
env->DeleteLocalRef(clazz);
clazz = env->FindClass("android/net/ProxyInfo");
if (clazz == NULL) {
return;
}
fields.proxyConfigGetHost =
env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
fields.proxyConfigGetPort =
env->GetMethodID(clazz, "getPort", "()I");
fields.proxyConfigGetExclusionList =
env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
env->DeleteLocalRef(clazz);
gBufferingParamsFields.init(env);
// Modular DRM
FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
if (clazz) {
GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
env->DeleteLocalRef(clazz);
} else {
ALOGE("JNI android_media_MediaPlayer_native_init couldn't "
"get clazz android/media/MediaDrm$MediaDrmStateException");
}
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
gVolumeShaperFields.init(env);
}
可以看到主要是初始化 fields 这个结构体,其定义如下:
struct fields_t {
jfieldID context;
jfieldID surface_texture;
jmethodID post_event;
jmethodID proxyConfigGetHost;
jmethodID proxyConfigGetPort;
jmethodID proxyConfigGetExclusionList;
};
static fields_t fields;
这里不做过多详解。
其中 post_event 为 MediaPlayer 中静态方法 postEventFromNative 的 id,该方法可以从 native 层代码回到 java 层,通过给构造方法时创建的 EventHandler 来实现线程切换,这里主要为实现我们注册的各种回调的调用,与一些自带事件的传递:
private static void postEventFromNative(Object mediaplayer_ref,
int what, int arg1, int arg2, Object obj)
{
// 第一个参数为 之前传入 native 的 mediaplayer 弱引用
final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
if (mp == null) {
return;
}
switch (what) {
case MEDIA_INFO:
if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
new Thread(new Runnable() {
@Override
public void run() {
// this acquires the wakelock if needed, and sets the client side state
mp.start();
}
}).start();
Thread.yield();
}
break;
case MEDIA_DRM_INFO:
// We need to derive mDrmInfo before prepare() returns so processing it here
// before the notification is sent to EventHandler below. EventHandler runs in the
// notification looper so its handleMessage might process the event after prepare()
// has returned.
Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
if (obj instanceof Parcel) {
Parcel parcel = (Parcel)obj;
DrmInfo drmInfo = new DrmInfo(parcel);
synchronized (mp.mDrmLock) {
mp.mDrmInfo = drmInfo;
}
} else {
Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
}
break;
case MEDIA_PREPARED:
// By this time, we've learned about DrmInfo's presence or absence. This is meant
// mainly for prepareAsync() use case. For prepare(), this still can run to a race
// condition b/c MediaPlayerNative releases the prepare() lock before calling notify
// so we also set mDrmInfoResolved in prepare().
synchronized (mp.mDrmLock) {
mp.mDrmInfoResolved = true;
}
break;
}
// 回调事件主要走这个逻辑
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
然后我们来看看之前 MediaPlayer 构造方法中调用的 native_setup 方法:
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
ALOGV("native_setup");
// 使用智能指针 new 一个 MediaPlayer 对象
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
// 创建 监听器 然后 调用 mp->setListener 方法
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.
// 将我们新的 C++ MediaPlayer 对象放置到 一个 java 对象中的不透明的 对象
// 其实就是将 MediaPlayer 对象的地址放到 java 层中
setMediaPlayer(env, thiz, mp);
}
这里 setMediaPlayer 中主要就是将我们 CPP 层 new 的 MediaPlayer 对象的地址,以 Long 的形式赋值 Java 层的 MediaPlayer.mNativeContext 变量,之后就可以使用。
2.2.3 setDataSource 过程
创建完毕后 MediaPlayer 为 Idle 状态,接下来需要调用 setDataSource 进入初始化模式,来到该方法:
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// 调用重载
setDataSource(path, null, null);
}
@UnsupportedAppUsage
public void setDataSource(String path, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// 调用重载
setDataSource(path, headers, null);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
String[] keys = null;
String[] values = null;
if (headers != null) {
keys = new String[headers.size()];
values = new String[headers.size()];
int i = 0;
for (Map.Entry<String, String> entry: headers.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
++i;
}
}
// 以上主要处理一个播放列表多个视频,如果只给出 url,则会直接走这个逻辑
setDataSource(path, keys, values, cookies);
}
可以看到最终都是会调用四个参数的方法,我们重点来到该方法:
private void setDataSource(String path, String[] keys, String[] values,
List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
if ("file".equals(scheme)) {
// 如果是本地文件
path = uri.getPath();
} else if (scheme != null) {
// 如果不是文件
// handle non-file sources
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
return;
}
// 文件直接获取文件描述符并设置
final File file = new File(path);
try (FileInputStream is = new FileInputStream(file)) {
setDataSource(is.getFD());
}
}
可以看到主要分两中情况,如果是网络URL,则会调用 MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies)
获取一个 IBinder
,然后传递给 nativeSetDataSource
方法,如果是文件则直接获取文件描述符,并调用 setDataSource
方法,我们先来看文件描述符的方法:
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
public void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
try (ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd)) {
if (modernFd == null) {
_setDataSource(fd, offset, length);
} else {
_setDataSource(modernFd.getFileDescriptor(), offset, length);
}
} catch (IOException e) {
Log.w(TAG, "Ignoring IO error while setting data source", e);
}
}
private native void _setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException;
主要是调用 native 方法,我们来到 native 层:
这里使用 ndk 动态注册,方法名不同,但不妨碍理解
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
if (fileDescriptor == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
// 将 Java 中的文件描述符转换为 native 层的 linux 原生的文件描述符
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("setDataSourceFD: fd %d", fd);
// process_media_player_cal 为一个封装,确保状态安全,根据传入的结果判断需不需要抛出异常等操作,这里传入的是 mp->setDataSource(fd, offset, length) 的返回值
// 后面会进行分析
process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}
然后我们来到 网络状态,首先是 MediaHTTPService.createHttpServiceBinderIfNecessary 方法:
static IBinder createHttpServiceBinderIfNecessary(
String path, List<HttpCookie> cookies) {
if (path.startsWith("http://") || path.startsWith("https://")) {
return (new MediaHTTPService(cookies)).asBinder();
} else if (path.startsWith("widevine://")) {
Log.d(TAG, "Widevine classic is no longer supported");
}
return null;
}
该方法返回一个 MediaHTTPService 对象,该对象有一个 makeHTTPConnection 方法可以获取一个 HTTP 连接,最终是使用 native 层的网络连接逻辑,这里先暂时不展开。MediaHTTPService 实现了 IBinder 对象,作为一个接口。
构造完该方法后,会调用:
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
该方法为一个 native 方法,我们来到该方法的 native 层
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
jobjectArray keys, jobjectArray values) {
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
if (path == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
const char *tmp = env->GetStringUTFChars(path, NULL);
if (tmp == NULL) { // Out of memory
return;
}
ALOGV("setDataSource: path %s", tmp);
String8 pathStr(tmp);
env->ReleaseStringUTFChars(path, tmp);
tmp = NULL;
// We build a KeyedVector out of the key and val arrays
KeyedVector<String8, String8> headersVector;
if (!ConvertKeyValueArraysToKeyedVector(
env, keys, values, &headersVector)) {
return;
}
sp<IMediaHTTPService> httpService;
if (httpServiceBinderObj != NULL) {
// 将 java 层 的 IBinder 对象转为 CPP 层的 IBinder 对象
sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
// 强转成 IMediaHTTPService
httpService = interface_cast<IMediaHTTPService>(binder);
}
// 调用 mp->setDataSource 获取返回值
status_t opStatus =
mp->setDataSource(
httpService,
pathStr,
headersVector.size() > 0? &headersVector : NULL);
// 判断结果是否需要进行异常处理
process_media_player_call(
env, thiz, opStatus, "java/io/IOException",
"setDataSource failed." );
}
可以看到无论是本地文件还是网络视频,最终都是先调用到 native 层的 MediaPlayer 对象的 setDataSource 方法,其中本地文件传入 文件描述符,网络视频则最终构造成一个 IMediaHTTPService 的 Binder 接口。而在 process_media_player_call 中会进行异常处理,如果出现异常,则会调用回 Java 层,发送消息到 EventHandler 中,先来看看 process_media_player_call 方法:
static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
{
if (exception == NULL) { // Don't throw exception. Instead, send an event.
if (opStatus != (status_t) OK) {
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
//没有异常,直接发送一个 MEDIA_ERROR 事件给 EventHandelr
if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
}
} else { // Throw exception!
// 下面全部都为异常抛出
if ( opStatus == (status_t) INVALID_OPERATION ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
} else if ( opStatus == (status_t) BAD_VALUE ) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
} else if ( opStatus == (status_t) PERMISSION_DENIED ) {
jniThrowException(env, "java/lang/SecurityException", NULL);
} else if ( opStatus != (status_t) OK ) {
if (strlen(message) > 230) {
// if the message is too long, don't bother displaying the status code
jniThrowException( env, exception, message);
} else {
char msg[256];
// append the status code to the message
sprintf(msg, "%s: status=0x%X", message, opStatus);
jniThrowException( env, exception, msg);
}
}
}
}
这里主要调用 mp->notify 与 jniThrowException 两种方法,jniThrowException 实际上就是 ndk 里的抛出异常的方法,这里来看看第一个:
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
bool send = true;
bool locked = false;
// TODO: In the future, we might be on the same thread if the app is
// running in the same process as the media server. In that case,
// this will deadlock.
//
// The threadId hack below works around this for the care of prepare,
// seekTo, start, and reset within the same process.
// FIXME: Remember, this is a hack, it's not even a hack that is applied
// consistently for all use-cases, this needs to be revisited.
if (mLockThreadId != getThreadId()) {
mLock.lock();
locked = true;
}
// Allows calls from JNI in idle state to notify errors
if (!(msg == MEDIA_ERROR && mCurrentState == MEDIA_PLAYER_IDLE) && mPlayer == 0) {
ALOGV("notify(%d, %d, %d) callback on disconnected mediaplayer", msg, ext1, ext2);
if (locked) mLock.unlock(); // release the lock when done.
return;
}
// 省略一堆判断后调用 ALOGV 打log
// 主要调用 Listener ,Listener 在刚刚 setup 的时候创建
sp<MediaPlayerListener> listener = mListener;
if (locked) mLock.unlock();
// this prevents re-entrant calls into client code
if ((listener != 0) && send) {
Mutex::Autolock _l(mNotifyLock);
ALOGV("callback application");
listener->notify(msg, ext1, ext2, obj);
ALOGV("back from callback");
}
}
调用 MediaPlayerListener 的 notify 方法:
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (obj && obj->dataSize() > 0) {
jobject jParcel = createJavaParcelObject(env);
if (jParcel != NULL) {
Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
nativeParcel->setData(obj->data(), obj->dataSize());
// 通过 JNI 调用 fields.post_event 对应句柄的方法
env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
msg, ext1, ext2, jParcel);
env->DeleteLocalRef(jParcel);
}
} else {
env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
msg, ext1, ext2, NULL);
}
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
LOGW_EX(env);
env->ExceptionClear();
}
}
这里通过 CallStaticVoidMethod 方法调用 fields.post_event 对应 句柄的 方法,之前的创建过程来看,其实就是 MediaPlayer 的 postEventFromNative 方法,前面有分析。
综上,最终都是调用 mp->setDataSource 方法,这里该方法最终会具体到不同的播放器实现,等到之后会分析,这里暂时就分析到这里。
2.2.4 setDisplay 过程
setDataSource 之后,MediaPlayer 变为 Init 模式,接下来需要调用 Prepare 进行准备,但在此之间,可以调用 setDisplay 设定视频的输出位置。
public void setDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;
Surface surface;
if (sh != null) {
surface = sh.getSurface();
} else {
surface = null;
}
// 调用 native 方法给视频设置 surface
_setVideoSurface(surface);
// 更新 Surface 到屏幕上
updateSurfaceScreenOn();
}
接下来看看那个 native 方法:
static void
android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
{
setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
}
static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL) {
if (mediaPlayerMustBeAlive) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
}
return;
}
decVideoSurfaceRef(env, thiz);
sp<IGraphicBufferProducer> new_st;
if (jsurface) {
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
// 获取 IGraphicBufferProducer 对象
new_st = surface->getIGraphicBufferProducer();
if (new_st == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"The surface does not have a binding SurfaceTexture!");
return;
}
// 调用 incStrong 方法
new_st->incStrong((void*)decVideoSurfaceRef);
} else {
jniThrowException(env, "java/lang/IllegalArgumentException",
"The surface has been released");
return;
}
}
// 设置 java MediaPlayer 对象中的 surface_texture 变量为 GraphicBufferProducer 对象的地址
env->SetLongField(thiz, fields.surface_texture, (jlong)new_st.get());
// This will fail if the media player has not been initialized yet. This
// can be the case if setDisplay() on MediaPlayer.java has been called
// before setDataSource(). The redundant call to setVideoSurfaceTexture()
// in prepare/prepareAsync covers for this case.
// 如果这时 MediaPlayer 不处于 init 状态将会设置失败(例如在 setDataSource 前调用 setDisplay),
// 此时可以等在 prepare/prepareAsync 中调用 setVideoSurfaceTexture() 来设置
mp->setVideoSurfaceTexture(new_st);
}
static void
decVideoSurfaceRef(JNIEnv *env, jobject thiz)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL) {
return;
}
// 获取旧的 SurfaceTexture
sp<IGraphicBufferProducer> old_st = getVideoSurfaceTexture(env, thiz);
if (old_st != NULL) {
old_st->decStrong((void*)decVideoSurfaceRef);
}
}
先说明几个概念:
- SurfaceTexture:SurfaceTexture 是 Android 3.0 加入的一个类,这个类跟 SufaceView 很像,可以从视频解码里获取图像流(image stream)。其接收图像流后不需要显示出来,我们可以在这里接收图像流,并对副本进行一些处理再交给另一个 SurfaceView 显示。
- Surface:处理被屏幕排序的原生 Buffer,Android 中的 Surface 是用来画图形(graphic)或图像(image)的地方。对于 View 及其子类,都是画在 Surface 上的,各 Surface 对象通过 SurfaceFlinger 合成到 frameBuffer。每个 Surface 都是双缓冲的(一个渲染线程,一个 UI 更新线程),有一个 backBuffer 和一个 frontBuffer。Canvas 对象就是用来管理 Surface 的绘图操作。Canvas 对应 Bitmap,存储 Surface 中的内容。
- SurfaceView:SurfaceView 是 View 的子类,实现了 Parcelable 接口,内嵌了一个 Surface。使用 View 的方式控制这个 Surface 的格式和尺寸和位置。其实就是在常规 View 的区域里是 Surface 可以用于渲染。
- SurfaceHolder:管理 Surface 的容器。是一个接口,可被理解为一个 Surface 的监听器,通过回调函数监听 Surface 的创建,通过获取 Canvas 对象将其锁定,完成修改后释放锁并提交改变,展示新的图像数据。
2.3 开始 prepare 后的流程
这里书中介绍了 native 层与 MediaPlayer 有关的一些头文件和 cpp 文件,这里不做展开,直接给出一个依赖关系图。
MediaPlayer 大致上分为 Client 和 Server 两个部分,分别在两个进程中执行,使用 Binder 进行一个 IPC。
在我们 setDisplay 之后,就需要调用 prepare 或者 prepareAsync 方法进行准备。接下来分析其流程。
public void prepare() throws IOException, IllegalStateException {
// 调用
_prepare();
scanInternalSubtitleTracks();
// DrmInfo, if any, has been resolved by now.
synchronized (mDrmLock) {
mDrmInfoResolved = true;
}
}
来到 native 层:
static void
android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
// Handle the case where the display surface was set before the mp was
// initialized. We try again to make it stick.
// 再次调用 setVideoSurfaceTexture 设置 SurfaceTexture ,避免之前的提到的错误
sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz);
mp->setVideoSurfaceTexture(st);
// 调用 mp->prepare() 方法,判断结果
process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
}
这里调用 了 mp->prepare() 方法:
status_t MediaPlayer::prepare()
{
ALOGV("prepare");
// 初始化锁
Mutex::Autolock _l(mLock);
mLockThreadId = getThreadId();
if (mPrepareSync) {
mLockThreadId = 0;
return -EALREADY;
}
mPrepareSync = true;
// 调用 prepareAsync_l
status_t ret = prepareAsync_l();
if (ret != NO_ERROR) {
mLockThreadId = 0;
return ret;
}
if (mPrepareSync) {
// 等待同步锁
mSignal.wait(mLock); // wait for prepare done
mPrepareSync = false;
}
ALOGV("prepare complete - status=%d", mPrepareStatus);
mLockThreadId = 0;
return mPrepareStatus;
}
可以看到同步等待实际上就是调用 prepareAsync_l 方法然后使用一个同步锁去等待准备完成。
这里的 prepareAsync_l 方法等之后在介绍,以上就是 prepare 方法的大致过程。
这里 GraphicBufferProducer 是 App 和 BufferQueue 的重要桥梁,GraphicBufferProducer 承担着单个应用进程中的 UI 显示需求,与 BufferQueue 打交道。BPGraphicBufferProducer 是 GraphicBufferProducer 客户端的代理对象,负责和 SurfaceFlinger 交互。GraphicBufferProducer 通过 IGraphicBufferProducer 对象向 BufferQueue 拿到 Buffer,然后填充 UI,填充完毕后通知 SurfaceFlinger。
接下来来到 PrepareAsync 方法,该方法直接就是一个 native:
public native void prepareAsync() throws IllegalStateException;
因此直接来到 native 层:
static void
android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
// Handle the case where the display surface was set before the mp was
// initialized. We try again to make it stick.
// 同上
sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz);
mp->setVideoSurfaceTexture(st);
// 同上
process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );
}
来到 MediaPlayer::prepareAsync
status_t MediaPlayer::prepareAsync()
{
ALOGV("prepareAsync");
// 依然还是初始化锁
Mutex::Autolock _l(mLock);
// 但是这里直接返回,没有进行一个锁的等待操作
return prepareAsync_l();
}
最终两种准备方法都会来到 prepareAsync_l,因此最后来分析该方法:
status_t MediaPlayer::prepareAsync_l()
{
// 参数检查
if ( (mPlayer != 0) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {
if (mAudioAttributesParcel != NULL) {
// 设置参数
mPlayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel);
} else {
// 设置 音频 type
mPlayer->setAudioStreamType(mStreamType);
}
// 更改当前状态
mCurrentState = MEDIA_PLAYER_PREPARING;
// 调用 mPlayer->prepareAsync()
return mPlayer->prepareAsync();
}
ALOGE("prepareAsync called in state %d, mPlayer(%p)", mCurrentState, mPlayer.get());
return INVALID_OPERATION;
}
最终会调用 mPlayer->prepareAsync()
方法,这里 mPlayer 是一个 MediaPlayerService::Client 对象,来到该方法:
status_t MediaPlayerService::Client::prepareAsync()
{
ALOGV("[%d] prepareAsync", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
// 重点调用 MediaPlayerBase 的 prepareAsync 方法
status_t ret = p->prepareAsync();
#if CALLBACK_ANTAGONIZER
ALOGD("start Antagonizer");
if (ret == NO_ERROR) mAntagonizer->start();
#endif
return ret;
}
这里的 getPlayer() 返回的是 MediaPlayerBase 接口,一般有两个实现,AwesomePlayer 和 NuPlayer,其中 AwesomePlayer 在 安卓 L 被弃用(但是书中依然使用该源码),因此这里直接无视书本,改为分析 NuPlayer(部分,具体在 第五章 分析)
NuPlayer 中,最终实现了 MediaPlayerInterface 的为 NuPlayerDriver,因此这里会先来到 NuPlayerDriver::prepareAsync 方法
status_t NuPlayerDriver::prepareAsync() {
ALOGV("prepareAsync(%p)", this);
// 同步锁
Mutex::Autolock autoLock(mLock);
switch (mState) {
case STATE_UNPREPARED:
// 主要走这里,为准备状态
mState = STATE_PREPARING; // 更新状态
mIsAsyncPrepare = true; // 设定是否异步
mPlayer->prepareAsync(); // 调用 mPlayer
return OK;
case STATE_STOPPED:
// this is really just paused. handle as seek to start
mAtEOS = false;
mState = STATE_STOPPED_AND_PREPARING;
mIsAsyncPrepare = true;
mPlayer->seekToAsync(0, MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC /* mode */,
true /* needNotify */);
return OK;
default:
return INVALID_OPERATION;
};
}
可以看到主要是调用 mPlayer->prepareAsync(),这里的 mPlayer 就是 NuPlayer 对象,因此这里是调用 NuPlayer::prepareAsync()
void NuPlayer::prepareAsync() {
ALOGV("prepareAsync");
(new AMessage(kWhatPrepare, this))->post();
}
这里直接创建一个消息,然后 post,可以看出 NuPlayer 内部是使用消息队列的机制。
消息经过 Looper 的串行化,最后会回到 NuPlayer::OnMessageReceive 方法,这里只给出 kWhatPrepare 的消息处理:
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
// 省略其他种类消息处理
case kWhatPrepare:
{
ALOGV("onMessageReceived kWhatPrepare");
mSource->prepareAsync();
break;
}
}
}
这里是调用 mSource->prepareAsync(),这里的 mSource 在之前 调用 mp->setDataSource 的时候会调用到具体播放器的方法,在那里会将 mSource 赋值给对应的源,这里暂时不分析,等到后面分析 NuPlayer 的时候会在做分析。
然后来到 start 方法:
public void start() throws IllegalStateException {
// 判断是否需要延迟,来调用 startImpl();
//FIXME use lambda to pass startImpl to superclass
final int delay = getStartDelayMs();
if (delay == 0) {
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
// fail silently for a state exception when it is happening after
// a delayed start, as the player state could have changed between the
// call to start() and the execution of startImpl()
}
}
}.start();
}
}
private void startImpl() {
baseStart(0); // unknown device at this point
// 设置保持唤醒
stayAwake(true);
tryToEnableNativeRoutingCallback();
// 调用 native 方法
_start();
}
private PowerManager.WakeLock mWakeLock = null;
private void stayAwake(boolean awake) {
if (mWakeLock != null) {
if (awake && !mWakeLock.isHeld()) {
mWakeLock.acquire(); // 获取唤醒锁,保持不息屏
} else if (!awake && mWakeLock.isHeld()) {
mWakeLock.release();
}
}
mStayAwake = awake;
updateSurfaceScreenOn();
}
这里 mWakeLock 的获取方式如下,先获取 PowerManager 然后在获取
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer.class.getName());
2.4 C++ 中 MediaPlayer 的 C/S 架构
这里我们来分析 Java 的一个函数在 C++ 层的全过程,包括 server 端。
首先回到之前的 MediaPlayer::setDataSource 方法:
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;
// 获取 Server 端的 MediaPlayerService
const sp<IMediaPlayerService> service(getMediaPlayerService());
if (service != 0) {
// 调用 Create
sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
// 调用 setDataSource,这里返回值判断是否出错,如果出错调用 clear 清除
(NO_ERROR != player->setDataSource(fd, offset, length))) {
player.clear();
}
// 挖个坑,后面分析
err = attachNewPlayer(player);
}
return err;
}
这里获取了一个 MediaPlayerService,然后调用了 create 方法,接下来来到 MediaPlayerService::create 方法:
sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client,
audio_session_t audioSessionId)
{
// 获取 IPCThreadState 的 CallingPid
pid_t pid = IPCThreadState::self()->getCallingPid();
int32_t connId = android_atomic_inc(&mNextConnId);
// 构造一个 Client
sp<Client> c = new Client(
this, pid, connId, client, audioSessionId,
// 获取 IPCThreadState 的 CallingUid 用于身份验证
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);
// 添加到容器
mClients.add(w);
}
return c;
}
在 Anroid 中,ProcessState 是客户端和服务端的公共部分,作为 Binder 通信的基础。ProcessState 是一个 singleton 类,每个进程只有一个对象,这个对象负责打开 Binder 驱动,建立线程池。
在线程方面与之相关的对象是 IPCThreadState,每个线程都有一个 IPCThreadState 实例登记在 Linux 线程的上下文附属数据中,负责 Binder 的读取、写入和请求处理。IPCThreadState 建立时获取进程的 ProcessState 然后存在成员变量 mProcess 中,之后线程可以通过自己的 IPCThreadState 对象里的 mProcess 获取 ProcessState 就可以获取 Binder 句柄进行通信(只是为了说明,实际上 mProcess 没办法直接显式获取,需要调用 IPCThreadState 相关封装方法)。通过 IPCThreadState::transact 方法把 data 和 handle 填充进一个字段,然后进行进程通信。
接下来先来看看这个 Client 对象,其结构体定义如下:
class Client : public BnMediaPlayer {
// IMediaPlayer interface
virtual void disconnect();
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer);
virtual status_t setBufferingSettings(const BufferingSettings& buffering) override;
virtual status_t getBufferingSettings(
BufferingSettings* buffering /* nonnull */) override;
virtual status_t prepareAsync();
virtual status_t start();
virtual status_t stop();
virtual status_t pause();
virtual status_t isPlaying(bool* state);
virtual status_t setPlaybackSettings(const AudioPlaybackRate& rate);
virtual status_t getPlaybackSettings(AudioPlaybackRate* rate /* nonnull */);
virtual status_t setSyncSettings(const AVSyncSettings& rate, float videoFpsHint);
virtual status_t getSyncSettings(AVSyncSettings* rate /* nonnull */,
float* videoFps /* nonnull */);
virtual status_t seekTo(
int msec,
MediaPlayerSeekMode mode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC);
virtual status_t getCurrentPosition(int* msec);
virtual status_t getDuration(int* msec);
virtual status_t reset();
virtual status_t notifyAt(int64_t mediaTimeUs);
virtual status_t setAudioStreamType(audio_stream_type_t type);
virtual status_t setLooping(int loop);
virtual status_t setVolume(float leftVolume, float rightVolume);
// 省略部分代码
}
Client 的继承关系为 Client -> BnMediaPlayer -> IMediaPlayer。在之前的 Service 的 create 方法中,我们创建了一个 Client 对象,并加入一个容器里。这里的 player 变量是以 Client 来初始化的,可以认为 player 就是 Client 对象。后面会分析。
Client 是 MediaPlayerService 内部的一个类,因为 MediaPlayerService 运行在服务端,因此 Client 也运行在 服务端。
接下来分析一下其 SetDataSource 方法,之前调用了 player::setDataSource 实际上会调用 Client 的对应方法:
status_t MediaPlayerService::Client::setDataSource(
const sp<IMediaHTTPService> &httpService,
const char *url,
const KeyedVector<String8, String8> *headers)
{
ALOGV("setDataSource(%s)", url);
if (url == NULL)
return UNKNOWN_ERROR;
// 如果是网络源,先检查是否有权限
if ((strncmp(url, "http://", 7) == 0) ||
(strncmp(url, "https://", 8) == 0) ||
(strncmp(url, "rtsp://", 7) == 0)) {
if (!checkPermission("android.permission.INTERNET")) {
return PERMISSION_DENIED;
}
}
if (strncmp(url, "content://", 10) == 0) {
// 如果是 content 的源,则从 contentProvider 获取数据
// get a filedescriptor for the content Uri and
// pass it to the setDataSource(fd) method
String16 url16(url);
int fd = android::openContentProviderFile(url16);
if (fd < 0)
{
ALOGE("Couldn't open fd for %s", url);
return UNKNOWN_ERROR;
}
status_t status = setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus
close(fd);
return mStatus = status;
} else {
player_type playerType = MediaPlayerFactory::getPlayerType(this, url);
sp<MediaPlayerBase> p = setDataSource_pre(playerType);
if (p == NULL) {
return NO_INIT;
}
return mStatus =
setDataSource_post(
p, p->setDataSource(httpService, url, headers));
}
}
还记得我们最初在 MediaPlayer::setDataSource 中挖的坑,这里构造出 player 变量后,调用了 attachNewPlayer(player);,我们来看看:
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;
// 获取 Server 端的 MediaPlayerService
const sp<IMediaPlayerService> service(getMediaPlayerService());
if (service != 0) {
// 调用 Create
sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
// 调用 setDataSource,这里返回值判断是否出错,如果出错调用 clear 清除
(NO_ERROR != player->setDataSource(fd, offset, length))) {
player.clear();
}
// 挖个坑,后面分析
err = attachNewPlayer(player);
}
return err;
}
status_t MediaPlayer::attachNewPlayer(const sp<IMediaPlayer>& player)
{
status_t err = UNKNOWN_ERROR;
sp<IMediaPlayer> p;
{ // scope for the lock
// 同步锁
Mutex::Autolock _l(mLock);
if ( !( (mCurrentState & MEDIA_PLAYER_IDLE) ||
(mCurrentState == MEDIA_PLAYER_STATE_ERROR ) ) ) {
ALOGE("attachNewPlayer called in state %d", mCurrentState);
return INVALID_OPERATION;
}
clear_l();
// 将 mPlayer 赋值给 p,将传入的 player 赋值给 mPlayer
// mPlayer 在 MediaPlayer.h 中声明。
// 回顾之前的方法,可以看到许多 MediaPlayer 的调用都会调用其 mPlayer ,实际上就是这里的 Client 对象
// 这里进行了一个代理
// 实际上还存在 IPC 操作,这里 Client 是客户端中的代理类,最终会调用到服务端的对应方法
p = mPlayer;
mPlayer = player;
if (player != 0) {
mCurrentState = MEDIA_PLAYER_INITIALIZED;
err = NO_ERROR;
} else {
ALOGE("Unable to create media player");
}
}
if (p != 0) {
// 将旧的 player disconnect 一下
p->disconnect();
}
return err;
}
下面介绍一下 IMediaPlayer.h、mediaplayer.h、ImediaPlayerClient.h 的区别
- 从包结构上看:IMediaPlayer 和 IMediaPlayerClient.h 都在 frameworks/av/media/libmedia 中,而 mediaplayer.h 在 /av/include/media 包中
- 从功能上看,它们背负的职责不一样
IMediaPlayer 定义了一些播放器的基本方法,以及一个 disconnect 方法,其主要为提供给 java 层调用的接口。
BnMediaPlayer 定义了一个 onTransact 方法,用来供 Binder 来进行 IPC 调用
下面来看看 IMediaPlayerClient 的源码:
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <media/IMediaPlayerClient.h>
namespace android {
enum {
NOTIFY = IBinder::FIRST_CALL_TRANSACTION,
};
class BpMediaPlayerClient: public BpInterface<IMediaPlayerClient>
{
public:
// 内部定义了一个 BpMediaPlClient 类
explicit BpMediaPlayerClient(const sp<IBinder>& impl)
: BpInterface<IMediaPlayerClient>(impl)
{
}
virtual void notify(int msg, int ext1, int ext2, const Parcel *obj)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerClient::getInterfaceDescriptor());
data.writeInt32(msg);
data.writeInt32(ext1);
data.writeInt32(ext2);
if (obj && obj->dataSize() > 0) {
data.appendFrom(const_cast<Parcel *>(obj), 0, obj->dataSize());
}
remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
}
};
IMPLEMENT_META_INTERFACE(MediaPlayerClient, "android.media.IMediaPlayerClient");
// ----------------------------------------------------------------------
// 实现 onTransact 方法供 IPC 回调
status_t BnMediaPlayerClient::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
case NOTIFY: {
CHECK_INTERFACE(IMediaPlayerClient, data, reply);
int msg = data.readInt32();
int ext1 = data.readInt32();
int ext2 = data.readInt32();
Parcel obj;
if (data.dataAvail() > 0) {
obj.appendFrom(const_cast<Parcel *>(&data), data.dataPosition(), data.dataAvail());
}
notify(msg, ext1, ext2, &obj);
return NO_ERROR;
} break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
} // namespace android
IMedaPlayerClient 是描述一个 MediaPlayer 客户端的接口。