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;
        }
  • 如果是在 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;
        }
  • STARTED 和 FLUSHED 状态下被认为是 decoder 正在运行:
bool MediaCodec::isExecuting() const {
    return mState == STARTED || mState == FLUSHED;
}
  • kFlagStickyError:出错的状态
  • returnBuffersToCodec:将 input 和 output buffer 返回给 codec,具体如何回收我们后面看到 buffer 传递时再来学习。
                case kWhatFlushCompleted:
                {
					......
                    if (mFlags & kFlagIsAsync) {
                        setState(FLUSHED);
                    } else {
                        setState(STARTED);
                        mCodec->signalResume();
                    }

                    postPendingRepliesAndDeferredMessages("kWhatFlushCompleted");
                    break;
                }

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;
        }

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

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

            if (mReplyID) {
                mDeferredMessages.push_back(msg);
                break;
            }

但是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;
}

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

关注公众号《青山渺渺》阅读完整内容; 如有问题可在公众号后台私信,也可进入音视频开发技术分享群一起讨论!

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值