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();
         }
     }
 }