/* * 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 #include "SoftMPEG4.h" #include #include #include #include #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 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(MEDIA_MIMETYPE_VIDEO_MPEG4) : const_cast(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(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 &inQueue = getPortQueue(0); List &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::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; if (PVDecodeVideoFrame( mHandle, &bitstream, ×tamp, &bufferSize, &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::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); }