zoukankan      html  css  js  c++  java
  • ①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框架中视频播放流程
  • 相关阅读:
    游标cursor
    SQL: EXISTS
    LeetCode Reverse Integer
    LeetCode Same Tree
    LeetCode Maximum Depth of Binary Tree
    LeetCode 3Sum Closest
    LeetCode Linked List Cycle
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Balanced Binary Tree
    LeetCode Validate Binary Search Tree
  • 原文地址:https://www.cnblogs.com/tocy/p/1-android-nuplayer-arch-intro.html
Copyright © 2011-2022 走看看