diff --git a/lib/native/freebsd-64/libscreencapture.so b/lib/native/freebsd-64/libscreencapture.so index a592d3a57..cc172d350 100755 Binary files a/lib/native/freebsd-64/libscreencapture.so and b/lib/native/freebsd-64/libscreencapture.so differ diff --git a/lib/native/freebsd/libscreencapture.so b/lib/native/freebsd/libscreencapture.so index 97049f50f..8c1b59cd1 100755 Binary files a/lib/native/freebsd/libscreencapture.so and b/lib/native/freebsd/libscreencapture.so differ diff --git a/lib/native/linux-64/libscreencapture.so b/lib/native/linux-64/libscreencapture.so index 6a593db01..4a8b768c7 100755 Binary files a/lib/native/linux-64/libscreencapture.so and b/lib/native/linux-64/libscreencapture.so differ diff --git a/lib/native/linux/libscreencapture.so b/lib/native/linux/libscreencapture.so index 521d48aa1..c289f09f8 100755 Binary files a/lib/native/linux/libscreencapture.so and b/lib/native/linux/libscreencapture.so differ diff --git a/lib/native/mac/libscreencapture.jnilib b/lib/native/mac/libscreencapture.jnilib index 0633cdf4a..1e00a5344 100755 Binary files a/lib/native/mac/libscreencapture.jnilib and b/lib/native/mac/libscreencapture.jnilib differ diff --git a/lib/native/windows-64/screencapture.dll b/lib/native/windows-64/screencapture.dll index e1897bc8f..e22c9ea0e 100755 Binary files a/lib/native/windows-64/screencapture.dll and b/lib/native/windows-64/screencapture.dll differ diff --git a/lib/native/windows/screencapture.dll b/lib/native/windows/screencapture.dll index cca9d5a1e..98f715b2c 100644 Binary files a/lib/native/windows/screencapture.dll and b/lib/native/windows/screencapture.dll differ diff --git a/src/native/screencapture/Makefile b/src/native/screencapture/Makefile index 7948f893c..cd576e9dd 100644 --- a/src/native/screencapture/Makefile +++ b/src/native/screencapture/Makefile @@ -4,7 +4,7 @@ # \author Sebastien Vincent CC = gcc -CFLAGS = -std=c99 -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600 -Wall -Wextra -pedantic -Wstrict-prototypes -Wredundant-decls +CFLAGS = -std=c99 -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600 -Wall -Wextra -pedantic -Wstrict-prototypes -Wredundant-decls -O3 # uncomment to compile on Linux LDFLAGS = diff --git a/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.c b/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.c index 39e375cdb..a80e54a85 100644 --- a/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.c +++ b/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.c @@ -14,6 +14,7 @@ #include #include +#include #if defined(_WIN32) || defined(_WIN64) @@ -22,6 +23,8 @@ #include typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int8 uint8_t; #elif defined(__APPLE__) @@ -56,7 +59,7 @@ typedef __int32 int32_t; * \param h capture height * \return 0 if success, -1 otherwise */ -static int windows_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, int32_t h) +static int windows_grab_screen(jbyte* data, int32_t x, int32_t y, int32_t w, int32_t h) { static const RGBQUAD redColor = {0x00, 0x00, 0xFF, 0x00}; static const RGBQUAD greenColor = {0x00, 0xFF, 0x00, 0x00}; @@ -72,6 +75,9 @@ static int windows_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, i BITMAPINFOHEADER* bitmap_hdr = NULL; RGBQUAD *pixels = NULL; size_t i = 0; + size_t off = 0; + uint32_t test = 1; + int little_endian = *((uint8_t*)&test); /* get handle to the entire screen of Windows */ desktop = GetDC(NULL); @@ -186,8 +192,20 @@ static int windows_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, i for(i = 0 ; i < size ; i++) { - RGBQUAD* pixel = &pixels[i]; - data[i] = 0xFF000000 | pixel->rgbRed << 16 | pixel->rgbGreen << 8 | pixel->rgbBlue; + RGBQUAD* quad = &pixels[i]; + uint32_t pixel = 0xFF000000 | quad->rgbRed << 16 | quad->rgbGreen << 8 | quad->rgbBlue; + + if(little_endian) + { + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + pixel = 0xff << 24 | b << 16 | g << 8 | r; + } + + memcpy(data + off, &pixel, 4); + off += 4; } /* cleanup */ @@ -211,7 +229,7 @@ static int windows_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, i * \param h capture height * \return 0 if success, -1 otherwise */ -static int quartz_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, int32_t h) +static int quartz_grab_screen(jbyte* data, int32_t x, int32_t y, int32_t w, int32_t h) { CGImageRef img = NULL; CGDataProviderRef provider = NULL; @@ -221,6 +239,8 @@ static int quartz_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, in size_t off = 0; size_t i = 0; CGRect rect; + uint32_t test_endian = 1; + int little_endian = *((uint8_t*)&test_endian); rect = CGRectMake(x, y, w, h); img = CGWindowListCreateImage(rect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault); @@ -242,8 +262,17 @@ static int quartz_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, in { uint32_t pixel = *((uint32_t*)&pixels[i]); - pixel |= 0xff000000; /* ARGB */ - data[off++] = pixel; + if(little_endian) + { + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + pixel = 0xff << 24 | b << 16 | g << 8 | r; + } + + memcpy(data + off, &pixel, 4); + off += 4; } /* cleanup */ @@ -264,7 +293,7 @@ static int quartz_grab_screen(int32_t* data, int32_t x, int32_t y, int32_t w, in * \param h capture height * \return 0 if success, -1 otherwise */ -static int x11_grab_screen(const char* x11display, int32_t* data, int32_t x, int32_t y, int32_t w, int32_t h) +static int x11_grab_screen(const char* x11display, jbyte* data, int32_t x, int32_t y, int32_t w, int32_t h) { const char* display_str; /* display string */ Display* display = NULL; /* X11 display */ @@ -281,13 +310,9 @@ static int x11_grab_screen(const char* x11display, int32_t* data, int32_t x, int int i = 0; int j = 0; size_t size = 0; - - if(!data) - { - /* fprintf(stderr, "data is NULL!\n"); */ - return -1; - } - + uint32_t test_endian = 1; + int little_endian = *((uint8_t*)&test_endian); + display_str = x11display ? x11display : getenv("DISPLAY"); if(!display_str) @@ -388,7 +413,7 @@ static int x11_grab_screen(const char* x11display, int32_t* data, int32_t x, int } } - /* convert to Java ARGB */ + /* convert to bytes but keep ARGB */ for(j = 0 ; j < h ; j++) { for(i = 0 ; i < w ; i++) @@ -398,8 +423,17 @@ static int x11_grab_screen(const char* x11display, int32_t* data, int32_t x, int */ uint32_t pixel = (uint32_t)XGetPixel(img, i, j); - pixel |= 0xff000000; /* ARGB */ - data[off++] = pixel; + if(little_endian) + { + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + pixel = 0xff << 24 | b << 16 | g << 8 | r; + } + + memcpy(data + off, &pixel, 4); + off += 4; } } @@ -428,29 +462,27 @@ static int x11_grab_screen(const char* x11display, int32_t* data, int32_t x, int * \param y y position to start capture * \param width capture width * \param height capture height - * \return array of ARGB pixels (jint) + * \param output output buffer, screen bytes will be stored in + * \return true if success, false otherwise */ -JNIEXPORT jintArray JNICALL Java_net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture_grabScreen - (JNIEnv* env, jclass obj, jint x, jint y, jint width, jint height) +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture_grabScreen + (JNIEnv* env, jclass obj, jint x, jint y, jint width, jint height, jbyteArray output) { - int32_t* data = NULL; /* jint is always four-bytes signed integer */ - size_t size = width * height; - jintArray ret = NULL; + jint size = width * height * 4; + jbyte* data = NULL; obj = obj; /* not used */ - ret = (*env)->NewIntArray(env, size); - - if(!ret) + if(!output || (*env)->GetArrayLength(env, output) < size) { - return NULL; + return JNI_FALSE; } - data = (*env)->GetIntArrayElements(env, ret, NULL); - + data = (*env)->GetPrimitiveArrayCritical(env, output, 0); + if(!data) { - return NULL; + return JNI_FALSE; } #if defined (_WIN32) || defined(_WIN64) @@ -461,13 +493,11 @@ JNIEXPORT jintArray JNICALL Java_net_java_sip_communicator_impl_neomedia_imgstre if(x11_grab_screen(NULL, data, x, y, width, height) == -1) #endif { - (*env)->ReleaseIntArrayElements(env, ret, data, 0); - return NULL; + (*env)->ReleasePrimitiveArrayCritical(env, output, data, 0); + return JNI_FALSE; } - /* updates array with data's content */ - (*env)->ReleaseIntArrayElements(env, ret, data, 0); - - return ret; + (*env)->ReleasePrimitiveArrayCritical(env, output, data, 0); + return JNI_TRUE; } diff --git a/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.h b/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.h index 7c9d4dcde..ed9d8b1ea 100644 --- a/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.h +++ b/src/native/screencapture/net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture.h @@ -10,10 +10,10 @@ extern "C" { /* * Class: net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture * Method: grabScreen - * Signature: (IIII)[I + * Signature: (IIII[B)Z */ -JNIEXPORT jintArray JNICALL Java_net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture_grabScreen - (JNIEnv *, jclass, jint, jint, jint, jint); +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_neomedia_imgstreaming_NativeScreenCapture_grabScreen + (JNIEnv *, jclass, jint, jint, jint, jint, jbyteArray); #ifdef __cplusplus } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java index 069e91eec..de1341faf 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/VideoMediaDeviceSession.java @@ -132,13 +132,13 @@ protected DataSource createCaptureDevice() } /* - * FIXME There is no video in calls when using the QuickTime/QTKit - * CaptureDevice so the local video support is disabled for it. + * FIXME Cloning a Desktop streaming capture device no longer works + * since it becames a PullBufferCaptureDevice */ -// if (!QuickTimeAuto.LOCATOR_PROTOCOL.equals(protocol)) + if (!ImageStreamingUtils.LOCATOR_PROTOCOL.equals(protocol)) { - DataSource cloneableDataSource - = Manager.createCloneableDataSource(captureDevice); + DataSource cloneableDataSource = + Manager.createCloneableDataSource(captureDevice); if (cloneableDataSource != null) captureDevice = cloneableDataSource; diff --git a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteract.java b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteract.java index d93a7ce4f..e45f53d40 100644 --- a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteract.java +++ b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteract.java @@ -16,6 +16,40 @@ */ public interface DesktopInteract { + /** + * Capture the full desktop screen using native grabber. + * + * Contrary to other captureScreen method, it only returns raw bytes + * and not BufferedImage. It is done in order to limit + * slow operation such as converting ARGB images (uint32_t) to bytes + * especially for big big screen. For example a 1920x1200 desktop consumes + * 9 MB of memory for grabbing and another 9 MB array for convertion operation. + * + * @param output output buffer to store bytes in. + * Be sure that output length is sufficient + * @return true if success, false if JNI error or output length too short + */ + public boolean captureScreen(byte output[]); + + /** + * Capture a part of the desktop screen using native grabber. + * + * Contrary to other captureScreen method, it only returns raw bytes + * and not BufferedImage. It is done in order to limit + * slow operation such as converting ARGB images (uint32_t) to bytes + * especially for big big screen. For example a 1920x1200 desktop consumes + * 9 MB of memory for grabbing and another 9 MB array for convertion operation. + * + * @param x x position to start capture + * @param y y position to start capture + * @param width capture width + * @param height capture height + * @param output output buffer to store bytes in. + * Be sure that output length is sufficient + * @return true if success, false if JNI error or output length too short + */ + public boolean captureScreen(int x, int y, int width, int height, byte output[]); + /** * Capture the full desktop screen. * diff --git a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteractImpl.java b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteractImpl.java index 2e8628f49..7267038bb 100644 --- a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteractImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/DesktopInteractImpl.java @@ -48,7 +48,56 @@ public DesktopInteractImpl() throws AWTException, SecurityException } /** - * Capture the full desktop screen. + * Capture the full desktop screen using native grabber. + * + * Contrary to other captureScreen method, it only returns raw bytes + * and not BufferedImage. It is done in order to limit + * slow operation such as converting ARGB images (uint32_t) to bytes + * especially for big big screen. For example a 1920x1200 desktop consumes + * 9 MB of memory for grabbing and another 9 MB array for convertion operation. + * + * @param output output buffer to store bytes in. + * Be sure that output length is sufficient + * @return true if success, false if JNI error or output length too short + */ + public boolean captureScreen(byte output[]) + { + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + + return captureScreen(0, 0, (int)dim.getWidth(), (int)dim.getHeight(), output); + } + + /** + * Capture a part of the desktop screen using native grabber. + * + * Contrary to other captureScreen method, it only returns raw bytes + * and not BufferedImage. It is done in order to limit + * slow operation such as converting ARGB images (uint32_t) to bytes + * especially for big big screen. For example a 1920x1200 desktop consumes + * 9 MB of memory for grabbing and another 9 MB array for convertion operation. + * + * @param x x position to start capture + * @param y y position to start capture + * @param width capture width + * @param height capture height + * @param output output buffer to store bytes in. + * Be sure that output length is sufficient + * @return true if success, false if JNI error or output length too short + */ + public boolean captureScreen(int x, int y, int width, int height, byte output[]) + { + if(OSUtils.IS_LINUX || OSUtils.IS_FREEBSD || OSUtils.IS_WINDOWS + || OSUtils.IS_MAC) + { + return NativeScreenCapture.grabScreen( + x, y, width, height, output); + } + + return false; + } + + /** + * Capture the full desktop screen using java.awt.Robot. * * @return BufferedImage of the desktop screen */ @@ -60,7 +109,7 @@ public BufferedImage captureScreen() } /** - * Capture a part of the desktop screen. + * Capture a part of the desktop screen using java.awt.Robot. * * @param x x position to start capture * @param y y position to start capture @@ -72,31 +121,18 @@ public BufferedImage captureScreen() public BufferedImage captureScreen(int x, int y, int width, int height) { BufferedImage img = null; - - if(OSUtils.IS_LINUX || OSUtils.IS_FREEBSD || OSUtils.IS_WINDOWS || OSUtils.IS_MAC) + Rectangle rect = null; + + if(robot == null) { - img = NativeScreenCapture.captureScreen(x, y, width, height); + /* Robot has not been created so abort */ + return null; } - /* in case native screen grabber is not available - * for the current OS or if it has failed, - * fallback to Java AWT Robot - */ - if(img == null) - { - Rectangle rect = null; - - if(robot == null) - { - /* Robot has not been created so abort */ - return null; - } - - logger.info("Begin capture: " + System.nanoTime()); - rect = new Rectangle(x, y, width, height); - img = robot.createScreenCapture(rect); - logger.info("End capture: " + System.nanoTime()); - } + logger.info("Begin capture: " + System.nanoTime()); + rect = new Rectangle(x, y, width, height); + img = robot.createScreenCapture(rect); + logger.info("End capture: " + System.nanoTime()); return img; } diff --git a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/ImageStreamingUtils.java b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/ImageStreamingUtils.java index 4a2c5a13f..78dbf3d91 100644 --- a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/ImageStreamingUtils.java +++ b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/ImageStreamingUtils.java @@ -60,10 +60,12 @@ public static BufferedImage getScaledImage(BufferedImage src, * Get raw bytes from ARGB BufferedImage. * * @param src ARGB + * @param output output buffer, if not null and if its length is at least + * image's (width * height) * 4, method will put bytes in it. * @return raw bytes or null if src is not an ARGB - * BufferedImage + * BufferedImage */ - public static byte[] getImageBytes(BufferedImage src) + public static byte[] getImageBytes(BufferedImage src, byte output[]) { if(src.getType() != BufferedImage.TYPE_INT_ARGB) throw new IllegalArgumentException("src.type"); @@ -71,11 +73,21 @@ public static byte[] getImageBytes(BufferedImage src) WritableRaster raster = src.getRaster(); int width = src.getWidth(); int height = src.getHeight(); - - /* allocate our bytes array */ - byte[] data = new byte[width * height * 4]; + int size = width * height * 4; int off = 0; int pixel[] = new int[4]; + byte data[] = null; + + if(output == null || output.length < size) + { + /* allocate our bytes array */ + data = new byte[size]; + } + else + { + /* use output */ + data = output; + } for(int y = 0 ; y < height ; y++) for(int x = 0 ; x < width ; x++) diff --git a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/NativeScreenCapture.java b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/NativeScreenCapture.java index 9090849cf..66f170ff9 100644 --- a/src/net/java/sip/communicator/impl/neomedia/imgstreaming/NativeScreenCapture.java +++ b/src/net/java/sip/communicator/impl/neomedia/imgstreaming/NativeScreenCapture.java @@ -23,51 +23,14 @@ public class NativeScreenCapture } /** - * Capture desktop screen. + * Grab desktop screen and get raw bytes. * * @param x x position to start capture * @param y y position to start capture * @param width capture width * @param height capture height - * @return BufferedImage of the desktop screen + * @param output output buffer to store screen bytes + * @return true if grab success, false otherwise */ - public static BufferedImage captureScreen(int x, - int y, - int width, - int height) - { - DirectColorModel model - = new DirectColorModel(32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000); - int masks[] = {0xFF0000, 0xFF00, 0xFF, 0xFF000000}; - WritableRaster raster = null; - DataBufferInt buffer = null; - BufferedImage image = null; - int data[] = null; - - data = grabScreen(x, y, width, height); - - if(data == null) - { - return null; - } - - buffer = new DataBufferInt(data, data.length); - raster - = Raster.createPackedRaster( - buffer, width, height, width, masks, null); - image = new BufferedImage(model, raster, false, null); - - return image; - } - - /** - * Grab desktop screen and get ARGB pixels. - * - * @param x x position to start capture - * @param y y position to start capture - * @param width capture width - * @param height capture height - * @return array of ARGB pixels - */ - private static native int[] grabScreen(int x, int y, int width, int height); + public static native boolean grabScreen(int x, int y, int width, int height, byte output[]); } diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferCaptureDevice.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferCaptureDevice.java new file mode 100644 index 000000000..dd3b4a99e --- /dev/null +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferCaptureDevice.java @@ -0,0 +1,721 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import javax.media.*; +import javax.media.control.*; +import javax.media.protocol.*; + +import net.java.sip.communicator.impl.neomedia.control.*; +import net.java.sip.communicator.util.*; + +/** + * Provides a base implementation of PullBufferDataSource and + * CaptureDevice in order to facilitate implementers by taking care of + * boilerplate in the most common cases. + * + * @author Lubomir Marinov + */ +public abstract class AbstractPullBufferCaptureDevice + extends PullBufferDataSource + implements CaptureDevice +{ + + /** + * The Logger used by the AbstractPullBufferCaptureDevice + * class and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(AbstractPullBufferCaptureDevice.class); + + /** + * The value of the formatControls property of + * AbstractPullBufferCaptureDevice which represents an empty array + * of FormatControls. Explicitly defined in order to reduce + * unnecessary allocations. + */ + protected static final FormatControl[] EMPTY_FORMAT_CONTROLS + = new FormatControl[0]; + + /** + * The value of the streams property of + * AbstractPullBufferCaptureDevice which represents an empty array + * of PullBufferStreams. Explicitly defined in order to reduce + * unnecessary allocations. + */ + protected static final PullBufferStream[] EMPTY_STREAMS + = new PullBufferStream[0]; + + /** + * The indicator which determines whether a connection to the media source + * specified by the MediaLocator of this DataSource has + * been opened. + */ + private boolean connected = false; + + /** + * The array of FormatControl instances each one of which can be + * used before {@link #connect()} to get and set the capture Format + * of each one of the capture streams. + */ + private FormatControl[] formatControls; + + /** + * The indicator which determines whether the transfer of media data from + * this DataSource has been started. + */ + private boolean started = false; + + /** + * The PullBufferStreams through which this + * PullBufferDataSource gives access to its media data. + *

+ * Warning: Caution is advised when directly using the field and access to + * it is to be synchronized with sync root this. + *

+ */ + protected AbstractPullBufferStream[] streams; + + /** + * Initializes a new AbstractPullBufferCaptureDevice instance. + */ + protected AbstractPullBufferCaptureDevice() + { + } + + /** + * Initializes a new AbstractPullBufferCaptureDevice instance from + * a specific MediaLocator. + * + * @param locator the MediaLocator to create the new instance from + */ + protected AbstractPullBufferCaptureDevice(MediaLocator locator) + { + setLocator(locator); + } + + /** + * Opens a connection to the media source specified by the + * MediaLocator of this DataSource. + * + * @throws IOException if anything goes wrong while opening the connection + * to the media source specified by the MediaLocator of this + * DataSource + */ + public synchronized void connect() + throws IOException + { + if (!connected) + { + doConnect(); + connected = true; + } + } + + /** + * Creates a new FormatControl instance which is to be associated + * with a PullBufferStream at a specific zero-based index in the + * list of streams of this PullBufferDataSource. As the + * FormatControls of a PullBufferDataSource can be + * requested before {@link #connect()}, its PullBufferStreams may + * not exist at the time of the request for the creation of the + * FormatControl. + * + * @param streamIndex the zero-based index of the PullBufferStream + * in the list of streams of this PullBufferDataSource which is to + * be associated with the new FormatControl instance + * @return a new FormatControl instance which is to be associated + * with a PullBufferStream at the specified streamIndex in + * the list of streams of this PullBufferDataSource + */ + protected FormatControl createFormatControl(final int streamIndex) + { + return + new AbstractFormatControl() + { + /** + * The Format of this FormatControl and, + * respectively, of the media data of its owner. + */ + private Format format; + + /** + * Gets the Format of the media data of the owner of + * this FormatControl. + * + * @return the Format of the media data of the owner of + * this FormatControl + */ + public Format getFormat() + { + format + = AbstractPullBufferCaptureDevice.this + .internalGetFormat(streamIndex, format); + return format; + } + + /** + * Gets the Formats in which the owner of this + * FormatControl is capable of providing media data. + * + * @return an array of Formats in which the owner of + * this FormatControl is capable of providing media + * data + */ + public Format[] getSupportedFormats() + { + return + AbstractPullBufferCaptureDevice.this + .getSupportedFormats(streamIndex); + } + + /** + * Implements {@link FormatControl#setFormat(Format)}. Attempts + * to set the Format in which the owner of this + * FormatControl is to provide media data. + * + * @param format the Format to be set on this instance + * @return the currently set Format after the attempt + * to set it on this instance if format is supported by + * this instance and regardless of whether it was actually set; + * null if format is not supported by this + * instance + */ + @Override + public Format setFormat(Format format) + { + Format setFormat = super.setFormat(format); + + if (setFormat != null) + { + setFormat + = AbstractPullBufferCaptureDevice.this + .internalSetFormat( + streamIndex, + setFormat, + format); + if (setFormat != null) + this.format = setFormat; + } + return setFormat; + } + }; + } + + /** + * Creates the FormatControls of this CaptureDevice. + * + * @return an array of the FormatControls of this + * CaptureDevice + */ + protected FormatControl[] createFormatControls() + { + FormatControl formatControl = createFormatControl(0); + + return + (formatControl == null) + ? EMPTY_FORMAT_CONTROLS + : new FormatControl[] { formatControl }; + } + + /** + * Create a new PullBufferStream which is to be at a specific + * zero-based index in the list of streams of this + * PullBufferDataSource. The Format-related information of + * the new instance is to be abstracted by a specific + * FormatControl. + * + * @param streamIndex the zero-based index of the PullBufferStream + * in the list of streams of this PullBufferDataSource + * @param formatControl the FormatControl which is to abstract the + * Format-related information of the new instance + * @return a new PullBufferStream which is to be at the specified + * streamIndex in the list of streams of this + * PullBufferDataSource and which has its Format-related + * information abstracted by the specified formatControl + */ + protected abstract AbstractPullBufferStream createStream( + int streamIndex, + FormatControl formatControl); + + /** + * Closes the connection to the media source specified by the + * MediaLocator of this DataSource. If such a connection + * has not been opened, the call is ignored. + */ + public synchronized void disconnect() + { + try + { + stop(); + } + catch (IOException ioex) + { + logger.error("Failed to stop " + getClass().getSimpleName(), ioex); + } + + if (connected) + { + doDisconnect(); + connected = false; + } + } + + /** + * Opens a connection to the media source specified by the + * MediaLocator of this DataSource. Allows extenders to + * override and be sure that there will be no request to open a connection + * if the connection has already been opened. + * + * @throws IOException if anything goes wrong while opening the connection + * to the media source specified by the MediaLocator of this + * DataSource + */ + protected synchronized void doConnect() + throws IOException + { + } + + /** + * Closes the connection to the media source specified by the + * MediaLocator of this DataSource. Allows extenders to + * override and be sure that there will be no request to close a connection + * if the connection has not been opened yet. + */ + protected synchronized void doDisconnect() + { + /* + * While it is not clear whether the streams can be released upon + * disconnect, com.imb.media.protocol.SuperCloneableDataSource gets the + * streams of the DataSource it adapts (i.e. this DataSource when + * SourceCloneable support is to be created for it) before #connect(). + * Unfortunately, it means that it isn't clear when the streams are to + * be disposed. + */ +// if (streams != null) +// try +// { +// for (AbstractPullBufferStream stream : streams) +// stream.close(); +// } +// finally +// { +// streams = null; +// } + } + + /** + * Starts the transfer of media data from this DataSource. Allows + * extenders to override and be sure that there will be no request to start + * the transfer of media data if it has already been started. + * + * @throws IOException if anything goes wrong while starting the transfer of + * media data from this DataSource + */ + protected synchronized void doStart() + throws IOException + { + if (streams != null) + for (AbstractPullBufferStream stream : streams) + stream.start(); + } + + /** + * Stops the transfer of media data from this DataSource. Allows + * extenders to override and be sure that there will be no request to stop + * the transfer of media data if it has not been started yet. + * + * @throws IOException if anything goes wrong while stopping the transfer of + * media data from this DataSource + */ + protected synchronized void doStop() + throws IOException + { + if (streams != null) + for (AbstractPullBufferStream stream : streams) + stream.stop(); + } + + /** + * Gets the CaptureDeviceInfo of this CaptureDevice which + * describes it. + * + * @return the CaptureDeviceInfo of this CaptureDevice + * which describes it + */ + public CaptureDeviceInfo getCaptureDeviceInfo() + { + return getCaptureDeviceInfo(this); + } + + /** + * Gets the CaptureDeviceInfo of a specific CaptureDevice + * by locating its registration in JMF using its MediaLocator. + * + * @param captureDevice the CaptureDevice to gets the + * CaptureDeviceInfo of + * @return the CaptureDeviceInfo of the specified + * CaptureDevice as registered in JMF + */ + public static CaptureDeviceInfo getCaptureDeviceInfo( + DataSource captureDevice) + { + /* + * TODO The implemented search for the CaptureDeviceInfo of this + * CaptureDevice by looking for its MediaLocator is inefficient. + */ + @SuppressWarnings("unchecked") + Vector captureDeviceInfos + = (Vector) + CaptureDeviceManager.getDeviceList(null); + MediaLocator locator = captureDevice.getLocator(); + + for (CaptureDeviceInfo captureDeviceInfo : captureDeviceInfos) + if (captureDeviceInfo.getLocator().equals(locator)) + return captureDeviceInfo; + return null; + } + + /** + * Gets the content type of the media represented by this instance. The + * AbstractPullBufferCaptureDevice implementation always returns + * {@link ContentDescriptor#RAW}. + * + * @return the content type of the media represented by this instance + */ + public String getContentType() + { + return ContentDescriptor.RAW; + } + + /** + * Gets the control of the specified type available for this instance. + * + * @param controlType the type of the control available for this instance to + * be retrieved + * @return an Object which represents the control of the specified + * type available for this instance if such a control is indeed available; + * otherwise, null + */ + public Object getControl(String controlType) + { + return AbstractControls.getControl(this, controlType); + } + + /** + * Implements {@link Controls#getControls()}. Gets the controls available + * for this instance. + * + * @return an array of Objects which represent the controls + * available for this instance + */ + public Object[] getControls() + { + FormatControl[] formatControls = internalGetFormatControls(); + + if ((formatControls == null) || (formatControls.length == 0)) + return ControlsAdapter.EMPTY_CONTROLS; + else + { + Object[] controls = new Object[formatControls.length]; + + System.arraycopy( + formatControls, + 0, + controls, + 0, + formatControls.length); + return controls; + } + } + + /** + * Gets the duration of the media represented by this instance. The + * AbstractPullBufferCaptureDevice always returns + * {@link #DURATION_UNBOUNDED}. + * + * @return the duration of the media represented by this instance + */ + public Time getDuration() + { + return DURATION_UNBOUNDED; + } + + /** + * Gets the Format to be reported by the FormatControl of + * a PullBufferStream at a specific zero-based index in the list of + * streams of this PullBufferDataSource. The + * PullBufferStream may not exist at the time of requesting its + * Format. Allows extenders to override the default behavior which + * is to report any last-known format or the first Format from the + * list of supported formats as defined in the JMF registration of this + * CaptureDevice. + * + * @param streamIndex the zero-based index of the PullBufferStream + * the Format of which is to be retrieved + * @param oldValue the last-known Format for the + * PullBufferStream at the specified streamIndex + * @return the Format to be reported by the FormatControl + * of the PullBufferStream at the specified streamIndex in + * the list of streams of this PullBufferDataSource. + */ + protected Format getFormat(int streamIndex, Format oldValue) + { + if (oldValue != null) + return oldValue; + + Format[] supportedFormats = getSupportedFormats(streamIndex); + + return + ((supportedFormats == null) || (supportedFormats.length < 1)) + ? null + : supportedFormats[0]; + } + + /** + * Gets an array of FormatControl instances each one of which can + * be used before {@link #connect()} to get and set the capture + * Format of each one of the capture streams. + * + * @return an array of FormatControl instances each one of which + * can be used before {@link #connect()} to get and set the capture + * Format of each one of the capture streams + */ + public FormatControl[] getFormatControls() + { + return AbstractFormatControl.getFormatControls(this); + } + + /** + * Gets the PullBufferStreams through which this + * PullBufferDataSource gives access to its media data. + * + * @return an array of the PullBufferStreams through which this + * PullBufferDataSource gives access to its media data + */ + public synchronized PullBufferStream[] getStreams() + { + if (streams == null) + { + FormatControl[] formatControls = internalGetFormatControls(); + + if (formatControls != null) + { + int formatControlCount = formatControls.length; + + streams = new AbstractPullBufferStream[formatControlCount]; + for (int i = 0; i < formatControlCount; i++) + streams[i] = createStream(i, formatControls[i]); + + /* + * Start the streams if this DataSource has already been + * started. + */ + if (started) + for (AbstractPullBufferStream stream : streams) + try + { + stream.start(); + } + catch (IOException ioex) + { + throw new UndeclaredThrowableException(ioex); + } + } + } + if (streams == null) + return EMPTY_STREAMS; + else + { + PullBufferStream[] clone = new PullBufferStream[streams.length]; + + System.arraycopy(streams, 0, clone, 0, streams.length); + return clone; + } + } + + /** + * Gets the Formats which are to be reported by a + * FormatControl as supported formats for a + * PullBufferStream at a specific zero-based index in the list of + * streams of this PullBufferDataSource. + * + * @param streamIndex the zero-based index of the PullBufferStream + * for which the specified FormatControl is to report the list of + * supported Formats + * @return an array of Formats to be reported by a + * FormatControl as the supported formats for the + * PullBufferStream at the specified streamIndex in the + * list of streams of this PullBufferDataSource + */ + protected Format[] getSupportedFormats(int streamIndex) + { + CaptureDeviceInfo captureDeviceInfo = getCaptureDeviceInfo(); + + return + (captureDeviceInfo == null) ? null : captureDeviceInfo.getFormats(); + } + + /** + * Gets the Format to be reported by the FormatControl of + * a PullBufferStream at a specific zero-based index in the list of + * streams of this PullBufferDataSource. The + * PullBufferStream may not exist at the time of requesting its + * Format. + * + * @param streamIndex the zero-based index of the PullBufferStream + * the Format of which is to be retrieved + * @param oldValue the last-known Format for the + * PullBufferStream at the specified streamIndex + * @return the Format to be reported by the FormatControl + * of the PullBufferStream at the specified streamIndex in + * the list of streams of this PullBufferDataSource. + */ + private Format internalGetFormat(int streamIndex, Format oldValue) + { + synchronized (this) + { + if (streams != null) + { + AbstractPullBufferStream stream = streams[streamIndex]; + + if (stream != null) + { + Format streamFormat = stream.internalGetFormat(); + + if (streamFormat != null) + return streamFormat; + } + } + } + return getFormat(streamIndex, oldValue); + } + + /** + * Gets an array of FormatControl instances each one of which can + * be used before {@link #connect()} to get and set the capture + * Format of each one of the capture streams. + * + * @return an array of FormatControl instances each one of which + * can be used before {@link #connect()} to get and set the capture + * Format of each one of the capture streams + */ + private synchronized FormatControl[] internalGetFormatControls() + { + if (formatControls == null) + formatControls = createFormatControls(); + return formatControls; + } + + /** + * Attempts to set the Format to be reported by the + * FormatControl of a PullBufferStream at a specific + * zero-based index in the list of streams of this + * PullBufferDataSource. + * + * @param streamIndex the zero-based index of the PullBufferStream + * the Format of which is to be set + * @param oldValue the last-known Format for the + * PullBufferStream at the specified streamIndex + * @param newValue the Format which is to be set + * @return the Format to be reported by the FormatControl + * of the PullBufferStream at the specified streamIndex + * in the list of streams of this PullBufferStream or null + * if the attempt to set the Format did not success and any + * last-known Format is to be left in effect + */ + private Format internalSetFormat( + int streamIndex, + Format oldValue, + Format newValue) + { + synchronized (this) + { + if (streams != null) + { + AbstractPullBufferStream stream = streams[streamIndex]; + + if (stream != null) + return stream.internalSetFormat(newValue); + } + } + return setFormat(streamIndex, oldValue, newValue); + } + + /** + * Attempts to set the Format to be reported by the + * FormatControl of a PullBufferStream at a specific + * zero-based index in the list of streams of this + * PullBufferDataSource. The PullBufferStream does not + * exist at the time of the attempt to set its Format. Allows + * extenders to override the default behavior which is to not attempt to set + * the specified Format so that they can enable setting the + * Format prior to creating the PullBufferStream. If + * setting the Format of an existing PullBufferStream is + * desired, AbstractPullBufferStream#doSetFormat(Format) should be + * overridden instead. + * + * @param streamIndex the zero-based index of the PullBufferStream + * the Format of which is to be set + * @param oldValue the last-known Format for the + * PullBufferStream at the specified streamIndex + * @param newValue the Format which is to be set + * @return the Format to be reported by the FormatControl + * of the PullBufferStream at the specified streamIndex + * in the list of streams of this PullBufferStream or null + * if the attempt to set the Format did not success and any + * last-known Format is to be left in effect + */ + protected Format setFormat( + int streamIndex, + Format oldValue, + Format newValue) + { + return oldValue; + } + + /** + * Starts the transfer of media data from this DataSource + * + * @throws IOException if anything goes wrong while starting the transfer of + * media data from this DataSource + */ + public synchronized void start() + throws IOException + { + if (!started) + { + if (!connected) + throw + new IOException( + getClass().getSimpleName() + " not connected"); + + doStart(); + started = true; + } + } + + /** + * Stops the transfer of media data from this DataSource. + * + * @throws IOException if anything goes wrong while stopping the transfer of + * media data from this DataSource + */ + public synchronized void stop() + throws IOException + { + if (started) + { + doStop(); + started = false; + } + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferStream.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferStream.java new file mode 100644 index 000000000..d46f40396 --- /dev/null +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/AbstractPullBufferStream.java @@ -0,0 +1,241 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol; + +import java.io.*; + +import javax.media.*; +import javax.media.control.*; +import javax.media.protocol.*; + +import net.java.sip.communicator.impl.neomedia.control.*; +import net.java.sip.communicator.util.*; + +/** + * Provides a base implementation of PullBufferStream in order to + * facilitate implementers by taking care of boilerplate in the most common + * cases. + * + * @author Lubomir Marinov + */ +public abstract class AbstractPullBufferStream + extends AbstractControls + implements PullBufferStream +{ + + /** + * The Logger used by the AbstractPullBufferStream class + * and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(AbstractPullBufferStream.class); + + /** + * The (default) ContentDescriptor of the + * AbstractPullBufferStream instances. + */ + private static final ContentDescriptor CONTENT_DESCRIPTOR + = new ContentDescriptor(ContentDescriptor.RAW); + + /** + * The FormatControl which gives access to the Format of + * the media data provided by this SourceStream and which, + * optionally, allows setting it. + */ + private final FormatControl formatControl; + + /** + * Initializes a new AbstractPullBufferStream instance which is to + * have its Format-related information abstracted by a specific + * FormatControl. + * + * @param formatControl the FormatControl which is to abstract the + * Format-related information of the new instance + */ + protected AbstractPullBufferStream(FormatControl formatControl) + { + this.formatControl = formatControl; + } + + /** + * Releases the resources used by this instance throughout its existence and + * makes it available for garbage collection. This instance is considered + * unusable after closing. + */ + public void close() + { + try + { + stop(); + } + catch (IOException ioex) + { + logger.error("Failed to stop " + getClass().getSimpleName(), ioex); + } + } + + /** + * Gets the Format of this PullBufferStream as directly + * known by it. Allows extenders to override the Format known to + * the PullBufferDataSource which created this instance and + * possibly provide more details on the currently set Format. + * + * @return the Format of this PullBufferStream as directly + * known by it or null if this PullBufferStream does not + * directly know its Format and it relies on the + * PullBufferDataSource which created it to report its + * Format + */ + protected Format doGetFormat() + { + return null; + } + + /** + * Attempts to set the Format of this PullBufferStream. + * Allows extenders to enable setting the Format of an existing + * PullBufferStream (in contract to setting it before the + * PullBufferStream is created by the PullBufferDataSource + * which will provide it). + * + * @param format the Format to be set as the format of this + * PullBufferStream + * @return the Format of this PullBufferStream or + * null if the attempt to set the Format did not succeed + * and any last-known Format is to be left in effect + */ + protected Format doSetFormat(Format format) + { + return null; + } + + /** + * Determines whether the end of this SourceStream has been + * reached. The AbstractPullBufferStream implementation always + * returns false. + * + * @return true if the end of this SourceStream has been + * reached; otherwise, false + */ + public boolean endOfStream() + { + return false; + } + + /** + * Determines if read will block. + * + * @return true if read block, false otherwise + */ + public boolean willReadBlock() + { + return true; + } + + /** + * Gets a ContentDescriptor which describes the type of the content + * made available by this SourceStream. The + * AbstractPullBufferStream implementation always returns a + * ContentDescriptor with content type equal to + * ContentDescriptor#RAW. + * + * @return a ContentDescriptor which describes the type of the + * content made available by this SourceStream + */ + public ContentDescriptor getContentDescriptor() + { + return CONTENT_DESCRIPTOR; + } + + /** + * Gets the length in bytes of the content made available by this + * SourceStream. The AbstractPullBufferStream + * implementation always returns LENGTH_UNKNOWN. + * + * @return the length in bytes of the content made available by this + * SourceStream if it is known; otherwise, LENGTH_UKNOWN + */ + public long getContentLength() + { + return LENGTH_UNKNOWN; + } + + /** + * Implements {@link Controls#getControls()}. Gets the controls available + * for this instance. + * + * @return an array of Objects which represent the controls + * available for this instance + */ + public Object[] getControls() + { + if (formatControl != null) + return new Object[] { formatControl }; + else + return ControlsAdapter.EMPTY_CONTROLS; + } + + /** + * Gets the Format of the media data made available by this + * PullBufferStream. + * + * @return the Format of the media data made available by this + * PullBufferStream + */ + public Format getFormat() + { + return (formatControl == null) ? null : formatControl.getFormat(); + } + + /** + * Gets the Format of this PullBufferStream as directly + * known by it. + * + * @return the Format of this PullBufferStream as directly + * known by it + */ + Format internalGetFormat() + { + return doGetFormat(); + } + + /** + * Attempts to set the Format of this PullBufferStream. + * + * @param format the Format to be set as the format of this + * PullBufferStream + * @return the Format of this PullBufferStream or + * null if the attempt to set the Format did not succeed + * and any last-known Format is to be left in effect + */ + Format internalSetFormat(Format format) + { + return doSetFormat(format); + } + + /** + * Starts the transfer of media data from this PullBufferStream. + * + * @throws IOException if anything goes wrong while starting the transfer of + * media data from this PullBufferStream + */ + public void start() + throws IOException + { + } + + /** + * Stops the transfer of media data from this PullBufferStream. + * + * @throws IOException if anything goes wrong while stopping the transfer of + * media data from this PullBufferStream + */ + public void stop() + throws IOException + { + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/DataSource.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/DataSource.java index 222b3bd48..271b671fb 100644 --- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/DataSource.java +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/DataSource.java @@ -23,7 +23,7 @@ * @author Damian Minkov */ public class DataSource - extends AbstractPushBufferCaptureDevice + extends AbstractPullBufferCaptureDevice { /** @@ -61,23 +61,23 @@ public DataSource(MediaLocator locator) } /** - * Create a new PushBufferStream which is to be at a specific + * Create a new PullBufferStream which is to be at a specific * zero-based index in the list of streams of this - * PushBufferDataSource. The Format-related information of + * PullBufferDataSource. The Format-related information of * the new instance is to be abstracted by a specific * FormatControl. * - * @param streamIndex the zero-based index of the PushBufferStream - * in the list of streams of this PushBufferDataSource + * @param streamIndex the zero-based index of the PullBufferStream + * in the list of streams of this PullBufferDataSource * @param formatControl the FormatControl which is to abstract the * Format-related information of the new instance - * @return a new PushBufferStream which is to be at the specified + * @return a new PullBufferStream which is to be at the specified * streamIndex in the list of streams of this - * PushBufferDataSource and which has its Format-related + * PullBufferDataSource and which has its Format-related * information abstracted by the specified formatControl - * @see AbstractPushBufferCaptureDevice#createStream(int, FormatControl) + * @see AbstractPullBufferCaptureDevice#createStream(int, FormatControl) */ - protected AbstractPushBufferStream createStream( + protected AbstractPullBufferStream createStream( int streamIndex, FormatControl formatControl) { diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java index b7f1b8581..bf270927f 100644 --- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/imgstreaming/ImageStream.java @@ -29,8 +29,7 @@ * @author Damian Minkov */ public class ImageStream - extends AbstractPushBufferStream - implements Runnable + extends AbstractPullBufferStream { /** * The Logger @@ -42,11 +41,6 @@ public class ImageStream */ private long seqNo = 0; - /** - * Capture thread reference. - */ - private Thread captureThread = null; - /** * If stream is started or not. */ @@ -83,164 +77,162 @@ public class ImageStream public void read(Buffer buffer) throws IOException { - synchronized(buf) + //System.out.println(System.currentTimeMillis()); + byte data[] = (byte[])buffer.getData(); + int dataLength = (data != null) ? data.length : 0; + long begin = System.currentTimeMillis(); + /* maximum time allowed for a capture to respect frame rate */ + long maxTime = 1000 / 10; + int wait = 0; + + if((data != null) || (dataLength != 0)) { - try + byte buf[] = readScreen(data); + + if(buf != data) + { + /* 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); + buffer.setFormat(getFormat()); + buffer.setHeader(null); + buffer.setTimeStamp(System.nanoTime()); + buffer.setSequenceNumber(seqNo); + buffer.setFlags(Buffer.FLAG_SYSTEM_TIME | Buffer.FLAG_LIVE_DATA); + seqNo++; + } + + wait = (int)(maxTime - (System.currentTimeMillis() - begin)); + + try + { + /* sleep to respect as much as possible the + * frame rate + */ + if(wait > 0) { - Object bufData = buf.getData(); - int bufLength = buf.getLength(); - - if ((bufData != null) || (bufLength != 0)) - { - buffer.setData(bufData); - buffer.setOffset(0); - buffer.setLength(bufLength); - buffer.setFormat(buf.getFormat()); - buffer.setHeader(null); - buffer.setTimeStamp(buf.getTimeStamp()); - buffer.setSequenceNumber(buf.getSequenceNumber()); - buffer.setFlags(buf.getFlags()); - - /* clear buf so JMF will not get twice the same image */ - buf.setData(null); - buf.setLength(0); - } + Thread.sleep(wait); } - catch (Exception e) + else { + /* yield a little bit to not use all the + * CPU + */ + Thread.yield(); } } + catch(Exception e) + { + } } /** * Start desktop capture stream. * - * @see AbstractPushBufferStream#start() + * @see AbstractPullBufferStream#start() */ @Override public void start() { - if(captureThread == null || !captureThread.isAlive()) + if(desktopInteract == null) { - logger.info("Start stream"); - captureThread = new Thread(this); - - /* - * Set the started indicator before calling Thread#start() because - * the Thread may exist upon start if Thread#run() starts executing - * before setting the started indicator. - */ - started = true; - captureThread.start(); + try + { + desktopInteract = new DesktopInteractImpl(); + } + catch(Exception e) + { + logger.warn("Cannot create DesktopInteract object!"); + started = false; + return; + } } + + started = true; } /** * Stop desktop capture stream. * - * @see AbstractPushBufferStream#stop() + * @see AbstractPullBufferStream#stop() */ @Override public void stop() { logger.info("Stop stream"); started = false; - captureThread = null; } /** - * Thread entry point. + * Read screen. + * + * @param output output buffer for screen bytes + * @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 void run() + public byte[] readScreen(byte output[]) { VideoFormat format = (VideoFormat) getFormat(); Dimension formatSize = format.getSize(); int width = (int) formatSize.getWidth(); int height = (int) formatSize.getHeight(); - - if(desktopInteract == null) + 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 - { - desktopInteract = new DesktopInteractImpl(); - } - catch(Exception e) - { - logger.warn("Cannot create DesktopInteract object!"); - started = false; - return; - } + output = null; + output = new byte[size]; } - while(started) + /* get desktop screen via native grabber if available */ + if(desktopInteract.captureScreen(output)) { - byte data[] = null; - BufferedImage scaledScreen = null; - BufferedImage screen = null; - - /* get desktop screen and resize it */ - screen = desktopInteract.captureScreen(); + return output; + } - if(screen.getType() == BufferedImage.TYPE_INT_ARGB) - { - /* with our native screencapture we - * automatically create BufferedImage in - * ARGB format so no need to rescale/convert - * to ARGB - */ - scaledScreen = screen; - } - else - { - /* convert to ARGB BufferedImage */ - scaledScreen - = ImageStreamingUtils - .getScaledImage( - screen, - width, - height, - BufferedImage.TYPE_INT_ARGB); - } + 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 + * + * 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 + = ImageStreamingUtils + .getScaledImage( + screen, + width, + height, + BufferedImage.TYPE_INT_ARGB); /* get raw bytes */ - data = ImageStreamingUtils.getImageBytes(scaledScreen); - - /* notify JMF that new data is available */ - synchronized (buf) - { - buf.setData(data); - buf.setOffset(0); - buf.setLength(data.length); - buf.setFormat(format); - buf.setHeader(null); - buf.setTimeStamp(System.nanoTime()); - buf.setSequenceNumber(seqNo++); - buf.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME); - } - - /* pass to JMF handler */ - BufferTransferHandler transferHandler = this.transferHandler; - - if(transferHandler != null) - { - transferHandler.transferData(this); - Thread.yield(); - } - - /* cleanup */ - screen = null; - scaledScreen = null; - data = null; - - try - { - /* 100 ms */ - Thread.sleep(100); - } - catch(InterruptedException e) - { - /* do nothing */ - } + data = ImageStreamingUtils.getImageBytes(scaledScreen, output); } + + screen = null; + scaledScreen = null; + return data; } }