• Android 13 - Media框架(12)- MediaCodec(二)


    前面一节我们学习了 MediaCodec 的创建以及配置过程,了解部分设计机制以及功能,这一节我们将继续学习其他方法。

    1、start

    start 会在两种情况下调用,一种是 configure 完成后调用 start 开始播放,另一种是 flush 完成后调用 start 恢复播放,直接看 kWhatStart 是如何处理的:

            case kWhatStart:
            {
            	// 1. 如果状态为 FLUSHED,那么直接将状态置为 STARTED
                if (mState == FLUSHED) {
                    setState(STARTED);
                    // 2. 处理 MediaCodec持有的 input buffer
                    if (mHavePendingInputBuffers) {
                        onInputBufferAvailable();
                        mHavePendingInputBuffers = false;
                    }
                    // 3. 调用 resume 恢复解码流程
                    mCodec->signalResume();
                    PostReplyWithError(msg, OK);
                    break;
                } else if (mState != CONFIGURED) {
                    PostReplyWithError(msg, INVALID_OPERATION);
                    break;
                }
    			// 如果有其他阻塞调用,需要等待阻塞调用完成
                if (mReplyID) {
                    mDeferredMessages.push_back(msg);
                    break;
                }
                sp<AReplyToken> replyID;
                CHECK(msg->senderAwaitsResponse(&replyID));
                TunnelPeekState previousState = mTunnelPeekState;
                if (previousState != TunnelPeekState::kLegacyMode) {
                    mTunnelPeekState = TunnelPeekState::kEnabledNoBuffer;
                    ALOGV("TunnelPeekState: %s -> %s",
                            asString(previousState),
                            asString(TunnelPeekState::kEnabledNoBuffer));
                }
    
                mReplyID = replyID;
                setState(STARTING);
    
                mCodec->initiateStart();
                break;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 如果是在 flush 之后调用 start,则需要将 MediaCodec 持有的 input buffer 都回传给上层(mHavePendingInputBuffers 何时为 true 后面我们碰到了再了解),接着调用 signalResume 恢复编解码流程;
    • 如果是在 configure 之后调用 start,则调用 initiateStart 开始解码流程。

    initiateStart 上方有个 TunnelPeekState,暂时不清楚是做什么用的,后续在实际使用中碰到了再回过头来记录。

    2、flush

    flush 意为冲刷,指的是清空 input buffer 和 output buffer 中的数据,以及清空 decoder 中的数据。flush 的步骤同样比较简单:

            case kWhatFlush:
            {
            	// 1、判断是否正在播放的状态
                if (!isExecuting()) {
                    PostReplyWithError(msg, INVALID_OPERATION);
                    break;
                } else if (mFlags & kFlagStickyError) {		// 2、是否处在出错的状态
                    PostReplyWithError(msg, getStickyError());
                    break;
                }
    			......
                setState(FLUSHING);
    			// 3、调用 signalFlush
                mCodec->signalFlush();
                // 4、将所有的 buffer 返回给codec
                returnBuffersToCodec();
                TunnelPeekState previousState = mTunnelPeekState;
                if (previousState != TunnelPeekState::kLegacyMode) {
                    mTunnelPeekState = TunnelPeekState::kEnabledNoBuffer;
                }
                break;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • STARTED 和 FLUSHED 状态下被认为是 decoder 正在运行:
    bool MediaCodec::isExecuting() const {
        return mState == STARTED || mState == FLUSHED;
    }
    
    • 1
    • 2
    • 3
    • kFlagStickyError:出错的状态
    • returnBuffersToCodec:将 input 和 output buffer 返回给 codec,具体如何回收我们后面看到 buffer 传递时再来学习。
                    case kWhatFlushCompleted:
                    {
    					......
                        if (mFlags & kFlagIsAsync) {
                            setState(FLUSHED);
                        } else {
                            setState(STARTED);
                            mCodec->signalResume();
                        }
    
                        postPendingRepliesAndDeferredMessages("kWhatFlushCompleted");
                        break;
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    CodecBase flush 执行完成之后的 callback 处理比较有意思,如果 MediaCodec 用的是异步模式则直接将状态置为 FLUSHED,如果是同步模式则还需要调用一次 signalResume 方法,为什么要这样处理我们会在后面的 buffer 传递学习中看看能否找到答案。

    3、stop & release

    我们都知道播放器有 pause(暂停) 和 stop(停止) 方法,但是在编解码流程中是没有pause的,stop 使用的频率似乎也不是那么高。

    阅读 MediaCodec 源码会发现,stop 和 release 共享一套处理流程,但是他们的功能是完全不一样的:

    • stop 用于重置底层的 codec 组件,这里要注意是不会释放底层组件的,MediaCodec状态会被置为 INITIALIZED,我们之前看到这个状态是调用 createByType/CreateByComponentName 完成后,CodeBase 送回 callback 后 MediaCodec 状态被设置为 INITIALIZED,也就是说此时 Codec 组件处于刚刚创建的状态,如果要继续使用需要再调用 configure 方法重新配置。
    • release 用于释放底层 codec 组件,释放完成后 MediaCodec 状态置为 UNINITIALIZED,由于该 MediaCodec 内部持有的组件被释放,所以这个 MediaCodec 对象将无法再被使用(我们无法再使用该对象创建新的组件,这里和 MediaPlayer Java api有点类似,调用 release 释放 native 对象后就无法再使用了)。
            case kWhatStop: {
                if (mReplyID) {
                    mDeferredMessages.push_back(msg);
                    break;
                }
                [[fallthrough]];
            }
            case kWhatRelease:
            {
            	// 设置目标状态,如果调用的是 stop 方法则目标状态为INITIALIZED,如果是 release 则为 UNINITIALIZED
                State targetState =
                    (msg->what() == kWhatStop) ? INITIALIZED : UNINITIALIZED;
    			// 正在 release 且目标为 UNINITIALIZED, 或者 正在 stop 目标为 INITIALIZED 则不处理当前事件
                if ((mState == RELEASING && targetState == UNINITIALIZED)
                        || (mState == STOPPING && targetState == INITIALIZED)) {
                    mDeferredMessages.push_back(msg);
                    break;
                }
    			// 获取 reply token
                sp<AReplyToken> replyID;
                CHECK(msg->senderAwaitsResponse(&replyID));
    			
    			......
                // already stopped/released
                if (mState == UNINITIALIZED && mReleasedByResourceManager) {
                    sp<AMessage> response = new AMessage;
                    response->setInt32("err", OK);
                    response->postReply(replyID);
                    break;
                }
    			// MediaCodec 被资源管理器回收,这部分可先不看
                int32_t reclaimed = 0;
                msg->findInt32("reclaimed", &reclaimed);
                if (reclaimed) {
                    if (!mReleasedByResourceManager) {
                        // notify the async client
                        if (mFlags & kFlagIsAsync) {
                            onError(DEAD_OBJECT, ACTION_CODE_FATAL);
                        }
                        mReleasedByResourceManager = true;
                    }
    
                    int32_t force = 0;
                    msg->findInt32("force", &force);
                    if (!force && hasPendingBuffer()) {
                        ALOGW("Can't reclaim codec right now due to pending buffers.");
    
                        // return WOULD_BLOCK to ask resource manager to retry later.
                        sp<AMessage> response = new AMessage;
                        response->setInt32("err", WOULD_BLOCK);
                        response->postReply(replyID);
    
                        break;
                    }
                }
    			// 如果stop方法没有被调用,直接调用release方法,isReleasingAllocatedComponent 置为true
    			// 如果调用的是 stop,或者当前状态已经处在 UNINITIALIZED 状态,则置为 false
    			// 异常处理
                bool isReleasingAllocatedComponent =
                        (mFlags & kFlagIsComponentAllocated) && targetState == UNINITIALIZED;
                if (!isReleasingAllocatedComponent // See 1
                        && mState != INITIALIZED
                        && mState != CONFIGURED && !isExecuting()) {
                    // 1) Permit release to shut down the component if allocated.
                    //
                    // 2) We may be in "UNINITIALIZED" state already and
                    // also shutdown the encoder/decoder without the
                    // client being aware of this if media server died while
                    // we were being stopped. The client would assume that
                    // after stop() returned, it would be safe to call release()
                    // and it should be in this case, no harm to allow a release()
                    // if we're already uninitialized.
                    sp<AMessage> response = new AMessage;
                    // TODO: we shouldn't throw an exception for stop/release. Change this to wait until
                    // the previous stop/release completes and then reply with OK.
                    status_t err = mState == targetState ? OK : INVALID_OPERATION;
                    response->setInt32("err", err);
                    if (err == OK && targetState == UNINITIALIZED) {
                        mComponentName.clear();
                    }
                    response->postReply(replyID);
                    break;
                }
    
    			// 如果在 执行 flush configure start 期间收到执行 release 方法,那么立即结束这些方法的调用
                // If we're flushing, configuring or starting  but
                // received a release request, post the reply for the pending call
                // first, and consider it done. The reply token will be replaced
                // after this, and we'll no longer be able to reply.
                if (mState == FLUSHING || mState == CONFIGURING || mState == STARTING) {
                    // mReply is always set if in these states.
                    postPendingRepliesAndDeferredMessages(
                            std::string("kWhatRelease:") + stateString(mState));
                }
                // 如果执行 stop 期间执行了 release,那么理解结束 stop 调用
                // If we're stopping but received a release request, post the reply
                // for the pending call if necessary. Note that the reply may have been
                // already posted due to an error.
                if (mState == STOPPING && mReplyID) {
                    postPendingRepliesAndDeferredMessages("kWhatRelease:STOPPING");
                }
    
                if (mReplyID) {
                    // State transition replies are handled above, so this reply
                    // would not be related to state transition. As we are
                    // shutting down the component, just fail the operation.
                    postPendingRepliesAndDeferredMessages("kWhatRelease:reply", UNKNOWN_ERROR);
                }
                mReplyID = replyID;
                setState(msg->what() == kWhatStop ? STOPPING : RELEASING);
    
                mCodec->initiateShutdown(
                        msg->what() == kWhatStop /* keepComponentAllocated */);
    
                returnBuffersToCodec(reclaimed);
    
                if (mSoftRenderer != NULL && (mFlags & kFlagPushBlankBuffersOnShutdown)) {
                    pushBlankBuffersToNativeWindow(mSurface.get());
                }
                break;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    处理 stop 和 release 方法的代码比较长,主要是因为 release 涉及到了组件的释放,考虑了比较多的情况(异常),而且为了性能也不要求 release 方法阻塞调用。

    我们之前说 MediaCodec 的接口都是阻塞调用(例如上面的start、flush),这里的 stop 调用也会遵循该规则,这些函数调用完成之前,无论外部有几个线程调用 MediaCodec 接口,这些调用都会被延迟处理,从如下代码就可以看到这个特点:

                if (mReplyID) {
                    mDeferredMessages.push_back(msg);
                    break;
                }
    
    • 1
    • 2
    • 3
    • 4

    但是release就不会遵循以上规则了,当我们想释放资源时,肯定是想越快释放越好,如果处在configuring/flushing阶段,我们肯定也不想等太久,所以如果有某些线程在这期间调用了 release,MediaCodec将会立即响应并执行(release的处理并没有以上代码检查)。

    这两个方法就了解到这边,主要就是调用的 CodecBase 的 initiateShutdown 方法。

    4、reset

    故名思意,reset 指的就是将 MediaCodec 重置的意思,从代码上来看是销毁CodecBase对象以及 codec component,然后重新调用 init 创建一个 codec component,这里和 stop 是不同的(stop只是将codec component重置)。

    status_t MediaCodec::reset() {
        /* When external-facing MediaCodec object is created,
           it is already initialized.  Thus, reset is essentially
           release() followed by init(), plus clearing the state */
    
        status_t err = release();
    
        // unregister handlers
        if (mCodec != NULL) {
            if (mCodecLooper != NULL) {
                mCodecLooper->unregisterHandler(mCodec->id());
            } else {
                mLooper->unregisterHandler(mCodec->id());
            }
            mCodec = NULL;
        }
        mLooper->unregisterHandler(id());
    
        mFlags = 0;    // clear all flags
        mStickyError = OK;
    
        // reset state not reset by setState(UNINITIALIZED)
        mDequeueInputReplyID = 0;
        mDequeueOutputReplyID = 0;
        mDequeueInputTimeoutGeneration = 0;
        mDequeueOutputTimeoutGeneration = 0;
        mHaveInputSurface = false;
    
        if (err == OK) {
            err = init(mInitName);
        }
        return err;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    MediaCodec 的学习到这就先告一段落,还有一大块和 Buffer 传递相关的内容等我们后面 OMX 以及 CodecBase 学习完成再补上。

  • 相关阅读:
    探索未来科技:深入了解设备交互 API 如何改变我们的生活
    程序员工作压力大,年轻人放弃互联网了?
    解决SpringCloud的Gateway网关无法访问服务的静态资源
    商超智能守护:AI监控技术在零售安全中的应用
    行列式展开:行列式等于它的任一行(列)的元素与其对应的代数余子式的乘积之和
    复旦、人大等发布大五人格+MBTI测试 角色扮演AI特质还原率达82.8%
    哈希切割+布隆过滤器
    101. 垃圾回收与内存泄漏?
    浮点数在计算机中的二进制表示
    丁鹿学堂:前端开发基础知识之像素详解
  • 原文地址:https://blog.csdn.net/qq_41828351/article/details/133895866