Software MPEG4/H263 video encoder is now OMX-based

o related-to-bug: 6401068

Change-Id: If8eccea060f38e42ad31eb6e91aaa832e67c5559
diff --git a/media/libstagefright/codecs/m4v_h263/enc/Android.mk b/media/libstagefright/codecs/m4v_h263/enc/Android.mk
index 7cbb38f..e6aa563 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/Android.mk
+++ b/media/libstagefright/codecs/m4v_h263/enc/Android.mk
@@ -3,6 +3,7 @@
 
 LOCAL_SRC_FILES := \
     M4vH263Encoder.cpp \
+    SoftMPEG4Encoder.cpp \
     src/bitstream_io.cpp \
     src/combined_encode.cpp \
     src/datapart_encode.cpp \
@@ -35,3 +36,39 @@
     $(TOP)/frameworks/native/include/media/openmax
 
 include $(BUILD_STATIC_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+        SoftMPEG4Encoder.cpp
+
+LOCAL_C_INCLUDES := \
+        frameworks/av/media/libstagefright/include \
+        frameworks/native/include/media/openmax \
+        $(LOCAL_PATH)/src \
+        $(LOCAL_PATH)/include \
+        $(LOCAL_PATH)/../common/include \
+        $(LOCAL_PATH)/../common
+
+LOCAL_CFLAGS := \
+    -DBX_RC \
+    -DOSCL_IMPORT_REF= -DOSCL_UNUSED_ARG= -DOSCL_EXPORT_REF=
+
+
+LOCAL_STATIC_LIBRARIES := \
+        libstagefright_m4vh263enc
+
+LOCAL_SHARED_LIBRARIES := \
+        libstagefright \
+        libstagefright_enc_common \
+        libstagefright_foundation \
+        libstagefright_omx \
+        libutils \
+
+
+LOCAL_MODULE := libstagefright_soft_mpeg4enc
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp
new file mode 100644
index 0000000..a5a2332
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) 2012 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 "SoftMPEG4Encoder"
+#include <utils/Log.h>
+
+#include "mp4enc_api.h"
+#include "OMX_Video.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+#include "SoftMPEG4Encoder.h"
+
+namespace android {
+
+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;
+}
+
+inline static void ConvertYUV420SemiPlanarToYUV420Planar(
+        uint8_t *inyuv, uint8_t* outyuv,
+        int32_t width, int32_t height) {
+
+    int32_t outYsize = width * height;
+    uint32_t *outy =  (uint32_t *) outyuv;
+    uint16_t *outcb = (uint16_t *) (outyuv + outYsize);
+    uint16_t *outcr = (uint16_t *) (outyuv + outYsize + (outYsize >> 2));
+
+    /* Y copying */
+    memcpy(outy, inyuv, outYsize);
+
+    /* U & V copying */
+    uint32_t *inyuv_4 = (uint32_t *) (inyuv + outYsize);
+    for (int32_t i = height >> 1; i > 0; --i) {
+        for (int32_t j = width >> 2; j > 0; --j) {
+            uint32_t temp = *inyuv_4++;
+            uint32_t tempU = temp & 0xFF;
+            tempU = tempU | ((temp >> 8) & 0xFF00);
+
+            uint32_t tempV = (temp >> 8) & 0xFF;
+            tempV = tempV | ((temp >> 16) & 0xFF00);
+
+            // Flip U and V
+            *outcb++ = tempV;
+            *outcr++ = tempU;
+        }
+    }
+}
+
+SoftMPEG4Encoder::SoftMPEG4Encoder(
+            const char *name,
+            const OMX_CALLBACKTYPE *callbacks,
+            OMX_PTR appData,
+            OMX_COMPONENTTYPE **component)
+    : SimpleSoftOMXComponent(name, callbacks, appData, component),
+      mEncodeMode(COMBINE_MODE_WITH_ERR_RES),
+      mVideoWidth(176),
+      mVideoHeight(144),
+      mVideoFrameRate(30),
+      mVideoBitRate(192000),
+      mVideoColorFormat(OMX_COLOR_FormatYUV420Planar),
+      mIDRFrameRefreshIntervalInSec(1),
+      mNumInputFrames(-1),
+      mStarted(false),
+      mSawInputEOS(false),
+      mSignalledError(false),
+      mHandle(new tagvideoEncControls),
+      mEncParams(new tagvideoEncOptions),
+      mInputFrameData(NULL) {
+
+   if (!strcmp(name, "OMX.google.h263.encoder")) {
+        mEncodeMode = H263_MODE;
+    } else {
+        CHECK(!strcmp(name, "OMX.google.mpeg4.encoder"));
+    }
+
+    initPorts();
+    ALOGI("Construct SoftMPEG4Encoder");
+}
+
+SoftMPEG4Encoder::~SoftMPEG4Encoder() {
+    ALOGV("Destruct SoftMPEG4Encoder");
+    releaseEncoder();
+    List<BufferInfo *> &outQueue = getPortQueue(1);
+    List<BufferInfo *> &inQueue = getPortQueue(0);
+    CHECK(outQueue.empty());
+    CHECK(inQueue.empty());
+}
+
+OMX_ERRORTYPE SoftMPEG4Encoder::initEncParams() {
+    CHECK(mHandle != NULL);
+    memset(mHandle, 0, sizeof(tagvideoEncControls));
+
+    CHECK(mEncParams != NULL);
+    memset(mEncParams, 0, sizeof(tagvideoEncOptions));
+    if (!PVGetDefaultEncOption(mEncParams, 0)) {
+        ALOGE("Failed to get default encoding parameters");
+        return OMX_ErrorUndefined;
+    }
+    mEncParams->encMode = mEncodeMode;
+    mEncParams->encWidth[0] = mVideoWidth;
+    mEncParams->encHeight[0] = mVideoHeight;
+    mEncParams->encFrameRate[0] = mVideoFrameRate;
+    mEncParams->rcType = VBR_1;
+    mEncParams->vbvDelay = 5.0f;
+
+    // FIXME:
+    // Add more profile and level support for MPEG4 encoder
+    mEncParams->profile_level = CORE_PROFILE_LEVEL2;
+    mEncParams->packetSize = 32;
+    mEncParams->rvlcEnable = PV_OFF;
+    mEncParams->numLayers = 1;
+    mEncParams->timeIncRes = 1000;
+    mEncParams->tickPerSrc = mEncParams->timeIncRes / mVideoFrameRate;
+
+    mEncParams->bitRate[0] = mVideoBitRate;
+    mEncParams->iQuant[0] = 15;
+    mEncParams->pQuant[0] = 12;
+    mEncParams->quantType[0] = 0;
+    mEncParams->noFrameSkipped = PV_OFF;
+
+    if (mVideoColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
+        // Color conversion is needed.
+        CHECK(mInputFrameData == NULL);
+        mInputFrameData =
+            (uint8_t *) malloc((mVideoWidth * mVideoHeight * 3 ) >> 1);
+        CHECK(mInputFrameData != NULL);
+    }
+
+    // PV's MPEG4 encoder requires the video dimension of multiple
+    if (mVideoWidth % 16 != 0 || mVideoHeight % 16 != 0) {
+        ALOGE("Video frame size %dx%d must be a multiple of 16",
+            mVideoWidth, mVideoHeight);
+        return OMX_ErrorBadParameter;
+    }
+
+    // Set IDR frame refresh interval
+    if (mIDRFrameRefreshIntervalInSec < 0) {
+        mEncParams->intraPeriod = -1;
+    } else if (mIDRFrameRefreshIntervalInSec == 0) {
+        mEncParams->intraPeriod = 1;  // All I frames
+    } else {
+        mEncParams->intraPeriod =
+            (mIDRFrameRefreshIntervalInSec * mVideoFrameRate);
+    }
+
+    mEncParams->numIntraMB = 0;
+    mEncParams->sceneDetect = PV_ON;
+    mEncParams->searchRange = 16;
+    mEncParams->mv8x8Enable = PV_OFF;
+    mEncParams->gobHeaderInterval = 0;
+    mEncParams->useACPred = PV_ON;
+    mEncParams->intraDCVlcTh = 0;
+
+    return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE SoftMPEG4Encoder::initEncoder() {
+    CHECK(!mStarted);
+
+    OMX_ERRORTYPE errType = OMX_ErrorNone;
+    if (OMX_ErrorNone != (errType = initEncParams())) {
+        ALOGE("Failed to initialized encoder params");
+        mSignalledError = true;
+        notify(OMX_EventError, OMX_ErrorUndefined, 0, 0);
+        return errType;
+    }
+
+    if (!PVInitVideoEncoder(mHandle, mEncParams)) {
+        ALOGE("Failed to initialize the encoder");
+        mSignalledError = true;
+        notify(OMX_EventError, OMX_ErrorUndefined, 0, 0);
+        return OMX_ErrorUndefined;
+    }
+
+    mNumInputFrames = -1;  // 1st buffer for codec specific data
+    mStarted = true;
+
+    return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE SoftMPEG4Encoder::releaseEncoder() {
+    if (!mStarted) {
+        return OMX_ErrorNone;
+    }
+
+    PVCleanUpVideoEncoder(mHandle);
+
+    delete mInputFrameData;
+    mInputFrameData = NULL;
+
+    delete mEncParams;
+    mEncParams = NULL;
+
+    delete mHandle;
+    mHandle = NULL;
+
+    mStarted = false;
+
+    return OMX_ErrorNone;
+}
+
+void SoftMPEG4Encoder::initPorts() {
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    InitOMXParams(&def);
+
+    const size_t kInputBufferSize = (mVideoWidth * mVideoHeight * 3) >> 1;
+
+    // 256 * 1024 is a magic number for PV's encoder, not sure why
+    const size_t kOutputBufferSize =
+        (kInputBufferSize > 256 * 1024)
+            ? kInputBufferSize: 256 * 1024;
+
+    def.nPortIndex = 0;
+    def.eDir = OMX_DirInput;
+    def.nBufferCountMin = kNumBuffers;
+    def.nBufferCountActual = def.nBufferCountMin;
+    def.nBufferSize = kInputBufferSize;
+    def.bEnabled = OMX_TRUE;
+    def.bPopulated = OMX_FALSE;
+    def.eDomain = OMX_PortDomainVideo;
+    def.bBuffersContiguous = OMX_FALSE;
+    def.nBufferAlignment = 1;
+
+    def.format.video.cMIMEType = const_cast<char *>("video/raw");
+
+    def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
+    def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
+    def.format.video.xFramerate = (mVideoFrameRate << 16);  // Q16 format
+    def.format.video.nBitrate = mVideoBitRate;
+    def.format.video.nFrameWidth = mVideoWidth;
+    def.format.video.nFrameHeight = mVideoHeight;
+    def.format.video.nStride = mVideoWidth;
+    def.format.video.nSliceHeight = mVideoHeight;
+
+    addPort(def);
+
+    def.nPortIndex = 1;
+    def.eDir = OMX_DirOutput;
+    def.nBufferCountMin = kNumBuffers;
+    def.nBufferCountActual = def.nBufferCountMin;
+    def.nBufferSize = kOutputBufferSize;
+    def.bEnabled = OMX_TRUE;
+    def.bPopulated = OMX_FALSE;
+    def.eDomain = OMX_PortDomainVideo;
+    def.bBuffersContiguous = OMX_FALSE;
+    def.nBufferAlignment = 2;
+
+    def.format.video.cMIMEType =
+        (mEncodeMode == COMBINE_MODE_WITH_ERR_RES)
+            ? const_cast<char *>(MEDIA_MIMETYPE_VIDEO_MPEG4)
+            : const_cast<char *>(MEDIA_MIMETYPE_VIDEO_H263);
+
+    def.format.video.eCompressionFormat =
+        (mEncodeMode == COMBINE_MODE_WITH_ERR_RES)
+            ? OMX_VIDEO_CodingMPEG4
+            : OMX_VIDEO_CodingH263;
+
+    def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
+    def.format.video.xFramerate = (0 << 16);  // Q16 format
+    def.format.video.nBitrate = mVideoBitRate;
+    def.format.video.nFrameWidth = mVideoWidth;
+    def.format.video.nFrameHeight = mVideoHeight;
+    def.format.video.nStride = mVideoWidth;
+    def.format.video.nSliceHeight = mVideoHeight;
+
+    addPort(def);
+}
+
+OMX_ERRORTYPE SoftMPEG4Encoder::internalGetParameter(
+        OMX_INDEXTYPE index, OMX_PTR params) {
+    switch (index) {
+        case OMX_IndexParamVideoErrorCorrection:
+        {
+            return OMX_ErrorNotImplemented;
+        }
+
+        case OMX_IndexParamVideoBitrate:
+        {
+            OMX_VIDEO_PARAM_BITRATETYPE *bitRate =
+                (OMX_VIDEO_PARAM_BITRATETYPE *) params;
+
+            if (bitRate->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            bitRate->eControlRate = OMX_Video_ControlRateVariable;
+            bitRate->nTargetBitrate = mVideoBitRate;
+            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 > 1) {
+                return OMX_ErrorNoMore;
+            }
+
+            if (formatParams->nPortIndex == 0) {
+                formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
+                if (formatParams->nIndex == 0) {
+                    formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
+                } else {
+                    formatParams->eColorFormat = OMX_COLOR_FormatYUV420SemiPlanar;
+                }
+            } else {
+                formatParams->eCompressionFormat =
+                    (mEncodeMode == COMBINE_MODE_WITH_ERR_RES)
+                        ? OMX_VIDEO_CodingMPEG4
+                        : OMX_VIDEO_CodingH263;
+
+                formatParams->eColorFormat = OMX_COLOR_FormatUnused;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoH263:
+        {
+            OMX_VIDEO_PARAM_H263TYPE *h263type =
+                (OMX_VIDEO_PARAM_H263TYPE *)params;
+
+            if (h263type->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            h263type->nAllowedPictureTypes =
+                (OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP);
+            h263type->eProfile = OMX_VIDEO_H263ProfileBaseline;
+            h263type->eLevel = OMX_VIDEO_H263Level45;
+            h263type->bPLUSPTYPEAllowed = OMX_FALSE;
+            h263type->bForceRoundingTypeToZero = OMX_FALSE;
+            h263type->nPictureHeaderRepetition = 0;
+            h263type->nGOBHeaderInterval = 0;
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoMpeg4:
+        {
+            OMX_VIDEO_PARAM_MPEG4TYPE *mpeg4type =
+                (OMX_VIDEO_PARAM_MPEG4TYPE *)params;
+
+            if (mpeg4type->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            mpeg4type->eProfile = OMX_VIDEO_MPEG4ProfileCore;
+            mpeg4type->eLevel = OMX_VIDEO_MPEG4Level2;
+            mpeg4type->nAllowedPictureTypes =
+                (OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP);
+            mpeg4type->nBFrames = 0;
+            mpeg4type->nIDCVLCThreshold = 0;
+            mpeg4type->bACPred = OMX_TRUE;
+            mpeg4type->nMaxPacketSize = 256;
+            mpeg4type->nTimeIncRes = 1000;
+            mpeg4type->nHeaderExtension = 0;
+            mpeg4type->bReversibleVLC = OMX_FALSE;
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoProfileLevelQuerySupported:
+        {
+            OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel =
+                (OMX_VIDEO_PARAM_PROFILELEVELTYPE *)params;
+
+            if (profileLevel->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            if (profileLevel->nProfileIndex > 0) {
+                return OMX_ErrorNoMore;
+            }
+
+            if (mEncodeMode == H263_MODE) {
+                profileLevel->eProfile = OMX_VIDEO_H263ProfileBaseline;
+                profileLevel->eLevel = OMX_VIDEO_H263Level45;
+            } else {
+                profileLevel->eProfile = OMX_VIDEO_MPEG4ProfileCore;
+                profileLevel->eLevel = OMX_VIDEO_MPEG4Level2;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        default:
+            return SimpleSoftOMXComponent::internalGetParameter(index, params);
+    }
+}
+
+OMX_ERRORTYPE SoftMPEG4Encoder::internalSetParameter(
+        OMX_INDEXTYPE index, const OMX_PTR params) {
+    switch (index) {
+        case OMX_IndexParamVideoErrorCorrection:
+        {
+            return OMX_ErrorNotImplemented;
+        }
+
+        case OMX_IndexParamVideoBitrate:
+        {
+            OMX_VIDEO_PARAM_BITRATETYPE *bitRate =
+                (OMX_VIDEO_PARAM_BITRATETYPE *) params;
+
+            if (bitRate->nPortIndex != 1 ||
+                bitRate->eControlRate != OMX_Video_ControlRateVariable) {
+                return OMX_ErrorUndefined;
+            }
+
+            mVideoBitRate = bitRate->nTargetBitrate;
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamPortDefinition:
+        {
+            OMX_PARAM_PORTDEFINITIONTYPE *def =
+                (OMX_PARAM_PORTDEFINITIONTYPE *)params;
+            if (def->nPortIndex > 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            if (def->nPortIndex == 0) {
+                if (def->format.video.eCompressionFormat != OMX_VIDEO_CodingUnused ||
+                    (def->format.video.eColorFormat != OMX_COLOR_FormatYUV420Planar &&
+                     def->format.video.eColorFormat != OMX_COLOR_FormatYUV420SemiPlanar)) {
+                    return OMX_ErrorUndefined;
+                }
+            } else {
+                if ((mEncodeMode == COMBINE_MODE_WITH_ERR_RES &&
+                        def->format.video.eCompressionFormat != OMX_VIDEO_CodingMPEG4) ||
+                    (mEncodeMode == H263_MODE &&
+                        def->format.video.eCompressionFormat != OMX_VIDEO_CodingH263) ||
+                    (def->format.video.eColorFormat != OMX_COLOR_FormatUnused)) {
+                    return OMX_ErrorUndefined;
+                }
+            }
+
+            OMX_ERRORTYPE err = SimpleSoftOMXComponent::internalSetParameter(index, params);
+            if (OMX_ErrorNone != err) {
+                return err;
+            }
+
+            if (def->nPortIndex == 0) {
+                mVideoWidth = def->format.video.nFrameWidth;
+                mVideoHeight = def->format.video.nFrameHeight;
+                mVideoFrameRate = def->format.video.xFramerate >> 16;
+                mVideoColorFormat = def->format.video.eColorFormat;
+            } else {
+                mVideoBitRate = def->format.video.nBitrate;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamStandardComponentRole:
+        {
+            const OMX_PARAM_COMPONENTROLETYPE *roleParams =
+                (const OMX_PARAM_COMPONENTROLETYPE *)params;
+
+            if (strncmp((const char *)roleParams->cRole,
+                        (mEncodeMode == H263_MODE)
+                            ? "video_encoder.h263": "video_encoder.mpeg4",
+                        OMX_MAX_STRINGNAME_SIZE - 1)) {
+                return OMX_ErrorUndefined;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoPortFormat:
+        {
+            const OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
+                (const OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
+
+            if (formatParams->nPortIndex > 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            if (formatParams->nIndex > 1) {
+                return OMX_ErrorNoMore;
+            }
+
+            if (formatParams->nPortIndex == 0) {
+                if (formatParams->eCompressionFormat != OMX_VIDEO_CodingUnused ||
+                    ((formatParams->nIndex == 0 &&
+                      formatParams->eColorFormat != OMX_COLOR_FormatYUV420Planar) ||
+                    (formatParams->nIndex == 1 &&
+                     formatParams->eColorFormat != OMX_COLOR_FormatYUV420SemiPlanar))) {
+                    return OMX_ErrorUndefined;
+                }
+                mVideoColorFormat = formatParams->eColorFormat;
+            } else {
+                if ((mEncodeMode == H263_MODE &&
+                        formatParams->eCompressionFormat != OMX_VIDEO_CodingH263) ||
+                    (mEncodeMode == COMBINE_MODE_WITH_ERR_RES &&
+                        formatParams->eCompressionFormat != OMX_VIDEO_CodingMPEG4) ||
+                    formatParams->eColorFormat != OMX_COLOR_FormatUnused) {
+                    return OMX_ErrorUndefined;
+                }
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoH263:
+        {
+            OMX_VIDEO_PARAM_H263TYPE *h263type =
+                (OMX_VIDEO_PARAM_H263TYPE *)params;
+
+            if (h263type->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            if (h263type->eProfile != OMX_VIDEO_H263ProfileBaseline ||
+                h263type->eLevel != OMX_VIDEO_H263Level45 ||
+                (h263type->nAllowedPictureTypes & OMX_VIDEO_PictureTypeB) ||
+                h263type->bPLUSPTYPEAllowed != OMX_FALSE ||
+                h263type->bForceRoundingTypeToZero != OMX_FALSE ||
+                h263type->nPictureHeaderRepetition != 0 ||
+                h263type->nGOBHeaderInterval != 0) {
+                return OMX_ErrorUndefined;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        case OMX_IndexParamVideoMpeg4:
+        {
+            OMX_VIDEO_PARAM_MPEG4TYPE *mpeg4type =
+                (OMX_VIDEO_PARAM_MPEG4TYPE *)params;
+
+            if (mpeg4type->nPortIndex != 1) {
+                return OMX_ErrorUndefined;
+            }
+
+            if (mpeg4type->eProfile != OMX_VIDEO_MPEG4ProfileCore ||
+                mpeg4type->eLevel != OMX_VIDEO_MPEG4Level2 ||
+                (mpeg4type->nAllowedPictureTypes & OMX_VIDEO_PictureTypeB) ||
+                mpeg4type->nBFrames != 0 ||
+                mpeg4type->nIDCVLCThreshold != 0 ||
+                mpeg4type->bACPred != OMX_TRUE ||
+                mpeg4type->nMaxPacketSize != 256 ||
+                mpeg4type->nTimeIncRes != 1000 ||
+                mpeg4type->nHeaderExtension != 0 ||
+                mpeg4type->bReversibleVLC != OMX_FALSE) {
+                return OMX_ErrorUndefined;
+            }
+
+            return OMX_ErrorNone;
+        }
+
+        default:
+            return SimpleSoftOMXComponent::internalSetParameter(index, params);
+    }
+}
+
+void SoftMPEG4Encoder::onQueueFilled(OMX_U32 portIndex) {
+    if (mSignalledError || mSawInputEOS) {
+        return;
+    }
+
+    if (!mStarted) {
+        if (OMX_ErrorNone != initEncoder()) {
+            return;
+        }
+    }
+
+    List<BufferInfo *> &inQueue = getPortQueue(0);
+    List<BufferInfo *> &outQueue = getPortQueue(1);
+
+    while (!mSawInputEOS && !inQueue.empty() && !outQueue.empty()) {
+        BufferInfo *inInfo = *inQueue.begin();
+        OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
+        BufferInfo *outInfo = *outQueue.begin();
+        OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
+
+        outHeader->nTimeStamp = 0;
+        outHeader->nFlags = 0;
+        outHeader->nOffset = 0;
+        outHeader->nFilledLen = 0;
+        outHeader->nOffset = 0;
+
+        uint8_t *outPtr = (uint8_t *) outHeader->pBuffer;
+        int32_t dataLength = outHeader->nAllocLen;
+
+        if (mNumInputFrames < 0) {
+            if (!PVGetVolHeader(mHandle, outPtr, &dataLength, 0)) {
+                ALOGE("Failed to get VOL header");
+                mSignalledError = true;
+                notify(OMX_EventError, OMX_ErrorUndefined, 0, 0);
+                return;
+            }
+            ALOGV("Output VOL header: %d bytes", dataLength);
+            ++mNumInputFrames;
+            outHeader->nFlags |= OMX_BUFFERFLAG_CODECCONFIG;
+            outHeader->nFilledLen = dataLength;
+            outQueue.erase(outQueue.begin());
+            outInfo->mOwnedByUs = false;
+            notifyFillBufferDone(outHeader);
+            return;
+        }
+
+        // Save the input buffer info so that it can be
+        // passed to an output buffer
+        InputBufferInfo info;
+        info.mTimeUs = inHeader->nTimeStamp;
+        info.mFlags = inHeader->nFlags;
+        mInputBufferInfoVec.push(info);
+
+        if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
+            mSawInputEOS = true;
+        }
+
+        if (inHeader->nFilledLen > 0) {
+            const void *inData = inHeader->pBuffer + inHeader->nOffset;
+            uint8_t *inputData = (uint8_t *) inData;
+            if (mVideoColorFormat != OMX_COLOR_FormatYUV420Planar) {
+                ConvertYUV420SemiPlanarToYUV420Planar(
+                    inputData, mInputFrameData, mVideoWidth, mVideoHeight);
+                inputData = mInputFrameData;
+            }
+            CHECK(inputData != NULL);
+
+            VideoEncFrameIO vin, vout;
+            memset(&vin, 0, sizeof(vin));
+            memset(&vout, 0, sizeof(vout));
+            vin.height = ((mVideoHeight  + 15) >> 4) << 4;
+            vin.pitch = ((mVideoWidth + 15) >> 4) << 4;
+            vin.timestamp = (inHeader->nTimeStamp + 500) / 1000;  // in ms
+            vin.yChan = inputData;
+            vin.uChan = vin.yChan + vin.height * vin.pitch;
+            vin.vChan = vin.uChan + ((vin.height * vin.pitch) >> 2);
+
+            unsigned long modTimeMs = 0;
+            int32_t nLayer = 0;
+            MP4HintTrack hintTrack;
+            if (!PVEncodeVideoFrame(mHandle, &vin, &vout,
+                    &modTimeMs, outPtr, &dataLength, &nLayer) ||
+                !PVGetHintTrack(mHandle, &hintTrack)) {
+                ALOGE("Failed to encode frame or get hink track at frame %lld",
+                    mNumInputFrames);
+                mSignalledError = true;
+                notify(OMX_EventError, OMX_ErrorUndefined, 0, 0);
+            }
+            CHECK(NULL == PVGetOverrunBuffer(mHandle));
+            if (hintTrack.CodeType == 0) {  // I-frame serves as sync frame
+                outHeader->nFlags |= OMX_BUFFERFLAG_SYNCFRAME;
+            }
+
+            ++mNumInputFrames;
+        } else {
+            dataLength = 0;
+        }
+
+        inQueue.erase(inQueue.begin());
+        inInfo->mOwnedByUs = false;
+        notifyEmptyBufferDone(inHeader);
+
+        outQueue.erase(outQueue.begin());
+        CHECK(!mInputBufferInfoVec.empty());
+        InputBufferInfo *inputBufInfo = mInputBufferInfoVec.begin();
+        mInputBufferInfoVec.erase(mInputBufferInfoVec.begin());
+        outHeader->nTimeStamp = inputBufInfo->mTimeUs;
+        outHeader->nFlags |= (inputBufInfo->mFlags | OMX_BUFFERFLAG_ENDOFFRAME);
+        outHeader->nFilledLen = dataLength;
+        outInfo->mOwnedByUs = false;
+        notifyFillBufferDone(outHeader);
+    }
+}
+
+}  // namespace android
+
+android::SoftOMXComponent *createSoftOMXComponent(
+        const char *name, const OMX_CALLBACKTYPE *callbacks,
+        OMX_PTR appData, OMX_COMPONENTTYPE **component) {
+    return new android::SoftMPEG4Encoder(name, callbacks, appData, component);
+}
diff --git a/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.h b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.h
new file mode 100644
index 0000000..3e90d54
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef SOFT_MPEG4_ENCODER_H_
+#define SOFT_MPEG4_ENCODER_H_
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/foundation/ABase.h>
+#include "SimpleSoftOMXComponent.h"
+#include "mp4enc_api.h"
+
+
+namespace android {
+
+struct MediaBuffer;
+
+struct SoftMPEG4Encoder : public SimpleSoftOMXComponent {
+    SoftMPEG4Encoder(
+            const char *name,
+            const OMX_CALLBACKTYPE *callbacks,
+            OMX_PTR appData,
+            OMX_COMPONENTTYPE **component);
+
+    // Override SimpleSoftOMXComponent methods
+    virtual OMX_ERRORTYPE internalGetParameter(
+            OMX_INDEXTYPE index, OMX_PTR params);
+
+    virtual OMX_ERRORTYPE internalSetParameter(
+            OMX_INDEXTYPE index, const OMX_PTR params);
+
+    virtual void onQueueFilled(OMX_U32 portIndex);
+
+protected:
+    virtual ~SoftMPEG4Encoder();
+
+private:
+    enum {
+        kNumBuffers = 2,
+    };
+
+    // OMX input buffer's timestamp and flags
+    typedef struct {
+        int64_t mTimeUs;
+        int32_t mFlags;
+    } InputBufferInfo;
+
+    MP4EncodingMode mEncodeMode;
+    int32_t  mVideoWidth;
+    int32_t  mVideoHeight;
+    int32_t  mVideoFrameRate;
+    int32_t  mVideoBitRate;
+    int32_t  mVideoColorFormat;
+    int32_t  mIDRFrameRefreshIntervalInSec;
+
+    int64_t  mNumInputFrames;
+    bool     mStarted;
+    bool     mSawInputEOS;
+    bool     mSignalledError;
+
+    tagvideoEncControls   *mHandle;
+    tagvideoEncOptions    *mEncParams;
+    uint8_t               *mInputFrameData;
+    Vector<InputBufferInfo> mInputBufferInfoVec;
+
+    void initPorts();
+    OMX_ERRORTYPE initEncParams();
+    OMX_ERRORTYPE initEncoder();
+    OMX_ERRORTYPE releaseEncoder();
+
+    DISALLOW_EVIL_CONSTRUCTORS(SoftMPEG4Encoder);
+};
+
+}  // namespace android
+
+#endif  // SOFT_MPEG4_ENCODER_H_
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index 9b7bb5a..6e53095 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -45,7 +45,9 @@
     { "OMX.google.g711.alaw.decoder", "g711dec", "audio_decoder.g711alaw" },
     { "OMX.google.g711.mlaw.decoder", "g711dec", "audio_decoder.g711mlaw" },
     { "OMX.google.h263.decoder", "mpeg4dec", "video_decoder.h263" },
+    { "OMX.google.h263.encoder", "mpeg4enc", "video_encoder.h263" },
     { "OMX.google.mpeg4.decoder", "mpeg4dec", "video_decoder.mpeg4" },
+    { "OMX.google.mpeg4.encoder", "mpeg4enc", "video_encoder.mpeg4" },
     { "OMX.google.mp3.decoder", "mp3dec", "audio_decoder.mp3" },
     { "OMX.google.vorbis.decoder", "vorbisdec", "audio_decoder.vorbis" },
     { "OMX.google.vpx.decoder", "vpxdec", "video_decoder.vpx" },