美文网首页
Android多媒体框架--07:prepareAsync流程分

Android多媒体框架--07:prepareAsync流程分

作者: DarcyZhou | 来源:发表于2023-04-21 11:03 被阅读0次

"本文转载自:[yanbixing123]的Android MultiMedia框架完全解析 - prepareAsync的过程分析"

1.概述

  MediaPlayer播放音视频时,首先是setDataSource()方法设置数据源到GenericSource,然后是执行prepareAsync。java层调用的prepareAsync()方法最终还是要回归到MediaPlayerService中的Client中,整体流程如下:

01.png

前半部分的流程主要是prepareAsync()方法的层层调用,内容较为简单。下面将重要的过程拉出来分析。

2.DataSource

  根据上面的整体流程图,第4步中调用到GenericSource::prepareAsync()方法,具体代码如下:

  • GenericSource.cpp
void NuPlayer::GenericSource::prepareAsync() {
    ALOGV("prepareAsync: (looper: %d)", (mLooper != NULL));

    if (mLooper == NULL) {// 创建looper并启动AHandler循环
        mLooper = new ALooper;
        mLooper->setName("generic");
        mLooper->start();

        mLooper->registerHandler(this);
    }

    sp<AMessage> msg = new AMessage(kWhatPrepareAsync, this);//发送事件
    msg->post();
}

  这个NuPlayer::GenericSource继承自NuPlayer::Source,而NuPlayer::Source又继承自AHandler,所以在GenericSource中也可以使用AHandler-Amessage-ALooper机制,在这个函数中创建了ALooper,并且设置Looper的Handler为这个GenericSource,然后发送kWhatPrepareAsync这个AMessage来交给onMessageReceived函数来运行,最终运行到NuPlayer::GenericSource::onPrepareAsync函数中(GenericSource.cpp):

void NuPlayer::GenericSource::onPrepareAsync() {
    ALOGV("onPrepareAsync: mDataSource: %d", (mDataSource != NULL));

    // delayed data source creation
    if (mDataSource == NULL) {
        // set to false first, if the extractor
        // comes back as secure, set it to true then.
        mIsSecure = false;

        if (!mUri.empty()) {// 因为是本地文件,所以mUri不用初始化,自然为空。
            // 略掉网络媒体源创建DataSource相关代码。
        } else {// 处理本地媒体文件源
            // media.stagefright.extractremote属性一般不会设置
            if (property_get_bool("media.stagefright.extractremote", true) &&
                    !FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
                // 通过Binder机制,获取"media.extractor"服务的远程代理
                sp<IBinder> binder =
                        defaultServiceManager()->getService(String16("media.extractor"));
                if (binder != nullptr) {
                    ALOGD("FileSource remote");
                    // 强转为IMediaExtractorService对象指针
                    sp<IMediaExtractorService> mediaExService(
                            interface_cast<IMediaExtractorService>(binder));
                    // 调用服务的代理对象接口,获取IDataSource对象指针
                    sp<IDataSource> source =
                            mediaExService->makeIDataSource(mFd, mOffset, mLength);
                    ALOGV("IDataSource(FileSource): %p %d %lld %lld",
                            source.get(), mFd, (long long)mOffset, (long long)mLength);
                    if (source.get() != nullptr) {
                        // 通过获取IDataSource对象指针初始化mDataSource
                        mDataSource = DataSource::CreateFromIDataSource(source);
                        if (mDataSource != nullptr) {
                            close(mFd);
                            mFd = -1;
                        }
                    } else {
                        ALOGW("extractor service cannot make data source");
                    }
                } else {
                    ALOGW("extractor service not running");
                }
            }
            // 如果没有从extractor服务中成功获取DataSource就自己创建
            if (mDataSource == nullptr) {
                ALOGD("FileSource local");
                mDataSource = new FileSource(mFd, mOffset, mLength);
            }
            // TODO: close should always be done on mFd, see the lines following
            // DataSource::CreateFromIDataSource above,
            // and the FileSource constructor should dup the mFd argument as needed.
            mFd = -1;
        }
        ...
    }

    // init extractor from data source
    status_t err = initFromDataSource();
    ...
    finishPrepareAsync();

    ALOGV("onPrepareAsync: Done");
}

  Android底层框架为了解封装的通用性,直接提供了一个解封装相关的服务:MediaExtractorService,服务名称为:“media.extractor”。NuPlayer作为众多播放器的一种,也是可以直接使用该服务的。在这里就通过该服务,创建了一个DataSource对象,并为其初始化。主要的初始化方式有两个:

(1)从MediaExtractorService服务中获取。(最终还是new FileSource)

(2)如果第一步未能初始化成功,直接自己创建一个new FileSource。

  • getService(String16("media.extractor")):Binder远端获取指定服务;

  • mediaExService->makeIDataSource:调用服务接口,创建IDataSource对象;

  • DataSource::CreateFromIDataSource:调用CreateFromIDataSource通过前面创建的IDataSource初始化mDataSource。

  接下来再看一下第8步,调用CreateFromIDataSource()方法:

sp<DataSource> DataSource::CreateFromIDataSource(const sp<IDataSource> &source) {
    return new TinyCacheSource(new CallbackDataSource(source));
}

  将server端的FileSource对象,通过IDataSource接口传递到client端后,依次通过CallbackDataSource、TinyCacheSource对象包起来,已达到后续可以通过IDataSource对象调用远端FileSource对象的目的。

02
  • DataSource:该类规定了媒体源文件基本的操作接口。

  • IDataSource:它是实现远程调用stagefright DataSource的Binder接口。Android媒体相关的各种服务中,创建的DataSource对象,都通过这个client的远程接口句柄来调用。

  • CallbackDataSource:实现了DataSource接口(实现关系),但它的私有字段mIDataSource中,保留了IDataSource(服务端DataSource)的引用(组合关系),让Client端程序可以回调到server端的DataSource对象,从而具备了”回调“功能。

  • TinyCacheSource:该类实现了DataSource接口(实现关系),在私有字段mSource中可以持有DataSource的引用,这个引用通常是用来存放CallbackDataSource对象的,所以和CallbackDataSource形成了组合关系。另外,该类中还有一个用于缓存的数组mCache[kCacheSize],对于小于kCacheSize的读取,它将提前读取并缓存在mCache中,这不仅极大减少了Client端到Server端的数据读取操作,对提高数据类型嗅探和元数据(metadata)的提取也有较高效率。

总结:

  • prepareAsync()方法是异步执行的,整个的prepare动作是在子线程执行的onPrepareAsync函数中。

  • onPrepareAsync函数主要的作用就是初始化mDataSource字段。共有两种方式,首先尝试通过"media.extractor"服务获取server端DataSource,失败后尝试直接自己new FileSource。

  • 远端服务实例化mDataSource能否成功,主要看该服务在系统中是否启用(一般来说都是正常运行的)。

  • 如果无法通过"media.extractor"初始化mDataSource,就直接自己创建(new FileSource)。

  • 不管通过server端还是自己new的方式,mDataSource最终关联的对象都是FileSource的实例。

3.initFromDataSource

  上接NuPlayer::GenericSource::onPrepareAsync()方法中在创建DataSource对象后,会调用initFromDataSource接口从数据源初始化提取器。接下来的代码流程如下:

03.png

(1)首先,看一下第2步的initFromDataSource()方法:

status_t NuPlayer::GenericSource::initFromDataSource() {
    sp<IMediaExtractor> extractor;
    CHECK(mDataSource != NULL);
    // 创建提取器
    extractor = MediaExtractor::Create(mDataSource, NULL,
                mIsStreaming ? 0 : AVNuUtils::get()->getFlags());
    ...
    mFileMeta = extractor->getMetaData(); // 获取元数据
    ...
    size_t numtracks = extractor->countTracks(); // 获取媒体源中的轨道数量,通常为三个,音频、视频、字幕各一个
    ...
    for (size_t i = 0; i < numtracks; ++i) {// 遍历轨道,将音视频轨道信息的mime添加到mMimes中
        sp<IMediaSource> track = extractor->getTrack(i);// 获取各轨道
        ...
        sp<MetaData> meta = extractor->getTrackMetaData(i);// 获取各轨道的元数据
        ...
        // Do the string compare immediately with "mime",
        // we can't assume "mime" would stay valid after another
        // extractor operation, some extractors might modify meta
        // during getTrack() and make it invalid.
        if (!strncasecmp(mime, "audio/", 6)) {// 音频轨道
            if (mAudioTrack.mSource == NULL) {// 初始化各种音频轨道信息
                mAudioTrack.mIndex = i;
                mAudioTrack.mSource = track;
                mAudioTrack.mPackets =
                    new AnotherPacketSource(mAudioTrack.mSource->getFormat());

                if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
                    mAudioIsVorbis = true;
                } else {
                    mAudioIsVorbis = false;
                }
                if (AVNuUtils::get()->isByteStreamModeEnabled(meta)) {
                    mIsByteMode = true;
                }

                mMimes.add(String8(mime));// 将音频轨道mime信息,添加到mMimes中
            }
        } else if (!strncasecmp(mime, "video/", 6)) {// 视频轨道
            if (!strncasecmp(mime, MEDIA_MIMETYPE_VIDEO_NOT_SUPPORT, 17)) {
                ALOGE("video codec not support");
                return UNKNOWN_ERROR;
            }
            if (mVideoTrack.mSource == NULL) {// 初始化各种视频轨道信息
                mVideoTrack.mIndex = i;
                mVideoTrack.mSource = track;
                mVideoTrack.mPackets =
                    new AnotherPacketSource(mVideoTrack.mSource->getFormat());

                // video always at the beginning
                mMimes.insertAt(String8(mime), 0);// 将视频轨道mime信息,添加到mMimes队首
            }
        }

        mSources.push(track); // 将各轨道信息统一保存在保存在mSources中
        int64_t durationUs;
        if (meta->findInt64(kKeyDuration, &durationUs)) {// 获取媒体播放时长
            // 将个轨道中最大的播放时长作为媒体文件的播放时长
            if (durationUs > mDurationUs) {
                mDurationUs = durationUs;
            }
        }

        int32_t bitrate;
        // 通比特率为各轨道比特率之和
        if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
            totalBitrate += bitrate;
        } else {
            totalBitrate = -1;
        }
    }

    ALOGV("initFromDataSource mSources.size(): %zu  mIsSecure: %d  mime[0]: %s", mSources.size(),
            mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));

    if (mSources.size() == 0) {
        ALOGE("b/23705695");
        return UNKNOWN_ERROR;
    }

    // Modular DRM: The return value doesn't affect source initialization.
    (void)checkDrmInfo();
    // 初始化比特率
    mBitrate = totalBitrate;

    return OK;
}

这里首先会根据mDataSource类型创建对应的Extractor(它通过sniff函数检测出媒体类型,然后创建出对应Extractor),然后通过Extractor获取数据源的元数据和tracks数量等重要信息。然后再通过一个for循环来遍历这些tracks,将从Extractor中解析出来的metadata等等数据,保存在GenericSource结构体中的mVideoTrack和mAudioTrack中。

(2)在第6步中会通过CreateFromService()方法创建Extractor:

sp<MediaExtractor> MediaExtractor::CreateFromService(
        const sp<DataSource> &source, const char *mime,
        const uint32_t flags) {

    ALOGV("MediaExtractor::CreateFromService %s flags %d", mime, flags);
    RegisterDefaultSniffers();//(1)注册嗅探器
    ...
    if (mime == NULL) {
        float confidence;
        if (!sniff(source, &tmp, &confidence, &meta)) {//(2)嗅探源文件
            ALOGW("FAILED to autodetect media content.");

            return NULL;
        }

        mime = tmp.string();
        ALOGV("Autodetected media content as '%s' with confidence %.2f",
             mime, confidence);
    }
    ...
    MediaExtractor *ret = NULL;
    if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
            || !strcasecmp(mime, "audio/mp4")) {
        ret = new MPEG4Extractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
        ret = new MP3Extractor(source, meta);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
            || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
        ret = new AMRExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
        ret = new FLACExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
        ret = new WAVExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
        ret = new OggExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
        ret = new MatroskaExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
        ret = new MPEG2TSExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
        ret = new AACExtractor(source, meta);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
        ret = new MPEG2PSExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {
        ret = new MidiExtractor(source);
    }
    ...
    return ret;
}

  在RegisterDefaultSniffers先将不同格式的嗅探器进行注册,然后再使用sniff()方法对媒体输入源进行文件头的读取,根据文件头内容嗅探出需要什么样的解封装组件,也就是不同的MediaExtractor实现。MediaExtractor有大量的实现,分别针对MP3、AAC、OGG、WAV、MPEG4等格式输入的解封装操作。如下常见的一些实现:

04

  嗅探器的实现原理基本上都是读取媒体源文件的头信息,不同格式都会有自己的特征,嗅探器就是根据这些特征,来判断是否是需要找的类型。

bool MediaExtractor::sniff(
        const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta) {
    *mimeType = "";
    *confidence = 0.0f;
    meta->clear();

    {
        Mutex::Autolock autoLock(gSnifferMutex);
        if (!gSniffersRegistered) {
            return false;
        }
    }

    for (List<SnifferFunc>::iterator it = gSniffers.begin();
         it != gSniffers.end(); ++it) {
        String8 newMimeType;
        float newConfidence;
        sp<AMessage> newMeta;
        if ((*it)(source, &newMimeType, &newConfidence, &newMeta)) {
            if (newConfidence > *confidence) {
                *mimeType = newMimeType;
                *confidence = newConfidence;
                *meta = newMeta;
            }
        }
    }

    return *confidence > 0.0;
}

其中:

if ((*it)(source, &newMimeType, &newConfidence, &newMeta)) {

会去遍历各提取器的嗅探函数,目的就是判断源文件(码流)类型是否和当前格式匹配。

  • source:这是个DataSource类型的指针,该类型通过层层包裹包含了一系列读取媒体源文件的功能。嗅探函数通过该指针通源文件中读取头信息,来判断源文件的类型。

  • newMimeType:String8类型的指针,一旦嗅探函数通过头信息探测出源文件属于当前类型,该变量会通过指针赋值。这些类型定义在MediaDefs.cpp中如:

const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4";
const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/x-wav";
const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS = "video/mp2ts";
const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS = "video/mp2p";

再返回到GenericSource::initFromDataSource()方法中:

mFileMeta = extractor->getMetaData();

对于媒体文件而言,元数据一般有:音频采样率、视频帧率、视频尺寸、比特率、编解码、播放时长等基本信息,此外也可能含有其它杂七杂八的信息:名称、版权、专辑、时间、艺术家等。

4.finishPrepareAsync

  onPrepareAsync()执行完数据初始化(initFromDataSource)后,最后通过finishPrepareAsync接口结束数据的准备工作。其代码流程如下:

05

(1)首先,看到finishPrepareAsync()方法

void NuPlayer::GenericSource::finishPrepareAsync() {
    ALOGV("finishPrepareAsync");

    status_t err = startSources();
    if (err != OK) {
        ALOGE("Failed to init start data source!");
        notifyPreparedAndCleanup(err);
        return;
    }

    if (mIsStreaming) {
        mCachedSource->resumeFetchingIfNecessary();
        mPreparing = true;
        schedulePollBuffering();
    } else {
        notifyPrepared();
    }

    if (mAudioTrack.mSource != NULL) {
        postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
    }

    if (mVideoTrack.mSource != NULL) {
        postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
    }
}

(2)先看第一段代码:startSources()

status_t NuPlayer::GenericSource::startSources() {
    // 在我们开始缓冲之前,立即启动所选的A/V track。
    // 如果我们将它延迟到start(),那么在准备期间缓冲的所有数据都将被浪费。
    // (并不是在start()开始执行后,才开始读取数据)
    ...
    if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) {
        ALOGE("failed to start audio track!");
        return UNKNOWN_ERROR;
    }

    if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) {
        ALOGE("failed to start video track!");
        return UNKNOWN_ERROR;
    }

    return OK;
}

(3)再看到notifyPrepared()

跳转到NuPlayer::Source::notifyPrepared中去执行了:

void NuPlayer::Source::notifyPrepared(status_t err) {
    ALOGV("Source::notifyPrepared %d", err);
    sp<AMessage> notify = dupNotify();
    notify->setInt32("what", kWhatPrepared);
    notify->setInt32("err", err);
    notify->post();
}

发送消息到NuPlayer::onSourceNotify中去执行,最终是找到NuPlayerDriver:

driver->notifyPrepareCompleted(err);

对应notifyPrepareCompleted()接口的定义:

void NuPlayerDriver::notifyPrepareCompleted(status_t err) {
    ALOGV("notifyPrepareCompleted %d", err);

    Mutex::Autolock autoLock(mLock);

    if (mState != STATE_PREPARING) {
        // We were preparing asynchronously when the client called
        // reset(), we sent a premature "prepared" notification and
        // then initiated the reset. This notification is stale.
        CHECK(mState == STATE_RESET_IN_PROGRESS || mState == STATE_IDLE);
        return;
    }

    CHECK_EQ(mState, STATE_PREPARING);

    mAsyncResult = err;

    if (err == OK) {
        // update state before notifying client, so that if client calls back into NuPlayerDriver
        // in response, NuPlayerDriver has the right state
        mState = STATE_PREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_PREPARED);
        }
    } else {
        mState = STATE_UNPREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
        }
    }

    sp<MetaData> meta = mPlayer->getFileMeta();
    int32_t loop;
    if (meta != NULL
            && meta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
        mAutoLoop = true;
    }

    mCondition.broadcast();
}

这里notifyListener_l(MEDIA_PREPARED)直接调用到NuPlayerDriver::notifyListener_l()函数,然后同样通过IMediaPlayerClient的Bp端传到Bn端,再传递到mediaplayer.cpp中。

相关文章

网友评论

      本文标题:Android多媒体框架--07:prepareAsync流程分

      本文链接:https://www.haomeiwen.com/subject/zjpbddtx.html