安卓音视频播放-总体架构

系列文章:

安卓上我们经常会使用MediaPlayer这个类去播放音频和视频,这篇笔记便从MediaPlayer着手,一层层分析安卓的音视频播放框架。

MediaPlayer

MediaPlayer的使用很简单,如果是想要在一个SurfaceView上播放,assets下的video.mp4视频,只需要下面的几行代码就能在手机上看到视频画面了:

SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface);
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        MediaPlayer player = new MediaPlayer();
        player.setDisplay(holder); //设置画面显示在哪

        try {
            player.setDataSource(getAssets().openFd("video.mp4"));  //设置视频源
            player.prepare(); //准备视频数据
        } catch (IOException e) {
            e.printStackTrace();
        }
        player.start(); //开始播放
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
});

MediaPlayer的API和用法很简单,基本上只需要看熟谷歌官方给的这幅状态图,就能很方便的使用了。其实大部分情况下无非也就是setDataSource、prepare、start、pause、stop、reset、release这几个方法的调用:

1.jpeg

具体的使用细节我这边就不去赘述了,感兴趣的可以参考下官方文档

安卓Media框架

我们在应用里面调了MediaPlayer的方法,其实底层都会通过IPC机制调到MediaPlayerService。其实不仅是MediaPlayer,android.media包下的媒体播放接口像AudioTrack、SoundPool、MediaCodec都是会调到MediaPlayerService去做具体的编解码操作的,安卓的媒体播放是个典型的C/S架构,可以参考下官方文档的架构图:

2.png

使用C/S架构的好处就是可以比较方便的统一管理软硬件编解码资源.

整个框架除了java层的MediaPlayer之外还涉及三个关键so库:

  • libmedia_jni.so 负责使用jni连接java层和native层,然后调用MediaPlayer类提供的接口
  • libmedia.so 对上层提供了MediaPlayer类负责客户端与MediaPlayerService的IPC通讯
  • libmediaplayerservice.so 负责统筹调度具体的编码器和解码器,它内部也实现了libmedia.so的IMediaPlayer类用于接收客户端通过IPC机制发送的指令

他们的依赖关系如下

3.png

代码细节

然后我们来追踪下具体的代码实现.

其实android.media.MediaPlayer这个java类只是native层的一个代理,具体的实现都是通过jni调用到libmedia_jni.so里面的c/c++代码:

public class MediaPlayer extends PlayerBase implements SubtitleController.Listener {
  ...
  static {
    System.loadLibrary("media_jni");
    native_init();
  }
  ...
  private static native final void native_init();
  ...
  private native void _setVideoSurface(Surface surface);
  ...
  private native void _prepare() throws IOException, IllegalStateException;
  ...
  private native void _start() throws IllegalStateException;
  ...
  public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException {
    _setDataSource(fd, offset, length);
  }
  ...
  public void setDisplay(SurfaceHolder sh) {
    mSurfaceHolder = sh;
    Surface surface;
    if (sh != null) {
      surface = sh.getSurface();
    } else {
      surface = null;
    }
    _setVideoSurface(surface);
    updateSurfaceScreenOn();
  }
  ...
  public void prepare() throws IOException, IllegalStateException {
    _prepare();
    scanInternalSubtitleTracks();
  }
  ...
  public void start() throws IllegalStateException {
    baseStart();
    stayAwake(true);
    _start();
  }
  ...
}

libmedia_jni.so的实现可以在/frameworks/base/media/jni/android_media_MediaPlayer.cpp里面找到:

static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    sp<MediaPlayer> mp = new MediaPlayer();
    ...
    setMediaPlayer(env, thiz, mp);
}

static void
android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    ...
    sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz);
    mp->setVideoSurfaceTexture(st);

    process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
}

static void
android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    ...
    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
}
...

而libmedia_jni.so内部也是依赖了MediaPlayer这个类去干活,它的代码可以在/frameworks/av/include/media/mediaplayer.h和/frameworks/av/media/libmedia/mediaplayer.cpp找到,而它编译之后打包在libmedia.so中

上面我们看到libmedia_jni.so里面调用了MediaPlayer的方法去干活,那MediaPlayer又是怎么干活的呢,看看具体代码:

//mediaplayer.h
sp<IMediaPlayer> mPlayer;

//mediaplayer.cpp
status_t MediaPlayer::prepare()
{
    ...
    status_t ret = prepareAsync_l();
    ...
}

status_t MediaPlayer::prepareAsync_l()
{
    ...
    return mPlayer->prepareAsync();
    ...
}

这里又依赖了一个IMediaPlayer,让我们继续挖一挖这个IMediaPlayer又是什么来的:

status_t MediaPlayer::attachNewPlayer(const sp<IMediaPlayer>& player)
{
    ...
    mPlayer = player;
    ...
}

status_t MediaPlayer::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url, const KeyedVector<String8, String8> *headers)
{
    ALOGV("setDataSource(%s)", url);
    status_t err = BAD_VALUE;
    if (url != NULL) {
        const sp<IMediaPlayerService> service(getMediaPlayerService());
        if (service != 0) {
            sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
            if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
                (NO_ERROR != player->setDataSource(httpService, url, headers))) {
                player.clear();
            }
            err = attachNewPlayer(player);
        }
    }
    return err;
}

// MediaPlayer继承IMediaDeathNotifier
IMediaDeathNotifier::getMediaPlayerService() {
    Mutex::Autolock _l(sServiceLock);
    if (sMediaPlayerService == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16("media.player"));
            if (binder != 0) {
                break;
            }
            usleep(500000); // 0.5 s
        } while (true);

        if (sDeathNotifier == NULL) {
            sDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(sDeathNotifier);
        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    return sMediaPlayerService;
}

可以看到getMediaPlayerService方法实际是从ServiceManager里面获取了"media.player"这个服务,然后拿到了IMediaPlayerService的Binder代理,又去到了MediaPlayerService::create方法:

sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client,
        audio_session_t audioSessionId)
{
    pid_t pid = IPCThreadState::self()->getCallingPid();
    int32_t connId = android_atomic_inc(&mNextConnId);

    sp<Client> c = new Client(
            this, pid, connId, client, audioSessionId,
            IPCThreadState::self()->getCallingUid());

    ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid,
         IPCThreadState::self()->getCallingUid());

    wp<Client> w = c;
    {
        Mutex::Autolock lock(mLock);
        mClients.add(w);
    }
    return c;
}

MediaPlayerService会创建一个Client返回给客户端,客户端这个Client调用到MediaPlayerService的功能了。顺嘴说一句,Client是MediaPlayerService的一个内部类,它继承了BnMediaPlayerService,而BnMediaPlayer又继承了BnInterface<IMediaPlayer>

//MediaPlayerService.h
class MediaPlayerService : public BnMediaPlayerService {
    ...
    class Client : public BnMediaPlayer {
        ...
    }
    ...
}

//IMediaPlayer.h
class BnMediaPlayer: public BnInterface<IMediaPlayer>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};

MediaPlayerService的工作原理

查看MediaPlayerService的源码,可以知道在setDataSource的时候查找支持该源的播放器,然后创建出来使用:

status_t MediaPlayerService::Client::setDataSource(const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers)
{
    ...
    player_type playerType = MediaPlayerFactory::getPlayerType(this, url);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    ...
    setDataSource_post(p, p->setDataSource(httpService, url, headers));
    ...
}

sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(player_type playerType) {
    ...
    sp<MediaPlayerBase> p = createPlayer(playerType);
    ...
}

sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
    sp<MediaPlayerBase> p = mPlayer;
    ...
    p = MediaPlayerFactory::createPlayer(playerType, this, notify, mPid);
    ...
    return p;
}

可以看到内部都是通过MediaPlayerFactory这个工厂去实现的,MediaPlayerFactory::registerBuiltinFactories方法注册了一些播放器,根据音视频源选择合适的播放器去播放。值得强调的是在sdk 23及以前的系统中会有StagefrightPlayer、NuPlayer两个播放器,sdk 24之后,真正工作的播放器就只有一个NuPlayer了。当然,各个厂家自己的提供的播放器也可以在这里注册,像小米盒子的ROM就导入过VLC框架的播放器。由于我司还有大量的安卓4.4的机器,所以我这里会把两个播放器都讲一下。

// android sdk 23
void MediaPlayerFactory::registerBuiltinFactories() {
    Mutex::Autolock lock_(&sLock);

    if (sInitComplete)
        return;

    registerFactory_l(new StagefrightPlayerFactory(), STAGEFRIGHT_PLAYER);
    registerFactory_l(new NuPlayerFactory(), NU_PLAYER);
    registerFactory_l(new TestPlayerFactory(), TEST_PLAYER);

    sInitComplete = true;
}

// android sdk 24
void MediaPlayerFactory::registerBuiltinFactories() {
    Mutex::Autolock lock_(&sLock);

    if (sInitComplete)
        return;

    registerFactory_l(new NuPlayerFactory(), NU_PLAYER);
    registerFactory_l(new TestPlayerFactory(), TEST_PLAYER);

    sInitComplete = true;
}

StagefrightPlayer实际上指的是AwesomePlayer,在早期的安卓系统使用AwesomePlayer去播放本地视频,用NuPlayer去播放流媒体。后来因为某些原因(具体原因我没有找到,只是说AwesomePlayer有问题)所以逐渐用弃用了AwesomePlayer,统一使用NuPlayer去播放。在某些过度版本的安卓系统开发者选项里面还可以选择NuPlayer代替AwesomePlayer,到后期都不用选了,只有一个NuPlayer可以用。

关于AwesomePlayer和NuPlayer的具体代码实现,我会在下篇文章继续解析.让我们继续讲这两个播放器都依赖的OpenMax框架.

OpenMax(OMX)框架

开放多媒体加速层(英语:Open Media Acceleration,缩写为OpenMAX),一个不需要授权、跨平台的软件抽象层,以C语言实现的软件界面,用来处理多媒体。它是由Khronos Group提出的标准,也由他们来维持,目标在于创造一个统一的界面,加速大量多媒体资料的处理。

也就是说OpenMax提供了具体的软硬件编解码能力,AwesomePlayer和NuPlayer依赖它,就能实现编解码功能.

OpenMax分成三层:

开发层(Development Layer,DL)

这一层定义了一些基础的音频、视频以及图像算法,比如音频信号处理的快速傅立叶变换、滤波器,图像处理的色域转换(RGB、YUV等)、视频处理的MPEG-4, H.264, MP3, AAC 和 JPEG编解码等.

DL层分为五个应用领域:

AC - 音频编解码器
IC - 图像编解码器
IP - 图像处理(通用图像处理功能)
SP - 信号处理(通用音频处理功能)
VC - 视频编解码器(H264和MP4组件)

它们都是一些比较算法层面的接口,由芯片原厂实现

整合层(Integration Layer,IL)

这一层整合了DL层的算法和功能,作为一个比较低层级的编解码器接口,也就是说实现了这一层的接口就实现了一个编解码器.它可以是软件的也可以是硬件的

应用层(Application Layer,AL)

AL层为多媒体中间件与应用层之间提供一个标准化的API接口,不同的系统都应有对应的实现,应用程序依赖这一层的接口进行编程,就能获得很好的跨平台特性.

完整框架图

到这里,整个音视频播放架构就很清晰了

4.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 一、简历准备 1、个人技能 (1)自定义控件、UI设计、常用动画特效 自定义控件 ①为什么要自定义控件? Andr...
    lucas777阅读 5,186评论 2 54
  • 提醒一下,纯个人笔记,你完全可能看晕 一、音频数字化基础知识 见书,列出知识点如下: 声音声波,声音频率、响度, ...
    YY17阅读 31,199评论 6 48
  • 最近在项目中要加入视频直播和点播功能,那么问题来了,我需要一个播放器来播放视频流,那该如何选择呢?除了原生的Vid...
    范蓄能阅读 5,330评论 1 32
  • 前言 随着音视频领域的火热,在很多领域(教育,游戏,娱乐,体育,跑步,餐饮,音乐等)尝试做音视频直播/点播功能,那...
    passiontim阅读 3,267评论 1 46
  • 2017.8.22-2017.8.25,看小说总是很快,特别一本温情脉脉,细腻又真诚的小说。书的前几页,我断断续续...
    征儿阅读 899评论 0 3