Do not write into EglSurfaceTexture after creation

... beacuse it prevents other clients from connecting.
Instead, render/compress black image into output buffers
directly if the input surface texture doesn't have any
buffer yet.

Bug: 301023410
Test: OpenCamera
Test: atest virtual_camera_tests
Change-Id: I289ff00b590c9bc18052ae0cc15a9d7320b8f033
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
index 8a2db1c..8621160 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.cc
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -24,6 +24,7 @@
 #include <mutex>
 #include <thread>
 
+#include "GLES/gl.h"
 #include "VirtualCameraSessionContext.h"
 #include "aidl/android/hardware/camera/common/Status.h"
 #include "aidl/android/hardware/camera/device/BufferStatus.h"
@@ -271,9 +272,9 @@
     }
 
     auto status = streamConfig->format == PixelFormat::BLOB
-                      ? renderIntoBlobStreamBuffer(
-                            reqBuffer.getStreamId(), reqBuffer.getBufferId(),
-                            streamConfig->bufferSize, reqBuffer.getFence())
+                      ? renderIntoBlobStreamBuffer(reqBuffer.getStreamId(),
+                                                   reqBuffer.getBufferId(),
+                                                   reqBuffer.getFence())
                       : renderIntoImageStreamBuffer(reqBuffer.getStreamId(),
                                                     reqBuffer.getBufferId(),
                                                     reqBuffer.getFence());
@@ -354,17 +355,21 @@
 }
 
 ndk::ScopedAStatus VirtualCameraRenderThread::renderIntoBlobStreamBuffer(
-    const int streamId, const int bufferId, const size_t bufferSize,
-    sp<Fence> fence) {
+    const int streamId, const int bufferId, 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);
+  if (hwBuffer == nullptr) {
+    ALOGE("%s: Failed to fetch hardware buffer %d for streamId %d", __func__,
+          bufferId, streamId);
+    return cameraStatus(Status::INTERNAL_ERROR);
+  }
+
+  std::optional<Stream> stream = mSessionContext.getStreamConfig(streamId);
+  if (!stream.has_value()) {
+    ALOGE("%s, failed to fetch information about stream %d", __func__, streamId);
+    return cameraStatus(Status::INTERNAL_ERROR);
+  }
 
   AHardwareBuffer_Planes planes_info;
 
@@ -377,27 +382,36 @@
     return cameraStatus(Status::INTERNAL_ERROR);
   }
 
-  android_ycbcr ycbcr;
-  status_t status =
-      gBuffer->lockYCbCr(AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, &ycbcr);
-  ALOGV("Locked buffers");
-  if (status != NO_ERROR) {
-    AHardwareBuffer_unlock(hwBuffer.get(), nullptr);
-    ALOGE("%s: Failed to lock graphic buffer: %d", __func__, status);
-    return cameraStatus(Status::INTERNAL_ERROR);
-  }
+  sp<GraphicBuffer> gBuffer = mEglSurfaceTexture->getCurrentBuffer();
+  bool compressionSuccess = true;
+  if (gBuffer != nullptr) {
+    android_ycbcr ycbcr;
+    status_t status =
+        gBuffer->lockYCbCr(AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, &ycbcr);
+    ALOGV("Locked buffers");
+    if (status != NO_ERROR) {
+      AHardwareBuffer_unlock(hwBuffer.get(), nullptr);
+      ALOGE("%s: Failed to lock graphic buffer: %d", __func__, status);
+      return cameraStatus(Status::INTERNAL_ERROR);
+    }
 
-  bool success = compressJpeg(gBuffer->getWidth(), gBuffer->getHeight(), ycbcr,
-                              bufferSize, planes_info.planes[0].data);
+    compressionSuccess =
+        compressJpeg(gBuffer->getWidth(), gBuffer->getHeight(), ycbcr,
+                     stream->bufferSize, planes_info.planes[0].data);
 
-  status_t res = gBuffer->unlock();
-  if (res != NO_ERROR) {
-    ALOGE("Failed to unlock graphic buffer: %d", res);
+    status_t res = gBuffer->unlock();
+    if (res != NO_ERROR) {
+      ALOGE("Failed to unlock graphic buffer: %d", res);
+    }
+  } else {
+    compressionSuccess =
+        compressBlackJpeg(stream->width, stream->height, stream->bufferSize,
+                          planes_info.planes[0].data);
   }
   AHardwareBuffer_unlock(hwBuffer.get(), nullptr);
   ALOGV("Unlocked buffers");
-  return success ? ndk::ScopedAStatus::ok()
-                 : cameraStatus(Status::INTERNAL_ERROR);
+  return compressionSuccess ? ndk::ScopedAStatus::ok()
+                            : cameraStatus(Status::INTERNAL_ERROR);
 }
 
 ndk::ScopedAStatus VirtualCameraRenderThread::renderIntoImageStreamBuffer(
@@ -435,7 +449,15 @@
   mEglDisplayContext->makeCurrent();
   framebuffer->beforeDraw();
 
-  mEglTextureProgram->draw(mEglSurfaceTexture->updateTexture());
+  if (mEglSurfaceTexture->getCurrentBuffer() == nullptr) {
+    // If there's no current buffer, nothing was written to the surface and
+    // texture is not initialized yet. Let's render the framebuffer black
+    // instead of rendering the texture.
+    glClearColor(0.0f, 0.5f, 0.5f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+  } else {
+    mEglTextureProgram->draw(mEglSurfaceTexture->updateTexture());
+  }
   framebuffer->afterDraw();
 
   const std::chrono::nanoseconds after =