/* * Copyright (C) 2013 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 "SoftVPXEncoder" #include "SoftVPXEncoder.h" #include #include #include #include #include namespace android { template static void InitOMXParams(T *params) { params->nSize = sizeof(T); // OMX IL 1.1.2 params->nVersion.s.nVersionMajor = 1; params->nVersion.s.nVersionMinor = 1; params->nVersion.s.nRevision = 2; params->nVersion.s.nStep = 0; } static int GetCPUCoreCount() { int cpuCoreCount = 1; #if defined(_SC_NPROCESSORS_ONLN) cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); #else // _SC_NPROC_ONLN must be defined... cpuCoreCount = sysconf(_SC_NPROC_ONLN); #endif CHECK_GE(cpuCoreCount, 1); return cpuCoreCount; } // This color conversion utility is copied from SoftMPEG4Encoder.cpp inline static void ConvertSemiPlanarToPlanar(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; } } } static void ConvertRGB32ToPlanar( const uint8_t *src, uint8_t *dstY, int32_t width, int32_t height) { CHECK((width & 1) == 0); CHECK((height & 1) == 0); uint8_t *dstU = dstY + width * height; uint8_t *dstV = dstU + (width / 2) * (height / 2); for (int32_t y = 0; y < height; ++y) { for (int32_t x = 0; x < width; ++x) { #ifdef SURFACE_IS_BGR32 unsigned blue = src[4 * x]; unsigned green = src[4 * x + 1]; unsigned red= src[4 * x + 2]; #else unsigned red= src[4 * x]; unsigned green = src[4 * x + 1]; unsigned blue = src[4 * x + 2]; #endif 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 / 2] = U; dstV[x / 2] = V; } } if ((y & 1) == 0) { dstU += width / 2; dstV += width / 2; } src += 4 * width; dstY += width; } } SoftVPXEncoder::SoftVPXEncoder(const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) : SimpleSoftOMXComponent(name, callbacks, appData, component), mCodecContext(NULL), mCodecConfiguration(NULL), mCodecInterface(NULL), mWidth(176), mHeight(144), mBitrate(192000), // in bps mFramerate(30 << 16), // in Q16 format mBitrateUpdated(false), mBitrateControlMode(VPX_VBR), // variable bitrate mDCTPartitions(0), mErrorResilience(OMX_FALSE), mColorFormat(OMX_COLOR_FormatYUV420Planar), mLevel(OMX_VIDEO_VP8Level_Version0), mKeyFrameInterval(0), mMinQuantizer(0), mMaxQuantizer(0), mTemporalLayers(0), mTemporalPatternType(OMX_VIDEO_VPXTemporalLayerPatternNone), mTemporalPatternLength(0), mTemporalPatternIdx(0), mLastTimestamp(0x7FFFFFFFFFFFFFFFLL), mConversionBuffer(NULL), mInputDataIsMeta(false), mGrallocModule(NULL), mKeyFrameRequested(false) { memset(mTemporalLayerBitrateRatio, 0, sizeof(mTemporalLayerBitrateRatio)); mTemporalLayerBitrateRatio[0] = 100; initPorts(); } SoftVPXEncoder::~SoftVPXEncoder() { releaseEncoder(); } void SoftVPXEncoder::initPorts() { OMX_PARAM_PORTDEFINITIONTYPE inputPort; OMX_PARAM_PORTDEFINITIONTYPE outputPort; InitOMXParams(&inputPort); InitOMXParams(&outputPort); inputPort.nBufferCountMin = kNumBuffers; inputPort.nBufferCountActual = inputPort.nBufferCountMin; inputPort.bEnabled = OMX_TRUE; inputPort.bPopulated = OMX_FALSE; inputPort.eDomain = OMX_PortDomainVideo; inputPort.bBuffersContiguous = OMX_FALSE; inputPort.format.video.pNativeRender = NULL; inputPort.format.video.nFrameWidth = mWidth; inputPort.format.video.nFrameHeight = mHeight; inputPort.format.video.nStride = inputPort.format.video.nFrameWidth; inputPort.format.video.nSliceHeight = inputPort.format.video.nFrameHeight; inputPort.format.video.nBitrate = 0; // frameRate is in Q16 format. inputPort.format.video.xFramerate = mFramerate; inputPort.format.video.bFlagErrorConcealment = OMX_FALSE; inputPort.nPortIndex = kInputPortIndex; inputPort.eDir = OMX_DirInput; inputPort.nBufferAlignment = kInputBufferAlignment; inputPort.format.video.cMIMEType = const_cast(MEDIA_MIMETYPE_VIDEO_RAW); inputPort.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; inputPort.format.video.eColorFormat = mColorFormat; inputPort.format.video.pNativeWindow = NULL; inputPort.nBufferSize = (inputPort.format.video.nStride * inputPort.format.video.nSliceHeight * 3) / 2; addPort(inputPort); outputPort.nBufferCountMin = kNumBuffers; outputPort.nBufferCountActual = outputPort.nBufferCountMin; outputPort.bEnabled = OMX_TRUE; outputPort.bPopulated = OMX_FALSE; outputPort.eDomain = OMX_PortDomainVideo; outputPort.bBuffersContiguous = OMX_FALSE; outputPort.format.video.pNativeRender = NULL; outputPort.format.video.nFrameWidth = mWidth; outputPort.format.video.nFrameHeight = mHeight; outputPort.format.video.nStride = outputPort.format.video.nFrameWidth; outputPort.format.video.nSliceHeight = outputPort.format.video.nFrameHeight; outputPort.format.video.nBitrate = mBitrate; outputPort.format.video.xFramerate = 0; outputPort.format.video.bFlagErrorConcealment = OMX_FALSE; outputPort.nPortIndex = kOutputPortIndex; outputPort.eDir = OMX_DirOutput; outputPort.nBufferAlignment = kOutputBufferAlignment; outputPort.format.video.cMIMEType = const_cast(MEDIA_MIMETYPE_VIDEO_VP8); outputPort.format.video.eCompressionFormat = OMX_VIDEO_CodingVP8; outputPort.format.video.eColorFormat = OMX_COLOR_FormatUnused; outputPort.format.video.pNativeWindow = NULL; outputPort.nBufferSize = 1024 * 1024; // arbitrary addPort(outputPort); } status_t SoftVPXEncoder::initEncoder() { vpx_codec_err_t codec_return; mCodecContext = new vpx_codec_ctx_t; mCodecConfiguration = new vpx_codec_enc_cfg_t; mCodecInterface = vpx_codec_vp8_cx(); if (mCodecInterface == NULL) { return UNKNOWN_ERROR; } ALOGD("VP8: initEncoder. BRMode: %u. TSLayers: %zu. KF: %u. QP: %u - %u", (uint32_t)mBitrateControlMode, mTemporalLayers, mKeyFrameInterval, mMinQuantizer, mMaxQuantizer); codec_return = vpx_codec_enc_config_default(mCodecInterface, mCodecConfiguration, 0); // Codec specific flags if (codec_return != VPX_CODEC_OK) { ALOGE("Error populating default configuration for vpx encoder."); return UNKNOWN_ERROR; } mCodecConfiguration->g_w = mWidth; mCodecConfiguration->g_h = mHeight; mCodecConfiguration->g_threads = GetCPUCoreCount(); mCodecConfiguration->g_error_resilient = mErrorResilience; switch (mLevel) { case OMX_VIDEO_VP8Level_Version0: mCodecConfiguration->g_profile = 0; break; case OMX_VIDEO_VP8Level_Version1: mCodecConfiguration->g_profile = 1; break; case OMX_VIDEO_VP8Level_Version2: mCodecConfiguration->g_profile = 2; break; case OMX_VIDEO_VP8Level_Version3: mCodecConfiguration->g_profile = 3; break; default: mCodecConfiguration->g_profile = 0; } // OMX timebase unit is microsecond // g_timebase is in seconds (i.e. 1/1000000 seconds) mCodecConfiguration->g_timebase.num = 1; mCodecConfiguration->g_timebase.den = 1000000; // rc_target_bitrate is in kbps, mBitrate in bps mCodecConfiguration->rc_target_bitrate = (mBitrate + 500) / 1000; mCodecConfiguration->rc_end_usage = mBitrateControlMode; // Disable frame drop - not allowed in MediaCodec now. mCodecConfiguration->rc_dropframe_thresh = 0; if (mBitrateControlMode == VPX_CBR) { // Disable spatial resizing. mCodecConfiguration->rc_resize_allowed = 0; // Single-pass mode. mCodecConfiguration->g_pass = VPX_RC_ONE_PASS; // Maximum amount of bits that can be subtracted from the target // bitrate - expressed as percentage of the target bitrate. mCodecConfiguration->rc_undershoot_pct = 100; // Maximum amount of bits that can be added to the target // bitrate - expressed as percentage of the target bitrate. mCodecConfiguration->rc_overshoot_pct = 15; // Initial value of the buffer level in ms. mCodecConfiguration->rc_buf_initial_sz = 500; // Amount of data that the encoder should try to maintain in ms. mCodecConfiguration->rc_buf_optimal_sz = 600; // The amount of data that may be buffered by the decoding // application in ms. mCodecConfiguration->rc_buf_sz = 1000; // Enable error resilience - needed for packet loss. mCodecConfiguration->g_error_resilient = 1; // Disable lagged encoding. mCodecConfiguration->g_lag_in_frames = 0; // Maximum key frame interval - for CBR boost to 3000 mCodecConfiguration->kf_max_dist = 3000; // Encoder determines optimal key frame placement automatically. mCodecConfiguration->kf_mode = VPX_KF_AUTO; } // Frames temporal pattern - for now WebRTC like pattern is only supported. switch (mTemporalLayers) { case 0: { mTemporalPatternLength = 0; break; } case 1: { mCodecConfiguration->ts_number_layers = 1; mCodecConfiguration->ts_rate_decimator[0] = 1; mCodecConfiguration->ts_periodicity = 1; mCodecConfiguration->ts_layer_id[0] = 0; mTemporalPattern[0] = kTemporalUpdateLastRefAll; mTemporalPatternLength = 1; break; } case 2: { mCodecConfiguration->ts_number_layers = 2; mCodecConfiguration->ts_rate_decimator[0] = 2; mCodecConfiguration->ts_rate_decimator[1] = 1; mCodecConfiguration->ts_periodicity = 2; mCodecConfiguration->ts_layer_id[0] = 0; mCodecConfiguration->ts_layer_id[1] = 1; mTemporalPattern[0] = kTemporalUpdateLastAndGoldenRefAltRef; mTemporalPattern[1] = kTemporalUpdateGoldenWithoutDependencyRefAltRef; mTemporalPattern[2] = kTemporalUpdateLastRefAltRef; mTemporalPattern[3] = kTemporalUpdateGoldenRefAltRef; mTemporalPattern[4] = kTemporalUpdateLastRefAltRef; mTemporalPattern[5] = kTemporalUpdateGoldenRefAltRef; mTemporalPattern[6] = kTemporalUpdateLastRefAltRef; mTemporalPattern[7] = kTemporalUpdateNone; mTemporalPatternLength = 8; break; } case 3: { mCodecConfiguration->ts_number_layers = 3; mCodecConfiguration->ts_rate_decimator[0] = 4; mCodecConfiguration->ts_rate_decimator[1] = 2; mCodecConfiguration->ts_rate_decimator[2] = 1; mCodecConfiguration->ts_periodicity = 4; mCodecConfiguration->ts_layer_id[0] = 0; mCodecConfiguration->ts_layer_id[1] = 2; mCodecConfiguration->ts_layer_id[2] = 1; mCodecConfiguration->ts_layer_id[3] = 2; mTemporalPattern[0] = kTemporalUpdateLastAndGoldenRefAltRef; mTemporalPattern[1] = kTemporalUpdateNoneNoRefGoldenRefAltRef; mTemporalPattern[2] = kTemporalUpdateGoldenWithoutDependencyRefAltRef; mTemporalPattern[3] = kTemporalUpdateNone; mTemporalPattern[4] = kTemporalUpdateLastRefAltRef; mTemporalPattern[5] = kTemporalUpdateNone; mTemporalPattern[6] = kTemporalUpdateGoldenRefAltRef; mTemporalPattern[7] = kTemporalUpdateNone; mTemporalPatternLength = 8; break; } default: { ALOGE("Wrong number of temporal layers %zu", mTemporalLayers); return UNKNOWN_ERROR; } } // Set bitrate values for each layer for (size_t i = 0; i < mCodecConfiguration->ts_number_layers; i++) { mCodecConfiguration->ts_target_bitrate[i] = mCodecConfiguration->rc_target_bitrate * mTemporalLayerBitrateRatio[i] / 100; } if (mKeyFrameInterval > 0) { mCodecConfiguration->kf_max_dist = mKeyFrameInterval; mCodecConfiguration->kf_min_dist = mKeyFrameInterval; mCodecConfiguration->kf_mode = VPX_KF_AUTO; } if (mMinQuantizer > 0) { mCodecConfiguration->rc_min_quantizer = mMinQuantizer; } if (mMaxQuantizer > 0) { mCodecConfiguration->rc_max_quantizer = mMaxQuantizer; } codec_return = vpx_codec_enc_init(mCodecContext, mCodecInterface, mCodecConfiguration, 0); // flags if (codec_return != VPX_CODEC_OK) { ALOGE("Error initializing vpx encoder"); return UNKNOWN_ERROR; } codec_return = vpx_codec_control(mCodecContext, VP8E_SET_TOKEN_PARTITIONS, mDCTPartitions); if (codec_return != VPX_CODEC_OK) { ALOGE("Error setting dct partitions for vpx encoder."); return UNKNOWN_ERROR; } // Extra CBR settings if (mBitrateControlMode == VPX_CBR) { codec_return = vpx_codec_control(mCodecContext, VP8E_SET_STATIC_THRESHOLD, 1); if (codec_return == VPX_CODEC_OK) { uint32_t rc_max_intra_target = mCodecConfiguration->rc_buf_optimal_sz * (mFramerate >> 17) / 10; // Don't go below 3 times per frame bandwidth. if (rc_max_intra_target < 300) { rc_max_intra_target = 300; } codec_return = vpx_codec_control(mCodecContext, VP8E_SET_MAX_INTRA_BITRATE_PCT, rc_max_intra_target); } if (codec_return == VPX_CODEC_OK) { codec_return = vpx_codec_control(mCodecContext, VP8E_SET_CPUUSED, -8); } if (codec_return != VPX_CODEC_OK) { ALOGE("Error setting cbr parameters for vpx encoder."); return UNKNOWN_ERROR; } } if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar || mInputDataIsMeta) { if (mConversionBuffer == NULL) { mConversionBuffer = (uint8_t *)malloc(mWidth * mHeight * 3 / 2); if (mConversionBuffer == NULL) { ALOGE("Allocating conversion buffer failed."); return UNKNOWN_ERROR; } } } return OK; } status_t SoftVPXEncoder::releaseEncoder() { if (mCodecContext != NULL) { vpx_codec_destroy(mCodecContext); delete mCodecContext; mCodecContext = NULL; } if (mCodecConfiguration != NULL) { delete mCodecConfiguration; mCodecConfiguration = NULL; } if (mConversionBuffer != NULL) { delete mConversionBuffer; mConversionBuffer = NULL; } // this one is not allocated by us mCodecInterface = NULL; return OK; } OMX_ERRORTYPE SoftVPXEncoder::internalGetParameter(OMX_INDEXTYPE index, OMX_PTR param) { // can include extension index OMX_INDEXEXTTYPE const int32_t indexFull = index; switch (indexFull) { case OMX_IndexParamVideoPortFormat: { OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = (OMX_VIDEO_PARAM_PORTFORMATTYPE *)param; if (formatParams->nPortIndex == kInputPortIndex) { if (formatParams->nIndex >= kNumberOfSupportedColorFormats) { return OMX_ErrorNoMore; } // Color formats, in order of preference if (formatParams->nIndex == 0) { formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; } else if (formatParams->nIndex == 1) { formatParams->eColorFormat = OMX_COLOR_FormatYUV420SemiPlanar; } else { formatParams->eColorFormat = OMX_COLOR_FormatAndroidOpaque; } formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; formatParams->xFramerate = mFramerate; return OMX_ErrorNone; } else if (formatParams->nPortIndex == kOutputPortIndex) { formatParams->eCompressionFormat = OMX_VIDEO_CodingVP8; formatParams->eColorFormat = OMX_COLOR_FormatUnused; formatParams->xFramerate = 0; return OMX_ErrorNone; } else { return OMX_ErrorBadPortIndex; } } case OMX_IndexParamVideoBitrate: { OMX_VIDEO_PARAM_BITRATETYPE *bitrate = (OMX_VIDEO_PARAM_BITRATETYPE *)param; if (bitrate->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } bitrate->nTargetBitrate = mBitrate; if (mBitrateControlMode == VPX_VBR) { bitrate->eControlRate = OMX_Video_ControlRateVariable; } else if (mBitrateControlMode == VPX_CBR) { bitrate->eControlRate = OMX_Video_ControlRateConstant; } else { return OMX_ErrorUnsupportedSetting; } return OMX_ErrorNone; } // VP8 specific parameters that use extension headers case OMX_IndexParamVideoVp8: { OMX_VIDEO_PARAM_VP8TYPE *vp8Params = (OMX_VIDEO_PARAM_VP8TYPE *)param; if (vp8Params->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } vp8Params->eProfile = OMX_VIDEO_VP8ProfileMain; vp8Params->eLevel = mLevel; vp8Params->nDCTPartitions = mDCTPartitions; vp8Params->bErrorResilientMode = mErrorResilience; return OMX_ErrorNone; } case OMX_IndexParamVideoAndroidVp8Encoder: { OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE *vp8AndroidParams = (OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE *)param; if (vp8AndroidParams->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } vp8AndroidParams->nKeyFrameInterval = mKeyFrameInterval; vp8AndroidParams->eTemporalPattern = mTemporalPatternType; vp8AndroidParams->nTemporalLayerCount = mTemporalLayers; vp8AndroidParams->nMinQuantizer = mMinQuantizer; vp8AndroidParams->nMaxQuantizer = mMaxQuantizer; memcpy(vp8AndroidParams->nTemporalLayerBitrateRatio, mTemporalLayerBitrateRatio, sizeof(mTemporalLayerBitrateRatio)); return OMX_ErrorNone; } case OMX_IndexParamVideoProfileLevelQuerySupported: { OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileAndLevel = (OMX_VIDEO_PARAM_PROFILELEVELTYPE *)param; if (profileAndLevel->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } switch (profileAndLevel->nProfileIndex) { case 0: profileAndLevel->eLevel = OMX_VIDEO_VP8Level_Version0; break; case 1: profileAndLevel->eLevel = OMX_VIDEO_VP8Level_Version1; break; case 2: profileAndLevel->eLevel = OMX_VIDEO_VP8Level_Version2; break; case 3: profileAndLevel->eLevel = OMX_VIDEO_VP8Level_Version3; break; default: return OMX_ErrorNoMore; } profileAndLevel->eProfile = OMX_VIDEO_VP8ProfileMain; return OMX_ErrorNone; } case OMX_IndexParamVideoProfileLevelCurrent: { OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileAndLevel = (OMX_VIDEO_PARAM_PROFILELEVELTYPE *)param; if (profileAndLevel->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } profileAndLevel->eLevel = mLevel; profileAndLevel->eProfile = OMX_VIDEO_VP8ProfileMain; return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalGetParameter(index, param); } } OMX_ERRORTYPE SoftVPXEncoder::internalSetParameter(OMX_INDEXTYPE index, const OMX_PTR param) { // can include extension index OMX_INDEXEXTTYPE const int32_t indexFull = index; switch (indexFull) { case OMX_IndexParamStandardComponentRole: return internalSetRoleParams( (const OMX_PARAM_COMPONENTROLETYPE *)param); case OMX_IndexParamVideoBitrate: return internalSetBitrateParams( (const OMX_VIDEO_PARAM_BITRATETYPE *)param); 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: return internalSetFormatParams( (const OMX_VIDEO_PARAM_PORTFORMATTYPE *)param); case OMX_IndexParamVideoVp8: return internalSetVp8Params( (const OMX_VIDEO_PARAM_VP8TYPE *)param); case OMX_IndexParamVideoAndroidVp8Encoder: return internalSetAndroidVp8Params( (const OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE *)param); case OMX_IndexParamVideoProfileLevelCurrent: return internalSetProfileLevel( (const OMX_VIDEO_PARAM_PROFILELEVELTYPE *)param); case kStoreMetaDataExtensionIndex: { // storeMetaDataInBuffers const StoreMetaDataInBuffersParams *storeParam = (const StoreMetaDataInBuffersParams *)param; if (storeParam->nPortIndex != kInputPortIndex) { return OMX_ErrorBadPortIndex; } mInputDataIsMeta = (storeParam->bStoreMetaData == OMX_TRUE); return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalSetParameter(index, param); } } OMX_ERRORTYPE SoftVPXEncoder::setConfig( OMX_INDEXTYPE index, const OMX_PTR _params) { switch (index) { case OMX_IndexConfigVideoIntraVOPRefresh: { OMX_CONFIG_INTRAREFRESHVOPTYPE *params = (OMX_CONFIG_INTRAREFRESHVOPTYPE *)_params; if (params->nPortIndex != kOutputPortIndex) { return OMX_ErrorBadPortIndex; } mKeyFrameRequested = params->IntraRefreshVOP; return OMX_ErrorNone; } case OMX_IndexConfigVideoBitrate: { OMX_VIDEO_CONFIG_BITRATETYPE *params = (OMX_VIDEO_CONFIG_BITRATETYPE *)_params; if (params->nPortIndex != kOutputPortIndex) { return OMX_ErrorBadPortIndex; } if (mBitrate != params->nEncodeBitrate) { mBitrate = params->nEncodeBitrate; mBitrateUpdated = true; } return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::setConfig(index, _params); } } OMX_ERRORTYPE SoftVPXEncoder::internalSetProfileLevel( const OMX_VIDEO_PARAM_PROFILELEVELTYPE* profileAndLevel) { if (profileAndLevel->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } if (profileAndLevel->eProfile != OMX_VIDEO_VP8ProfileMain) { return OMX_ErrorBadParameter; } if (profileAndLevel->eLevel == OMX_VIDEO_VP8Level_Version0 || profileAndLevel->eLevel == OMX_VIDEO_VP8Level_Version1 || profileAndLevel->eLevel == OMX_VIDEO_VP8Level_Version2 || profileAndLevel->eLevel == OMX_VIDEO_VP8Level_Version3) { mLevel = (OMX_VIDEO_VP8LEVELTYPE)profileAndLevel->eLevel; } else { return OMX_ErrorBadParameter; } return OMX_ErrorNone; } OMX_ERRORTYPE SoftVPXEncoder::internalSetVp8Params( const OMX_VIDEO_PARAM_VP8TYPE* vp8Params) { if (vp8Params->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } if (vp8Params->eProfile != OMX_VIDEO_VP8ProfileMain) { return OMX_ErrorBadParameter; } if (vp8Params->eLevel == OMX_VIDEO_VP8Level_Version0 || vp8Params->eLevel == OMX_VIDEO_VP8Level_Version1 || vp8Params->eLevel == OMX_VIDEO_VP8Level_Version2 || vp8Params->eLevel == OMX_VIDEO_VP8Level_Version3) { mLevel = vp8Params->eLevel; } else { return OMX_ErrorBadParameter; } if (vp8Params->nDCTPartitions <= kMaxDCTPartitions) { mDCTPartitions = vp8Params->nDCTPartitions; } else { return OMX_ErrorBadParameter; } mErrorResilience = vp8Params->bErrorResilientMode; return OMX_ErrorNone; } OMX_ERRORTYPE SoftVPXEncoder::internalSetAndroidVp8Params( const OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE* vp8AndroidParams) { if (vp8AndroidParams->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } if (vp8AndroidParams->eTemporalPattern != OMX_VIDEO_VPXTemporalLayerPatternNone && vp8AndroidParams->eTemporalPattern != OMX_VIDEO_VPXTemporalLayerPatternWebRTC) { return OMX_ErrorBadParameter; } if (vp8AndroidParams->nTemporalLayerCount > OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS) { return OMX_ErrorBadParameter; } if (vp8AndroidParams->nMinQuantizer > vp8AndroidParams->nMaxQuantizer) { return OMX_ErrorBadParameter; } mTemporalPatternType = vp8AndroidParams->eTemporalPattern; if (vp8AndroidParams->eTemporalPattern == OMX_VIDEO_VPXTemporalLayerPatternWebRTC) { mTemporalLayers = vp8AndroidParams->nTemporalLayerCount; } else if (vp8AndroidParams->eTemporalPattern == OMX_VIDEO_VPXTemporalLayerPatternNone) { mTemporalLayers = 0; } // Check the bitrate distribution between layers is in increasing order if (mTemporalLayers > 1) { for (size_t i = 0; i < mTemporalLayers - 1; i++) { if (vp8AndroidParams->nTemporalLayerBitrateRatio[i + 1] <= vp8AndroidParams->nTemporalLayerBitrateRatio[i]) { ALOGE("Wrong bitrate ratio - should be in increasing order."); return OMX_ErrorBadParameter; } } } mKeyFrameInterval = vp8AndroidParams->nKeyFrameInterval; mMinQuantizer = vp8AndroidParams->nMinQuantizer; mMaxQuantizer = vp8AndroidParams->nMaxQuantizer; memcpy(mTemporalLayerBitrateRatio, vp8AndroidParams->nTemporalLayerBitrateRatio, sizeof(mTemporalLayerBitrateRatio)); ALOGD("VP8: internalSetAndroidVp8Params. BRMode: %u. TS: %zu. KF: %u." " QP: %u - %u BR0: %u. BR1: %u. BR2: %u", (uint32_t)mBitrateControlMode, mTemporalLayers, mKeyFrameInterval, mMinQuantizer, mMaxQuantizer, mTemporalLayerBitrateRatio[0], mTemporalLayerBitrateRatio[1], mTemporalLayerBitrateRatio[2]); return OMX_ErrorNone; } OMX_ERRORTYPE SoftVPXEncoder::internalSetFormatParams( const OMX_VIDEO_PARAM_PORTFORMATTYPE* format) { if (format->nPortIndex == kInputPortIndex) { if (format->eColorFormat == OMX_COLOR_FormatYUV420Planar || format->eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar || format->eColorFormat == OMX_COLOR_FormatAndroidOpaque) { mColorFormat = format->eColorFormat; OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef; def->format.video.eColorFormat = mColorFormat; return OMX_ErrorNone; } else { ALOGE("Unsupported color format %i", format->eColorFormat); return OMX_ErrorUnsupportedSetting; } } else if (format->nPortIndex == kOutputPortIndex) { if (format->eCompressionFormat == OMX_VIDEO_CodingVP8) { return OMX_ErrorNone; } else { return OMX_ErrorUnsupportedSetting; } } else { return OMX_ErrorBadPortIndex; } } OMX_ERRORTYPE SoftVPXEncoder::internalSetRoleParams( const OMX_PARAM_COMPONENTROLETYPE* role) { const char* roleText = (const char*)role->cRole; const size_t roleTextMaxSize = OMX_MAX_STRINGNAME_SIZE - 1; if (strncmp(roleText, "video_encoder.vp8", roleTextMaxSize)) { ALOGE("Unsupported component role"); return OMX_ErrorBadParameter; } return OMX_ErrorNone; } OMX_ERRORTYPE SoftVPXEncoder::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.eColorFormat == OMX_COLOR_FormatYUV420Planar || port->format.video.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar || port->format.video.eColorFormat == OMX_COLOR_FormatAndroidOpaque) { mColorFormat = port->format.video.eColorFormat; } else { return OMX_ErrorUnsupportedSetting; } OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef; def->format.video.nFrameWidth = mWidth; def->format.video.nFrameHeight = mHeight; def->format.video.xFramerate = mFramerate; def->format.video.eColorFormat = mColorFormat; def = &editPortInfo(kOutputPortIndex)->mDef; def->format.video.nFrameWidth = mWidth; def->format.video.nFrameHeight = mHeight; return OMX_ErrorNone; } else if (port->nPortIndex == kOutputPortIndex) { mBitrate = port->format.video.nBitrate; mWidth = port->format.video.nFrameWidth; mHeight = port->format.video.nFrameHeight; OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef; def->format.video.nFrameWidth = mWidth; def->format.video.nFrameHeight = mHeight; def->format.video.nBitrate = mBitrate; return OMX_ErrorNone; } else { return OMX_ErrorBadPortIndex; } } OMX_ERRORTYPE SoftVPXEncoder::internalSetBitrateParams( const OMX_VIDEO_PARAM_BITRATETYPE* bitrate) { if (bitrate->nPortIndex != kOutputPortIndex) { return OMX_ErrorUnsupportedIndex; } mBitrate = bitrate->nTargetBitrate; if (bitrate->eControlRate == OMX_Video_ControlRateVariable) { mBitrateControlMode = VPX_VBR; } else if (bitrate->eControlRate == OMX_Video_ControlRateConstant) { mBitrateControlMode = VPX_CBR; } else { return OMX_ErrorUnsupportedSetting; } return OMX_ErrorNone; } vpx_enc_frame_flags_t SoftVPXEncoder::getEncodeFlags() { vpx_enc_frame_flags_t flags = 0; int patternIdx = mTemporalPatternIdx % mTemporalPatternLength; mTemporalPatternIdx++; switch (mTemporalPattern[patternIdx]) { case kTemporalUpdateLast: flags |= VP8_EFLAG_NO_UPD_GF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_REF_GF; flags |= VP8_EFLAG_NO_REF_ARF; break; case kTemporalUpdateGoldenWithoutDependency: flags |= VP8_EFLAG_NO_REF_GF; // Deliberately no break here. case kTemporalUpdateGolden: flags |= VP8_EFLAG_NO_REF_ARF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_LAST; break; case kTemporalUpdateAltrefWithoutDependency: flags |= VP8_EFLAG_NO_REF_ARF; flags |= VP8_EFLAG_NO_REF_GF; // Deliberately no break here. case kTemporalUpdateAltref: flags |= VP8_EFLAG_NO_UPD_GF; flags |= VP8_EFLAG_NO_UPD_LAST; break; case kTemporalUpdateNoneNoRefAltref: flags |= VP8_EFLAG_NO_REF_ARF; // Deliberately no break here. case kTemporalUpdateNone: flags |= VP8_EFLAG_NO_UPD_GF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_LAST; flags |= VP8_EFLAG_NO_UPD_ENTROPY; break; case kTemporalUpdateNoneNoRefGoldenRefAltRef: flags |= VP8_EFLAG_NO_REF_GF; flags |= VP8_EFLAG_NO_UPD_GF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_LAST; flags |= VP8_EFLAG_NO_UPD_ENTROPY; break; case kTemporalUpdateGoldenWithoutDependencyRefAltRef: flags |= VP8_EFLAG_NO_REF_GF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_LAST; break; case kTemporalUpdateLastRefAltRef: flags |= VP8_EFLAG_NO_UPD_GF; flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_REF_GF; break; case kTemporalUpdateGoldenRefAltRef: flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_LAST; break; case kTemporalUpdateLastAndGoldenRefAltRef: flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_REF_GF; break; case kTemporalUpdateLastRefAll: flags |= VP8_EFLAG_NO_UPD_ARF; flags |= VP8_EFLAG_NO_UPD_GF; break; } return flags; } void SoftVPXEncoder::onQueueFilled(OMX_U32 portIndex) { // Initialize encoder if not already if (mCodecContext == NULL) { if (OK != initEncoder()) { ALOGE("Failed to initialize encoder"); notify(OMX_EventError, OMX_ErrorUndefined, 0, // Extra notification data NULL); // Notification data pointer return; } } vpx_codec_err_t codec_return; List &inputBufferInfoQueue = getPortQueue(kInputPortIndex); List &outputBufferInfoQueue = getPortQueue(kOutputPortIndex); while (!inputBufferInfoQueue.empty() && !outputBufferInfoQueue.empty()) { BufferInfo *inputBufferInfo = *inputBufferInfoQueue.begin(); OMX_BUFFERHEADERTYPE *inputBufferHeader = inputBufferInfo->mHeader; BufferInfo *outputBufferInfo = *outputBufferInfoQueue.begin(); OMX_BUFFERHEADERTYPE *outputBufferHeader = outputBufferInfo->mHeader; if (inputBufferHeader->nFlags & OMX_BUFFERFLAG_EOS) { inputBufferInfoQueue.erase(inputBufferInfoQueue.begin()); inputBufferInfo->mOwnedByUs = false; notifyEmptyBufferDone(inputBufferHeader); outputBufferHeader->nFilledLen = 0; outputBufferHeader->nFlags = OMX_BUFFERFLAG_EOS; outputBufferInfoQueue.erase(outputBufferInfoQueue.begin()); outputBufferInfo->mOwnedByUs = false; notifyFillBufferDone(outputBufferHeader); return; } uint8_t *source = inputBufferHeader->pBuffer + inputBufferHeader->nOffset; if (mInputDataIsMeta) { CHECK_GE(inputBufferHeader->nFilledLen, 4 + sizeof(buffer_handle_t)); uint32_t bufferType = *(uint32_t *)source; CHECK_EQ(bufferType, kMetadataBufferTypeGrallocSource); 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 = *(buffer_handle_t *)(source + 4); void *bits; CHECK_EQ(0, grmodule->lock( grmodule, handle, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, 0, 0, mWidth, mHeight, &bits)); ConvertRGB32ToPlanar( (const uint8_t *)bits, mConversionBuffer, mWidth, mHeight); source = mConversionBuffer; CHECK_EQ(0, grmodule->unlock(grmodule, handle)); } else if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) { ConvertSemiPlanarToPlanar( source, mConversionBuffer, mWidth, mHeight); source = mConversionBuffer; } vpx_image_t raw_frame; vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, mWidth, mHeight, kInputBufferAlignment, source); vpx_enc_frame_flags_t flags = 0; if (mTemporalPatternLength > 0) { flags = getEncodeFlags(); } if (mKeyFrameRequested) { flags |= VPX_EFLAG_FORCE_KF; mKeyFrameRequested = false; } if (mBitrateUpdated) { mCodecConfiguration->rc_target_bitrate = mBitrate/1000; vpx_codec_err_t res = vpx_codec_enc_config_set(mCodecContext, mCodecConfiguration); if (res != VPX_CODEC_OK) { ALOGE("vp8 encoder failed to update bitrate: %s", vpx_codec_err_to_string(res)); notify(OMX_EventError, OMX_ErrorUndefined, 0, // Extra notification data NULL); // Notification data pointer } mBitrateUpdated = false; } uint32_t frameDuration; if (inputBufferHeader->nTimeStamp > mLastTimestamp) { frameDuration = (uint32_t)(inputBufferHeader->nTimeStamp - mLastTimestamp); } else { frameDuration = (uint32_t)(((uint64_t)1000000 << 16) / mFramerate); } mLastTimestamp = inputBufferHeader->nTimeStamp; codec_return = vpx_codec_encode( mCodecContext, &raw_frame, inputBufferHeader->nTimeStamp, // in timebase units frameDuration, // frame duration in timebase units flags, // frame flags VPX_DL_REALTIME); // encoding deadline if (codec_return != VPX_CODEC_OK) { ALOGE("vpx encoder failed to encode frame"); notify(OMX_EventError, OMX_ErrorUndefined, 0, // Extra notification data NULL); // Notification data pointer return; } vpx_codec_iter_t encoded_packet_iterator = NULL; const vpx_codec_cx_pkt_t* encoded_packet; while ((encoded_packet = vpx_codec_get_cx_data( mCodecContext, &encoded_packet_iterator))) { if (encoded_packet->kind == VPX_CODEC_CX_FRAME_PKT) { outputBufferHeader->nTimeStamp = encoded_packet->data.frame.pts; outputBufferHeader->nFlags = 0; if (encoded_packet->data.frame.flags & VPX_FRAME_IS_KEY) outputBufferHeader->nFlags |= OMX_BUFFERFLAG_SYNCFRAME; outputBufferHeader->nOffset = 0; outputBufferHeader->nFilledLen = encoded_packet->data.frame.sz; memcpy(outputBufferHeader->pBuffer, encoded_packet->data.frame.buf, encoded_packet->data.frame.sz); outputBufferInfo->mOwnedByUs = false; outputBufferInfoQueue.erase(outputBufferInfoQueue.begin()); notifyFillBufferDone(outputBufferHeader); } } inputBufferInfo->mOwnedByUs = false; inputBufferInfoQueue.erase(inputBufferInfoQueue.begin()); notifyEmptyBufferDone(inputBufferHeader); } } OMX_ERRORTYPE SoftVPXEncoder::getExtensionIndex( const char *name, OMX_INDEXTYPE *index) { if (!strcmp(name, "OMX.google.android.index.storeMetaDataInBuffers")) { *(int32_t*)index = kStoreMetaDataExtensionIndex; return OMX_ErrorNone; } return SimpleSoftOMXComponent::getExtensionIndex(name, index); } } // namespace android android::SoftOMXComponent *createSoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) { return new android::SoftVPXEncoder(name, callbacks, appData, component); }