/* * Copyright (C) 2016 Intel Corporation. All rights reserved. * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "vaapiencoder_vp9.h" #include "common/scopedlogger.h" #include "common/common_def.h" #include "vaapi/vaapicontext.h" #include "vaapi/vaapidisplay.h" #include "vaapicodedbuffer.h" #include "vaapiencpicture.h" #include namespace YamiMediaCodec { // each frame can have at most 3 reference frames, golden, alt and last enum maxSizeValues { kMaxReferenceFrames = 8, kMaxWidth = 4096, kMaxHeight = kMaxWidth, kMaxHeaderSize=kMaxWidth }; enum VP9QPValues { kMinQPValue = 9, kDefaultQPValue = 60, kMaxQPValue = 127 }; enum VP9FrameType { kKeyFrame, kInterFrame }; enum VP9LevelValues { kDefaultSharpnessLevel, kDefaultFilterLevel = 10 }; class VaapiEncPictureVP9 : public VaapiEncPicture { public: VaapiEncPictureVP9(const ContextPtr& context, const SurfacePtr& surface, int64_t timeStamp) : VaapiEncPicture(context, surface, timeStamp) { return; } VAGenericID getCodedBufferID() { return m_codedBuffer->getID(); } }; VaapiEncoderVP9::VaapiEncoderVP9() : m_frameCount(0) { m_videoParamCommon.profile = VAProfileVP9Profile0; m_videoParamCommon.rcParams.minQP = kMinQPValue; m_videoParamCommon.rcParams.maxQP = kMaxQPValue; m_videoParamCommon.rcParams.initQP = kDefaultQPValue; // add extra surfaces to operate due to kMaxReferenceFrames // vaapi_encoder class will create 5 extra surfaces already m_maxOutputBuffer = kMaxReferenceFrames; } VaapiEncoderVP9::~VaapiEncoderVP9() {} YamiStatus VaapiEncoderVP9::getMaxOutSize(uint32_t* maxSize) { FUNC_ENTER(); *maxSize = m_maxCodedbufSize; return YAMI_SUCCESS; } YamiStatus VaapiEncoderVP9::resetParams() { // intel driver cannot handle non 8-bit aligned resolutions, once // fixed this can go away if (width() != ALIGN8(width()) || height() != ALIGN8(height())) { ERROR("Input resolution %dx%d is not 8-bit aligned", width(), height()); return YAMI_INVALID_PARAM; } m_maxCodedbufSize = width() * height() * 3 / 2; // adding extra padding. In particular small resolutions require more // space depending on other quantization parameters during execution. The // value below is a good compromise m_maxCodedbufSize += kMaxHeaderSize; if (ipPeriod() == 0) m_videoParamCommon.intraPeriod = 1; return YAMI_SUCCESS; } YamiStatus VaapiEncoderVP9::start() { YamiStatus status; FUNC_ENTER(); status = resetParams(); if (status != YAMI_SUCCESS) return status; return VaapiEncoderBase::start(); } void VaapiEncoderVP9::flush() { FUNC_ENTER(); m_frameCount = 0; m_reference.clear(); VaapiEncoderBase::flush(); } YamiStatus VaapiEncoderVP9::stop() { flush(); return VaapiEncoderBase::stop(); } YamiStatus VaapiEncoderVP9::setParameters(VideoParamConfigType type, Yami_PTR videoEncParams) { YamiStatus status = YAMI_INVALID_PARAM; if (!videoEncParams) return YAMI_INVALID_PARAM; switch (type) { case VideoParamsTypeVP9: { VideoParamsVP9* vp9Params = (VideoParamsVP9*)videoEncParams; PARAMETER_ASSIGN(m_videoParamsVP9, *vp9Params); status = YAMI_SUCCESS; } break; default: status = VaapiEncoderBase::setParameters(type, videoEncParams); break; } return status; } YamiStatus VaapiEncoderVP9::getParameters(VideoParamConfigType type, Yami_PTR videoEncParams) { YamiStatus status = YAMI_INVALID_PARAM; if (!videoEncParams) return status; switch (type) { case VideoParamsTypeVP9: { VideoParamsVP9* vp9Params = (VideoParamsVP9*)videoEncParams; PARAMETER_ASSIGN(*vp9Params, m_videoParamsVP9); status = YAMI_SUCCESS; } break; default: status = VaapiEncoderBase::getParameters(type, videoEncParams); break; } return status; } YamiStatus VaapiEncoderVP9::doEncode(const SurfacePtr& surface, uint64_t timeStamp, bool forceKeyFrame) { YamiStatus ret; if (!surface) return YAMI_INVALID_PARAM; PicturePtr picture(new VaapiEncPictureVP9(m_context, surface, timeStamp)); if (!(m_frameCount % keyFramePeriod()) || forceKeyFrame) picture->m_type = VAAPI_PICTURE_I; else picture->m_type = VAAPI_PICTURE_P; m_frameCount++; CodedBufferPtr codedBuffer = VaapiCodedBuffer::create(m_context, m_maxCodedbufSize); if (!codedBuffer) return YAMI_OUT_MEMORY; picture->m_codedBuffer = codedBuffer; codedBuffer->setFlag(ENCODE_BUFFERFLAG_ENDOFFRAME); if (picture->m_type == VAAPI_PICTURE_I) { codedBuffer->setFlag(ENCODE_BUFFERFLAG_SYNCFRAME); } ret = encodePicture(picture); if (ret != YAMI_SUCCESS) { return ret; } output(picture); return YAMI_SUCCESS; } bool VaapiEncoderVP9::fill(VAEncSequenceParameterBufferVP9* seqParam) const { seqParam->max_frame_width = kMaxWidth; seqParam->max_frame_height = kMaxHeight; seqParam->bits_per_second = bitRate(); seqParam->intra_period = intraPeriod(); seqParam->kf_min_dist = 1; seqParam->kf_max_dist = intraPeriod(); return true; } // Fills in VA picture parameter buffer bool VaapiEncoderVP9::fill(VAEncPictureParameterBufferVP9* picParam, const PicturePtr& picture, const SurfacePtr& surface) { picParam->reconstructed_frame = surface->getID(); picParam->coded_buf = picture->getCodedBufferID(); if (picture->m_type == VAAPI_PICTURE_I) { for (uint32_t i = 0; i < kMaxReferenceFrames ; i++) picParam->reference_frames[i] = VA_INVALID_SURFACE; } else { picParam->pic_flags.bits.frame_type = kInterFrame; ReferenceQueue::const_iterator it = m_reference.begin(); for (uint32_t i = 0; it != m_reference.end(); ++it, i++) { picParam->reference_frames[i] = (*it)->getID(); DEBUG("reference frame[%d] 0x%x", i, (*it)->getID()); } picParam->pic_flags.bits.frame_context_idx = 0; // last/golden/alt is used as reference frame. L0 forward picParam->ref_flags.bits.ref_frame_ctrl_l0 = 0x7; // golden and alt are last KeyFrame // last is last decoded frame if (getReferenceMode()) { // intel driver updates a new slot with every new frame and keeps // the reference frames in a circular buffer, the buffer is defined // with 8 slots but it is up to the application to use as many as // desired. This scheme implements the use of 3 slots for // last/gold/alt refs m_currentReferenceIndex = (m_currentReferenceIndex + 1) % 3; // set to refresh next slot with current reconstructed surface picParam->refresh_frame_flags = 1 << m_currentReferenceIndex; // assign the references on only 3 slots picParam->ref_flags.bits.ref_arf_idx = m_currentReferenceIndex; picParam->ref_flags.bits.ref_gf_idx = m_currentReferenceIndex + 1; picParam->ref_flags.bits.ref_last_idx = m_currentReferenceIndex - 1; if (m_currentReferenceIndex == 0) { picParam->ref_flags.bits.ref_last_idx = m_currentReferenceIndex + 2; } else if (m_currentReferenceIndex == 2) { picParam->ref_flags.bits.ref_gf_idx = m_currentReferenceIndex - 2; } } else { picParam->refresh_frame_flags = 0x01; // refresh last frame picParam->ref_flags.bits.ref_last_idx = 0; picParam->ref_flags.bits.ref_gf_idx = 1; picParam->ref_flags.bits.ref_arf_idx = 2; } } picParam->frame_width_src = width(); picParam->frame_height_src = height(); picParam->frame_width_dst = width(); picParam->frame_height_dst = height(); picParam->pic_flags.bits.show_frame = 1; picParam->luma_ac_qindex = (initQP() >= minQP() && initQP() <= maxQP()) ? initQP() : kDefaultQPValue; picParam->luma_dc_qindex_delta = 1; picParam->chroma_ac_qindex_delta = 1; picParam->chroma_dc_qindex_delta = 1; picParam->filter_level = kDefaultFilterLevel; picParam->sharpness_level = kDefaultSharpnessLevel; return true; } bool VaapiEncoderVP9::fill( VAEncMiscParameterTypeVP9PerSegmantParam* segParam) const { return true; } bool VaapiEncoderVP9::ensureSequence(const PicturePtr& picture) { if (picture->m_type != VAAPI_PICTURE_I) return true; VAEncSequenceParameterBufferVP9* seqParam; if (!picture->editSequence(seqParam) || !fill(seqParam)) { ERROR("failed to create sequence parameter buffer (SPS)"); return false; } return true; } bool VaapiEncoderVP9::ensurePicture(const PicturePtr& picture, const SurfacePtr& surface) { VAEncPictureParameterBufferVP9* picParam; if (!picture->editPicture(picParam) || !fill(picParam, picture, surface)) { ERROR("failed to create picture parameter buffer (PPS)"); return false; } return true; } bool VaapiEncoderVP9::ensureQMatrix(const PicturePtr& picture) { VAEncMiscParameterTypeVP9PerSegmantParam* segmentParam; if (picture->m_type != VAAPI_PICTURE_I) return true; if (!picture->editQMatrix(segmentParam) || !fill(segmentParam)) { ERROR("failed to create qMatrix"); return false; } return true; } bool VaapiEncoderVP9::referenceListUpdate(const PicturePtr& pic, const SurfacePtr& recon) { if (pic->m_type == VAAPI_PICTURE_I) { m_reference.clear(); m_reference.insert(m_reference.end(), kMaxReferenceFrames, recon); if (getReferenceMode()) m_currentReferenceIndex = 0; } else { if (getReferenceMode()) { ReferenceQueue::iterator it = m_reference.begin() + m_currentReferenceIndex; (*it) = recon; #if __ENABLE_DEBUG__ for (it = m_reference.begin(); it != m_reference.end(); ++it) DEBUG("Update ref frames 0x%x", (*it)->getID()); #endif } else { m_reference.pop_front(); m_reference.push_front(recon); } } return true; } YamiStatus VaapiEncoderVP9::encodePicture(const PicturePtr& picture) { YamiStatus ret = YAMI_FAIL; SurfacePtr reconstruct = createSurface(); if (!reconstruct) return ret; if (!ensureSequence(picture)) return ret; if (!ensureQMatrix(picture)) return ret; if (!ensureMiscParams(picture.get())) return ret; if (!ensurePicture(picture, reconstruct)) return ret; if (!picture->encode()) return ret; if (!referenceListUpdate(picture, reconstruct)) return ret; return YAMI_SUCCESS; } }