| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "SoftAVC" |
| #include <utils/Log.h> |
| |
| #include "SoftAVC.h" |
| |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| |
| #include "avcdec_api.h" |
| #include "avcdec_int.h" |
| |
| namespace android { |
| |
| static const char kStartCode[4] = { 0x00, 0x00, 0x00, 0x01 }; |
| |
| template<class T> |
| static void InitOMXParams(T *params) { |
| params->nSize = sizeof(T); |
| params->nVersion.s.nVersionMajor = 1; |
| params->nVersion.s.nVersionMinor = 0; |
| params->nVersion.s.nRevision = 0; |
| params->nVersion.s.nStep = 0; |
| } |
| |
| static int32_t Malloc(void *userData, int32_t size, int32_t attrs) { |
| return reinterpret_cast<int32_t>(malloc(size)); |
| } |
| |
| static void Free(void *userData, int32_t ptr) { |
| free(reinterpret_cast<void *>(ptr)); |
| } |
| |
| SoftAVC::SoftAVC( |
| const char *name, |
| const OMX_CALLBACKTYPE *callbacks, |
| OMX_PTR appData, |
| OMX_COMPONENTTYPE **component) |
| : SimpleSoftOMXComponent(name, callbacks, appData, component), |
| mHandle(new tagAVCHandle), |
| mInputBufferCount(0), |
| mWidth(160), |
| mHeight(120), |
| mCropLeft(0), |
| mCropTop(0), |
| mCropRight(mWidth - 1), |
| mCropBottom(mHeight - 1), |
| mSPSSeen(false), |
| mPPSSeen(false), |
| mCurrentTimeUs(-1), |
| mEOSStatus(INPUT_DATA_AVAILABLE), |
| mOutputPortSettingsChange(NONE) { |
| initPorts(); |
| CHECK_EQ(initDecoder(), (status_t)OK); |
| } |
| |
| SoftAVC::~SoftAVC() { |
| PVAVCCleanUpDecoder(mHandle); |
| |
| delete mHandle; |
| mHandle = NULL; |
| } |
| |
| void SoftAVC::initPorts() { |
| OMX_PARAM_PORTDEFINITIONTYPE def; |
| InitOMXParams(&def); |
| |
| def.nPortIndex = 0; |
| def.eDir = OMX_DirInput; |
| def.nBufferCountMin = kNumInputBuffers; |
| def.nBufferCountActual = def.nBufferCountMin; |
| def.nBufferSize = 8192; |
| def.bEnabled = OMX_TRUE; |
| def.bPopulated = OMX_FALSE; |
| def.eDomain = OMX_PortDomainVideo; |
| def.bBuffersContiguous = OMX_FALSE; |
| def.nBufferAlignment = 1; |
| |
| def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_AVC); |
| def.format.video.pNativeRender = NULL; |
| def.format.video.nFrameWidth = mWidth; |
| def.format.video.nFrameHeight = mHeight; |
| def.format.video.nStride = def.format.video.nFrameWidth; |
| def.format.video.nSliceHeight = def.format.video.nFrameHeight; |
| def.format.video.nBitrate = 0; |
| def.format.video.xFramerate = 0; |
| def.format.video.bFlagErrorConcealment = OMX_FALSE; |
| def.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC; |
| def.format.video.eColorFormat = OMX_COLOR_FormatUnused; |
| def.format.video.pNativeWindow = NULL; |
| |
| addPort(def); |
| |
| def.nPortIndex = 1; |
| def.eDir = OMX_DirOutput; |
| def.nBufferCountMin = kNumOutputBuffers; |
| def.nBufferCountActual = def.nBufferCountMin; |
| def.bEnabled = OMX_TRUE; |
| def.bPopulated = OMX_FALSE; |
| def.eDomain = OMX_PortDomainVideo; |
| def.bBuffersContiguous = OMX_FALSE; |
| def.nBufferAlignment = 2; |
| |
| def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW); |
| def.format.video.pNativeRender = NULL; |
| def.format.video.nFrameWidth = mWidth; |
| def.format.video.nFrameHeight = mHeight; |
| def.format.video.nStride = def.format.video.nFrameWidth; |
| def.format.video.nSliceHeight = def.format.video.nFrameHeight; |
| def.format.video.nBitrate = 0; |
| def.format.video.xFramerate = 0; |
| def.format.video.bFlagErrorConcealment = OMX_FALSE; |
| def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; |
| def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar; |
| def.format.video.pNativeWindow = NULL; |
| |
| def.nBufferSize = |
| (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2; |
| |
| addPort(def); |
| } |
| |
| status_t SoftAVC::initDecoder() { |
| memset(mHandle, 0, sizeof(tagAVCHandle)); |
| mHandle->AVCObject = NULL; |
| mHandle->userData = this; |
| mHandle->CBAVC_DPBAlloc = ActivateSPSWrapper; |
| mHandle->CBAVC_FrameBind = BindFrameWrapper; |
| mHandle->CBAVC_FrameUnbind = UnbindFrame; |
| mHandle->CBAVC_Malloc = Malloc; |
| mHandle->CBAVC_Free = Free; |
| |
| return OK; |
| } |
| |
| OMX_ERRORTYPE SoftAVC::internalGetParameter( |
| OMX_INDEXTYPE index, OMX_PTR params) { |
| switch (index) { |
| case OMX_IndexParamVideoPortFormat: |
| { |
| OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = |
| (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; |
| |
| if (formatParams->nPortIndex > 1) { |
| return OMX_ErrorUndefined; |
| } |
| |
| if (formatParams->nIndex != 0) { |
| return OMX_ErrorNoMore; |
| } |
| |
| if (formatParams->nPortIndex == 0) { |
| formatParams->eCompressionFormat = OMX_VIDEO_CodingAVC; |
| formatParams->eColorFormat = OMX_COLOR_FormatUnused; |
| formatParams->xFramerate = 0; |
| } else { |
| CHECK_EQ(formatParams->nPortIndex, 1u); |
| |
| formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; |
| formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; |
| formatParams->xFramerate = 0; |
| } |
| |
| return OMX_ErrorNone; |
| } |
| |
| default: |
| return SimpleSoftOMXComponent::internalGetParameter(index, params); |
| } |
| } |
| |
| OMX_ERRORTYPE SoftAVC::internalSetParameter( |
| OMX_INDEXTYPE index, const OMX_PTR params) { |
| switch (index) { |
| case OMX_IndexParamStandardComponentRole: |
| { |
| const OMX_PARAM_COMPONENTROLETYPE *roleParams = |
| (const OMX_PARAM_COMPONENTROLETYPE *)params; |
| |
| if (strncmp((const char *)roleParams->cRole, |
| "video_decoder.avc", |
| OMX_MAX_STRINGNAME_SIZE - 1)) { |
| return OMX_ErrorUndefined; |
| } |
| |
| return OMX_ErrorNone; |
| } |
| |
| case OMX_IndexParamVideoPortFormat: |
| { |
| OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = |
| (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; |
| |
| if (formatParams->nPortIndex > 1) { |
| return OMX_ErrorUndefined; |
| } |
| |
| if (formatParams->nIndex != 0) { |
| return OMX_ErrorNoMore; |
| } |
| |
| return OMX_ErrorNone; |
| } |
| |
| default: |
| return SimpleSoftOMXComponent::internalSetParameter(index, params); |
| } |
| } |
| |
| OMX_ERRORTYPE SoftAVC::getConfig( |
| OMX_INDEXTYPE index, OMX_PTR params) { |
| switch (index) { |
| case OMX_IndexConfigCommonOutputCrop: |
| { |
| OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params; |
| |
| if (rectParams->nPortIndex != 1) { |
| return OMX_ErrorUndefined; |
| } |
| |
| rectParams->nLeft = mCropLeft; |
| rectParams->nTop = mCropTop; |
| rectParams->nWidth = mCropRight - mCropLeft + 1; |
| rectParams->nHeight = mCropBottom - mCropTop + 1; |
| |
| return OMX_ErrorNone; |
| } |
| |
| default: |
| return OMX_ErrorUnsupportedIndex; |
| } |
| } |
| |
| static void findNALFragment( |
| const OMX_BUFFERHEADERTYPE *inHeader, |
| const uint8_t **fragPtr, size_t *fragSize) { |
| const uint8_t *data = inHeader->pBuffer + inHeader->nOffset; |
| |
| size_t size = inHeader->nFilledLen; |
| |
| CHECK(size >= 4); |
| CHECK(!memcmp(kStartCode, data, 4)); |
| |
| size_t offset = 4; |
| while (offset + 3 < size && memcmp(kStartCode, &data[offset], 4)) { |
| ++offset; |
| } |
| |
| *fragPtr = &data[4]; |
| if (offset + 3 >= size) { |
| *fragSize = size - 4; |
| } else { |
| *fragSize = offset - 4; |
| } |
| } |
| |
| void SoftAVC::onQueueFilled(OMX_U32 portIndex) { |
| if (mOutputPortSettingsChange != NONE) { |
| return; |
| } |
| |
| List<BufferInfo *> &inQueue = getPortQueue(0); |
| List<BufferInfo *> &outQueue = getPortQueue(1); |
| |
| if (mEOSStatus == OUTPUT_FRAMES_FLUSHED) { |
| return; |
| } |
| |
| while ((mEOSStatus != INPUT_DATA_AVAILABLE || !inQueue.empty()) |
| && outQueue.size() == kNumOutputBuffers) { |
| if (mEOSStatus == INPUT_EOS_SEEN) { |
| OMX_BUFFERHEADERTYPE *outHeader; |
| if (drainOutputBuffer(&outHeader)) { |
| List<BufferInfo *>::iterator it = outQueue.begin(); |
| while ((*it)->mHeader != outHeader) { |
| ++it; |
| } |
| |
| BufferInfo *outInfo = *it; |
| outInfo->mOwnedByUs = false; |
| outQueue.erase(it); |
| outInfo = NULL; |
| |
| notifyFillBufferDone(outHeader); |
| outHeader = NULL; |
| return; |
| } |
| |
| BufferInfo *outInfo = *outQueue.begin(); |
| outHeader = outInfo->mHeader; |
| |
| outHeader->nOffset = 0; |
| outHeader->nFilledLen = 0; |
| outHeader->nFlags = OMX_BUFFERFLAG_EOS; |
| outHeader->nTimeStamp = 0; |
| |
| outQueue.erase(outQueue.begin()); |
| outInfo->mOwnedByUs = false; |
| notifyFillBufferDone(outHeader); |
| |
| mEOSStatus = OUTPUT_FRAMES_FLUSHED; |
| return; |
| } |
| |
| BufferInfo *inInfo = *inQueue.begin(); |
| OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; |
| |
| if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { |
| inQueue.erase(inQueue.begin()); |
| inInfo->mOwnedByUs = false; |
| notifyEmptyBufferDone(inHeader); |
| |
| mEOSStatus = INPUT_EOS_SEEN; |
| continue; |
| } |
| |
| mCurrentTimeUs = inHeader->nTimeStamp; |
| |
| const uint8_t *fragPtr; |
| size_t fragSize; |
| findNALFragment(inHeader, &fragPtr, &fragSize); |
| |
| bool releaseFragment; |
| OMX_BUFFERHEADERTYPE *outHeader; |
| status_t err = decodeFragment( |
| fragPtr, fragSize, |
| &releaseFragment, &outHeader); |
| |
| if (releaseFragment) { |
| CHECK_GE(inHeader->nFilledLen, fragSize + 4); |
| |
| inHeader->nOffset += fragSize + 4; |
| inHeader->nFilledLen -= fragSize + 4; |
| |
| if (inHeader->nFilledLen == 0) { |
| inInfo->mOwnedByUs = false; |
| inQueue.erase(inQueue.begin()); |
| inInfo = NULL; |
| notifyEmptyBufferDone(inHeader); |
| inHeader = NULL; |
| } |
| } |
| |
| if (outHeader != NULL) { |
| List<BufferInfo *>::iterator it = outQueue.begin(); |
| while ((*it)->mHeader != outHeader) { |
| ++it; |
| } |
| |
| BufferInfo *outInfo = *it; |
| outInfo->mOwnedByUs = false; |
| outQueue.erase(it); |
| outInfo = NULL; |
| |
| notifyFillBufferDone(outHeader); |
| outHeader = NULL; |
| return; |
| } |
| |
| if (err == INFO_FORMAT_CHANGED) { |
| return; |
| } |
| |
| if (err != OK) { |
| notify(OMX_EventError, OMX_ErrorUndefined, err, NULL); |
| return; |
| } |
| } |
| } |
| |
| status_t SoftAVC::decodeFragment( |
| const uint8_t *fragPtr, size_t fragSize, |
| bool *releaseFragment, |
| OMX_BUFFERHEADERTYPE **outHeader) { |
| *releaseFragment = true; |
| *outHeader = NULL; |
| |
| int nalType; |
| int nalRefIdc; |
| AVCDec_Status res = |
| PVAVCDecGetNALType( |
| const_cast<uint8_t *>(fragPtr), fragSize, |
| &nalType, &nalRefIdc); |
| |
| if (res != AVCDEC_SUCCESS) { |
| LOGV("cannot determine nal type"); |
| return ERROR_MALFORMED; |
| } |
| |
| if (nalType != AVC_NALTYPE_SPS && nalType != AVC_NALTYPE_PPS |
| && (!mSPSSeen || !mPPSSeen)) { |
| // We haven't seen SPS or PPS yet. |
| return OK; |
| } |
| |
| switch (nalType) { |
| case AVC_NALTYPE_SPS: |
| { |
| mSPSSeen = true; |
| |
| res = PVAVCDecSeqParamSet( |
| mHandle, const_cast<uint8_t *>(fragPtr), |
| fragSize); |
| |
| if (res != AVCDEC_SUCCESS) { |
| return ERROR_MALFORMED; |
| } |
| |
| AVCDecObject *pDecVid = (AVCDecObject *)mHandle->AVCObject; |
| |
| int32_t width = |
| (pDecVid->seqParams[0]->pic_width_in_mbs_minus1 + 1) * 16; |
| |
| int32_t height = |
| (pDecVid->seqParams[0]->pic_height_in_map_units_minus1 + 1) * 16; |
| |
| int32_t crop_left, crop_right, crop_top, crop_bottom; |
| if (pDecVid->seqParams[0]->frame_cropping_flag) |
| { |
| crop_left = 2 * pDecVid->seqParams[0]->frame_crop_left_offset; |
| crop_right = |
| width - (2 * pDecVid->seqParams[0]->frame_crop_right_offset + 1); |
| |
| if (pDecVid->seqParams[0]->frame_mbs_only_flag) |
| { |
| crop_top = 2 * pDecVid->seqParams[0]->frame_crop_top_offset; |
| crop_bottom = |
| height - |
| (2 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1); |
| } |
| else |
| { |
| crop_top = 4 * pDecVid->seqParams[0]->frame_crop_top_offset; |
| crop_bottom = |
| height - |
| (4 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1); |
| } |
| } else { |
| crop_bottom = height - 1; |
| crop_right = width - 1; |
| crop_top = crop_left = 0; |
| } |
| |
| status_t err = OK; |
| |
| if (mWidth != width || mHeight != height) { |
| mWidth = width; |
| mHeight = height; |
| |
| updatePortDefinitions(); |
| |
| notify(OMX_EventPortSettingsChanged, 1, 0, NULL); |
| mOutputPortSettingsChange = AWAITING_DISABLED; |
| |
| err = INFO_FORMAT_CHANGED; |
| } |
| |
| if (mCropLeft != crop_left |
| || mCropTop != crop_top |
| || mCropRight != crop_right |
| || mCropBottom != crop_bottom) { |
| mCropLeft = crop_left; |
| mCropTop = crop_top; |
| mCropRight = crop_right; |
| mCropBottom = crop_bottom; |
| |
| notify(OMX_EventPortSettingsChanged, |
| 1, |
| OMX_IndexConfigCommonOutputCrop, |
| NULL); |
| } |
| |
| return err; |
| } |
| |
| case AVC_NALTYPE_PPS: |
| { |
| mPPSSeen = true; |
| |
| res = PVAVCDecPicParamSet( |
| mHandle, const_cast<uint8_t *>(fragPtr), |
| fragSize); |
| |
| if (res != AVCDEC_SUCCESS) { |
| LOGV("PVAVCDecPicParamSet returned error %d", res); |
| return ERROR_MALFORMED; |
| } |
| |
| return OK; |
| } |
| |
| case AVC_NALTYPE_SLICE: |
| case AVC_NALTYPE_IDR: |
| { |
| res = PVAVCDecodeSlice( |
| mHandle, const_cast<uint8_t *>(fragPtr), |
| fragSize); |
| |
| if (res == AVCDEC_PICTURE_OUTPUT_READY) { |
| *releaseFragment = false; |
| |
| if (!drainOutputBuffer(outHeader)) { |
| return UNKNOWN_ERROR; |
| } |
| |
| return OK; |
| } |
| |
| if (res == AVCDEC_PICTURE_READY || res == AVCDEC_SUCCESS) { |
| return OK; |
| } else { |
| LOGV("PVAVCDecodeSlice returned error %d", res); |
| return ERROR_MALFORMED; |
| } |
| } |
| |
| case AVC_NALTYPE_SEI: |
| { |
| res = PVAVCDecSEI( |
| mHandle, const_cast<uint8_t *>(fragPtr), |
| fragSize); |
| |
| if (res != AVCDEC_SUCCESS) { |
| return ERROR_MALFORMED; |
| } |
| |
| return OK; |
| } |
| |
| case AVC_NALTYPE_AUD: |
| case AVC_NALTYPE_FILL: |
| case AVC_NALTYPE_EOSEQ: |
| { |
| return OK; |
| } |
| |
| default: |
| { |
| LOGE("Should not be here, unknown nalType %d", nalType); |
| |
| return ERROR_MALFORMED; |
| } |
| } |
| |
| return OK; |
| } |
| |
| bool SoftAVC::drainOutputBuffer(OMX_BUFFERHEADERTYPE **outHeader) { |
| int32_t index; |
| int32_t Release; |
| AVCFrameIO Output; |
| Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL; |
| AVCDec_Status status = |
| PVAVCDecGetOutput(mHandle, &index, &Release, &Output); |
| |
| if (status != AVCDEC_SUCCESS) { |
| return false; |
| } |
| |
| PortInfo *port = editPortInfo(1); |
| CHECK_GE(index, 0); |
| CHECK_LT((size_t)index, port->mBuffers.size()); |
| CHECK(port->mBuffers.editItemAt(index).mOwnedByUs); |
| |
| *outHeader = port->mBuffers.editItemAt(index).mHeader; |
| (*outHeader)->nOffset = 0; |
| (*outHeader)->nFilledLen = port->mDef.nBufferSize; |
| (*outHeader)->nFlags = 0; |
| |
| return true; |
| } |
| |
| void SoftAVC::onPortFlushCompleted(OMX_U32 portIndex) { |
| if (portIndex == 0) { |
| PVAVCDecReset(mHandle); |
| |
| mEOSStatus = INPUT_DATA_AVAILABLE; |
| } |
| } |
| |
| void SoftAVC::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { |
| if (portIndex != 1) { |
| return; |
| } |
| |
| switch (mOutputPortSettingsChange) { |
| case NONE: |
| break; |
| |
| case AWAITING_DISABLED: |
| { |
| CHECK(!enabled); |
| mOutputPortSettingsChange = AWAITING_ENABLED; |
| break; |
| } |
| |
| default: |
| { |
| CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); |
| CHECK(enabled); |
| mOutputPortSettingsChange = NONE; |
| break; |
| } |
| } |
| } |
| |
| void SoftAVC::updatePortDefinitions() { |
| OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef; |
| def->format.video.nFrameWidth = mWidth; |
| def->format.video.nFrameHeight = mHeight; |
| def->format.video.nStride = def->format.video.nFrameWidth; |
| def->format.video.nSliceHeight = def->format.video.nFrameHeight; |
| |
| def = &editPortInfo(1)->mDef; |
| def->format.video.nFrameWidth = mWidth; |
| def->format.video.nFrameHeight = mHeight; |
| def->format.video.nStride = def->format.video.nFrameWidth; |
| def->format.video.nSliceHeight = def->format.video.nFrameHeight; |
| |
| def->nBufferSize = |
| (def->format.video.nFrameWidth |
| * def->format.video.nFrameHeight * 3) / 2; |
| } |
| |
| // static |
| int32_t SoftAVC::ActivateSPSWrapper( |
| void *userData, unsigned int sizeInMbs, unsigned int numBuffers) { |
| return static_cast<SoftAVC *>(userData)->activateSPS(sizeInMbs, numBuffers); |
| } |
| |
| // static |
| int32_t SoftAVC::BindFrameWrapper( |
| void *userData, int32_t index, uint8_t **yuv) { |
| return static_cast<SoftAVC *>(userData)->bindFrame(index, yuv); |
| } |
| |
| // static |
| void SoftAVC::UnbindFrame(void *userData, int32_t index) { |
| } |
| |
| int32_t SoftAVC::activateSPS( |
| unsigned int sizeInMbs, unsigned int numBuffers) { |
| PortInfo *port = editPortInfo(1); |
| CHECK_GE(port->mBuffers.size(), numBuffers); |
| CHECK_GE(port->mDef.nBufferSize, (sizeInMbs << 7) * 3); |
| |
| return 1; |
| } |
| |
| int32_t SoftAVC::bindFrame(int32_t index, uint8_t **yuv) { |
| PortInfo *port = editPortInfo(1); |
| |
| CHECK_GE(index, 0); |
| CHECK_LT((size_t)index, port->mBuffers.size()); |
| |
| BufferInfo *outBuffer = |
| &port->mBuffers.editItemAt(index); |
| |
| CHECK(outBuffer->mOwnedByUs); |
| |
| outBuffer->mHeader->nTimeStamp = mCurrentTimeUs; |
| *yuv = outBuffer->mHeader->pBuffer; |
| |
| return 1; |
| } |
| |
| } // namespace android |
| |
| android::SoftOMXComponent *createSoftOMXComponent( |
| const char *name, const OMX_CALLBACKTYPE *callbacks, |
| OMX_PTR appData, OMX_COMPONENTTYPE **component) { |
| return new android::SoftAVC(name, callbacks, appData, component); |
| } |