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在playerBase创建一个默认的AudioAttributes,以及player类型
*PLAYER_TYPE_JAM_MEDIAPLAYER 表示MediaPlayer */
super(new AudioAttributes.Builder().build(),
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
Looper looper;
/** 获取当前线程的looper,如果当前线程没有lopper那么就获取主线程的looper */
if ((looper = Looper.myLooper()) != null) {
/** 创建一个handler主要用于控制mediaplayer的播放 */
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
/** 控制播放时间和播放进度的相关 */
mTimeProvider = new TimeProvider(this);
/** 创建了一个InputStream向量集合 */
mOpenSubtitleSources = new Vector<InputStream>();
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
/**通过jni向下调用 */
native_setup(new WeakReference<MediaPlayer>(this));
/**playerbase的方法 下面我们来具体分析下 */
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);
// initialize mHasAppOpsPlayAudio
/** 检查usage权限*/
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) {
mHasAppOpsPlayAudio = false;
}
try {
/** 调用了AudioService的 trackPlayer 这块不多说了,因为一说又牵扯了很多,主要将mediaplayer统一管理起来,
* 已方便提供一些duck,mediaplayer的状态监听啊,以及外部对mediaplayer控制等功能*/
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方法的源码:
/** 这是一个简单的create方法,带holder的可以支持视频播放的*/
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
/** 我们发现 也是会new MediaPlayer 然后设置各种参数*/
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);
// 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;
}
不管我们自己new MediaPlayer,然后设置各种参数也好,直接通过create的方式初始化也罢,最终结果是一样的。
setAudioAttributes()
说setAudioAttributes()先说下setAudioStreamType(),源码如下:
/**
* Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
* for a list of stream types. Must call this method before prepare() or
* prepareAsync() in order for the target stream type to become effective
* thereafter.
*
* @param streamtype the audio stream type
* @deprecated use {@link #setAudioAttributes(AudioAttributes)}
* @see android.media.AudioManager
*/
public void setAudioStreamType(int streamtype) {
/** 检查传入的stream是否为 STREAM_ACCESSIBILITY,STREAM_ACCESSIBILITY不允许做playback用
* 如果是则抛 IllegalArgumentException 异常,*/
deprecateStreamTypeForPlayback(streamtype, "MediaPlayer", "setAudioStreamType()");
/**new mediaplayer的时候已经创建了一个默认的AudioAttributes,如果不set则会使用默认的,如果调用了
* setAudioStreamType则会对应更新AudioAttributes*/
baseUpdateAudioAttributes(
new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build());
/** 通过jni向下调用 */
_setAudioStreamType(streamtype);
/** 吐槽下搞个mStreamType 又搞了一个getAudioStreamType(private)没人用,是未将来版本提供的还是
*冗余就不太清楚了 */
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);
}
/**更新AudioAttribute*/
baseUpdateAudioAttributes(attributes);
/**赋值了又不用,暂不关注*/
mUsage = attributes.getUsage();
/**赋值了也不用,暂不关注*/
mBypassInterruptionPolicy = (attributes.getAllFlags()
& AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
Parcel pattributes = Parcel.obtain();
attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS);
/**通过jni设置下去*/
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) {
/**在mediaplayer中对左右声道声音重新计算下 然后通过子类的playerSetVolume的jni设置下去*/
baseSetVolume(leftVolume, rightVolume);
}
setLooping()
设置播放模式 循环播放。
/**没有啥逻辑直接通过jni设置下去*/
public native void setLooping(boolean looping);
setSurface()/setDisplay()
关于播放视频时对于surface和surfaceHolder的设置
/**设置播放视频的surface*/
public void setSurface(Surface surface) {
if (mScreenOnWhilePlaying && surface != null) {
Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
}
mSurfaceHolder = null;
_setVideoSurface(surface);
/**更新下surface状态即播放视频时是否保持常亮*/
updateSurfaceScreenOn();
}
/**设置播放视频的surfaceHolder*/
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 ()
/**,代码没有逻辑,直接调用了native的方法,获取当前播放的位置 单位ms*/
public native int getCurrentPosition();
getDuration()
/**获取总时长ms 这个方法要在prepare完成后调用,否则获取失败或者抛出异常*/
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);
/**通过jni 将reset设置下去*/
_reset();
// make sure none of the listeners get called anymore
if (mEventHandler != null) {
mEventHandler.removeCallbacksAndMessages(null);
}
synchronized (mIndexTrackPairs) {
mIndexTrackPairs.clear();
mInbandTrackIndices.clear();
};
/**重置DRM状态(主要用于视频播放时版权相关的东西)*/
resetDrmState();
}
setDataSource()
setDataSource这里不多说了,支持各种参数如文件的 path的 uri的以及资源文件的,具体怎么用百度也是一堆堆的。
prepare()
public void prepare() throws IOException, IllegalStateException {
/**通过jni方式将prepare设置下去*/
_prepare();
/**设置一些字幕需要东西,抽空单独来说MediaPlayer字幕相关*/
scanInternalSubtitleTracks();
// DrmInfo, if any, has been resolved by now.
synchronized (mDrmLock) {
mDrmInfoResolved = true;
}
}
还有一个prepareAsync(),异步的prepare需要通过setOnPreparedListener来监听prepare的完成,当收到onPrepared()回调时,便可以开始播放了。
start()
start的逻辑不是很复杂
public void start() throws IllegalStateException {
//FIXME use lambda to pass startImpl to superclass
final int delay = getStartDelayMs();
/**没有延迟的直接播放*/
if (delay == 0) {
startImpl();
} else {
/**有延迟的起个thread播放*/
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();
stayAwake(true);
_start();
}
关于baseStart中只关注 如下两行代码即可
mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
getService().playerEvent(mPlayerIId, mState);
把播放状态传递给了AudioService,包括pause的stop的也都是这么处理的。
pause()
public void pause() throws IllegalStateException {
/**更新屏幕是否常亮状态*/
stayAwake(false);
/**通过jni 将pause状态设置下去*/
_pause();
/**与start逻辑相同 将mediaplayer的pause状态传递给AudioService*/
basePause();
}
stop()
public void stop() throws IllegalStateException {
/**更新屏幕是否常亮状态*/
stayAwake(false);
/**通过jni 将stop状态设置下去*/
_stop();
/**逻辑同 pause,只是状态变成了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;
// Modular DRM clean up
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,以上有说的不准确的地方,还望各位大佬多多指点~。