Firefly开源社区

打印 上一主题 下一主题

MediaCodec JAVA API 和 Stagefright

493

积分

6

威望

0

贡献

技术达人

Rank: 2

积分
493
QQ

MediaCodec JAVA API 和 Stagefright

发表于 2016-1-13 14:08:26      浏览:9350 | 回复:3        打印      只看该作者   [复制链接] 楼主
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;
                    }


回复

使用道具 举报

发表于 2016-1-13 14:43:59        只看该作者  沙发
支持原创
暴走的创客!
回复

使用道具 举报

15

积分

0

威望

0

贡献

技术小白

积分
15
发表于 2016-4-27 13:11:10        只看该作者  板凳
佩服。最近被解码搞得焦头烂额
回复

使用道具 举报

88

积分

10

威望

10

贡献

技术小白

积分
88
发表于 2016-5-12 09:33:35        只看该作者  地板
这种技术贴还是太少
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

友情链接 : 爱板网 电子发烧友论坛 云汉电子社区 粤ICP备14022046号-2
快速回复 返回顶部 返回列表