2019年就要结束了,回首碌碌无为的一年,对于没有好文凭和好背景(大厂工作经验)的大龄程序员,又背负着各种贷款和养家糊口的压力,总觉得要做点什么,突然有个想法,从application到framework到hal整理下整个audio源码解析,路慢慢而修远兮,吾将上下而求索。话不多说,干就得了
正文
android关于媒体播放的方式主要有三种,我们通过一些资料以及官方文档大概都了解MediaPlayer、SoundPool、以及AudioTrack。或者他们的区别和使用场景,我们也都很熟悉,但无论哪一种方式,又是如何将声音一步一步播放出来最终人耳可以感受到的呢?今天先来分析下MediaPlayer。
MediaPlayer 状态图
说API之前先看下MediaPlayer的状态转化图:
这张图完全说明了MediaPlayer的各个状态的转化逻辑,哪些状态可以来回转化,哪些状态不可以,这个图非常重要。
; MediaPlayer的初始化
这种通过构造函数来初始化可能是我们使用最多的了,那么就看看初始化都做了什么
/frameworks/base/media/java/android/media/MediaPlayer.java
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;
}
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
native_setup(new WeakReference<MediaPlayer>(this));
baseRegisterPlayer();
}
初始化过程中,主要用native_setup()通过jni同步在native层初始化对应mediaplayer,以及baseRegisterPlayer(),这是父类PlayerBase中的方法,具体做了什么呢
protected void baseRegisterPlayer() {
int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
updateAppOpsPlayAudio();
mAppOpsCallback = new IAppOpsCallbackWrapper(this);
try {
mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
ActivityThread.currentPackageName(), mAppOpsCallback);
} catch (RemoteException 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;
}
这里主要是一个内部权限的处理,以及通知给AudioService trackPlayer(),其实MediaPlayer的初始化方式很多,比如create(),MediaPlayer的create方法,为我们提供了各种参数的支持,其实create方法只不过是把我们new Mediaplayer,以及setAudioAttributes,setDataSource,等一系列初始化操作,全都替我们弄好了,这里简单看几个create方法的源码:
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
MediaPlayer mp = new MediaPlayer();
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
mp.setAudioSessionId(audioSessionId);
mp.setDataSource(context, uri);
if (holder != null) {
mp.setDisplay(holder);
}
mp.prepare();
return mp;
} catch (IOException ex) {
Log.d(TAG, "create failed:", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "create failed:", ex);
} catch (SecurityException ex) {
Log.d(TAG, "create failed:", ex);
}
return null;
}
不管我们自己new MediaPlayer,然后设置各种参数也好,直接通过create的方式初始化也罢,最终结果是一样的。
setAudioAttributes()
说setAudioAttributes()先说下setAudioStreamType(),源码如下:
public void setAudioStreamType(int streamtype) {
deprecateStreamTypeForPlayback(streamtype, "MediaPlayer", "setAudioStreamType()");
baseUpdateAudioAttributes(
new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build());
_setAudioStreamType(streamtype);
mStreamType = streamtype;
}
然后再说setAudioAttributes()
public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException {
if (attributes == null) {
final String msg = "Cannot set AudioAttributes to null";
throw new IllegalArgumentException(msg);
}
baseUpdateAudioAttributes(attributes);
mUsage = attributes.getUsage();
mBypassInterruptionPolicy = (attributes.getAllFlags()
& AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
Parcel pattributes = Parcel.obtain();
attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS);
setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes);
pattributes.recycle();
}
通过setAudioStreamType注释发现谷歌不建议使用setAudioAttributes,但自己内部create的方法调用的都是setAudioAttributes,有点意思,个人建议还是使用setAudioAttributes这种方式(谷歌不建议个人怀疑只是为了排除STREAM_ACCESSIBILITY)setAudioAttributes因此这种方法在车载开发中,尤其使用了 AUDIO_DEVICE_OUT_BUS时,屡试不爽,因为AUDIO_DEVICE_OUT_BUS就是通过AUdioAttributes的usage处理的而不是streamType。
setVolume()
这个使用的不是很多,因为AudioManager有专门的调音接口,代码比较简单。
public void setVolume(float leftVolume, float rightVolume) {
baseSetVolume(leftVolume, rightVolume);
}
setLooping()
设置播放模式 循环播放。
public native void setLooping(boolean looping);
setSurface()/setDisplay()
关于播放视频时对于surface和surfaceHolder的设置
public void setSurface(Surface surface) {
if (mScreenOnWhilePlaying && surface != null) {
Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
}
mSurfaceHolder = null;
_setVideoSurface(surface);
updateSurfaceScreenOn();
}
public void setDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;
Surface surface;
if (sh != null) {
surface = sh.getSurface();
} else {
surface = null;
}
_setVideoSurface(surface);
updateSurfaceScreenOn();
}
两个方法都调用了updateSurfaceScreenOn,简单看下逻辑。
private void updateSurfaceScreenOn() {
if (mSurfaceHolder != null) {
mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
}
}
getCurrentPosition ()
public native int getCurrentPosition();
getDuration()
public native int getDuration();
isPlaying()
关于isPlaying的使用后续会继续说明
public native boolean isPlaying();
reset()
终于可以说到这个状态图了,就先从reset方法说起。
public void reset() {
mSelectedSubtitleTrackIndex = -1;
synchronized(mOpenSubtitleSources) {
for (final InputStream is: mOpenSubtitleSources) {
try {
is.close();
} catch (IOException e) {
}
}
mOpenSubtitleSources.clear();
}
if (mSubtitleController != null) {
mSubtitleController.reset();
}
if (mTimeProvider != null) {
mTimeProvider.close();
mTimeProvider = null;
}
stayAwake(false);
_reset();
if (mEventHandler != null) {
mEventHandler.removeCallbacksAndMessages(null);
}
synchronized (mIndexTrackPairs) {
mIndexTrackPairs.clear();
mInbandTrackIndices.clear();
};
resetDrmState();
}
setDataSource()
setDataSource这里不多说了,支持各种参数如文件的 path的 uri的以及资源文件的,具体怎么用百度也是一堆堆的。
prepare()
public void prepare() throws IOException, IllegalStateException {
_prepare();
scanInternalSubtitleTracks();
synchronized (mDrmLock) {
mDrmInfoResolved = true;
}
}
还有一个prepareAsync(),异步的prepare需要通过setOnPreparedListener来监听prepare的完成,当收到onPrepared()回调时,便可以开始播放了。
start()
start的逻辑不是很复杂
public void start() throws IllegalStateException {
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) {
}
}
}.start();
}
}
private void startImpl() {
baseStart();
stayAwake(true);
_start();
}
关于baseStart中只关注 如下两行代码即可
mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
getService().playerEvent(mPlayerIId, mState);
把播放状态传递给了AudioService,包括pause的stop的也都是这么处理的。
pause()
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
basePause();
}
stop()
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
baseStop();
}
release()
public void release() {
baseRelease();
stayAwake(false);
updateSurfaceScreenOn();
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
if (mTimeProvider != null) {
mTimeProvider.close();
mTimeProvider = null;
}
mOnSubtitleDataListener = null;
mOnDrmConfigHelper = null;
mOnDrmInfoHandlerDelegate = null;
mOnDrmPreparedHandlerDelegate = null;
resetDrmState();
_release();
}
其实release后我们发现基本所有的设置都被清楚了,通过状态图也能看出,release后基本mediaplayer就不能在用了。因此只有当我们在退出播放,或者不需要在使用mediaplayer的时候才会调用此方法。
其他
mediaplayer还有一些其他API这里就不一一列举了,常用的listener还有setOnCompletionListener以及seErrorListener,如果两个都存在,在发生error的时候,CompletionListener是收不到回调的,因为代码逻辑处理只回调error的listener。
最后
关于MediaPlayer的源码API就说这么多,关于MediaPlayer字幕以及MediaPlayer与AudioService的交互,以后抽时间在整理下,下篇将研究下SoundPool的源码API,以上有说的不准确的地方,还望各位大佬多多指点~。