Files

674 lines
23 KiB
C++

/*
* Copyright (c) 2021 Amlogic, Inc. All rights reserved.
*
* This source code is subject to the terms and conditions defined in the
* file 'LICENSE' which is part of this source code package.
*
* Description:
*/
#define LOG_NDEBUG 1
#include <cinttypes>
#include <chrono>
#include <string.h>
#include <DebugHelper.h>
#include "VtInstance.h"
#include "VtAllocSolidColorBuffer.h"
#ifndef LINUX_COMPILE
#include <HwcConfig.h>
#endif
#define VTINST_LOGV(fmt,...) MESON_LOGV("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__)
#define VTINST_LOGD(fmt,...) MESON_LOGD("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__)
#define VTINST_LOGW(fmt,...) MESON_LOGW("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__)
#define VTINST_LOGE(fmt,...) MESON_LOGE("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__)
namespace {
constexpr int kReleaseThreadPollMs = 16;
}
VtInstance::VtInstance(int tunnelId)
: mTunnelId(tunnelId),
mVideoTunnelDev(VideoTunnelDev::getInstance()),
mSolidBufferAllocator(VtAllocSolidColorBuffer::getInstance()),
mOwnsDefaultDependencies(false),
mReleaseThreadExit(false),
mReleaseThreadCreated(false),
mReleaseThread(0) {
mConsumers.clear();
mRefreshConsumers.clear();
mBufferQueue.clear();
memset(&mCrop, 0, sizeof(vt_rect_t));
snprintf(mName, 64, "VtInstance-%d", tunnelId);
memset(&mLastData, 0, sizeof(last_data_t));
mLastData.videoStatus = VT_VIDEO_STATUS_SHOW;
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
}
// New constructor with dependency injection (for testing)
VtInstance::VtInstance(int tunnelId,
VideoTunnelDev& videoTunnelDev,
ISolidColorBufferAllocator& solidBufferAllocator)
: mTunnelId(tunnelId),
mVideoTunnelDev(videoTunnelDev),
mSolidBufferAllocator(solidBufferAllocator),
mOwnsDefaultDependencies(false) {
mConsumers.clear();
mBufferQueue.clear();
memset(&mCrop, 0, sizeof(vt_rect_t));
snprintf(mName, 64, "VtInstance-%d", tunnelId);
memset(&mLastData, 0, sizeof(last_data_t));
mLastData.videoStatus = VT_VIDEO_STATUS_SHOW;
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
}
VtInstance::~VtInstance() {
stopReleaseThread();
auto it = mConsumers.begin();
for (; it != mConsumers.end(); it++) {
if ((*it).get())
(*it)->setReleaseListener(nullptr);
}
mConsumers.clear();
mRefreshConsumers.clear();
releaseInstanceBuffers();
disconnect();
}
int32_t VtInstance::onFrameDisplayed(std::shared_ptr<VtBufferItem> & item) {
bool needStopReleaseThread = false;
std::unique_lock<std::mutex> lock(mMutex);
std::shared_ptr<VtBufferItem> ptrBufferItem;
if (mBufferQueue.empty()) {
VTINST_LOGV("buffer queue is empty (%s)", item->toString().c_str());
item->closeFd();
return -1;
}
for (auto it = mBufferQueue.begin(); it != mBufferQueue.end();) {
ptrBufferItem = *it;
if (ptrBufferItem.get() != item.get()) {
++it;
continue;
}
if (ptrBufferItem->unrefHandle()) {
it = mBufferQueue.erase(it);
needStopReleaseThread = queueReleaseBufferLocked(ptrBufferItem);
}
lock.unlock();
if (needStopReleaseThread)
stopReleaseThread();
return 0;
}
if (!item->mIsSolidBuffer)
VTINST_LOGE("cannot find (%s) in buffer queue", item->toString().c_str());
item->closeFd();
return -1;
}
int32_t VtInstance::handleRefresh() {
int32_t ret = 0;
enum vt_cmd cmd;
struct vt_cmd_data cmdData;
struct vt_meta_data meta_data;
if (mRefreshConsumers.empty())
return ret;
for (auto it = mRefreshConsumers.begin(); it != mRefreshConsumers.end(); it++) {
if (mLastData.gameMode == 1) {
memset(&cmdData, 0, sizeof(cmdData));
cmd = VT_CMD_SET_GAME_MODE;
cmdData.data = mLastData.gameMode;
ret = (*it)->onVtCmds(cmd, cmdData);
}
if (mLastData.transform != 0) {
memset(&cmdData, 0, sizeof(cmdData));
cmd = VT_CMD_SET_VIDEO_TRANSFORM;
cmdData.transform = mLastData.transform;
ret = (*it)->onVtCmds(cmd, cmdData);
}
if (vt_rect_is_valid(&mLastData.displayFrame)) {
memset(&cmdData, 0, sizeof(cmdData));
cmd = VT_CMD_SET_DISPLAY_FRAME;
cmdData.rect = mLastData.displayFrame;
ret = (*it)->onVtCmds(cmd, cmdData);
}
if (mLastData.solidBufferType != SOLID_COLOR_FOR_INVALID &&
(mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ONCE ||
mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS ||
mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR)) {
VTINST_LOGD("status:%s, solidBufferType:%s",
vtVideoStatusToString(mLastData.videoStatus),
vtVideoColorTypeToString(mLastData.solidBufferType));
struct uvm_fd_info info;
memset(&info, 0, sizeof(info));
mSolidBufferAllocator.getBufferInfo(mLastData.solidBufferType, info);
if (info.fd >= 0) {
std::vector<std::shared_ptr<VtBufferItem>> items;
info.fd = dup(info.fd);
memset(&meta_data, 0, sizeof(meta_data));
std::shared_ptr<VtBufferItem> item =
std::make_shared<VtBufferItem>(info.fd, info.timestamp, info, meta_data, true);
items.push_back(item);
ret = sendBufferToConsumer(mRefreshConsumers, items);
}
}
}
/* refresh buffers*/
if (!mBufferQueue.empty() &&
mLastData.videoStatus == VT_VIDEO_STATUS_SHOW &&
mLastData.solidBufferType == SOLID_COLOR_FOR_INVALID) {
std::vector<std::shared_ptr<VtBufferItem>> items;
std::shared_ptr<VtBufferItem> lastItem = mBufferQueue.back();
if (lastItem.get()) {
items.push_back(lastItem);
ret = sendBufferToConsumer(mRefreshConsumers, items);
}
}
mRefreshConsumers.clear();
return ret;
}
int32_t VtInstance::registerVtConsumer(
std::shared_ptr<VtConsumer>& consumer, bool refresh) {
/* only Hwc2Layer will call this api when have
* an videotunnel layer create
*/
if (!consumer || !consumer.get()) {
VTINST_LOGE("consumer is null");
return -1;
}
consumer->setReleaseListener(this);
mConsumers.push_back(consumer);
if (refresh)
mRefreshConsumers.push_back(consumer);
return 0;
}
int32_t VtInstance::unregisterVtConsumer(
std::shared_ptr<VtConsumer>& consumer) {
/* only Hwc2Layer will call this api when videotunnel layer destroy.
* and Hwc2Layer will release all buffer Fd before call this api
*/
int32_t ret = -1;
if (!consumer || !consumer.get()) {
VTINST_LOGE("consumer is null");
return ret;
}
for (auto it = mRefreshConsumers.begin(); it != mRefreshConsumers.end(); ) {
std::shared_ptr<VtConsumer> item = (*it);
if (item.get() == consumer.get()) {
it = mRefreshConsumers.erase(it);
ret = 0;
break;
} else {
it++;
}
}
for (auto it = mConsumers.begin(); it != mConsumers.end(); ) {
std::shared_ptr<VtConsumer> item = (*it);
if (item.get() == consumer.get()) {
VTINST_LOGD("remove consumer");
it = mConsumers.erase(it);
ret = 0;
break;
} else {
it++;
}
}
if (ret)
VTINST_LOGW("cannot find VT consumer %p",consumer.get());
return ret;
}
int32_t VtInstance::connect() {
int32_t ret = -1;
if (mTunnelId >= 0) {
ret = mVideoTunnelDev.connect(mTunnelId);
if (ret >= 0) {
VTINST_LOGD("connect to videotunnel succeeded");
} else {
VTINST_LOGE("connect to videotunnel filed");
}
} else {
VTINST_LOGE("tunnel id is invalid");
}
return ret;
}
void VtInstance::disconnect() {
stopReleaseThread();
{
std::lock_guard<std::mutex> lock(mMutex);
releaseDelayedBuffersLocked(true);
}
VTINST_LOGD("disconnect videotunnel");
if (mTunnelId >= 0)
mVideoTunnelDev.disconnect(mTunnelId);
mTunnelId = -1;
memset(&mLastData, 0, sizeof(last_data_t));
mLastData.videoStatus = VT_VIDEO_STATUS_SHOW;
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
}
int32_t VtInstance::sendBufferToConsumer(
std::vector<std::shared_ptr<VtConsumer>> & consumers,
std::vector<std::shared_ptr<VtBufferItem>> & items) {
int32_t ret = 0;
if (!items.empty() && !consumers.empty()) {
for (auto it = consumers.begin(); it != consumers.end(); ) {
ret = (*it)->onFrameAvailable(items);
if (ret < 0 && ret != -EAGAIN) {
VTINST_LOGE("call consumer(%p) onFrameAvailable failed", (*it).get());
it = consumers.erase(it);
ret = -EAGAIN;
} else {
it++;
if (ret != -EAGAIN)
for (auto item = items.begin(); item != items.end(); item++)
(*item)->refHandle();
}
}
}
return ret;
}
int32_t VtInstance::acquireBuffer() {
bool isNeedDisplay = true;
int32_t ret = 0;
std::vector<std::shared_ptr<VtBufferItem>> items;
if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR ||
mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS ||
mLastData.videoStatus == VT_VIDEO_STATUS_HIDE ||
mLastData.videoStatus == VT_VIDEO_STATUS_HOLD_FRAME) {
isNeedDisplay = false;
}
{
std::lock_guard<std::mutex> lock(mMutex);
while (true) {
std::shared_ptr<VtBufferItem> item = std::make_shared<VtBufferItem>();
ret = mVideoTunnelDev.acquireBuffer(mTunnelId,
item->mBufferFd,
item->mTimeStamp,
item->mMetaData);
if (ret == 0) {
if (isNeedDisplay) {
memset(&item->mInfo, 0, sizeof(item->mInfo));
item->mInfo.fd = item->mBufferFd;
UvmDev::getInstance().getVideoInfo(item->mInfo);
#ifndef LINUX_COMPILE
if (HwcConfig::enableDrmVideo()) {
item->mInfo.crop_left += mCrop.left;
item->mInfo.crop_top += mCrop.top;
item->mInfo.crop_right -= mCrop.right;
item->mInfo.crop_bottom -= mCrop.bottom;
} else {
item->mInfo.crop_left = mCrop.left;
item->mInfo.crop_top = mCrop.top;
item->mInfo.crop_right = mCrop.right;
item->mInfo.crop_bottom = mCrop.bottom;
}
#else
item->mInfo.crop_left += mCrop.left;
item->mInfo.crop_top += mCrop.top;
item->mInfo.crop_right -= mCrop.right;
item->mInfo.crop_bottom -= mCrop.bottom;
#endif
item->mInfo.buf_stride = item->mInfo.buf_width;
item->mInfo.buf_format = DRM_FORMAT_NV21;
VTINST_LOGV("acquire buffer %d timeStamp %" PRId64
", stride %d, w/h(%d %d), crop(%d %d %d %d)",
item->mBufferFd, item->mTimeStamp, item->mInfo.buf_stride, item->mInfo.buf_width,
item->mInfo.buf_height, item->mInfo.crop_left, item->mInfo.crop_top,
item->mInfo.crop_right, item->mInfo.crop_bottom);
VTINST_LOGV("hdr types: %d hdr data: smpte2086[%f %f %f %f %f %f %f %f %f %f] cta8613[%f %f]",
item->mMetaData.hdr.validTypes,
item->mMetaData.hdr.smpte2086.displayPrimaryRed.x,
item->mMetaData.hdr.smpte2086.displayPrimaryRed.y,
item->mMetaData.hdr.smpte2086.displayPrimaryGreen.x,
item->mMetaData.hdr.smpte2086.displayPrimaryGreen.y,
item->mMetaData.hdr.smpte2086.displayPrimaryBlue.x,
item->mMetaData.hdr.smpte2086.displayPrimaryBlue.y,
item->mMetaData.hdr.smpte2086.whitePoint.x,
item->mMetaData.hdr.smpte2086.whitePoint.y,
item->mMetaData.hdr.smpte2086.maxLuminance,
item->mMetaData.hdr.smpte2086.minLuminance,
item->mMetaData.hdr.cta8613.maxContentLightLevel,
item->mMetaData.hdr.cta8613.maxFrameAverageLightLevel);
VTINST_LOGV("buffer info[%d %d %d %d %d %d %d %d %d]",item->mMetaData.stride_in_pixel,
item->mMetaData.aligned_height, item->mMetaData.bufferFormat,
item->mMetaData.buffer_w, item->mMetaData.buffer_h,
item->mMetaData.crop_x, item->mMetaData.crop_y,
item->mMetaData.crop_w, item->mMetaData.crop_h);
items.push_back(item);
mBufferQueue.push_back(std::move(item));
} else {
VTINST_LOGV("acquire buffer %d timeStamp %" PRId64 " will drop for status %s",
item->mBufferFd, item->mTimeStamp, vtVideoStatusToString(mLastData.videoStatus));
mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, -1);
}
} else if (ret == -EAGAIN) {
/* no buffer available */
break;
} else {
VTINST_LOGE("acquire buffer error %d", ret);
return ret;
}
}
}
if (isNeedDisplay) {
mLastData.videoStatus = VT_VIDEO_STATUS_SHOW;
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
ret = sendBufferToConsumer(mConsumers, items);
}
return ret;
}
bool VtInstance::shouldDelayReleaseLocked() {
return DebugHelper::getInstance().discardOutFence() &&
DebugHelper::getInstance().videoOutFenceDepth() > 0;
}
bool VtInstance::isReleaseFenceReadyLocked(std::shared_ptr<VtBufferItem> & item) {
if (!item || item->mIsSolidBuffer)
return true;
return item->getReleaseFenceStatus() != DrmFence::Status::Unsignaled;
}
bool VtInstance::queueReleaseBufferLocked(std::shared_ptr<VtBufferItem> & item) {
if (!item)
return false;
if (item->mIsSolidBuffer) {
releaseBufferLocked(item);
return false;
}
if (!shouldDelayReleaseLocked()) {
releaseDelayedBuffersLocked(true);
releaseBufferLocked(item);
return mReleaseThreadCreated;
}
if (!startReleaseThreadLocked()) {
releaseBufferLocked(item);
return false;
}
mDelayedReleaseQueue.push_back(item);
if (mDelayedReleaseQueue.size() >= DebugHelper::getInstance().videoOutFenceDepth()) {
releaseDelayedBuffersLocked(false);
}
mReleaseCondition.notify_all();
return false;
}
void VtInstance::releaseDelayedBuffersLocked(bool force) {
if (mDelayedReleaseQueue.empty())
return;
if (force || !shouldDelayReleaseLocked()) {
while (!mDelayedReleaseQueue.empty()) {
std::shared_ptr<VtBufferItem> item = mDelayedReleaseQueue.front();
mDelayedReleaseQueue.pop_front();
releaseBufferLocked(item);
}
return;
}
while (!mDelayedReleaseQueue.empty()) {
std::shared_ptr<VtBufferItem> item = mDelayedReleaseQueue.front();
if (!isReleaseFenceReadyLocked(item))
break;
mDelayedReleaseQueue.pop_front();
releaseBufferLocked(item);
}
}
bool VtInstance::startReleaseThreadLocked() {
if (mReleaseThreadCreated)
return true;
mReleaseThreadExit = false;
int ret = pthread_create(&mReleaseThread, NULL, releaseThreadMain, this);
if (ret) {
VTINST_LOGE("failed to create release thread: %s", strerror(ret));
mReleaseThreadExit = false;
mReleaseThread = 0;
return false;
}
mReleaseThreadCreated = true;
pthread_setname_np(mReleaseThread, "VtReleaseThread");
return true;
}
void VtInstance::stopReleaseThread() {
pthread_t releaseThread;
{
std::unique_lock<std::mutex> lock(mMutex);
if (!mReleaseThreadCreated)
return;
mReleaseThreadExit = true;
releaseThread = mReleaseThread;
mReleaseCondition.notify_all();
}
pthread_join(releaseThread, NULL);
std::lock_guard<std::mutex> lock(mMutex);
mReleaseThreadCreated = false;
mReleaseThreadExit = false;
}
void *VtInstance::releaseThreadMain(void * data) {
set_thread_debug_info("VtReleaseThread");
VtInstance* pThis = static_cast<VtInstance*>(data);
pThis->releaseThreadLoop();
return NULL;
}
void VtInstance::releaseThreadLoop() {
while (true) {
std::unique_lock<std::mutex> lock(mMutex);
mReleaseCondition.wait_for(lock, std::chrono::milliseconds(kReleaseThreadPollMs),
[this] { return mReleaseThreadExit; });
if (mReleaseThreadExit)
return;
releaseDelayedBuffersLocked(false);
}
}
void VtInstance::releaseBufferLocked(std::shared_ptr<VtBufferItem> & item) {
if (item.get()) {
if (!item->mIsSolidBuffer) {
VTINST_LOGV("release buffer (%s)", item->toString().c_str());
mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, item->getReleaseFenceFd());
} else {
VTINST_LOGV("release solid buffer %d", item->mBufferFd);
item->closeFd();
}
}
}
void VtInstance::releaseInstanceBuffers() {
std::lock_guard<std::mutex> lock(mMutex);
releaseDelayedBuffersLocked(true);
if (mBufferQueue.empty()) {
VTINST_LOGV("buffer queue is empty");
return;
}
for (auto it = mBufferQueue.begin(); it != mBufferQueue.end(); ++it) {
std::shared_ptr<VtBufferItem> item = *it;
if (!item->mIsSolidBuffer) {
VTINST_LOGV("release buffer(%s)", item->toString().c_str());
mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, -1);
} else {
VTINST_LOGV("release solid buffer %d", item->mBufferFd);
item->closeFd();
}
}
mBufferQueue.clear();
}
void VtInstance::onVtCmds(vt_cmd_t cmd,
vt_cmd_data_t cmdData,
bool & needSendToConsumer,
struct uvm_fd_info & info) {
bool debuglog = true;
vt_video_color_t prevType = mLastData.solidBufferType;
needSendToConsumer = false;
switch (cmd) {
case VT_CMD_SET_SHOW_SOLID_COLOR:
mLastData.videoStatus = VT_VIDEO_STATUS_COLOR_ONCE;
mLastData.solidBufferType = (vt_video_color_t)cmdData.data;
break;
case VT_CMD_SET_COLOR_BLACK:
mLastData.videoStatus = (vt_video_status_t)cmdData.data;
mLastData.solidBufferType = SOLID_COLOR_FOR_BLACK;
break;
case VT_CMD_SET_COLOR_BLUE:
mLastData.videoStatus = (vt_video_status_t)cmdData.data;
mLastData.solidBufferType = SOLID_COLOR_FOR_BLUE;
break;
case VT_CMD_SET_COLOR_GREEN:
/* cannot support green buffer */
//mLastData.videoStatus = (vt_video_status_t)cmdData.data;
//mLastData.solidBufferType = SOLID_COLOR_FOR_GREEN;
mLastData.videoStatus = VT_VIDEO_STATUS_SHOW;
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
break;
case VT_CMD_SET_VIDEO_STATUS:
mLastData.videoStatus = (vt_video_status_t)cmdData.data;
if (mLastData.videoStatus == VT_VIDEO_STATUS_BLANK ||
mLastData.videoStatus == VT_VIDEO_STATUS_HIDE) {
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
needSendToConsumer = true;
}
break;
case VT_CMD_SET_SOURCE_CROP:
mCrop = cmdData.rect;
debuglog = false;
VTINST_LOGD("cmd VT_CMD_SET_SOURCE_CROP(%d %d %d %d)", mCrop.left,
mCrop.top, mCrop.right, mCrop.bottom);
break;
case VT_CMD_SET_VIDEO_TRANSFORM:
mLastData.transform = cmdData.transform;
needSendToConsumer = true;
break;
case VT_CMD_SET_GAME_MODE:
mLastData.gameMode = cmdData.data;
needSendToConsumer = true;
break;
case VT_CMD_SET_DISPLAY_FRAME:
mLastData.displayFrame = cmdData.rect;
needSendToConsumer = true;
break;
default:
debuglog = false;
needSendToConsumer = true;
break;
}
if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_DISABLE ||
mLastData.videoStatus == VT_VIDEO_STATUS_SHOW)
mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID;
if (debuglog)
VTINST_LOGD("cmd:%s, status:%s, solidBufferType:%s",
vtCmdToString(cmd), vtVideoStatusToString(mLastData.videoStatus),
vtVideoColorTypeToString(mLastData.solidBufferType));
if (prevType != mLastData.solidBufferType && mLastData.solidBufferType != SOLID_COLOR_FOR_INVALID)
mSolidBufferAllocator.getBufferInfo(mLastData.solidBufferType, info);
}
int32_t VtInstance::receiveCmds() {
bool needSendToConsumer;
int32_t ret = 0;
enum vt_cmd cmd;
struct vt_cmd_data cmdData;
struct uvm_fd_info info;
std::vector<std::shared_ptr<VtBufferItem>> items;
struct vt_meta_data meta_data;
ret = mVideoTunnelDev.receiveCmd(mTunnelId, cmd, cmdData);
if (ret < 0)
return ret;
memset(&info, 0, sizeof(uvm_fd_info));
info.fd = -1;
info.fps = -1;
onVtCmds(cmd, cmdData, needSendToConsumer, info);
if (info.fd >= 0) {
VTINST_LOGD("alloc solid buffer %d, stride %d, w/h(%d %d), crop(%d %d %d %d)",
info.fd, info.buf_stride, info.buf_width,
info.buf_height, info.crop_left, info.crop_top,
info.crop_right, info.crop_bottom);
std::lock_guard<std::mutex> lock(mMutex);
memset(&meta_data, 0, sizeof(meta_data));
std::shared_ptr<VtBufferItem> item =
std::make_shared<VtBufferItem> (info.fd, info.timestamp, info, meta_data, true);
items.push_back(item);
mBufferQueue.push_back(std::move(item));
}
if (!items.empty())
return sendBufferToConsumer(mConsumers,items);
if (needSendToConsumer) {
for (auto it = mConsumers.begin(); it != mConsumers.end(); it++) {
ret = (*it)->onVtCmds(cmd, cmdData);
}
}
return ret;
}
bool VtInstance::needDestroyThisInstance() {
return mConsumers.empty() ? true : false;
}
void VtInstance::onClearAlwaysVRRMode() {
std::lock_guard<std::mutex> lock(mMutex);
if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR) {
mLastData.videoStatus = VT_VIDEO_STATUS_COLOR_ONCE;
VTINST_LOGV("change video statu %s -> %s",
vtVideoStatusToString(VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR),
vtVideoStatusToString(mLastData.videoStatus));
}
}