MediaCodec JAVA API 和 Stagefright

Android App 通过 MediaCodec Java API 获得的编解码器,实际上是由 StageFright 媒体框架提供。android.media.MediaCodec 调用 libmedia_jni.so 中 JNI native 函数,这些 JNI 函数再去调用 libstagefright.so 库获得 StageFright 框架中的编解码器。

## MediaCodec Java 使用 libmedia_jni.so JNI native 函数

XBMC 通过 MediaCodec API 创建解码器时,使用了 android.media.MediaCodec 包中 createByCodecName(), configure() 和 start() 方法。

其中 createByCodecName() 为 MediaCodec 类静态函数,用于创建 MediaCodec 对象:

frameworks/base/media/java/android/media/MediaCodec.java

final public class MediaCodec {
    public static MediaCodec createByCodecName(String name) {
        return new MediaCodec(
                name, false , false );
    }

    private MediaCodec(
            String name, boolean nameIsType, boolean encoder) {
        native_setup(name, nameIsType, encoder);
    }

}

在 MediaCodec 构造函数中,调用 JNI 函数 native_setup():

frameworks/base/media/jni/android_media_MediaCodec.cpp

static void android_media_MediaCodec_native_setup(
        JNIEnv *env, jobject thiz,
        jstring name, jboolean nameIsType, jboolean encoder) {

    const char *tmp = env->GetStringUTFChars(name, NULL);
    sp codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
    env->ReleaseStringUTFChars(name, tmp);

    setMediaCodec(env,thiz, codec);
}

JNI 中 JMediaCodec 类封装了 StageFright 框架编解码器 API。

struct JMediaCodec : public RefBase {
    JMediaCodec(
            JNIEnv *env, jobject thiz,
            const char *name, bool nameIsType, bool encoder);

    status_t initCheck() const;

    status_t configure(
            const sp &format,
            const sp &bufferProducer,
            const sp &crypto,
            int flags);

    status_t start();

    status_t queueInputBuffer(
            size_t index,
            size_t offset, size_t size, int64_t timeUs, uint32_t flags,
            AString *errorDetailMsg);
    ...
    }

JMediaCodec 对象创建时通过调用 StageFright 框架中的 MediaCodec 函数获得解码器对象。

JMediaCodec::JMediaCodec(
        JNIEnv *env, jobject thiz,
        const char *name, bool nameIsType, bool encoder)
    : mClass(NULL),
      mObject(NULL) {

    mLooper = new ALooper;

    if (nameIsType) {
        mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
    } else {
        mCodec = MediaCodec::CreateByComponentName(mLooper, name);
    }
}

MediaCodec Java API 中 configure() 和 start() 等方法也使用类似的方式,通过 JNI 访问 StageFright 框架函数:

frameworks/base/media/java/android/media/MediaCodec.java

    public void configure(
            MediaFormat format,
            Surface surface, MediaCrypto crypto, int flags) {
        Map formatMap = format.getMap();

        String[] keys = null;
        Object[] values = null;

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

            int i = 0;
            for (Map.Entry entry: formatMap.entrySet()) {
                keys[i] = entry.getKey();
                values[i] = entry.getValue();
                ++i;
            }
        }

        native_configure(keys, values, surface, crypto, flags);
    }

    public native final void start();
}

native_configure() 和 start() 使用的 JMediaCodec 对象 codec 在 native_setup() 中创建。

frameworks/base/media/jni/android_media_MediaCodec.cpp

static void android_media_MediaCodec_native_configure(
        JNIEnv *env,
        jobject thiz,
        jobjectArray keys, jobjectArray values,
        jobject jsurface,
        jobject jcrypto,
        jint flags) {

    sp codec = getMediaCodec(env, thiz);

    sp format;
    status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);

    sp bufferProducer;
    if (jsurface != NULL) {
        sp surface(android_view_Surface_getSurface(env, jsurface));
        if (surface != NULL) {
            bufferProducer = surface->getIGraphicBufferProducer();
        }
    }

    err = codec->configure(format, bufferProducer, crypto, flags);
}

status_t JMediaCodec::configure(
        const sp &format,
        const sp &bufferProducer,
        const sp &crypto,
        int flags) {
    sp client;
    if (bufferProducer != NULL) {
        mSurfaceTextureClient = new Surface(bufferProducer, true );
    } else {
        mSurfaceTextureClient.clear();
    }

    return mCodec->configure(format, mSurfaceTextureClient, crypto, flags);
}

static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
    sp codec = getMediaCodec(env, thiz);

    status_t err = codec->start();
}

status_t JMediaCodec::start() {
    return mCodec->start();
}

## StageFright MediaCodec

StageFright 框架支持 OpenMAX IL 接口的编解码器,硬件厂商只需提供 OMX 接口的组件就可以为 Android 平台上提供通用的硬件编解码支持。StageFright 支持软件和硬件实现的编解码器, 如果一种编码有多种解码器组件,用户可以配置 /etc/media_codecs.xml 进行选择。

使用 OMX 组件有两种模式:一是阻塞式的,StageFright 用 OMXCodec 类封装相应函数;另一种是非阻塞式的,用 ACodec 类封装。Android 提供的 MediaPlayer 采用的阻塞式方法,MediaCodec 则使用非阻塞式的方法。

非阻塞式方法通过异步函数调用实现,调用程序使用解码组件时,不等待处理结束直接返回,然后借助消息处理机制,解码组件处理完数据后会发送消息给调用程序,这时调用程序再处理解码后的数据。

MediaCodec 结构继承自消息处理类 AHandler:

frameworks/av/include/media/stagefright/MediaCodec.h

struct MediaCodec : public AHandler {
    static sp CreateByComponentName(
            const sp &looper, const char *name);

    status_t configure(
            const sp &format,
            const sp &nativeWindow,
            const sp &crypto,
            uint32_t flags);

    status_t start();
}

通过 MediaCodec 静态函数 CreateByComponentName() 创建 MediaCodec 对象:

frameworks/av/media/libstagefright/MediaCodec.cpp

// static
sp MediaCodec::CreateByComponentName(
        const sp &looper, const char *name) {
    sp codec = new MediaCodec(looper);
    if (codec->init(name, false , false ) != OK) {
        return NULL;
    }

    return codec;
}

MediaCodec 构造函数中会创建 ACodec 对象:

MediaCodec::MediaCodec(const sp &looper)
    : mState(UNINITIALIZED),
      mLooper(looper),
      mCodec(new ACodec),
      mReplyID(0),
      mFlags(0),
      mSoftRenderer(NULL),
      mDequeueInputTimeoutGeneration(0),
      mDequeueInputReplyID(0),
      mDequeueOutputTimeoutGeneration(0),
      mDequeueOutputReplyID(0),
      mHaveInputSurface(false) {
}

MediaCodec 对象初始化时先使用 MediaCodecList 获取编解码器组件列表,从中查询对应的解码器组件,然后告诉 ACodec 回调的通知消息 kWhatCodecNotify,发送 kWhatInit 消息到消息队列后返回:

status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) {
    bool needDedicatedLooper = false;
    if (nameIsType && !strncasecmp(name, "video/", 6)) {
        needDedicatedLooper = true;
    } else {
        AString tmp = name;
        const MediaCodecList *mcl = MediaCodecList::getInstance();
        ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
    }

    mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id()));

    sp msg = new AMessage(kWhatInit, id());
    msg->setString("name", name);
    msg->setInt32("nameIsType", nameIsType);

    sp response;
    return PostAndAwaitResponse(msg, &response);
}

在 MediaCodec 的消息处理 onMessageRececived() 函数中对 KWhatInit 消息进行处理,包括设置状态,调用 ACodec 对象 mCodec->initiateAllocateComponent() 函数。

void MediaCodec::onMessageReceived(const sp &msg) {
    switch (msg->what()) {
        case kWhatInit:
        {
            mReplyID = replyID;
            setState(INITIALIZING);

            AString name;
            CHECK(msg->findString("name", &name));

            int32_t nameIsType;
            int32_t encoder = false;
            CHECK(msg->findInt32("nameIsType", &nameIsType));
            if (nameIsType) {
                CHECK(msg->findInt32("encoder", &encoder));
            }

            sp format = new AMessage;

            if (nameIsType) {
                format->setString("mime", name.c_str());
                format->setInt32("encoder", encoder);
            } else {
                format->setString("componentName", name.c_str());
            }

            mCodec->initiateAllocateComponent(format);
            break;
        }
        ...
    }
}

ACodec 初始化并为组件分配内存动作完成后,发送 ACodec::kWhatComponentAllocated 消息通知MediaCodec,MediaCodec 的 onMessageReceived() 中再对此消息进行响应:

        case kWhatCodecNotify:
        {
            int32_t what;
            CHECK(msg->findInt32("what", &what));

            switch (what) {

                case ACodec::kWhatComponentAllocated:
                {
                    CHECK_EQ(mState, INITIALIZING);
                    setState(INITIALIZED);

                    CHECK(msg->findString("componentName", &mComponentName));

                    if (mComponentName.startsWith("OMX.google.") ||
                            mComponentName.startsWith("OMX.ffmpeg.")) {
                        mFlags |= kFlagIsSoftwareCodec;
                    } else {
                        mFlags &= ~kFlagIsSoftwareCodec;
                    }

                    if (mComponentName.endsWith(".secure")) {
                        mFlags |= kFlagIsSecure;
                    } else {
                        mFlags &= ~kFlagIsSecure;
                    }

                    (new AMessage)->postReply(mReplyID);
                    break;
                }
                ...
            }
        }

configure() 和 start() 函数也使用同样方式分别发送 kWhatConfigure 和 kWhatStart 消息到消息队列

status_t MediaCodec::configure(
        const sp &format,
        const sp &nativeWindow,
        const sp &crypto,
        uint32_t flags) {
    sp msg = new AMessage(kWhatConfigure, id());

    msg->setMessage("format", format);
    msg->setInt32("flags", flags);

    if (nativeWindow != NULL) {
        msg->setObject(
                "native-window",
                new NativeWindowWrapper(nativeWindow));
    }

    sp response;
    return PostAndAwaitResponse(msg, &response);
}

status_t MediaCodec::start() {
    sp msg = new AMessage(kWhatStart, id());

    sp response;
    return PostAndAwaitResponse(msg, &response);
}

消息处理函数中调用 mCodec->initiateConfigureComponent(format) 和 mCodec->initiateStart():

        case kWhatConfigure:
        {
            uint32_t replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));

            mCodec->initiateConfigureComponent(format);
            break;
        }

        case kWhatStart:
        {
            uint32_t replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));

            mReplyID = replyID;
            setState(STARTING);

            mCodec->initiateStart();
            break;
        }

然后再响应 ACodec 返回的消息,其中 initiateStart() 完成后没有消息通知 MediaCodec。

                case ACodec::kWhatComponentConfigured:
                {
                    CHECK_EQ(mState, CONFIGURING);
                    setState(CONFIGURED);

                    // reset input surface flag
                    mHaveInputSurface = false;

                    (new AMessage)->postReply(mReplyID);
                    break;
                }

支持原创

佩服。最近被解码搞得焦头烂额

这种技术贴还是太少