| /* |
| * 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 "SoftMPEG4" |
| #include <utils/Log.h> |
| |
| #include "SoftMPEG4.h" |
| |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <media/IOMX.h> |
| |
| #include "mp4dec_api.h" |
| |
| namespace android { |
| |
| static const CodecProfileLevel kM4VProfileLevels[] = { |
| { OMX_VIDEO_MPEG4ProfileSimple, OMX_VIDEO_MPEG4Level0 }, |
| { OMX_VIDEO_MPEG4ProfileSimple, OMX_VIDEO_MPEG4Level0b }, |
| { OMX_VIDEO_MPEG4ProfileSimple, OMX_VIDEO_MPEG4Level1 }, |
| { OMX_VIDEO_MPEG4ProfileSimple, OMX_VIDEO_MPEG4Level2 }, |
| { OMX_VIDEO_MPEG4ProfileSimple, OMX_VIDEO_MPEG4Level3 }, |
| }; |
| |
| static const CodecProfileLevel kH263ProfileLevels[] = { |
| { OMX_VIDEO_H263ProfileBaseline, OMX_VIDEO_H263Level10 }, |
| { OMX_VIDEO_H263ProfileBaseline, OMX_VIDEO_H263Level20 }, |
| { OMX_VIDEO_H263ProfileBaseline, OMX_VIDEO_H263Level30 }, |
| { OMX_VIDEO_H263ProfileBaseline, OMX_VIDEO_H263Level45 }, |
| { OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level10 }, |
| { OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level20 }, |
| { OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level30 }, |
| { OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level45 }, |
| }; |
| |
| 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; |
| } |
| |
| SoftMPEG4::SoftMPEG4( |
| const char *name, |
| const OMX_CALLBACKTYPE *callbacks, |
| OMX_PTR appData, |
| OMX_COMPONENTTYPE **component) |
| : SimpleSoftOMXComponent(name, callbacks, appData, component), |
| mMode(MODE_MPEG4), |
| mHandle(new tagvideoDecControls), |
| mInputBufferCount(0), |
| mWidth(352), |
| mHeight(288), |
| mCropLeft(0), |
| mCropTop(0), |
| mCropRight(mWidth - 1), |
| mCropBottom(mHeight - 1), |
| mSignalledError(false), |
| mInitialized(false), |
| mFramesConfigured(false), |
| mNumSamplesOutput(0), |
| mOutputPortSettingsChange(NONE) { |
| if (!strcmp(name, "OMX.google.h263.decoder")) { |
| mMode = MODE_H263; |
| } else { |
| CHECK(!strcmp(name, "OMX.google.mpeg4.decoder")); |
| } |
| |
| initPorts(); |
| CHECK_EQ(initDecoder(), (status_t)OK); |
| } |
| |
| SoftMPEG4::~SoftMPEG4() { |
| if (mInitialized) { |
| PVCleanUpVideoDecoder(mHandle); |
| } |
| |
| delete mHandle; |
| mHandle = NULL; |
| } |
| |
| void SoftMPEG4::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 = |
| (mMode == MODE_MPEG4) |
| ? const_cast<char *>(MEDIA_MIMETYPE_VIDEO_MPEG4) |
| : const_cast<char *>(MEDIA_MIMETYPE_VIDEO_H263); |
| |
| 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 = |
| mMode == MODE_MPEG4 ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263; |
| |
| 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 SoftMPEG4::initDecoder() { |
| memset(mHandle, 0, sizeof(tagvideoDecControls)); |
| return OK; |
| } |
| |
| OMX_ERRORTYPE SoftMPEG4::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 = |
| (mMode == MODE_MPEG4) |
| ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263; |
| |
| 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; |
| } |
| |
| case OMX_IndexParamVideoProfileLevelQuerySupported: |
| { |
| OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel = |
| (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params; |
| |
| if (profileLevel->nPortIndex != 0) { // Input port only |
| LOGE("Invalid port index: %ld", profileLevel->nPortIndex); |
| return OMX_ErrorUnsupportedIndex; |
| } |
| |
| size_t index = profileLevel->nProfileIndex; |
| if (mMode == MODE_H263) { |
| size_t nProfileLevels = |
| sizeof(kH263ProfileLevels) / sizeof(kH263ProfileLevels[0]); |
| if (index >= nProfileLevels) { |
| return OMX_ErrorNoMore; |
| } |
| |
| profileLevel->eProfile = kH263ProfileLevels[index].mProfile; |
| profileLevel->eLevel = kH263ProfileLevels[index].mLevel; |
| } else { |
| size_t nProfileLevels = |
| sizeof(kM4VProfileLevels) / sizeof(kM4VProfileLevels[0]); |
| if (index >= nProfileLevels) { |
| return OMX_ErrorNoMore; |
| } |
| |
| profileLevel->eProfile = kM4VProfileLevels[index].mProfile; |
| profileLevel->eLevel = kM4VProfileLevels[index].mLevel; |
| } |
| return OMX_ErrorNone; |
| } |
| |
| default: |
| return SimpleSoftOMXComponent::internalGetParameter(index, params); |
| } |
| } |
| |
| OMX_ERRORTYPE SoftMPEG4::internalSetParameter( |
| OMX_INDEXTYPE index, const OMX_PTR params) { |
| switch (index) { |
| case OMX_IndexParamStandardComponentRole: |
| { |
| const OMX_PARAM_COMPONENTROLETYPE *roleParams = |
| (const OMX_PARAM_COMPONENTROLETYPE *)params; |
| |
| if (mMode == MODE_MPEG4) { |
| if (strncmp((const char *)roleParams->cRole, |
| "video_decoder.mpeg4", |
| OMX_MAX_STRINGNAME_SIZE - 1)) { |
| return OMX_ErrorUndefined; |
| } |
| } else { |
| if (strncmp((const char *)roleParams->cRole, |
| "video_decoder.h263", |
| 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 SoftMPEG4::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; |
| } |
| } |
| |
| void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) { |
| if (mSignalledError || mOutputPortSettingsChange != NONE) { |
| return; |
| } |
| |
| List<BufferInfo *> &inQueue = getPortQueue(0); |
| List<BufferInfo *> &outQueue = getPortQueue(1); |
| |
| while (!inQueue.empty() && outQueue.size() == kNumOutputBuffers) { |
| BufferInfo *inInfo = *inQueue.begin(); |
| OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; |
| |
| PortInfo *port = editPortInfo(1); |
| |
| OMX_BUFFERHEADERTYPE *outHeader = |
| port->mBuffers.editItemAt(mNumSamplesOutput & 1).mHeader; |
| |
| if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { |
| inQueue.erase(inQueue.begin()); |
| inInfo->mOwnedByUs = false; |
| notifyEmptyBufferDone(inHeader); |
| |
| ++mInputBufferCount; |
| |
| outHeader->nFilledLen = 0; |
| outHeader->nFlags = OMX_BUFFERFLAG_EOS; |
| |
| 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; |
| } |
| |
| uint8_t *bitstream = inHeader->pBuffer + inHeader->nOffset; |
| |
| if (!mInitialized) { |
| uint8_t *vol_data[1]; |
| int32_t vol_size = 0; |
| |
| vol_data[0] = NULL; |
| |
| if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { |
| vol_data[0] = bitstream; |
| vol_size = inHeader->nFilledLen; |
| } |
| |
| MP4DecodingMode mode = |
| (mMode == MODE_MPEG4) ? MPEG4_MODE : H263_MODE; |
| |
| Bool success = PVInitVideoDecoder( |
| mHandle, vol_data, &vol_size, 1, mWidth, mHeight, mode); |
| |
| if (!success) { |
| LOGW("PVInitVideoDecoder failed. Unsupported content?"); |
| |
| notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); |
| mSignalledError = true; |
| return; |
| } |
| |
| MP4DecodingMode actualMode = PVGetDecBitstreamMode(mHandle); |
| if (mode != actualMode) { |
| notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); |
| mSignalledError = true; |
| return; |
| } |
| |
| PVSetPostProcType((VideoDecControls *) mHandle, 0); |
| |
| if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { |
| inInfo->mOwnedByUs = false; |
| inQueue.erase(inQueue.begin()); |
| inInfo = NULL; |
| notifyEmptyBufferDone(inHeader); |
| inHeader = NULL; |
| } |
| |
| mInitialized = true; |
| |
| if (mode == MPEG4_MODE && portSettingsChanged()) { |
| return; |
| } |
| |
| continue; |
| } |
| |
| if (!mFramesConfigured) { |
| PortInfo *port = editPortInfo(1); |
| OMX_BUFFERHEADERTYPE *outHeader = port->mBuffers.editItemAt(1).mHeader; |
| |
| PVSetReferenceYUV(mHandle, outHeader->pBuffer); |
| |
| mFramesConfigured = true; |
| } |
| |
| uint32_t useExtTimestamp = (inHeader->nOffset == 0); |
| |
| // decoder deals in ms, OMX in us. |
| uint32_t timestamp = |
| useExtTimestamp ? (inHeader->nTimeStamp + 500) / 1000 : 0xFFFFFFFF; |
| |
| int32_t bufferSize = inHeader->nFilledLen; |
| |
| // The PV decoder is lying to us, sometimes it'll claim to only have |
| // consumed a subset of the buffer when it clearly consumed all of it. |
| // ignore whatever it says... |
| int32_t tmp = bufferSize; |
| |
| if (PVDecodeVideoFrame( |
| mHandle, &bitstream, ×tamp, &tmp, |
| &useExtTimestamp, |
| outHeader->pBuffer) != PV_TRUE) { |
| LOGE("failed to decode video frame."); |
| |
| notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); |
| mSignalledError = true; |
| return; |
| } |
| |
| if (portSettingsChanged()) { |
| return; |
| } |
| |
| // decoder deals in ms, OMX in us. |
| outHeader->nTimeStamp = timestamp * 1000; |
| |
| CHECK_LE(bufferSize, inHeader->nFilledLen); |
| inHeader->nOffset += inHeader->nFilledLen - bufferSize; |
| inHeader->nFilledLen = bufferSize; |
| |
| if (inHeader->nFilledLen == 0) { |
| inInfo->mOwnedByUs = false; |
| inQueue.erase(inQueue.begin()); |
| inInfo = NULL; |
| notifyEmptyBufferDone(inHeader); |
| inHeader = NULL; |
| } |
| |
| ++mInputBufferCount; |
| |
| outHeader->nOffset = 0; |
| outHeader->nFilledLen = (mWidth * mHeight * 3) / 2; |
| outHeader->nFlags = 0; |
| |
| 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; |
| |
| ++mNumSamplesOutput; |
| } |
| } |
| |
| bool SoftMPEG4::portSettingsChanged() { |
| int32_t disp_width, disp_height; |
| PVGetVideoDimensions(mHandle, &disp_width, &disp_height); |
| |
| int32_t buf_width, buf_height; |
| PVGetBufferDimensions(mHandle, &buf_width, &buf_height); |
| |
| CHECK_LE(disp_width, buf_width); |
| CHECK_LE(disp_height, buf_height); |
| |
| LOGV("disp_width = %d, disp_height = %d, buf_width = %d, buf_height = %d", |
| disp_width, disp_height, buf_width, buf_height); |
| |
| if (mCropRight != disp_width - 1 |
| || mCropBottom != disp_height - 1) { |
| mCropLeft = 0; |
| mCropTop = 0; |
| mCropRight = disp_width - 1; |
| mCropBottom = disp_height - 1; |
| |
| notify(OMX_EventPortSettingsChanged, |
| 1, |
| OMX_IndexConfigCommonOutputCrop, |
| NULL); |
| } |
| |
| if (buf_width != mWidth || buf_height != mHeight) { |
| mWidth = buf_width; |
| mHeight = buf_height; |
| |
| updatePortDefinitions(); |
| |
| if (mMode == MODE_H263) { |
| PVCleanUpVideoDecoder(mHandle); |
| |
| uint8_t *vol_data[1]; |
| int32_t vol_size = 0; |
| |
| vol_data[0] = NULL; |
| if (!PVInitVideoDecoder( |
| mHandle, vol_data, &vol_size, 1, mWidth, mHeight, |
| H263_MODE)) { |
| notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); |
| mSignalledError = true; |
| return true; |
| } |
| } |
| |
| mFramesConfigured = false; |
| |
| notify(OMX_EventPortSettingsChanged, 1, 0, NULL); |
| mOutputPortSettingsChange = AWAITING_DISABLED; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void SoftMPEG4::onPortFlushCompleted(OMX_U32 portIndex) { |
| if (portIndex == 0 && mInitialized) { |
| CHECK_EQ((int)PVResetVideoDecoder(mHandle), (int)PV_TRUE); |
| } |
| } |
| |
| void SoftMPEG4::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 SoftMPEG4::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 + 15) & -16) |
| * ((def->format.video.nFrameHeight + 15) & -16) * 3) / 2; |
| } |
| |
| } // namespace android |
| |
| android::SoftOMXComponent *createSoftOMXComponent( |
| const char *name, const OMX_CALLBACKTYPE *callbacks, |
| OMX_PTR appData, OMX_COMPONENTTYPE **component) { |
| return new android::SoftMPEG4(name, callbacks, appData, component); |
| } |
| |