blob: 411a251d150717ad633d3e7362ef65d735c8c536 [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/foundation/AUtils.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,
352 * 288 * 3 / 2 /* minInputBufferSize */,
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;
if (inHeader == NULL) {
inQueue.erase(inQueue.begin());
inInfo->mOwnedByUs = false;
continue;
}
PortInfo *port = editPortInfo(1);
OMX_BUFFERHEADERTYPE *outHeader =
port->mBuffers.editItemAt(mNumSamplesOutput & 1).mHeader;
if (inHeader->nFilledLen == 0) {
inQueue.erase(inQueue.begin());
inInfo->mOwnedByUs = false;
notifyEmptyBufferDone(inHeader);
++mInputBufferCount;
if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
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;
uint32_t *start_code = (uint32_t *)bitstream;
bool volHeader = *start_code == 0xB0010000;
if (volHeader) {
PVCleanUpVideoDecoder(mHandle);
mInitialized = false;
}
if (!mInitialized) {
uint8_t *vol_data[1];
int32_t vol_size = 0;
vol_data[0] = NULL;
if ((inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) || volHeader) {
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,
outputBufferWidth(), outputBufferHeight(), 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);
bool hasFrameData = false;
if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) {
inInfo->mOwnedByUs = false;
inQueue.erase(inQueue.begin());
inInfo = NULL;
notifyEmptyBufferDone(inHeader);
inHeader = NULL;
} else if (volHeader) {
hasFrameData = true;
}
mInitialized = true;
if (mode == MPEG4_MODE && handlePortSettingsChange()) {
return;
}
if (!hasFrameData) {
continue;
}
}
if (!mFramesConfigured) {
PortInfo *port = editPortInfo(1);
OMX_BUFFERHEADERTYPE *outHeader = port->mBuffers.editItemAt(1).mHeader;
OMX_U32 yFrameSize = sizeof(uint8) * mHandle->size;
if ((outHeader->nAllocLen < yFrameSize) ||
(outHeader->nAllocLen - yFrameSize < yFrameSize / 2)) {
ALOGE("Too small output buffer for reference frame: %lu bytes",
(unsigned long)outHeader->nAllocLen);
android_errorWriteLog(0x534e4554, "30033990");
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return;
}
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;
OMX_U32 frameSize;
OMX_U64 yFrameSize = (OMX_U64)mWidth * (OMX_U64)mHeight;
if (yFrameSize > ((OMX_U64)UINT32_MAX / 3) * 2) {
ALOGE("Frame size too large");
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return;
}
frameSize = (OMX_U32)(yFrameSize + (yFrameSize / 2));
if (outHeader->nAllocLen < frameSize) {
android_errorWriteLog(0x534e4554, "27833616");
ALOGE("Insufficient output buffer size");
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return;
}
// Need to check if header contains new info, e.g., width/height, etc.
VopHeaderInfo header_info;
uint8_t *bitstreamTmp = bitstream;
if (PVDecodeVopHeader(
mHandle, &bitstreamTmp, &timestamp, &tmp,
&header_info, &useExtTimestamp,
outHeader->pBuffer) != PV_TRUE) {
ALOGE("failed to decode vop header.");
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return;
}
if (handlePortSettingsChange()) {
return;
}
// 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 (PVDecodeVopBody(mHandle, &tmp) != PV_TRUE) {
ALOGE("failed to decode video frame.");
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return;
}
// H263 doesn't have VOL header, the frame size information is in short header, i.e. the
// decoder may detect size change after PVDecodeVideoFrame.
if (handlePortSettingsChange()) {
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 = frameSize;
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::handlePortSettingsChange() {
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);
CropSettingsMode cropSettingsMode = kCropUnSet;
if (disp_width != buf_width || disp_height != buf_height) {
cropSettingsMode = kCropSet;
if (mCropWidth != disp_width || mCropHeight != disp_height) {
mCropLeft = 0;
mCropTop = 0;
mCropWidth = disp_width;
mCropHeight = disp_height;
cropSettingsMode = kCropChanged;
}
}
bool portWillReset = false;
const bool fakeStride = true;
SoftVideoDecoderOMXComponent::handlePortSettingsChange(
&portWillReset, buf_width, buf_height, cropSettingsMode, fakeStride);
if (portWillReset) {
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, outputBufferWidth(), outputBufferHeight(),
H263_MODE)) {
notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
mSignalledError = true;
return true;
}
}
mFramesConfigured = false;
}
return portWillReset;
}
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(bool updateCrop, bool updateInputSize) {
SoftVideoDecoderOMXComponent::updatePortDefinitions(updateCrop, updateInputSize);
/* We have to align our width and height - this should affect stride! */
OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef;
def->format.video.nStride = align(def->format.video.nStride, 16);
def->format.video.nSliceHeight = align(def->format.video.nSliceHeight, 16);
def->nBufferSize = (def->format.video.nStride * def->format.video.nSliceHeight * 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");
}
return NULL;
}