Fix crash when there's no input before processCaptureRequest.
Initialize texture surface with black texture so there's always
buffer available even if the client didn't write anything to
the surface yet.
Bug: 301023410
Test: atest virtual_camera_tests
Change-Id: I626e776d6686d0ed0f62fe6d8ef5920857e45932
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
index 582e47f..5756797 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.cc
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -357,6 +357,11 @@
sp<Fence> fence) {
ALOGV("%s", __func__);
sp<GraphicBuffer> gBuffer = mEglSurfaceTexture->getCurrentBuffer();
+ if (gBuffer == nullptr) {
+ // Most probably nothing was yet written to input surface if we reached this.
+ ALOGE("%s: Cannot fetch most recent buffer from SurfaceTexture", __func__);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
std::shared_ptr<AHardwareBuffer> hwBuffer =
mSessionContext.fetchHardwareBuffer(streamId, bufferId);
diff --git a/services/camera/virtualcamera/tests/EglUtilTest.cc b/services/camera/virtualcamera/tests/EglUtilTest.cc
index 86fc50c..d387ebf 100644
--- a/services/camera/virtualcamera/tests/EglUtilTest.cc
+++ b/services/camera/virtualcamera/tests/EglUtilTest.cc
@@ -14,46 +14,72 @@
* limitations under the License.
*/
+#include <cstdint>
+#include "android/hardware_buffer.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "system/graphics.h"
+#include "ui/GraphicBuffer.h"
#include "util/EglDisplayContext.h"
#include "util/EglProgram.h"
+#include "util/EglSurfaceTexture.h"
#include "util/EglUtil.h"
+#include "utils/Errors.h"
namespace android {
namespace companion {
namespace virtualcamera {
namespace {
+using ::testing::Eq;
+using ::testing::NotNull;
+
+constexpr int kWidth = 64;
+constexpr int kHeight = 64;
constexpr char kGlExtYuvTarget[] = "GL_EXT_YUV_target";
+uint8_t getY(const android_ycbcr& ycbcr, const int x, const int y) {
+ uint8_t* yPtr = reinterpret_cast<uint8_t*>(ycbcr.y);
+ return *(yPtr + ycbcr.ystride * y + x);
+}
+
+uint8_t getCb(const android_ycbcr& ycbcr, const int x, const int y) {
+ uint8_t* cbPtr = reinterpret_cast<uint8_t*>(ycbcr.cb);
+ return *(cbPtr + ycbcr.cstride * (y / 2) + (x / 2) * ycbcr.chroma_step);
+}
+
+uint8_t getCr(const android_ycbcr& ycbcr, const int x, const int y) {
+ uint8_t* crPtr = reinterpret_cast<uint8_t*>(ycbcr.cr);
+ return *(crPtr + ycbcr.cstride * (y / 2) + (x / 2) * ycbcr.chroma_step);
+}
+
TEST(EglDisplayContextTest, SuccessfulInitialization) {
EglDisplayContext displayContext;
EXPECT_TRUE(displayContext.isInitialized());
}
-class EglProgramTest : public ::testing::Test {
- public:
+class EglTest : public ::testing::Test {
+public:
void SetUp() override {
- ASSERT_TRUE(mEglDisplayContext.isInitialized());
- ASSERT_TRUE(mEglDisplayContext.makeCurrent());
+ ASSERT_TRUE(mEglDisplayContext.isInitialized());
+ ASSERT_TRUE(mEglDisplayContext.makeCurrent());
}
- private:
+private:
EglDisplayContext mEglDisplayContext;
};
-TEST_F(EglProgramTest, EglTestPatternProgramSuccessfulInit) {
+TEST_F(EglTest, EglTestPatternProgramSuccessfulInit) {
EglTestPatternProgram eglTestPatternProgram;
// Verify the shaders compiled and linked successfully.
EXPECT_TRUE(eglTestPatternProgram.isInitialized());
}
-TEST_F(EglProgramTest, EglTextureProgramSuccessfulInit) {
+TEST_F(EglTest, EglTextureProgramSuccessfulInit) {
if (!isGlExtensionSupported(kGlExtYuvTarget)) {
- GTEST_SKIP() << "Skipping test because of missing required GL extension "
- << kGlExtYuvTarget;
+ GTEST_SKIP() << "Skipping test because of missing required GL extension " << kGlExtYuvTarget;
}
EglTextureProgram eglTextureProgram;
@@ -62,6 +88,35 @@
EXPECT_TRUE(eglTextureProgram.isInitialized());
}
+TEST_F(EglTest, EglSurfaceTextureBlackAfterInit) {
+ if (!isGlExtensionSupported(kGlExtYuvTarget)) {
+ GTEST_SKIP() << "Skipping test because of missing required GL extension " << kGlExtYuvTarget;
+ }
+
+ EglSurfaceTexture surfaceTexture(kWidth, kHeight);
+ surfaceTexture.updateTexture();
+ sp<GraphicBuffer> buffer = surfaceTexture.getCurrentBuffer();
+
+ ASSERT_THAT(buffer, NotNull());
+ const int width = buffer->getWidth();
+ const int height = buffer->getHeight();
+ ASSERT_THAT(width, Eq(kWidth));
+ ASSERT_THAT(height, Eq(kHeight));
+
+ android_ycbcr ycbcr;
+ status_t ret = buffer->lockYCbCr(AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, &ycbcr);
+ ASSERT_THAT(ret, Eq(NO_ERROR));
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ EXPECT_THAT(getY(ycbcr, i, j), Eq(0x00));
+ EXPECT_THAT(getCb(ycbcr, i, j), Eq(0x7f));
+ EXPECT_THAT(getCr(ycbcr, i, j), Eq(0x7f));
+ }
+ }
+
+ buffer->unlock();
+}
+
} // namespace
} // namespace virtualcamera
} // namespace companion
diff --git a/services/camera/virtualcamera/util/EglSurfaceTexture.cc b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
index ac92bc4..266d65a 100644
--- a/services/camera/virtualcamera/util/EglSurfaceTexture.cc
+++ b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
@@ -15,7 +15,6 @@
*/
// #define LOG_NDEBUG 0
-#include <memory>
#define LOG_TAG "EglSurfaceTexture"
#include <cstdint>
@@ -32,6 +31,26 @@
namespace companion {
namespace virtualcamera {
+namespace {
+
+void submitBlackBufferYCbCr420(Surface& surface) {
+ ANativeWindow_Buffer buffer;
+
+ int ret = surface.lock(&buffer, nullptr);
+ if (ret != NO_ERROR) {
+ ALOGE("%s: Cannot lock output surface: %d", __func__, ret);
+ return;
+ }
+ uint8_t* data = reinterpret_cast<uint8_t*>(buffer.bits);
+ const int yPixNr = buffer.width * buffer.height;
+ const int uvPixNr = (buffer.width / 2) * (buffer.height / 2);
+ memset(data, 0x00, yPixNr);
+ memset(data + yPixNr, 0x7f, 2 * uvPixNr);
+ surface.unlockAndPost();
+}
+
+} // namespace
+
EglSurfaceTexture::EglSurfaceTexture(const uint32_t width, const uint32_t height)
: mWidth(width), mHeight(height) {
glGenTextures(1, &mTextureId);
@@ -48,6 +67,14 @@
mGlConsumer->setDefaultBufferFormat(AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420);
mSurface = sp<Surface>::make(mBufferProducer);
+ // Submit black buffer to the surface to make sure there's input buffer
+ // to process in case capture request comes before client writes something
+ // to the surface.
+ //
+ // Note that if the client does write something before capture request is
+ // processed (& updateTexture is called), this black buffer will be
+ // skipped (and recycled).
+ submitBlackBufferYCbCr420(*mSurface);
}
EglSurfaceTexture::~EglSurfaceTexture() {