/* * Copyright 2014 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. */ #include //#define LOG_NDEBUG 0 #define LOG_TAG "SoftVideoEncoderOMXComponent" #include #include #include "include/SoftVideoEncoderOMXComponent.h" #include #include #include #include #include #include #include #include #include #include namespace android { const static OMX_COLOR_FORMATTYPE kSupportedColorFormats[] = { OMX_COLOR_FormatYUV420Planar, OMX_COLOR_FormatYUV420SemiPlanar, OMX_COLOR_FormatAndroidOpaque }; 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; } SoftVideoEncoderOMXComponent::SoftVideoEncoderOMXComponent( const char *name, const char *componentRole, OMX_VIDEO_CODINGTYPE codingType, const CodecProfileLevel *profileLevels, size_t numProfileLevels, int32_t width, int32_t height, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) : SimpleSoftOMXComponent(name, callbacks, appData, component), mInputDataIsMeta(false), mWidth(width), mHeight(height), mBitrate(192000), mFramerate(30 << 16), // Q16 format mColorFormat(OMX_COLOR_FormatYUV420Planar), mGrallocModule(NULL), mMinOutputBufferSize(384), // arbitrary, using one uncompressed macroblock mMinCompressionRatio(1), // max output size is normally the input size mComponentRole(componentRole), mCodingType(codingType), mProfileLevels(profileLevels), mNumProfileLevels(numProfileLevels) { } void SoftVideoEncoderOMXComponent::initPorts( OMX_U32 numInputBuffers, OMX_U32 numOutputBuffers, OMX_U32 outputBufferSize, const char *mime, OMX_U32 minCompressionRatio) { OMX_PARAM_PORTDEFINITIONTYPE def; mMinOutputBufferSize = outputBufferSize; mMinCompressionRatio = minCompressionRatio; InitOMXParams(&def); def.nPortIndex = kInputPortIndex; def.eDir = OMX_DirInput; def.nBufferCountMin = numInputBuffers; def.nBufferCountActual = def.nBufferCountMin; def.bEnabled = OMX_TRUE; def.bPopulated = OMX_FALSE; def.eDomain = OMX_PortDomainVideo; def.bBuffersContiguous = OMX_FALSE; 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; // frameRate is in Q16 format. def.format.video.xFramerate = mFramerate; def.format.video.bFlagErrorConcealment = OMX_FALSE; def.nBufferAlignment = kInputBufferAlignment; def.format.video.cMIMEType = const_cast("video/raw"); def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; def.format.video.eColorFormat = mColorFormat; def.format.video.pNativeWindow = NULL; // buffersize set in updatePortParams addPort(def); InitOMXParams(&def); def.nPortIndex = kOutputPortIndex; def.eDir = OMX_DirOutput; def.nBufferCountMin = numOutputBuffers; def.nBufferCountActual = def.nBufferCountMin; def.bEnabled = OMX_TRUE; def.bPopulated = OMX_FALSE; def.eDomain = OMX_PortDomainVideo; def.bBuffersContiguous = OMX_FALSE; def.format.video.pNativeRender = NULL; def.format.video.nFrameWidth = mWidth; def.format.video.nFrameHeight = mHeight; def.format.video.nStride = 0; def.format.video.nSliceHeight = 0; def.format.video.nBitrate = mBitrate; def.format.video.xFramerate = 0 << 16; def.format.video.bFlagErrorConcealment = OMX_FALSE; def.nBufferAlignment = kOutputBufferAlignment; def.format.video.cMIMEType = const_cast(mime); def.format.video.eCompressionFormat = mCodingType; def.format.video.eColorFormat = OMX_COLOR_FormatUnused; def.format.video.pNativeWindow = NULL; // buffersize set in updatePortParams addPort(def); updatePortParams(); } void SoftVideoEncoderOMXComponent::updatePortParams() { OMX_PARAM_PORTDEFINITIONTYPE *inDef = &editPortInfo(kInputPortIndex)->mDef; inDef->format.video.nFrameWidth = mWidth; inDef->format.video.nFrameHeight = mHeight; inDef->format.video.nStride = inDef->format.video.nFrameWidth; inDef->format.video.nSliceHeight = inDef->format.video.nFrameHeight; inDef->format.video.xFramerate = mFramerate; inDef->format.video.eColorFormat = mColorFormat; uint32_t rawBufferSize = inDef->format.video.nStride * inDef->format.video.nSliceHeight * 3 / 2; if (inDef->format.video.eColorFormat == OMX_COLOR_FormatAndroidOpaque) { inDef->nBufferSize = max(sizeof(VideoNativeMetadata), sizeof(VideoGrallocMetadata)); } else { inDef->nBufferSize = rawBufferSize; } OMX_PARAM_PORTDEFINITIONTYPE *outDef = &editPortInfo(kOutputPortIndex)->mDef; outDef->format.video.nFrameWidth = mWidth; outDef->format.video.nFrameHeight = mHeight; outDef->format.video.nBitrate = mBitrate; outDef->nBufferSize = max(mMinOutputBufferSize, rawBufferSize / mMinCompressionRatio); } OMX_ERRORTYPE SoftVideoEncoderOMXComponent::internalSetPortParams( const OMX_PARAM_PORTDEFINITIONTYPE *port) { if (port->nPortIndex == kInputPortIndex) { mWidth = port->format.video.nFrameWidth; mHeight = port->format.video.nFrameHeight; // xFramerate comes in Q16 format, in frames per second unit mFramerate = port->format.video.xFramerate; if (port->format.video.eCompressionFormat != OMX_VIDEO_CodingUnused || (port->format.video.eColorFormat != OMX_COLOR_FormatYUV420Planar && port->format.video.eColorFormat != OMX_COLOR_FormatYUV420SemiPlanar && port->format.video.eColorFormat != OMX_COLOR_FormatAndroidOpaque)) { return OMX_ErrorUnsupportedSetting; } mColorFormat = port->format.video.eColorFormat; } else if (port->nPortIndex == kOutputPortIndex) { if (port->format.video.eCompressionFormat != mCodingType || port->format.video.eColorFormat != OMX_COLOR_FormatUnused) { return OMX_ErrorUnsupportedSetting; } mBitrate = port->format.video.nBitrate; } else { return OMX_ErrorBadPortIndex; } updatePortParams(); return OMX_ErrorNone; } OMX_ERRORTYPE SoftVideoEncoderOMXComponent::internalSetParameter( OMX_INDEXTYPE index, const OMX_PTR param) { // can include extension index OMX_INDEXEXTTYPE const int32_t indexFull = index; switch (indexFull) { case OMX_IndexParamVideoErrorCorrection: { return OMX_ErrorNotImplemented; } case OMX_IndexParamStandardComponentRole: { const OMX_PARAM_COMPONENTROLETYPE *roleParams = (const OMX_PARAM_COMPONENTROLETYPE *)param; if (strncmp((const char *)roleParams->cRole, mComponentRole, OMX_MAX_STRINGNAME_SIZE - 1)) { return OMX_ErrorUnsupportedSetting; } return OMX_ErrorNone; } case OMX_IndexParamPortDefinition: { OMX_ERRORTYPE err = internalSetPortParams((const OMX_PARAM_PORTDEFINITIONTYPE *)param); if (err != OMX_ErrorNone) { return err; } return SimpleSoftOMXComponent::internalSetParameter(index, param); } case OMX_IndexParamVideoPortFormat: { const OMX_VIDEO_PARAM_PORTFORMATTYPE* format = (const OMX_VIDEO_PARAM_PORTFORMATTYPE *)param; if (format->nPortIndex == kInputPortIndex) { if (format->eColorFormat == OMX_COLOR_FormatYUV420Planar || format->eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar || format->eColorFormat == OMX_COLOR_FormatAndroidOpaque) { mColorFormat = format->eColorFormat; updatePortParams(); return OMX_ErrorNone; } else { ALOGE("Unsupported color format %i", format->eColorFormat); return OMX_ErrorUnsupportedSetting; } } else if (format->nPortIndex == kOutputPortIndex) { if (format->eCompressionFormat == mCodingType) { return OMX_ErrorNone; } else { return OMX_ErrorUnsupportedSetting; } } else { return OMX_ErrorBadPortIndex; } } case kStoreMetaDataExtensionIndex: { // storeMetaDataInBuffers const StoreMetaDataInBuffersParams *storeParam = (const StoreMetaDataInBuffersParams *)param; if (storeParam->nPortIndex == kOutputPortIndex) { return storeParam->bStoreMetaData ? OMX_ErrorUnsupportedSetting : OMX_ErrorNone; } else if (storeParam->nPortIndex != kInputPortIndex) { return OMX_ErrorBadPortIndex; } mInputDataIsMeta = (storeParam->bStoreMetaData == OMX_TRUE); if (mInputDataIsMeta) { mColorFormat = OMX_COLOR_FormatAndroidOpaque; } else if (mColorFormat == OMX_COLOR_FormatAndroidOpaque) { mColorFormat = OMX_COLOR_FormatYUV420Planar; } updatePortParams(); return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalSetParameter(index, param); } } OMX_ERRORTYPE SoftVideoEncoderOMXComponent::internalGetParameter( OMX_INDEXTYPE index, OMX_PTR param) { switch ((int)index) { case OMX_IndexParamVideoErrorCorrection: { return OMX_ErrorNotImplemented; } case OMX_IndexParamVideoPortFormat: { OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = (OMX_VIDEO_PARAM_PORTFORMATTYPE *)param; if (formatParams->nPortIndex == kInputPortIndex) { if (formatParams->nIndex >= NELEM(kSupportedColorFormats)) { return OMX_ErrorNoMore; } // Color formats, in order of preference formatParams->eColorFormat = kSupportedColorFormats[formatParams->nIndex]; formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; formatParams->xFramerate = mFramerate; return OMX_ErrorNone; } else if (formatParams->nPortIndex == kOutputPortIndex) { formatParams->eCompressionFormat = mCodingType; formatParams->eColorFormat = OMX_COLOR_FormatUnused; formatParams->xFramerate = 0; return OMX_ErrorNone; } else { return OMX_ErrorBadPortIndex; } } case OMX_IndexParamVideoProfileLevelQuerySupported: { OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel = (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) param; if (profileLevel->nPortIndex != kOutputPortIndex) { ALOGE("Invalid port index: %u", profileLevel->nPortIndex); return OMX_ErrorUnsupportedIndex; } if (profileLevel->nProfileIndex >= mNumProfileLevels) { return OMX_ErrorNoMore; } profileLevel->eProfile = mProfileLevels[profileLevel->nProfileIndex].mProfile; profileLevel->eLevel = mProfileLevels[profileLevel->nProfileIndex].mLevel; return OMX_ErrorNone; } case OMX_IndexParamConsumerUsageBits: { OMX_U32 *usageBits = (OMX_U32 *)param; *usageBits = GRALLOC_USAGE_SW_READ_OFTEN; return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalGetParameter(index, param); } } // static void SoftVideoEncoderOMXComponent::ConvertFlexYUVToPlanar( uint8_t *dst, size_t dstStride, size_t dstVStride, struct android_ycbcr *ycbcr, int32_t width, int32_t height) { const uint8_t *src = (const uint8_t *)ycbcr->y; const uint8_t *srcU = (const uint8_t *)ycbcr->cb; const uint8_t *srcV = (const uint8_t *)ycbcr->cr; uint8_t *dstU = dst + dstVStride * dstStride; uint8_t *dstV = dstU + (dstVStride >> 1) * (dstStride >> 1); for (size_t y = height; y > 0; --y) { memcpy(dst, src, width); dst += dstStride; src += ycbcr->ystride; } if (ycbcr->cstride == ycbcr->ystride >> 1 && ycbcr->chroma_step == 1) { // planar for (size_t y = height >> 1; y > 0; --y) { memcpy(dstU, srcU, width >> 1); dstU += dstStride >> 1; srcU += ycbcr->cstride; memcpy(dstV, srcV, width >> 1); dstV += dstStride >> 1; srcV += ycbcr->cstride; } } else { // arbitrary for (size_t y = height >> 1; y > 0; --y) { for (size_t x = width >> 1; x > 0; --x) { *dstU++ = *srcU; *dstV++ = *srcV; srcU += ycbcr->chroma_step; srcV += ycbcr->chroma_step; } dstU += (dstStride >> 1) - (width >> 1); dstV += (dstStride >> 1) - (width >> 1); srcU += ycbcr->cstride - (width >> 1) * ycbcr->chroma_step; srcV += ycbcr->cstride - (width >> 1) * ycbcr->chroma_step; } } } // static void SoftVideoEncoderOMXComponent::ConvertYUV420SemiPlanarToYUV420Planar( const uint8_t *inYVU, uint8_t* outYUV, int32_t width, int32_t height) { // TODO: add support for stride 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, inYVU, outYsize); /* U & V copying */ // FIXME this only works if width is multiple of 4 uint32_t *inYVU_4 = (uint32_t *) (inYVU + outYsize); for (int32_t i = height >> 1; i > 0; --i) { for (int32_t j = width >> 2; j > 0; --j) { uint32_t temp = *inYVU_4++; uint32_t tempU = temp & 0xFF; tempU = tempU | ((temp >> 8) & 0xFF00); uint32_t tempV = (temp >> 8) & 0xFF; tempV = tempV | ((temp >> 16) & 0xFF00); *outCb++ = tempU; *outCr++ = tempV; } } } // static void SoftVideoEncoderOMXComponent::ConvertRGB32ToPlanar( uint8_t *dstY, size_t dstStride, size_t dstVStride, const uint8_t *src, size_t width, size_t height, size_t srcStride, bool bgr) { CHECK((width & 1) == 0); CHECK((height & 1) == 0); uint8_t *dstU = dstY + dstStride * dstVStride; uint8_t *dstV = dstU + (dstStride >> 1) * (dstVStride >> 1); #ifdef SURFACE_IS_BGR32 bgr = !bgr; #endif const size_t redOffset = bgr ? 2 : 0; const size_t greenOffset = 1; const size_t blueOffset = bgr ? 0 : 2; for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { unsigned red = src[redOffset]; unsigned green = src[greenOffset]; unsigned blue = src[blueOffset]; // using ITU-R BT.601 conversion matrix unsigned luma = ((red * 66 + green * 129 + blue * 25) >> 8) + 16; dstY[x] = luma; if ((x & 1) == 0 && (y & 1) == 0) { unsigned U = ((-red * 38 - green * 74 + blue * 112) >> 8) + 128; unsigned V = ((red * 112 - green * 94 - blue * 18) >> 8) + 128; dstU[x >> 1] = U; dstV[x >> 1] = V; } src += 4; } if ((y & 1) == 0) { dstU += dstStride >> 1; dstV += dstStride >> 1; } src += srcStride - 4 * width; dstY += dstStride; } } const uint8_t *SoftVideoEncoderOMXComponent::extractGraphicBuffer( uint8_t *dst, size_t dstSize, const uint8_t *src, size_t srcSize, size_t width, size_t height) const { size_t dstStride = width; size_t dstVStride = height; MetadataBufferType bufferType = *(MetadataBufferType *)src; bool usingANWBuffer = bufferType == kMetadataBufferTypeANWBuffer; if (!usingANWBuffer && bufferType != kMetadataBufferTypeGrallocSource) { ALOGE("Unsupported metadata type (%d)", bufferType); return NULL; } if (mGrallocModule == NULL) { CHECK_EQ(0, hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &mGrallocModule)); } const gralloc_module_t *grmodule = (const gralloc_module_t *)mGrallocModule; buffer_handle_t handle; int format; size_t srcStride; size_t srcVStride; if (usingANWBuffer) { if (srcSize < sizeof(VideoNativeMetadata)) { ALOGE("Metadata is too small (%zu vs %zu)", srcSize, sizeof(VideoNativeMetadata)); return NULL; } VideoNativeMetadata &nativeMeta = *(VideoNativeMetadata *)src; ANativeWindowBuffer *buffer = nativeMeta.pBuffer; handle = buffer->handle; format = buffer->format; srcStride = buffer->stride; srcVStride = buffer->height; // convert stride from pixels to bytes if (format != HAL_PIXEL_FORMAT_YV12 && format != HAL_PIXEL_FORMAT_YCbCr_420_888) { // TODO do we need to support other formats? srcStride *= 4; } if (nativeMeta.nFenceFd >= 0) { sp fence = new Fence(nativeMeta.nFenceFd); nativeMeta.nFenceFd = -1; status_t err = fence->wait(IOMX::kFenceTimeoutMs); if (err != OK) { ALOGE("Timed out waiting on input fence"); return NULL; } } } else { // TODO: remove this part. Check if anyone uses this. if (srcSize < sizeof(VideoGrallocMetadata)) { ALOGE("Metadata is too small (%zu vs %zu)", srcSize, sizeof(VideoGrallocMetadata)); return NULL; } VideoGrallocMetadata &grallocMeta = *(VideoGrallocMetadata *)(src); handle = grallocMeta.pHandle; // assume HAL_PIXEL_FORMAT_RGBA_8888 // there is no way to get the src stride without the graphic buffer format = HAL_PIXEL_FORMAT_RGBA_8888; srcStride = width * 4; srcVStride = height; } size_t neededSize = dstStride * dstVStride + (width >> 1) + (dstStride >> 1) * ((dstVStride >> 1) + (height >> 1) - 1); if (dstSize < neededSize) { ALOGE("destination buffer is too small (%zu vs %zu)", dstSize, neededSize); return NULL; } void *bits = NULL; struct android_ycbcr ycbcr; status_t res; if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) { res = grmodule->lock_ycbcr( grmodule, handle, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, 0, 0, width, height, &ycbcr); } else { res = grmodule->lock( grmodule, handle, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, 0, 0, width, height, &bits); } if (res != OK) { ALOGE("Unable to lock image buffer %p for access", handle); return NULL; } switch (format) { case HAL_PIXEL_FORMAT_YV12: // YCrCb / YVU planar // convert to flex YUV ycbcr.y = bits; ycbcr.cr = (uint8_t *)bits + srcStride * srcVStride; ycbcr.cb = (uint8_t *)ycbcr.cr + (srcStride >> 1) * (srcVStride >> 1); ycbcr.chroma_step = 1; ycbcr.cstride = srcVStride >> 1; ycbcr.ystride = srcVStride; ConvertFlexYUVToPlanar(dst, dstStride, dstVStride, &ycbcr, width, height); break; case HAL_PIXEL_FORMAT_YCrCb_420_SP: // YCrCb / YVU semiplanar, NV21 // convert to flex YUV ycbcr.y = bits; ycbcr.cr = (uint8_t *)bits + srcStride * srcVStride; ycbcr.cb = (uint8_t *)ycbcr.cr + 1; ycbcr.chroma_step = 2; ycbcr.cstride = srcVStride; ycbcr.ystride = srcVStride; ConvertFlexYUVToPlanar(dst, dstStride, dstVStride, &ycbcr, width, height); break; case HAL_PIXEL_FORMAT_YCbCr_420_888: ConvertFlexYUVToPlanar(dst, dstStride, dstVStride, &ycbcr, width, height); break; case HAL_PIXEL_FORMAT_RGBA_8888: case HAL_PIXEL_FORMAT_BGRA_8888: #ifdef MTK_HARDWARE case HAL_PIXEL_FORMAT_RGBX_8888: #endif ConvertRGB32ToPlanar( dst, dstStride, dstVStride, (const uint8_t *)bits, width, height, srcStride, format == HAL_PIXEL_FORMAT_BGRA_8888); break; default: ALOGE("Unsupported pixel format %#x", format); dst = NULL; break; } if (grmodule->unlock(grmodule, handle) != OK) { ALOGE("Unable to unlock image buffer %p for access", handle); } return dst; } OMX_ERRORTYPE SoftVideoEncoderOMXComponent::getExtensionIndex( const char *name, OMX_INDEXTYPE *index) { if (!strcmp(name, "OMX.google.android.index.storeMetaDataInBuffers") || !strcmp(name, "OMX.google.android.index.storeANWBufferInMetadata")) { *(int32_t*)index = kStoreMetaDataExtensionIndex; return OMX_ErrorNone; } return SimpleSoftOMXComponent::getExtensionIndex(name, index); } } // namespace android