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 有哪些状态,状态如下:

    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

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

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

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

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 中的对象才会销毁。


原文阅读:
Android Media Framework(六)NuPlayerDriver

扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。

<think>我们正在讨论Android音频系统中的NuPlayer和AudioOutput组件。根据引用内容,我们可以整理出以下关键点: 1. **NuPlayer**:是Android多媒体框架中的核心播放器组件,负责媒体文件的解析、解码和渲染。它支持多种媒体格式和流协议(如HTTP、RTSP等),是Android默认的媒体播放引擎之一。 2. **AudioOutput**:是MediaPlayerService中定义的一个类(继承自MediaPlayerBase::AudioSink),它作为音频输出层,封装了与底层音频硬件交互的细节。它内部使用AudioTrack来与AudioFlinger通信,最终将音频数据写入音频硬件。 3. **关系**: -MediaPlayerService中创建NuPlayerDriver(包装NuPlayer)时,会创建一个AudioOutput实例(见引用[1][2])。 - 然后通过`NuPlayer::setAudioSink(mAudioOutput)`将AudioOutput设置为NuPlayer的音频接收端(Sink)(引用[1][2])。 - 这样,NuPlayer解码后的音频数据就可以通过AudioOutput输出到音频设备。 4. **协作流程**: - NuPlayer解码音频数据后,需要将PCM数据传递给音频输出。 - AudioOutput作为NuPlayer的音频接收端,它通过内部的AudioTrack将数据写入AudioFlinger,最终由音频硬件播放。 5. **类定义**(引用[3]): ```cpp class MediaPlayerService::AudioOutput : public MediaPlayerBase::AudioSink { public: virtual status_t open(...); // 打开音频输出 private: sp<AudioTrack> mTrack; // 实际使用的AudioTrack // ... 其他成员 }; ``` 6. **NuPlayer的初始化**(引用[4]): ```cpp NuPlayer::NuPlayer(pid_t pid) { // ... 初始化成员变量,包括音频解码器mAudioDecoder } ``` 7. **整体架构**(引用[5]): - 应用程序通过MediaPlayer调用MediaPlayerService。 - MediaPlayerService创建NuPlayerDriver(包含NuPlayer)和AudioOutput。 - NuPlayerDriver将AudioOutput设置给NuPlayer作为音频接收端。 **作用总结**: - **NuPlayer**:负责媒体文件的解复用、音频解码(通过mAudioDecoder),并将解码后的音频数据传递给AudioSink(即AudioOutput)。 - **AudioOutput**:作为音频接收端,管理AudioTrack的创建、配置和数据写入,是NuPlayer与底层音频系统(AudioFlinger)之间的桥梁。 **协作流程详解**: 1. NuPlayer在播放过程中,音频解码器(mAudioDecoder)解码得到PCM数据。 2. NuPlayer调用其AudioSink(即AudioOutput)的`write()`方法(或类似机制)传递数据。 3. AudioOutput内部通过AudioTrack将数据写入AudioFlinger的共享内存。 4. AudioFlinger混合所有音频流后,通过HAL层写入音频设备。 **关键代码路径**: - NuPlayer调用AudioSink:`frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp` - AudioOutput实现:`frameworks/av/media/libmediaplayerservice/MediaPlayerService.h` (类定义) 和 `.cpp` (实现) **引用说明**: - 创建AudioOutput并设置为NuPlayer的Sink在MediaPlayerService::Client::setDataSource_pre中完成[^1][^2]。 - AudioOutput内部使用AudioTrack[^3]。 - NuPlayer初始化时创建音频解码器[^4]。 - 整体架构中MediaPlayerService作为服务端创建播放器实例[^5]。 **注意事项**: - AudioOutput还支持音频回调(AudioCallback),用于在需要新数据时通知NuPlayer(拉模式)。 - NuPlayer在暂停/恢复等操作时,会通过AudioOutput控制AudioTrack的状态(如pause/play)。 **示例调用栈**(简化): 1. `NuPlayer::onRenderAudio()` (渲染音频数据) 2. 调用 `mAudioSink->write()` (即AudioOutput::write) 3. 调用 `mTrack->write()` (AudioTrack的写入方法)</think>### NuPlayer与AudioOutput在Android音频系统中的作用及协作关系 #### 一、核心组件作用 1. **NuPlayer的作用** - **媒体播放核心引擎**:Android默认的高性能媒体播放器(替代旧版Stagefright)[^4][^5] - **功能实现**: - 媒体文件解析(解复用) - 音视频解码(通过mAudioDecoder) - 音视频同步 - 播放状态管理(播放/暂停/停止) - **关键特性**: $$ \text{播放延迟} \propto \frac{1}{\text{硬件加速级别}} $$ 支持硬件加速解码,降低CPU占用 2. **AudioOutput的作用** - **音频输出抽象层**:作为NuPlayer的音频接收端(Sink)[^1][^3] - **核心职责**: - 管理底层AudioTrack实例 - 封装音频硬件操作接口 - 处理音频格式转换(采样率/声道数) - 实现音频回调机制(AudioCallback) - **关键数据结构**: ```cpp class AudioOutput { sp<AudioTrack> mTrack; // 实际音频硬件接口 AudioCallback mCallback; // 数据回调函数 uint32_t sampleRate; // 当前采样率 audio_format_t format; // 音频格式 } ``` #### 二、协作流程 ```mermaid sequenceDiagram participant NuPlayer participant AudioOutput participant AudioTrack participant AudioFlinger NuPlayer->>AudioOutput: setAudioSink() [绑定] Note right of NuPlayer: 播放初始化阶段 NuPlayer->>AudioOutput: open(sampleRate, format) AudioOutput->>AudioTrack: 创建/配置 AudioTrack->>AudioFlinger: 注册音频流 loop 播放过程 NuPlayer->>+AudioOutput: write(PCM数据) AudioOutput->>AudioTrack: 写入数据缓冲区 AudioTrack->>AudioFlinger: 提交混合 AudioFlinger-->>AudioOutput: 通过回调请求新数据 AudioOutput-->>-NuPlayer: 回调mCallback() end Note left of NuPlayer: 暂停/恢复 NuPlayer->>AudioOutput: pause() AudioOutput->>AudioTrack: pause() AudioTrack->>AudioFlinger: 暂停硬件输出 ``` #### 三、关键交互点 1. **初始化绑定** 在`MediaPlayerService::setDataSource_pre()`中创建NuPlayer和AudioOutput,并通过: ```cpp static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); ``` 完成绑定[^1][^2] 2. **音频数据流** - NuPlayer解码后通过`AudioSink::write()`写入数据 - AudioOutput通过内部AudioTrack将数据送入AudioFlinger - 当缓冲区不足时,通过回调机制通知NuPlayer提供新数据 3. **播放控制** ```cpp // NuPlayer调用暂停 void NuPlayer::onPause() { if (mAudioSink != nullptr) { mAudioSink->pause(); // 调用AudioOutput::pause() } } // AudioOutput实现 status_t AudioOutput::pause() { return mTrack->pause(); // 调用底层AudioTrack } ``` #### 四、架构设计优势 1. **分层解耦** $$ \text{NuPlayer} \xrightarrow{\text{音频数据}} \text{AudioOutput} \xrightarrow{\text{硬件指令}} \text{AudioTrack} $$ 各层职责清晰,NuPlayer无需关心硬件细节 2. **状态同步机制** 通过`mState`变量(PLAYING/PAUSED)确保: - 暂停时保留播放位置 - 避免无效状态转换 3. **资源复用** AudioOutput维护`mRecycledTrack`和`mNextOutput`实现: - AudioTrack实例池化 - 快速切换音频流[^3] #### 五、典型问题分析 1. **音频卡顿** - **根本原因**:NuPlayer解码延迟 > AudioOutput缓冲区周期 - **解决方案**:增大`bufferCount`参数(见`open()`函数)[^3] 2. **播放位置漂移** - **机制缺陷**:暂停时未保存硬件位置 ```cpp // 优化方案 status_t AudioOutput::pause() { mPausedPosition = mTrack->getPosition(); // 保存位置 return mTrack->pause(); } ``` 3. **跨进程调用瓶颈** NuPlayer(MediaServer进程) ↔ AudioOutput(APP进程) 需Binder通信,可通过共享内存优化数据传递。 --- ### 总结关系图 ```mermaid graph LR A[应用层 MediaPlayer] --> B[MediaPlayerService] B --> C[NuPlayerDriver] C --> D[NuPlayer] D -->|setAudioSink| E[AudioOutput] E -->|控制/数据| F[AudioTrack] F --> G[AudioFlinger] G --> H[音频硬件] style D fill:#9f9,stroke:#333 style E fill:#f96,stroke:#333 ``` > NuPlayer是播放引擎,AudioOutput是其专用音频输出接口,二者共同构成Android媒体播放的音频处理流水线[^1][^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值