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
2 changes: 1 addition & 1 deletion docs/guide/advanced/video-codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ class MyDecoder implements VideoDecoder {

- The image and its buffer are read-only and valid only until `decode()` returns; copy the data to keep it longer.
- A decoded frame must carry the capture time of the image it was decoded from as its timestamp.
- `onDecodedFrame()` takes a reference of its own, so the caller still releases the frame it created. Frame buffers other than `NativeI420Buffer` are copied, and have to provide direct byte buffers.
- `onDecodedFrame()` takes a reference of its own, so the caller still releases the frame it created. A buffer whose `toI420()` returns a `NativeI420Buffer` is shared; any other is copied, and has to provide direct byte buffers as large as its strides and height require.

## Errors

Expand Down
4 changes: 3 additions & 1 deletion docs/guide/video/custom-video-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ VideoFrame frame = new VideoFrame(buffer, System.nanoTime());
videoSource.pushFrame(frame);

// Don't forget to release the frame when done
frame.dispose();
frame.release();
```

A frame can also carry a `VideoFrameBuffer` of your own. The source takes its pixels from `toI420()`: a `NativeI420Buffer` returned there is used as it is, and any other `I420Buffer` is copied, so its planes have to be direct byte buffers that are as large as their strides and the height require. A buffer that does not fit its description is rejected with a `RuntimeException`. `VideoFrame.copy()` works the same way, and always returns a frame with a `NativeI420Buffer`.

## Video Format Considerations

When pushing video frames, you need to consider the following:
Expand Down
4 changes: 2 additions & 2 deletions webrtc-jni/src/main/cpp/include/JNI_NativeI420Buffer.h

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

9 changes: 6 additions & 3 deletions webrtc-jni/src/main/cpp/include/api/VideoFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ namespace jni
// is released. Returns null if the pixels cannot be converted.
JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::VideoFrame & frame);

// Returns the native pixel buffer of a Java VideoFrameBuffer. A
// NativeI420Buffer is shared; any other buffer is converted to I420
// in Java and copied. Returns null if it has no readable pixels.
// Returns the native pixel buffer of a Java VideoFrameBuffer, which
// is converted to I420 in Java first. A NativeI420Buffer that comes
// out of that is shared; any other I420 buffer is copied. Returns
// null if it has no readable pixels, such as a released buffer or
// one with heap byte buffers, and throws a RuntimeException if its
// size, strides and planes do not fit together.
webrtc::scoped_refptr<webrtc::VideoFrameBuffer> toNativeBuffer(JNIEnv * env, const JavaRef<jobject> & javaBuffer);
}

Expand Down
17 changes: 10 additions & 7 deletions webrtc-jni/src/main/cpp/src/JNI_NativeI420Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_media_video_NativeI420Buffer_al
}

JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_media_video_NativeI420Buffer_copy
(JNIEnv* env, jclass caller, jint width, jint height, jobject jSrcY, jint srcStrideY,
jobject jSrcU, jint srcStrideU, jobject jSrcV, jint srcStrideV)
(JNIEnv * env, jclass caller, jobject jSource)
{
try {
const uint8_t * src_y = static_cast<uint8_t*>(env->GetDirectBufferAddress(jSrcY));
const uint8_t * src_u = static_cast<uint8_t*>(env->GetDirectBufferAddress(jSrcU));
const uint8_t * src_v = static_cast<uint8_t*>(env->GetDirectBufferAddress(jSrcV));
webrtc::scoped_refptr<webrtc::VideoFrameBuffer> source =
jni::VideoFrame::toNativeBuffer(env, jni::JavaLocalRef<jobject>(env, jSource));
webrtc::scoped_refptr<webrtc::I420BufferInterface> sourceI420 = source ? source->ToI420() : nullptr;

webrtc::scoped_refptr<webrtc::I420BufferInterface> i420Buffer = webrtc::I420Buffer::Copy(width, height,
src_y, srcStrideY, src_u, srcStrideU, src_v, srcStrideV);
if (sourceI420 == nullptr) {
env->Throw(jni::JavaRuntimeException(env, "The buffer cannot be read"));
return nullptr;
}

webrtc::scoped_refptr<webrtc::I420BufferInterface> i420Buffer = webrtc::I420Buffer::Copy(*sourceI420);

jni::JavaLocalRef<jobject> jBuffer = jni::I420Buffer::toJava(env, i420Buffer);

Expand Down
66 changes: 21 additions & 45 deletions webrtc-jni/src/main/cpp/src/api/VideoFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,43 +61,14 @@ namespace jni
int rotation = obj.getInt(javaClass->rotation);
int64_t timestamp_ns = obj.getLong(javaClass->timestampNs);
JavaLocalRef<jobject> jBuffer = obj.getObject(javaClass->buffer);

/*
// Get the I420Buffer properties
const auto i420Class = JavaClasses::get<JavaNativeI420BufferClass>(env);
JavaObject bufferObj(env, jBuffer);

int width = bufferObj.getInt(i420Class->width);
int height = bufferObj.getInt(i420Class->height);

JavaLocalRef<jobject> dataY = bufferObj.getObject(i420Class->dataY);
JavaLocalRef<jobject> dataU = bufferObj.getObject(i420Class->dataU);
JavaLocalRef<jobject> dataV = bufferObj.getObject(i420Class->dataV);

int strideY = bufferObj.getInt(i420Class->strideY);
int strideU = bufferObj.getInt(i420Class->strideU);
int strideV = bufferObj.getInt(i420Class->strideV);

// Get the direct buffer addresses
const uint8_t * y_data = static_cast<uint8_t*>(env->GetDirectBufferAddress(dataY.get()));
const uint8_t * u_data = static_cast<uint8_t*>(env->GetDirectBufferAddress(dataU.get()));
const uint8_t * v_data = static_cast<uint8_t*>(env->GetDirectBufferAddress(dataV.get()));

// Create a native I420Buffer
webrtc::scoped_refptr<webrtc::I420Buffer> buffer = webrtc::I420Buffer::Copy(
width, height, y_data, strideY, u_data, strideU, v_data, strideV);
*/

webrtc::I420BufferInterface * i420Buffer = GetHandle<webrtc::I420BufferInterface>(env, jBuffer);

// A buffer that has been released for good has no pixels left.
if (i420Buffer == nullptr) {

webrtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer = toNativeBuffer(env, jBuffer);

if (buffer == nullptr) {
throw JavaWrappedException(JavaLocalRef<jthrowable>(env,
JavaRuntimeException(env, "The buffer of the VideoFrame has been released")));
JavaRuntimeException(env, "The buffer of the VideoFrame cannot be read")));
}

webrtc::scoped_refptr<webrtc::I420BufferInterface> buffer(i420Buffer);

return webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
//.set_timestamp_rtp(timestamp_rtp)
Expand Down Expand Up @@ -136,28 +107,33 @@ namespace jni

webrtc::scoped_refptr<webrtc::VideoFrameBuffer> toNativeBuffer(JNIEnv * env, const JavaRef<jobject> & javaBuffer)
{
const auto nativeClass = JavaClasses::get<JavaNativeI420BufferClass>(env);

if (env->IsInstanceOf(javaBuffer, nativeClass->cls)) {
webrtc::I420BufferInterface * buffer = GetHandle<webrtc::I420BufferInterface>(env, javaBuffer);

// Released for good, so there is nothing to read.
if (buffer == nullptr) {
return nullptr;
}

return webrtc::scoped_refptr<webrtc::VideoFrameBuffer>(buffer);
if (javaBuffer.get() == nullptr) {
return nullptr;
}

const auto javaClass = JavaClasses::get<JavaI420BufferClass>(env);

// A NativeI420Buffer returns itself, and so may any other buffer that keeps its pixels in one.
JavaLocalRef<jobject> i420(env, env->CallObjectMethod(javaBuffer, javaClass->toI420));
ExceptionCheck(env);

if (i420.get() == nullptr) {
return nullptr;
}

const auto nativeClass = JavaClasses::get<JavaNativeI420BufferClass>(env);

if (env->IsInstanceOf(i420, nativeClass->cls)) {
webrtc::I420BufferInterface * buffer = GetHandle<webrtc::I420BufferInterface>(env, i420);

// Released for good, so there is nothing to read.
if (buffer == nullptr) {
return nullptr;
}

return webrtc::scoped_refptr<webrtc::VideoFrameBuffer>(buffer);
}

const jint width = env->CallIntMethod(i420, javaClass->getWidth);
const jint height = env->CallIntMethod(i420, javaClass->getHeight);
const jint strideY = env->CallIntMethod(i420, javaClass->getStrideY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ public String toString() {
* Creates a copy of this I420 buffer. This method performs a deep copy of all data in the buffer.
*
* @return A new NativeI420Buffer instance with the same dimensions and data as this buffer.
*
* @throws RuntimeException if this buffer has been released.
*/
public NativeI420Buffer copy() {
return copy(width, height, dataY.slice(), strideY, dataU.slice(), strideU, dataV.slice(), strideV);
return copy(this);
}

/**
Expand Down Expand Up @@ -148,10 +150,14 @@ public static NativeI420Buffer allocate(int width, int height) {
private static native NativeI420Buffer allocateBuffer(int width, int height);

/**
* Creates a copy of an I420 buffer with the given dimensions and data.
* Copies the pixels of any buffer, as {@link VideoFrameBuffer#toI420()} provides them, into a new
* buffer.
*
* @throws RuntimeException if the buffer has no readable pixels, such as a released buffer or one
* with heap byte buffers, or if the size, strides and planes of the I420
* buffer do not fit together.
*/
private static native NativeI420Buffer copy(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU,
int strideU, ByteBuffer dataV, int strideV);
static native NativeI420Buffer copy(VideoFrameBuffer buffer);

/**
* Crops this buffer without copying. The cropped buffer shares the pixels of this one and holds a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,18 @@ public VideoFrame(VideoFrameBuffer buffer, int rotation, long timestampNs) {
}

/**
* Creates a deep copy of this VideoFrame. The new frame will have its own copy of the buffer data.
* Creates a deep copy of this VideoFrame. The new frame will have its own copy of the buffer data,
* in a {@link NativeI420Buffer} made from what {@link VideoFrameBuffer#toI420()} of the buffer
* returns.
*
* @return A new VideoFrame with a copy of the buffer data.
*
* @throws RuntimeException if the buffer has no readable pixels, such as a released buffer or one
* with heap byte buffers, or if the size, strides and planes of the I420
* buffer do not fit together.
*/
public VideoFrame copy() {
NativeI420Buffer nativeBuffer = (NativeI420Buffer) buffer;

return new VideoFrame(nativeBuffer.copy(), rotation, timestampNs);
return new VideoFrame(NativeI420Buffer.copy(buffer), rotation, timestampNs);
}

@Override
Expand Down
Loading
Loading