问题描述
最近在使用MediaCodec做视频播放,在小屏切换节目时,小屏就会出现闪一下的现象(黑屏->透明->视频画面。
看见这一现象后,想到了一个解决方案,就是参考tif框架中LiveTv中的遮黑处理,在停止播放时,再SurfaceView上覆盖一张黑色背景的View,再等视频画面出现时,把黑色背景的View隐藏,如此闪屏现象就消失了。
但是收到的出画消息好像不太精,准修改后出画速度好像没有以前快了,所以决定寻找一下根本原因。
原因分析:
在加了大量的debug log后,终于在SurfaceUtils.cpp找到了问题关键。
也就是在MediaCodec stop时,是调用到pushBlankBuffersToNativeWindow方法导致,SurfaceView的区域会显示黑色,又因为SurfaceView在绘画之前会是透明或者半透明,所以最终就会出现黑屏->透明->视频画面的闪一下的现象。
那怎样让MediaCodec stop不调用到pushBlankBuffersToNativeWindow呢?
在ACodec.cpp中,加log分析后,在不改动framework code 的前提下,好像并不能避免。
相关code路径:
android/frameworks/av/media/libstagefright/ACodec.cpp
android/frameworks/av/media/libstagefright/SurfaceUtils.cpp
原因如下:
MediaCodec 需要出画,MediaCodec的CodeName就需要是.secure结尾,所以mCodec->mFlags就会添加 kFlagPushBlankBuffersToNativeWindowOnShutdown flags。
bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
ALOGV("onAllocateComponent");
CHECK(mCodec->mOMXNode == NULL);
mCodec->mFatalError = false;
sp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);
notify->setInt32("generation", mCodec->mNodeGeneration + 1);
sp<RefBase> obj;
CHECK(msg->findObject("codecInfo", &obj));
sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
if (info == nullptr) {
ALOGE("Unexpected nullptr for codec information");
mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
return false;
}
AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();
AString componentName;
CHECK(msg->findString("componentName", &componentName));
sp<CodecObserver> observer = new CodecObserver(notify);
sp<IOMX> omx;
sp<IOMXNode> omxNode;
status_t err = NAME_NOT_FOUND;
OMXClient client;
if (client.connect(owner.c_str()) != OK) {
mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
return false;
}
omx = client.interface();
pid_t tid = gettid();
int prevPriority = androidGetThreadPriority(tid);
androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
err = omx->allocateNode(componentName.c_str(), observer, &omxNode);
androidSetThreadPriority(tid, prevPriority);
if (err != OK) {
ALOGE("Unable to instantiate codec '%s' with err %#x.", componentName.c_str(), err);
mCodec->signalError((OMX_ERRORTYPE)err, makeNoSideEffectStatus(err));
return false;
}
mDeathNotifier = new DeathNotifier(new AMessage(kWhatOMXDied, mCodec));
auto tOmxNode = omxNode->getHalInterface<IOmxNode>();
if (tOmxNode && !tOmxNode->linkToDeath(mDeathNotifier, 0)) {
mDeathNotifier.clear();
}
++mCodec->mNodeGeneration;
mCodec->mComponentName = componentName;
mCodec->mRenderTracker.setComponentName(componentName);
mCodec->mFlags = 0;
//kFlagPushBlankBuffersToNativeWindowOnShutdown 设置了遮黑的flags
if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
mCodec->mFlags |= kFlagIsGrallocUsageProtected;
mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}
mCodec->mOMX = omx;
mCodec->mOMXNode = omxNode;
mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());
mCodec->changeState(mCodec->mLoadedState);
return true;
}
在MediaCodec stop时,又会判断mCodec->mFlags,导致call到了pushBlankBuffersToNativeWindow。
void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() {
if (mComponentNowIdle && mCodec->allYourBuffersAreBelongToUs()) {
status_t err = mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateLoaded);
if (err == OK) {
err = mCodec->freeBuffersOnPort(kPortIndexInput);
status_t err2 = mCodec->freeBuffersOnPort(kPortIndexOutput);
if (err == OK) {
err = err2;
}
}
//判断使用遮黑的flags
if ((mCodec->mFlags & kFlagPushBlankBuffersToNativeWindowOnShutdown)
&& mCodec->mNativeWindow != NULL) {
// We push enough 1x1 blank buffers to ensure that one of
// them has made it to the display. This allows the OMX
// component teardown to zero out any protected buffers
// without the risk of scanning out one of those buffers.
pushBlankBuffersToNativeWindow(mCodec->mNativeWindow.get());
}
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return;
}
mCodec->changeState(mCodec->mIdleToLoadedState);
}
}
总结
就MediaCodec会闪一下的问题,希望有大神路过时指点一二。也希望遇见同样问题的朋友,可以一起交流心得。谢谢!