• ①Android NuPlayer播放框架


    [时间:2016-09] [状态:Open]
    [关键词:android,nuplayer,开源播放器,播放框架,nuplayerdriver]

    0 NuPlayer简介

    Android2.3时引入流媒体框架,而流媒体框架的核心是NuPlayer。在之前的版本中一般认为Local Playback就用Stagefrightplayer+Awesomeplayer,流媒体用NuPlayer。Android4.0之后HttpLive和RTSP协议开始使用NuPlayer播放器,Android5.0(L版本)之后本地播放也开始使用NuPlayer播放器。 Android7.0(N版本)则完全去掉了Awesomeplayer。
    通俗点说,NuPlayer是AOSP中提供的多媒体播放框架,能够支持本地文件、HTTP(HLS)、RTSP等协议的播放,通常支持H.264、H.265/HEVC、AAC编码格式,支持MP4、MPEG-TS封装。
    在实现上NuPlayer和Awesomeplayer不同,NuPlayer基于StagefrightPlayer的基础类构建,利用了更底层的ALooper/AHandler机制来异步地处理请求,ALooper列队消息请求,AHandler中去处理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。

    1 NuPlayer框架

    下图是NuPlayer整体框架图
    nuplayer arch

    或者下图
    nuplayer class diagram

    Android层的多媒体框架,有多层实现,甚至有跨进程的调用。这里重点关注NuPlayerDriver之后的实现和相关逻辑。至于上层的调用逻辑,建议参考其他资料。

    各部分功能如下:

    • NuPlayer::Source:解析模块(parser,功能类似FFmpeg的avformat)。其接口与MediaExtractor和MediaSource组合的接口差不多,同时提供了用于快速定位的seekTo接口。
    • NuPlayer::Decoder:解码模块(decoder,功能类似FFmpeg的avcodec),封装了用于AVC、AAC解码的接口,通过ACodec实现解码(包含OMX硬解码和软解码)。
    • NuPlayer::Render:渲染模块(render,功能类似声卡驱动和显卡驱动),主要用于音视频渲染和同步,与NativeWindow有关。

    2 多媒体文件如何通过NuPlayer播放的

    在AOSP中,通常将一个多媒体文件或者URL称为DataSource。通常多媒体文件中包含至少一个音频流、视频流或者字幕流,NuPlayer将这三种统称为Track,细分下也有AudioTrack、VideoTrack、SubtitleTrack。将一个多媒体文件解析之后就可以通过解码器还原为原始数据,然后渲染了。具体流程参考下图:
    multimedia-file-play-proc

    DataSource有两个概念:

    • 上图中的DataSourceInput(DataSource)指的是单纯的原始数据(容器格式,没有经过demuxer处理)。
    • 在后文中setDataSource中DataSource指的是从数据输入到demux输出的一个过程(即图中最外层的DataSource)。

    VideoTrack与AudioTrack指的是Extractor(即demux)的两个通道,从这里输出的分别就是单纯的解复用后的Video和Audio流。再经过Decoder后输出的就是音、视频的输出了:

    • VideoRenderer + Surface即视频的输出;
    • AudioSink即音频的输出;

    至于Android应用层如何调用MediaPlayer,建议参考我之前的文章MediaPlayer Interface&State

    3 我个人对于AOSP的源码分析的方法

    鉴于AOSP是一个操作系统,整体比较复杂,从实际出发,可以关注于某个点。比如我这里主要关注NuPlayer的框架,其内部实现逻辑。那么最终就落实到如何从一个类中提取出需要的框架及知识点。那么一个类的对外接口部分通常包括:

    • 构造函数和析构函数
    • 必须调用的接口
    • 可选的调用接口

    在多媒体播放中,通过关注的点有:

    • 如何实现解复用,得到音频、视频、字幕等数据
    • 如何实现解码
    • 如何实现音视频同步
    • 如何渲染视频
    • 如何播放音频
    • 如何实现快速定位

    4 NuPlayer接口实现分析(NuPlayerDriver)

    NuPlayer框架中最顶层的类是NuPlayerDriver,继承自MediaPlayerInterface,主要提供一个状态转换机制,作为NuPlayer类的Wrapper。NuPlayerDriver类中最重要的成员是以下几个:

    • State mState 播放器状体标志
    • sp<ALooper> mLooper 内部消息驱动机制
    • sp<NuPlayer> mPlayer 真正完成播放器的类

    先说明下我参考的是Android 7的源码,NuPlayerDriver.cpp (目录:./frameworks/av/media/libmediaplayerservice/nuplayer/)

    4.1 构造函数&析构函数

    从代码中可以看到,构造函数中最主要的作用是创建ALooper和NuPlayer实例,并将它们关联起来。

    mLooper = (new ALooper);
    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO);
    
    mPlayer = new NuPlayer(pid);
    mLooper->registerHandler(mPlayer);
    
    mPlayer->setDriver(this);
    

    析构函数主要就是销毁创建的ALooper和NuPlayer,由于是智能指针,直接调用stop即可。

    mLooper->stop();
    

    4.2 SetDataSource

    这个接口实现很简单,检查当前的播放状态,然后直接调用NuPlayer::setDataSourceAsync函数。

    status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
        ALOGV("setDataSource(%p) file(%d)", this, fd);
        Mutex::Autolock autoLock(mLock);
    
        if (mState != STATE_IDLE) {
            return INVALID_OPERATION;
        }
    
        mState = STATE_SET_DATASOURCE_PENDING;
    
        mPlayer->setDataSourceAsync(fd, offset, length);
    
        while (mState == STATE_SET_DATASOURCE_PENDING) {
            mCondition.wait(mLock);
        }
    
        return mAsyncResult;
    }
    

    最后等待该函数返回,并调用NuPlayerDriver::notifySetDataSourceCompleted接口,改变播放器状态。

    4.3 setVideoSurfaceTexture

    这个接口的实现思路跟SetDataSource,不过调用的是NuPlayer::setVideoSurfaceTextureAsync,该请求处理完之后调用NuPlayerDriver::notifySetSurfaceComplete接口。

    4.4 prepare/prepareAsync

    这两个接口基本功能是一致的,只是第二个是异步的调用过程。第一个通过prepare_l接口实现,二者最终均调用NuPlayer::prepareAsync,请求处理完成之后调用NuPlayerDriver::notifyPrepareCompleted接口。不过这里面有大量的关于播放状态判断的代码。比如prepareAsync中代码

    ALOGV("prepareAsync(%p)", this);
    Mutex::Autolock autoLock(mLock);
    
    switch (mState) {
        case STATE_UNPREPARED:
            mState = STATE_PREPARING;
            mIsAsyncPrepare = true;
            mPlayer->prepareAsync();
            return OK;
        case STATE_STOPPED:
            // this is really just paused. handle as seek to start
            mAtEOS = false;
            mState = STATE_STOPPED_AND_PREPARING;
            mIsAsyncPrepare = true;
            mPlayer->seekToAsync(0, true /* needNotify */);
            return OK;
        default:
            return INVALID_OPERATION;
    };
    

    4.5 start/stop

    这两个函数作为开始播放和停止播放的接口,主要涉及到播放器内部状态的切换和判断。最终功能实现通过调用NuPlayer::start和NuPlayer::pause接口。
    下面是start函数实现代码(start_l),主要需要判断不同状态下的调用逻辑:

    switch (mState) {
        case STATE_UNPREPARED:
        {
            status_t err = prepare_l();
    
            if (err != OK) {
                return err;
            }
    
            CHECK_EQ(mState, STATE_PREPARED);
    
            // fall through
        }
    
        case STATE_PAUSED:
        case STATE_STOPPED_AND_PREPARED:
        case STATE_PREPARED:
        {
            mPlayer->start();
    
            // fall through
        }
    
        case STATE_RUNNING:
        {
            if (mAtEOS) {
                mPlayer->seekToAsync(0);
                mAtEOS = false;
                mPositionUs = -1;
            }
            break;
        }
    
        default:
            return INVALID_OPERATION;
    }
    
    mState = STATE_RUNNING;
    

    stop接口实现则相对简单,主要是判断什么状态下可以调用stop接口,并上报MEDIA_STOPPED状态。代码如下:

    switch (mState) {
        case STATE_RUNNING:
            mPlayer->pause();
            // fall through
    
        case STATE_PAUSED:
            mState = STATE_STOPPED;
            notifyListener_l(MEDIA_STOPPED);
            break;
    
        case STATE_PREPARED:
        case STATE_STOPPED:
        case STATE_STOPPED_AND_PREPARING:
        case STATE_STOPPED_AND_PREPARED:
            mState = STATE_STOPPED;
            break;
    
        default:
            return INVALID_OPERATION;
    }
    

    4.6 pause / reset

    pause用于实现暂停,其实现比较简单,直接调用NuPlayer::puase实现,代码如下:

    switch (mState) {
        case STATE_PAUSED:
        case STATE_PREPARED:
            return OK;
    
        case STATE_RUNNING:
            mState = STATE_PAUSED;
            notifyListener_l(MEDIA_PAUSED);
            mPlayer->pause();
            break;
    
        default:
            return INVALID_OPERATION;
    }
    

    reset重置播放器,这是一个同步调用的接口。最终实现通过调用NuPlayer::resetAsync接口,然后调用NuPlayerDriver::notifyResetComplete通知。

    4.7 isPlaying / getDuration / getCurrentPosition

    这几个接口主要用于获取播放器的状态。
    isPlaying直接通过播放器状态判断,其实现如下:

    bool NuPlayerDriver::isPlaying() {
        return mState == STATE_RUNNING && !mAtEOS;
    }
    

    getDuration的实现也相对简单,代码如下。不过具体获取的mDurationUs需要通过NuPlayer上报或者定期查询更新下。

    status_t NuPlayerDriver::getDuration(int *msec) {
        Mutex::Autolock autoLock(mLock);
    
        if (mDurationUs < 0) {
            return UNKNOWN_ERROR;
        }
    
        *msec = (mDurationUs + 500ll) / 1000;
    
        return OK;
    }
    

    getCurrentPosition则是通过调用NuPlayer::getCurrentPosition获取。

    4.8 getMetadata

    这个接口主要是判断播放器的属性,比如是否支持暂停、seek、向前seek、向后seek等。

    5 后续细节分析

    本文主要分析了NuPlayerDriver的接口实现,接下来分析的部分包括:

    • ALooper机制
    • NuPlayer
    • NuPlayer::Decoder
    • NuPlayer::Source
    • NuPlayer::Render
    • ACodec

    总结下来,NuPlayerDriver主要是接口层的一个衔接,并记录了播放器内部的状态数据,以保证其符合Android MediaPlayer状态调用逻辑。代码相对简单。

    参考文献

    1. NuPlayer介绍
    2. NuPlayer for HTTP live streaming
    3. Stagefright框架中视频播放流程
  • 相关阅读:
    POJ 3630 Phone List | Trie 树
    POJ 3974 Palindrome | 马拉车模板
    POJ 3422 Kaka's Matrix Travels | 最小费用最大流
    POJ 2195 Going Home | 带权二分图匹配
    POJ 3068 "Shortest" pair of paths | 最小费用最大流
    POJ 3686 The Windy's | 最小费用最大流
    洛谷 最小费用最大流 模板 P3381
    POJ 2987 Firing | 最大权闭合团
    POJ 3469 Dual Core CPU | 最小割
    POJ 3281 Dining | 最大流
  • 原文地址:https://www.cnblogs.com/tocy/p/1-android-nuplayer-arch-intro.html
Copyright © 2020-2023  润新知