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 学习完成再补上。