Fix and optimize input buffer filling in HardwareVideoEncoder.
Previously input buffers would be filled incorrectly for sparsely
packed buffers where stride is not equal to the plane width.
Bug: webrtc:8478
Change-Id: I080fa3c354a27982bb996be8c1e41b103384e4bc
Reviewed-on: https://webrtc-review.googlesource.com/17321
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20550}
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index 8d3d587..19eb00b 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -131,6 +131,7 @@
"src/jni/videotrack_jni.cc",
"src/jni/wrapped_native_i420_buffer.cc",
"src/jni/wrapped_native_i420_buffer.h",
+ "src/jni/yuvhelper.cc",
]
configs += [ ":libjingle_peerconnection_jni_warnings_config" ]
@@ -386,9 +387,9 @@
android_library("libjingle_peerconnection_java") {
java_files = [
+ "api/org/webrtc/AudioProcessingFactory.java",
"api/org/webrtc/AudioSource.java",
"api/org/webrtc/AudioTrack.java",
- "api/org/webrtc/AudioProcessingFactory.java",
"api/org/webrtc/CallSessionFileRotatingLogSink.java",
"api/org/webrtc/Camera1Capturer.java",
"api/org/webrtc/Camera1Enumerator.java",
@@ -454,6 +455,7 @@
"api/org/webrtc/VideoSource.java",
"api/org/webrtc/VideoTrack.java",
"api/org/webrtc/YuvConverter.java",
+ "api/org/webrtc/YuvHelper.java",
"src/java/org/webrtc/AndroidVideoTrackSourceObserver.java",
"src/java/org/webrtc/BaseBitrateAdjuster.java",
"src/java/org/webrtc/BitrateAdjuster.java",
@@ -507,16 +509,16 @@
android_manifest = "instrumentationtests/AndroidManifest.xml"
java_files = [
- "instrumentationtests/src/org/webrtc/DefaultAudioProcessingFactoryTest.java",
"instrumentationtests/src/org/webrtc/Camera1CapturerUsingByteBufferTest.java",
"instrumentationtests/src/org/webrtc/Camera1CapturerUsingTextureTest.java",
"instrumentationtests/src/org/webrtc/Camera2CapturerTest.java",
"instrumentationtests/src/org/webrtc/CameraVideoCapturerTestFixtures.java",
+ "instrumentationtests/src/org/webrtc/DefaultAudioProcessingFactoryTest.java",
"instrumentationtests/src/org/webrtc/EglRendererTest.java",
"instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java",
"instrumentationtests/src/org/webrtc/GlRectDrawerTest.java",
- "instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java",
"instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java",
+ "instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java",
"instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java",
"instrumentationtests/src/org/webrtc/NetworkMonitorTest.java",
"instrumentationtests/src/org/webrtc/PeerConnectionTest.java",
@@ -525,6 +527,7 @@
"instrumentationtests/src/org/webrtc/SurfaceViewRendererOnMeasureTest.java",
"instrumentationtests/src/org/webrtc/VideoFileRendererTest.java",
"instrumentationtests/src/org/webrtc/WebRtcJniBootTest.java",
+ "instrumentationtests/src/org/webrtc/YuvHelperTest.java",
]
data = [
diff --git a/sdk/android/api/org/webrtc/YuvHelper.java b/sdk/android/api/org/webrtc/YuvHelper.java
new file mode 100644
index 0000000..344d5d9
--- /dev/null
+++ b/sdk/android/api/org/webrtc/YuvHelper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import java.nio.ByteBuffer;
+
+/** 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. */
+ 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;
+
+ 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 startU = height * width;
+ final int startV = startU + chromaHeight * chromaWidth;
+
+ dst.position(startY);
+ final ByteBuffer dstY = dst.slice();
+ dst.position(startU);
+ final ByteBuffer dstU = dst.slice();
+ dst.position(startV);
+ final ByteBuffer dstV = dst.slice();
+
+ I420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, width, dstU, chromaWidth,
+ dstV, chromaWidth, width, height);
+ }
+
+ /** 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();
+
+ I420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, width, dstUV,
+ chromaWidth * 2, width, height);
+ }
+
+ public static native 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);
+ public static native 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);
+}
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java
index 340d184..4260397 100644
--- a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java
+++ b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java
@@ -148,6 +148,8 @@
@Before
public void setUp() {
+ NativeLibrary.initialize(new NativeLibrary.DefaultLoader());
+
eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
index 49b601e..5b3c04f 100644
--- a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
+++ b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java
@@ -328,6 +328,8 @@
// # Tests
@Before
public void setUp() {
+ NativeLibrary.initialize(new NativeLibrary.DefaultLoader());
+
eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/YuvHelperTest.java b/sdk/android/instrumentationtests/src/org/webrtc/YuvHelperTest.java
new file mode 100644
index 0000000..a6ded5c
--- /dev/null
+++ b/sdk/android/instrumentationtests/src/org/webrtc/YuvHelperTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import java.nio.ByteBuffer;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BaseJUnit4ClassRunner.class)
+public class YuvHelperTest {
+ private static final int TEST_WIDTH = 3;
+ private static final int TEST_HEIGHT = 3;
+ private static final int TEST_CHROMA_WIDTH = 2;
+ private static final int TEST_CHROMA_HEIGHT = 2;
+
+ private static final int TEST_I420_STRIDE_Y = 3;
+ private static final int TEST_I420_STRIDE_V = 2;
+ private static final int TEST_I420_STRIDE_U = 4;
+
+ private static final ByteBuffer TEST_I420_Y = getTestY();
+ private static final ByteBuffer TEST_I420_U = getTestU();
+ private static final ByteBuffer TEST_I420_V = getTestV();
+
+ private static ByteBuffer getTestY() {
+ final ByteBuffer testY = ByteBuffer.allocateDirect(TEST_HEIGHT * TEST_I420_STRIDE_Y);
+ testY.put(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9});
+ return testY;
+ }
+
+ private static ByteBuffer getTestU() {
+ final ByteBuffer testU = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * TEST_I420_STRIDE_V);
+ testU.put(new byte[] {51, 52, 53, 54});
+ return testU;
+ }
+
+ private static ByteBuffer getTestV() {
+ final ByteBuffer testV = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * TEST_I420_STRIDE_U);
+ testV.put(new byte[] {101, 102, 103, 104, 105, 106, 107, 108});
+ return testV;
+ }
+
+ @Before
+ public void setUp() {
+ NativeLibrary.initialize(new NativeLibrary.DefaultLoader());
+ }
+
+ @SmallTest
+ @Test
+ public void testI420Copy() {
+ final int dstStrideY = TEST_WIDTH;
+ final int dstStrideU = TEST_CHROMA_WIDTH;
+ final int dstStrideV = TEST_CHROMA_WIDTH;
+ final ByteBuffer dstY = ByteBuffer.allocateDirect(TEST_HEIGHT * dstStrideY);
+ final ByteBuffer dstU = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * dstStrideU);
+ final ByteBuffer dstV = ByteBuffer.allocateDirect(TEST_CHROMA_HEIGHT * dstStrideV);
+
+ YuvHelper.I420Copy(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
+ TEST_I420_V, TEST_I420_STRIDE_U, dstY, dstStrideY, dstU, dstStrideU, dstV, dstStrideV,
+ TEST_WIDTH, TEST_HEIGHT);
+
+ assertByteBufferContentEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, dstY);
+ assertByteBufferContentEquals(new byte[] {51, 52, 53, 54}, dstU);
+ assertByteBufferContentEquals(new byte[] {101, 102, 105, 106}, dstV);
+ }
+
+ @SmallTest
+ @Test
+ public void testI420CopyTight() {
+ final ByteBuffer dst = ByteBuffer.allocateDirect(
+ TEST_WIDTH * TEST_HEIGHT + TEST_CHROMA_WIDTH * TEST_CHROMA_HEIGHT * 2);
+
+ YuvHelper.I420Copy(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
+ TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT);
+
+ assertByteBufferContentEquals(
+ new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 51, 52, 53, 54, 101, 102, 105, 106}, dst);
+ }
+
+ @SmallTest
+ @Test
+ public void testI420ToNV12() {
+ final int dstStrideY = TEST_WIDTH;
+ final int dstStrideUV = TEST_CHROMA_WIDTH * 2;
+ final ByteBuffer dstY = ByteBuffer.allocateDirect(TEST_HEIGHT * dstStrideY);
+ final ByteBuffer dstUV = ByteBuffer.allocateDirect(2 * TEST_CHROMA_HEIGHT * dstStrideUV);
+
+ YuvHelper.I420ToNV12(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
+ TEST_I420_V, TEST_I420_STRIDE_U, dstY, dstStrideY, dstUV, dstStrideUV, TEST_WIDTH,
+ TEST_HEIGHT);
+
+ assertByteBufferContentEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, dstY);
+ assertByteBufferContentEquals(new byte[] {51, 101, 52, 102, 53, 105, 54, 106}, dstUV);
+ }
+
+ @SmallTest
+ @Test
+ public void testI420ToNV12Tight() {
+ final int dstStrideY = TEST_WIDTH;
+ final int dstStrideUV = TEST_CHROMA_WIDTH * 2;
+ final ByteBuffer dst = ByteBuffer.allocateDirect(
+ TEST_WIDTH * TEST_HEIGHT + TEST_CHROMA_WIDTH * TEST_CHROMA_HEIGHT * 2);
+
+ YuvHelper.I420ToNV12(TEST_I420_Y, TEST_I420_STRIDE_Y, TEST_I420_U, TEST_I420_STRIDE_V,
+ TEST_I420_V, TEST_I420_STRIDE_U, dst, TEST_WIDTH, TEST_HEIGHT);
+
+ assertByteBufferContentEquals(
+ new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 51, 101, 52, 102, 53, 105, 54, 106}, dst);
+ }
+
+ private static void assertByteBufferContentEquals(byte[] expected, ByteBuffer test) {
+ assertTrue(
+ "ByteBuffer is too small. Expected " + expected.length + " but was " + test.capacity(),
+ test.capacity() >= expected.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("Unexpected ByteBuffer contents at index: " + i, expected[i], test.get(i));
+ }
+ }
+}
diff --git a/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java b/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
index e0cd6b1..61cf9a9 100644
--- a/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
+++ b/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java
@@ -567,36 +567,27 @@
/**
* Enumeration of supported YUV color formats used for MediaCodec's input.
*/
- private static enum YuvFormat {
+ private enum YuvFormat {
I420 {
@Override
- void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer) {
- VideoFrame.I420Buffer i420 = buffer.toI420();
- inputBuffer.put(i420.getDataY());
- inputBuffer.put(i420.getDataU());
- inputBuffer.put(i420.getDataV());
+ void fillBuffer(ByteBuffer dstBuffer, VideoFrame.Buffer srcBuffer) {
+ VideoFrame.I420Buffer i420 = srcBuffer.toI420();
+ YuvHelper.I420Copy(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(),
+ i420.getDataV(), i420.getStrideV(), dstBuffer, i420.getWidth(), i420.getHeight());
i420.release();
}
},
NV12 {
@Override
- void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer) {
- VideoFrame.I420Buffer i420 = buffer.toI420();
- inputBuffer.put(i420.getDataY());
-
- // Interleave the bytes from the U and V portions, starting with U.
- ByteBuffer u = i420.getDataU();
- ByteBuffer v = i420.getDataV();
- int i = 0;
- while (u.hasRemaining() && v.hasRemaining()) {
- inputBuffer.put(u.get());
- inputBuffer.put(v.get());
- }
+ void fillBuffer(ByteBuffer dstBuffer, VideoFrame.Buffer srcBuffer) {
+ VideoFrame.I420Buffer i420 = srcBuffer.toI420();
+ YuvHelper.I420ToNV12(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(),
+ i420.getDataV(), i420.getStrideV(), dstBuffer, i420.getWidth(), i420.getHeight());
i420.release();
}
};
- abstract void fillBuffer(ByteBuffer inputBuffer, VideoFrame.Buffer buffer);
+ abstract void fillBuffer(ByteBuffer dstBuffer, VideoFrame.Buffer srcBuffer);
static YuvFormat valueOf(int colorFormat) {
switch (colorFormat) {
diff --git a/sdk/android/src/jni/yuvhelper.cc b/sdk/android/src/jni/yuvhelper.cc
new file mode 100644
index 0000000..bdf5375
--- /dev/null
+++ b/sdk/android/src/jni/yuvhelper.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <jni.h>
+
+#include "sdk/android/src/jni/jni_helpers.h"
+#include "third_party/libyuv/include/libyuv/convert.h"
+
+namespace webrtc {
+namespace jni {
+
+JNI_FUNCTION_DECLARATION(void,
+ YuvHelper_I420Copy,
+ JNIEnv* jni,
+ jclass,
+ jobject j_src_y,
+ jint src_stride_y,
+ jobject j_src_u,
+ jint src_stride_u,
+ jobject j_src_v,
+ jint src_stride_v,
+ jobject j_dst_y,
+ jint dst_stride_y,
+ jobject j_dst_u,
+ jint dst_stride_u,
+ jobject j_dst_v,
+ jint dst_stride_v,
+ jint width,
+ jint height) {
+ const uint8_t* src_y =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_y));
+ const uint8_t* src_u =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_u));
+ const uint8_t* src_v =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_v));
+ uint8_t* dst_y = static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_y));
+ uint8_t* dst_u = static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_u));
+ uint8_t* dst_v = static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_v));
+
+ libyuv::I420Copy(src_y, src_stride_y, src_u, src_stride_u, src_v,
+ src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u,
+ dst_v, dst_stride_v, width, height);
+}
+
+JNI_FUNCTION_DECLARATION(void,
+ YuvHelper_I420ToNV12,
+ JNIEnv* jni,
+ jclass,
+ jobject j_src_y,
+ jint src_stride_y,
+ jobject j_src_u,
+ jint src_stride_u,
+ jobject j_src_v,
+ jint src_stride_v,
+ jobject j_dst_y,
+ jint dst_stride_y,
+ jobject j_dst_uv,
+ jint dst_stride_uv,
+ jint width,
+ jint height) {
+ const uint8_t* src_y =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_y));
+ const uint8_t* src_u =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_u));
+ const uint8_t* src_v =
+ static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src_v));
+ uint8_t* dst_y = static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_y));
+ uint8_t* dst_uv =
+ static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_uv));
+
+ libyuv::I420ToNV12(src_y, src_stride_y, src_u, src_stride_u, src_v,
+ src_stride_v, dst_y, dst_stride_y, dst_uv, dst_stride_uv,
+ width, height);
+}
+
+} // namespace jni
+} // namespace webrtc