Snap for 9679998 from ccca500200c421d3aefd6b451f4256fa4a47a3c4 to sdk-release
Change-Id: Ia5476d5e12298eff6c12b36df323ad616f00f319
diff --git a/Android.bp b/Android.bp
index 60ffc69..64a9333 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,3 +1,32 @@
+package {
+ default_applicable_licenses: ["hardware_google_aemu_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "hardware_google_aemu_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ "SPDX-license-identifier-MIT",
+ ],
+ // large-scale-change unable to identify any license_text files
+}
+
cc_library_headers {
name: "aemu_common_headers",
host_supported: true,
diff --git a/base/Android.bp b/base/Android.bp
index 27fa87e..e767958 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_google_aemu_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_google_aemu_license"],
+}
+
cc_library_static {
name: "gfxstream_base",
defaults: ["gfxstream_defaults"],
diff --git a/base/Tracing.cpp b/base/Tracing.cpp
index 7f372bc..2d9df3a 100644
--- a/base/Tracing.cpp
+++ b/base/Tracing.cpp
@@ -67,35 +67,35 @@
#endif
__attribute__((always_inline)) void beginTrace(const char* name) {
- if (CC_LIKELY(*tracingDisabledPtr)) return;
+ if (CC_LIKELY(!tracingDisabledPtr)) return;
#ifdef USE_PERFETTO_TRACING
virtualdeviceperfetto::beginTrace(name);
#endif
}
__attribute__((always_inline)) void endTrace() {
- if (CC_LIKELY(*tracingDisabledPtr)) return;
+ if (CC_LIKELY(!tracingDisabledPtr)) return;
#ifdef USE_PERFETTO_TRACING
virtualdeviceperfetto::endTrace();
#endif
}
__attribute__((always_inline)) void traceCounter(const char* name, int64_t value) {
- if (CC_LIKELY(*tracingDisabledPtr)) return;
+ if (CC_LIKELY(!tracingDisabledPtr)) return;
#ifdef USE_PERFETTO_TRACING
virtualdeviceperfetto::traceCounter(name, value);
#endif
}
ScopedTrace::ScopedTrace(const char* name) {
- if (CC_LIKELY(*tracingDisabledPtr)) return;
+ if (CC_LIKELY(!tracingDisabledPtr)) return;
#ifdef USE_PERFETTO_TRACING
virtualdeviceperfetto::beginTrace(name);
#endif
}
ScopedTrace::~ScopedTrace() {
- if (CC_LIKELY(*tracingDisabledPtr)) return;
+ if (CC_LIKELY(!tracingDisabledPtr)) return;
#ifdef USE_PERFETTO_TRACING
virtualdeviceperfetto::endTrace();
#endif
diff --git a/host-common/Android.bp b/host-common/Android.bp
index bac2451..f004d7a 100644
--- a/host-common/Android.bp
+++ b/host-common/Android.bp
@@ -1,3 +1,14 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_google_aemu_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["hardware_google_aemu_license"],
+}
+
cc_library_static {
name: "gfxstream_host_common",
defaults: [ "gfxstream_defaults" ],
diff --git a/host-common/MediaCudaUtils.cpp b/host-common/MediaCudaUtils.cpp
index 8b5c1ee..d131dd3 100644
--- a/host-common/MediaCudaUtils.cpp
+++ b/host-common/MediaCudaUtils.cpp
@@ -104,7 +104,8 @@
void media_cuda_utils_nv12_updater(void* privData,
uint32_t type,
- uint32_t* textures) {
+ uint32_t* textures,
+ void* callerData) {
constexpr uint32_t kFRAMEWORK_FORMAT_NV12 = 3;
if (type != kFRAMEWORK_FORMAT_NV12) {
return;
diff --git a/host-common/MediaH264DecoderCuvid.cpp b/host-common/MediaH264DecoderCuvid.cpp
index 4bd2ba8..65c7463 100644
--- a/host-common/MediaH264DecoderCuvid.cpp
+++ b/host-common/MediaH264DecoderCuvid.cpp
@@ -599,7 +599,10 @@
NVDEC_API_CALL(cuGraphicsUnregisterResource(CudaRes));
}
-void cuda_nv12_updater(void* privData, uint32_t type, uint32_t* textures) {
+void cuda_nv12_updater(void* privData,
+ uint32_t type,
+ uint32_t* textures,
+ void* callerData) {
constexpr uint32_t kFRAMEWORK_FORMAT_NV12 = 3;
if (type != kFRAMEWORK_FORMAT_NV12) {
return;
diff --git a/host-common/MediaH264DecoderGeneric.cpp b/host-common/MediaH264DecoderGeneric.cpp
index 29edcd4..d2c82f2 100644
--- a/host-common/MediaH264DecoderGeneric.cpp
+++ b/host-common/MediaH264DecoderGeneric.cpp
@@ -22,6 +22,8 @@
// for Linux and Window, Cuvid is available
#include "host-common/MediaCudaDriverHelper.h"
#include "host-common/MediaCudaVideoHelper.h"
+#else
+#include "host-common/MediaVideoToolBoxVideoHelper.h"
#endif
#include <cstdint>
@@ -68,15 +70,11 @@
}
bool canDecodeToGpuTexture() {
-#ifndef __APPLE__
if (emuglConfig_get_current_renderer() == SELECTED_RENDERER_HOST) {
return true;
} else {
return false;
}
-#else
- return false;
-#endif
}
}; // end namespace
@@ -147,7 +145,30 @@
H264_DPRINT("succeeded to init cuda decoder");
}
}
-// TODO: add video toolbox for apple
+#else
+ //TODO: once all the CTS passed with VTB, remove this
+ const bool is_vtb_allowed = android::base::System::getEnvironmentVariable(
+ "ANDROID_EMU_MEDIA_DECODER_VTB") == "1";
+
+ if (is_vtb_allowed) {
+ MediaVideoToolBoxVideoHelper::FrameStorageMode fMode =
+ (mParser.version() >= 200 && mUseGpuTexture)
+ ? MediaVideoToolBoxVideoHelper::FrameStorageMode::
+ USE_GPU_TEXTURE
+ : MediaVideoToolBoxVideoHelper::FrameStorageMode::
+ USE_BYTE_BUFFER;
+ auto macDecoder = new MediaVideoToolBoxVideoHelper(
+ mOutputWidth, mOutputHeight,
+ MediaVideoToolBoxVideoHelper::OutputTreatmentMode::SAVE_RESULT,
+ fMode);
+
+ if (mUseGpuTexture && mParser.version() >= 200) {
+ H264_DPRINT("use gpu texture on OSX");
+ macDecoder->resetTexturePool(mRenderer.getTexturePool());
+ }
+ mHwVideoHelper.reset(macDecoder);
+ mHwVideoHelper->init();
+ }
#endif
mSnapshotHelper.reset(
@@ -214,12 +235,15 @@
*retSzBytes = szBytes;
*retErr = (int32_t)Err::NoErr;
+
+ H264_DPRINT("done decoding this frame");
}
void MediaH264DecoderGeneric::decodeFrameInternal(const uint8_t* data,
size_t len,
uint64_t pts) {
if (mTrialPeriod) {
+ H264_DPRINT("still in trial period");
try_decode(data, len, pts);
} else {
mVideoHelper->decode(data, len, pts);
@@ -314,6 +338,11 @@
bool needToCopyToGuest = true;
if (mParser.version() == 200) {
if (mUseGpuTexture && pFrame->texture[0] > 0 && pFrame->texture[1] > 0) {
+ H264_DPRINT(
+ "calling rendering to host side color buffer with id %d "
+ "tex %d tex %d",
+ param.hostColorBufferId, pFrame->texture[0],
+ pFrame->texture[1]);
mRenderer.renderToHostColorBufferWithTextures(
param.hostColorBufferId, pFrame->width, pFrame->height,
TextureFrame{pFrame->texture[0], pFrame->texture[1]});
diff --git a/host-common/MediaVideoToolBoxUtils.cpp b/host-common/MediaVideoToolBoxUtils.cpp
new file mode 100644
index 0000000..bfe2efa
--- /dev/null
+++ b/host-common/MediaVideoToolBoxUtils.cpp
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 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 "host-common/MediaVideoToolBoxUtils.h"
+#include "OpenglRender/MediaNative.h"
+
+#define MEDIA_VTB_DEBUG 0
+
+#if MEDIA_VTB_DEBUG
+#define VTB_DPRINT(fmt, ...) \
+ fprintf(stderr, "vtb-utils: %s:%d " fmt "\n", __func__, __LINE__, \
+ ##__VA_ARGS__);
+#else
+#define VTB_DPRINT(fmt, ...)
+#endif
+
+extern "C" {
+#define MEDIA_VTB_COPY_Y_TEXTURE 1
+#define MEDIA_VTB_COPY_UV_TEXTURE 2
+
+void media_vtb_utils_nv12_updater(void* privData,
+ uint32_t type,
+ uint32_t* textures,
+ void* callerData) {
+ constexpr uint32_t kFRAMEWORK_FORMAT_NV12 = 3;
+ if (type != kFRAMEWORK_FORMAT_NV12) {
+ return;
+ }
+ // get the textures
+ media_vtb_utils_copy_context* myctx =
+ (media_vtb_utils_copy_context*)privData;
+ int ww = myctx->dest_width;
+ int hh = myctx->dest_height;
+ void* iosurface = myctx->iosurface;
+ int yy = 0;
+ int uv = 0;
+
+ MediaNativeCallerData* cdata = (struct MediaNativeCallerData*)callerData;
+
+ void* nscontext = cdata->ctx;
+ auto converter = cdata->converter;
+ converter(nscontext, iosurface, textures[0], textures[1]);
+
+ VTB_DPRINT("done ");
+}
+
+} // end of extern C
diff --git a/host-common/MediaVideoToolBoxVideoHelper.cpp b/host-common/MediaVideoToolBoxVideoHelper.cpp
new file mode 100644
index 0000000..5c4772c
--- /dev/null
+++ b/host-common/MediaVideoToolBoxVideoHelper.cpp
@@ -0,0 +1,714 @@
+// Copyright (C) 2021 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 "host-common/MediaVideoToolBoxUtils.h"
+#include "host-common/MediaVideoToolBoxVideoHelper.h"
+#include "host-common/YuvConverter.h"
+#include "android/utils/debug.h"
+
+#define MEDIA_VTB_DEBUG 0
+
+#if MEDIA_VTB_DEBUG
+#define VTB_DPRINT(fmt, ...) \
+ fprintf(stderr, "media-vtb-video-helper: %s:%d " fmt "\n", __func__, \
+ __LINE__, ##__VA_ARGS__);
+#else
+#define VTB_DPRINT(fmt, ...)
+#endif
+
+namespace android {
+namespace emulation {
+
+using TextureFrame = MediaTexturePool::TextureFrame;
+using FrameInfo = MediaSnapshotState::FrameInfo;
+using ColorAspects = MediaSnapshotState::ColorAspects;
+using H264NaluType = H264NaluParser::H264NaluType;
+
+MediaVideoToolBoxVideoHelper::MediaVideoToolBoxVideoHelper(
+ int w,
+ int h,
+ OutputTreatmentMode oMode,
+ FrameStorageMode fMode)
+ : mOutputWidth(w),
+ mOutputHeight(h),
+ mUseGpuTexture(fMode == FrameStorageMode::USE_GPU_TEXTURE) {
+ mIgnoreDecoderOutput = (oMode == OutputTreatmentMode::IGNORE_RESULT);
+}
+
+MediaVideoToolBoxVideoHelper::~MediaVideoToolBoxVideoHelper() {
+ deInit();
+}
+
+void MediaVideoToolBoxVideoHelper::deInit() {
+ VTB_DPRINT("deInit calling");
+
+ resetDecoderSession();
+
+ resetFormatDesc();
+
+ mVtbReady = false;
+
+ mFfmpegVideoHelper.reset();
+
+ mSavedDecodedFrames.clear();
+}
+
+bool MediaVideoToolBoxVideoHelper::init() {
+ VTB_DPRINT("init calling");
+ mVtbReady = false;
+ return true;
+}
+
+static void dumpBytes(const uint8_t* img, size_t szBytes, bool all = false) {
+ printf("data=");
+ size_t numBytes = szBytes;
+ if (!all) {
+ numBytes = 32;
+ }
+
+ for (size_t i = 0; i < (numBytes > szBytes ? szBytes : numBytes); ++i) {
+ if (i % 8 == 0) {
+ printf("\n");
+ }
+ printf("0x%02x ", img[i]);
+ }
+ printf("\n");
+
+ fflush(stdout);
+}
+
+void MediaVideoToolBoxVideoHelper::extractFrameInfo() {
+ // at this moment, ffmpeg should have decoded the first frame to obtain
+ // w/h/colorspace info and number of b frame buffers
+
+ mFfmpegVideoHelper->flush();
+ MediaSnapshotState::FrameInfo frame;
+ bool success = mFfmpegVideoHelper->receiveFrame(&frame);
+ if (success) {
+ mColorAspects = frame.color;
+ mVtbBufferSize = mFfmpegVideoHelper->frameReorderBufferSize();
+ VTB_DPRINT("vtb buffer size is %d", mVtbBufferSize);
+ }
+}
+
+void MediaVideoToolBoxVideoHelper::decode(const uint8_t* frame,
+ size_t szBytes,
+ uint64_t inputPts) {
+ VTB_DPRINT("%s(frame=%p, sz=%zu)", __func__, frame, szBytes);
+
+ ++mTotalFrames;
+ const bool parseOk = parseInputFrames(frame, szBytes);
+ if (!parseOk) {
+ // cannot parse, probably cannot decode either
+ // just fail
+ VTB_DPRINT("Failed to parse frame=%p, sz=%zu, give up.", frame, szBytes);
+ mIsGood = false;
+ return;
+ }
+
+ // has to go in the FIFO order
+ for (int i = 0; i < mInputFrames.size(); ++i) {
+ InputFrame& f = mInputFrames[i];
+ switch (f.type) {
+ case H264NaluType::SPS:
+ mSPS.assign(f.data, f.data + f.size);
+ VTB_DPRINT("this is an SPS frame");
+ // dumpBytes(mSPS.data(), mSPS.size());
+ mVtbReady = false;
+ { // create ffmpeg decoder with only 1 thread to avoid frame delay
+ constexpr int numthreads = 1;
+ mFfmpegVideoHelper.reset(new MediaFfmpegVideoHelper(264, numthreads));
+ mFfmpegVideoHelper->init();
+ }
+ break;
+ case H264NaluType::PPS:
+ VTB_DPRINT("this is an PPS frame");
+ mPPS.assign(f.data, f.data + f.size);
+ // dumpBytes(mPPS.data(), mPPS.size());
+ mVtbReady = false;
+ break;
+ case H264NaluType::SEI:
+ VTB_DPRINT("this is SEI frame");
+ break;
+ case H264NaluType::CodedSliceIDR:
+ VTB_DPRINT("this is an IDR frame");
+ if (mFfmpegVideoHelper) {
+ mFfmpegVideoHelper->decode(frame, szBytes, inputPts);
+ extractFrameInfo();
+ mFfmpegVideoHelper.reset();
+ }
+ if (!mVtbReady) {
+ createCMFormatDescription();
+ Boolean needNewSession = ( VTDecompressionSessionCanAcceptFormatDescription(mDecoderSession, mCmFmtDesc) == false);
+ if (needNewSession) {
+ resetDecoderSession();
+ }
+ if (!mDecoderSession) {
+ // all the previously decoded frames need to be
+ // retrieved
+ flush();
+ recreateDecompressionSession();
+ }
+ if (mIsGood) {
+ mVtbReady = true;
+ }
+ }
+ handleIDRFrame(f.data, f.size, inputPts);
+ break;
+ case H264NaluType::CodedSliceNonIDR:
+ VTB_DPRINT("this is an non-IDR frame");
+ handleIDRFrame(f.data, f.size, inputPts);
+ break;
+ default:
+ VTB_DPRINT("Support for nalu_type=%u not implemented",
+ (uint8_t)f.type);
+ // cannot handle such video, give up so we can fall
+ // back to ffmpeg
+ mIsGood = false;
+ break;
+ }
+ }
+
+ if (mFfmpegVideoHelper) {
+ // we have not reached idr frame yet, keep decoding
+ mFfmpegVideoHelper->decode(frame, szBytes, inputPts);
+ }
+
+ mInputFrames.clear();
+}
+
+void MediaVideoToolBoxVideoHelper::flush() {
+ VTB_DPRINT("started flushing");
+ for (auto& iter : mVtbBufferMap) {
+ mSavedDecodedFrames.push_back(std::move(iter.second));
+ }
+ mVtbBufferMap.clear();
+ VTB_DPRINT("done one flushing");
+}
+
+//------------------------ private helper functions
+//-----------------------------------------
+
+void MediaVideoToolBoxVideoHelper::handleIDRFrame(const uint8_t* ptr,
+ size_t szBytes,
+ uint64_t pts) {
+ uint8_t* fptr = const_cast<uint8_t*>(ptr);
+
+ // We can assume fptr has a valid start code header because it has already
+ // gone through validation in H264NaluParser.
+ uint8_t startHeaderSz = fptr[2] == 1 ? 3 : 4;
+ uint32_t dataSz = szBytes - startHeaderSz;
+ std::unique_ptr<uint8_t> idr(new uint8_t[dataSz + 4]);
+ uint32_t dataSzNl = htonl(dataSz);
+
+ // AVCC format requires us to replace the start code header on this NALU
+ // with the size of the data. Start code is either 0x000001 or 0x00000001.
+ // The size needs to be the first four bytes in network byte order.
+ memcpy(idr.get(), &dataSzNl, 4);
+ memcpy(idr.get() + 4, ptr + startHeaderSz, dataSz);
+
+ CMSampleBufferRef sampleBuf = nullptr;
+ sampleBuf = createSampleBuffer(mCmFmtDesc, (void*)idr.get(), dataSz + 4);
+ if (!sampleBuf) {
+ VTB_DPRINT("%s: Failed to create CMSampleBufferRef", __func__);
+ return;
+ }
+
+ CMSampleBufferSetOutputPresentationTimeStamp(sampleBuf, CMTimeMake(pts, 1));
+
+ OSStatus status;
+ status = VTDecompressionSessionDecodeFrame(mDecoderSession, sampleBuf,
+ 0, // decodeFlags
+ NULL, // sourceFrameRefCon
+ 0); // infoFlagsOut
+
+ if (status == noErr) {
+ // TODO: this call blocks until the frame has been decoded. Perhaps it
+ // will be more efficient to signal the guest when the frame is ready to
+ // be read instead.
+ status = VTDecompressionSessionWaitForAsynchronousFrames(
+ mDecoderSession);
+ VTB_DPRINT("Success decoding IDR frame");
+ } else {
+ VTB_DPRINT("%s: Failed to decompress frame (err=%d)", __func__, status);
+ }
+
+ CFRelease(sampleBuf);
+}
+
+// chop the frame into sub frames that video tool box will consume
+// one by one
+bool MediaVideoToolBoxVideoHelper::parseInputFrames(const uint8_t* frame,
+ size_t sz) {
+ mInputFrames.clear();
+ VTB_DPRINT("input frame %d bytes", (int)sz);
+ while (1) {
+ const uint8_t* remainingFrame = parseOneFrame(frame, sz);
+ if (remainingFrame == nullptr)
+ break;
+ int consumed = (remainingFrame - frame);
+ VTB_DPRINT("consumed %d bytes", consumed);
+ frame = remainingFrame;
+ sz = sz - consumed;
+ }
+ return mInputFrames.size() > 0;
+}
+
+const uint8_t* MediaVideoToolBoxVideoHelper::parseOneFrame(const uint8_t* frame,
+ size_t szBytes) {
+ if (frame == nullptr || szBytes <= 0) {
+ return nullptr;
+ }
+
+ const uint8_t* currentNalu =
+ H264NaluParser::getNextStartCodeHeader(frame, szBytes);
+ if (currentNalu == nullptr) {
+ VTB_DPRINT("No start code header found in this frame");
+ return nullptr;
+ }
+
+ const uint8_t* nextNalu = nullptr;
+ size_t remaining = szBytes - (currentNalu - frame);
+
+ // Figure out the size of |currentNalu|.
+ size_t currentNaluSize = remaining;
+ // 3 is the minimum size of the start code header (3 or 4 bytes).
+ // dumpBytes(currentNalu, currentNaluSize);
+
+ // usually guest sends one frame a time, but sometime, SEI prefixes IDR
+ // frame as one frame from guest, we need to separate SEI out from IDR:
+ // video tool box cannot handle SEI+IDR combo-frame; for other cases, there
+ // is no need to check.
+ if (H264NaluType::SEI == H264NaluParser::getFrameNaluType(frame, szBytes, nullptr)
+ || H264NaluType::SPS == H264NaluParser::getFrameNaluType(frame, szBytes, nullptr)
+ || H264NaluType::PPS == H264NaluParser::getFrameNaluType(frame, szBytes, nullptr)
+
+ ) {
+ nextNalu = H264NaluParser::getNextStartCodeHeader(currentNalu + 3,
+ remaining - 3);
+ if (nextNalu != nullptr) {
+ currentNaluSize = nextNalu - currentNalu;
+ }
+ }
+
+ const uint8_t* remainingFrame = currentNalu + currentNaluSize;
+
+ // |data| is currentNalu, but with the start code header discarded.
+ uint8_t* data = nullptr;
+ H264NaluType naluType = H264NaluParser::getFrameNaluType(
+ currentNalu, currentNaluSize, &data);
+
+ if (naluType == H264NaluType::Undefined)
+ return nullptr;
+
+ size_t dataSize = currentNaluSize - (data - currentNalu);
+ const std::string naluTypeStr = H264NaluParser::naluTypeToString(naluType);
+ VTB_DPRINT("Got frame type=%u (%s)", (uint8_t)naluType,
+ naluTypeStr.c_str());
+
+ if (naluType == H264NaluType::SPS || naluType == H264NaluType::PPS) {
+ mInputFrames.push_back(InputFrame(naluType, data, dataSize));
+ } else {
+ mInputFrames.push_back(
+ InputFrame(naluType, currentNalu, currentNaluSize));
+ }
+ return remainingFrame;
+}
+
+// static
+void MediaVideoToolBoxVideoHelper::videoToolboxDecompressCallback(
+ void* opaque,
+ void* sourceFrameRefCon,
+ OSStatus status,
+ VTDecodeInfoFlags flags,
+ CVPixelBufferRef image_buffer,
+ CMTime pts,
+ CMTime duration) {
+ VTB_DPRINT("%s", __func__);
+ auto ptr = static_cast<MediaVideoToolBoxVideoHelper*>(opaque);
+
+ // if (ptr->mDecodedFrame) {
+ // CVPixelBufferRelease(ptr->mDecodedFrame);
+ // ptr->mDecodedFrame = nullptr;
+ // }
+
+ if (!image_buffer) {
+ VTB_DPRINT("%s: output image buffer is null", __func__);
+ ptr->mIsGood = false;
+ return;
+ }
+
+ ptr->mOutputPts = pts.value;
+ ptr->mDecodedFrame = CVPixelBufferRetain(image_buffer);
+ CVOpenGLTextureCacheRef _CVGLTextureCache;
+ CVOpenGLTextureRef _CVGLTexture;
+ CGLPixelFormatObj _CGLPixelFormat;
+
+ // Image is ready to be comsumed
+ ptr->copyFrame();
+ ptr->mImageReady = true;
+ VTB_DPRINT("Got decoded frame");
+ CVPixelBufferRelease(ptr->mDecodedFrame);
+ ptr->mDecodedFrame = nullptr;
+}
+
+// static
+CFDictionaryRef MediaVideoToolBoxVideoHelper::createOutputBufferAttributes(
+ int width,
+ int height,
+ OSType pix_fmt) {
+ CFMutableDictionaryRef buffer_attributes;
+ CFMutableDictionaryRef io_surface_properties;
+ CFNumberRef cv_pix_fmt;
+ CFNumberRef w;
+ CFNumberRef h;
+
+ w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width);
+ h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height);
+ cv_pix_fmt =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt);
+
+ buffer_attributes = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ io_surface_properties = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ if (pix_fmt) {
+ CFDictionarySetValue(buffer_attributes,
+ kCVPixelBufferPixelFormatTypeKey, cv_pix_fmt);
+ }
+ CFDictionarySetValue(buffer_attributes,
+ kCVPixelBufferIOSurfacePropertiesKey,
+ io_surface_properties);
+ CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w);
+ CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h);
+ // Not sure if this will work becuase we are passing the pixel buffer back
+ // into the guest
+ CFDictionarySetValue(buffer_attributes,
+ kCVPixelBufferIOSurfaceOpenGLTextureCompatibilityKey,
+ kCFBooleanTrue);
+
+ CFRelease(io_surface_properties);
+ CFRelease(cv_pix_fmt);
+ CFRelease(w);
+ CFRelease(h);
+
+ return buffer_attributes;
+}
+
+// static
+CMSampleBufferRef MediaVideoToolBoxVideoHelper::createSampleBuffer(
+ CMFormatDescriptionRef fmtDesc,
+ void* buffer,
+ size_t sz) {
+ OSStatus status;
+ CMBlockBufferRef blockBuf = nullptr;
+ CMSampleBufferRef sampleBuf = nullptr;
+
+ status = CMBlockBufferCreateWithMemoryBlock(
+ kCFAllocatorDefault, // structureAllocator
+ buffer, // memoryBlock
+ sz, // blockLength
+ kCFAllocatorNull, // blockAllocator
+ NULL, // customBlockSource
+ 0, // offsetToData
+ sz, // dataLength
+ 0, // flags
+ &blockBuf);
+
+ if (!status) {
+ status = CMSampleBufferCreate(kCFAllocatorDefault, // allocator
+ blockBuf, // dataBuffer
+ TRUE, // dataReady
+ 0, // makeDataReadyCallback
+ 0, // makeDataReadyRefCon
+ fmtDesc, // formatDescription
+ 1, // numSamples
+ 0, // numSampleTimingEntries
+ NULL, // sampleTimingArray
+ 0, // numSampleSizeEntries
+ NULL, // sampleSizeArray
+ &sampleBuf);
+ }
+
+ if (blockBuf) {
+ CFRelease(blockBuf);
+ }
+
+ return sampleBuf;
+}
+
+void MediaVideoToolBoxVideoHelper::resetDecoderSession() {
+ if (mDecoderSession) {
+ VTDecompressionSessionInvalidate(mDecoderSession);
+ CFRelease(mDecoderSession);
+ mDecoderSession = nullptr;
+ }
+ if (mDecodedFrame) {
+ CVPixelBufferRelease(mDecodedFrame);
+ mDecodedFrame = nullptr;
+ }
+}
+
+void MediaVideoToolBoxVideoHelper::getOutputWH() {
+ if (!mCmFmtDesc)
+ return;
+
+ CMVideoDimensions vsize = CMVideoFormatDescriptionGetDimensions(mCmFmtDesc);
+
+ if (mOutputWidth == vsize.width && mOutputHeight == vsize.height) {
+ VTB_DPRINT("initial w/h is the same as vtb w/h");
+ } else {
+ VTB_DPRINT("initial w/h are not the same as vtb w/h");
+ }
+ mOutputWidth = vsize.width;
+ mOutputHeight = vsize.height;
+}
+
+void MediaVideoToolBoxVideoHelper::resetFormatDesc() {
+ if (mCmFmtDesc) {
+ CFRelease(mCmFmtDesc);
+ mCmFmtDesc = nullptr;
+ }
+}
+
+void MediaVideoToolBoxVideoHelper::createCMFormatDescription() {
+ uint8_t* parameterSets[2] = {mSPS.data(), mPPS.data()};
+ size_t parameterSetSizes[2] = {mSPS.size(), mPPS.size()};
+
+ resetFormatDesc();
+
+ OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
+ kCFAllocatorDefault, 2, (const uint8_t* const*)parameterSets,
+ parameterSetSizes, 4, &mCmFmtDesc);
+
+ if (status == noErr) {
+ getOutputWH();
+ VTB_DPRINT("Created CMFormatDescription from SPS/PPS sets w %d h %d",
+ mOutputWidth, mOutputHeight);
+ } else {
+ VTB_DPRINT("Unable to create CMFormatDescription (%d)\n", (int)status);
+ }
+}
+
+void MediaVideoToolBoxVideoHelper::recreateDecompressionSession() {
+ if (mCmFmtDesc == nullptr) {
+ VTB_DPRINT("CMFormatDescription not created. Need sps and pps NALUs.");
+ return;
+ }
+
+ CMVideoCodecType codecType = kCMVideoCodecType_H264;
+ CFMutableDictionaryRef decoder_spec = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ // enable hardware acceleration, but allows vtb to fallback to
+ // its software implementation.
+ CFDictionarySetValue(
+ decoder_spec,
+ kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
+ kCFBooleanTrue);
+
+ CFDictionaryRef bufAttr;
+
+ if (mUseGpuTexture) {
+ // use NV12 format
+ bufAttr = createOutputBufferAttributes(
+ mOutputWidth, mOutputHeight,
+ kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
+ } else {
+ bufAttr = createOutputBufferAttributes(
+ mOutputWidth, mOutputHeight,
+ kCVPixelFormatType_420YpCbCr8Planar);
+ }
+
+ VTDecompressionOutputCallbackRecord decoderCb;
+ decoderCb.decompressionOutputCallback = videoToolboxDecompressCallback;
+ decoderCb.decompressionOutputRefCon = this;
+
+ OSStatus status;
+ status = VTDecompressionSessionCreate(
+ NULL, // allocator
+ mCmFmtDesc, // videoFormatDescription
+ decoder_spec, // videoDecoderSpecification
+ bufAttr, // destinationImageBufferAttributes
+ &decoderCb, // outputCallback
+ &mDecoderSession); // decompressionSessionOut
+
+ if (decoder_spec) {
+ CFRelease(decoder_spec);
+ }
+ if (bufAttr) {
+ CFRelease(bufAttr);
+ }
+
+ mIsGood = false;
+ switch (status) {
+ case kVTVideoDecoderNotAvailableNowErr:
+ VTB_DPRINT("VideoToolbox session not available");
+ return;
+ case kVTVideoDecoderUnsupportedDataFormatErr:
+ VTB_DPRINT("VideoToolbox does not support this format");
+ return;
+ case kVTVideoDecoderMalfunctionErr:
+ VTB_DPRINT("VideoToolbox malfunction");
+ return;
+ case kVTVideoDecoderBadDataErr:
+ VTB_DPRINT("VideoToolbox reported invalid data");
+ return;
+ case 0:
+ dprint("VideoToolBox decoding session is created successfully");
+ mIsGood = true;
+ return;
+ default:
+ VTB_DPRINT("Unknown VideoToolbox session creation error %d",
+ status);
+ return;
+ }
+}
+
+void MediaVideoToolBoxVideoHelper::copyFrameToTextures() {
+ // TODO
+ auto startTime = std::chrono::steady_clock::now();
+ TextureFrame texFrame;
+ if (mUseGpuTexture && mTexturePool != nullptr) {
+ VTB_DPRINT("decoded surface is %p w %d h %d",
+ CVPixelBufferGetIOSurface(mDecodedFrame), mOutputWidth,
+ mOutputHeight);
+ auto my_copy_context = media_vtb_utils_copy_context{
+ CVPixelBufferGetIOSurface(mDecodedFrame), mOutputWidth,
+ mOutputHeight};
+ texFrame = mTexturePool->getTextureFrame(mOutputWidth, mOutputHeight);
+ VTB_DPRINT("ask videocore to copy to %d and %d", texFrame.Ytex,
+ texFrame.UVtex);
+ mTexturePool->saveDecodedFrameToTexture(
+ texFrame, &my_copy_context,
+ (void*)media_vtb_utils_nv12_updater);
+ }
+
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - startTime);
+ VTB_DPRINT("used %d ms", (int)elapsed.count());
+
+ auto ptspair = std::make_pair(mOutputPts, mTotalFrames);
+ mVtbBufferMap[ptspair] = MediaSnapshotState::FrameInfo{
+ std::vector<uint8_t>(),
+ std::vector<uint32_t>{texFrame.Ytex, texFrame.UVtex},
+ (int)mOutputWidth,
+ (int)mOutputHeight,
+ (uint64_t)(mOutputPts),
+ ColorAspects{mColorAspects}};
+}
+
+void MediaVideoToolBoxVideoHelper::copyFrameToCPU() {
+ int imageSize = CVPixelBufferGetDataSize(mDecodedFrame);
+ int stride = CVPixelBufferGetBytesPerRow(mDecodedFrame);
+
+ mOutBufferSize = mOutputWidth * mOutputHeight * 3 / 2;
+
+ VTB_DPRINT("copying size=%d dimension=[%dx%d] stride=%d", imageSize,
+ mOutputWidth, mOutputHeight, stride);
+
+ // Copies the image data to the guest.
+ mSavedDecodedFrame.resize(mOutputWidth * mOutputHeight * 3 / 2);
+ uint8_t* dst = mSavedDecodedFrame.data();
+
+ CVPixelBufferLockBaseAddress(mDecodedFrame, kCVPixelBufferLock_ReadOnly);
+ if (CVPixelBufferIsPlanar(mDecodedFrame)) {
+ imageSize = 0; // add up the size from the planes
+ int planes = CVPixelBufferGetPlaneCount(mDecodedFrame);
+ for (int i = 0; i < planes; ++i) {
+ void* planeData =
+ CVPixelBufferGetBaseAddressOfPlane(mDecodedFrame, i);
+ int linesize = CVPixelBufferGetBytesPerRowOfPlane(mDecodedFrame, i);
+ int planeWidth = CVPixelBufferGetWidthOfPlane(mDecodedFrame, i);
+ int planeHeight = CVPixelBufferGetHeightOfPlane(mDecodedFrame, i);
+ VTB_DPRINT("plane=%d data=%p linesize=%d pwidth=%d pheight=%d", i,
+ planeData, linesize, planeWidth, planeHeight);
+ // For kCVPixelFormatType_420YpCbCr8Planar, plane 0 is Y, UV planes
+ // are 1 and 2
+ if (planeWidth != mOutputWidth && planeWidth != mOutputWidth / 2) {
+ VTB_DPRINT("ERROR: Unable to determine YUV420 plane type");
+ continue;
+ }
+
+ // Sometimes the buffer stride can be longer than the actual data
+ // width. This means that the extra bytes are just padding and need
+ // to be discarded.
+ if (linesize <= planeWidth) {
+ int sz = planeHeight * planeWidth;
+ imageSize += sz;
+ memcpy(dst, planeData, sz);
+ dst += sz;
+ } else {
+ // Need to copy line by line
+ int sz = planeWidth;
+ for (int j = 0; j < planeHeight; ++j) {
+ uint8_t* ptr = (uint8_t*)planeData;
+ ptr += linesize * j;
+ memcpy(dst, ptr, sz);
+ imageSize += sz;
+ dst += sz;
+ }
+ }
+ }
+ if (imageSize != mOutBufferSize) {
+ VTB_DPRINT(
+ "ERROR: Total size of planes not same as guestSz "
+ "(guestSz=%u, imageSize=%d)",
+ mOutBufferSize, imageSize);
+ }
+ } else {
+ if (imageSize > mOutBufferSize) {
+ VTB_DPRINT(
+ "Buffer size mismatch (guestSz=%u, imageSize=%d). Using "
+ "guestSz instead.",
+ mOutBufferSize, imageSize);
+ imageSize = mOutBufferSize;
+ }
+
+ // IMPORTANT: mDecodedFrame must be locked before accessing the contents
+ // with CPU
+ void* data = CVPixelBufferGetBaseAddress(mDecodedFrame);
+ memcpy(dst, data, imageSize);
+ }
+ CVPixelBufferUnlockBaseAddress(mDecodedFrame, kCVPixelBufferLock_ReadOnly);
+
+ auto ptspair = std::make_pair(mOutputPts, mTotalFrames);
+ mVtbBufferMap[ptspair] = MediaSnapshotState::FrameInfo{
+ std::vector<uint8_t>(), std::vector<uint32_t>{},
+ (int)mOutputWidth, (int)mOutputHeight,
+ (uint64_t)(mOutputPts), ColorAspects{mColorAspects}};
+ mVtbBufferMap[ptspair].data.swap(mSavedDecodedFrame);
+}
+
+void MediaVideoToolBoxVideoHelper::copyFrame() {
+ mOutputWidth = CVPixelBufferGetWidth(mDecodedFrame);
+ mOutputHeight = CVPixelBufferGetHeight(mDecodedFrame);
+
+ if (mUseGpuTexture) {
+ copyFrameToTextures();
+ } else {
+ copyFrameToCPU();
+ }
+
+ if (mVtbBufferMap.size() > mVtbBufferSize) {
+ mSavedDecodedFrames.push_back(std::move(mVtbBufferMap.begin()->second));
+ mVtbBufferMap.erase(mVtbBufferMap.begin());
+ }
+}
+
+} // namespace emulation
+} // namespace android
diff --git a/host-common/include/host-common/MediaVideoToolBoxUtils.h b/host-common/include/host-common/MediaVideoToolBoxUtils.h
new file mode 100644
index 0000000..c7902d2
--- /dev/null
+++ b/host-common/include/host-common/MediaVideoToolBoxUtils.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 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.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <cstdint>
+
+extern "C" {
+struct media_vtb_utils_copy_context {
+ media_vtb_utils_copy_context(void* surface, unsigned int w, unsigned int h)
+ : iosurface(surface), dest_width(w), dest_height(h){};
+ void* iosurface;
+ unsigned int dest_width;
+ unsigned int dest_height;
+};
+
+void media_vtb_utils_nv12_updater(void* privData,
+ uint32_t type,
+ uint32_t* textures,
+ void* callerData);
+
+} // end of extern C
diff --git a/host-common/include/host-common/MediaVideoToolBoxVideoHelper.h b/host-common/include/host-common/MediaVideoToolBoxVideoHelper.h
new file mode 100644
index 0000000..a61ba56
--- /dev/null
+++ b/host-common/include/host-common/MediaVideoToolBoxVideoHelper.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2021 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.
+
+#pragma once
+
+#include "host-common/H264NaluParser.h"
+#include "host-common/MediaFfmpegVideoHelper.h"
+#include "host-common/MediaSnapshotState.h"
+#include "host-common/MediaTexturePool.h"
+#include "host-common/MediaVideoHelper.h"
+#include "host-common/YuvConverter.h"
+
+// this is apple's video tool box header
+#include <VideoToolbox/VideoToolbox.h>
+
+#ifndef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder
+#define kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder \
+ CFSTR("RequireHardwareAcceleratedVideoDecoder")
+#endif
+
+#include <cstdint>
+#include <list>
+#include <map>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <string.h>
+
+#include <stddef.h>
+
+namespace android {
+namespace emulation {
+
+class MediaVideoToolBoxVideoHelper : public MediaVideoHelper {
+public:
+ enum class FrameStorageMode {
+ USE_BYTE_BUFFER = 1,
+ USE_GPU_TEXTURE = 2,
+ };
+
+ enum class OutputTreatmentMode {
+ IGNORE_RESULT = 1,
+ SAVE_RESULT = 2,
+ };
+
+ MediaVideoToolBoxVideoHelper(int w,
+ int h,
+ OutputTreatmentMode outMode,
+ FrameStorageMode fMode);
+ ~MediaVideoToolBoxVideoHelper() override;
+
+ // return true if success; false otherwise
+ bool init() override;
+ void decode(const uint8_t* frame,
+ size_t szBytes,
+ uint64_t inputPts) override;
+ void flush() override;
+ void deInit() override;
+
+ void resetTexturePool(MediaTexturePool* pool = nullptr) {
+ mTexturePool = pool;
+ }
+
+ virtual int error() const override { return mErrorCode; }
+ virtual bool good() const override { return mIsGood; }
+ virtual bool fatal() const override { return false; }
+
+private:
+ // static
+ static void videoToolboxDecompressCallback(void* opaque,
+ void* sourceFrameRefCon,
+ OSStatus status,
+ VTDecodeInfoFlags flags,
+ CVPixelBufferRef image_buffer,
+ CMTime pts,
+ CMTime duration);
+ static CFDictionaryRef createOutputBufferAttributes(int width,
+ int height,
+ OSType pix_fmt);
+ static CMSampleBufferRef createSampleBuffer(CMFormatDescriptionRef fmtDesc,
+ void* buffer,
+ size_t sz);
+
+ void copyFrame();
+ void copyFrameToTextures();
+ void copyFrameToCPU();
+ void createCMFormatDescription();
+ void recreateDecompressionSession();
+ void getOutputWH();
+ void resetDecoderSession();
+ void resetFormatDesc();
+
+ struct InputFrame {
+ InputFrame(H264NaluParser::H264NaluType in_type,
+ const uint8_t* in_data,
+ int in_size)
+ : type(in_type), data(in_data), size(in_size) {}
+
+ H264NaluParser::H264NaluType type;
+ const uint8_t* data;
+ int size;
+ };
+
+ std::vector<InputFrame> mInputFrames;
+
+ bool parseInputFrames(const uint8_t* frame, size_t sz);
+
+ // returns the remaining part of the frame, nullptr if none
+ const uint8_t* parseOneFrame(const uint8_t* frame, size_t szBytes);
+
+ void handleIDRFrame(const uint8_t* ptr, size_t szBytes, uint64_t pts);
+
+ std::vector<uint8_t> mSPS; // sps NALU
+ std::vector<uint8_t> mPPS; // pps NALU
+
+ // turn on gpu texture mode
+ bool mUseGpuTexture = true;
+ MediaTexturePool* mTexturePool = nullptr;
+
+ uint64_t mNumInputFrame{0};
+ uint64_t mNumOutputFrame{0};
+ int mErrorCode = 0;
+ bool mIsGood = true;
+ unsigned int mHeight = 0;
+ unsigned int mWidth = 0;
+ unsigned int mOutputHeight = 0;
+ unsigned int mOutputWidth = 0;
+ unsigned int mSurfaceHeight = 0;
+ unsigned int mBPP = 0;
+ unsigned int mSurfaceWidth = 0;
+ unsigned int mLumaWidth = 0;
+ unsigned int mLumaHeight = 0;
+ unsigned int mChromaHeight = 0;
+ unsigned int mOutBufferSize = 0;
+
+ uint64_t mOutputPts = 0;
+ bool mImageReady = false;
+
+ // used in vtb callback to saved decoded frame
+ std::vector<uint8_t> mSavedDecodedFrame;
+
+ // TODO: get color aspects from some where
+ MediaSnapshotState::ColorAspects mColorAspects{0};
+
+ std::mutex mFrameLock;
+
+ // this is only set to true after video session is created without errors
+ // it is reset to false when new sps/pps comes
+ bool mVtbReady = false;
+ // videotoolbox stuff
+ // the fmt, this will be recreated each time
+ // we get a sps+pps frames
+ CMFormatDescriptionRef mCmFmtDesc = nullptr;
+ // The VideoToolbox decoder session: this could fail to
+ // create due to incompatible formats coming from android guest
+ VTDecompressionSessionRef mDecoderSession = nullptr;
+ // where the decoded frame is stored
+ CVPixelBufferRef mDecodedFrame = nullptr;
+
+ // need this ffmpeg helper to get the w/h/colorspace info
+ // TODO: replace it with webrtc h264 parser once it is built
+ // for all platforms
+ std::unique_ptr<MediaFfmpegVideoHelper> mFfmpegVideoHelper;
+ void extractFrameInfo();
+
+ uint64_t mTotalFrames = 0;
+ // vtb decoder does not reorder output frames, that means
+ // the video could see jumps all the times
+ int mVtbBufferSize = 8;
+ using PtsPair = std::pair<uint64_t, uint64_t>;
+ std::map<PtsPair, MediaSnapshotState::FrameInfo> mVtbBufferMap;
+
+}; // MediaVideoToolBoxVideoHelper
+
+} // namespace emulation
+} // namespace android
diff --git a/host-common/include/host-common/window_agent.h b/host-common/include/host-common/window_agent.h
index 23a475c..a9489e0 100644
--- a/host-common/include/host-common/window_agent.h
+++ b/host-common/include/host-common/window_agent.h
@@ -127,6 +127,7 @@
void (*show_virtual_scene_controls)(bool);
void (*quit_request)(void);
void (*getWindowPosition)(int*, int*);
+ bool (*hasWindow)();
} QAndroidEmulatorWindowAgent;
#ifndef USING_ANDROID_BP
diff --git a/snapshot/Android.bp b/snapshot/Android.bp
index 58b14ed..5252536 100644
--- a/snapshot/Android.bp
+++ b/snapshot/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_google_aemu_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_google_aemu_license"],
+}
+
cc_library_static {
name: "gfxstream_snapshot",
defaults: [ "gfxstream_defaults" ],