Android9.0Auidio之MediaPlayer(一)

正文

android关于媒体播放的方式主要有三种,我们通过一些资料以及官方文档大概都了解MediaPlayer、SoundPool、以及AudioTrack。或者他们的区别和使用场景,我们也都很熟悉,但无论哪一种方式,又是如何将声音一步一步播放出来最终人耳可以感受到的呢?今天先来分析下MediaPlayer。

MediaPlayer 状态图

说API之前先看下MediaPlayer的状态转化图:
media player状态图
这张图完全说明了MediaPlayer的各个状态的转化逻辑,哪些状态可以来回转化,哪些状态不可以,这个图非常重要。

MediaPlayer的初始化

这种通过构造函数来初始化可能是我们使用最多的了,那么就看看初始化都做了什么

/frameworks/base/media/java/android/media/MediaPlayer.java 

    static {
        System.loadLibrary("media_jni");
        native_init();
    }

    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注释发现谷歌不建议使用setAudioStreamType,建议还是使用setAudioAttributes这种方式(谷歌不建议个人怀疑只是为了排除STREAM_ACCESSIBILITY)。因此这种方法在车载开发中,尤其使用了 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()
总共有15种方法类型 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715142155519.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RmdmJydGRoeQ==,size_16,color_FFFFFF,t_70)

最终到JNI调用的native方法有三种:

private native void nativeSetDataSource(
        IBinder httpServiceBinder, String path, String[] keys, String[] values)
        throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;

private native void _setDataSource(FileDescriptor fd, long offset, long length)
            throws IOException, IllegalArgumentException, IllegalStateException;

private native void _setDataSource(MediaDataSource dataSource)
         throws IllegalArgumentException, IllegalStateException;

三类:一类是流媒体,其他两个是本地媒体。

prepare()
    public void prepare() throws IOException, IllegalStateException {
	/**通过jni方式将prepare设置下去*/
        _prepare();
		/**设置一些字幕需要东西,抽空单独来说MediaPlayer字幕相关*/
        scanInternalSubtitleTracks();

还有一个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();
        }
    }

关于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,以上有说的不准确的地方,还望各位大佬多多指点~。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值