Android音视频框架探索(三):系统播放器MediaPlayer的创建流程

0. 前言

在前面的文章《Android音视频学习(一):MediaPlayer》中,我们对Android系统提供的音视频组件MediaPlayer的应用层接口,主要状态转换和使用流程有了一个大概的了解。今天我们将更进一步,深入Android源码讨论MediaPlayer的主体架构。本文主要介绍MediaPlayer从App到其底层NuPlayer实现的创建过程。

1. MediaPlayer的代码结构

首先介绍一下MediaPlayer的代码结构。在实现上MediaPlayer也是类似C/S架构形式,App作为使用者,即客户端,主要使用java的MediaPlayer接口来控制播放功能,经过JNI,其底层的C++对象通过Binder与MediaServer进行通信。

而系统的MediaServer进程则是服务端,其中运行的MediaPlayerService对象是系统层面的MediaPlayer的总管对象,系统内当前所有的MediaPlayer会由其来管理。当收到客户端的Binder请求时,MediaPlayerService会创建具体的MediaPlayerService::Client,为当前的App进行服务。在层次图上这里结构有一些代码上的封装,但MediaPlayerService::Client可以看作具体持有和使用播放器的核心NuPlayer

MediaPlayer的java层API在安卓源码目录的frameworks/base/media/java/android/media/MediaPlayer.java下。
MediaPlayerService的代码在frameworks/av/media/libmediaplayerservice/MediaPlayerService.h NuPlayer的代码则在frameworks/av/media/libmediaplayerservice/nuplayer/

2. 创建流程源码探究

2.1 构造方法

首先从MediaPlayer的构造方法看起。

2.1.1 主要流程

MediaPlayer的构造方法主要流程如下,主要是初始化,创建native层的C++对象MediaPlayer(继承自MediaPlayerClient, IMediaPlayerClient Binder)为其设置一个listener,用于java层对象收到回调。

2.1.2 源码走读

java 复制代码
// MediaPlayer最主要的构造方法
// Context是App的上下文对象
// sessionId是AudioSessionID, 用于音频系统,需要创建一个会话id,如果设置为AudioSystem.AUDIO_SESSION_ALLOCATE (0), 则会由底层自动生成一个唯一id
private MediaPlayer(Context context, int sessionId) {
    super(new AudioAttributes.Builder().build(),
            AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);

    // 这里是创建EventHandler,并将自身引用传入,用于响应时间
    // looper是事件系统的事件循环,代表着事件监听者的线程。
    Looper looper;
    if ((looper = Looper.myLooper()) != null) {
        // 如果当前线程有事件循环,则EventHandler交给本线程监听
        mEventHandler = new EventHandler(this, looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
        // 否则交由主线程的事件循环监听。
        mEventHandler = new EventHandler(this, looper);
    } else {
        mEventHandler = null;
    }

    // 初始化一个计时器,可以为MediaPlayer提供当前时间,设置定时任务等行管操作
    mTimeProvider = new TimeProvider(this);
    // 字幕流输入,不是很重要
    mOpenSubtitleSources = new Vector<InputStream>();

    // AttributionSource用于追踪应用的权限,经常被用于不同应用之间确认权限信息,这里主要是用于native层确认权限信息。
    AttributionSource attributionSource =
            context == null ? AttributionSource.myAttributionSource()
                    : context.getAttributionSource();
    // set the package name to empty if it was null
    if (attributionSource.getPackageName() == null) {
        attributionSource = attributionSource.withPackageName("");
    }

    // 这里其实就是将attributionSource内容,序列化为Parcel结构,用于Binder通信
    try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
        // 主流程!调用native_setup完成native层的创建工作
        native_setup(new WeakReference<>(this), attributionSourceState.getParcel(),
                resolvePlaybackSessionId(context, sessionId));
    }
    // 调用基类的方法,实例化完成后向AudioService设置一下audioSessionid和当前player的信息
    baseRegisterPlayer(getAudioSessionId());
}

native_setup()是一个native方法,它会通过JNI调用到frameworks/base/media/jni/android_media_MediaPlayer.cpp下的android_media_MediaPlayer_native_setup()方法。

C++ 复制代码
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                       jobject jAttributionSource,
                                       jint jAudioSessionId)
{
    ALOGV("native_setup");
    // 获取Parcel,反序列化并通过其构造C++层的attributionSource对象
    Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
    android::content::AttributionSourceState attributionSource;
    attributionSource.readFromParcel(parcel);
    // 创建C++层的MediaPlayer
    sp<MediaPlayer> mp = sp<MediaPlayer>::make(
        attributionSource, static_cast<audio_session_t>(jAudioSessionId));
    if (mp == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
        return;
    }

    // 创建一个JNI层的listener,用于触发MediaPlayer的回调。
    // 这里JNIMediaPlayerListener会在env里创建MediaPlayer的全局jclass引用,以及weak_this的全局引用,方便从其他线程触发回调。
    // listener会交给C++的mp保留,事件触发时回调给jni层。
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);

    // 将C++的mp对象设置给java对象,会将原始指针保存在java对象的mNativeContext成员中
    setMediaPlayer(env, thiz, mp);
}

让我们来看看C++层的MediaPlayer在创建时做了什么,这里就进入了frameworks/av/media/libmedia/mediaplayer.cpp的MediaPlayer::MediaPlayer()方法。

C++ 复制代码
// 构造方法
MediaPlayer::MediaPlayer(const AttributionSourceState& attributionSource,
    const audio_session_t sessionId) : mAttributionSource(attributionSource)
{
    // 以下都可以看作是状态的初始化,一开始没办法搞明白每个成员的意义
    // 但是不要紧,我们在具体功能中才能去理解,尽量先从大意上有个认识就行
    ALOGV("constructor");
    mListener = NULL;             // JNI监听者
    mCookie = NULL;
    mStreamType = AUDIO_STREAM_MUSIC;
    mAudioAttributesParcel = NULL;
    mCurrentPosition = -1;
    mCurrentSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
    mSeekPosition = -1;
    mSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
    mCurrentState = MEDIA_PLAYER_IDLE;       // MediaPlayer的状态,构造后进入idle
    mPrepareSync = false;
    mPrepareStatus = NO_ERROR;
    mLoop = false;
    mLeftVolume = mRightVolume = 1.0;
    mVideoWidth = mVideoHeight = 0;
    mLockThreadId = 0;
    // 如果audioSeesionid 传入的是AUDIO_SESSION_ALLOCATE (0), 那么底层具体的id则会自行生成一个唯一id
    if (sessionId == AUDIO_SESSION_ALLOCATE) {
        mAudioSessionId = static_cast<audio_session_t>(
            AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION));
    } else {
        mAudioSessionId = sessionId;
    }
    AudioSystem::acquireAudioSessionId(mAudioSessionId, (pid_t)-1, (uid_t)-1); // always in client.
    mSendLevel = 0;
    mRetransmitEndpointValid = false;
}

// JNI层在构造之后调用的setListener(), 只是对象赋值而已。
status_t MediaPlayer::setListener(const sp<MediaPlayerListener>& listener)
{
    ALOGV("setListener");
    Mutex::Autolock _l(mLock);
    mListener = listener;
    return NO_ERROR;
}

2.2 setDataSource()

setDataSource()方法为MediaPlayer设置需要播放的输入媒体源,在实现上它还包含了创建底层播放器核心NuPlayer的任务。

2.2.1 主要流程

App调用setDataSource()的主要任务是为了给播放器设置输入参数,如远程文件url、本地文件路径信息等。

而在setDataSource()这个方法内具体还会通过MediaPlayerService创建底层播放器实现NuPlayer。主要的流程如下图所示:

2.2.2 源码走读

2.2.2.1 从Java层到JNI

setDataSource()方法有很多种参数输入的版本,我们从最简单的版本看起:

java 复制代码
// 输入是String的版本,也可以是本地文件,也可以是远程url
public void setDataSource(String path)
        throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
    setDataSource(path, null, null);
}

继而会调用三参数版本的同名方法。

java 复制代码
// 注意这里输入的headers和cookies都为null
private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies)
        throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
    String[] keys = null;
    String[] values = null;

    if (headers != null) {
        keys = new String[headers.size()];
        values = new String[headers.size()];

        int i = 0;
        for (Map.Entry<String, String> entry: headers.entrySet()) {
            keys[i] = entry.getKey();
            values[i] = entry.getValue();
            ++i;
        }
    }
    // 所以这里相当于调用setDataSource(path, null, null, null);
    setDataSource(path, keys, values, cookies);
}

然后会调用4参数的版本:

java 复制代码
private void setDataSource(String path, String[] keys, String[] values,
        List<HttpCookie> cookies)
        throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
    // 解析url和对应的协议
    final Uri uri = Uri.parse(path);
    final String scheme = uri.getScheme();
    if ("file".equals(scheme)) {
        path = uri.getPath();
    } else if (scheme != null) {
        // 如果是非本地文件资源,会走这里
        nativeSetDataSource(
            MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
            path,
            keys,
            values);
        return;
    }

    // 如果是本地文件,打开走文件描述符fd版本的输入
    final File file = new File(path);
    try (FileInputStream is = new FileInputStream(file)) {
        setDataSource(is.getFD());
    }
}

我们先看看输入是文件的版本:

java 复制代码
// 直接设置offest为0,length为MAX,调参数为(fd, offset, length)的版本
public void setDataSource(FileDescriptor fd)
        throws IOException, IllegalArgumentException, IllegalStateException {
    // intentionally less than LONG_MAX
    setDataSource(fd, 0, 0x7ffffffffffffffL);
}

public void setDataSource(FileDescriptor fd, long offset, long length)
        throws IOException, IllegalArgumentException, IllegalStateException {
    try (ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd)) {
        if (modernFd == null) {
            _setDataSource(fd, offset, length);
        } else {
            _setDataSource(modernFd.getFileDescriptor(), offset, length);
        }
    } catch (IOException e) {
        Log.w(TAG, "Ignoring IO error while setting data source", e);
    }
}

之后调用_setDataSource(),也是一个native方法:

java 复制代码
private native void _setDataSource(FileDescriptor fd, long offset, long length)
        throws IOException, IllegalArgumentException, IllegalStateException;

对应的JNI层的方法:

C++ 复制代码
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
    // 从java对象的mNativeContext成员中拿到C++对象mediaplayer的指针
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }

    if (fileDescriptor == NULL) {
        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
        return;
    }
    // 从java对象中获取文件描述符fd
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    ALOGV("setDataSourceFD: fd %d", fd);
    // 注意,这里套了两层
    // 首先是调用C++层的方法,mp->setDataSource()
    // 然后根据其返回结果,调用process_media_player_call()对异常进行处理。
    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}
2.2.2.2 通过MediaPlayerService来创建MediaPlayerService::Client

然后来看C++层对象的setDataSource():

C++ 复制代码
status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
    ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);
    status_t err = UNKNOWN_ERROR;
    // 获得mediaPlayerService(的Binder代理)对象
    const sp<IMediaPlayerService> service(getMediaPlayerService());
    if (service != 0) {
        // 调用service的create方法,创建IMediaPlayer Binder对象(实体在MediaPlayerService进程)
        sp<IMediaPlayer> player(service->create(this, mAudioSessionId, mAttributionSource));
        // doSetRetransmitEndpoint是有调用setRetransmitEndpoint()后才会生效,功能是"转发XX至对应IP?"(先忽略,不影响主流程,一般也不会生效)
        // 具体调用IMediaPlayer的setDataSource方法来完成功能
        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
            (NO_ERROR != player->setDataSource(fd, offset, length))) {
            player.clear();
        }
        // 保留新创建的IMediaPlayer引用,会存放在mPlayer字段中
        err = attachNewPlayer(player);
    }
    return err;
}

看一下MediaPlayerServicecreate方法:

C++ 复制代码
sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client,
        audio_session_t audioSessionId, const AttributionSourceState& attributionSource)
{
    // -- 创建一个单调递增的connect id
    int32_t connId = android_atomic_inc(&mNextConnId);
    // -- 创建AttributionSourceState
    AttributionSourceState verifiedAttributionSource = attributionSource;
    verifiedAttributionSource.pid = VALUE_OR_FATAL(
        legacy2aidl_pid_t_int32_t(IPCThreadState::self()->getCallingPid()));
    verifiedAttributionSource.uid = VALUE_OR_FATAL(
        legacy2aidl_uid_t_int32_t(IPCThreadState::self()->getCallingUid()));

    // 创建MediaPlayer(实体)
    sp<Client> c = new Client(
            this, verifiedAttributionSource, connId, client, audioSessionId);

    ALOGV("Create new client(%d) from %s, ", connId,
        verifiedAttributionSource.toString().c_str());

    // 弱引用加入到mClients列表中
    wp<Client> w = c;
    {
        Mutex::Autolock lock(mLock);
        mClients.add(w);
    }
    return c;
}
2.2.2.3 创建具体的NuPlayer

MediaPlayerService::create()创建一个MediaPlayerService::Client后,所调用MediaPlayerService::ClientsetDataSource方法的具体实现:

c++ 复制代码
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
    ALOGV("setDataSource fd=%d (%s), offset=%lld, length=%lld",
            fd, nameForFd(fd).c_str(), (long long) offset, (long long) length);
    // 拿到fd对应的文件状态,判断参数offset和length是否有效
    struct stat sb;
    int ret = fstat(fd, &sb);
    if (ret != 0) {
        ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
        return UNKNOWN_ERROR;
    }

    ALOGV("st_dev  = %llu", static_cast<unsigned long long>(sb.st_dev));
    ALOGV("st_mode = %u", sb.st_mode);
    ALOGV("st_uid  = %lu", static_cast<unsigned long>(sb.st_uid));
    ALOGV("st_gid  = %lu", static_cast<unsigned long>(sb.st_gid));
    ALOGV("st_size = %llu", static_cast<unsigned long long>(sb.st_size));

    if (offset >= sb.st_size) {
        ALOGE("offset error");
        return UNKNOWN_ERROR;
    }
    if (offset + length > sb.st_size) {
        length = sb.st_size - offset;
        ALOGV("calculated length = %lld", (long long)length);
    }

    // 根据输入判断Player实现的具体type,这里有Stagefright和NuPlayer,在Androd6.0之后主要使用Nuplayer
    // 在最新的Android代码中,这里player_type虽然有STAGEFRIGHT_PLAYER,但实际上的工厂类Factory等,都不再包含Stagefright的代码,所以这里默认创建的是NuPlayer
    player_type playerType = MediaPlayerFactory::getPlayerType(this,
                                                               fd,
                                                               offset,
                                                               length);
                                                               
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }

    // now set data source
    return mStatus = setDataSource_post(p, p->setDataSource(fd, offset, length));
}

具体看看setDataSource_pre(),里面会创建NuPlayer示例:

c++ 复制代码
sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
        player_type playerType)
{
    ALOGV("player type = %d", playerType);

    // 这里会创建NuPlayer
    sp<MediaPlayerBase> p = createPlayer(playerType);
    if (p == NULL) {
        return p;
    }

    // 创建几个Service的状态监听,在进程挂掉后会触发回调
    std::vector<DeathNotifier> deathNotifiers;

    // Listen to death of media.extractor service
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("media.extractor"));
    if (binder == NULL) {
        ALOGE("extractor service not available");
        return NULL;
    }
    deathNotifiers.emplace_back(
            binder, [l = wp<MediaPlayerBase>(p)]() {
        sp<MediaPlayerBase> listener = l.promote();
        if (listener) {
            ALOGI("media.extractor died. Sending death notification.");
            listener->sendEvent(MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED,
                                MEDIAEXTRACTOR_PROCESS_DEATH);
        } else {
            ALOGW("media.extractor died without a death handler.");
        }
    });

    {
        using ::android::hidl::base::V1_0::IBase;

        // Listen to death of OMX service
        {
            sp<IBase> base = ::android::hardware::media::omx::V1_0::
                    IOmx::getService();
            if (base == nullptr) {
                ALOGD("OMX service is not available");
            } else {
                deathNotifiers.emplace_back(
                        base, [l = wp<MediaPlayerBase>(p)]() {
                    sp<MediaPlayerBase> listener = l.promote();
                    if (listener) {
                        ALOGI("OMX service died. "
                              "Sending death notification.");
                        listener->sendEvent(
                                MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED,
                                MEDIACODEC_PROCESS_DEATH);
                    } else {
                        ALOGW("OMX service died without a death handler.");
                    }
                });
            }
        }

        // Listen to death of Codec2 services
        {
            for (std::shared_ptr<Codec2Client> const& client :
                    Codec2Client::CreateFromAllServices()) {
                sp<IBase> hidlBase = client->getHidlBase();
                ::ndk::SpAIBinder aidlBase = client->getAidlBase();
                auto onBinderDied = [l = wp<MediaPlayerBase>(p),
                                     name = std::string(client->getServiceName())]() {
                    sp<MediaPlayerBase> listener = l.promote();
                    if (listener) {
                        ALOGI("Codec2 service \"%s\" died. "
                              "Sending death notification.",
                              name.c_str());
                        listener->sendEvent(
                                MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED,
                                MEDIACODEC_PROCESS_DEATH);
                    } else {
                        ALOGW("Codec2 service \"%s\" died "
                              "without a death handler.",
                              name.c_str());
                    }
                };
                if (hidlBase) {
                    deathNotifiers.emplace_back(hidlBase, onBinderDied);
                } else if (aidlBase.get() != nullptr) {
                    deathNotifiers.emplace_back(aidlBase, onBinderDied);
                }
            }
        }
    }

    Mutex::Autolock lock(mLock);

    mDeathNotifiers.clear();
    mDeathNotifiers.swap(deathNotifiers);
    mAudioDeviceUpdatedListener = new AudioDeviceUpdatedNotifier(p);

    if (!p->hardwareOutput()) {
        mAudioOutput = new AudioOutput(mAudioSessionId, mAttributionSource,
                mAudioAttributes, mAudioDeviceUpdatedListener);
        static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
    }

    return p;
}

createPlayer()方法里面会调用MediaPlayerFactory的静态方法来创建具体的播放器NuPlayer

C++ 复制代码
sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
    // 这里getPlayer就是返回的mPlayer,第一次创建时为nullptr
    sp<MediaPlayerBase> p = getPlayer();
    if ((p != NULL) && (p->playerType() != playerType)) {
        ALOGV("delete player");
        p.clear();
    }
    if (p == NULL) {
        // 通过MediaPlayerFactory来创建
        p = MediaPlayerFactory::createPlayer(playerType, mListener,
            VALUE_OR_FATAL(aidl2legacy_int32_t_pid_t(mAttributionSource.pid)));
    }

    if (p != NULL) {
        p->setUID(VALUE_OR_FATAL(aidl2legacy_int32_t_uid_t(mAttributionSource.uid)));
    }

    return p;
}
2.2.2.4 MediaPlayerFactory创建NuPlayer的具体过程

继续上文,具体是调用MediaPlayerFactory::createPlayer()

C++ 复制代码
sp<MediaPlayerBase> MediaPlayerFactory::createPlayer(
        player_type playerType,
        const sp<MediaPlayerBase::Listener> &listener,
        pid_t pid) {
    sp<MediaPlayerBase> p;
    IFactory* factory;
    status_t init_result;
    Mutex::Autolock lock_(&sLock);

    // 根据sFactoryMap中注册的type,这里选到目前只有NuPlayerFactory 
    // sFactoryMap的注册逻辑可以参考frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp中的内容
    if (sFactoryMap.indexOfKey(playerType) < 0) {
        ALOGE("Failed to create player object of type %d, no registered"
              " factory", playerType);
        return p;
    }

    // 一定是NuPlayerFactory
    factory = sFactoryMap.valueFor(playerType);
    CHECK(NULL != factory);
    // 调用对应的创建方法
    p = factory->createPlayer(pid);

    if (p == NULL) {
        ALOGE("Failed to create player object of type %d, create failed",
               playerType);
        return p;
    }

    init_result = p->initCheck();
    if (init_result == NO_ERROR) {
        p->setNotifyCallback(listener);
    } else {
        ALOGE("Failed to create player object of type %d, initCheck failed"
              " (res = %d)", playerType, init_result);
        p.clear();
    }

    return p;
}

NuPlayer::createPlayer()里面则是创建了一个NuPlayerDriver

C++ 复制代码
class NuPlayerFactory : public MediaPlayerFactory::IFactory {
  public:
    // ...
    virtual sp<MediaPlayerBase> createPlayer(pid_t pid) {
        ALOGV(" create NuPlayer");
        return new NuPlayerDriver(pid);
    }
};
2.2.2.5 NuPlayerDriver及NuPlayer的构造过程

这个NuPlayerDriver继承自MediaPlayerInterface,它其中包含一个成员mPlayer,具体指向NuPlayer,在构造函数中会一起创建。

C++ 复制代码
// struct NuPlayerDriver : public MediaPlayerInterface

NuPlayerDriver::NuPlayerDriver(pid_t pid)
    : mState(STATE_IDLE),
      mIsAsyncPrepare(false),
      mAsyncResult(UNKNOWN_ERROR),
      mSetSurfaceInProgress(false),
      mDurationUs(-1),
      mPositionUs(-1),
      mSeekInProgress(false),
      mPlayingTimeUs(0),
      mRebufferingTimeUs(0),
      mRebufferingEvents(0),
      mRebufferingAtExit(false),
      mLooper(new ALooper),                     // 会创建一个ALooper, 事件循环
      mMediaClock(new MediaClock),
      mPlayer(new NuPlayer(pid, mMediaClock)),  // 创建新的mPlayer
      mPlayerFlags(0),
      mCachedPlayerIId(PLAYER_PIID_INVALID),
      mMetricsItem(NULL),
      mClientUid(-1),
      mAtEOS(false),
      mLooping(false),
      mAutoLoop(false) {
    ALOGD("NuPlayerDriver(%p) created, clientPid(%d)", this, pid);
    mLooper->setName("NuPlayerDriver Looper");

    // 初始化时钟, 这里时钟也是基于单独运行在一个新线程上的新的ALooper,在init()中创建
    mMediaClock->init();                        

    // set up an analytics record
    mMetricsItem = mediametrics::Item::create(kKeyPlayer);
    
    // 启动事件循环
    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO);

    // 事件处理者,mPlayer 
    mLooper->registerHandler(mPlayer);

    // NuPlayer初始化
    mPlayer->init(this);
}

时钟在创建时也会创建一个单独的ALooper:

C++ 复制代码
MediaClock::MediaClock()
    : mAnchorTimeMediaUs(-1),
      mAnchorTimeRealUs(-1),
      mMaxTimeMediaUs(INT64_MAX),
      mStartingTimeMediaUs(-1),
      mPlaybackRate(1.0),
      mGeneration(0) {
    mLooper = new ALooper;
    mLooper->setName("MediaClock");
    mLooper->start(false /* runOnCallingThread */,
                   false /* canCallJava */,
                   ANDROID_PRIORITY_AUDIO);
}

void MediaClock::init() {
    mLooper->registerHandler(this);
}
2.2.2.6 MediaPlayerService::Client::setDataSource的后续调用

在2.2.2.3节最后,通过setDataSource_pre()MediaPlayerService::Client创建了具体的NuPlayerDriver实例,之后会调用其setDataSource()方法,里面会调用NuPlayersetDataSourceAsync()方法。

C++ 复制代码
status_t NuPlayerDriver::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url,
        const KeyedVector<String8, String8> *headers) {
    ALOGV("setDataSource(%p) url(%s)", this, uriDebugString(url, false).c_str());
    ATRACE_BEGIN(StringPrintf("setDataSource(%p)", this).c_str());
    Mutex::Autolock autoLock(mLock);

    if (mState != STATE_IDLE) {
        ATRACE_END();
        return INVALID_OPERATION;
    }

    mState = STATE_SET_DATASOURCE_PENDING;

    // 调用NuPlayer的setDataSourceAsync()方法
    mPlayer->setDataSourceAsync(httpService, url, headers);

    // 同步等待上面的方法完成
    while (mState == STATE_SET_DATASOURCE_PENDING) {
        mCondition.wait(mLock);
    }
    ATRACE_END();

    return mAsyncResult;
}

NuPlayerSetDataSource具体做了什么,我们在后续文章中进行解析。

之后MediaPlayerService::Client会调用setDataSource_post()方法。

C++ 复制代码
status_t MediaPlayerService::Client::setDataSource_post(
        const sp<MediaPlayerBase>& p,
        status_t status)
{
    ALOGV(" setDataSource");
    if (status != OK) {
        ALOGE("  error: %d", status);
        return status;
    }

    // 设置转发端口?不是很重要
    if (mRetransmitEndpointValid) {
        status = p->setRetransmitEndpoint(&mRetransmitEndpoint);
        if (status != NO_ERROR) {
            ALOGE("setRetransmitEndpoint error: %d", status);
        }
    }

    if (status == OK) {
        Mutex::Autolock lock(mLock);
        // 将NuPlayerDriver保留引用
        mPlayer = p;
    }
    return status;
}

3. 后记(碎碎念)

说实话,这篇博文鸽了两个多月,除了之前工作上的任务繁忙了一点,其他原因还是得反思一下,就是步子迈大了,把控不住就容易犯懒,降低动力。

我原本预期是想用一篇文章来介绍和讲解MediaPlayer的主要结构和接口,但是当我这么处理时,我现在回头来想,其实中间过程很难推动。有以下原因:

  1. 标题开太大,但是自身储备不足,整体结构难以把控。在这种情况下做一个整体的输出,其实耗费心力会很大,慢慢会降低动力。保持动力还是得快速输出。
  2. 涉及内容太多的话,如果代码走读讲解,文章会很臃肿,容易偏离主题。
  3. 有些内容单独讲解效果会更好,比如包括setDisplay()等方法,涉及到部分Android系统的渲染框架,那么补充一些渲染方面组件的知识,单独再写一篇内容,那样组织起来会更好。

所以写文章也好,干活也好,今后出现这种推进效率很低的情况的时候,我得及时思考一下,是否我当前的任务目标拆解的不够细致。出现这种情况时,我得将目标更进一步拆分,一点一点攻克。

4. 参考资料

  1. 《Android MultiMedia框架完全解析 - MediaPlayer的C/S架构与Binder机制实现》
  2. 《# Android MultiMedia框架完全解析 - setDataSource继续分析》
  3. 《Android音视频开发》:何俊林
相关推荐
积跬步DEV18 分钟前
Android 获取签名 keystore 的 SHA1和MD5值
android
陈旭金-小金子2 小时前
发现 Kotlin MultiPlatform 的一点小变化
android·开发语言·kotlin
光电的一只菜鸡3 小时前
ubuntu之坑(十四)——安装FFmpeg进行本地视频推流(在海思平台上运行)
ubuntu·ffmpeg·音视频
weixin_428498493 小时前
FFmpeg 压缩视频文件
ffmpeg
二流小码农4 小时前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
江湖有缘5 小时前
使用obsutil工具在OBS上完成基本的数据存取【玩转华为云】
android·java·华为云
移动开发者1号6 小时前
Android 多 BaseUrl 动态切换策略(结合 ServiceManager 实现)
android·kotlin
移动开发者1号6 小时前
Kotlin实现文件上传进度监听:RequestBody封装详解
android·kotlin
柿蒂10 小时前
WorkManager 任务链详解:优雅处理云相册上传队列
android