Android系統播放器MediaPlayer原始碼分析(一)
前言
對於MediaPlayer播放器的原始碼分析內容相對來說比較多,會從Java->JNI->C/C++慢慢分析,後面會慢慢更新。另外,部落格只作為自己學習記錄的一種方式,對於其他的不過多的評論。
MediaPlayerDemo
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private static final String TAG = "MainActivity";
private SurfaceView surfaceView;
private MediaPlayer mediaPlayer;
private SurfaceHolder holder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
surfaceView= findViewById(R.id.surfaceView);
mediaPlayer=new MediaPlayer();
holder = surfaceView.getHolder();
holder.addCallback(this);
try {
mediaPlayer.setDataSource("/sdcard/m.mp4");
mediaPlayer.prepare();
/**
* 阻塞準備,多用於網路視訊流
*/
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mediaPlayer.setDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mediaPlayer.release();//釋放
}
}
從建立到setDisplay過程時序圖
建立過程
建立MediaPlayer物件有兩種,一種是create,一種是直接new,先來看create方法。
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
//1. new一個MediaPlayer
MediaPlayer mp = new MediaPlayer();
//2.如果音訊相關處理為空,就建立一個音訊屬性
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa); //3.設定音訊屬性
mp.setAudioSessionId(audioSessionId); //4.設定聲音的會話id,因為聲音和視訊是分開渲染的,而且在後面根據音視訊進行分別解碼的時候,就需要根據id來判斷流是音訊還是視訊
mp.setDataSource(context, uri);//5.設定資料來源
if (holder != null) { // surfaceHolder,控制surface
mp.setDisplay(holder);
}
mp.prepare(); //準備
return mp;
} catch (IOException ex) {
//省略,這個就是對各種Exception的處理
}
return null;
}
總結,這裡可以看出來,使用create()建立播放器的時候,內部會new一個新的mediaplayer,並且設定了資料來源,並且做好了prepare準備工作,也就是說在外部再呼叫一下start()就可以播放音視訊資源了。
還有一種方式就是new MediaPlayer也就是我demo裡的那種,下面看下原始碼
public MediaPlayer() {
Looper looper; //定義一個looper
//如果Mylooper不為空,就賦值
if ((looper = Looper.myLooper()) != null) {
//例項化mEventHandler物件
mEventHandler = new EventHandler(this, looper);
} //如果主執行緒的looper不為空,也可以賦值到looper中
else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
//時間資料容器
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
* 使用native_setup開始建立MediaPlayer了,此處為軟引用,為了在C++中更好的建立MediaPlayer
*/
native_setup(new WeakReference<MediaPlayer>(this));
}
EventHandler是為MediaPlayer的一個內部類,繼承與Handler,用來處理各種訊息:MEDIA_PREPARED,MEDIA_PLAYBACK_COMPLETE,MEDIA_STOPPED,MEDIA_SEEK_COMPLETE等等,分別呼叫不同的介面來進行處理。最後是呼叫了native層的方法來進行建立,那麼肯定需要先載入.so檔案,所以在MediaPlayer中有一段靜態程式碼塊,用來載入和連結庫檔案
static {
System.loadLibrary("media_jni");//media_jni.so
native_init();
}
進入到android_media_MediaPlayer.cpp分析,檔案目錄在:frameworks/base/media/jni/android_media_MediaPlayer.cpp
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
jclass clazz;//類的控制代碼
//這裡是通過Native層呼叫Java層,獲取到MediaPlayer物件
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}
// 獲取Java層的mNativeContext成員變數,實際上對應的是一個記憶體地址,就是jni層的MediaPlayer物件
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
//獲取Java層靜態方法,第一個引數是類,第二個引數是方法名,第三個引數是該方法的簽名
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
native_init方法是通過JNI呼叫Java層的MediaPlayer類,然後拿到mNativeContext的指標,接著呼叫了MediaPlayer.java的靜態方法postEventFromNative,這個方法的作用就是把Native事件回撥到Java層,然後使用EventHandler post事件回到主執行緒,使用軟引用指向原生的MediaPlayer,是為了保證native層的程式碼是安全的。
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
//在jni層建立了一個MediaPlayer
sp<MediaPlayer> mp = new MediaPlayer();
//給MediaPlayer建立了一個一個listener
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.將這個建立好的jni物件儲存到Java中
setMediaPlayer(env, thiz, mp);
}
來看下這個JNIMediaPlayerListener,也是在這個cpp檔案裡
class JNIMediaPlayerListener: public MediaPlayerListener
{
public:
JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
~JNIMediaPlayerListener();
virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);
private:
JNIMediaPlayerListener();
jclass mClass; // Reference to MediaPlayer class
jobject mObject; // Weak ref to MediaPlayer Java object to call on
};
有個notify方法,從下面可以看出,它的作用就是從JNI層向Java層通知
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());
//向Java層傳送通知事件
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);
}
}
fields.post_event也就是Java層的MediaPlayer.java中的postEventFromNative,可以看下它的實現
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.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
上面也說過是使用mEventHandler來處理的
來看下setMediaPlayer(env, thiz, mp)這個函式
static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
Mutex::Autolock l(sLock);//鎖
sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
//將新的Player物件儲存在Java層的mNativeContext中
env->SetLongField(thiz, fields.context, (jlong)player.get());
return old;
}
總結一下,在Java使用new或者create()建立MediaPlayer的時候,最後都會呼叫到native_up函式,這個函式的作用就是建立c++層的MediaPlayer以及去設定一些回撥用的listener,然後將其儲存在Java層中,到此建立過程就完成了。
setDataSource過程
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) {
// 1、處理非檔案資源
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
return;
}
//2、處理檔案型別
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.");
}
}
先來看下處理檔案型別的,點進去發現最後都是調到了native層,
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
//省略,一些異常程式碼
//獲取path變數
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
// 對mp->setDataSource(fd, offset, length)函式得到的狀態,進行相應的通知
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) { // Don't throw exception. Instead, send an event.
if (opStatus != (status_t) OK) { //不丟擲異常,傳送一個事件來替代異常
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
//通知MEDIA_ERROR,也就是傳送了一個通知事件
if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
}
} else { // Throw exception! 省略了
}
}
再來看下非檔案型別的:
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,jobjectArray keys, jobjectArray values) {
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
//省略異常程式碼
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) {
//通過Binder機制將httpServiceBinderObj傳給IPC並返回binder
//後面會具體分析,這裡只是JNI層
sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
//強制轉換成IMediaHTTPService
httpService = interface_cast<IMediaHTTPService>(binder);
}
//這裡和上面的檔案型別的操作一樣
status_t opStatus =
mp->setDataSource(
httpService,
pathStr,
headersVector.size() > 0? &headersVector : NULL);
process_media_player_call(
env, thiz, opStatus, "java/io/IOException",
"setDataSource failed." );
}
setDisPlay過程
setDisplay是用來設定播放視訊的
public void displaysetDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;//1. surfaceHolder用來控制Surface的
Surface surface;
if (sh != null) {
surface = sh.getSurface();
} else {
surface = null;
}
_setVideoSurface(surface);//2. 給視訊設定Surface,Surface通俗點說就是畫畫的地方
updateSurfaceScreenOn();//3.將畫面更新到螢幕上
}
_setVideoSurface對應JNI層的程式碼是:
static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
//省略丟擲異常程式碼
//減少Java層mNativeSurfaceTexture儲存的對Jni層之前對ISurfaceTexture的引用
decVideoSurfaceRef(env, thiz);
//IGraphicBufferProducer
sp<IGraphicBufferProducer> new_st;
if (jsurface) {
//得到Java層的Surface
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {//不為空,獲取IGraphicBufferProducer
new_st = surface->getIGraphicBufferProducer();
//省略 為空丟擲異常
}
//incStrong,智慧指標的引用計數 +1
new_st->incStrong((void*)decVideoSurfaceRef);
} else {
//異常程式碼
}
}
//重新設定Java層的mNativeSurfacetexture儲存jni層的物件的引用
env->SetLongField(thiz, fields.surface_texture, (jlong)new_st.get());
//如果mediaPlayer還沒有被初始化,setDataSource就會失敗,,
//但是在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;