1209 lines
26 KiB
C++
1209 lines
26 KiB
C++
|
|
||
|
|
||
|
#define LOG_NDEBUG 0
|
||
|
#define LOG_TAG "EISChannel"
|
||
|
|
||
|
#include <utils/plat_log.h>
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <errno.h>
|
||
|
#include <unistd.h>
|
||
|
#include <pthread.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <memory.h>
|
||
|
|
||
|
#include <ion_memmanager.h>
|
||
|
#include <memoryAdapter.h>
|
||
|
#include <utils/Thread.h>
|
||
|
#include <utils/Mutex.h>
|
||
|
#include <utils/Condition.h>
|
||
|
#include <MediaStructConvert.h>
|
||
|
|
||
|
#include <SystemBase.h>
|
||
|
#include <mpi_eis.h>
|
||
|
#include <mpi_videoformat_conversion.h>
|
||
|
|
||
|
#include "EISChannel.h"
|
||
|
|
||
|
#include <ConfigOption.h>
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
namespace EyeseeLinux {
|
||
|
|
||
|
|
||
|
// DoCaptureProcess start -->
|
||
|
EISChannel::DoCaptureProcess::DoCaptureProcess(EISChannel *pEisChn): mpEisChn(pEisChn)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoCaptureProcess::SendCommand_TakePicture()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypeCapture_TakePicture;
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoCaptureProcess::SendCommand_CancelContinuousPicture()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypeCapture_CancelContinuousPicture;
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
// DoCaptureProcess end <--
|
||
|
|
||
|
// DoPreviewThread start -->
|
||
|
EISChannel::DoPreviewThread::DoPreviewThread(EISChannel *pEisChn)
|
||
|
: mpEisChn(pEisChn)
|
||
|
, mbWaitReleaseAllFrames(false)
|
||
|
, mbWaitPreviewFrame(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPreviewThread::startThread()
|
||
|
{
|
||
|
status_t ret = run("EISChnPreviewTh");
|
||
|
|
||
|
if(ret != NO_ERROR)
|
||
|
{
|
||
|
aloge("fatal error! run thread fail!");
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void EISChannel::DoPreviewThread::stopThread()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypePreview_Exit;
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
join();
|
||
|
|
||
|
mMsgQueue.flushMessage();
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPreviewThread::notifyNewFrameCome()
|
||
|
{
|
||
|
AutoMutex lock(mWaitLock);
|
||
|
|
||
|
if(mbWaitPreviewFrame)
|
||
|
{
|
||
|
mbWaitPreviewFrame = false;
|
||
|
|
||
|
EyeseeMessage msg;
|
||
|
msg.mMsgType = MsgTypePreview_InputFrameAvailable;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
}
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPreviewThread::releaseAllFrames()
|
||
|
{
|
||
|
AutoMutex lock(mWaitLock);
|
||
|
|
||
|
mbWaitReleaseAllFrames = true;
|
||
|
|
||
|
EyeseeMessage msg;
|
||
|
msg.mMsgType = MsgTypePreview_releaseAllFrames;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
while(mbWaitReleaseAllFrames)
|
||
|
{
|
||
|
mCondReleaseAllFramesFinished.wait(mWaitLock);
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
bool EISChannel::DoPreviewThread::threadLoop()
|
||
|
{
|
||
|
if(!exitPending())
|
||
|
{
|
||
|
return mpEisChn->previewThread(); // to show and encode
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
//DoPreviewThread end <--
|
||
|
|
||
|
//DoPictureThread start -->
|
||
|
EISChannel::DoPictureThread::DoPictureThread(EISChannel *pEisChn)
|
||
|
: mpEisChn(pEisChn)
|
||
|
, mbWaitReleaseAllFrames(false)
|
||
|
, mbWaitPictureFrame(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPictureThread::startThread()
|
||
|
{
|
||
|
status_t ret = run("EISChnPictureTh");
|
||
|
|
||
|
if(ret != NO_ERROR)
|
||
|
{
|
||
|
aloge("fatal error! run thread fail!");
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void EISChannel::DoPictureThread::stopThread()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypePicture_Exit;
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
join();
|
||
|
|
||
|
mMsgQueue.flushMessage();
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPictureThread::notifyNewFrameCome()
|
||
|
{
|
||
|
AutoMutex lock(mWaitLock);
|
||
|
|
||
|
if(mbWaitPictureFrame)
|
||
|
{
|
||
|
mbWaitPictureFrame = false;
|
||
|
|
||
|
EyeseeMessage msg;
|
||
|
msg.mMsgType = MsgTypePicture_InputFrameAvailable;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
}
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPictureThread::releaseAllFrames()
|
||
|
{
|
||
|
AutoMutex lock(mWaitLock);
|
||
|
|
||
|
mbWaitReleaseAllFrames = true;
|
||
|
|
||
|
EyeseeMessage msg;
|
||
|
msg.mMsgType = MsgTypePicture_releaseAllFrames;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
while(mbWaitReleaseAllFrames)
|
||
|
{
|
||
|
mCondReleaseAllFramesFinished.wait(mWaitLock);
|
||
|
}
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoPictureThread::notifyPictureEnd()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypePicture_SendPictureEnd;
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
bool EISChannel::DoPictureThread::threadLoop()
|
||
|
{
|
||
|
if(!exitPending())
|
||
|
{
|
||
|
return mpEisChn->pictureThread();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
// DoPictureThread end <--
|
||
|
|
||
|
// DoCommandThread start -->
|
||
|
EISChannel::DoCommandThread::DoCommandThread(EISChannel *pEisChn): mpEisChn(pEisChn)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoCommandThread::startThread()
|
||
|
{
|
||
|
status_t ret = run("EISChnCommand");
|
||
|
|
||
|
if(ret != NO_ERROR)
|
||
|
{
|
||
|
aloge("fatal error! run thread fail!");
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void EISChannel::DoCommandThread::stopThread()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
msg.mMsgType = MsgTypeCommand_Exit;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
join();
|
||
|
mMsgQueue.flushMessage();
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoCommandThread::SendCommand_TakePicture(unsigned int msgType)
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypeCommand_TakePicture;
|
||
|
msg.mPara0 = msgType;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::DoCommandThread::SendCommand_CancelContinuousPicture()
|
||
|
{
|
||
|
EyeseeMessage msg;
|
||
|
|
||
|
msg.mMsgType = MsgTypeCommand_CancelContinuousPicture;
|
||
|
|
||
|
mMsgQueue.queueMessage(&msg);
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
bool EISChannel::DoCommandThread::threadLoop()
|
||
|
{
|
||
|
if(!exitPending())
|
||
|
{
|
||
|
return mpEisChn->commandThread();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
//DoCommandThread end <--
|
||
|
|
||
|
|
||
|
EISChannel::EISChannel(EIS_GRP nGroupId, EIS_ATTR_S *pChnAttr) : mpPrevThread(NULL)
|
||
|
, mpPicThread(NULL)
|
||
|
, mpCallbackNotifier(NULL)
|
||
|
, mpPreviewWindow(NULL)
|
||
|
, mpPreviewQueue(NULL)
|
||
|
, mpPictureQueue(NULL)
|
||
|
, mNewZoom(0)
|
||
|
, mLastZoom(-1)
|
||
|
, mMaxZoom(0xffffffff)
|
||
|
//, mpMemOps(NULL)
|
||
|
, mChannelState(EIS_CHN_STATE_CONSTRUCTED)
|
||
|
, mTakePictureMode(TAKE_PICTURE_MODE_NULL)
|
||
|
, mbProcessTakePictureStart(false)
|
||
|
, mContinuousPictureCnt(0)
|
||
|
, mContinuousPictureMax(0)
|
||
|
, mContinuousPictureStartTime(0)
|
||
|
, mContinuousPictureLast(0)
|
||
|
, mContinuousPictureInterval(0)
|
||
|
, mGroupId(nGroupId)
|
||
|
, mFrameWidth(0)
|
||
|
, mFrameHeight(0)
|
||
|
, mFrameCounter(0)
|
||
|
{
|
||
|
memset(&mRectCrop, 0, sizeof(mRectCrop));
|
||
|
memset(&mPicFrmBuffer, 0, sizeof(mPicFrmBuffer));
|
||
|
memcpy(&mAttr, pChnAttr, sizeof(EIS_ATTR_S));
|
||
|
//mColorSpace = V4L2_COLORSPACE_JPEG;
|
||
|
|
||
|
ERRORTYPE ret;
|
||
|
mChannelId = 0;
|
||
|
#if 0
|
||
|
ret = AW_MPI_EIS_CreateChn(mChannelId,&mAttr);
|
||
|
if(SUCCESS == ret)
|
||
|
{
|
||
|
alogd("create EIS scaler channel[%d] success!", mChannelId);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mChannelId = MM_INVALID_CHN;
|
||
|
aloge("fatal error! create EIS scaler channel fail!");
|
||
|
}
|
||
|
#endif /* 0 */
|
||
|
|
||
|
AW_MPI_EIS_EnableChn(mGroupId);
|
||
|
|
||
|
//AW_MPI_EIS_SetPortAttr(mGroupId, mChannelId, &mAttr);
|
||
|
if (NO_ERROR != GetChnOutFrameSize(mChannelId, &mAttr, mFrameWidth, mFrameHeight))
|
||
|
{
|
||
|
aloge("get eis chn out frame size error!");
|
||
|
}
|
||
|
|
||
|
mpCallbackNotifier = new CallbackNotifier(mChannelId, this);
|
||
|
|
||
|
mbPreviewEnable = true;
|
||
|
mpPreviewWindow = new PreviewWindow(this);
|
||
|
|
||
|
mpPreviewQueue = new OSAL_QUEUE;
|
||
|
OSAL_QueueCreate(mpPreviewQueue, EIS_MAX_BUFFER_NUM);
|
||
|
|
||
|
mpPictureQueue = new OSAL_QUEUE;
|
||
|
OSAL_QueueCreate(mpPictureQueue, EIS_MAX_BUFFER_NUM);
|
||
|
|
||
|
mpCapProcess = new DoCaptureProcess(this);
|
||
|
mpPrevThread = new DoPreviewThread(this);
|
||
|
mpPicThread = new DoPictureThread(this);
|
||
|
mpCommandThread = new DoCommandThread(this);
|
||
|
//mpCommandThread->startThread();
|
||
|
|
||
|
mTakePicMsgType = 0;
|
||
|
mbTakePictureStart = false;
|
||
|
mbProcessTakePictureStart = false;
|
||
|
mParameters.setPictureMode(TAKE_PICTURE_MODE_FAST);
|
||
|
}
|
||
|
|
||
|
EISChannel::~EISChannel()
|
||
|
{
|
||
|
ERRORTYPE ret;
|
||
|
|
||
|
if(mGroupId != MM_INVALID_DEV)
|
||
|
{
|
||
|
ret = AW_MPI_EIS_DisableChn(mGroupId);
|
||
|
if(SUCCESS != ret)
|
||
|
{
|
||
|
aloge("fatal error! AW_MPI_EIS_DisableChn fail!");
|
||
|
}
|
||
|
|
||
|
mGroupId = MM_INVALID_DEV;
|
||
|
}
|
||
|
|
||
|
if (mpCommandThread != NULL)
|
||
|
{
|
||
|
mpCommandThread->stopThread();
|
||
|
delete mpCommandThread;
|
||
|
}
|
||
|
if(mpCapProcess != NULL)
|
||
|
{
|
||
|
delete mpCapProcess;
|
||
|
}
|
||
|
if (mpPrevThread != NULL)
|
||
|
{
|
||
|
mpPrevThread->stopThread();
|
||
|
delete mpPrevThread;
|
||
|
}
|
||
|
if (mpPicThread != NULL)
|
||
|
{
|
||
|
mpPicThread->stopThread();
|
||
|
delete mpPicThread;
|
||
|
}
|
||
|
|
||
|
OSAL_QueueTerminate(mpPreviewQueue);
|
||
|
delete mpPreviewQueue;
|
||
|
mpPreviewQueue = NULL;
|
||
|
|
||
|
OSAL_QueueTerminate(mpPictureQueue);
|
||
|
delete mpPictureQueue;
|
||
|
mpPictureQueue = NULL;
|
||
|
|
||
|
if (mpPreviewWindow != NULL) {
|
||
|
delete mpPreviewWindow;
|
||
|
}
|
||
|
if (mpCallbackNotifier != NULL) {
|
||
|
delete mpCallbackNotifier;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::GetChnOutFrameSize(EIS_CHN chnId, EIS_ATTR_S *pAttr, int &mFrameWidth, int &mFrameHeight)
|
||
|
{
|
||
|
if ((chnId == MM_INVALID_CHN) || (NULL == pAttr))
|
||
|
{
|
||
|
return BAD_VALUE;
|
||
|
}
|
||
|
|
||
|
mFrameWidth = pAttr->iVideoOutWidth;
|
||
|
mFrameHeight = pAttr->iVideoOutHeight;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::startChannel()
|
||
|
{
|
||
|
if(mbPreviewEnable)
|
||
|
{
|
||
|
mpPreviewWindow->startPreview();
|
||
|
}
|
||
|
|
||
|
mpPrevThread->startThread();
|
||
|
mpPicThread->startThread();
|
||
|
mpCommandThread->startThread();
|
||
|
|
||
|
mChannelState = EIS_CHN_STATE_STARTED;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::stopChannel()
|
||
|
{
|
||
|
AutoMutex lock1(mLock);
|
||
|
|
||
|
if (mChannelState != EIS_CHN_STATE_STARTED)
|
||
|
{
|
||
|
aloge("stopChannel in error state %d", mChannelState);
|
||
|
return INVALID_OPERATION;
|
||
|
}
|
||
|
|
||
|
alogv("stopChannel");
|
||
|
if (mbPreviewEnable == true)
|
||
|
{
|
||
|
mpPreviewWindow->stopPreview();
|
||
|
}
|
||
|
|
||
|
mpPrevThread->releaseAllFrames();
|
||
|
mpPicThread->releaseAllFrames();
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
EIS_CHN EISChannel::getChannelId()
|
||
|
{
|
||
|
AutoMutex lock(mLock);
|
||
|
return mChannelId;
|
||
|
}
|
||
|
|
||
|
|
||
|
status_t EISChannel::setParameters(CameraParameters ¶m)
|
||
|
{
|
||
|
mParameters = param;
|
||
|
mColorSpace = mParameters.getColorSpace();
|
||
|
int rotation = param.getPreviewRotation();
|
||
|
|
||
|
if(rotation!=0 && rotation!=90 && rotation!=180 && rotation!=270 && rotation!=360)
|
||
|
{
|
||
|
aloge("fatal error! eis rotation[%d] is invalid!", rotation);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
alogd("eis paramPreviewRotation set to %d degree", rotation);
|
||
|
mpPreviewWindow->setPreviewRotation(rotation);
|
||
|
}
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::getParameters(CameraParameters ¶m)
|
||
|
{
|
||
|
param = mParameters;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::setChannelDisplay(int hlay)
|
||
|
{
|
||
|
return mpPreviewWindow->setPreviewWindow(hlay);
|
||
|
}
|
||
|
|
||
|
bool EISChannel::isPreviewEnabled()
|
||
|
{
|
||
|
return mpPreviewWindow->isPreviewEnabled();
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::startRender()
|
||
|
{
|
||
|
status_t ret = NO_ERROR;
|
||
|
AutoMutex autoLock(mLock);
|
||
|
|
||
|
mbPreviewEnable = true;
|
||
|
if (EIS_CHN_STATE_STARTED == mChannelState)
|
||
|
{
|
||
|
mpPreviewWindow->setDispBufferNum(2);
|
||
|
ret = mpPreviewWindow->startPreview();
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::stopRender()
|
||
|
{
|
||
|
status_t ret = NO_ERROR;
|
||
|
|
||
|
AutoMutex autoLock(mLock);
|
||
|
|
||
|
mbPreviewEnable = false;
|
||
|
ret = mpPreviewWindow->stopPreview();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::releaseAllEISFrames()
|
||
|
{
|
||
|
mpPrevThread->releaseAllFrames();
|
||
|
mpPicThread->releaseAllFrames();
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::releaseRecordingFrame(uint32_t index)
|
||
|
{
|
||
|
VIDEO_FRAME_BUFFER_S *pFrmbuf = NULL;
|
||
|
|
||
|
mFrameBuffersLock.lock();
|
||
|
for(std::list<VIDEO_FRAME_BUFFER_S>::iterator it=mFrameBuffers.begin(); it!=mFrameBuffers.end(); ++it)
|
||
|
{
|
||
|
if(it->mFrameBuf.mId == index)
|
||
|
{
|
||
|
if(NULL == pFrmbuf)
|
||
|
{
|
||
|
pFrmbuf = &*it;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! MPP-EIS frame id[0x%x] is repeated!", index);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
mFrameBuffersLock.unlock();
|
||
|
|
||
|
if(NULL == pFrmbuf)
|
||
|
{
|
||
|
aloge("fatal error! not find frame index[0x%x]", index);
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
int ret;
|
||
|
AutoMutex lock(mRefCntLock);
|
||
|
|
||
|
if (pFrmbuf->mRefCnt > 0 && --pFrmbuf->mRefCnt == 0) // only when refcnt equal to 0, frm can be released.
|
||
|
{
|
||
|
ret = AW_MPI_EIS_ReleaseData(mGroupId, &pFrmbuf->mFrameBuf);
|
||
|
if (ret != SUCCESS)
|
||
|
{
|
||
|
aloge("AW_MPI_EIS_ReleaseFrame error! ChnId:%d, mPicId:%d", mGroupId, pFrmbuf->mFrameBuf.mId);
|
||
|
}
|
||
|
}
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::startRecording(CameraRecordingProxyListener *pCb, int recorderId)
|
||
|
{
|
||
|
return mpCallbackNotifier->startRecording(pCb, recorderId);
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::stopRecording(int recorderId)
|
||
|
{
|
||
|
return mpCallbackNotifier->stopRecording(recorderId);
|
||
|
}
|
||
|
|
||
|
void EISChannel::setDataListener(DataListener *pCb)
|
||
|
{
|
||
|
mpCallbackNotifier->setDataListener(pCb);
|
||
|
}
|
||
|
|
||
|
void EISChannel::setNotifyListener(NotifyListener *pCb)
|
||
|
{
|
||
|
mpCallbackNotifier->setNotifyListener(pCb);
|
||
|
}
|
||
|
|
||
|
|
||
|
status_t EISChannel::doTakePicture(unsigned int msgType)
|
||
|
{
|
||
|
AutoMutex lock(mLock);
|
||
|
|
||
|
int jpeg_quality = mParameters.getJpegQuality();
|
||
|
if (jpeg_quality <= 0) {
|
||
|
jpeg_quality = 90;
|
||
|
}
|
||
|
mpCallbackNotifier->setJpegQuality(jpeg_quality);
|
||
|
|
||
|
int jpeg_rotate = mParameters.getJpegRotation();
|
||
|
if (jpeg_rotate <= 0) {
|
||
|
jpeg_rotate = 0;
|
||
|
}
|
||
|
mpCallbackNotifier->setJpegRotate(jpeg_rotate);
|
||
|
|
||
|
SIZE_S size;
|
||
|
mParameters.getPictureSize(size);
|
||
|
mpCallbackNotifier->setPictureSize(size.Width, size.Height);
|
||
|
|
||
|
mParameters.getJpegThumbnailSize(size);
|
||
|
mpCallbackNotifier->setJpegThumbnailSize(size.Width, size.Height);
|
||
|
|
||
|
char *pGpsMethod = mParameters.getGpsProcessingMethod();
|
||
|
if (pGpsMethod != NULL) {
|
||
|
mpCallbackNotifier->setGPSLatitude(mParameters.getGpsLatitude());
|
||
|
mpCallbackNotifier->setGPSLongitude(mParameters.getGpsLongitude());
|
||
|
mpCallbackNotifier->setGPSAltitude(mParameters.getGpsAltitude());
|
||
|
mpCallbackNotifier->setGPSTimestamp(mParameters.getGpsTimestamp());
|
||
|
mpCallbackNotifier->setGPSMethod(pGpsMethod);
|
||
|
}
|
||
|
|
||
|
mTakePicMsgType = msgType;
|
||
|
mpCallbackNotifier->enableMessage(msgType);
|
||
|
|
||
|
mTakePictureMode = mParameters.getPictureMode();
|
||
|
mpCapProcess->SendCommand_TakePicture();
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::doCancelContinuousPicture()
|
||
|
{
|
||
|
AutoMutex lock(mLock);
|
||
|
|
||
|
mpCapProcess->SendCommand_CancelContinuousPicture();
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::takePicture(unsigned int msgType, PictureRegionCallback *pPicReg)
|
||
|
{
|
||
|
AutoMutex lock(mLock);
|
||
|
|
||
|
if(mbTakePictureStart)
|
||
|
{
|
||
|
aloge("fatal error! EIS During taking picture, we don't accept new takePicture command!");
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mbTakePictureStart = true;
|
||
|
|
||
|
mpCallbackNotifier->setPictureRegionCallback(pPicReg);
|
||
|
mpCommandThread->SendCommand_TakePicture(msgType);
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::notifyPictureRelease()
|
||
|
{
|
||
|
return mpCallbackNotifier->notifyPictureRelease();
|
||
|
}
|
||
|
|
||
|
status_t EISChannel::cancelContinuousPicture()
|
||
|
{
|
||
|
AutoMutex lock(mLock);
|
||
|
|
||
|
if(!mbTakePictureStart)
|
||
|
{
|
||
|
aloge("fatal error! EIS not start take picture!");
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
mpCommandThread->SendCommand_CancelContinuousPicture();
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
void EISChannel::increaseBufRef(VIDEO_FRAME_BUFFER_S *pBuf)
|
||
|
{
|
||
|
VIDEO_FRAME_BUFFER_S *pFrame = (VIDEO_FRAME_BUFFER_S*)pBuf;
|
||
|
|
||
|
AutoMutex lock(mRefCntLock);
|
||
|
++pFrame->mRefCnt; // when someone want to use this frm, the counter of this frm will be increased
|
||
|
}
|
||
|
|
||
|
void EISChannel::decreaseBufRef(unsigned int nBufId)
|
||
|
{
|
||
|
releaseRecordingFrame(nBufId);
|
||
|
}
|
||
|
|
||
|
// will be called in previewwindow
|
||
|
void EISChannel::NotifyRenderStart()
|
||
|
{
|
||
|
mpCallbackNotifier->NotifyRenderStart();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void EISChannel::process() // call in capture thread
|
||
|
{
|
||
|
bool bRunningFlag = true;
|
||
|
//bool bTakePictureStart = false;
|
||
|
EyeseeMessage msg;
|
||
|
status_t getMsgRet;
|
||
|
ERRORTYPE ret;
|
||
|
|
||
|
PROCESS_MESSAGE:
|
||
|
getMsgRet = mpCapProcess->mMsgQueue.dequeueMessage(&msg);
|
||
|
if(getMsgRet == NO_ERROR)
|
||
|
{
|
||
|
if(DoCaptureProcess::MsgTypeCapture_TakePicture == msg.mMsgType)
|
||
|
{
|
||
|
if(!mbProcessTakePictureStart)
|
||
|
{
|
||
|
mbProcessTakePictureStart = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
alogd("Be careful! take picture is doing already!");
|
||
|
}
|
||
|
alogd("EIS take picture mode is [0x%x]", mTakePictureMode);
|
||
|
//set picture number to callbackNotifier
|
||
|
int nPicNum = 0;
|
||
|
switch(mTakePictureMode)
|
||
|
{
|
||
|
case TAKE_PICTURE_MODE_FAST:
|
||
|
nPicNum = 1;
|
||
|
break;
|
||
|
case TAKE_PICTURE_MODE_CONTINUOUS:
|
||
|
nPicNum = mParameters.getContinuousPictureNumber();
|
||
|
break;
|
||
|
default:
|
||
|
nPicNum = 1;
|
||
|
break;
|
||
|
}
|
||
|
mpCallbackNotifier->setPictureNum(nPicNum);
|
||
|
}
|
||
|
else if(DoCaptureProcess::MsgTypeCapture_CancelContinuousPicture == msg.mMsgType)
|
||
|
{
|
||
|
if(mbProcessTakePictureStart)
|
||
|
{
|
||
|
if(TAKE_PICTURE_MODE_CONTINUOUS == mTakePictureMode)
|
||
|
{
|
||
|
mpPicThread->notifyPictureEnd();
|
||
|
|
||
|
mContinuousPictureStartTime = 0;
|
||
|
mContinuousPictureLast = 0;
|
||
|
mContinuousPictureInterval = 0;
|
||
|
mContinuousPictureCnt = 0;
|
||
|
mContinuousPictureMax = 0;
|
||
|
|
||
|
mbProcessTakePictureStart = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! take picture mode[0x%x] is not continuous!", mTakePictureMode);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! not start take picture, mode[0x%x]!", mTakePictureMode);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("unknown msg[0x%x]!", msg.mMsgType);
|
||
|
}
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
|
||
|
//VI_FRAME_BUF_INFO_S buffer;
|
||
|
VIDEO_FRAME_INFO_S stVideoFrameInfo;
|
||
|
ret = AW_MPI_EIS_GetData(mGroupId, &stVideoFrameInfo, 2000);
|
||
|
if (ret != SUCCESS)
|
||
|
{
|
||
|
aloge("AW_MPI_EIS_GetData error!");
|
||
|
return;
|
||
|
}
|
||
|
//VIDEO_FRAME_BUFFER_S *pFrmbuf = mpVideoFrmBuffer + buffer.u32PoolId;
|
||
|
VIDEO_FRAME_BUFFER_S *pFrmbuf = NULL;
|
||
|
|
||
|
mFrameBuffersLock.lock();
|
||
|
for(std::list<VIDEO_FRAME_BUFFER_S>::iterator it=mFrameBuffers.begin(); it!=mFrameBuffers.end(); ++it)
|
||
|
{
|
||
|
if(it->mFrameBuf.mId == stVideoFrameInfo.mId)
|
||
|
{
|
||
|
if(NULL == pFrmbuf)
|
||
|
{
|
||
|
pFrmbuf = &*it;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! MPP-EIS frame id[0x%x] is repeated!", stVideoFrameInfo.mId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(NULL == pFrmbuf)
|
||
|
{
|
||
|
alogd("EISChannel[%d] frame buffer array did not contain this bufferId[0x%x], add it.", mGroupId, stVideoFrameInfo.mId);
|
||
|
VIDEO_FRAME_BUFFER_S newFrame;
|
||
|
|
||
|
memset(&newFrame, 0, sizeof(VIDEO_FRAME_BUFFER_S));
|
||
|
mFrameBuffers.push_back(newFrame);
|
||
|
|
||
|
pFrmbuf = &mFrameBuffers.back();
|
||
|
}
|
||
|
|
||
|
pFrmbuf->mFrameBuf = stVideoFrameInfo;
|
||
|
|
||
|
if(mRectCrop.Width != 0 && mRectCrop.Height != 0)
|
||
|
{
|
||
|
pFrmbuf->mFrameBuf.VFrame.mOffsetTop = mRectCrop.Y;
|
||
|
pFrmbuf->mFrameBuf.VFrame.mOffsetBottom = mRectCrop.Y + mRectCrop.Height;
|
||
|
pFrmbuf->mFrameBuf.VFrame.mOffsetLeft = mRectCrop.X;
|
||
|
pFrmbuf->mFrameBuf.VFrame.mOffsetRight = mRectCrop.X + mRectCrop.Width;
|
||
|
}
|
||
|
|
||
|
pFrmbuf->mColorSpace = mColorSpace;
|
||
|
//pFrmbuf->mIsThumbAvailable = 0;
|
||
|
//pFrmbuf->mThumbUsedForPhoto = 0;
|
||
|
pFrmbuf->mRefCnt = 1;
|
||
|
mFrameBuffersLock.unlock();
|
||
|
|
||
|
if(false == mbProcessTakePictureStart)
|
||
|
{
|
||
|
{
|
||
|
AutoMutex lock(mRefCntLock);
|
||
|
pFrmbuf->mRefCnt++;
|
||
|
}
|
||
|
|
||
|
OSAL_Queue(mpPreviewQueue, pFrmbuf);
|
||
|
|
||
|
mpPrevThread->notifyNewFrameCome();
|
||
|
}
|
||
|
else // to process picture taken operation samuel.zhou
|
||
|
{
|
||
|
if (mTakePictureMode == TAKE_PICTURE_MODE_NORMAL)
|
||
|
{
|
||
|
aloge("fatal error! EIS can't support normal mode take picture !");
|
||
|
mpPicThread->notifyPictureEnd();
|
||
|
|
||
|
mbProcessTakePictureStart = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mRefCntLock.lock();
|
||
|
pFrmbuf->mRefCnt++;
|
||
|
mRefCntLock.unlock();
|
||
|
|
||
|
OSAL_Queue(mpPreviewQueue, pFrmbuf);
|
||
|
mpPrevThread->notifyNewFrameCome();
|
||
|
|
||
|
if (mTakePictureMode == TAKE_PICTURE_MODE_FAST)
|
||
|
{
|
||
|
mRefCntLock.lock();
|
||
|
pFrmbuf->mRefCnt++;
|
||
|
mRefCntLock.unlock();
|
||
|
|
||
|
mIsPicCopy = false;
|
||
|
OSAL_Queue(mpPictureQueue, pFrmbuf);
|
||
|
|
||
|
mpPicThread->notifyNewFrameCome();
|
||
|
mpPicThread->notifyPictureEnd();
|
||
|
|
||
|
mbProcessTakePictureStart = false;
|
||
|
}
|
||
|
else if(mTakePictureMode == TAKE_PICTURE_MODE_CONTINUOUS)
|
||
|
{
|
||
|
bool bPermit = false;
|
||
|
if (0 == mContinuousPictureStartTime) //let's begin!
|
||
|
{
|
||
|
mContinuousPictureStartTime = CDX_GetSysTimeUsMonotonic()/1000;
|
||
|
|
||
|
mContinuousPictureLast = mContinuousPictureStartTime;
|
||
|
mContinuousPictureInterval = mParameters.getContinuousPictureIntervalMs();
|
||
|
|
||
|
mContinuousPictureCnt = 0;
|
||
|
mContinuousPictureMax = mParameters.getContinuousPictureNumber();
|
||
|
|
||
|
bPermit = true;
|
||
|
alogd("begin continous picture, will take [%d]pics, interval[%llu]ms!", mContinuousPictureMax, mContinuousPictureInterval);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(mContinuousPictureInterval <= 0)
|
||
|
{
|
||
|
bPermit = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
uint64_t nCurTime = CDX_GetSysTimeUsMonotonic()/1000;
|
||
|
if(nCurTime > mContinuousPictureLast + mContinuousPictureInterval)
|
||
|
{
|
||
|
bPermit = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(bPermit)
|
||
|
{
|
||
|
mRefCntLock.lock();
|
||
|
pFrmbuf->mRefCnt++;
|
||
|
mRefCntLock.unlock();
|
||
|
|
||
|
mIsPicCopy = false;
|
||
|
OSAL_Queue(mpPictureQueue, pFrmbuf);
|
||
|
mpPicThread->notifyNewFrameCome();
|
||
|
|
||
|
mContinuousPictureCnt++;
|
||
|
|
||
|
if(mContinuousPictureCnt >= mContinuousPictureMax)
|
||
|
{
|
||
|
mpPicThread->notifyPictureEnd();
|
||
|
|
||
|
mContinuousPictureStartTime = 0;
|
||
|
mContinuousPictureLast = 0;
|
||
|
mContinuousPictureInterval = 0;
|
||
|
mContinuousPictureCnt = 0;
|
||
|
mContinuousPictureMax = 0;
|
||
|
|
||
|
mbProcessTakePictureStart = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mContinuousPictureLast = mContinuousPictureStartTime+mContinuousPictureCnt*mContinuousPictureInterval;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! any other take picture mode[0x%x]?", mTakePictureMode);
|
||
|
mbProcessTakePictureStart = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mFrameCounter++;
|
||
|
releaseRecordingFrame(pFrmbuf->mFrameBuf.mId);
|
||
|
}
|
||
|
|
||
|
|
||
|
bool EISChannel::previewThread()
|
||
|
{
|
||
|
bool bRunningFlag = true;
|
||
|
EyeseeMessage msg;
|
||
|
status_t getMsgRet;
|
||
|
ERRORTYPE ret;
|
||
|
VIDEO_FRAME_BUFFER_S *pFrmbuf;
|
||
|
|
||
|
PROCESS_MESSAGE:
|
||
|
getMsgRet = mpPrevThread->mMsgQueue.dequeueMessage(&msg);
|
||
|
if(getMsgRet == NO_ERROR)
|
||
|
{
|
||
|
if(DoPreviewThread::MsgTypePreview_InputFrameAvailable == msg.mMsgType)
|
||
|
{
|
||
|
}
|
||
|
else if(DoPreviewThread::MsgTypePreview_releaseAllFrames == msg.mMsgType)
|
||
|
{
|
||
|
AutoMutex lock(mpPrevThread->mWaitLock);
|
||
|
for(;;)
|
||
|
{
|
||
|
pFrmbuf = (VIDEO_FRAME_BUFFER_S*)OSAL_Dequeue(mpPreviewQueue);
|
||
|
if(pFrmbuf)
|
||
|
{
|
||
|
releaseRecordingFrame(pFrmbuf->mFrameBuf.mId);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(mpPrevThread->mbWaitReleaseAllFrames)
|
||
|
{
|
||
|
mpPrevThread->mbWaitReleaseAllFrames = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! check code!");
|
||
|
}
|
||
|
|
||
|
mpPrevThread->mCondReleaseAllFramesFinished.signal();
|
||
|
}
|
||
|
else if(DoPreviewThread::MsgTypePreview_Exit == msg.mMsgType)
|
||
|
{
|
||
|
bRunningFlag = false;
|
||
|
goto _exit0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("unknown msg[0x%x]!", msg.mMsgType);
|
||
|
}
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
|
||
|
pFrmbuf = (VIDEO_FRAME_BUFFER_S*)OSAL_Dequeue(mpPreviewQueue);
|
||
|
if (pFrmbuf == NULL)
|
||
|
{
|
||
|
{ // key point
|
||
|
AutoMutex lock(mpPrevThread->mWaitLock);
|
||
|
|
||
|
if(OSAL_GetElemNum(mpPreviewQueue) > 0)
|
||
|
{
|
||
|
alogd("Low probability! EIS preview new frame come before check again.");
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mpPrevThread->mbWaitPreviewFrame = true;
|
||
|
}
|
||
|
}
|
||
|
mpPrevThread->mMsgQueue.waitMessage();
|
||
|
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
|
||
|
mpCallbackNotifier->onNextFrameAvailable(pFrmbuf); // -->EyeseeRecorder one new frame samuel.zhou
|
||
|
mpPreviewWindow->onNextFrameAvailable(pFrmbuf); // to show
|
||
|
|
||
|
releaseRecordingFrame(pFrmbuf->mFrameBuf.mId);
|
||
|
//return true;
|
||
|
goto PROCESS_MESSAGE;
|
||
|
_exit0:
|
||
|
return bRunningFlag;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool EISChannel::pictureThread()
|
||
|
{
|
||
|
bool bRunningFlag = true;
|
||
|
EyeseeMessage msg;
|
||
|
status_t getMsgRet;
|
||
|
ERRORTYPE ret;
|
||
|
bool bDrainPictureQueue = false;
|
||
|
VIDEO_FRAME_BUFFER_S *pFrmbuf;
|
||
|
|
||
|
PROCESS_MESSAGE:
|
||
|
getMsgRet = mpPicThread->mMsgQueue.dequeueMessage(&msg);
|
||
|
if(getMsgRet == NO_ERROR)
|
||
|
{
|
||
|
if(DoPictureThread::MsgTypePicture_InputFrameAvailable == msg.mMsgType)
|
||
|
{
|
||
|
}
|
||
|
else if(DoPictureThread::MsgTypePicture_SendPictureEnd == msg.mMsgType)
|
||
|
{
|
||
|
bDrainPictureQueue = true;
|
||
|
}
|
||
|
else if(DoPictureThread::MsgTypePicture_releaseAllFrames == msg.mMsgType)
|
||
|
{
|
||
|
AutoMutex lock(mpPicThread->mWaitLock);
|
||
|
for(;;)
|
||
|
{
|
||
|
pFrmbuf = (VIDEO_FRAME_BUFFER_S*)OSAL_Dequeue(mpPictureQueue);
|
||
|
if(pFrmbuf)
|
||
|
{
|
||
|
releaseRecordingFrame(pFrmbuf->mFrameBuf.mId);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(mpPicThread->mbWaitReleaseAllFrames)
|
||
|
{
|
||
|
mpPicThread->mbWaitReleaseAllFrames = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("fatal error! check code!");
|
||
|
}
|
||
|
|
||
|
mpPicThread->mCondReleaseAllFramesFinished.signal();
|
||
|
}
|
||
|
else if(DoPictureThread::MsgTypePicture_Exit == msg.mMsgType)
|
||
|
{
|
||
|
bRunningFlag = false;
|
||
|
goto _exit0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("unknown msg[0x%x]!", msg.mMsgType);
|
||
|
}
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
pFrmbuf = (VIDEO_FRAME_BUFFER_S*)OSAL_Dequeue(mpPictureQueue);
|
||
|
if (pFrmbuf == NULL)
|
||
|
{
|
||
|
if(bDrainPictureQueue)
|
||
|
{
|
||
|
mpCallbackNotifier->disableMessage(mTakePicMsgType);
|
||
|
bDrainPictureQueue = false;
|
||
|
mpCallbackNotifier->takePictureEnd();
|
||
|
|
||
|
mLock.lock();
|
||
|
if(!mbTakePictureStart)
|
||
|
{
|
||
|
aloge("fatal error! why EIS takePictureStart is false when we finish take picture?");
|
||
|
}
|
||
|
mbTakePictureStart = false;
|
||
|
mLock.unlock();
|
||
|
}
|
||
|
{
|
||
|
AutoMutex lock(mpPicThread->mWaitLock);
|
||
|
if(OSAL_GetElemNum(mpPictureQueue) > 0)
|
||
|
{
|
||
|
alogd("Low probability! EIS picture new frame come before check again.");
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mpPicThread->mbWaitPictureFrame = true;
|
||
|
}
|
||
|
}
|
||
|
mpPicThread->mMsgQueue.waitMessage();
|
||
|
goto PROCESS_MESSAGE;
|
||
|
}
|
||
|
|
||
|
mpCallbackNotifier->takePicture(pFrmbuf);
|
||
|
|
||
|
releaseRecordingFrame(pFrmbuf->mFrameBuf.mId);
|
||
|
|
||
|
if(!bDrainPictureQueue)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
//return true;
|
||
|
goto PROCESS_MESSAGE;
|
||
|
_exit0:
|
||
|
return bRunningFlag;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool EISChannel::commandThread()
|
||
|
{
|
||
|
bool bRunningFlag = true;
|
||
|
EyeseeMessage msg;
|
||
|
status_t getMsgRet;
|
||
|
ERRORTYPE ret;
|
||
|
|
||
|
PROCESS_MESSAGE:
|
||
|
getMsgRet = mpCommandThread->mMsgQueue.dequeueMessage(&msg);
|
||
|
if(getMsgRet == NO_ERROR)
|
||
|
{
|
||
|
if(DoCommandThread::MsgTypeCommand_TakePicture == msg.mMsgType)
|
||
|
{
|
||
|
doTakePicture(msg.mPara0);
|
||
|
}
|
||
|
else if(DoCommandThread::MsgTypeCommand_CancelContinuousPicture == msg.mMsgType)
|
||
|
{
|
||
|
doCancelContinuousPicture();
|
||
|
}
|
||
|
else if(DoCommandThread::MsgTypeCommand_Exit == msg.mMsgType)
|
||
|
{
|
||
|
bRunningFlag = false;
|
||
|
goto _exit0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
aloge("unknown msg[0x%x]!", msg.mMsgType);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mpCommandThread->mMsgQueue.waitMessage();
|
||
|
}
|
||
|
//return true;
|
||
|
goto PROCESS_MESSAGE;
|
||
|
_exit0:
|
||
|
return bRunningFlag;
|
||
|
}
|
||
|
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|