简介:
安卓通过MediaPlayer这个类提供了一整套接口呈现给给客户实现视音频的播放。可是凡事必究其根,我们今天就来看看安卓的MediaPlayer框架(基于Android 8.0)究竟是怎么实现的。因为框架层全是C/C++代码,建议读者拥有相关基础,没有也没关系,都能看懂。
概要:
先给出网上扒的MediaPlayer状态图,MediaPlayer常用的方法也就这些了。
因为音视频相关的编解码,解封装,渲染等操作需要大量的运算,所以谷歌将这些方法通过底层C/C++代码来实现了。但是java怎么调用的C++代码呢?于是JNI(java native interface)就应运而生了。JNI允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。
代码分析:
我们写java程序如果要用到视音频播放肯定少不了MediaPlayer,常见的调用方法大致如下:
mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.test);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.setLooping(true);
mediaPlayer.start();
是不是很熟悉?先通过creat()函数创建一个mediaPlayer,再设置音频流类型,设计播放窗口,就start(),一般都是固定的几步,可是它的底层是怎么运作的呢?我们现在来分析一下。就看creat()函数吧,跟着它往下读。
creat()
当然先去MediaPlayer.java里找一找了。
目录:xref: /frameworks/base/media/java/android/media/MediaPlayer.java
//往下调用
public static MediaPlayer create(Context context, int resid) {
int s = AudioSystem.newAudioSessionId();
return create(context, resid, null, s > 0 ? s : 0);
}
//还是要预先创建一个自己,然后调用setDataSource()函数
public static MediaPlayer create(Context context, int resid,
AudioAttributes audioAttributes, int audioSessionId) {
try {
AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
if (afd == null) return null;
MediaPlayer mp = new MediaPlayer();
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
mp.setAudioSessionId(audioSessionId);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
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;
}
public void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
_setDataSource(fd, offset, length);
}
private native void _setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException;
我把这几个相关的函数穿了起来,层层调用。java对上层那么多的重载函数,在底层都是互相调用,最终归为一两个特别的函数上。还记得初学java的MediaPlayer时,网上说可以new一个MediaPlayer也可以不new,直接调用creat()函数,我觉得不可思议,现在算是真相大白了。creat()函数在内部先new了一个MediaPlayer来处理业务,然后调用MediaPlayer的setDataSource()函数来设置播放资源,setDataSource()函数是我们研究框架的开路先锋,他是最重要的一个函数了。注意最后一个函数的native标志,它标示了这是一个底层函数的声明,你在当前目录是找不到它的定义的,这就是JNI。
但是看native层的代码之前,我们注意到在MediaPlayer.java的构造函数之前有一段静态代码段:
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
嗯?提前调用了native层的native_init方法。没完呢,我们注意到在MediaPlayer.java的构造函数里也有一段native代码:
native_setup(new WeakReference<MediaPlayer>(this));
private native final void native_setup(Object mediaplayer_this);
这两个native函数在new一个MediaPlayer时就会调用,别放过,很重要,我们马上就看看。
JNI:
JNI的全称就是Java Native Interface,就是Java和C/C++相互通信的接口,实现了一个工程,多种语言并存。那么什么时候需要用到JNI呢?
1.需要调用java语言不支持的依赖于操作系统平台的特性的一些功能;
2.为了整合一些以前使用非java语言开发的某些系统,例如C和C++;
3.为了节省程序的运行时间,必须采用一些低级或中级语言。
关于JNI的用法请看:JNI的用法
MediaPlayer.java调用了底层JNI,根据JNI特有的命名规则,我们要找android_media_MediaPlayer.cpp文件。好了我们上面看到java层调用了native层的函数,有native_init(),native_setup(),_setDataSource(),然后我们兴高采烈的在android_media_MediaPlayer.cpp文件中并没有找到这三个函数的定义,什么情况?
不慌,细心的人就会发现JNI有这么一块代码,实现了对函数换名!
xref: /frameworks/base/media/jni/android_media_MediaPlayer.cpp
static const 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 *