diff --git a/src/org/jitsi/impl/neomedia/codec/FFmpeg.java b/src/org/jitsi/impl/neomedia/codec/FFmpeg.java index 4ca46b99cc23925a132e3738dec2112dadb860f3..f1f01c7d8d9c8ab09d2666f96c84e302d5fbc85a 100644 --- a/src/org/jitsi/impl/neomedia/codec/FFmpeg.java +++ b/src/org/jitsi/impl/neomedia/codec/FFmpeg.java @@ -45,7 +45,7 @@ public class FFmpeg public static final int CODEC_FLAG_LOOP_FILTER = 0x00000800; /** - * Allow to pass incomplete frame to decoder. + * The flag which allows incomplete frames to be passed to a decoder. */ public static final int CODEC_FLAG2_CHUNKS = 0x00008000; @@ -69,11 +69,6 @@ public class FFmpeg */ public static final int CODEC_ID_H264 = 28; - /** - * VP8 codec ID - */ - public static final int CODEC_ID_VP8 = 142; - /** * MJPEG codec ID. */ @@ -84,6 +79,11 @@ public class FFmpeg */ public static final int CODEC_ID_MP3 = 0x15000 + 1; + /** + * VP8 codec ID + */ + public static final int CODEC_ID_VP8 = 142; + /** * Work around bugs in encoders which sometimes cannot be detected * automatically. @@ -246,9 +246,11 @@ public class FFmpeg public static native long avcodec_alloc_context3(long codec); /** - * Allocate a AVFrame. + * Allocates an <tt>AVFrame</tt> instance and sets its fields to default + * values. The result must be freed using {@link #avcodec_free_frame(long)}. * - * @return native pointer to AVFrame + * @return an <tt>AVFrame *</tt> value which points to an <tt>AVFrame</tt> + * instance filled with default values or <tt>0</tt> on failure */ public static native long avcodec_alloc_frame(); @@ -337,6 +339,25 @@ public static native int avcodec_encode_video(long ctx, byte[] buff, */ public static native long avcodec_find_encoder(int id); + /** + * Frees an <tt>AVFrame</tt> instance specified as an <tt>AVFrame *</tt> + * value and any dynamically allocated objects in it (e.g. + * <tt>extended_data</tt>). + * <p> + * <b>Warning</b>: The method/function does NOT free the data buffers + * themselves because it does not know how since they might have been + * allocated with a custom <tt>get_buffer()</tt>. + * </p> + * + * @param frame an <tt>AVFrame *</tt> value which points to the + * <tt>AVFrame</tt> instance to be freed + */ + public static void avcodec_free_frame(long frame) + { + // FIXME Invoke the native function avcodec_free_frame(AVFrame **). + av_free(frame); + } + /** * Initializes the specified <tt>AVCodecContext</tt> to use the specified * <tt>AVCodec</tt>. diff --git a/src/org/jitsi/impl/neomedia/codec/video/AVFrame.java b/src/org/jitsi/impl/neomedia/codec/video/AVFrame.java index f01a5cc13035546a02e52d36b4eb1136f3a45404..40cf3816176397f5b18a1b0a431c79650dd650ed 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/AVFrame.java +++ b/src/org/jitsi/impl/neomedia/codec/video/AVFrame.java @@ -6,22 +6,56 @@ */ package org.jitsi.impl.neomedia.codec.video; +import java.awt.*; + +import javax.media.*; + +import org.jitsi.impl.neomedia.codec.*; + /** * Represents a pointer to a native FFmpeg <tt>AVFrame</tt> object. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class AVFrame { + /** + * The <tt>ByteBuffer</tt> whose native memory is set on the native + * counterpart of this instance/<tt>AVFrame</tt>. + */ + private ByteBuffer data; + + /** + * The indicator which determines whether the native memory represented by + * this instance is to be freed upon finalization. + */ + private boolean free; + /** * The pointer to the native FFmpeg <tt>AVFrame</tt> object represented by * this instance. */ - private final long ptr; + private long ptr; + + /** + * Initializes a new <tt>FinalizableAVFrame</tt> instance which is to + * allocate a new native FFmpeg <tt>AVFrame</tt> and represent it. + */ + public AVFrame() + { + this.ptr = FFmpeg.avcodec_alloc_frame(); + if (this.ptr == 0) + throw new OutOfMemoryError("avcodec_alloc_frame()"); + + this.free = true; + } /** * Initializes a new <tt>AVFrame</tt> instance which is to represent a - * specific pointer to a native FFmpeg <tt>AVFrame</tt> object. + * specific pointer to a native FFmpeg <tt>AVFrame</tt> object. Because the + * native memory/<tt>AVFrame</tt> has been allocated outside the new + * instance, the new instance does not automatically free it upon + * finalization. * * @param ptr the pointer to the native FFmpeg <tt>AVFrame</tt> object to be * represented by the new instance @@ -32,6 +66,72 @@ public AVFrame(long ptr) throw new IllegalArgumentException("ptr"); this.ptr = ptr; + this.free = false; + } + + public synchronized int avpicture_fill( + ByteBuffer data, + AVFrameFormat format) + { + Dimension size = format.getSize(); + int ret + = FFmpeg.avpicture_fill( + ptr, + data.getPtr(), + format.getPixFmt(), + size.width, size.height); + + if (ret >= 0) + { + if (this.data != null) + this.data.free(); + + this.data = data; + } + return ret; + } + + /** + * Deallocates the native memory/FFmpeg <tt>AVFrame</tt> object represented + * by this instance if this instance has allocated it upon initialization + * and it has not been deallocated yet i.e. ensures that {@link #free()} is + * invoked on this instance. + * + * @see Object#finalize() + */ + @Override + protected void finalize() + throws Throwable + { + try + { + free(); + } + finally + { + super.finalize(); + } + } + + /** + * Deallocates the native memory/FFmpeg <tt>AVFrame</tt> object represented + * by this instance if this instance has allocated it upon initialization + * and it has not been deallocated yet. + */ + public synchronized void free() + { + if (free && (ptr != 0)) + { + FFmpeg.avcodec_free_frame(ptr); + free = false; + ptr = 0; + } + + if (data != null) + { + data.free(); + data = null; + } } /** @@ -41,8 +141,26 @@ public AVFrame(long ptr) * @return the pointer to the native FFmpeg <tt>AVFrame</tt> object * represented by this instance */ - public long getPtr() + public synchronized long getPtr() { return ptr; } + + public static int read(Buffer buffer, Format format, ByteBuffer data) + { + AVFrameFormat frameFormat = (AVFrameFormat) format; + + Object o = buffer.getData(); + AVFrame frame; + + if (o instanceof AVFrame) + frame = (AVFrame) o; + else + { + frame = new AVFrame(); + buffer.setData(frame); + } + + return frame.avpicture_fill(data, frameFormat); + } } diff --git a/src/org/jitsi/impl/neomedia/codec/video/ByteBuffer.java b/src/org/jitsi/impl/neomedia/codec/video/ByteBuffer.java index bbfb24631bea89a6df8bc55496f4d206f3d2ffb3..288b7eb986d728afe0df4f4825c7a9bcd150f58d 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/ByteBuffer.java +++ b/src/org/jitsi/impl/neomedia/codec/video/ByteBuffer.java @@ -9,9 +9,11 @@ import org.jitsi.impl.neomedia.codec.*; /** - * 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. + * Represents a buffer of native memory with a specific size/capacity which may + * contains a specific number of bytes of valid data. If the memory represented + * by a <tt>ByteBuffer</tt> instance has been allocated by the + * <tt>ByteBuffer</tt> instance itself, the native memory will automatically be + * freed upon finalization. * * @author Lyubomir Marinov */ @@ -19,16 +21,12 @@ public class ByteBuffer { /** - * The maximum number of bytes which can be written into the native memory - * represented by this instance. + * The maximum number of bytes which may be written into the native memory + * represented by this instance. If <tt>0</tt>, this instance has been + * initialized to provide read-only access to the native memory it + * represents and will not deallocate it upon finalization. */ - public final int capacity; - - /** - * The indicator which determines whether this instance is free to be - * written bytes into. - */ - private boolean free; + private int capacity; /** * The number of bytes of valid data that the native memory represented by @@ -39,69 +37,119 @@ public class ByteBuffer /** * The pointer to the native memory represented by this instance. */ - public final long ptr; + private long ptr; /** * Initializes a new <tt>ByteBuffer</tt> instance with a specific - * <tt>capacity</tt>. + * <tt>capacity</tt> of native memory. The new instance allocates the native + * memory and automatically frees it upon finalization. * * @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); + if (capacity < 1) + throw new IllegalArgumentException("capacity"); - this.free = true; + this.ptr = FFmpeg.av_malloc(capacity); + if (this.ptr == 0) + throw new OutOfMemoryError("av_malloc(" + this.capacity + ")"); + else + { + this.capacity = capacity; + this.length = 0; + } + } + + /** + * Initializes a new <tt>ByteBuffer</tt> instance which is to represent a + * specific block of native memory. Since the specified native memory has + * been allocated outside the new instance, the new instance will not + * automatically free it. + * + * @param ptr a pointer to the block of native memory to be represented by + * the new instance + */ + public ByteBuffer(long ptr) + { + this.ptr = ptr; + + this.capacity = 0; this.length = 0; + } - if (this.ptr == 0) + /** + * {@inheritDoc} + * + * Frees the native memory represented by this instance if the native memory + * has been allocated by this instance and has not been freed yet i.e. + * ensures that {@link #free()} is invoked on this instance. + * + * @see Object#finalize() + */ + @Override + protected void finalize() + throws Throwable + { + try + { + free(); + } + finally { - throw - new OutOfMemoryError( - getClass().getSimpleName() - + " with capacity " - + this.capacity); + super.finalize(); } } /** - * Gets the number of bytes of valid data that the native memory represented - * by this instance contains. + * Frees the native memory represented by this instance if the native memory + * has been allocated by this instance and has not been freed yet. + */ + public synchronized void free() + { + if ((capacity != 0) && (ptr != 0)) + { + FFmpeg.av_free(ptr); + capacity = 0; + ptr = 0; + } + } + + /** + * Gets the maximum number of bytes which may be written into the native + * memory represented by this instance. If <tt>0</tt>, this instance has + * been initialized to provide read-only access to the native memory it + * represents and will not deallocate it upon finalization. * - * @return the number of bytes of valid data that the native memory - * represented by this instance contains + * @return the maximum number of bytes which may be written into the native + * memory represented by this instance */ - public int getLength() + public synchronized int getCapacity() { - return length; + return capacity; } /** - * Determines whether this instance is free to be written bytes into. + * Gets the number of bytes of valid data that the native memory represented + * by this instance contains. * - * @return <tt>true</tt> if this instance is free to be written bytes into - * or <tt>false</tt> is the native memory represented by this instance is - * already is use + * @return the number of bytes of valid data that the native memory + * represented by this instance contains */ - public boolean isFree() + public int getLength() { - return free; + return length; } /** - * Sets the indicator which determines whether this instance is free to be - * written bytes into. + * Gets the pointer to the native memory represented by this instance. * - * @param free <tt>true</tt> if this instance is to be made available for - * writing bytes into; otherwise, <tt>false</tt> + * @return the pointer to the native memory represented by this instance */ - public void setFree(boolean free) + public synchronized long getPtr() { - this.free = free; - if (this.free) - setLength(0); + return ptr; } /** @@ -110,9 +158,13 @@ public void setFree(boolean free) * * @param length the number of bytes of valid data that the native memory * represented by this instance contains + * @throws IllegalArgumentException if <tt>length</tt> is a negative value */ public void setLength(int length) { + if (length < 0) + throw new IllegalArgumentException("length"); + this.length = length; } } diff --git a/src/org/jitsi/impl/neomedia/codec/video/FinalizableAVFrame.java b/src/org/jitsi/impl/neomedia/codec/video/FinalizableAVFrame.java deleted file mode 100644 index f29762687bff7ab35563c042e307985a8efcdbdb..0000000000000000000000000000000000000000 --- a/src/org/jitsi/impl/neomedia/codec/video/FinalizableAVFrame.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package org.jitsi.impl.neomedia.codec.video; - -import java.awt.*; - -import javax.media.*; - -import org.jitsi.impl.neomedia.codec.*; -import org.jitsi.impl.neomedia.jmfext.media.protocol.*; - -/** - * Represents an <tt>AVFrame</tt> used to provide captured media data in native - * format without representing the very frame data in the Java heap. Since the - * user may not know when the <tt>AVFrame</tt> instances are really safe for - * deallocation, <tt>FinalizableAVFrame</tt> relies on the Java finalization - * mechanism to reclaim the represented native memory. - * - * @author Lyubomir Marinov - */ -public class FinalizableAVFrame - extends AVFrame -{ - - /** - * The indicator which determines whether the native memory represented by - * this instance has already been freed/deallocated. - */ - private boolean freed = false; - - /** - * Initializes a new <tt>FinalizableAVFrame</tt> instance which is to - * allocate a new native FFmpeg <tt>AVFrame</tt> and represent it. - */ - public FinalizableAVFrame() - { - super(FFmpeg.avcodec_alloc_frame()); - } - - /** - * Deallocates the native memory represented by this instance. - * - * @see Object#finalize() - */ - @Override - protected void finalize() - throws Throwable - { - try - { - if (!freed) - { - long ptr = getPtr(); - long bufferPtr = FFmpeg.avpicture_get_data0(ptr); - - if (bufferPtr != 0) - freeData0(bufferPtr); - FFmpeg.av_free(ptr); - freed = true; - } - } - finally - { - super.finalize(); - } - } - - /** - * Frees the memory pointed to by the <tt>data0</tt> member of the native - * <tt>AVFrame</tt>. - * @param data0 pointer to free - */ - protected void freeData0(long data0) - { - FFmpeg.av_free(data0); - } - - /** - * Read the frame. - * - * @param buffer buffer - * @param format format of the buffer - * @param data the data - * @param byteBufferPool the <tt>ByteBuffer</tt> pool - */ - public static void read( - Buffer buffer, - Format format, - ByteBuffer data, - final ByteBufferPool byteBufferPool) - { - Object bufferData = buffer.getData(); - AVFrame frame; - long framePtr; - long bufferPtrToReturnFree; - - if (bufferData instanceof AVFrame) - { - frame = (AVFrame) bufferData; - framePtr = frame.getPtr(); - bufferPtrToReturnFree = FFmpeg.avpicture_get_data0(framePtr); - } - else - { - frame - = new FinalizableAVFrame() - { - @Override - protected void freeData0(long data0) - { - byteBufferPool.returnFreeBuffer(data0); - } - }; - buffer.setData(frame); - framePtr = frame.getPtr(); - bufferPtrToReturnFree = 0; - } - - AVFrameFormat frameFormat = (AVFrameFormat) format; - Dimension frameSize = frameFormat.getSize(); - - FFmpeg.avpicture_fill( - framePtr, - data.ptr, - frameFormat.getPixFmt(), - frameSize.width, frameSize.height); - - if (bufferPtrToReturnFree != 0) - byteBufferPool.returnFreeBuffer(bufferPtrToReturnFree); - } -} diff --git a/src/org/jitsi/impl/neomedia/codec/video/HFlip.java b/src/org/jitsi/impl/neomedia/codec/video/HFlip.java index 4f7a62d2d09370ad46bd5b2e1253665f1f97fe9a..eacc7ede58f3d0b1a1c372293bbf7080f762928f 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/HFlip.java +++ b/src/org/jitsi/impl/neomedia/codec/video/HFlip.java @@ -113,7 +113,7 @@ protected synchronized void doClose() { if (outputFrame != 0) { - FFmpeg.av_free(outputFrame); + FFmpeg.avcodec_free_frame(outputFrame); outputFrame = 0; } } diff --git a/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIDecoder.java b/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIDecoder.java index 179d76020b5e8c7b6eacf099d301a946ed3bad69..d53029af93e5ea0d14c83abb76c28ee983c8cd1c 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIDecoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIDecoder.java @@ -121,7 +121,7 @@ public synchronized void close() FFmpeg.av_free(avcontext); avcontext = 0; - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; } } diff --git a/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIEncoder.java b/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIEncoder.java index ef3a6b5079eebd51b881b2dcd6dff94ba0d93cef..a297105d4aa2e4a1c84d1c8731eb2cf87e8c4c23 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h263p/JNIEncoder.java @@ -114,7 +114,7 @@ public synchronized void close() FFmpeg.av_free(avcontext); avcontext = 0; - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; FFmpeg.av_free(rawFrameBuffer); rawFrameBuffer = 0; diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java index 8aaab8697216c42ccd1dee781e1b563c7842610f..8fb269f7ec81815617a5461c4c242c56f233ba59 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java @@ -132,7 +132,7 @@ public synchronized void close() FFmpeg.av_free(avctx); avctx = 0; - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; gotPictureAtLeastOnce = false; diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java index b35cd73245bf8ffa80c9105a5f262cc3cbcee9b0..629e3f32c6c6feb010b915985854594de2a72401 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java @@ -228,7 +228,7 @@ public synchronized void close() if (avframe != 0) { - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; } if (rawFrameBuffer != 0) diff --git a/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIDecoder.java b/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIDecoder.java index 0ac0749368e38b8ff1de723796dbe1b5198b3ff3..27285ef05a6e496cb41f53cf27ab9308e50609e2 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIDecoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIDecoder.java @@ -103,7 +103,7 @@ protected void doClose() if(avframe != 0) { - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; } } diff --git a/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIEncoder.java b/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIEncoder.java index fb983d9afcfaa4880e83c856f6740a9fcce829c6..c75f44275bddcf1003393be18da6e7778c210502 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/vp8/JNIEncoder.java @@ -120,7 +120,7 @@ protected void doClose() if(avframe != 0) { - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); avframe = 0; } diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/AbstractBufferStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/AbstractBufferStream.java index e4f92f23c3fadf5bd5a071c88eb3213cb1462786..5aa7dfe16910c9aed8396d0c0bddc7e139f9db0b 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/AbstractBufferStream.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/AbstractBufferStream.java @@ -75,6 +75,10 @@ protected AbstractBufferStream( * Releases the resources used by this instance throughout its existence and * makes it available for garbage collection. This instance is considered * unusable after closing. + * <p> + * <b>Warning</b>: The method is not invoked by the framework, extenders may + * choose to invoke it. + * </p> */ public void close() { diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/ByteBufferPool.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/ByteBufferPool.java index 3fc4daf67407a4e0018b031e59bdff56cf3f58c1..8dbb5eb4ce617c470633fc5f4aa23e422625762d 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/ByteBufferPool.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/ByteBufferPool.java @@ -6,11 +6,11 @@ */ package org.jitsi.impl.neomedia.jmfext.media.protocol; +import java.lang.ref.*; import java.util.*; import org.jitsi.impl.neomedia.codec.*; import org.jitsi.impl.neomedia.codec.video.*; -import org.jitsi.util.*; /** * Represents a pool of <tt>ByteBuffer</tt>s which reduces the allocations and @@ -21,67 +21,31 @@ */ public class ByteBufferPool { - - /** - * The <tt>Logger</tt> used by the <tt>ByteBufferPool</tt> class and its - * instances for logging output. - */ - private static final Logger logger = Logger.getLogger(ByteBufferPool.class); - /** * The <tt>ByteBuffer</tt>s which are managed by this * <tt>ByteBufferPool</tt>. */ - private final List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(); + private final List<PooledByteBuffer> buffers + = new ArrayList<PooledByteBuffer>(); /** - * The indicator which determines whether this <tt>ByteBufferPool</tt> has - * been closed. Introduced to determine when <tt>ByteBuffer</tt>s are to be - * disposed of and no longer be pooled. + * Drains this <tt>ByteBufferPool</tt> i.e. frees the <tt>ByteBuffer</tt>s + * that it contains. */ - private boolean closed = false; - - /** - * Closes this <tt>ByteBufferPool</tt> i.e. releases the resource allocated - * by this <tt>ByteBufferPool</tt> during its existence and prepares it to - * be garbage collected. - */ - public void close() + public synchronized void drain() { - synchronized (buffers) + for (Iterator<PooledByteBuffer> i = buffers.iterator(); i.hasNext();) { - closed = true; - - Iterator<ByteBuffer> bufferIter = buffers.iterator(); - boolean loggerIsTraceEnabled = logger.isTraceEnabled(); - int leakedCount = 0; + PooledByteBuffer buffer = i.next(); - while (bufferIter.hasNext()) - { - ByteBuffer buffer = bufferIter.next(); - - if (buffer.isFree()) - { - bufferIter.remove(); - FFmpeg.av_free(buffer.ptr); - } - else if (loggerIsTraceEnabled) - leakedCount++; - } - if (loggerIsTraceEnabled) - { - if (logger.isTraceEnabled()) - logger.trace( - "Leaking " + leakedCount + " ByteBuffer instances."); - } + i.remove(); + buffer.doFree(); } } /** - * Gets a <tt>ByteBuffer</tt> out of the pool of free <tt>ByteBuffer</tt>s - * (i.e. <tt>ByteBuffer</tt>s ready for writing captured media data into - * them) which is capable to receiving at least <tt>capacity</tt> number of - * bytes. + * Gets a <tt>ByteBuffer</tt> out of this pool of <tt>ByteBuffer</tt>s which + * is capable to receiving at least <tt>capacity</tt> number of bytes. * * @param capacity the minimal number of bytes that the returned * <tt>ByteBuffer</tt> is to be capable of receiving @@ -89,80 +53,93 @@ else if (loggerIsTraceEnabled) * data into and which is capable of receiving at least <tt>capacity</tt> * number of bytes */ - public ByteBuffer getFreeBuffer(int capacity) + public synchronized ByteBuffer getBuffer(int capacity) { - synchronized (buffers) - { - if (closed) - return null; + /* + * XXX Pad with FF_INPUT_BUFFER_PADDING_SIZE or hell will break loose. + */ + capacity += FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE; - int bufferCount = buffers.size(); - ByteBuffer freeBuffer = null; + ByteBuffer buffer = null; - /* - * XXX Pad with FF_INPUT_BUFFER_PADDING_SIZE or hell will break - * loose. - */ - capacity += FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE; + for (Iterator<PooledByteBuffer> i = buffers.iterator(); i.hasNext();) + { + ByteBuffer aBuffer = i.next(); - for (int bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) + if (aBuffer.getCapacity() >= capacity) { - ByteBuffer buffer = buffers.get(bufferIndex); - - if (buffer.isFree() && (buffer.capacity >= capacity)) - { - freeBuffer = buffer; - break; - } + i.remove(); + buffer = aBuffer; + break; } - if (freeBuffer == null) - { - freeBuffer = new ByteBuffer(capacity); - buffers.add(freeBuffer); - } - freeBuffer.setFree(false); - return freeBuffer; } + if (buffer == null) + buffer = new PooledByteBuffer(capacity, this); + return buffer; } /** - * Returns a specific <tt>ByteBuffer</tt> into the pool of free - * <tt>ByteBuffer</tt>s (i.e. <tt>ByteBuffer</tt>s ready for writing - * captured media data into them). + * Returns a specific <tt>ByteBuffer</tt> into this pool of + * <tt>ByteBuffer</tt>s. * - * @param buffer the <tt>ByteBuffer</tt> to be returned into the pool of - * free <tt>ByteBuffer</tt>s + * @param buffer the <tt>ByteBuffer</tt> to be returned into this pool of + * <tt>ByteBuffer</tt>s */ - public void returnFreeBuffer(ByteBuffer buffer) + private synchronized void returnBuffer(PooledByteBuffer buffer) { - synchronized (buffers) - { - buffer.setFree(true); - if (closed && buffers.remove(buffer)) - FFmpeg.av_free(buffer.ptr); - } + if (!buffers.contains(buffer)) + buffers.add(buffer); } /** - * Returns a specific <tt>ByteBuffer</tt> given by the pointer to the native - * memory that it represents into the pool of free <tt>ByteBuffer</tt>s - * (i.e. <tt>ByteBuffer</tt>s ready for writing captured media data into - * them). - * - * @param bufferPtr the pointer to the native memory represented by the - * <tt>ByteBuffer</tt> to be returned into the pool of free - * <tt>ByteBuffer</tt>s + * Implements a <tt>ByteBuffer</tt> which is pooled in a + * <tt>ByteBufferPool</tt> in order to reduce the numbers of allocations + * and deallocations of <tt>ByteBuffer</tt>s and their respective native + * memory. */ - public void returnFreeBuffer(long bufferPtr) + private static class PooledByteBuffer + extends ByteBuffer { - synchronized (buffers) + /** + * The <tt>ByteBufferPool</tt> in which this instance is pooled and in + * which it should returns upon {@link #free()}. + */ + private final WeakReference<ByteBufferPool> pool; + + public PooledByteBuffer(int capacity, ByteBufferPool pool) { - for (ByteBuffer buffer : buffers) - if (buffer.ptr == bufferPtr) - { - returnFreeBuffer(buffer); - break; - } + super(capacity); + + this.pool = new WeakReference<ByteBufferPool>(pool); + } + + /** + * Invokes {@link ByteBuffer#free()} i.e. does not make any attempt to + * return this instance to the associated <tt>ByteBufferPool</tt> and + * frees the native memory represented by this instance. + */ + void doFree() + { + super.free(); + } + + /** + * {@inheritDoc} + * + * Returns this <tt>ByteBuffer</tt> and, respectively, the native memory + * that it represents to the associated <tt>ByteBufferPool</tt>. If the + * <tt>ByteBufferPool</tt> has already been finalized by the garbage + * collector, frees the native memory represented by this instance. + */ + @Override + public void free() + { + ByteBufferPool pool = this.pool.get(); + + if (pool == null) + doFree(); + else + pool.returnBuffer(this); } } } diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/directshow/DirectShowStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/directshow/DirectShowStream.java index dadab0520446dd6007885255489d5ca297eedba1..bda25c3f20f5817f6e0023d4eaa122235cd8fc7f 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/directshow/DirectShowStream.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/directshow/DirectShowStream.java @@ -24,14 +24,23 @@ * @author Lyubomir Marinov * @author Sebastien Vincent */ -public class DirectShowStream extends AbstractPushBufferStream +public class DirectShowStream + extends AbstractPushBufferStream { + /** + * The indicator which determines whether {@link #grabber} + * automatically drops late frames. If <tt>false</tt>, we have to drop them + * ourselves because DirectShow will buffer them all and the video will + * be late. + */ + private boolean automaticallyDropsLateVideoFrames = false; + /** * The pool of <tt>ByteBuffer</tt>s this instances is using to transfer the * media data captured by {@link #grabber} out of this instance * through the <tt>Buffer</tt>s specified in its {@link #read(Buffer)}. */ - private final ByteBufferPool bufferPool = new ByteBufferPool(); + private final ByteBufferPool byteBufferPool = new ByteBufferPool(); /** * The captured media data to be returned in {@link #read(Buffer)}. @@ -55,13 +64,26 @@ public class DirectShowStream extends AbstractPushBufferStream */ private final Format format; + /** + * Delegate class to handle video data. + */ + final DSCaptureDevice.GrabberDelegate grabber + = new DSCaptureDevice.GrabberDelegate() + { + @Override + public void frameReceived(long ptr, int length) + { + processFrame(ptr, length); + } + }; + /** * The captured media data to become the value of {@link #data} as soon as * the latter becomes is consumed. Thus prepares this * <tt>DirectShowStream</tt> to provide the latest available frame and not * wait for DirectShow to capture a new one. */ - private ByteBuffer nextData = null; + private ByteBuffer nextData; /** * The time stamp in nanoseconds of {@link #nextData}. @@ -77,27 +99,6 @@ public class DirectShowStream extends AbstractPushBufferStream */ private Thread transferDataThread; - /** - * The indicator which determines whether {@link #grabber} - * automatically drops late frames. If <tt>false</tt>, we have to drop them - * ourselves because DirectShow will buffer them all and the video will - * be late. - */ - private boolean automaticallyDropsLateVideoFrames = false; - - /** - * Delegate class to handle video data. - */ - final DSCaptureDevice.GrabberDelegate grabber - = new DSCaptureDevice.GrabberDelegate() - { - @Override - public void frameReceived(long ptr, int length) - { - processFrame(ptr, length); - } - }; - /** * Initializes a new <tt>DirectShowStream</tt> instance which is to have its * <tt>Format</tt>-related information abstracted by a specific @@ -115,6 +116,22 @@ public void frameReceived(long ptr, int length) format = (VideoFormat) formatControl.getFormat(); } + /** + * Gets the <tt>Format</tt> of this <tt>PushBufferStream</tt> as directly + * known by it. + * + * @return the <tt>Format</tt> of this <tt>PushBufferStream</tt> as directly + * known by it or <tt>null</tt> if this <tt>PushBufferStream</tt> does not + * directly know its <tt>Format</tt> and it relies on the + * <tt>PushBufferDataSource</tt> which created it to report its + * <tt>Format</tt> + */ + @Override + protected Format doGetFormat() + { + return (this.format == null) ? super.doGetFormat() : this.format; + } + /** * Process received frames from DirectShow capture device * @@ -129,43 +146,43 @@ private void processFrame(long ptr, int length) { if(!automaticallyDropsLateVideoFrames && (data != null)) { - if(nextData != null) + if (nextData != null) { - bufferPool.returnFreeBuffer(nextData); + nextData.free(); nextData = null; } - - nextData - = bufferPool.getFreeBuffer(length); + nextData = byteBufferPool.getBuffer(length); if(nextData != null) { nextData.setLength( DSCaptureDevice.getBytes(ptr, - nextData.ptr, - nextData.capacity)); + nextData.getPtr(), + nextData.getCapacity())); nextDataTimeStamp = System.nanoTime(); } return; } - if(data != null) + if (data != null) { - bufferPool.returnFreeBuffer(data); + data.free(); data = null; } - - data = bufferPool.getFreeBuffer(length); + data = byteBufferPool.getBuffer(length); if(data != null) { - data.setLength(DSCaptureDevice.getBytes(ptr, - data.ptr, data.capacity)); + data.setLength( + DSCaptureDevice.getBytes( + ptr, + data.getPtr(), + data.getCapacity())); dataTimeStamp = System.nanoTime(); } - if(nextData != null) + if (nextData != null) { - bufferPool.returnFreeBuffer(nextData); + nextData.free(); nextData = null; } @@ -188,32 +205,80 @@ private void processFrame(long ptr, int length) } /** - * Gets the <tt>Format</tt> of this <tt>PushBufferStream</tt> as directly - * known by it. + * Reads media data from this <tt>PushBufferStream</tt> into a specific + * <tt>Buffer</tt> without blocking. * - * @return the <tt>Format</tt> of this <tt>PushBufferStream</tt> as directly - * known by it or <tt>null</tt> if this <tt>PushBufferStream</tt> does not - * directly know its <tt>Format</tt> and it relies on the - * <tt>PushBufferDataSource</tt> which created it to report its - * <tt>Format</tt> + * @param buffer the <tt>Buffer</tt> in which media data is to be read from + * this <tt>PushBufferStream</tt> + * @throws IOException if anything goes wrong while reading media data from + * this <tt>PushBufferStream</tt> into the specified <tt>buffer</tt> */ - @Override - protected Format doGetFormat() + public void read(Buffer buffer) throws IOException { - return (this.format == null) ? super.doGetFormat() : this.format; - } + synchronized (dataSyncRoot) + { + if(data == null) + { + buffer.setLength(0); + return; + } - /** - * Releases the resources used by this instance throughout its existence and - * makes it available for garbage collection. This instance is considered - * unusable after closing. - * - * @see AbstractPushBufferStream#close() - */ - @Override - public void close() - { - bufferPool.close(); + Format bufferFormat = buffer.getFormat(); + + if(bufferFormat == null) + { + bufferFormat = getFormat(); + if(bufferFormat != null) + buffer.setFormat(bufferFormat); + } + if(bufferFormat instanceof AVFrameFormat) + { + if (AVFrame.read(buffer, bufferFormat, data) < 0) + data.free(); + /* + * XXX For the sake of safety, make sure that this instance does + * not reference the data instance as soon as it is set on the + * AVFrame. + */ + data = null; + } + else + { + Object o = buffer.getData(); + byte[] bytes; + int length = data.getLength(); + + if(o instanceof byte[]) + { + bytes = (byte[]) o; + if(bytes.length < length) + bytes = null; + } + else + bytes = null; + if(bytes == null) + { + bytes = new byte[length]; + buffer.setData(bytes); + } + + /* + * TODO Copy the media from the native memory into the Java + * heap. + */ + data.free(); + data = null; + + buffer.setLength(length); + buffer.setOffset(0); + } + + buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); + buffer.setTimeStamp(dataTimeStamp); + + if(!automaticallyDropsLateVideoFrames) + dataSyncRoot.notifyAll(); + } } /** @@ -289,12 +354,8 @@ private void runInTransferDataThread() synchronized (dataSyncRoot) { - if(data != null) - { - bufferPool.returnFreeBuffer(data); - data = null; - } - + if (data != null) + data.free(); data = nextData; dataTimeStamp = nextDataTimeStamp; nextData = null; @@ -339,8 +400,11 @@ private void runInTransferDataThread() * media data from this <tt>PushBufferStream</tt> */ @Override - public void start() throws IOException + public void start() + throws IOException { + super.start(); + if(!automaticallyDropsLateVideoFrames) { transferDataThread @@ -363,101 +427,35 @@ public void run() * media data from this <tt>PushBufferStream</tt> */ @Override - public void stop() throws IOException - { - transferDataThread = null; - - synchronized (dataSyncRoot) - { - if(data != null) - { - bufferPool.returnFreeBuffer(data); - data = null; - } - - if(nextData != null) - { - bufferPool.returnFreeBuffer(nextData); - nextData = null; - } - - if(!automaticallyDropsLateVideoFrames) - dataSyncRoot.notifyAll(); - } - } - - /** - * Reads media data from this <tt>PushBufferStream</tt> into a specific - * <tt>Buffer</tt> without blocking. - * - * @param buffer the <tt>Buffer</tt> in which media data is to be read from - * this <tt>PushBufferStream</tt> - * @throws IOException if anything goes wrong while reading media data from - * this <tt>PushBufferStream</tt> into the specified <tt>buffer</tt> - */ - public void read(Buffer buffer) throws IOException + public void stop() + throws IOException { - synchronized (dataSyncRoot) + try { - if(data == null) - { - buffer.setLength(0); - return; - } + transferDataThread = null; - Format bufferFormat = buffer.getFormat(); - - if(bufferFormat == null) - { - bufferFormat = getFormat(); - if(bufferFormat != null) - buffer.setFormat(bufferFormat); - } - if(bufferFormat instanceof AVFrameFormat) - { - FinalizableAVFrame.read( - buffer, - bufferFormat, - data, - bufferPool); - } - else + synchronized (dataSyncRoot) { - Object bufferData = buffer.getData(); - byte[] bufferByteData; - int dataLength = data.getLength(); - - if(bufferData instanceof byte[]) + if (data != null) { - bufferByteData = (byte[]) bufferData; - if(bufferByteData.length < dataLength) - bufferByteData = null; + data.free(); + data = null; } - else - bufferByteData = null; - if(bufferByteData == null) + if (nextData != null) { - bufferByteData = new byte[dataLength]; - buffer.setData(bufferByteData); + nextData.free(); + nextData = null; } - /* XXX */ - //DSCaptureDevice.getBytes(bufferByteData, 0, dataLength, - // data.ptr); - //CVPixelBuffer.memcpy(bufferByteData, 0, dataLength, data.ptr); - - buffer.setLength(dataLength); - buffer.setOffset(0); - - bufferPool.returnFreeBuffer(data); + if(!automaticallyDropsLateVideoFrames) + dataSyncRoot.notifyAll(); } + } + finally + { + super.stop(); - buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); - buffer.setTimeStamp(dataTimeStamp); - - data = null; - if(!automaticallyDropsLateVideoFrames) - dataSyncRoot.notifyAll(); + byteBufferPool.drain(); } } } diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java index 0f3ac5eaab9a9c8cc43c74a4dd61ab1440a1701c..c63d07028903176d285cf71f207183b1f1549354 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java @@ -37,9 +37,10 @@ public class ImageStream private static final Logger logger = Logger.getLogger(ImageStream.class); /** - * Sequence number. + * The pool of <tt>ByteBuffer</tt>s this instances is using to optimize the + * allocations and deallocations of <tt>ByteBuffer</tt>s. */ - private long seqNo = 0; + private final ByteBufferPool byteBufferPool = new ByteBufferPool(); /** * Desktop interaction (screen capture, key press, ...). @@ -47,19 +48,14 @@ public class ImageStream private DesktopInteract desktopInteract = null; /** - * Native buffer pointer. - */ - private ByteBuffer data = null; - - /** - * If stream has been reinitialized. + * Index of display that we will capture from. */ - private boolean reinit = false; + private int displayIndex = -1; /** - * Index of display that we will capture from. + * Sequence number. */ - private int displayIndex = -1; + private long seqNo = 0; /** * X origin. @@ -104,79 +100,60 @@ protected void doRead(Buffer buffer) * not its responsibility, the DataSource of this ImageStream knows the * output Format. */ - Format bufferFormat = buffer.getFormat(); + Format format = buffer.getFormat(); - if (bufferFormat == null) + if (format == null) { - bufferFormat = getFormat(); - if (bufferFormat != null) - buffer.setFormat(bufferFormat); + format = getFormat(); + if (format != null) + buffer.setFormat(format); } - if(bufferFormat instanceof AVFrameFormat) + if(format instanceof AVFrameFormat) { /* * Native transfer: we keep data in native memory rather than the * Java heap until we reach SwScaler. */ - Object dataAv = buffer.getData(); - AVFrame bufferFrame = null; - long bufferFramePtr = 0; + Object o = buffer.getData(); + AVFrame frame; - if (dataAv instanceof AVFrame) - { - bufferFrame = (AVFrame)dataAv; - bufferFramePtr = bufferFrame.getPtr(); - } + if (o instanceof AVFrame) + frame = (AVFrame) o; else { - bufferFrame = new FinalizableAVFrame(); - bufferFramePtr = bufferFrame.getPtr(); + frame = new AVFrame(); + buffer.setData(frame); } - AVFrameFormat bufferFrameFormat = (AVFrameFormat) bufferFormat; - Dimension bufferFrameSize = bufferFrameFormat.getSize(); + AVFrameFormat avFrameFormat = (AVFrameFormat) format; + Dimension size = avFrameFormat.getSize(); + ByteBuffer data = readScreenNative(size); - if(readScreenNative(bufferFrameSize)) + if(data != null) { - FFmpeg.avpicture_fill( - bufferFramePtr, - data.ptr, - bufferFrameFormat.getPixFmt(), - bufferFrameSize.width, bufferFrameSize.height); - buffer.setData(bufferFrame); + if (frame.avpicture_fill(data, avFrameFormat) < 0) + data.free(); } else { - /* this can happen when we disconnect a monitor from computer - * before or during grabbing + /* + * This can happen when we disconnect a monitor from computer + * before or during grabbing. */ - throw new IOException("Failed to grab screen"); + throw new IOException("Failed to grab screen."); } } else { - byte dataByte[] = (byte[])buffer.getData(); - int dataLength = (dataByte != null) ? dataByte.length : 0; + byte[] bytes = (byte[]) buffer.getData(); + Dimension size = ((VideoFormat) format).getSize(); - if((dataByte != null) || (dataLength != 0)) - { - Dimension bufferFrameSize = - ((VideoFormat)bufferFormat).getSize(); - byte buf[] = readScreen(dataByte, bufferFrameSize); - - if(buf != dataByte) - { - /* readScreen returns us a different buffer than JMF ones, - * it means that JMF's initial buffer was too short. - */ - //System.out.println("use our own buffer"); - buffer.setData(buf); - } - - buffer.setOffset(0); - buffer.setLength(buf.length); - } + bytes = readScreen(bytes, size); + + buffer.setData(bytes); + buffer.setOffset(0); + buffer.setLength(bytes.length); } buffer.setHeader(null); @@ -186,6 +163,104 @@ protected void doRead(Buffer buffer) seqNo++; } + /** + * Read screen. + * + * @param output output buffer for screen bytes + * @param dim dimension of the screen + * @return raw bytes, it could be equal to output or not. Take care in the + * caller to check if output is the returned value. + */ + public byte[] readScreen(byte[] output, Dimension dim) + { + VideoFormat format = (VideoFormat) getFormat(); + Dimension formatSize = format.getSize(); + int width = (int) formatSize.getWidth(); + int height = (int) formatSize.getHeight(); + BufferedImage scaledScreen = null; + BufferedImage screen = null; + byte data[] = null; + int size = width * height * 4; + + // If output is not large enough, enlarge it. + if ((output == null) || (output.length < size)) + output = new byte[size]; + + /* get desktop screen via native grabber if available */ + if(desktopInteract.captureScreen( + displayIndex, + x, y, dim.width, dim.height, + output)) + { + return output; + } + + System.out.println("failed to grab with native! " + output.length); + + /* OK native grabber failed or is not available, + * try with AWT Robot and convert it to the right format + * + * Note that it is very memory consuming since memory are allocated + * to capture screen (via Robot) and then for converting to raw bytes + * Moreover support for multiple display has not yet been investigated + * + * Normally not of our supported platform (Windows (x86, x64), + * Linux (x86, x86-64), Mac OS X (i386, x86-64, ppc) and + * FreeBSD (x86, x86-64) should go here. + */ + screen = desktopInteract.captureScreen(); + + if(screen != null) + { + /* convert to ARGB BufferedImage */ + scaledScreen + = ImgStreamingUtils.getScaledImage( + screen, + width, height, + BufferedImage.TYPE_INT_ARGB); + /* get raw bytes */ + data = ImgStreamingUtils.getImageBytes(scaledScreen, output); + } + + screen = null; + scaledScreen = null; + return data; + } + + /** + * Read screen and store result in native buffer. + * + * @param dim dimension of the video + * @return true if success, false otherwise + */ + private ByteBuffer readScreenNative(Dimension dim) + { + int size = dim.width * dim.height * 4; + + /* pad the buffer */ + size += FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE; + + /* allocate native array */ + ByteBuffer data = byteBufferPool.getBuffer(size); + + data.setLength(size); + + /* get desktop screen via native grabber */ + if (desktopInteract.captureScreen( + displayIndex, + x, y, dim.width, dim.height, + data.getPtr(), + data.getLength())) + { + return data; + } + else + { + data.free(); + return null; + } + } + /** * Sets the index of the display to be used by this <tt>ImageStream</tt>. * @@ -216,7 +291,10 @@ public void setOrigin(int x, int y) */ @Override public void start() + throws IOException { + super.start(); + if(desktopInteract == null) { try @@ -228,8 +306,6 @@ public void start() logger.warn("Cannot create DesktopInteract object!"); } } - - reinit = true; } /** @@ -239,109 +315,18 @@ public void start() */ @Override public void stop() + throws IOException { - if (logger.isInfoEnabled()) - logger.info("Stop stream"); - } - - /** - * Read screen and store result in native buffer. - * - * @param dim dimension of the video - * @return true if success, false otherwise - */ - private boolean readScreenNative(Dimension dim) - { - int size = dim.width * dim.height * 4; - - /* pad the buffer */ - size += FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE; - - /* allocate native array */ - if(data == null || reinit) - { - data = new ByteBuffer(size); - data.setLength(size); - reinit = false; - } - else if(data.capacity < size) - { - /* reallocate native array if capacity is not enough */ - data.setFree(true); - FFmpeg.av_free(data.ptr); - data = new ByteBuffer(size); - data.setLength(size); - } - - /* get desktop screen via native grabber */ - return desktopInteract.captureScreen(displayIndex, x, y, dim.width, - dim.height, data.ptr, data.getLength()); - } - - /** - * Read screen. - * - * @param output output buffer for screen bytes - * @param dim dimension of the screen - * @return raw bytes, it could be equal to output or not. Take care in the - * caller to check if output is the returned value. - */ - public byte[] readScreen(byte output[], Dimension dim) - { - VideoFormat format = (VideoFormat) getFormat(); - Dimension formatSize = format.getSize(); - int width = (int) formatSize.getWidth(); - int height = (int) formatSize.getHeight(); - BufferedImage scaledScreen = null; - BufferedImage screen = null; - byte data[] = null; - int size = width * height * 4; - - /* check if output buffer can hold all the screen - * if not allocate our own buffer - */ - if(output.length < size) + try { - output = null; - output = new byte[size]; + if (logger.isInfoEnabled()) + logger.info("Stop stream"); } - - /* get desktop screen via native grabber if available */ - if(desktopInteract.captureScreen(displayIndex, x, y, dim.width, - dim.height, output)) + finally { - return output; - } - - System.out.println("failed to grab with native! " + output.length); - - /* OK native grabber failed or is not available, - * try with AWT Robot and convert it to the right format - * - * Note that it is very memory consuming since memory are allocated - * to capture screen (via Robot) and then for converting to raw bytes - * Moreover support for multiple display has not yet been investigated - * - * Normally not of our supported platform (Windows (x86, x64), - * Linux (x86, x86-64), Mac OS X (i386, x86-64, ppc) and - * FreeBSD (x86, x86-64) should go here. - */ - screen = desktopInteract.captureScreen(); + super.stop(); - if(screen != null) - { - /* convert to ARGB BufferedImage */ - scaledScreen - = ImgStreamingUtils.getScaledImage( - screen, - width, height, - BufferedImage.TYPE_INT_ARGB); - /* get raw bytes */ - data = ImgStreamingUtils.getImageBytes(scaledScreen, output); + byteBufferPool.drain(); } - - screen = null; - scaledScreen = null; - return data; } } diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java index 86463e0c6cc7af4481f03a1391ea5ebccd830137..2c4cc99b6143fb27b49b433b68d7dca4535469bf 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/quicktime/QuickTimeStream.java @@ -203,18 +203,16 @@ private void captureOutputDidOutputVideoFrameWithSampleBuffer( { if (nextData != null) { - byteBufferPool.returnFreeBuffer(nextData); + nextData.free(); nextData = null; } - - nextData - = byteBufferPool.getFreeBuffer(pixelBuffer.getByteCount()); + nextData = byteBufferPool.getBuffer(pixelBuffer.getByteCount()); if (nextData != null) { nextData.setLength( pixelBuffer.getBytes( - nextData.ptr, - nextData.capacity)); + nextData.getPtr(), + nextData.getCapacity())); nextDataTimeStamp = System.nanoTime(); if (nextDataFormat == null) nextDataFormat = videoFrameFormat; @@ -224,21 +222,24 @@ private void captureOutputDidOutputVideoFrameWithSampleBuffer( if (data != null) { - byteBufferPool.returnFreeBuffer(data); + data.free(); data = null; } - - data = byteBufferPool.getFreeBuffer(pixelBuffer.getByteCount()); + data = byteBufferPool.getBuffer(pixelBuffer.getByteCount()); if (data != null) { - data.setLength(pixelBuffer.getBytes(data.ptr, data.capacity)); + data.setLength( + pixelBuffer.getBytes( + data.getPtr(), + data.getCapacity())); dataTimeStamp = System.nanoTime(); if (dataFormat == null) dataFormat = videoFrameFormat; } + if (nextData != null) { - byteBufferPool.returnFreeBuffer(nextData); + nextData.free(); nextData = null; } @@ -273,8 +274,7 @@ public void close() super.close(); captureOutput.setDelegate(null); - - byteBufferPool.close(); + byteBufferPool.drain(); } /** @@ -504,43 +504,46 @@ public void read(Buffer buffer) } if (bufferFormat instanceof AVFrameFormat) { - FinalizableAVFrame.read( - buffer, - bufferFormat, - data, - byteBufferPool); + if (AVFrame.read(buffer, bufferFormat, data) < 0) + data.free(); + /* + * XXX For the sake of safety, make sure that this instance does + * not reference the data instance as soon as it is set on the + * AVFrame. + */ + data = null; } else { - Object bufferData = buffer.getData(); - byte[] bufferByteData; - int dataLength = data.getLength(); + Object o = buffer.getData(); + byte[] bytes; + int length = data.getLength(); - if (bufferData instanceof byte[]) + if (o instanceof byte[]) { - bufferByteData = (byte[]) bufferData; - if (bufferByteData.length < dataLength) - bufferByteData = null; + bytes = (byte[]) o; + if (bytes.length < length) + bytes = null; } else - bufferByteData = null; - if (bufferByteData == null) + bytes = null; + if (bytes == null) { - bufferByteData = new byte[dataLength]; - buffer.setData(bufferByteData); + bytes = new byte[length]; + buffer.setData(bytes); } - CVPixelBuffer.memcpy(bufferByteData, 0, dataLength, data.ptr); - buffer.setLength(dataLength); - buffer.setOffset(0); + CVPixelBuffer.memcpy(bytes, 0, length, data.getPtr()); + data.free(); + data = null; - byteBufferPool.returnFreeBuffer(data); + buffer.setLength(length); + buffer.setOffset(0); } buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); buffer.setTimeStamp(dataTimeStamp); - data = null; if (!automaticallyDropsLateVideoFrames) dataSyncRoot.notifyAll(); } @@ -567,11 +570,7 @@ private void runInTransferDataThread() synchronized (dataSyncRoot) { if (data != null) - { - byteBufferPool.returnFreeBuffer(data); - data = null; - } - + data.free(); data = nextData; dataTimeStamp = nextDataTimeStamp; if (dataFormat == null) @@ -737,6 +736,8 @@ public float setFrameRate(float frameRate) public void start() throws IOException { + super.start(); + if (!automaticallyDropsLateVideoFrames) { transferDataThread @@ -762,25 +763,34 @@ public void run() public void stop() throws IOException { - transferDataThread = null; - - synchronized (dataSyncRoot) + try { - if (data != null) - { - byteBufferPool.returnFreeBuffer(data); - data = null; - } - dataFormat = null; - if (nextData != null) + transferDataThread = null; + + synchronized (dataSyncRoot) { - byteBufferPool.returnFreeBuffer(nextData); - nextData = null; + if (data != null) + { + data.free(); + data = null; + } + dataFormat = null; + if (nextData != null) + { + nextData.free(); + nextData = null; + } + nextDataFormat = null; + + if (!automaticallyDropsLateVideoFrames) + dataSyncRoot.notifyAll(); } - nextDataFormat = null; + } + finally + { + super.stop(); - if (!automaticallyDropsLateVideoFrames) - dataSyncRoot.notifyAll(); + byteBufferPool.drain(); } } } diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/video4linux2/Video4Linux2Stream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/video4linux2/Video4Linux2Stream.java index 6ce39e47e15e13960d0031607eeffee5e34854fa..ced141bccdc242bce405ee91b23a7dc981366f27 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/video4linux2/Video4Linux2Stream.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/video4linux2/Video4Linux2Stream.java @@ -27,6 +27,17 @@ public class Video4Linux2Stream extends AbstractVideoPullBufferStream { + /** + * The <tt>AVCodecContext</tt> of the MJPEG decoder. + */ + private long avctx = 0; + + /** + * The <tt>AVFrame</tt> which represents the media data decoded by the MJPEG + * decoder/{@link #avctx}. + */ + private long avframe = 0; + /** * The pool of <tt>ByteBuffer</tt>s this instances is using to transfer the * media data captured by the Video for Linux Two API Specification device @@ -69,6 +80,11 @@ public class Video4Linux2Stream */ private long[] mmaps; + /** + * Native Video for Linux Two pixel format. + */ + private int nativePixelFormat = 0; + /** * The number of buffers through which the Video for Linux Two API * Specification device provides the captured media data to this instance @@ -82,32 +98,17 @@ public class Video4Linux2Stream */ private int requestbuffersMemory = 0; - /** - * The <tt>v4l2_buffer</tt> instance via which captured media data is - * fetched from the Video for Linux Two API Specification device to this - * instance in {@link #read(Buffer)}. - */ - private long v4l2_buffer; - - /** - * Native Video for Linux Two pixel format. - */ - private int nativePixelFormat = 0; - /** * Tell device to start capture in read() method. */ private boolean startInRead = false; /** - * AVCodecContext for MJPEG conversion. - */ - private long mjpeg_context = 0; - - /** - * AVFrame that is used in case of JPEG/MJPEG conversion. + * The <tt>v4l2_buffer</tt> instance via which captured media data is + * fetched from the Video for Linux Two API Specification device to this + * instance in {@link #read(Buffer)}. */ - private long avframe = 0; + private long v4l2_buffer; /** * Initializes a new <tt>Video4Linux2Stream</tt> instance which is to have @@ -152,6 +153,7 @@ public void close() Video4Linux2.free(v4l2_buffer); v4l2_buffer = 0; } + byteBufferPool.drain(); } /** @@ -244,25 +246,24 @@ protected void doRead(Buffer buffer) try { - ByteBuffer data = null; int index = Video4Linux2.v4l2_buffer_getIndex(v4l2_buffer); long mmap = mmaps[index]; int bytesused = Video4Linux2.v4l2_buffer_getBytesused(v4l2_buffer); - if(nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_MJPEG || - nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_JPEG) + if((nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_JPEG) + || (nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_MJPEG)) { - /* initialize FFmpeg's MJPEG decoder if not already done */ - if(mjpeg_context == 0) + /* Initialize the FFmpeg MJPEG decoder if necessary. */ + if(avctx == 0) { long avcodec = FFmpeg.avcodec_find_decoder(FFmpeg.CODEC_ID_MJPEG); - mjpeg_context = FFmpeg.avcodec_alloc_context3(avcodec); - FFmpeg.avcodeccontext_set_workaround_bugs(mjpeg_context, - FFmpeg.FF_BUG_AUTODETECT); + avctx = FFmpeg.avcodec_alloc_context3(avcodec); + FFmpeg.avcodeccontext_set_workaround_bugs(avctx, + FFmpeg.FF_BUG_AUTODETECT); - if (FFmpeg.avcodec_open2(mjpeg_context, avcodec) < 0) + if (FFmpeg.avcodec_open2(avctx, avcodec) < 0) { throw new RuntimeException("" + "Could not open codec CODEC_ID_MJPEG"); @@ -271,13 +272,13 @@ protected void doRead(Buffer buffer) avframe = FFmpeg.avcodec_alloc_frame(); } - if(FFmpeg.avcodec_decode_video(mjpeg_context, avframe, mmap, - bytesused) != -1) + if(FFmpeg.avcodec_decode_video(avctx, avframe, mmap, bytesused) + != -1) { Object out = buffer.getData(); - if (!(out instanceof AVFrame) || - (((AVFrame) out).getPtr() != avframe)) + if (!(out instanceof AVFrame) + || (((AVFrame) out).getPtr() != avframe)) { buffer.setData(new AVFrame(avframe)); } @@ -285,21 +286,24 @@ protected void doRead(Buffer buffer) } else { - data = byteBufferPool.getFreeBuffer(bytesused); + ByteBuffer data = byteBufferPool.getBuffer(bytesused); if (data != null) { - Video4Linux2.memcpy(data.ptr, mmap, bytesused); + Video4Linux2.memcpy(data.getPtr(), mmap, bytesused); + data.setLength(bytesused); + if (AVFrame.read(buffer, format, data) < 0) + data.free(); } - data.setLength(bytesused); - FinalizableAVFrame.read(buffer, format, data, byteBufferPool); } } finally { if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_QBUF, v4l2_buffer) == -1) + { throw new IOException("ioctl: request= VIDIOC_QBUF"); + } } buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); @@ -834,19 +838,20 @@ public void stop() { super.stop(); - if(mjpeg_context > 0) + if(avctx != 0) { - FFmpeg.avcodec_close(mjpeg_context); - FFmpeg.av_free(mjpeg_context); + FFmpeg.avcodec_close(avctx); + FFmpeg.av_free(avctx); + avctx = 0; } - mjpeg_context = 0; - if(avframe > 0) + if(avframe != 0) { - FFmpeg.av_free(avframe); + FFmpeg.avcodec_free_frame(avframe); + avframe = 0; } - avframe = 0; + byteBufferPool.drain(); } } }