
This ensures memory is released timely and avoids problems with garbage collection. Native buffers don't support array operation, so FileVideoCapturer had to be update to use FileChannel to write ByteBuffers directly. Bug: None Change-Id: I3f63d2adc159e9f39f0c68dd0bd6b1747686584e Reviewed-on: https://webrtc-review.googlesource.com/55262 Commit-Queue: Sami Kalliomäki <sakal@webrtc.org> Reviewed-by: Anders Carlsson <andersc@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22118}
179 lines
5.3 KiB
Java
179 lines
5.3 KiB
Java
/*
|
|
* 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;
|
|
import org.webrtc.VideoFrame.I420Buffer;
|
|
|
|
/** Implementation of VideoFrame.I420Buffer backed by Java direct byte buffers. */
|
|
public class JavaI420Buffer implements VideoFrame.I420Buffer {
|
|
private final int width;
|
|
private final int height;
|
|
private final ByteBuffer dataY;
|
|
private final ByteBuffer dataU;
|
|
private final ByteBuffer dataV;
|
|
private final int strideY;
|
|
private final int strideU;
|
|
private final int strideV;
|
|
private final Runnable releaseCallback;
|
|
private final Object refCountLock = new Object();
|
|
|
|
private int refCount;
|
|
|
|
private JavaI420Buffer(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU,
|
|
int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) {
|
|
this.width = width;
|
|
this.height = height;
|
|
this.dataY = dataY;
|
|
this.dataU = dataU;
|
|
this.dataV = dataV;
|
|
this.strideY = strideY;
|
|
this.strideU = strideU;
|
|
this.strideV = strideV;
|
|
this.releaseCallback = releaseCallback;
|
|
|
|
this.refCount = 1;
|
|
}
|
|
|
|
/** Wraps existing ByteBuffers into JavaI420Buffer object without copying the contents. */
|
|
public static JavaI420Buffer wrap(int width, int height, ByteBuffer dataY, int strideY,
|
|
ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) {
|
|
if (dataY == null || dataU == null || dataV == null) {
|
|
throw new IllegalArgumentException("Data buffers cannot be null.");
|
|
}
|
|
if (!dataY.isDirect() || !dataU.isDirect() || !dataV.isDirect()) {
|
|
throw new IllegalArgumentException("Data buffers must be direct byte buffers.");
|
|
}
|
|
|
|
// Slice the buffers to prevent external modifications to the position / limit of the buffer.
|
|
// Note that this doesn't protect the contents of the buffers from modifications.
|
|
dataY = dataY.slice();
|
|
dataU = dataU.slice();
|
|
dataV = dataV.slice();
|
|
|
|
final int chromaHeight = (height + 1) / 2;
|
|
final int minCapacityY = strideY * height;
|
|
final int minCapacityU = strideU * chromaHeight;
|
|
final int minCapacityV = strideV * chromaHeight;
|
|
if (dataY.capacity() < minCapacityY) {
|
|
throw new IllegalArgumentException("Y-buffer must be at least " + minCapacityY + " bytes.");
|
|
}
|
|
if (dataU.capacity() < minCapacityU) {
|
|
throw new IllegalArgumentException("U-buffer must be at least " + minCapacityU + " bytes.");
|
|
}
|
|
if (dataV.capacity() < minCapacityV) {
|
|
throw new IllegalArgumentException("V-buffer must be at least " + minCapacityV + " bytes.");
|
|
}
|
|
|
|
return new JavaI420Buffer(
|
|
width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback);
|
|
}
|
|
|
|
/** Allocates an empty I420Buffer suitable for an image of the given dimensions. */
|
|
public static JavaI420Buffer allocate(int width, int height) {
|
|
int chromaHeight = (height + 1) / 2;
|
|
int strideUV = (width + 1) / 2;
|
|
int yPos = 0;
|
|
int uPos = yPos + width * height;
|
|
int vPos = uPos + strideUV * chromaHeight;
|
|
|
|
ByteBuffer buffer =
|
|
JniCommon.nativeAllocateByteBuffer(width * height + 2 * strideUV * chromaHeight);
|
|
|
|
buffer.position(yPos);
|
|
buffer.limit(uPos);
|
|
ByteBuffer dataY = buffer.slice();
|
|
|
|
buffer.position(uPos);
|
|
buffer.limit(vPos);
|
|
ByteBuffer dataU = buffer.slice();
|
|
|
|
buffer.position(vPos);
|
|
buffer.limit(vPos + strideUV * chromaHeight);
|
|
ByteBuffer dataV = buffer.slice();
|
|
|
|
return new JavaI420Buffer(width, height, dataY, width, dataU, strideUV, dataV, strideUV,
|
|
() -> { JniCommon.nativeFreeByteBuffer(buffer); });
|
|
}
|
|
|
|
@Override
|
|
public int getWidth() {
|
|
return width;
|
|
}
|
|
|
|
@Override
|
|
public int getHeight() {
|
|
return height;
|
|
}
|
|
|
|
@Override
|
|
public ByteBuffer getDataY() {
|
|
// Return a slice to prevent relative reads from changing the position.
|
|
return dataY.slice();
|
|
}
|
|
|
|
@Override
|
|
public ByteBuffer getDataU() {
|
|
// Return a slice to prevent relative reads from changing the position.
|
|
return dataU.slice();
|
|
}
|
|
|
|
@Override
|
|
public ByteBuffer getDataV() {
|
|
// Return a slice to prevent relative reads from changing the position.
|
|
return dataV.slice();
|
|
}
|
|
|
|
@Override
|
|
public int getStrideY() {
|
|
return strideY;
|
|
}
|
|
|
|
@Override
|
|
public int getStrideU() {
|
|
return strideU;
|
|
}
|
|
|
|
@Override
|
|
public int getStrideV() {
|
|
return strideV;
|
|
}
|
|
|
|
@Override
|
|
public I420Buffer toI420() {
|
|
retain();
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public void retain() {
|
|
synchronized (refCountLock) {
|
|
++refCount;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
synchronized (refCountLock) {
|
|
if (--refCount == 0 && releaseCallback != null) {
|
|
releaseCallback.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public VideoFrame.Buffer cropAndScale(
|
|
int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
|
|
return VideoFrame.cropAndScaleI420(
|
|
this, cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
|
|
}
|
|
}
|