From c87fd586256cbb2168541263f31b4e4140633cdf Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Thu, 15 Apr 2010 08:26:48 +0000 Subject: [PATCH] Creates less garbage in the QuickTime video CaptureDevice in order to resolve short freezes in the video. Comes at the expense of execution speed due to an additional copying of each frame. The performance penalty is to be addressed in a subsequent commit. --- lib/native/mac/libjquicktime.jnilib | Bin 89896 -> 98424 bytes ...or_impl_neomedia_quicktime_CVPixelBuffer.h | 26 +- ...or_impl_neomedia_quicktime_CVPixelBuffer.m | 110 +++++- .../neomedia/codec/video/AVFrameFormat.java | 24 +- .../impl/neomedia/device/QuickTimeAuto.java | 2 + .../protocol/quicktime/QuickTimeStream.java | 362 ++++++++++++++++-- .../neomedia/quicktime/CVPixelBuffer.java | 53 +++ 7 files changed, 538 insertions(+), 39 deletions(-) diff --git a/lib/native/mac/libjquicktime.jnilib b/lib/native/mac/libjquicktime.jnilib index 12ae5c0a5e1e686420715d0aa119cfe99c08a405..0b52c09fe28dbc894a60ae61864687125874fee5 100755 GIT binary patch literal 98424 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 P>Izbh!xyLO3ex`r6N0-2 literal 89896 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 bT6G@D4UYBo?d}%gln~Po9Y{_1Sd#w_Mr}p* diff --git a/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.h b/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.h index 89b7450f4..a1b447577 100644 --- a/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.h +++ b/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.h @@ -7,14 +7,30 @@ #ifdef __cplusplus extern "C" { #endif +/* + * Class: net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer + * Method: getByteCount + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getByteCount + (JNIEnv *, jclass, jlong); + /* * Class: net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer * Method: getBytes * Signature: (J)[B */ -JNIEXPORT jbyteArray JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes +JNIEXPORT jbyteArray JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes__J (JNIEnv *, jclass, jlong); +/* + * Class: net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer + * Method: getBytes + * Signature: (JJI)I + */ +JNIEXPORT jint JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes__JJI + (JNIEnv *, jclass, jlong, jlong, jint); + /* * Class: net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer * Method: getHeight @@ -31,6 +47,14 @@ JNIEXPORT jint JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CV JNIEXPORT jint JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getWidth (JNIEnv *, jclass, jlong); +/* + * Class: net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer + * Method: memcpy + * Signature: ([BIIJ)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_memcpy + (JNIEnv *, jclass, jbyteArray, jint, jint, jlong); + #ifdef __cplusplus } #endif diff --git a/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.m b/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.m index 609dc4143..65958f337 100644 --- a/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.m +++ b/src/native/macosx/quicktime/net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.m @@ -1,33 +1,60 @@ #include "net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer.h" #import +#include -JNIEXPORT jbyteArray JNICALL -Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes - (JNIEnv *jniEnv, jclass clazz, jlong ptr) +static size_t +CVPixelBuffer_getByteCount(CVPixelBufferRef pixelBuffer, size_t planeCount) { - CVPixelBufferRef pixelBuffer; - size_t planeCount; size_t byteCount; - jbyteArray bytes; - pixelBuffer = (CVPixelBufferRef) ptr; - - planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); if (planeCount) { size_t planeIndex; byteCount = 0; for (planeIndex = 0; planeIndex < planeCount; planeIndex++) + { byteCount += CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, planeIndex) * CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex); + } } else + { byteCount = CVPixelBufferGetBytesPerRow(pixelBuffer) * CVPixelBufferGetHeight(pixelBuffer); + } + return byteCount; +} + +JNIEXPORT jint JNICALL +Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getByteCount + (JNIEnv *jniEnv, jclass clazz, jlong ptr) +{ + CVPixelBufferRef pixelBuffer; + size_t planeCount; + + pixelBuffer = (CVPixelBufferRef) ptr; + + planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); + return (jint) CVPixelBuffer_getByteCount(pixelBuffer, planeCount); +} + +JNIEXPORT jbyteArray JNICALL +Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes__J + (JNIEnv *jniEnv, jclass clazz, jlong ptr) +{ + CVPixelBufferRef pixelBuffer; + size_t planeCount; + size_t byteCount; + jbyteArray bytes; + + pixelBuffer = (CVPixelBufferRef) ptr; + + planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); + byteCount = CVPixelBuffer_getByteCount(pixelBuffer, planeCount); bytes = (*jniEnv)->NewByteArray(jniEnv, byteCount); if (!bytes) return NULL; @@ -75,6 +102,59 @@ Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes return bytes; } +JNIEXPORT jint JNICALL +Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getBytes__JJI + (JNIEnv *jniEnv, jclass clazz, jlong ptr, jlong buf, jint bufLength) +{ + CVPixelBufferRef pixelBuffer; + size_t byteCount; + + pixelBuffer = (CVPixelBufferRef) ptr; + + if (kCVReturnSuccess == CVPixelBufferLockBaseAddress(pixelBuffer, 0)) + { + size_t planeCount; + jbyte *cBytes; + + planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); + byteCount = CVPixelBuffer_getByteCount(pixelBuffer, planeCount); + + if (planeCount) + { + size_t byteOffset; + size_t planeIndex; + + byteOffset = 0; + for (planeIndex = 0; planeIndex < planeCount; planeIndex++) + { + cBytes + = CVPixelBufferGetBaseAddressOfPlane( + pixelBuffer, + planeIndex); + byteCount + += CVPixelBufferGetBytesPerRowOfPlane( + pixelBuffer, + planeIndex) + * CVPixelBufferGetHeightOfPlane( + pixelBuffer, + planeIndex); + memcpy(buf, cBytes, byteCount); + byteOffset += byteCount; + } + byteCount = byteOffset; + } + else + { + cBytes = CVPixelBufferGetBaseAddress(pixelBuffer); + memcpy(buf, cBytes, byteCount); + } + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); + } + else + byteCount = 0; + return byteCount; +} + JNIEXPORT jint JNICALL Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getHeight (JNIEnv *jniEnv, jclass clazz, jlong ptr) @@ -88,3 +168,15 @@ Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_getWidth { return (jint) CVPixelBufferGetWidth((CVPixelBufferRef) ptr); } + +JNIEXPORT void JNICALL +Java_net_java_sip_communicator_impl_neomedia_quicktime_CVPixelBuffer_memcpy + (JNIEnv *jniEnv, jclass clazz, + jbyteArray dst, jint dstOffset, jint dstLength, + jlong src) +{ + (*jniEnv)->SetByteArrayRegion( + jniEnv, + dst, dstOffset, dstLength, + (jbyte *) src); +} diff --git a/src/net/java/sip/communicator/impl/neomedia/codec/video/AVFrameFormat.java b/src/net/java/sip/communicator/impl/neomedia/codec/video/AVFrameFormat.java index 54f76a34f..56fa83d17 100644 --- a/src/net/java/sip/communicator/impl/neomedia/codec/video/AVFrameFormat.java +++ b/src/net/java/sip/communicator/impl/neomedia/codec/video/AVFrameFormat.java @@ -19,12 +19,19 @@ * other VideoFormats and a very obvious one. * * @author Lubomir Marinov + * @author Sebastien Vincent */ public class AVFrameFormat extends VideoFormat { + /** - * Native FFMPEG format used. + * The encoding of the AVFrameFormat instances. + */ + public static final String AVFRAME = "AVFrame"; + + /** + * The native FFmpeg format represented by this instance. */ private int pixFmt; @@ -40,10 +47,13 @@ public AVFrameFormat() /** * Initializes a new AVFrameFormat instance with specific size and * frame rate. + * + * @param size the Dimension of the new instance + * @param frameRate the frame rate of the new instance */ public AVFrameFormat(Dimension size, float frameRate) { - super("AVFrame", size, NOT_SPECIFIED, AVFrame.class, frameRate); + super(AVFRAME, size, NOT_SPECIFIED, AVFrame.class, frameRate); this.pixFmt = FFmpeg.PIX_FMT_YUV420P; } @@ -65,9 +75,11 @@ public Object clone() } /** - * Copy specified Format. + * Copies the properties of the specified Format into this + * instance. * - * @param f Format to be copied + * @param f the Format the properties of which are to be copied + * into this instance */ @Override protected void copy(Format f) @@ -106,9 +118,9 @@ public boolean equals(Object obj) } /** - * Get the native FFMPEG format. + * Gets the native FFmpeg format represented by this instance. * - * @return native format + * @return the native FFmpeg format represented by this instance */ public int getPixFmt() { diff --git a/src/net/java/sip/communicator/impl/neomedia/device/QuickTimeAuto.java b/src/net/java/sip/communicator/impl/neomedia/device/QuickTimeAuto.java index 4d06d0b93..2757eb8ce 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/QuickTimeAuto.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/QuickTimeAuto.java @@ -9,6 +9,7 @@ import javax.media.*; import javax.media.format.*; +import net.java.sip.communicator.impl.neomedia.codec.video.*; import net.java.sip.communicator.impl.neomedia.quicktime.*; import net.java.sip.communicator.util.*; @@ -57,6 +58,7 @@ public QuickTimeAuto() + inputDevice.uniqueID()), new Format[] { + //new AVFrameFormat(), new RGBFormat(), //new YUVFormat(YUVFormat.YUV_420) }); diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java index 261ceb0ce..81cfa4dc0 100644 --- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java @@ -6,16 +6,19 @@ */ package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol.quicktime; -import java.awt.*; +import java.awt.Dimension; // disambiguation import java.io.*; +import java.util.*; import javax.media.*; import javax.media.control.*; import javax.media.format.*; import javax.media.protocol.*; +import net.java.sip.communicator.impl.neomedia.codec.video.*; import net.java.sip.communicator.impl.neomedia.jmfext.media.protocol.*; import net.java.sip.communicator.impl.neomedia.quicktime.*; +import net.java.sip.communicator.util.*; /** * Implements a PushBufferStream using QuickTime/QTKit. @@ -26,6 +29,13 @@ public class QuickTimeStream extends AbstractPushBufferStream { + /** + * The Logger used by the QuickTimeStream class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(QuickTimeStream.class); + /** * The indicator which determines whether {@link #captureOutput} * automatically drops late frames. If false, we have to drop them @@ -34,16 +44,36 @@ public class QuickTimeStream */ private final boolean automaticallyDropsLateVideoFrames; + /** + * The pool of ByteBuffers this instances is using to transfer the + * media data captured by {@link #captureOutput} out of this instance + * through the Buffers specified in its {@link #process(Buffer)}. + */ + private final List buffers = new ArrayList(); + /** * The QTCaptureOutput represented by this SourceStream. */ final QTCaptureDecompressedVideoOutput captureOutput = new QTCaptureDecompressedVideoOutput(); + /** + * The VideoFormat which has been successfully set on + * {@link #captureOutput}. + */ + private VideoFormat captureOutputFormat; + + /** + * The indicator which determines whether this QuickTimeStream has + * been closed. Introduced to determine when ByteBuffers are to be + * disposed of and no longer be pooled in {@link #buffers}. + */ + private boolean closed = false; + /** * The captured media data to be returned in {@link #read(Buffer)}. */ - private byte[] data; + private ByteBuffer data; /** * The Format of {@link #data} if known. If possible, determined by @@ -75,7 +105,7 @@ public class QuickTimeStream * QuickTimeStream to provide the latest available frame and not * wait for QuickTime/QTKit to capture a new one. */ - private byte[] nextData; + private ByteBuffer nextData; /** * The Format of {@link #nextData} if known. @@ -172,18 +202,45 @@ private void captureOutputDidOutputVideoFrameWithSampleBuffer( { if (!automaticallyDropsLateVideoFrames && (data != null)) { - nextData = pixelBuffer.getBytes(); - nextDataTimeStamp = System.nanoTime(); - if (nextDataFormat == null) - nextDataFormat = getVideoFrameFormat(pixelBuffer); + if (nextData != null) + { + returnFreeBuffer(nextData); + nextData = null; + } + + nextData = getFreeBuffer(pixelBuffer.getByteCount()); + if (nextData != null) + { + nextData.setLength( + pixelBuffer.getBytes( + nextData.ptr, + nextData.capacity)); + nextDataTimeStamp = System.nanoTime(); + if (nextDataFormat == null) + nextDataFormat = getVideoFrameFormat(pixelBuffer); + } return; } - data = pixelBuffer.getBytes(); - dataTimeStamp = System.nanoTime(); - if (dataFormat == null) - dataFormat = getVideoFrameFormat(pixelBuffer); - nextData = null; + if (data != null) + { + returnFreeBuffer(data); + data = null; + } + + data = getFreeBuffer(pixelBuffer.getByteCount()); + if (data != null) + { + data.setLength(pixelBuffer.getBytes(data.ptr, data.capacity)); + dataTimeStamp = System.nanoTime(); + if (dataFormat == null) + dataFormat = getVideoFrameFormat(pixelBuffer); + } + if (nextData != null) + { + returnFreeBuffer(nextData); + nextData = null; + } if (automaticallyDropsLateVideoFrames) transferData = (data != null); @@ -216,6 +273,32 @@ public void close() super.close(); captureOutput.setDelegate(null); + + synchronized (buffers) + { + closed = true; + + Iterator bufferIter = buffers.iterator(); + boolean loggerIsTraceEnabled = logger.isTraceEnabled(); + int leakedCount = 0; + + while (bufferIter.hasNext()) + { + ByteBuffer buffer = bufferIter.next(); + + if (buffer.isFree()) + { + bufferIter.remove(); + FFmpeg.av_free(buffer.ptr); + } else if (loggerIsTraceEnabled) + leakedCount++; + } + if (loggerIsTraceEnabled) + { + logger.trace( + "Leaking " + leakedCount + " ByteBuffer instances."); + } + } } /** @@ -250,7 +333,9 @@ protected Format doGetFormat() .intersects( new VideoFormat( null, - new Dimension(640, 480), + new Dimension( + DataSource.DEFAULT_WIDTH, + DataSource.DEFAULT_HEIGHT), Format.NOT_SPECIFIED, Format.byteArray, Format.NOT_SPECIFIED)); @@ -304,7 +389,19 @@ private Format getCaptureOutputFormat() 2, 3, 4); case CVPixelFormatType.kCVPixelFormatType_420YpCbCr8Planar: if ((width == 0) && (height == 0)) - return new YUVFormat(YUVFormat.YUV_420); + { + if (captureOutputFormat instanceof AVFrameFormat) + return new AVFrameFormat(); + else + return new YUVFormat(YUVFormat.YUV_420); + } + else if (captureOutputFormat instanceof AVFrameFormat) + { + return + new AVFrameFormat( + new Dimension(width, height), + Format.NOT_SPECIFIED); + } else { int strideY = width; @@ -328,6 +425,48 @@ private Format getCaptureOutputFormat() return null; } + /** + * Gets a ByteBuffer out of the pool of free ByteBuffers + * (i.e. ByteBuffers ready for writing captured media data into + * them) which is capable to receiving at least capacity number of + * bytes. + * + * @param capacity the minimal number of bytes that the returned + * ByteBuffer is to be capable of receiving + * @return a ByteBuffer which is ready for writing captured media + * data into and which is capable of receiving at least capacity + * number of bytes + */ + private ByteBuffer getFreeBuffer(int capacity) + { + synchronized (buffers) + { + if (closed) + return null; + + int bufferCount = buffers.size(); + ByteBuffer freeBuffer = null; + + for (int bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) + { + ByteBuffer buffer = buffers.get(bufferIndex); + + if (buffer.isFree() && (buffer.capacity >= capacity)) + { + freeBuffer = buffer; + break; + } + } + if (freeBuffer == null) + { + freeBuffer = new ByteBuffer(capacity); + buffers.add(freeBuffer); + } + freeBuffer.setFree(false); + return freeBuffer; + } + } + /** * Gets the Format of the media data made available by this * PushBufferStream as indicated by a specific @@ -378,15 +517,32 @@ public void read(Buffer buffer) buffer.setLength(0); else { - buffer.setData(data); - buffer - .setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); + Object bufferData = buffer.getData(); + byte[] bufferByteData = null; + int dataLength = data.getLength(); + + if (bufferData instanceof byte[]) + { + bufferByteData = (byte[]) bufferData; + if (bufferByteData.length < dataLength) + bufferByteData = null; + } + if (bufferByteData == null) + { + bufferByteData = new byte[dataLength]; + buffer.setData(bufferByteData); + } + CVPixelBuffer.memcpy(bufferByteData, 0, dataLength, data.ptr); + + buffer.setFlags( + Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); if (dataFormat != null) buffer.setFormat(dataFormat); - buffer.setLength(data.length); + buffer.setLength(dataLength); buffer.setOffset(0); buffer.setTimeStamp(dataTimeStamp); + returnFreeBuffer(data); data = null; if (!automaticallyDropsLateVideoFrames) @@ -395,6 +551,24 @@ public void read(Buffer buffer) } } + /** + * Returns a specific ByteBuffer into the pool of free + * ByteBuffers (i.e. ByteBuffers ready for writing + * captured media data into them). + * + * @param buffer the ByteBuffer to be returned into the pool of + * free ByteBuffers + */ + private void returnFreeBuffer(ByteBuffer buffer) + { + synchronized (buffers) + { + buffer.setFree(true); + if (closed && buffers.remove(buffer)) + FFmpeg.av_free(buffer.ptr); + } + } + /** * Calls {@link BufferTransferHandler#transferData(PushBufferStream)} from * inside {@link #transferDataThread} so that the call is not made in @@ -415,6 +589,12 @@ private void runInTransferDataThread() synchronized (dataSyncRoot) { + if (data != null) + { + returnFreeBuffer(data); + data = null; + } + data = nextData; dataTimeStamp = nextDataTimeStamp; if (dataFormat == null) @@ -457,7 +637,7 @@ private void runInTransferDataThread() } /** - * Set the Format of the media data made available by this + * Sets the Format of the media data made available by this * PushBufferStream to {@link #captureOutput}. * * @param format the Format of the media data made available by @@ -502,7 +682,25 @@ private void setCaptureOutputFormat(Format format) CVPixelBufferAttributeKey.kCVPixelBufferHeightKey); } - if (format.isSameEncoding(VideoFormat.RGB)) + String encoding; + + if (format instanceof AVFrameFormat) + { + int pixfmt = ((AVFrameFormat) format).getPixFmt(); + + if (pixfmt == FFmpeg.PIX_FMT_YUV420P) + encoding = VideoFormat.YUV; + else + encoding = null; + } + else if (format.isSameEncoding(VideoFormat.RGB)) + encoding = VideoFormat.RGB; + else if (format.isSameEncoding(VideoFormat.YUV)) + encoding = VideoFormat.YUV; + else + encoding = null; + + if (VideoFormat.RGB.equalsIgnoreCase(encoding)) { if (pixelBufferAttributes == null) pixelBufferAttributes = new NSMutableDictionary(); @@ -511,7 +709,7 @@ private void setCaptureOutputFormat(Format format) CVPixelFormatType.kCVPixelFormatType_32ARGB, CVPixelBufferAttributeKey.kCVPixelBufferPixelFormatTypeKey); } - else if (format.isSameEncoding(VideoFormat.YUV)) + else if (VideoFormat.YUV.equalsIgnoreCase(encoding)) { if (pixelBufferAttributes == null) pixelBufferAttributes = new NSMutableDictionary(); @@ -524,7 +722,10 @@ else if (format.isSameEncoding(VideoFormat.YUV)) throw new IllegalArgumentException("format"); if (pixelBufferAttributes != null) + { captureOutput.setPixelBufferAttributes(pixelBufferAttributes); + captureOutputFormat = videoFormat; + } } /** @@ -566,13 +767,128 @@ public void stop() synchronized (dataSyncRoot) { - data = null; + if (data != null) + { + returnFreeBuffer(data); + data = null; + } dataFormat = null; - nextData = null; + if (nextData != null) + { + returnFreeBuffer(nextData); + nextData = null; + } nextDataFormat = null; if (!automaticallyDropsLateVideoFrames) dataSyncRoot.notifyAll(); } } + + /** + * Represents a buffer of native memory with a specific size/capacity which + * either contains a specific number of bytes of valid data or is free for + * consumption. + */ + private static class ByteBuffer + { + + /** + * The maximum number of bytes which can be written into the native + * memory represented by this instance. + */ + public final int capacity; + + /** + * The indicator which determines whether this instance is free to be + * written bytes into. + */ + private boolean free; + + /** + * The number of bytes of valid data that the native memory represented + * by this instance contains. + */ + private int length; + + /** + * The pointer to the native memory represented by this instance. + */ + public final long ptr; + + /** + * Initializes a new ByteBuffer instance with a specific + * capacity. + * + * @param capacity the maximum number of bytes which can be written into + * the native memory represented by the new instance + */ + public ByteBuffer(int capacity) + { + this.capacity = capacity; + this.ptr = FFmpeg.av_malloc(this.capacity); + + this.free = true; + this.length = 0; + + if (this.ptr == 0) + { + throw + new OutOfMemoryError( + getClass().getSimpleName() + + " with capacity " + + this.capacity); + } + } + + /** + * Gets the number of bytes of valid data that the native memory + * represented by this instance contains. + * + * @return the number of bytes of valid data that the native memory + * represented by this instance contains + */ + public int getLength() + { + return length; + } + + /** + * Determines whether this instance is free to be written bytes into. + * + * @return true if this instance is free to be written bytes + * into or false is the native memory represented by this + * instance is already is use + */ + public boolean isFree() + { + return free; + } + + /** + * Sets the indicator which determines whether this instance is free to + * be written bytes into. + * + * @param free true if this instance is to be made available + * for writing bytes into; otherwise, false + */ + public void setFree(boolean free) + { + this.free = free; + if (this.free) + setLength(0); + } + + /** + * Sets the number of bytes of valid data that the native memory + * represented by this instance contains. + * + * @param length the number of bytes of valid data that the native + * memory represented by this instance contains + */ + public void setLength(int length) + { + this.length = length; + } + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/quicktime/CVPixelBuffer.java b/src/net/java/sip/communicator/impl/neomedia/quicktime/CVPixelBuffer.java index 3e5da5cc3..6833e0cbe 100644 --- a/src/net/java/sip/communicator/impl/neomedia/quicktime/CVPixelBuffer.java +++ b/src/net/java/sip/communicator/impl/neomedia/quicktime/CVPixelBuffer.java @@ -27,6 +27,29 @@ public CVPixelBuffer(long ptr) super(ptr); } + /** + * Gets the number of bytes which represent the pixels of the associated + * CoreVideo CVPixelBufferRef. + * + * @return the number of bytes which represent the pixels of the associated + * CoreVideo CVPixelBufferRef + */ + public int getByteCount() + { + return getByteCount(getPtr()); + } + + /** + * Gets the number of bytes which represent the pixels of a specific + * CoreVideo CVPixelBufferRef. + * + * @param ptr the CVPixelBufferRef to get the number of bytes which + * represent its pixels of + * @return the number of bytes which represent the pixels of the specified + * CoreVideo CVPixelBufferRef + */ + private static native int getByteCount(long ptr); + /** * Gets a byte array which represents the pixels of the associated * CoreVideo CVPixelBufferRef. @@ -49,6 +72,32 @@ public byte[] getBytes() */ private static native byte[] getBytes(long ptr); + /** + * Gets the bytes which represent the pixels of the associated + * CVPixelBufferRef into a specific native byte buffer with a + * specific capacity. + * + * @param buf the native byte buffer to return the bytes into + * @param bufLength the capacity in bytes of buf + * @return the number of bytes written into buf + */ + public int getBytes(long buf, int bufLength) + { + return getBytes(getPtr(), buf, bufLength); + } + + /** + * Gets the bytes which represent the pixels of a specific + * CVPixelBufferRef into a specific native byte buffer with a + * specific capacity. + * + * @param ptr the CVPixelBufferRef to get the bytes of + * @param buf the native byte buffer to return the bytes into + * @param bufLength the capacity in bytes of buf + * @return the number of bytes written into buf + */ + private static native int getBytes(long ptr, long buf, int bufLength); + /** * Gets the height in pixels of this CVPixelBuffer. * @@ -90,4 +139,8 @@ public int getWidth() * CVPixelBufferRef */ private static native int getWidth(long ptr); + + public static native void memcpy( + byte[] dst, int dstOffset, int dstLength, + long src); }