| /* |
| * 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 }, |
| }; |
| |
| SoftMPEG4::SoftMPEG4( |
| const char *name, |
| const char *componentRole, |
| OMX_VIDEO_CODINGTYPE codingType, |
| const CodecProfileLevel *profileLevels, |
| size_t numProfileLevels, |
| const OMX_CALLBACKTYPE *callbacks, |
| OMX_PTR appData, |
| OMX_COMPONENTTYPE **component) |
| : SoftVideoDecoderOMXComponent( |
| name, componentRole, codingType, profileLevels, numProfileLevels, |
| 352 /* width */, 288 /* height */, callbacks, appData, component), |
| mMode(codingType == OMX_VIDEO_CodingH263 ? MODE_H263 : MODE_MPEG4), |
| mHandle(new tagvideoDecControls), |
| mInputBufferCount(0), |
| mSignalledError(false), |
| mInitialized(false), |
| mFramesConfigured(false), |
| mNumSamplesOutput(0), |
| mPvTime(0) { |
| initPorts( |
| kNumInputBuffers, |
| 8192 /* inputBufferSize */, |
| kNumOutputBuffers, |
| (mMode == MODE_MPEG4) |
| ? MEDIA_MIMETYPE_VIDEO_MPEG4 : MEDIA_MIMETYPE_VIDEO_H263); |
| CHECK_EQ(initDecoder(), (status_t)OK); |
| } |
| |
| SoftMPEG4::~SoftMPEG4() { |
| if (mInitialized) { |
| PVCleanUpVideoDecoder(mHandle); |
| } |
| |
| delete mHandle; |
| mHandle = NULL; |
| } |
| |
| status_t SoftMPEG4::initDecoder() { |
| memset(mHandle, 0, sizeof(tagvideoDecControls)); |
| return OK; |
| } |
| |
| 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) && inHeader->nFilledLen == 0) { |
| 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) { |
| ALOGW("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 (int32_t), OMX in us (int64_t) |
| // so use fake timestamp instead |
| uint32_t timestamp = 0xFFFFFFFF; |
| if (useExtTimestamp) { |
| mPvToOmxTimeMap.add(mPvTime, inHeader->nTimeStamp); |
| timestamp = mPvTime; |
| mPvTime++; |
| } |
| |
| int32_t bufferSize = inHeader->nFilledLen; |
| int32_t tmp = bufferSize; |
| |
| // 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... |
| if (PVDecodeVideoFrame( |
| mHandle, &bitstream, ×tamp, &tmp, |
| &useExtTimestamp, |
| outHeader->pBuffer) != PV_TRUE) { |
| ALOGE("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 = mPvToOmxTimeMap.valueFor(timestamp); |
| mPvToOmxTimeMap.removeItem(timestamp); |
| |
| inHeader->nOffset += bufferSize; |
| inHeader->nFilledLen = 0; |
| if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { |
| outHeader->nFlags = OMX_BUFFERFLAG_EOS; |
| } else { |
| outHeader->nFlags = 0; |
| } |
| |
| 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; |
| |
| 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() { |
| uint32_t disp_width, disp_height; |
| PVGetVideoDimensions(mHandle, (int32 *)&disp_width, (int32 *)&disp_height); |
| |
| uint32_t buf_width, buf_height; |
| PVGetBufferDimensions(mHandle, (int32 *)&buf_width, (int32 *)&buf_height); |
| |
| CHECK_LE(disp_width, buf_width); |
| CHECK_LE(disp_height, buf_height); |
| |
| ALOGV("disp_width = %d, disp_height = %d, buf_width = %d, buf_height = %d", |
| disp_width, disp_height, buf_width, buf_height); |
| |
| if (mCropWidth != disp_width |
| || mCropHeight != disp_height) { |
| mCropLeft = 0; |
| mCropTop = 0; |
| mCropWidth = disp_width; |
| mCropHeight = disp_height; |
| |
| 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::onReset() { |
| SoftVideoDecoderOMXComponent::onReset(); |
| mPvToOmxTimeMap.clear(); |
| mSignalledError = false; |
| mFramesConfigured = false; |
| if (mInitialized) { |
| PVCleanUpVideoDecoder(mHandle); |
| mInitialized = false; |
| } |
| } |
| |
| void SoftMPEG4::updatePortDefinitions() { |
| SoftVideoDecoderOMXComponent::updatePortDefinitions(); |
| |
| /* We have to align our width and height - this should affect stride! */ |
| OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef; |
| 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) { |
| using namespace android; |
| if (!strcmp(name, "OMX.google.h263.decoder")) { |
| return new android::SoftMPEG4( |
| name, "video_decoder.h263", OMX_VIDEO_CodingH263, |
| kH263ProfileLevels, ARRAY_SIZE(kH263ProfileLevels), |
| callbacks, appData, component); |
| } else if (!strcmp(name, "OMX.google.mpeg4.decoder")) { |
| return new android::SoftMPEG4( |
| name, "video_decoder.mpeg4", OMX_VIDEO_CodingMPEG4, |
| kM4VProfileLevels, ARRAY_SIZE(kM4VProfileLevels), |
| callbacks, appData, component); |
| } else { |
| CHECK(!"Unknown component"); |
| } |
| } |
| |