Android: Respect input buffer layout of MediaFormat
On Android, MediaCodec can request a specific layout of the input buffer.
One can use the stride and slice height to calculate the layout from
the Encoder's MediaFormat. The current code assumes
a specific layout, which is a problematic in Android 12.
Fix this by honoring the stride and slice-height.
Bug: webrtc:13427
Change-Id: I2d3e429309e3add3ae668e0390460b51e6a49eb9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/240680
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Daniel.L (Byoungchan) Lee <daniel.l@hpcnt.com>
Cr-Commit-Position: refs/heads/main@{#36033}
diff --git a/sdk/android/api/org/webrtc/YuvHelper.java b/sdk/android/api/org/webrtc/YuvHelper.java
index 83bd7dc..afb8e83 100644
--- a/sdk/android/api/org/webrtc/YuvHelper.java
+++ b/sdk/android/api/org/webrtc/YuvHelper.java
@@ -14,55 +14,93 @@
/** Wraps libyuv methods to Java. All passed byte buffers must be direct byte buffers. */
public class YuvHelper {
- /** Helper method for copying I420 to tightly packed destination buffer. */
+ /**
+ * Copy I420 Buffer to a contiguously allocated buffer.
+ * <p> In Android, MediaCodec can request a buffer of a specific layout with the stride and
+ * slice-height (or plane height), and this function is used in this case.
+ * <p> For more information, see
+ * https://cs.android.com/android/platform/superproject/+/64fea7e5726daebc40f46890100837c01091100d:frameworks/base/media/java/android/media/MediaFormat.java;l=568
+ * @param dstStrideY the stride of output buffers' Y plane.
+ * @param dstSliceHeightY the slice-height of output buffer's Y plane.
+ * @param dstStrideU the stride of output buffers' U (and V) plane.
+ * @param dstSliceHeightU the slice-height of output buffer's U (and V) plane
+ */
public static void I420Copy(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
- ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int width, int height) {
- final int chromaHeight = (height + 1) / 2;
- final int chromaWidth = (width + 1) / 2;
+ ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int dstWidth, int dstHeight, int dstStrideY,
+ int dstSliceHeightY, int dstStrideU, int dstSliceHeightU) {
+ final int chromaWidth = (dstWidth + 1) / 2;
+ final int chromaHeight = (dstHeight + 1) / 2;
- final int minSize = width * height + chromaWidth * chromaHeight * 2;
- if (dst.capacity() < minSize) {
+ final int dstStartY = 0;
+ final int dstEndY = dstStartY + dstStrideY * dstHeight;
+ final int dstStartU = dstStartY + dstStrideY * dstSliceHeightY;
+ final int dstEndU = dstStartU + dstStrideU * chromaHeight;
+ final int dstStartV = dstStartU + dstStrideU * dstSliceHeightU;
+ // The last line doesn't need any padding, so use chromaWidth
+ // to calculate the exact end position.
+ final int dstEndV = dstStartV + dstStrideU * (chromaHeight - 1) + chromaWidth;
+ if (dst.capacity() < dstEndV) {
throw new IllegalArgumentException("Expected destination buffer capacity to be at least "
- + minSize + " was " + dst.capacity());
+ + dstEndV + " was " + dst.capacity());
}
- final int startY = 0;
- final int startU = height * width;
- final int startV = startU + chromaHeight * chromaWidth;
-
- dst.position(startY);
+ dst.limit(dstEndY);
+ dst.position(dstStartY);
final ByteBuffer dstY = dst.slice();
- dst.position(startU);
+ dst.limit(dstEndU);
+ dst.position(dstStartU);
final ByteBuffer dstU = dst.slice();
- dst.position(startV);
+ dst.limit(dstEndV);
+ dst.position(dstStartV);
final ByteBuffer dstV = dst.slice();
- nativeI420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, width, dstU,
- chromaWidth, dstV, chromaWidth, width, height);
+ I420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, dstStrideY, dstU,
+ dstStrideU, dstV, dstStrideU, dstWidth, dstHeight);
+ }
+
+ /** Helper method for copying I420 to tightly packed destination buffer. */
+ public static void I420Copy(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
+ ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int dstWidth, int dstHeight) {
+ I420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dst, dstWidth, dstHeight,
+ dstWidth, dstHeight, (dstWidth + 1) / 2, (dstHeight + 1) / 2);
+ }
+
+ /**
+ * Copy I420 Buffer to a contiguously allocated buffer.
+ * @param dstStrideY the stride of output buffers' Y plane.
+ * @param dstSliceHeightY the slice-height of output buffer's Y plane.
+ */
+ public static void I420ToNV12(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
+ ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int dstWidth, int dstHeight, int dstStrideY,
+ int dstSliceHeightY) {
+ final int chromaHeight = (dstHeight + 1) / 2;
+ final int chromaWidth = (dstWidth + 1) / 2;
+
+ final int dstStartY = 0;
+ final int dstEndY = dstStartY + dstStrideY * dstHeight;
+ final int dstStartUV = dstStartY + dstStrideY * dstSliceHeightY;
+ final int dstEndUV = dstStartUV + chromaWidth * chromaHeight * 2;
+ if (dst.capacity() < dstEndUV) {
+ throw new IllegalArgumentException("Expected destination buffer capacity to be at least "
+ + dstEndUV + " was " + dst.capacity());
+ }
+
+ dst.limit(dstEndY);
+ dst.position(dstStartY);
+ final ByteBuffer dstY = dst.slice();
+ dst.limit(dstEndUV);
+ dst.position(dstStartUV);
+ final ByteBuffer dstUV = dst.slice();
+
+ I420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, dstStrideY, dstUV,
+ chromaWidth * 2, dstWidth, dstHeight);
}
/** Helper method for copying I420 to tightly packed NV12 destination buffer. */
public static void I420ToNV12(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
- ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int width, int height) {
- final int chromaWidth = (width + 1) / 2;
- final int chromaHeight = (height + 1) / 2;
-
- final int minSize = width * height + chromaWidth * chromaHeight * 2;
- if (dst.capacity() < minSize) {
- throw new IllegalArgumentException("Expected destination buffer capacity to be at least "
- + minSize + " was " + dst.capacity());
- }
-
- final int startY = 0;
- final int startUV = height * width;
-
- dst.position(startY);
- final ByteBuffer dstY = dst.slice();
- dst.position(startUV);
- final ByteBuffer dstUV = dst.slice();
-
- nativeI420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, width, dstUV,
- chromaWidth * 2, width, height);
+ ByteBuffer srcV, int srcStrideV, ByteBuffer dst, int dstWidth, int dstHeight) {
+ I420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dst, dstWidth, dstHeight,
+ dstWidth, dstHeight);
}
/** Helper method for rotating I420 to tightly packed destination buffer. */
@@ -109,9 +147,18 @@
src, srcStride, dstY, dstStrideY, dstU, dstStrideU, dstV, dstStrideV, width, height);
}
+ /**
+ * Copies I420 to the I420 dst buffer.
+ * <p> Unlike `libyuv::I420Copy`, this function checks if the height <= 0, so flipping is not
+ * supported.
+ */
public static void I420Copy(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
ByteBuffer srcV, int srcStrideV, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU,
int dstStrideU, ByteBuffer dstV, int dstStrideV, int width, int height) {
+ if (srcY == null || srcU == null || srcV == null || dstY == null || dstU == null || dstV == null
+ || width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Invalid I420Copy input arguments");
+ }
nativeI420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, dstStrideY, dstU,
dstStrideU, dstV, dstStrideV, width, height);
}
@@ -119,6 +166,10 @@
public static void I420ToNV12(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU,
ByteBuffer srcV, int srcStrideV, ByteBuffer dstY, int dstStrideY, ByteBuffer dstUV,
int dstStrideUV, int width, int height) {
+ if (srcY == null || srcU == null || srcV == null || dstY == null || dstUV == null || width <= 0
+ || height <= 0) {
+ throw new IllegalArgumentException("Invalid I420ToNV12 input arguments");
+ }
nativeI420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, dstStrideY, dstUV,
dstStrideUV, width, height);
}