Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/tools/video/video-buffer-converter.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public void onImage(BufferedImage image) {
## Error handling and edge cases
- All methods throw `NullPointerException` if src/dst is null; ensure proper checks.
- `ByteBuffer` destinations must be writable (not read-only) for `convertFromI420`.
- A `ByteBuffer` is read or written between its position and its limit only, also when it is a slice or a view of a larger buffer. The window must hold the whole frame; the position and limit stay as they are.
- Ensure the correct FourCC is used for the actual memory layout you pass/expect.
- Beware of frame rotation metadata; conversions do not rotate pixels. Handle `VideoFrame.rotation` separately if your renderer requires upright images.

Expand Down
8 changes: 4 additions & 4 deletions webrtc-jni/src/main/cpp/include/JNI_VideoBufferConverter.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 34 additions & 12 deletions webrtc-jni/src/main/cpp/src/JNI_VideoBufferConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,35 @@ size_t CalcBufferSize(int width, int height, int fourCC) {
return bufferSize;
}

// Throws and returns false unless [offset, offset + length) lies within the array.
static bool CheckArrayWindow(JNIEnv * env, jbyteArray array, jint offset, jint length)
{
jsize arrayLength = env->GetArrayLength(array);

if (offset < 0 || length < 0 || offset > arrayLength - length) {
env->Throw(jni::JavaRuntimeException(env, "Invalid array window [offset %d, length %d, array %d]",
offset, length, arrayLength));
return false;
}

return true;
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_VideoBufferConverter_I420toByteArray
(JNIEnv * env, jclass cls, jobject jSrcY, jint srcStrideY, jobject jSrcU, jint srcStrideU,
jobject jSrcV, jint srcStrideV, jbyteArray dst, jint width, jint height, jint fourCC)
jobject jSrcV, jint srcStrideV, jbyteArray dst, jint dstOffset, jint dstLength,
jint width, jint height, jint fourCC)
{
try {
jsize arrayLength = env->GetArrayLength(dst);
if (!CheckArrayWindow(env, dst, dstOffset, dstLength)) {
return;
}

size_t requiredSize = CalcBufferSize(width, height, fourCC);

if (arrayLength < requiredSize) {
if (static_cast<size_t>(dstLength) < requiredSize) {
env->Throw(jni::JavaRuntimeException(env, "Insufficient buffer size [has %d, need %zd]",
arrayLength, requiredSize));
dstLength, requiredSize));
return;
}

Expand All @@ -89,7 +107,7 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_VideoBufferConverter_I
if (!arrayPtr) {
return;
}
uint8_t * dstPtr = reinterpret_cast<uint8_t *>(arrayPtr.get());
uint8_t * dstPtr = reinterpret_cast<uint8_t *>(arrayPtr.get()) + dstOffset;

const int conversionResult = libyuv::ConvertFromI420(srcY, srcStrideY, srcU, srcStrideU,
srcV, srcStrideV, dstPtr, 0, width, height, static_cast<uint32_t>(fourCC));
Expand Down Expand Up @@ -138,16 +156,20 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_VideoBufferConverter_I
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_VideoBufferConverter_byteArrayToI420
(JNIEnv * env, jclass cls, jbyteArray src, jint width, jint height, jobject jDstY, jint dstStrideY,
jobject jDstU, jint dstStrideU, jobject jDstV, jint dstStrideV, jint fourCC)
(JNIEnv * env, jclass cls, jbyteArray src, jint srcOffset, jint srcLength, jint width, jint height,
jobject jDstY, jint dstStrideY, jobject jDstU, jint dstStrideU, jobject jDstV, jint dstStrideV,
jint fourCC)
{
try {
jsize arrayLength = env->GetArrayLength(src);
if (!CheckArrayWindow(env, src, srcOffset, srcLength)) {
return;
}

size_t requiredSize = CalcBufferSize(width, height, fourCC);

if (arrayLength < requiredSize) {
if (static_cast<size_t>(srcLength) < requiredSize) {
env->Throw(jni::JavaRuntimeException(env, "Insufficient buffer size [has %d, need %zd]",
arrayLength, requiredSize));
srcLength, requiredSize));
return;
}

Expand All @@ -162,10 +184,10 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_VideoBufferConverter_b
if (!arrayPtr) {
return;
}
const uint8_t * srcPtr = reinterpret_cast<uint8_t *>(arrayPtr.get());
const uint8_t * srcPtr = reinterpret_cast<uint8_t *>(arrayPtr.get()) + srcOffset;

const int conversionResult = libyuv::ConvertToI420(
srcPtr, arrayLength,
srcPtr, srcLength,
dstY, dstStrideY,
dstU, dstStrideU,
dstV, dstStrideV,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static void convertFromI420(VideoFrameBuffer src, byte[] dst, FourCC four
i420.getDataY(), i420.getStrideY(),
i420.getDataU(), i420.getStrideU(),
i420.getDataV(), i420.getStrideV(),
dst,
dst, 0, dst.length,
i420.getWidth(), i420.getHeight(),
fourCC.value());
}
Expand All @@ -67,10 +67,15 @@ public static void convertFromI420(VideoFrameBuffer src, byte[] dst, FourCC four
* Convert an arbitrary {@link VideoFrameBuffer} to the specified pixel format
* and write the result into a {@link ByteBuffer}. If the destination buffer
* is a direct buffer, a native direct path is used; otherwise its backing
* array (or a temporary array) is employed.
* array is employed.
* <p>
* The result is written to the bytes between the buffer's position and its
* limit, starting at the position. The position and limit stay as they are,
* and no byte outside of them is touched.
*
* @param src Source video frame buffer.
* @param dst Writable destination {@link ByteBuffer}. Must not be read-only.
* @param dst Writable destination {@link ByteBuffer}. Must not be read-only,
* and must have enough bytes remaining for the converted frame.
* @param fourCC Target FourCC format identifier.
*
* @throws NullPointerException if {@code src} or {@code dst} is {@code null}.
Expand All @@ -92,30 +97,23 @@ public static void convertFromI420(VideoFrameBuffer src, ByteBuffer dst, FourCC
I420Buffer i420 = src.toI420();

if (dst.isDirect()) {
// The slice starts at the position and ends at the limit, which
// the native code sees as the buffer's address and capacity.
I420toDirectBuffer(
i420.getDataY(), i420.getStrideY(),
i420.getDataU(), i420.getStrideU(),
i420.getDataV(), i420.getStrideV(),
dst,
dst.slice(),
i420.getWidth(), i420.getHeight(),
fourCC.value());
}
else {
byte[] arrayBuffer;

if (dst.hasArray()) {
arrayBuffer = dst.array();
}
else {
arrayBuffer = new byte[dst.remaining()];
dst.get(arrayBuffer);
}

// A writable heap buffer always has an accessible array.
I420toByteArray(
i420.getDataY(), i420.getStrideY(),
i420.getDataU(), i420.getStrideU(),
i420.getDataV(), i420.getStrideV(),
arrayBuffer,
dst.array(), dst.arrayOffset() + dst.position(), dst.remaining(),
i420.getWidth(), i420.getHeight(),
fourCC.value());
}
Expand All @@ -142,7 +140,7 @@ public static void convertToI420(byte[] src, I420Buffer dst, FourCC fourCC)
}

byteArrayToI420(
src,
src, 0, src.length,
dst.getWidth(), dst.getHeight(),
dst.getDataY(), dst.getStrideY(),
dst.getDataU(), dst.getStrideU(),
Expand All @@ -155,6 +153,10 @@ public static void convertToI420(byte[] src, I420Buffer dst, FourCC fourCC)
* specified by {@code fourCC}) to I420 and write the result into the provided
* {@link I420Buffer}. Uses a direct native path for direct buffers; otherwise
* reads from the backing or a temporary array.
* <p>
* The source frame is read from the bytes between the buffer's position and
* its limit, starting at the position. The position and limit stay as they
* are.
*
* @param src Source pixel data buffer.
* @param dst Destination I420 buffer (pre-allocated).
Expand All @@ -173,27 +175,35 @@ public static void convertToI420(ByteBuffer src, I420Buffer dst, FourCC fourCC)
}

if (src.isDirect()) {
// The slice starts at the position and ends at the limit, which
// the native code sees as the buffer's address and capacity.
directBufferToI420(
src,
src.slice(),
dst.getWidth(), dst.getHeight(),
dst.getDataY(), dst.getStrideY(),
dst.getDataU(), dst.getStrideU(),
dst.getDataV(), dst.getStrideV(),
fourCC.value());
}
else {
byte[] arrayBuffer;
byte[] array;
int offset;
int length = src.remaining();

if (src.hasArray()) {
arrayBuffer = src.array();
array = src.array();
offset = src.arrayOffset() + src.position();
}
else {
arrayBuffer = new byte[src.remaining()];
src.get(arrayBuffer);
// Read-only, so the array is not accessible. Copied through a
// duplicate, which leaves the position of the buffer as it is.
array = new byte[length];
offset = 0;
src.duplicate().get(array);
}

byteArrayToI420(
arrayBuffer,
array, offset, length,
dst.getWidth(), dst.getHeight(),
dst.getDataY(), dst.getStrideY(),
dst.getDataU(), dst.getStrideU(),
Expand All @@ -206,7 +216,7 @@ private native static void I420toByteArray(
ByteBuffer srcY, int srcStrideY,
ByteBuffer srcU, int srcStrideU,
ByteBuffer srcV, int srcStrideV,
byte[] dst,
byte[] dst, int dstOffset, int dstLength,
int width, int height,
int fourCC) throws Exception;

Expand All @@ -219,7 +229,7 @@ private native static void I420toDirectBuffer(
int fourCC) throws Exception;

private native static void byteArrayToI420(
byte[] src,
byte[] src, int srcOffset, int srcLength,
int width, int height,
ByteBuffer dstY, int dstStrideY,
ByteBuffer dstU, int dstStrideU,
Expand Down
Loading
Loading