/* * Copyright (C) 2019 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 #include #include #include #include "host/libs/config/cuttlefish_config.h" #include "vpx/vpx_encoder.h" #include "vpx/vpx_codec.h" #include "vpx/vp8cx.h" #include #define ENABLE_LOGGING 0 namespace android { namespace { int64_t GetNowUs() { auto now = std::chrono::steady_clock::now().time_since_epoch(); return std::chrono::duration_cast(now).count(); } } struct FrameBufferSource::Encoder { Encoder() = default; virtual ~Encoder() = default; virtual void forceIDRFrame() = 0; virtual bool isForcingIDRFrame() const = 0; virtual void storeFrame(const void* frame) = 0; virtual std::shared_ptr encodeStoredFrame(int64_t timeUs) = 0; }; //////////////////////////////////////////////////////////////////////////////// struct FrameBufferSource::VPXEncoder : public FrameBufferSource::Encoder { VPXEncoder(int width, int height, int rateHz); ~VPXEncoder() override; void forceIDRFrame() override; bool isForcingIDRFrame() const override; void storeFrame(const void* frame) override; std::shared_ptr encodeStoredFrame(int64_t timeUs) override; private: int mWidth, mHeight, mRefreshRateHz; int mSizeY, mSizeUV; void *mI420Data; vpx_codec_iface_t *mCodecInterface; std::unique_ptr mCodecConfiguration; using contextFreeFunc = std::function; std::unique_ptr mCodecContext; std::atomic mForceIDRFrame; bool mFirstFrame; bool mStoredFrame; int64_t mLastTimeUs; }; static int GetCPUCoreCount() { int cpuCoreCount; #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; } FrameBufferSource::VPXEncoder::VPXEncoder(int width, int height, int rateHz) : mWidth(width), mHeight(height), mRefreshRateHz(rateHz), mCodecContext(nullptr, vpx_codec_destroy), mForceIDRFrame(false), mFirstFrame(true), mStoredFrame(false), mLastTimeUs(0) { CHECK((width & 1) == 0); CHECK((height & 1) == 0); mSizeY = width * height; mSizeUV = (width / 2) * (height / 2); size_t totalSize = mSizeY + 2 * mSizeUV; mI420Data = malloc(totalSize); mCodecInterface = vpx_codec_vp8_cx(); mCodecConfiguration = std::make_unique(); auto res = vpx_codec_enc_config_default( mCodecInterface, mCodecConfiguration.get(), 0 /* usage */); mCodecConfiguration->g_w = width; mCodecConfiguration->g_h = height; mCodecConfiguration->g_threads = std::min(GetCPUCoreCount(), 64); mCodecConfiguration->g_error_resilient = false; mCodecConfiguration->g_timebase.num = 1; mCodecConfiguration->g_timebase.den = 1000000; mCodecConfiguration->rc_target_bitrate = 2500; // This appears to match x264 mCodecConfiguration->rc_end_usage = VPX_VBR; mCodecConfiguration->rc_dropframe_thresh = 0; mCodecConfiguration->g_lag_in_frames = 0; mCodecConfiguration->g_profile = 0; CHECK_EQ(res, VPX_CODEC_OK); mCodecContext.reset(new vpx_codec_ctx_t); res = vpx_codec_enc_init( mCodecContext.get(), mCodecInterface, mCodecConfiguration.get(), 0 /* flags */); CHECK_EQ(res, VPX_CODEC_OK); res = vpx_codec_control(mCodecContext.get(), VP8E_SET_TOKEN_PARTITIONS, 0); CHECK_EQ(res, VPX_CODEC_OK); } FrameBufferSource::VPXEncoder::~VPXEncoder() { free(mI420Data); mI420Data = nullptr; } void FrameBufferSource::VPXEncoder::forceIDRFrame() { mForceIDRFrame = true; } bool FrameBufferSource::VPXEncoder::isForcingIDRFrame() const { return mForceIDRFrame; } void FrameBufferSource::VPXEncoder::storeFrame(const void *frame) { uint8_t *yPlane = static_cast(mI420Data); uint8_t *uPlane = yPlane + mSizeY; uint8_t *vPlane = uPlane + mSizeUV; libyuv::ABGRToI420( static_cast(frame), mWidth * 4, yPlane, mWidth, uPlane, mWidth / 2, vPlane, mWidth / 2, mWidth, mHeight); mStoredFrame = true; } std::shared_ptr FrameBufferSource::VPXEncoder::encodeStoredFrame( int64_t timeUs) { if (!mStoredFrame) { return nullptr; } vpx_image_t raw_frame; vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, mWidth, mHeight, 2 /* stride_align */, reinterpret_cast(mI420Data)); vpx_enc_frame_flags_t flags = 0; if (mForceIDRFrame.exchange(false)) { flags |= VPX_EFLAG_FORCE_KF; } uint32_t frameDuration; if (!mFirstFrame) { frameDuration = timeUs - mLastTimeUs; } else { frameDuration = 1000000 / mRefreshRateHz; mFirstFrame = false; } mLastTimeUs = timeUs; auto res = vpx_codec_encode( mCodecContext.get(), &raw_frame, timeUs, frameDuration, flags, VPX_DL_REALTIME); if (res != VPX_CODEC_OK) { LOG(ERROR) << "vpx_codec_encode failed w/ " << res; return nullptr; } vpx_codec_iter_t iter = nullptr; const vpx_codec_cx_pkt_t *packet; std::shared_ptr accessUnit; while ((packet = vpx_codec_get_cx_data(mCodecContext.get(), &iter)) != nullptr) { if (packet->kind == VPX_CODEC_CX_FRAME_PKT) { LOG(VERBOSE) << "vpx_codec_encode returned packet of size " << packet->data.frame.sz; if (accessUnit != nullptr) { LOG(ERROR) << "vpx_codec_encode returned more than one packet of " "compressed data!"; return nullptr; } accessUnit.reset(new SBuffer(packet->data.frame.sz)); memcpy(accessUnit->data(), packet->data.frame.buf, packet->data.frame.sz); accessUnit->time_us(timeUs); } else { LOG(INFO) << "vpx_codec_encode returned a packet of type " << packet->kind; } } return accessUnit; } //////////////////////////////////////////////////////////////////////////////// FrameBufferSource::FrameBufferSource(Format format) : mInitCheck(-ENODEV), mState(STOPPED), mFormat(format), mScreenWidth(0), mScreenHeight(0), mScreenDpi(0), mScreenRate(0), mNumConsumers(0), mOnFrameFn(nullptr) { mInitCheck = 0; } FrameBufferSource::~FrameBufferSource() { stop(); } int32_t FrameBufferSource::initCheck() const { return mInitCheck; } int32_t FrameBufferSource::start() { std::lock_guard autoLock(mLock); if (mState != STOPPED) { return 0; } switch (mFormat) { case Format::VP8: { mEncoder.reset( new VPXEncoder(mScreenWidth, mScreenHeight, mScreenRate)); break; } default: LOG(FATAL) << "Should not be here."; } mState = RUNNING; return 0; } int32_t FrameBufferSource::stop() { std::lock_guard autoLock(mLock); if (mState == STOPPED) { return 0; } mState = STOPPING; mState = STOPPED; mEncoder.reset(); return 0; } int32_t FrameBufferSource::pause() { std::lock_guard autoLock(mLock); if (mState == PAUSED) { return 0; } if (mState != RUNNING) { return -EINVAL; } mState = PAUSED; LOG(VERBOSE) << "Now paused."; return 0; } int32_t FrameBufferSource::resume() { std::lock_guard autoLock(mLock); if (mState == RUNNING) { return 0; } if (mState != PAUSED) { return -EINVAL; } mState = RUNNING; LOG(VERBOSE) << "Now running."; return 0; } bool FrameBufferSource::paused() const { return mState == PAUSED; } int32_t FrameBufferSource::requestIDRFrame() { mEncoder->forceIDRFrame(); return 0; } void FrameBufferSource::setScreenParams(const int32_t screenParams[4]) { mScreenWidth = screenParams[0]; mScreenHeight = screenParams[1]; mScreenDpi = screenParams[2]; mScreenRate = screenParams[3]; } void FrameBufferSource::injectFrame(const void *data, size_t size) { // Only used in the case of CrosVM operation. (void)size; std::lock_guard autoLock(mLock); if (mState != State::RUNNING) { return; } mEncoder->storeFrame(data); // Only encode and forward the frame when there are consumers connected if (mNumConsumers) { auto accessUnit = mEncoder->encodeStoredFrame(GetNowUs()); StreamingSource::onAccessUnit(accessUnit); } } void FrameBufferSource::notifyNewStreamConsumer() { std::lock_guard autoLock(mLock); ++mNumConsumers; if (mState != State::RUNNING) { return; } mEncoder->forceIDRFrame(); auto accessUnit = mEncoder->encodeStoredFrame(GetNowUs()); if (!accessUnit) { // nullptr means there isn't a stored frame to encode. return; } StreamingSource::onAccessUnit(accessUnit); } void FrameBufferSource::notifyStreamConsumerDisconnected() { std::lock_guard autoLock(mLock); --mNumConsumers; } } // namespace android