Implement desktop streaming capture device as a PullBufferCaptureDevice (rather than a PushBuffer ones). Reduce memory allocation by refactoring screencapture JNI.

cusax-fix
Sebastien Vincent 16 years ago
parent 33374d3711
commit 6196c19805

@ -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 =

@ -14,6 +14,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
@ -22,6 +23,8 @@
#include <wingdi.h>
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;
}

@ -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
}

@ -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;

@ -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 <tt>BufferedImage</tt>. 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 <tt>BufferedImage</tt>. 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.
*

@ -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 <tt>BufferedImage</tt>. 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 <tt>BufferedImage</tt>. 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 <tt>java.awt.Robot</tt>.
*
* @return <tt>BufferedImage</tt> 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 <tt>java.awt.Robot</tt>.
*
* @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;
}

@ -60,10 +60,12 @@ public static BufferedImage getScaledImage(BufferedImage src,
* Get raw bytes from ARGB <tt>BufferedImage</tt>.
*
* @param src ARGB <BufferImage</tt>
* @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
* <tt>BufferedImage</tt>
* <tt>BufferedImage</tt>
*/
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++)

@ -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 <tt>BufferedImage</tt> 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[]);
}

@ -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 <tt>PullBufferDataSource</tt> and
* <tt>CaptureDevice</tt> 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 <tt>Logger</tt> used by the <tt>AbstractPullBufferCaptureDevice</tt>
* class and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(AbstractPullBufferCaptureDevice.class);
/**
* The value of the <tt>formatControls</tt> property of
* <tt>AbstractPullBufferCaptureDevice</tt> which represents an empty array
* of <tt>FormatControl</tt>s. Explicitly defined in order to reduce
* unnecessary allocations.
*/
protected static final FormatControl[] EMPTY_FORMAT_CONTROLS
= new FormatControl[0];
/**
* The value of the <tt>streams</tt> property of
* <tt>AbstractPullBufferCaptureDevice</tt> which represents an empty array
* of <tt>PullBufferStream</tt>s. 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 <tt>MediaLocator</tt> of this <tt>DataSource</tt> has
* been opened.
*/
private boolean connected = false;
/**
* The array of <tt>FormatControl</tt> instances each one of which can be
* used before {@link #connect()} to get and set the capture <tt>Format</tt>
* of each one of the capture streams.
*/
private FormatControl[] formatControls;
/**
* The indicator which determines whether the transfer of media data from
* this <tt>DataSource</tt> has been started.
*/
private boolean started = false;
/**
* The <tt>PullBufferStream</tt>s through which this
* <tt>PullBufferDataSource</tt> gives access to its media data.
* <p>
* Warning: Caution is advised when directly using the field and access to
* it is to be synchronized with sync root <tt>this</tt>.
* </p>
*/
protected AbstractPullBufferStream[] streams;
/**
* Initializes a new <tt>AbstractPullBufferCaptureDevice</tt> instance.
*/
protected AbstractPullBufferCaptureDevice()
{
}
/**
* Initializes a new <tt>AbstractPullBufferCaptureDevice</tt> instance from
* a specific <tt>MediaLocator</tt>.
*
* @param locator the <tt>MediaLocator</tt> to create the new instance from
*/
protected AbstractPullBufferCaptureDevice(MediaLocator locator)
{
setLocator(locator);
}
/**
* Opens a connection to the media source specified by the
* <tt>MediaLocator</tt> of this <tt>DataSource</tt>.
*
* @throws IOException if anything goes wrong while opening the connection
* to the media source specified by the <tt>MediaLocator</tt> of this
* <tt>DataSource</tt>
*/
public synchronized void connect()
throws IOException
{
if (!connected)
{
doConnect();
connected = true;
}
}
/**
* Creates a new <tt>FormatControl</tt> instance which is to be associated
* with a <tt>PullBufferStream</tt> at a specific zero-based index in the
* list of streams of this <tt>PullBufferDataSource</tt>. As the
* <tt>FormatControl</tt>s of a <tt>PullBufferDataSource</tt> can be
* requested before {@link #connect()}, its <tt>PullBufferStream</tt>s may
* not exist at the time of the request for the creation of the
* <tt>FormatControl</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* in the list of streams of this <tt>PullBufferDataSource</tt> which is to
* be associated with the new <tt>FormatControl</tt> instance
* @return a new <tt>FormatControl</tt> instance which is to be associated
* with a <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in
* the list of streams of this <tt>PullBufferDataSource</tt>
*/
protected FormatControl createFormatControl(final int streamIndex)
{
return
new AbstractFormatControl()
{
/**
* The <tt>Format</tt> of this <tt>FormatControl</tt> and,
* respectively, of the media data of its owner.
*/
private Format format;
/**
* Gets the <tt>Format</tt> of the media data of the owner of
* this <tt>FormatControl</tt>.
*
* @return the <tt>Format</tt> of the media data of the owner of
* this <tt>FormatControl</tt>
*/
public Format getFormat()
{
format
= AbstractPullBufferCaptureDevice.this
.internalGetFormat(streamIndex, format);
return format;
}
/**
* Gets the <tt>Format</tt>s in which the owner of this
* <tt>FormatControl</tt> is capable of providing media data.
*
* @return an array of <tt>Format</tt>s in which the owner of
* this <tt>FormatControl</tt> is capable of providing media
* data
*/
public Format[] getSupportedFormats()
{
return
AbstractPullBufferCaptureDevice.this
.getSupportedFormats(streamIndex);
}
/**
* Implements {@link FormatControl#setFormat(Format)}. Attempts
* to set the <tt>Format</tt> in which the owner of this
* <tt>FormatControl</tt> is to provide media data.
*
* @param format the <tt>Format</tt> to be set on this instance
* @return the currently set <tt>Format</tt> after the attempt
* to set it on this instance if <tt>format</tt> is supported by
* this instance and regardless of whether it was actually set;
* <tt>null</tt> if <tt>format</tt> 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 <tt>FormatControl</tt>s of this <tt>CaptureDevice</tt>.
*
* @return an array of the <tt>FormatControl</tt>s of this
* <tt>CaptureDevice</tt>
*/
protected FormatControl[] createFormatControls()
{
FormatControl formatControl = createFormatControl(0);
return
(formatControl == null)
? EMPTY_FORMAT_CONTROLS
: new FormatControl[] { formatControl };
}
/**
* Create a new <tt>PullBufferStream</tt> which is to be at a specific
* zero-based index in the list of streams of this
* <tt>PullBufferDataSource</tt>. The <tt>Format</tt>-related information of
* the new instance is to be abstracted by a specific
* <tt>FormatControl</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* in the list of streams of this <tt>PullBufferDataSource</tt>
* @param formatControl the <tt>FormatControl</tt> which is to abstract the
* <tt>Format</tt>-related information of the new instance
* @return a new <tt>PullBufferStream</tt> which is to be at the specified
* <tt>streamIndex</tt> in the list of streams of this
* <tt>PullBufferDataSource</tt> and which has its <tt>Format</tt>-related
* information abstracted by the specified <tt>formatControl</tt>
*/
protected abstract AbstractPullBufferStream createStream(
int streamIndex,
FormatControl formatControl);
/**
* Closes the connection to the media source specified by the
* <tt>MediaLocator</tt> of this <tt>DataSource</tt>. 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
* <tt>MediaLocator</tt> of this <tt>DataSource</tt>. 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 <tt>MediaLocator</tt> of this
* <tt>DataSource</tt>
*/
protected synchronized void doConnect()
throws IOException
{
}
/**
* Closes the connection to the media source specified by the
* <tt>MediaLocator</tt> of this <tt>DataSource</tt>. 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 <tt>DataSource</tt>. 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 <tt>DataSource</tt>
*/
protected synchronized void doStart()
throws IOException
{
if (streams != null)
for (AbstractPullBufferStream stream : streams)
stream.start();
}
/**
* Stops the transfer of media data from this <tt>DataSource</tt>. 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 <tt>DataSource</tt>
*/
protected synchronized void doStop()
throws IOException
{
if (streams != null)
for (AbstractPullBufferStream stream : streams)
stream.stop();
}
/**
* Gets the <tt>CaptureDeviceInfo</tt> of this <tt>CaptureDevice</tt> which
* describes it.
*
* @return the <tt>CaptureDeviceInfo</tt> of this <tt>CaptureDevice</tt>
* which describes it
*/
public CaptureDeviceInfo getCaptureDeviceInfo()
{
return getCaptureDeviceInfo(this);
}
/**
* Gets the <tt>CaptureDeviceInfo</tt> of a specific <tt>CaptureDevice</tt>
* by locating its registration in JMF using its <tt>MediaLocator</tt>.
*
* @param captureDevice the <tt>CaptureDevice</tt> to gets the
* <tt>CaptureDeviceInfo</tt> of
* @return the <tt>CaptureDeviceInfo</tt> of the specified
* <tt>CaptureDevice</tt> 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<CaptureDeviceInfo> captureDeviceInfos
= (Vector<CaptureDeviceInfo>)
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
* <tt>AbstractPullBufferCaptureDevice</tt> 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 <tt>Object</tt> which represents the control of the specified
* type available for this instance if such a control is indeed available;
* otherwise, <tt>null</tt>
*/
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 <tt>Object</tt>s 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
* <tt>AbstractPullBufferCaptureDevice</tt> always returns
* {@link #DURATION_UNBOUNDED}.
*
* @return the duration of the media represented by this instance
*/
public Time getDuration()
{
return DURATION_UNBOUNDED;
}
/**
* Gets the <tt>Format</tt> to be reported by the <tt>FormatControl</tt> of
* a <tt>PullBufferStream</tt> at a specific zero-based index in the list of
* streams of this <tt>PullBufferDataSource</tt>. The
* <tt>PullBufferStream</tt> may not exist at the time of requesting its
* <tt>Format</tt>. Allows extenders to override the default behavior which
* is to report any last-known format or the first <tt>Format</tt> from the
* list of supported formats as defined in the JMF registration of this
* <tt>CaptureDevice</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* the <tt>Format</tt> of which is to be retrieved
* @param oldValue the last-known <tt>Format</tt> for the
* <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* @return the <tt>Format</tt> to be reported by the <tt>FormatControl</tt>
* of the <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in
* the list of streams of this <tt>PullBufferDataSource</tt>.
*/
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 <tt>FormatControl</tt> instances each one of which can
* be used before {@link #connect()} to get and set the capture
* <tt>Format</tt> of each one of the capture streams.
*
* @return an array of <tt>FormatControl</tt> instances each one of which
* can be used before {@link #connect()} to get and set the capture
* <tt>Format</tt> of each one of the capture streams
*/
public FormatControl[] getFormatControls()
{
return AbstractFormatControl.getFormatControls(this);
}
/**
* Gets the <tt>PullBufferStream</tt>s through which this
* <tt>PullBufferDataSource</tt> gives access to its media data.
*
* @return an array of the <tt>PullBufferStream</tt>s through which this
* <tt>PullBufferDataSource</tt> 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 <tt>Format</tt>s which are to be reported by a
* <tt>FormatControl</tt> as supported formats for a
* <tt>PullBufferStream</tt> at a specific zero-based index in the list of
* streams of this <tt>PullBufferDataSource</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* for which the specified <tt>FormatControl</tt> is to report the list of
* supported <tt>Format</tt>s
* @return an array of <tt>Format</tt>s to be reported by a
* <tt>FormatControl</tt> as the supported formats for the
* <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in the
* list of streams of this <tt>PullBufferDataSource</tt>
*/
protected Format[] getSupportedFormats(int streamIndex)
{
CaptureDeviceInfo captureDeviceInfo = getCaptureDeviceInfo();
return
(captureDeviceInfo == null) ? null : captureDeviceInfo.getFormats();
}
/**
* Gets the <tt>Format</tt> to be reported by the <tt>FormatControl</tt> of
* a <tt>PullBufferStream</tt> at a specific zero-based index in the list of
* streams of this <tt>PullBufferDataSource</tt>. The
* <tt>PullBufferStream</tt> may not exist at the time of requesting its
* <tt>Format</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* the <tt>Format</tt> of which is to be retrieved
* @param oldValue the last-known <tt>Format</tt> for the
* <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* @return the <tt>Format</tt> to be reported by the <tt>FormatControl</tt>
* of the <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in
* the list of streams of this <tt>PullBufferDataSource</tt>.
*/
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 <tt>FormatControl</tt> instances each one of which can
* be used before {@link #connect()} to get and set the capture
* <tt>Format</tt> of each one of the capture streams.
*
* @return an array of <tt>FormatControl</tt> instances each one of which
* can be used before {@link #connect()} to get and set the capture
* <tt>Format</tt> of each one of the capture streams
*/
private synchronized FormatControl[] internalGetFormatControls()
{
if (formatControls == null)
formatControls = createFormatControls();
return formatControls;
}
/**
* Attempts to set the <tt>Format</tt> to be reported by the
* <tt>FormatControl</tt> of a <tt>PullBufferStream</tt> at a specific
* zero-based index in the list of streams of this
* <tt>PullBufferDataSource</tt>.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* the <tt>Format</tt> of which is to be set
* @param oldValue the last-known <tt>Format</tt> for the
* <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* @param newValue the <tt>Format</tt> which is to be set
* @return the <tt>Format</tt> to be reported by the <tt>FormatControl</tt>
* of the <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* in the list of streams of this <tt>PullBufferStream</tt> or <tt>null</tt>
* if the attempt to set the <tt>Format</tt> did not success and any
* last-known <tt>Format</tt> 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 <tt>Format</tt> to be reported by the
* <tt>FormatControl</tt> of a <tt>PullBufferStream</tt> at a specific
* zero-based index in the list of streams of this
* <tt>PullBufferDataSource</tt>. The <tt>PullBufferStream</tt> does not
* exist at the time of the attempt to set its <tt>Format</tt>. Allows
* extenders to override the default behavior which is to not attempt to set
* the specified <tt>Format</tt> so that they can enable setting the
* <tt>Format</tt> prior to creating the <tt>PullBufferStream</tt>. If
* setting the <tt>Format</tt> of an existing <tt>PullBufferStream</tt> is
* desired, <tt>AbstractPullBufferStream#doSetFormat(Format)</tt> should be
* overridden instead.
*
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* the <tt>Format</tt> of which is to be set
* @param oldValue the last-known <tt>Format</tt> for the
* <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* @param newValue the <tt>Format</tt> which is to be set
* @return the <tt>Format</tt> to be reported by the <tt>FormatControl</tt>
* of the <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt>
* in the list of streams of this <tt>PullBufferStream</tt> or <tt>null</tt>
* if the attempt to set the <tt>Format</tt> did not success and any
* last-known <tt>Format</tt> 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 <tt>DataSource</tt>
*
* @throws IOException if anything goes wrong while starting the transfer of
* media data from this <tt>DataSource</tt>
*/
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 <tt>DataSource</tt>.
*
* @throws IOException if anything goes wrong while stopping the transfer of
* media data from this <tt>DataSource</tt>
*/
public synchronized void stop()
throws IOException
{
if (started)
{
doStop();
started = false;
}
}
}

@ -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 <tt>PullBufferStream</tt> 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 <tt>Logger</tt> used by the <tt>AbstractPullBufferStream</tt> class
* and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(AbstractPullBufferStream.class);
/**
* The (default) <tt>ContentDescriptor</tt> of the
* <tt>AbstractPullBufferStream</tt> instances.
*/
private static final ContentDescriptor CONTENT_DESCRIPTOR
= new ContentDescriptor(ContentDescriptor.RAW);
/**
* The <tt>FormatControl</tt> which gives access to the <tt>Format</tt> of
* the media data provided by this <tt>SourceStream</tt> and which,
* optionally, allows setting it.
*/
private final FormatControl formatControl;
/**
* Initializes a new <tt>AbstractPullBufferStream</tt> instance which is to
* have its <tt>Format</tt>-related information abstracted by a specific
* <tt>FormatControl</tt>.
*
* @param formatControl the <tt>FormatControl</tt> which is to abstract the
* <tt>Format</tt>-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 <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
* known by it. Allows extenders to override the <tt>Format</tt> known to
* the <tt>PullBufferDataSource</tt> which created this instance and
* possibly provide more details on the currently set <tt>Format</tt>.
*
* @return the <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
* known by it or <tt>null</tt> if this <tt>PullBufferStream</tt> does not
* directly know its <tt>Format</tt> and it relies on the
* <tt>PullBufferDataSource</tt> which created it to report its
* <tt>Format</tt>
*/
protected Format doGetFormat()
{
return null;
}
/**
* Attempts to set the <tt>Format</tt> of this <tt>PullBufferStream</tt>.
* Allows extenders to enable setting the <tt>Format</tt> of an existing
* <tt>PullBufferStream</tt> (in contract to setting it before the
* <tt>PullBufferStream</tt> is created by the <tt>PullBufferDataSource</tt>
* which will provide it).
*
* @param format the <tt>Format</tt> to be set as the format of this
* <tt>PullBufferStream</tt>
* @return the <tt>Format</tt> of this <tt>PullBufferStream</tt> or
* <tt>null</tt> if the attempt to set the <tt>Format</tt> did not succeed
* and any last-known <tt>Format</tt> is to be left in effect
*/
protected Format doSetFormat(Format format)
{
return null;
}
/**
* Determines whether the end of this <tt>SourceStream</tt> has been
* reached. The <tt>AbstractPullBufferStream</tt> implementation always
* returns <tt>false</tt>.
*
* @return <tt>true</tt> if the end of this <tt>SourceStream</tt> has been
* reached; otherwise, <tt>false</tt>
*/
public boolean endOfStream()
{
return false;
}
/**
* Determines if read will block.
*
* @return <tt>true</tt> if read block, <tt>false</tt> otherwise
*/
public boolean willReadBlock()
{
return true;
}
/**
* Gets a <tt>ContentDescriptor</tt> which describes the type of the content
* made available by this <tt>SourceStream</tt>. The
* <tt>AbstractPullBufferStream</tt> implementation always returns a
* <tt>ContentDescriptor</tt> with content type equal to
* <tt>ContentDescriptor#RAW</tt>.
*
* @return a <tt>ContentDescriptor</tt> which describes the type of the
* content made available by this <tt>SourceStream</tt>
*/
public ContentDescriptor getContentDescriptor()
{
return CONTENT_DESCRIPTOR;
}
/**
* Gets the length in bytes of the content made available by this
* <tt>SourceStream</tt>. The <tt>AbstractPullBufferStream</tt>
* implementation always returns <tt>LENGTH_UNKNOWN</tt>.
*
* @return the length in bytes of the content made available by this
* <tt>SourceStream</tt> if it is known; otherwise, <tt>LENGTH_UKNOWN</tt>
*/
public long getContentLength()
{
return LENGTH_UNKNOWN;
}
/**
* Implements {@link Controls#getControls()}. Gets the controls available
* for this instance.
*
* @return an array of <tt>Object</tt>s 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 <tt>Format</tt> of the media data made available by this
* <tt>PullBufferStream</tt>.
*
* @return the <tt>Format</tt> of the media data made available by this
* <tt>PullBufferStream</tt>
*/
public Format getFormat()
{
return (formatControl == null) ? null : formatControl.getFormat();
}
/**
* Gets the <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
* known by it.
*
* @return the <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
* known by it
*/
Format internalGetFormat()
{
return doGetFormat();
}
/**
* Attempts to set the <tt>Format</tt> of this <tt>PullBufferStream</tt>.
*
* @param format the <tt>Format</tt> to be set as the format of this
* <tt>PullBufferStream</tt>
* @return the <tt>Format</tt> of this <tt>PullBufferStream</tt> or
* <tt>null</tt> if the attempt to set the <tt>Format</tt> did not succeed
* and any last-known <tt>Format</tt> is to be left in effect
*/
Format internalSetFormat(Format format)
{
return doSetFormat(format);
}
/**
* Starts the transfer of media data from this <tt>PullBufferStream</tt>.
*
* @throws IOException if anything goes wrong while starting the transfer of
* media data from this <tt>PullBufferStream</tt>
*/
public void start()
throws IOException
{
}
/**
* Stops the transfer of media data from this <tt>PullBufferStream</tt>.
*
* @throws IOException if anything goes wrong while stopping the transfer of
* media data from this <tt>PullBufferStream</tt>
*/
public void stop()
throws IOException
{
}
}

@ -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 <tt>PushBufferStream</tt> which is to be at a specific
* Create a new <tt>PullBufferStream</tt> which is to be at a specific
* zero-based index in the list of streams of this
* <tt>PushBufferDataSource</tt>. The <tt>Format</tt>-related information of
* <tt>PullBufferDataSource</tt>. The <tt>Format</tt>-related information of
* the new instance is to be abstracted by a specific
* <tt>FormatControl</tt>.
*
* @param streamIndex the zero-based index of the <tt>PushBufferStream</tt>
* in the list of streams of this <tt>PushBufferDataSource</tt>
* @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
* in the list of streams of this <tt>PullBufferDataSource</tt>
* @param formatControl the <tt>FormatControl</tt> which is to abstract the
* <tt>Format</tt>-related information of the new instance
* @return a new <tt>PushBufferStream</tt> which is to be at the specified
* @return a new <tt>PullBufferStream</tt> which is to be at the specified
* <tt>streamIndex</tt> in the list of streams of this
* <tt>PushBufferDataSource</tt> and which has its <tt>Format</tt>-related
* <tt>PullBufferDataSource</tt> and which has its <tt>Format</tt>-related
* information abstracted by the specified <tt>formatControl</tt>
* @see AbstractPushBufferCaptureDevice#createStream(int, FormatControl)
* @see AbstractPullBufferCaptureDevice#createStream(int, FormatControl)
*/
protected AbstractPushBufferStream createStream(
protected AbstractPullBufferStream createStream(
int streamIndex,
FormatControl formatControl)
{

@ -29,8 +29,7 @@
* @author Damian Minkov
*/
public class ImageStream
extends AbstractPushBufferStream
implements Runnable
extends AbstractPullBufferStream
{
/**
* The <tt>Logger</tt>
@ -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;
}
}

Loading…
Cancel
Save