Android 13 - Media框架(5)- NuPlayerDriver

前面的章节中我们了解到上层调用setDataSource后,MediaPlayerService::Client(IMediaPlayer)会调用MediaPlayerFactory创建MediaPlayerBase。Android为我们提供了默认的播放器实现NuPlayer,NuPlayerDriver实现了MediaPlayerBase接口,内部调用并管理有NuPlayer,起着承上启下的作用。

本节代码参考:
frameworks/av/media/libmediaplayerservice/include/MediaPlayerInterface.h
frameworks/av/media/libmediaplayerservice/nuplayer

1、MediaPlayerBase

MediaPlayerBase 是一个抽象类,定义了播放器需要的基本接口,并给出了一些方法的默认实现,如果我们想要实现播放器并且接入MediaPlayer,那么必须要继承于 MediaPlayerBase。

MediaPlayerBase::Listener 是一个内部抽象类,通过它可以将事件上抛给上层 MediaPlayerService::Client,MediaPlayerService::Client 中有 Listener 的具体实现。

MediaPlayerBase::AudioSink 同样是内部抽象类,定义了 Audio Output 所需要的基本接口,这个类用于走AudioTrack / AudioFlinger 进行软件合成输出声音,MediaPlayerService::AudioOutput 是 software output 的具体实现。

MediaPlayerInterface 继承于 MediaPlayerBase,实现了 hardwareOutput 方法,它走的是 software output,需要用到上面提到的 AudioOutput。我们接下来要看的 NuPlayerDriver 继承自于这个类,所以声音走的软件合成。

MediaPlayerHWInterface 也是继承于 MediaPlayerBase,从名字上就可以看出它走的是 hardware output,是没有setAudioSink接口的,具体如何进行硬件合成需要 vendor 自己来实现。

通常来说,我们实现的播放器需要继承于 MediaPlayerInterface / MediaPlayerHWInterface,确定声音走软件还是硬件合成。如果我们的播放器需要两者都支持,也可以直接继承于MediaPlayerBase,或者继承于MediaPlayerInterface。

MediaPlayerBase 提供有基础而全面的播放接口,但是如果我们实现的播放器还有更多的功能,可以使用它提供的 invoke 函数来实现自定义接口的调用。


2、NuPlayerDriver

NuPlayerDriver 会对上层下发的指令进行处理,根据当前的状态调用 NuPlayer 对应的功能。

2.1、NuPlayerDriver调用机制的理解

NuPlayer 内部使用的是 AMessage - AHandler - ALooper 这一套异步消息处理机制,从观察来看播放控制接口,例如 setDataSource、prepare、start、pause、reset 这类播控制接口都是异步调用的(NuPlayer 没有 stop);设定或获取一些参数的接口,例如 getTrackInfo、getSelectedTrack 这类都是同步调用的。

回到 NuPlayerDriver 中来,通过搜索 mCondition.wait 来看哪些接口是同步调用的,查找到 setDataSource、setVideoSurfaceTexture、prepare 和 reset 这四个接口是同步调用的。为什么他们四个是同步调用的,而其他的 start、pause、stop 是异步调用的呢?

我的理解是这样:异步调用的接口会依赖同步调用的4个接口所创建的对象,例如 setDataSource 会创建出 NuPlayer 中的 Source 对象,如果这步是异步的,Source 还没有创建出来就调用 prepare,那就会出现空指针的错误了;又比如 reset 会销毁 MediaPlayerClient 及其内部的对象,如果是异步的,reset处理过程中重新调用其他接口,也是很有可能出现空指针的问题。所以这类会创建或者销毁成员对象的接口必须要进行同步调用!

而 start、pause、stop 这类接口并不会创建或者销毁某些成员变量,不会对前后调用产生影响,调用后会在 ALooper 中按照调用顺序执行,所以异步处理不会有问题。

2.2、NuPlayerDriver中的状态

我们再看下 NuPlayer 中的基本播放控制方法,总共有 setDataSourceAsyncprepareAsyncstartpauseresetAsync 这5个,是没有stop 的,NuPlayer 的 stop 就是用的 pause 来假装的。

接下来再看 NuPlayer 有哪些状态,状态如下:

cpp 复制代码
    enum State {
        STATE_IDLE,
        STATE_SET_DATASOURCE_PENDING,
        STATE_UNPREPARED,
        STATE_PREPARING,
        STATE_PREPARED,
        STATE_RUNNING,
        STATE_PAUSED,
        STATE_RESET_IN_PROGRESS,
        STATE_STOPPED,                  // equivalent to PAUSED
        STATE_STOPPED_AND_PREPARING,    // equivalent to PAUSED, but seeking
        STATE_STOPPED_AND_PREPARED,     // equivalent to PAUSED, but seek complete
    };
  1. STATE_IDLE:MediaPlayerService::Client 调用 setDataSource 刚刚创建 NuPlayerDriver 时状态为 STATE_IDLE,reset 之后状态也变成 STATE_IDLE;
  2. STATE_SET_DATASOURCE_PENDING:调用 MediaPlayerBase setDataSource 后状态变成 STATE_SET_DATASOURCE_PENDING,这个过程可能会比较耗时,所以在等待过程中设置了这个中间状态,如果有人错误使用了多线程调用MediaPlayer,那么这个中间状态将会阻止这个错误调用;
  3. STATE_UNPREPARED:setDataSource 之后的状态置为 STATE_UNPREPARED;
  4. STATE_PREPARING:同样的,prepare过程可能会比较耗时,所以也设置了这个中间状态;prepare 有同步和异步两个版本,同步版本的作用和 SET_DATASOURCE_PENDING 作用相同;异步的版本会标记当前的状态为 preparing,当 prepareAsync 过程中调用 reset 销毁对象时,会直接退出 STATE_PREPARING 状态,进入到 reset 的状态中;
  5. STATE_RUNNING:正在播放的状态,这个状态下 isPlaying 接口返回值为 true;
  6. STATE_PAUSED:暂停状态为STATE_PAUSED,播放结束的状态也是STATE_PAUSED;
  7. STATE_STOPPED:停止播放状态;
  8. STATE_RESET_IN_PROGRESS:reset 的处理过程会将状态置为 STATE_RESET_IN_PROGRESS;
  9. STATE_STOPPED_AND_PREPARING:stop 之后需要重新 prepare 才能继续播放,stop 时资源都没有释放,所以是直接 seek 到文件起始位置播放;但是这里的 preparing 并不会影响 reset 的动作。
  10. STATE_STOPPED_AND_PREPARED:stop 之后 prepare 调用完成,状态置为 STATE_STOPPED_AND_PREPARED。

2.3、NuPlayerDriver中的状态切换

2.3.1、start

cpp 复制代码
status_t NuPlayerDriver::start_l() {
    switch (mState) {
    	// 1、
        case STATE_UNPREPARED:
        {
            status_t err = prepare_l();
            CHECK_EQ(mState, STATE_PREPARED);
            FALLTHROUGH_INTENDED;
        }
        // 2
        case STATE_PAUSED:
        case STATE_STOPPED_AND_PREPARED:
        case STATE_PREPARED:
        {
            mPlayer->start();
            FALLTHROUGH_INTENDED;
        }
        // 3
        case STATE_RUNNING:
        {
            if (mAtEOS) {
                mPlayer->seekToAsync(0);
                mAtEOS = false;
                mPositionUs = -1;
            }
            break;
        }
        default:
            return INVALID_OPERATION;
    }
    mState = STATE_RUNNING;
    return OK;
}

从以上代码可以看出,start 的处理情况有4种:

  1. 当前状态为 STATE_UNPREPARED 调用 start:这种情况应该来说是不会出现的,因为在 MediaPlayer Native 层已经进行了状态处理;
  2. 当前状态为 STATE_PAUSED、STATE_STOPPED_AND_PREPARED、STATE_PREPARED:这三种状态下直接调用 start 即可;如果调用之前出现错误,则seek到0的位置;
  3. 当前状态为 STATE_RUNNING:不做处理;
  4. 其他状态调用 start 返回 INVALID_OPERATION,MediaPlayer Native 层抛出 Error;

2.3.2、pause

cpp 复制代码
status_t NuPlayerDriver::pause() {
    ALOGD("pause(%p)", this);
    int unused;
    getCurrentPosition(&unused);

    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_PAUSED:
        case STATE_PREPARED:
            return OK;
		// 2
        case STATE_RUNNING:
            mState = STATE_PAUSED;
            notifyListener_l(MEDIA_PAUSED);
            mPlayer->pause();
            break;

        default:
            return INVALID_OPERATION;
    }

    return OK;
}

pause 的处理情况有3种:

  1. 状态为 STATE_PAUSED、STATE_PREPARED:不做处理;
  2. 状态为 STATE_RUNNING,调用 pause 方法;

2.3.3、stop

cpp 复制代码
status_t NuPlayerDriver::stop() {
    ALOGD("stop(%p)", this);
    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_RUNNING:
            mPlayer->pause();
            FALLTHROUGH_INTENDED;
		// 2
        case STATE_PAUSED:
            mState = STATE_STOPPED;
            notifyListener_l(MEDIA_STOPPED);
            break;
		// 3
        case STATE_PREPARED:
        case STATE_STOPPED:
        case STATE_STOPPED_AND_PREPARING:
        case STATE_STOPPED_AND_PREPARED:
            mState = STATE_STOPPED;
            break;
		// 4
        default:
            return INVALID_OPERATION;
    }
    return OK;
}
  1. 状态为 STATE_RUNNING:调用 pause 方法,并将状态置为 STATE_STOPPED;
  2. 状态为 STATE_PAUSED:直接将状态置为 STATE_STOPPED;
  3. 状态为 STATE_PREPARED、STATE_STOPPED、STATE_STOPPED_AND_PREPARING、STATE_STOPPED_AND_PREPARED:这些状态下本就没有开始播放,所以直接将状态置为 STATE_STOPPED;
  4. 其他状态返回 INVALID_OPERATION;

2.3.4、reset

cpp 复制代码
status_t NuPlayerDriver::reset() {
    ALOGD("reset(%p) at state %d", this, mState);

    updateMetrics("reset");
    logMetrics("reset");

    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_IDLE:
            return OK;
		// 2
        case STATE_SET_DATASOURCE_PENDING:
        case STATE_RESET_IN_PROGRESS:
            return INVALID_OPERATION;
		// 3
        case STATE_PREPARING:
        {
            CHECK(mIsAsyncPrepare);

            notifyListener_l(MEDIA_PREPARED);
            break;
        }

        default:
            break;
    }

    if (mState != STATE_STOPPED) {
        notifyListener_l(MEDIA_STOPPED);
    }

    mState = STATE_RESET_IN_PROGRESS;
    mPlayer->resetAsync();

    while (mState == STATE_RESET_IN_PROGRESS) {
        mCondition.wait(mLock);
    }
    return OK;
}

reset 的处理情况有3种:

  1. 状态为 STATE_IDLE:直接返回,不需要处理;
  2. 状态为 STATE_SET_DATASOURCE_PENDING、STATE_RESET_IN_PROGRESS:正在 reset 或者说正在初始化,同样也直接返回;
  3. 状态为 STATE_PREPARING:这时候正在 prepare,这时候直接回调上层 MEDIA_PREPARED,如果当前状态不是 stop则还要回调上层当前的状态为 STATE_STOPPED,所以我们上层应用在 prepareAsync、stop 的回调事件中应该判断当前 reset 是否被调用,如果被调用了则不应该 call start 方法,防止快切时出现问题;最后调用resetAsync,等待 reset 完成。

我们要注意的是 MediaPlayerService::Client 和 NuPlayerDriver 的 reset 方法并不会销毁对象,只有等MediaPlayer Native 的 disconnect 调用完成 MediaPlayerService 中的对象才会销毁。

2.3.5、notifyPrepareCompleted

如果在 prepareAsync 的状态下调用 reset,prepare 事件处理完成后会调用回调事件 notifyPrepareCompleted,这里面会怎么处理呢?

cpp 复制代码
void NuPlayerDriver::notifyPrepareCompleted(status_t err) {
    ALOGV("notifyPrepareCompleted %d", err);

    Mutex::Autolock autoLock(mLock);

    if (mState != STATE_PREPARING) {
    	// 直接退出
        CHECK(mState == STATE_RESET_IN_PROGRESS || mState == STATE_IDLE);
        return;
    }

    CHECK_EQ(mState, STATE_PREPARING);

    mAsyncResult = err;

    if (err == OK) {
        mState = STATE_PREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_PREPARED);
        }
    } else {
        mState = STATE_UNPREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
        }
    }

    sp<MetaData> meta = mPlayer->getFileMeta();
    int32_t loop;
    if (meta != NULL
            && meta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
        mAutoLoop = true;
    }

    mCondition.broadcast();
}

从代码里可以看到,如果 prepareAsync 完成时,则直接退出回调方法,不会向上 Callback。

2.3.6、MEDIA_PLAYBACK_COMPLETE

视频播放结束后,NuPlayer 会调用 Callback 上抛 MEDIA_PLAYBACK_COMPLETE 事件,如果设置了 Loop 则自动 seek 到开头位置开始播放,否则调用 pause 方法进入暂停的状态,并且将 mAtEOS 设置为 true,最终将 MEDIA_PLAYBACK_COMPLETE 回调给上层。

cpp 复制代码
void NuPlayerDriver::notifyListener_l(
        int msg, int ext1, int ext2, const Parcel *in) {
    switch (msg) {
        case MEDIA_PLAYBACK_COMPLETE:
        {
            if (mState != STATE_RESET_IN_PROGRESS) {
                if (mAutoLoop) {
                    audio_stream_type_t streamType = AUDIO_STREAM_MUSIC;
                    if (mAudioSink != NULL) {
                        streamType = mAudioSink->getAudioStreamType();
                    }
                    if (streamType == AUDIO_STREAM_NOTIFICATION) {
                        mAutoLoop = false;
                    }
                }
                // 1、如果有循环播放则seek到开始位置,然后直接返回
                if (mLooping || mAutoLoop) {
                    mPlayer->seekToAsync(0);
                    if (mAudioSink != NULL) {
                        mAudioSink->start();
                    }
                    return;
                }
				// 2、否则进入暂停状态
                mPlayer->pause();
                mState = STATE_PAUSED;
            }
            FALLTHROUGH_INTENDED;
        }

        case MEDIA_ERROR:
        {
            if (msg == MEDIA_ERROR) {
                Mutex::Autolock autoLock(mMetricsLock);
                if (mMetricsItem != NULL) {
                    mMetricsItem->setInt32(kPlayerError, ext1);
                    if (ext2 != 0) {
                        mMetricsItem->setInt32(kPlayerErrorCode, ext2);
                    }
                    mMetricsItem->setCString(kPlayerErrorState, stateString(mState).c_str());
                }
            }
            // 3、将 mAtEOS 设置为true,标记当前状态
            mAtEOS = true;
            break;
        }

        default:
            break;
    }

    mLock.unlock();
    // 4、callback到上层
    sendEvent(msg, ext1, ext2, in);
    mLock.lock();
}
相关推荐
枯骨成佛1 小时前
Android中Crash Debug技巧
android
kim56596 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼6 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ6 小时前
Android Studio使用c++编写
android·c++
csucoderlee7 小时前
Android Studio的新界面New UI,怎么切换回老界面
android·ui·android studio
kim56597 小时前
各版本android studio下载地址
android·ide·android studio
饮啦冰美式7 小时前
Android Studio 将项目打包成apk文件
android·ide·android studio
夜色。7 小时前
Unity6 + Android Studio 开发环境搭建【备忘】
android·unity·android studio
ROCKY_8178 小时前
AndroidStudio-滚动视图ScrollView
android
趴菜小玩家9 小时前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle