NuPlayer源码分析一:播放器创建

本文详细分析了NuPlayer源码中播放器的创建过程。介绍了MediaPlayer与NuPlayer的关系,阐述了NuPlayer创建涉及的函数,如createPlayer、setAudioSink、setDataSource等,还说明了各函数的具体逻辑和作用,包括获取播放器工厂、创建播放器对象、设置音频输出等,为理解底层播放器提供帮助。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NuPlayer源码分析一:播放器创建


源码环境:Oreo 8.0.0_r4

需要编译 /frameworks/av/media/libmediaplayerservice,生成libmediaplayerservice.so

该系列播放将详细分析NuPlayer源码,经过该系列博客之后,你应该就可以对底层播放器有了一个具体而全面的认识,系列文章分为下面几个部分:

简介

看过或者没看过MediaPlayer源码分析一文的朋友,都应该知道,严格意义上讲,MediaPlayer并不是播放器本身,它只是Android框架层众多媒体播放器(包括ROM厂商自定义的播放器)的“壳“。

MediaPlayer的所有主要操作(包括但不限于播放、暂停、快进等)都将通过调用链条,到达框架层的播放器,如NuPlayer。那么,这里就来分析一下,Android框架层真正的播放器,NuPlayer。

MediaPlayer和NuPlayer的关系如图:
在这里插入图片描述

NuPlayer前情提要

既然MediaPlayer只是NuPlayer等底层播放器的壳,那么是不是new MediaPlayer() 的时候,底层就会new NuPlayer()呢?

结论是否定的,对于NuPlayer播放器来说,NuPlayer类中,实现了播放相关函数,但播放器的控制流程却是在NuPlayerDriver类中实现。

虽然如此,但这也不意味着new MediaPlayer()会导致new NuPlayerDriver()的函数调用。

不管是NuPlayer还是NuPlayerDriver实例的创建,都是在MediaPlayer实例化后的setDataSource()函数执行过程中实现的。

MediaPlayer的调用代码如下:

player = new MediaPlayer();
player.setDataSource(path);

至于具体调用过程就不分析了, 有兴趣或者感到迷惑的同学可以去看一看MediaPlayer源码分析就全明白了。来看一下setDataSource部分的时序图:
这里写图片描述

我们从最开始接触到的NuPlayer世界的代码开始,也就是status_t MediaPlayerService::Client::setDataSource(...)函数。

status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length) {
	// 略掉一些对资源的判断,剩下和可能和NuPlayer有关的部分
    player_type playerType = MediaPlayerFactory::getPlayerType(this,
                                                               fd,
                                                               offset,
                                                               length);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    // now set data source
    return mStatus = setDataSource_post(p, p->setDataSource(fd, offset, length));
}

留下了三个比较重要的调用,先依次说一下它们的作用,然后再展开:

  1. MediaPlayerFactory::getPlayerType:该函数涉及Android底层媒体播放器的评分机制。通过评分,获得一个最优的播放器类型,具体怎么得到播放器类型,请阅:Android Framework层播放器评分机制,因为AndroidO只剩下了NuPlayerTestPlayer两种播放器,TestPlayer并未正式启用。所以,函数调用返回的是NuPlayer对应的播放器类型NU_PLAYER
  2. setDataSource_pre:该函数的作用,是根据前面获得的播放器类型创建播放器对象。
  3. setDataSource_post:将媒体资源设置给播放器,这才是真正的setDataSource操作。

接下来展开:setDataSource_presetDataSource_post函数。

为了让小伙伴们看代码片段的时候,可以有效的形成上下文逻辑,每部分代码,都会配一张图,以说明当前代码所处位置,比如:现在我们在这里(注意红色箭头,表示当前函数位置)。

在这里插入图片描述

NuPlayer播放器创建

在这里插入图片描述

前面已经提到,NuPlayer的创建过程,是在setDataSource_pre函数中实现,我们接下来就展开一下吧:

sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
        player_type playerType)
{
    // create the right type of player
    sp<MediaPlayerBase> p = createPlayer(playerType);
	// 删掉了大量注册服务监听的代码,包括extractor、IOMX
    if (!p->hardwareOutput()) { // 播放器音频是否通过硬件直接输出,NuPlayer是不需要的。
        Mutex::Autolock l(mLock);
        mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(), mPid, mAudioAttributes);
        static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
    }
    return p;
}

createPlayer

在这里插入图片描述

sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
    // 检查当前进程,是否已经有一个播放器不同类型的播放器了,如果有,干掉它
    sp<MediaPlayerBase> p = mPlayer;
    if ((p != NULL) && (p->playerType() != playerType)) {
        ALOGV("delete player");
        p.clear();
    }
    if (p == NULL) { // 创建对应类型的播放器。
        p = MediaPlayerFactory::createPlayer(playerType, this, notify, mPid);
    }
    if (p != NULL) {
        p->setUID(mUid);
    }
    return p;
}

这个函数最重要的部分是 MediaPlayerFactory::createPlayer:

sp<MediaPlayerBase> MediaPlayerFactory::createPlayer(
        player_type playerType,
        void* cookie,
        notify_callback_f notifyFunc,
        pid_t pid) {
    sp<MediaPlayerBase> p;
    IFactory* factory;
    status_t init_result;
	// 略掉一些非关键代码
    factory = sFactoryMap.valueFor(playerType);
    p = factory->createPlayer(pid);
	// 略掉一些非关键代码
    init_result = p->initCheck();
    return p;
}

这个函数体实现的也比较简单,逻辑如下:

  • sFactoryMap.valueFor:通过sFactoryMap和playerType获取播放器工厂对象。
  • factory->createPlayer:调用播放器工厂对象创建播放器对象。
  • p->initCheck:对播放器做初始化检查。
sFactoryMap.valueFor

sFactoryMap是个什么东西呢,看一下它的申明:

typedef KeyedVector<player_type, IFactory*> tFactoryMap;
static tFactoryMap sFactoryMap;

它是一个KeyedVector的结构,以播放器类型为键,对应的播放器工厂为值。在MediaPlayerService服务启动时,会通过MediaPlayerFactory::registerBuiltinFactories()函数调用,将所有的播放器工厂添加到这个Map结构中。这部分逻辑,在Android Framework层播放器评分机制一文中的注册播放器工厂小节中详细分析过,就不再赘述了。

我们已经知道此时的播放器类型为NU_PLAYER,sFactoryMap.valueFor(playerType);可以等价于:

sFactoryMap.valueFor(NU_PLAYER),所以,factory是NuPlayer播放器对应的工厂对象。简单看一下类图结构。

在这里插入图片描述

factory->createPlayer

在这里插入图片描述

通过类图,和前面的分析,到这里我们已经知道NuPlayer的播放器工厂是NuPlayerFactory类:

class NuPlayerFactory : public MediaPlayerFactory::IFactory {
  public:
	// 删掉了评分机制的代码
    virtual sp<MediaPlayerBase> createPlayer(pid_t pid) {
        ALOGV(" create NuPlayer");
        return new NuPlayerDriver(pid);
    }
};

说好的创建NuPlayer播放器呢,怎么冒出来一个NuPlayerDriver

其实,虽然播放器叫NuPlayer,但并意味着“播放器”只有NuPlayer对象。实际上,NuPlayer播放器由NuPlayerDriverNuPlayer两部分组成,NuPlayer对象负责播放、暂停等功能函数的实现,NuPlayerDriver则负责功能的调度,和MediaPlayerSerivce等外界沟通。

回到代码。

NuPlayerFactory::createPlayer函数只new了一个NuPlayerDriver,我们来看一下NuPlayerDriver的初始化过程:

NuPlayerDriver::NuPlayerDriver(pid_t pid)
    : mState(STATE_IDLE),
      mLooper(new ALooper),
      mPlayer(new NuPlayer(pid)),
      mLooping(false),
      mAutoLoop(false) {
    mLooper->setName("NuPlayerDriver Looper");
    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO);

    mLooper->registerHandler(mPlayer);
    mPlayer->setDriver(this);
}

为了简洁,代码依然删掉了不少暂时并不重要的。这部分代码,其实我在Android媒体底层通信框架Native Handler(三):NuPlayer一文中已经讲过。

有所不同的是,当时侧重点放在媒体通信部分,也就是NativeHandler逻辑部分。

NuPlayerDriver的构造函数部分,除了NativeHandler逻辑外,最重要的就是以下三个操作了:

  1. mState(STATE_IDLE):将播放器状态设置为STATE_IDLE(空闲)。
  2. new NuPlayer(pid):创建一个NuPlayer对象,并让NuPlayerDriver持有NuPlayer的引用。这里稍后展开。
  3. setDriver(this):将NuPlayerDriver设置给NuPlayer,让NuPlayer持有NuPlayerDriver的引用。

第二和第三点,让NuPlayerDriverNuPlayer相互持有引用,目的是在后续的流程控制中,方便彼此回调,配合工作。

到这里,NuPlayer的创建过程,算是明白了。NuPlayer的构造函数没什么好看的,就是给一堆成员赋初值的过程。

initCheck()

因为createPlayer函数创建并返回的是NuPlayerDriver对象,所以调用的是NuPlayerDriver::initCheck函数:

status_t NuPlayerDriver::initCheck() {
    return OK;
}

啥也没干,直接返回了OK,有点浪费时间的感觉kugualian

代码到哪儿了?

在这里插入图片描述

setAudioSink

在这里插入图片描述

AudioOutput对象是音频输出的抽象层,在不支持硬件驱动直接输出的接口下,需要手动设置音频输出的抽象层接口。

MediaPlayerBase和它子类的结构图如下:

通过createPlayer函数,返回的是NuPlayerDriver对象。

void NuPlayerDriver::setAudioSink(const sp<AudioSink> &audioSink) {
    mPlayer->setAudioSink(audioSink);
    mAudioSink = audioSink;
}

这个调用的mPlayer,在NuPlayerDriver构造函数的初始化列表中,已经新建了一个NuPlayer对象,并赋值给mPlayer。所以,来看一下NuPlayersetAudioSink

void NuPlayer::setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink) {
    sp<AMessage> msg = new AMessage(kWhatSetAudioSink, this);
    msg->setObject("sink", sink);
    msg->post();
}
case kWhatSetAudioSink:
        {
            sp<RefBase> obj;
            CHECK(msg->findObject("sink", &obj));
            mAudioSink = static_cast<MediaPlayerBase::AudioSink *>(obj.get());
            break;
        }

关于AMessagemsg->findObject等代码和为什么这么调用,可以去快速看一下Android媒体底层通信框架Native Handler(三):NuPlayer的总结部分。

可以看出,不管是NuPlayerDriver还是NuPlayersetAudioSink代码,都是将新建的AudioOutput对象存在对应的mAudioSink字段中了,方便以后播放音频做准备。

setDataSource

当前代码位置:

在这里插入图片描述

在前一个流程中,创建了NuPlayerNuPlayerDriver对象,并将NuPlayerDriver对象指针保存在了p中,接着,通过p调用了NuPlayerDriversetDataSource函数。

status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
    if (mState != STATE_IDLE) { // NuPlayerDriver构造中mState被设置成了STATE_IDLE。
        return INVALID_OPERATION;
    }
    mState = STATE_SET_DATASOURCE_PENDING; // 将播放器状态设置为STATE_SET_DATASOURCE_PENDING
    mPlayer->setDataSourceAsync(fd, offset, length); // 调用NuPlayer,设置媒体源
    while (mState == STATE_SET_DATASOURCE_PENDING) {
        mCondition.wait(mLock); // 加锁,直到被通知唤醒
    }
    return mAsyncResult;
}

该函数主要作用:

  • mState = STATE_SET_DATASOURCE_PENDING: 设置播放器状态,和流程控制有关,比较重要,后面很多流程都需要判断当前状态,上一个状态是NuPlayerDriver构造中设置的STATE_IDLE状态。
  • mPlayer->setDataSourceAsync:实际上NuPlayerDriver并没有处理资源的逻辑,前面也提到,它就是一层壳,需要将具体的动作交给NuPlayer对象去做。
  • while (mState == STATE_SET_DATASOURCE_PENDING):因为上一步的setDataSourceAsync流程中会用到NativeHandler机制,是异步的,所以在while循环体中加了一个锁,让当前线程阻塞。直到setDataSourceAsync流程执行完毕后,唤醒。

setDataSourceAsync

在这里插入图片描述

继续跟踪setDataSourceAsync函数:

void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
    sp<AMessage> msg = new AMessage(kWhatSetDataSource, this); // 新建消息,这属于常规操作了
    sp<AMessage> notify = new AMessage(kWhatSourceNotify, this); // 新建消息,用于和解封装模块通信,类似于一种listener的功能。

    sp<GenericSource> source = new GenericSource(notify, mUIDValid, mUID); // 创建解封装器
    status_t err = source->setDataSource(fd, offset, length);  // 为GenericSource设置媒体源

    msg->setObject("source", source);
    msg->post(); // 将创建并设置好的setDataSource,post给下一个流程处理
    mDataSourceType = DATA_SOURCE_TYPE_GENERIC_FD;
}

该函数主要逻辑如下:

  • new AMessage:构建了两个消息对象,msg用于向下一个流程发送消息和当前函数执行的成果(source)。notify用于在构建GenericSource的结果回调。
  • new GenericSource:只是一个解封装格式的类,同样的类还有RTSPSourceHTTPLiveSource等,是媒体流信息的直接处理者。媒体源信息也将被设置到该对象中。这会在一下篇文章进行展开,这里就先留个疑问。
  • source->setDataSource:将媒体流(源)设置给解封装格式的解析器,这个也在下一篇文章中展开。
  • msg->post():通过NativeHandler机制,将函数执行结果,也就是新创建的source对象发送给下一个函数执行onMessageReceived,这个过程是异步的,当前函数执行到这里就会退栈。

创建了一个解封装格式的解析器后,将结果postNuPlayer::onMessageReceived函数处理:

void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatSetDataSource:
        {
            status_t err = OK;
            sp<RefBase> obj;
            CHECK(msg->findObject("source", &obj));
            if (obj != NULL) {
                Mutex::Autolock autoLock(mSourceLock);
                mSource = static_cast<Source *>(obj.get()); // 将新创建的GenericSource对象,赋值给mSource
            } else {
                err = UNKNOWN_ERROR;
            }
            sp<NuPlayerDriver> driver = mDriver.promote();
            if (driver != NULL) {
                driver->notifySetDataSourceCompleted(err); // 通知NuPlayerDriver,任务完成
            }
            break;
        }
        // 略去一万行代码
	}
}

这段代码的重点在于:

  • mSource =:将之前创建的GenericSource对象赋值给了mSource字段。
  • driver->notifySetDataSourceCompleted:到这里,整个setDataSource的流程已经执行完毕,函数调用回到NuPlayerDriver中。

NuPlayerDriver::notifySetDataSourceCompleted

在这里插入图片描述

void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) { // err = OK;
    CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING); // 当前mState为STATE_SET_DATASOURCE_PENDING
    mAsyncResult = err;
    mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE; // 将状态设置为STATE_UNPREPARED
    mCondition.broadcast(); // 唤醒mCondition.wait(mLock);锁,完成setDataSource函数调用
}

如果没出以外,这里的入参值应该是OK的。所以,该函数的主要操作有:

  • 将当前状态设置成STATE_UNPREPARED。上一个状态未STATE_SET_DATASOURCE_PENDING。
  • mCondition.broadcast():发出唤醒mCondition锁广播。

释放锁后,NuPlayerDriver::setDataSource会将执行的结果mAsyncResult返回给调用者。setDataSource流程执行完毕。

<think>好的,我现在需要帮助用户了解NuPlayer::restartAudio的功能或解决与其相关的问题。首先,我得回忆Android媒体框架NuPlayer的相关知识。根据提供的引用,NuPlayerAndroid原生的播放器,替代了之前的AwesomePlayer。它的结构包含Source、Decoder和Renderer三大组件,这些组件是在接口调用时逐步创建的。 用户提到的restartAudio方法,可能涉及音频重新启动的逻辑。通常,在媒体播放过程中,如果遇到音频输出问题,比如设备切换、音频焦点丢失或解码错误,可能需要重新启动音频输出。根据引用[1],AudioOutput被创建后通过setAudioSink设置给NuPlayer的mAudioSink。这可能意味着restartAudio会重新初始化或重新连接AudioOutput,确保音频可以继续播放。 接下来,我需要考虑可能的问题场景。例如,当音频会话ID改变、音频设备断开或重新连接时,NuPlayer需要重新配置音频输出。此外,如果音频解码器遇到错误,可能需要重启音频管道。此时,restartAudio可能会调用mAudioSink->restart(),重新创建AudioOutput实例,并更新到Decoder或Renderer组件中。 在解决问题方面,用户可能遇到音频中断、无法恢复播放或音频设备切换失败的情况。这时候需要检查日志,查看是否有相关的错误信息,比如AudioSink的错误状态或初始化失败。同时,需要确认音频会话ID是否正确传递,以及AudioOutput的配置参数是否有效。引用[3]提到了JNI层的设置,可能涉及Surface和AudioAttributes的配置是否正确,这也可能影响音频的重启。 另外,需要参考引用[4]中关于NuPlayer Source的内容,确认在Source组件中是否有影响音频数据流的因素,比如缓冲区的处理或数据源的稳定性。如果Source出现问题,可能导致音频解码中断,进而触发restartAudio的调用。 总结来说,restartAudio的功能主要是在音频输出异常时重新初始化音频相关组件,确保播放的连续性。解决问题时需要检查音频配置、设备状态、解码错误以及数据源的稳定性,结合日志和代码路径进行分析。</think>### NuPlayer::restartAudio的功能解析 NuPlayerAndroid原生多媒体框架中负责媒体播放的核心组件,其`restartAudio`方法主要用于**音频输出的异常恢复或配置更新**。以下是具体功能分析: 1. **音频设备重连** 当音频输出设备发生变更(如蓝牙耳机断开/重连、HDMI切换)时,`restartAudio`会重新初始化$mAudioSink$(通过`setAudioSink`接口[^1]),确保音频数据正确路由到新设备。 2. **音频会话管理** 若音频会话ID(AudioSessionId)因系统策略调整需要更新(例如通话优先级抢占),该方法会重新创建`AudioOutput`对象并绑定新的会话ID: ```cpp mAudioOutput = new AudioOutput(newSessionId, ...); mDecoder->setAudioSink(mAudioOutput); ``` 3. **解码异常恢复** 当音频解码器(如NuPlayerDecoder)遇到硬解码器崩溃或缓冲区下溢时,`restartAudio`会触发解码器重启流程,并与Renderer重新同步时间戳[^4]。 --- ### 常见问题排查指南 #### 问题1:音频播放中断后无法恢复 - **可能原因**:`mAudioSink`未正确重新初始化 - **解决步骤**: 1. 检查`MediaPlayerService::Client`中是否成功创建新的`AudioOutput`实例 2. 验证音频属性参数(采样率、声道布局)是否与设备支持格式匹配 3. 通过`dumpsys media.nuplayer`查看`mAudioSink`状态 #### 问题2:设备切换后无声音 - **关键日志**: ```log AudioTrack: Invalid audio session ID / AudioFlinger could not create track ``` - **修复方向**: - 检查`IPCThreadState::self()->getCallingUid()`是否获取到有效UID - 确认`mAudioAttributes`包含正确的`USAGE`和`CONTENT_TYPE`参数 #### 问题3:音频/视频不同步 - **关联代码路径**: ```python NuPlayer::onResume() -> restartAudio() -> notifyVideoRenderingStart() # 需同步更新视频渲染时间基准 ``` - **调试验证**:通过`adb shell media_metrics`检查AV同步差值(`a-v sync offset`) --- ### 实现原理示意图 $$ \begin{array}{ccc} \text{NuPlayer} & \xrightarrow{\text{setAudioSink}} & \text{AudioOutput} \\ \downarrow \text{restartAudio()} & & \downarrow \text{restart()} \\ \text{DecoderBase} & \leftrightarrow & \text{AudioTrack} \end{array} $$ ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值