Add fence support in ImageWriter class.
- Add SyncFence class that include FileDescriptor and related functions
- expose fence file descriptor setter/getter in the Image class
Bug: 210919185
Test: android.hardware.camera2.cts.ImageWriterTest
Change-Id: I7428db26e3e6ca84675d2ce2ef4def46ed0b1397
diff --git a/core/api/current.txt b/core/api/current.txt
index 0316875..b9cdc2f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17061,6 +17061,14 @@
field public static final int MICROPHONE = 1; // 0x1
}
+ public final class SyncFence implements java.io.Closeable android.os.Parcelable {
+ method public void close() throws java.io.IOException;
+ method public int describeContents();
+ method public boolean isValid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.SyncFence> CREATOR;
+ }
+
public final class TriggerEvent {
field public android.hardware.Sensor sensor;
field public long timestamp;
@@ -20773,6 +20781,7 @@
method public abstract void close();
method public android.graphics.Rect getCropRect();
method public long getDataSpace();
+ method @NonNull public android.hardware.SyncFence getFence() throws java.io.IOException;
method public abstract int getFormat();
method @Nullable public android.hardware.HardwareBuffer getHardwareBuffer();
method public abstract int getHeight();
@@ -20781,6 +20790,7 @@
method public abstract int getWidth();
method public void setCropRect(android.graphics.Rect);
method public void setDataSpace(long);
+ method public void setFence(@NonNull android.hardware.SyncFence) throws java.io.IOException;
method public void setTimestamp(long);
}
diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java
new file mode 100644
index 0000000..b0a6f51
--- /dev/null
+++ b/core/java/android/hardware/SyncFence.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (C) 2022 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.
+ */
+
+package android.hardware;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * A SyncFence represents a synchronization primitive which signals when hardware buffers have
+ * completed work on a particular resource.
+ *
+ * <p>For example, a GPU rendering to a framebuffer may generate a synchronization fence,
+ * e.g., a VkFence, which signals when rendering has completed.
+ *
+ * Once the fence signals, then the backing storage for the framebuffer may be safely read from,
+ * such as for display or for media encoding.</p>
+ *
+ * @see <a href="https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/vkCreateFence.html">
+ * VkFence</a>
+ */
+public final class SyncFence implements Closeable, Parcelable {
+ private static final String TAG = "SyncFence";
+
+ /**
+ * Wrapped {@link android.os.ParcelFileDescriptor}.
+ */
+ private ParcelFileDescriptor mWrapped;
+
+ private SyncFence(@NonNull ParcelFileDescriptor wrapped) {
+ mWrapped = wrapped;
+ }
+
+ /***
+ * Create an empty SyncFence
+ *
+ * @return a SyncFence with invalid fence
+ * @hide
+ */
+ public static @NonNull SyncFence createEmpty() {
+ return new SyncFence(ParcelFileDescriptor.adoptFd(-1));
+ }
+
+ /**
+ * Create a new SyncFence wrapped around another descriptor. By default, all method calls are
+ * delegated to the wrapped descriptor.
+ *
+ * @param wrapped The descriptor to be wrapped.
+ * @hide
+ */
+ public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) {
+ return new SyncFence(wrapped);
+ }
+
+ /**
+ * Return a dup'd ParcelFileDescriptor from the SyncFence ParcelFileDescriptor.
+ * @hide
+ */
+ public @NonNull ParcelFileDescriptor getFdDup() throws IOException {
+ return mWrapped.dup();
+ }
+
+ /**
+ * Checks if the SyncFile object is valid.
+ *
+ * @return {@code true} if the file descriptor represents a valid, open file;
+ * {@code false} otherwise.
+ */
+ public boolean isValid() {
+ return mWrapped.getFileDescriptor().valid();
+ }
+
+ /**
+ * Close the SyncFence. This implementation closes the underlying OS resources allocated
+ * this stream.
+ *
+ * @throws IOException If an error occurs attempting to close this SyncFence.
+ */
+ @Override
+ public void close() throws IOException {
+ if (mWrapped != null) {
+ try {
+ mWrapped.close();
+ } finally {
+ // success
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return mWrapped.describeContents();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param out The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be {@code 0} or {@link #PARCELABLE_WRITE_RETURN_VALUE}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ try {
+ mWrapped.writeToParcel(out, flags);
+ } finally {
+ // success
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<SyncFence> CREATOR =
+ new Parcelable.Creator<SyncFence>() {
+ @Override
+ public SyncFence createFromParcel(Parcel in) {
+ return new SyncFence(ParcelFileDescriptor.CREATOR.createFromParcel(in));
+ }
+
+ @Override
+ public SyncFence[] newArray(int size) {
+ return new SyncFence[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 9d2c901..4a7ef55 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
+import android.hardware.SyncFence;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -58,7 +59,6 @@
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import android.util.Size;
@@ -817,12 +817,13 @@
ParcelImage parcelImage = new ParcelImage();
parcelImage.buffer = img.getHardwareBuffer();
- if (img.getFenceFd() >= 0) {
- try {
- parcelImage.fence = ParcelFileDescriptor.fromFd(img.getFenceFd());
- } catch (IOException e) {
- Log.e(TAG,"Failed to parcel buffer fence!");
+ try {
+ SyncFence fd = img.getFence();
+ if (fd.isValid()) {
+ parcelImage.fence = fd.getFdDup();
}
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parcel buffer fence!");
}
parcelImage.width = img.getWidth();
parcelImage.height = img.getHeight();
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c8ecfd0..289ae8a 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -16,10 +16,14 @@
package android.hardware.camera2.impl;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -30,6 +34,7 @@
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.extension.CaptureBundle;
import android.hardware.camera2.extension.CaptureStageImpl;
import android.hardware.camera2.extension.ICaptureProcessorImpl;
@@ -38,7 +43,6 @@
import android.hardware.camera2.extension.IPreviewExtenderImpl;
import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
import android.hardware.camera2.extension.ParcelImage;
-import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -50,11 +54,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
@@ -1656,12 +1656,13 @@
private static ParcelImage initializeParcelImage(Image img) {
ParcelImage parcelImage = new ParcelImage();
parcelImage.buffer = img.getHardwareBuffer();
- if (img.getFenceFd() >= 0) {
- try {
- parcelImage.fence = ParcelFileDescriptor.fromFd(img.getFenceFd());
- } catch (IOException e) {
- Log.e(TAG,"Failed to parcel buffer fence!");
+ try {
+ SyncFence fd = img.getFence();
+ if (fd.isValid()) {
+ parcelImage.fence = fd.getFdDup();
}
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parcel buffer fence!");
}
parcelImage.width = img.getWidth();
parcelImage.height = img.getHeight();
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 5261555..cf1aac0 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
@@ -24,7 +25,9 @@
import android.hardware.DataSpace;
import android.hardware.DataSpace.NamedDataSpace;
import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
+import java.io.IOException;
import java.nio.ByteBuffer;
/**
@@ -223,12 +226,17 @@
public abstract int getScalingMode();
/**
- * Get the fence file descriptor associated with this frame.
- * @return The fence file descriptor for this frame.
- * @hide
+ * Get the SyncFence object associated with this frame.
+ *
+ * <p>This function returns an invalid SyncFence after {@link #getPlanes()} on the image
+ * dequeued from {@link ImageWriter} via {@link ImageWriter#dequeueInputImage()}.</p>
+ *
+ * @return The SyncFence for this frame.
+ * @throws IOException if there is an error when a SyncFence object returns.
+ * @see android.hardware.SyncFence
*/
- public int getFenceFd() {
- return -1;
+ public @NonNull SyncFence getFence() throws IOException {
+ return SyncFence.createEmpty();
}
/**
@@ -283,6 +291,17 @@
return;
}
+ /**
+ * Set the fence file descriptor with this frame.
+ * @param fence The fence file descriptor to be set for this frame.
+ * @throws IOException if there is an error when setting a SyncFence.
+ * @see android.hardware.SyncFence
+ */
+ public void setFence(@NonNull SyncFence fence) throws IOException {
+ throwISEIfImageIsInvalid();
+ return;
+ }
+
private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
/**
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index e2e48d3..c9bda48 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -27,13 +27,16 @@
import android.hardware.DataSpace.NamedDataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer.Usage;
+import android.hardware.SyncFence;
import android.hardware.camera2.MultiResolutionImageReader;
import android.os.Handler;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.view.Surface;
import dalvik.system.VMRuntime;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -1219,9 +1222,15 @@
}
@Override
- public int getFenceFd() {
+ public SyncFence getFence() throws IOException {
throwISEIfImageIsInvalid();
- return nativeGetFenceFd();
+ // duplicate ParcelFileDescriptor because native still retains the fence ownership.
+ int fence = nativeGetFenceFd();
+ if (fence != -1) {
+ return SyncFence.create(ParcelFileDescriptor.fromFd(nativeGetFenceFd()));
+ } else {
+ return SyncFence.createEmpty();
+ }
}
@Override
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index a1aedf1..5c3f918 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -28,16 +28,19 @@
import android.hardware.DataSpace.NamedDataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer.Usage;
+import android.hardware.SyncFence;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.util.Size;
import android.view.Surface;
import dalvik.system.VMRuntime;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -1144,6 +1147,23 @@
}
@Override
+ public SyncFence getFence() throws IOException {
+ throwISEIfImageIsInvalid();
+ // if mNativeFenceFd is -1, the fence is closed
+ if (mNativeFenceFd != -1) {
+ return SyncFence.create(ParcelFileDescriptor.fromFd(mNativeFenceFd));
+ } else {
+ return SyncFence.createEmpty();
+ }
+ }
+
+ @Override
+ public void setFence(@NonNull SyncFence fence) throws IOException {
+ throwISEIfImageIsInvalid();
+ nativeSetFenceFd(fence.getFdDup().detachFd());
+ }
+
+ @Override
public Plane[] getPlanes() {
throwISEIfImageIsInvalid();
@@ -1268,6 +1288,8 @@
private synchronized native int nativeGetFormat(long dataSpace);
private synchronized native HardwareBuffer nativeGetHardwareBuffer();
+
+ private synchronized native void nativeSetFenceFd(int fenceFd);
}
// Native implemented ImageWriter methods.
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index eca26dc..8f5c9da 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -42,7 +42,6 @@
#include <deque>
#define IMAGE_BUFFER_JNI_ID "mNativeBuffer"
-#define IMAGE_FORMAT_UNKNOWN 0 // This is the same value as ImageFormat#UNKNOWN.
// ----------------------------------------------------------------------------
using namespace android;
@@ -991,6 +990,11 @@
static void Image_setFenceFd(JNIEnv* env, jobject thiz, int fenceFd) {
ALOGV("%s:", __FUNCTION__);
+ int curtFenceFd = reinterpret_cast<jint>(
+ env->GetIntField(thiz,gSurfaceImageClassInfo.mNativeFenceFd));
+ if (curtFenceFd != -1) {
+ close(curtFenceFd);
+ }
env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
}
@@ -1120,6 +1124,7 @@
{"nativeGetWidth", "()I", (void*)Image_getWidth },
{"nativeGetHeight", "()I", (void*)Image_getHeight },
{"nativeGetFormat", "(J)I", (void*)Image_getFormat },
+ {"nativeSetFenceFd", "(I)V", (void*)Image_setFenceFd },
{"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
(void*)Image_getHardwareBuffer },
};