/* * Copyright 2018 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 #ifdef MPEG4 #define LOG_TAG "C2SoftMpeg4Enc" #else #define LOG_TAG "C2SoftH263Enc" #endif #include #include #include #include #include #include #include #include #include #include #include "C2SoftMpeg4Enc.h" #include "mp4enc_api.h" namespace android { namespace { #ifdef MPEG4 constexpr char COMPONENT_NAME[] = "c2.android.mpeg4.encoder"; const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_MPEG4; #else constexpr char COMPONENT_NAME[] = "c2.android.h263.encoder"; const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_H263; #endif } // namepsace class C2SoftMpeg4Enc::IntfImpl : public SimpleInterface::BaseParams { public: explicit IntfImpl(const std::shared_ptr &helper) : SimpleInterface::BaseParams( helper, COMPONENT_NAME, C2Component::KIND_ENCODER, C2Component::DOMAIN_VIDEO, MEDIA_MIMETYPE_VIDEO) { noPrivateBuffers(); // TODO: account for our buffers here noInputReferences(); noOutputReferences(); noInputLatency(); noTimeStretch(); setDerivedInstance(this); addParameter( DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) .withConstValue(new C2ComponentAttributesSetting( C2Component::ATTRIB_IS_TEMPORAL)) .build()); addParameter( DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE) .withConstValue(new C2StreamUsageTuning::input( 0u, (uint64_t)C2MemoryUsage::CPU_READ)) .build()); addParameter( DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) .withDefault(new C2StreamPictureSizeInfo::input(0u, 176, 144)) .withFields({ #ifdef MPEG4 C2F(mSize, width).inRange(16, 176, 16), C2F(mSize, height).inRange(16, 144, 16), #else C2F(mSize, width).oneOf({176, 352}), C2F(mSize, height).oneOf({144, 288}), #endif }) .withSetter(SizeSetter) .build()); addParameter( DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE) .withDefault(new C2StreamFrameRateInfo::output(0u, 17.)) // TODO: More restriction? .withFields({C2F(mFrameRate, value).greaterThan(0.)}) .withSetter( Setter::StrictValueWithNoDeps) .build()); addParameter( DefineParam(mBitrate, C2_PARAMKEY_BITRATE) .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) .withFields({C2F(mBitrate, value).inRange(4096, 12000000)}) .withSetter(BitrateSetter) .build()); addParameter( DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL) .withDefault(new C2StreamSyncFrameIntervalTuning::output(0u, 1000000)) .withFields({C2F(mSyncFramePeriod, value).any()}) .withSetter(Setter::StrictValueWithNoDeps) .build()); #ifdef MPEG4 addParameter( DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) .withDefault(new C2StreamProfileLevelInfo::output( 0u, PROFILE_MP4V_SIMPLE, LEVEL_MP4V_2)) .withFields({ C2F(mProfileLevel, profile).equalTo( PROFILE_MP4V_SIMPLE), C2F(mProfileLevel, level).oneOf({ C2Config::LEVEL_MP4V_0, C2Config::LEVEL_MP4V_0B, C2Config::LEVEL_MP4V_1, C2Config::LEVEL_MP4V_2}) }) .withSetter(ProfileLevelSetter) .build()); #else addParameter( DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) .withDefault(new C2StreamProfileLevelInfo::output( 0u, PROFILE_H263_BASELINE, LEVEL_H263_45)) .withFields({ C2F(mProfileLevel, profile).equalTo( PROFILE_H263_BASELINE), C2F(mProfileLevel, level).oneOf({ C2Config::LEVEL_H263_10, C2Config::LEVEL_H263_20, C2Config::LEVEL_H263_30, C2Config::LEVEL_H263_40, C2Config::LEVEL_H263_45}) }) .withSetter(ProfileLevelSetter) .build()); #endif } static C2R BitrateSetter(bool mayBlock, C2P &me) { (void)mayBlock; C2R res = C2R::Ok(); if (me.v.value <= 4096) { me.set().value = 4096; } return res; } static C2R SizeSetter(bool mayBlock, const C2P &oldMe, C2P &me) { (void)mayBlock; C2R res = C2R::Ok(); if (!me.F(me.v.width).supportsAtAll(me.v.width)) { res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); me.set().width = oldMe.v.width; } if (!me.F(me.v.height).supportsAtAll(me.v.height)) { res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); me.set().height = oldMe.v.height; } return res; } static C2R ProfileLevelSetter( bool mayBlock, C2P &me) { (void)mayBlock; if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) { #ifdef MPEG4 me.set().profile = PROFILE_MP4V_SIMPLE; #else me.set().profile = PROFILE_H263_BASELINE; #endif } if (!me.F(me.v.level).supportsAtAll(me.v.level)) { #ifdef MPEG4 me.set().level = LEVEL_MP4V_2; #else me.set().level = LEVEL_H263_45; #endif } return C2R::Ok(); } // unsafe getters std::shared_ptr getSize_l() const { return mSize; } std::shared_ptr getFrameRate_l() const { return mFrameRate; } std::shared_ptr getBitrate_l() const { return mBitrate; } uint32_t getSyncFramePeriod() const { if (mSyncFramePeriod->value < 0 || mSyncFramePeriod->value == INT64_MAX) { return 0; } double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value; return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.); } private: std::shared_ptr mUsage; std::shared_ptr mSize; std::shared_ptr mFrameRate; std::shared_ptr mBitrate; std::shared_ptr mProfileLevel; std::shared_ptr mSyncFramePeriod; }; C2SoftMpeg4Enc::C2SoftMpeg4Enc(const char* name, c2_node_id_t id, const std::shared_ptr& intfImpl) : SimpleC2Component( std::make_shared>(name, id, intfImpl)), mIntf(intfImpl), mHandle(nullptr), mEncParams(nullptr), mStarted(false), mOutBufferSize(524288) { } C2SoftMpeg4Enc::~C2SoftMpeg4Enc() { onRelease(); } c2_status_t C2SoftMpeg4Enc::onInit() { #ifdef MPEG4 mEncodeMode = COMBINE_MODE_WITH_ERR_RES; #else mEncodeMode = H263_MODE; #endif if (!mHandle) { mHandle = new tagvideoEncControls; } if (!mEncParams) { mEncParams = new tagvideoEncOptions; } if (!(mEncParams && mHandle)) return C2_NO_MEMORY; mSignalledOutputEos = false; mSignalledError = false; return initEncoder(); } c2_status_t C2SoftMpeg4Enc::onStop() { if (!mStarted) { return C2_OK; } if (mHandle) { (void)PVCleanUpVideoEncoder(mHandle); } mStarted = false; mSignalledOutputEos = false; mSignalledError = false; return C2_OK; } void C2SoftMpeg4Enc::onReset() { onStop(); initEncoder(); } void C2SoftMpeg4Enc::onRelease() { onStop(); if (mEncParams) { delete mEncParams; mEncParams = nullptr; } if (mHandle) { delete mHandle; mHandle = nullptr; } } c2_status_t C2SoftMpeg4Enc::onFlush_sm() { return C2_OK; } static void fillEmptyWork(const std::unique_ptr &work) { uint32_t flags = 0; if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { flags |= C2FrameData::FLAG_END_OF_STREAM; ALOGV("signalling eos"); } work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; work->workletsProcessed = 1u; } c2_status_t C2SoftMpeg4Enc::initEncParams() { if (mHandle) { memset(mHandle, 0, sizeof(tagvideoEncControls)); } else return C2_CORRUPTED; if (mEncParams) { memset(mEncParams, 0, sizeof(tagvideoEncOptions)); } else return C2_CORRUPTED; if (!PVGetDefaultEncOption(mEncParams, 0)) { ALOGE("Failed to get default encoding parameters"); return C2_CORRUPTED; } if (mFrameRate->value == 0) { ALOGE("Framerate should not be 0"); return C2_BAD_VALUE; } mEncParams->encMode = mEncodeMode; mEncParams->encWidth[0] = mSize->width; mEncParams->encHeight[0] = mSize->height; mEncParams->encFrameRate[0] = mFrameRate->value + 0.5; mEncParams->rcType = VBR_1; mEncParams->vbvDelay = 5.0f; mEncParams->profile_level = CORE_PROFILE_LEVEL2; mEncParams->packetSize = 32; mEncParams->rvlcEnable = PV_OFF; mEncParams->numLayers = 1; mEncParams->timeIncRes = 1000; mEncParams->tickPerSrc = mEncParams->timeIncRes / (mFrameRate->value + 0.5); mEncParams->bitRate[0] = mBitrate->value; mEncParams->iQuant[0] = 15; mEncParams->pQuant[0] = 12; mEncParams->quantType[0] = 0; mEncParams->noFrameSkipped = PV_OFF; // PV's MPEG4 encoder requires the video dimension of multiple if (mSize->width % 16 != 0 || mSize->height % 16 != 0) { ALOGE("Video frame size %dx%d must be a multiple of 16", mSize->width, mSize->height); return C2_BAD_VALUE; } // Set IDR frame refresh interval mEncParams->intraPeriod = mIntf->getSyncFramePeriod(); 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 C2_OK; } c2_status_t C2SoftMpeg4Enc::initEncoder() { if (mStarted) { return C2_OK; } { IntfImpl::Lock lock = mIntf->lock(); mSize = mIntf->getSize_l(); mBitrate = mIntf->getBitrate_l(); mFrameRate = mIntf->getFrameRate_l(); } c2_status_t err = initEncParams(); if (C2_OK != err) { ALOGE("Failed to initialized encoder params"); mSignalledError = true; return err; } if (!PVInitVideoEncoder(mHandle, mEncParams)) { ALOGE("Failed to initialize the encoder"); mSignalledError = true; return C2_CORRUPTED; } // 1st buffer for codec specific data mNumInputFrames = -1; mStarted = true; return C2_OK; } void C2SoftMpeg4Enc::process( const std::unique_ptr &work, const std::shared_ptr &pool) { // Initialize output work work->result = C2_OK; work->workletsProcessed = 1u; work->worklets.front()->output.flags = work->input.flags; if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; } // Initialize encoder if not already initialized if (!mStarted && C2_OK != initEncoder()) { ALOGE("Failed to initialize encoder"); mSignalledError = true; work->result = C2_CORRUPTED; return; } std::shared_ptr block; C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); if (err != C2_OK) { ALOGE("fetchLinearBlock for Output failed with status %d", err); work->result = C2_NO_MEMORY; return; } C2WriteView wView = block->map().get(); if (wView.error()) { ALOGE("write view map failed %d", wView.error()); work->result = wView.error(); return; } uint8_t *outPtr = (uint8_t *)wView.data(); if (mNumInputFrames < 0) { // The very first thing we want to output is the codec specific data. int32_t outputSize = mOutBufferSize; if (!PVGetVolHeader(mHandle, outPtr, &outputSize, 0)) { ALOGE("Failed to get VOL header"); mSignalledError = true; work->result = C2_CORRUPTED; return; } else { ALOGV("Bytes Generated in header %d\n", outputSize); } ++mNumInputFrames; std::unique_ptr csd = C2StreamInitDataInfo::output::AllocUnique(outputSize, 0u); if (!csd) { ALOGE("CSD allocation failed"); mSignalledError = true; work->result = C2_NO_MEMORY; return; } memcpy(csd->m.value, outPtr, outputSize); work->worklets.front()->output.configUpdate.push_back(std::move(csd)); } std::shared_ptr rView; std::shared_ptr inputBuffer; bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); if (!work->input.buffers.empty()) { inputBuffer = work->input.buffers[0]; rView = std::make_shared( inputBuffer->data().graphicBlocks().front().map().get()); if (rView->error() != C2_OK) { ALOGE("graphic view map err = %d", rView->error()); work->result = rView->error(); return; } } else { fillEmptyWork(work); if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); } return; } uint64_t inputTimeStamp = work->input.ordinal.timestamp.peekull(); const C2ConstGraphicBlock inBuffer = inputBuffer->data().graphicBlocks().front(); if (inBuffer.width() < mSize->width || inBuffer.height() < mSize->height) { /* Expect width height to be configured */ ALOGW("unexpected Capacity Aspect %d(%d) x %d(%d)", inBuffer.width(), mSize->width, inBuffer.height(), mSize->height); work->result = C2_BAD_VALUE; return; } const C2PlanarLayout &layout = rView->layout(); uint8_t *yPlane = const_cast(rView->data()[C2PlanarLayout::PLANE_Y]); uint8_t *uPlane = const_cast(rView->data()[C2PlanarLayout::PLANE_U]); uint8_t *vPlane = const_cast(rView->data()[C2PlanarLayout::PLANE_V]); int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc; uint32_t width = mSize->width; uint32_t height = mSize->height; // width and height are always even (as block size is 16x16) CHECK_EQ((width & 1u), 0u); CHECK_EQ((height & 1u), 0u); size_t yPlaneSize = width * height; switch (layout.type) { case C2PlanarLayout::TYPE_RGB: [[fallthrough]]; case C2PlanarLayout::TYPE_RGBA: { MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); yPlane = conversionBuffer.data(); uPlane = yPlane + yPlaneSize; vPlane = uPlane + yPlaneSize / 4; yStride = width; uStride = vStride = width / 2; ConvertRGBToPlanarYUV(yPlane, yStride, height, conversionBuffer.size(), *rView.get()); break; } case C2PlanarLayout::TYPE_YUV: { if (!IsYUV420(*rView)) { ALOGE("input is not YUV420"); work->result = C2_BAD_VALUE; break; } if (layout.planes[layout.PLANE_Y].colInc == 1 && layout.planes[layout.PLANE_U].colInc == 1 && layout.planes[layout.PLANE_V].colInc == 1 && uStride == vStride && yStride == 2 * vStride) { // I420 compatible - planes are already set up above break; } // copy to I420 MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); MediaImage2 img = CreateYUV420PlanarMediaImage2(width, height, width, height); status_t err = ImageCopy(conversionBuffer.data(), &img, *rView); if (err != OK) { ALOGE("Buffer conversion failed: %d", err); work->result = C2_BAD_VALUE; return; } yPlane = conversionBuffer.data(); uPlane = yPlane + yPlaneSize; vPlane = uPlane + yPlaneSize / 4; yStride = width; uStride = vStride = width / 2; break; } case C2PlanarLayout::TYPE_YUVA: ALOGE("YUVA plane type is not supported"); work->result = C2_BAD_VALUE; return; default: ALOGE("Unrecognized plane type: %d", layout.type); work->result = C2_BAD_VALUE; return; } CHECK(NULL != yPlane); /* Encode frames */ VideoEncFrameIO vin, vout; memset(&vin, 0, sizeof(vin)); memset(&vout, 0, sizeof(vout)); vin.yChan = yPlane; vin.uChan = uPlane; vin.vChan = vPlane; vin.timestamp = (inputTimeStamp + 500) / 1000; // in ms vin.height = align(height, 16); vin.pitch = align(width, 16); uint32_t modTimeMs = 0; int32_t nLayer = 0; MP4HintTrack hintTrack; int32_t outputSize = mOutBufferSize; if (!PVEncodeVideoFrame(mHandle, &vin, &vout, &modTimeMs, outPtr, &outputSize, &nLayer) || !PVGetHintTrack(mHandle, &hintTrack)) { ALOGE("Failed to encode frame or get hint track at frame %" PRId64, mNumInputFrames); mSignalledError = true; work->result = C2_CORRUPTED; return; } ALOGV("outputSize filled : %d", outputSize); ++mNumInputFrames; CHECK(NULL == PVGetOverrunBuffer(mHandle)); fillEmptyWork(work); if (outputSize) { std::shared_ptr buffer = createLinearBuffer(block, 0, outputSize); work->worklets.front()->output.ordinal.timestamp = inputTimeStamp; if (hintTrack.CodeType == 0) { buffer->setInfo(std::make_shared( 0u /* stream id */, C2Config::SYNC_FRAME)); } work->worklets.front()->output.buffers.push_back(buffer); } if (eos) { mSignalledOutputEos = true; } mConversionBuffersInUse.erase(yPlane); } c2_status_t C2SoftMpeg4Enc::drain( uint32_t drainMode, const std::shared_ptr &pool) { (void)pool; if (drainMode == NO_DRAIN) { ALOGW("drain with NO_DRAIN: no-op"); return C2_OK; } if (drainMode == DRAIN_CHAIN) { ALOGW("DRAIN_CHAIN not supported"); return C2_OMITTED; } return C2_OK; } class C2SoftMpeg4EncFactory : public C2ComponentFactory { public: C2SoftMpeg4EncFactory() : mHelper(std::static_pointer_cast( GetCodec2PlatformComponentStore()->getParamReflector())) {} virtual c2_status_t createComponent( c2_node_id_t id, std::shared_ptr* const component, std::function deleter) override { *component = std::shared_ptr( new C2SoftMpeg4Enc( COMPONENT_NAME, id, std::make_shared(mHelper)), deleter); return C2_OK; } virtual c2_status_t createInterface( c2_node_id_t id, std::shared_ptr* const interface, std::function deleter) override { *interface = std::shared_ptr( new SimpleInterface( COMPONENT_NAME, id, std::make_shared(mHelper)), deleter); return C2_OK; } virtual ~C2SoftMpeg4EncFactory() override = default; private: std::shared_ptr mHelper; }; } // namespace android extern "C" ::C2ComponentFactory* CreateCodec2Factory() { ALOGV("in %s", __func__); return new ::android::C2SoftMpeg4EncFactory(); } extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { ALOGV("in %s", __func__); delete factory; }