blob: aa07e5793a7f2dae4cc535d5c6987f79f39330dc [file] [log] [blame]
/*
* 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, &timestamp, &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);
}