最近在学习视频播放器相关的项目,其中用的是MediaPlayer来做的,觉得不能只是停留在表面调用上,于是抽时间看了一下MediaPlayer的底层源码,做个记录,方便复习回顾。通过了解底层的源码以方便你熟悉这个动作执行的流程及遇到bug时的快速排查, MediaPlayer是Android中一个多媒体播放类,我们能通过它控制音视频流或本地音视频资源的播放过程。
2 MediaPlayer的常见状态:2.1 Idle 2.2 End 2.3 Error 2.4 Initialized 2.5 Prepared 2.6 Preparing 2.7 Started 2.8 Paused 2.9 Stopped 2.10 PlaybackCompleted状态
3 Mediaplayer的实例化, 3.1 MediaPlayer mp=new MediaPlayer() 3.2 MediaPlayer mp=MediaPlayer.create(this,R.raw.test);主要看3.1的方式,源码如下:
public MediaPlayer() {
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;
}
//时间数据容器,一般provider都是和数据联系起来。
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
//开始创建MediaPlayer
native_setup(new WeakReference<MediaPlayer>(this));
baseRegisterPlayer();
}
PlayerBase.java
protected void baseRegisterPlayer() {
int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
//通过binder机制获得系统原生服务,用于打开摄像头,获取声音等。
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
// initialize mHasAppOpsPlayAudio
updateAppOpsPlayAudio();
// register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
mAppOpsCallback = new IAppOpsCallbackWrapper(this);
try {
mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
ActivityThread.currentPackageName(), mAppOpsCallback);
} catch (RemoteException e) {
Log.e(TAG, "Error registering appOps callback", e);
mHasAppOpsPlayAudio = false;
}
try {
newPiid = getService().trackPlayer(
new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this)));
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
}
mPlayerIId = newPiid;
}
MediaPlayer.java
static {
//加载 链接库文件
System.loadLibrary("media_jni");
native_init();
}
android_media_MediaPlayer.cpp
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
jclass clazz; //类的句柄
//通过操作符(->)访问JNI中的函数
//通过Native层调用Java层,获取MediaPlayer对象
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}
//获取成员变量mNativeContext,它在MediaPlayer.java中是一个long型整数,实际对应的是一个
//内粗地址
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
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;
}
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;");
}
分析:通过JNI调用Java层的MediaPlayer类,然后拿到mNativeContext的指针,接着调用MediaPlayer.java中的静态方法postEventFromNative,把Native的事件回调到Java层,使用EventHandler post 事件回到主线程中,用软引用指向原生的MediaPlayer,以保证Native代码是安全的。如下:
private static void postEventFromNative(Object mediaplayer_ref,
int what, int arg1, int arg2, Object obj)
{
final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref)
.get();//得到软引用对象。
if (mp == null) {
return;
}
...
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
android_media_MediaPlayer.cpp
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);
}
4 mediaPlayer.setDataSource(url); 源码:
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
setDataSource(path, null, null);
}
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;
}
}
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) {
// 处理非文件资源
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
return;
}
// 处理文件资源
final File file = new File(path);
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
FileDescriptor fd = is.getFD();//得到文件标识符
setDataSource(fd);
is.close();
} else {
throw new IOException("setDataSource failed.");
}
}
private native void nativeSetDataSource(
IBinder httpServiceBinder, String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
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},
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
...
省略相关映射native函数声明
};
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
//得到MediaPlayer对象
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;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("setDataSourceFD: fd %d", fd);
process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}
static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
{
if (exception == NULL) { // 不抛出异常,发送一个onError事件
if (opStatus != (status_t) OK) {
//得到MediaPlayer对象
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
}
} else { // Throw exception!
if ( opStatus == (status_t) INVALID_OPERATION ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
}
...
}
}
分析:通过setDataSource(...),函数得到状态后,对各种状态进行通知。有异常的直接抛出,这样也就不会影响MediaPlayer后面的执行过程了。