diff --git a/deskshare/app/build.gradle b/deskshare/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..c28e270dbb19e6be2161cb29db2ed614c95ea742 --- /dev/null +++ b/deskshare/app/build.gradle @@ -0,0 +1,31 @@ +usePlugin 'java' +usePlugin 'war' + +version = '0.62' + +jar.enabled = true + +archivesBaseName = 'bbb-deskshare-app' + +dependencies { + providedCompile ':log4j-over-slf4j:1.5.6@jar', 'spring:spring-web:2.5.6@jar', 'javax.servlet:servlet-api:2.5@jar', 'org.apache.mina:mina-core:2.0.0-M6@jar' + providedCompile 'spring:spring-aop:2.5.6@jar', 'spring:spring-beans:2.5.6@jar', 'spring:spring-context:2.5.6@jar', 'spring:spring-core:2.5.6@jar' + providedCompile 'org/red5:red5:0.8@jar' + compile project(':common') + compile 'commons-fileupload:commons-fileupload:1.2.1@jar', 'commons-io:commons-io:1.4@jar' + compile ':logback-core:0.9.14@jar', ':logback-classic:0.9.14@jar', ':slf4j-api:1.5.6@jar' + compile 'spring:spring-webmvc:2.5.6@jar', 'org.apache.mina:mina-integration-spring:1.1.7@jar' +} + +war.doLast { + ant.unzip(src: war.archivePath, dest: "$buildDir/exploded") +} + +task deploy(dependsOn:war, type:Copy) { + def red5AppsDir = '/usr/share/red5/webapps' + def deskshareDir = new File("${red5AppsDir}/deskshare") + println "Deleting $deskshareDir" + ant.delete(dir: deskshareDir) + into(deskshareDir) + from "$buildDir/exploded" +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEndEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEndEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..19dad0488a62658d15ba1105ec4ea6d1e9ea232b --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEndEvent.java @@ -0,0 +1,40 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +public class CaptureEndEvent implements ICaptureEvent { + + private String room; + + public CaptureEndEvent(String room) { + this.room = room; + } + + public String getRoom() { + return room; + } + + public CaptureMessage getMessageType() { + return CaptureMessage.CAPTURE_END; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEvents.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEvents.java new file mode 100644 index 0000000000000000000000000000000000000000..bccb268aeb7438a14c6d26d21d4a9665835d34a8 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureEvents.java @@ -0,0 +1,50 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +public enum CaptureEvents { + CAPTURE_START(0), CAPTURE_UPDATE(1), CAPTURE_END(2); + + private final int event; + + CaptureEvents(int event) { + this.event = event; + } + + public int getEvent() { + return event; + } + + @Override + public String toString() { + switch (event) { + case 0: + return "Capture Start Event"; + case 1: + return "Capture Update Event"; + case 2: + return "Capture End Event"; + } + + return "Unknown Capture Event"; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureMessage.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..98a7671eced4e0daf5f61289b6c417bc99edac6d --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureMessage.java @@ -0,0 +1,28 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +public enum CaptureMessage { + CAPTURE_START, + CAPTURE_UPDATE, + CAPTURE_END +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureStartEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureStartEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..28e3089b59309c3edd67e9fa9db662914a6b60fb --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureStartEvent.java @@ -0,0 +1,52 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +public final class CaptureStartEvent implements ICaptureEvent { + + private final int width; + private final int height; + private final String room; + + public CaptureStartEvent(String room, int width, int height) { + this.room = room; + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public String getRoom() { + return room; + } + + public CaptureMessage getMessageType() { + return CaptureMessage.CAPTURE_START; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureUpdateEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureUpdateEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..6628e59e5ff16aeb358acb2bca5ba226b3f4a606 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CaptureUpdateEvent.java @@ -0,0 +1,58 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.apache.mina.core.buffer.IoBuffer; + +public class CaptureUpdateEvent implements ICaptureEvent { + + private final IoBuffer data; + private final String room; + private final long timestamp; + + public CaptureUpdateEvent(String room, IoBuffer data) { + + this.data = data; + this.room = room; + timestamp = System.currentTimeMillis(); + } + + public IoBuffer getData() { + return data; + } + + public String getRoom() { + return room; + } + + public CaptureMessage getMessageType() { + return CaptureMessage.CAPTURE_UPDATE; + } + + public static CaptureUpdateEvent copy(CaptureUpdateEvent event) { + return new CaptureUpdateEvent(event.getRoom(), event.getData()); + } + + public long getTimestamp() { + return timestamp; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CapturedScreen.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CapturedScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..ae25f5b8f0172c85b93a95e1391d753f81c42d19 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/CapturedScreen.java @@ -0,0 +1,67 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.awt.image.BufferedImage; + +public class CapturedScreen { + + private BufferedImage screen; + private String room; + + private int width; + private int height; + private int frameRate; + + public CapturedScreen(BufferedImage screen, String room, int width, + int height, int frameRate) { + super(); + this.screen = screen; + this.room = room; + this.width = width; + this.height = height; + this.frameRate = frameRate; + } + + + public BufferedImage getScreen() { + return screen; + } + + public String getRoom() { + return room; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getFrameRate() { + return frameRate; + } + + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareApplication.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..606b47cae50134521556f5df2e330f6bfae62f48 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareApplication.java @@ -0,0 +1,115 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.util.List; + +import org.bigbluebutton.deskshare.server.socket.DeskShareServer; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.adapter.MultiThreadedApplicationAdapter; +import org.red5.server.api.IBandwidthConfigure; +import org.red5.server.api.IConnection; +import org.red5.server.api.IScope; +import org.red5.server.api.Red5; +import org.red5.server.api.stream.IServerStream; +import org.red5.server.api.stream.IStreamCapableConnection; +import org.red5.server.api.stream.support.SimpleConnectionBWConfig; + +import org.slf4j.Logger; + +/** + * The Application class is the main class of the deskShare server side + * @author Snap + * + */ +public class DeskShareApplication extends MultiThreadedApplicationAdapter { + private static Logger log = Red5LoggerFactory.getLogger(DeskShareApplication.class, "deskshare"); + + private IScope appScope; + private IServerStream serverStream; + + private DeskShareServer deskShareServer; + + /** + * Runs when the application is first started. Sets up relevant objects to listen to the deskShare client + * and publish the stream on red5 + */ + public boolean appStart(IScope app){ + log.info("deskShare appStart"); + System.out.println("deskShare appStart"); + this.appScope = app; + + deskShareServer.start(); + + return true; + } + + public boolean appConnect(IConnection conn, Object[] params){ + log.info("deskShare appConnect to scope " + conn.getScope().getContext().toString()); + System.out.println("deskShare appConnect to scope " + conn.getScope().getContextPath()); + measureBandwidth(conn); + + if (conn instanceof IStreamCapableConnection){ + IStreamCapableConnection streamConn = (IStreamCapableConnection) conn; + SimpleConnectionBWConfig bwConfig = new SimpleConnectionBWConfig(); + bwConfig.getChannelBandwidth()[IBandwidthConfigure.OVERALL_CHANNEL] = + 1024*1024; + bwConfig.getChannelInitialBurst()[IBandwidthConfigure.OVERALL_CHANNEL] = + 128*1024; + streamConn.setBandwidthConfigure(bwConfig); + } + + return super.appConnect(conn, params); + } + + public void appDisconnect(IConnection conn){ + log.info("deskShare appDisconnect"); + if (getAppScope() == conn.getScope() && serverStream != null){ + serverStream.close(); + } + super.appDisconnect(conn); + } + + public List<String> getStreams(){ + IConnection conn = Red5.getConnectionLocal(); + return getBroadcastStreamNames(conn.getScope()); + } + + /** + * @return the appScope + */ + public IScope getAppScope() { + return appScope; + } + + /** + * Called when the application is stopped + */ + public void appStop(){ + deskShareServer.stop(); + } + + public void setDeskShareServer(DeskShareServer deskShareServer) { + this.deskShareServer = deskShareServer; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareConstants.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..f9ac14f814f8b7591ddac742bb14d29bb0ba15d1 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareConstants.java @@ -0,0 +1,36 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +/** + * The constants for this application + * @author Snap + * + */ +public class DeskShareConstants { + public static final int PORT = 1026; + + public static final int BIT_RATE = 25000; + public static final int BIT_RATE_TOLERANCE = 50000; + public static final int NUM_PICTURES_IN_GROUP = 1; + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareService.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareService.java new file mode 100644 index 0000000000000000000000000000000000000000..2e4b49a9fad8afc3efbb446c478acad381ea0733 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/DeskShareService.java @@ -0,0 +1,59 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.Red5; +import org.slf4j.Logger; + +public class DeskShareService { + final private Logger log = Red5LoggerFactory.getLogger(DeskShareService.class, "deskshare"); + + private FrameStreamerGateway sg; + + public boolean checkIfStreamIsPublishing(){ + String roomName = Red5.getConnectionLocal().getScope().getName(); + log.debug("Checking if {} is streaming.", roomName); + System.out.println("Checking if " + roomName + " is streaming."); + boolean streaming = sg.isStreaming(roomName); + if (streaming) { + System.out.println(roomName + " is streaming."); + } + return streaming; + } + + public int getVideoWidth(){ + String roomName = Red5.getConnectionLocal().getScope().getName(); + log.debug("Getting video width for {}.", roomName); + return sg.getRoomVideoWidth(roomName); + } + + public int getVideoHeight(){ + String roomName = Red5.getConnectionLocal().getScope().getName(); + log.debug("Getting video height for {}.", roomName); + return sg.getRoomVideoHeight(roomName); + } + + public void setStreamerGateway(FrameStreamerGateway sg) { + this.sg = sg; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamFactory.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..48caaef4ab245673c3228e5067f0145fc8825abf --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamFactory.java @@ -0,0 +1,65 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.bigbluebutton.deskshare.server.streamer.DeskShareStream; +import org.bigbluebutton.deskshare.server.streamer.FlvStreamToFile; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.slf4j.Logger; + +public class FrameStreamFactory { + final private Logger log = Red5LoggerFactory.getLogger(FrameStreamFactory.class, "deskshare"); + + private DeskShareApplication app; + + public IDeskShareStream createStream(String room, int screenWidth, int screenHeight) { +// int frameRate = event.getFrameRate(); + +// IScope scope = app.getAppScope(); + + IScope scope = app.getAppScope().getScope(room); +/* + if (scope == null) { + if (app.getAppScope().createChildScope("testroom")) { + log.debug("create child scope testroom"); + scope = app.getAppScope().getScope("testroom"); + } else { + log.warn("Failed to create child scope testroom"); + } + } +*/ +// log.debug("Created stream {}", scope.getName()); + return new DeskShareStream(scope, room, screenWidth, screenHeight); + //return new FlvStreamToFile(); + } + + public void setDeskShareApplication(DeskShareApplication app) { + this.app = app; + if (app == null) { + log.error("DeskShareApplication is NULL!!!"); + } else { + log.debug("DeskShareApplication is NOT NULL!!!"); + } + + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamerGateway.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamerGateway.java new file mode 100644 index 0000000000000000000000000000000000000000..ce65d52e71c97b9e1f057378a42bdc973ca871c8 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/FrameStreamerGateway.java @@ -0,0 +1,105 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.bigbluebutton.deskshare.server.session.ScreenVideoFrame; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.so.ISharedObject; +import org.slf4j.Logger; + +public class FrameStreamerGateway { + final private Logger log = Red5LoggerFactory.getLogger(FrameStreamerGateway.class, "deskshare"); + + private final Map<String, IDeskShareStream> streamsMap; + private FrameStreamFactory streamFactory; + private DeskShareApplication deskShareApp; + + public FrameStreamerGateway() { + streamsMap = new ConcurrentHashMap<String, IDeskShareStream>(); + } + + public void createNewStream(String room, int screenWidth, int screenHeight) { + log.debug("Creating stream " + room); + System.out.println("FrameStreamerGateway Creating stream " + room); + IDeskShareStream stream = streamFactory.createStream(room, screenWidth, screenHeight); + + streamsMap.put(room, stream); + stream.start(); + + //notify the clients in the room that the stream has now started broadcasting. + ISharedObject deskSO = deskShareApp.getSharedObject(stream.getScope(), "deskSO"); + deskSO.sendMessage("appletStarted" , new ArrayList<Object>()); + + } + + public void onCaptureEndEvent(String room) { + IDeskShareStream ds = streamsMap.remove(room); + if (ds != null) { + ds.stop(); + ds = null; + } + } + + public void onCaptureEvent(ScreenVideoFrame event) { +// System.out.println("FrameStreamerGateway onCaptureEvent"); + IDeskShareStream ds = streamsMap.get(event.getRoom()); + if (ds != null) { + CaptureUpdateEvent cue = new CaptureUpdateEvent(event.getRoom(), event.getVideoData()); +// System.out.println("FrameStreamerGateway passing to IDeskShareStream"); + ds.accept(cue); + } else { + System.out.println("FrameStreamerGateway: could not find DeskShareStream " + event.getRoom()); + } + } + + public boolean isStreaming(String room){ + return streamsMap.containsKey(room); + } + + public int getRoomVideoWidth(String room){ + IDeskShareStream ds = streamsMap.get(room); + if (ds != null) { + return ds.getWidth(); + } + return 0; + } + + public int getRoomVideoHeight(String room){ + IDeskShareStream ds = streamsMap.get(room); + if (ds != null) { + return ds.getHeight(); + } + return 0; + } + + public void setStreamFactory(FrameStreamFactory sf) { + this.streamFactory = sf; + } + + public void setDeskShareApplication(DeskShareApplication app) { + this.deskShareApp = app; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ICaptureEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ICaptureEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..bab56622e7417160ab151e71345c5b79eff641be --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ICaptureEvent.java @@ -0,0 +1,27 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +public interface ICaptureEvent { + + public CaptureMessage getMessageType(); +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/IDeskShareStream.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/IDeskShareStream.java new file mode 100644 index 0000000000000000000000000000000000000000..0eedb40f342fc33376db7eb908a238a519573647 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/IDeskShareStream.java @@ -0,0 +1,50 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.bigbluebutton.deskshare.server.session.ScreenVideoFrame; +import org.red5.server.api.IScope; + + + +/** + * The DeskShareStream class publishes captured video to a red5 stream. + * @author Snap + * + */ +public interface IDeskShareStream { + + public void stop(); + + public void start(); + + public void accept(CaptureUpdateEvent event); + + public void stream(ScreenVideoFrame frame); + + public int getWidth(); + + public int getHeight(); + + public IScope getScope(); + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/NewScreenListener.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/NewScreenListener.java new file mode 100644 index 0000000000000000000000000000000000000000..8a0d986eabfd1241ee8af13e0b2f38d0bbe3a6d4 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/NewScreenListener.java @@ -0,0 +1,29 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.awt.image.BufferedImage; + +public interface NewScreenListener { + + public void onNewScreen(BufferedImage newScreen); +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideo.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideo.java new file mode 100644 index 0000000000000000000000000000000000000000..5bb9f8e247d1c586b37394cb5ba5dc4328fa9457 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideo.java @@ -0,0 +1,79 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.apache.mina.core.buffer.IoBuffer; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.stream.IVideoStreamCodec; +import org.slf4j.Logger; + +public class ScreenVideo implements IVideoStreamCodec { + + private Logger log = Red5LoggerFactory.getLogger(ScreenVideo.class, "deskshare"); + static final String CODEC_NAME = "ScreenVideo"; + static final byte FLV_FRAME_KEY = 0x10; + static final byte FLV_CODEC_SCREEN = 0x03; + private IoBuffer data; + + public ScreenVideo() { + this.reset(); + } + + public String getName() { + return CODEC_NAME; + } + + public void reset() { + + } + + public boolean canHandleData(IoBuffer data) { + byte first = data.get(); + boolean result = ((first & 0x0f) == FLV_CODEC_SCREEN); + data.rewind(); + return result; + } + + public boolean canDropFrames() { + return true; + } + + public boolean addData(IoBuffer data) { + if (!this.canHandleData(data)) { + log.warn("Cannot handle data"); + return false; + } + this.data = data; + + log.debug("Adding data " + data.remaining()); + + this.data.rewind(); + return true; + } + + public IoBuffer getKeyframe() { + log.debug("getting keyFrame"); + return data; + } + +} + diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideoBroadcastStream.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideoBroadcastStream.java new file mode 100644 index 0000000000000000000000000000000000000000..a2bd51a741f01c8bc57ccc3af36c6da7456633d9 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/ScreenVideoBroadcastStream.java @@ -0,0 +1,273 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.io.IOException; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.event.IEvent; +import org.red5.server.api.stream.IBroadcastStream; +import org.red5.server.api.stream.IStreamCodecInfo; +import org.red5.server.api.stream.IStreamListener; +import org.red5.server.api.stream.IVideoStreamCodec; +import org.red5.server.api.stream.ResourceExistException; +import org.red5.server.api.stream.ResourceNotFoundException; +import org.red5.server.messaging.IMessageComponent; +import org.red5.server.messaging.IPipe; +import org.red5.server.messaging.IPipeConnectionListener; +import org.red5.server.messaging.IProvider; +import org.red5.server.messaging.OOBControlMessage; +import org.red5.server.messaging.PipeConnectionEvent; +import org.red5.server.net.rtmp.event.IRTMPEvent; +import org.red5.server.net.rtmp.event.VideoData; +import org.bigbluebutton.deskshare.server.ScreenVideo; +import org.red5.server.stream.codec.StreamCodecInfo; +import org.red5.server.stream.message.RTMPMessage; + +import org.slf4j.Logger; + +import org.red5.server.api.stream.IStreamPacket;; + +public class ScreenVideoBroadcastStream implements IBroadcastStream, IProvider, IPipeConnectionListener +{ + /** Listeners to get notified about received packets. */ + private Set<IStreamListener> streamListeners = new CopyOnWriteArraySet<IStreamListener>(); + final private Logger log = Red5LoggerFactory.getLogger(ScreenVideoBroadcastStream.class, "deskshare"); + + private String publishedStreamName; + private IPipe livePipe; + private IScope mScope; + + // Codec handling stuff for frame dropping + private StreamCodecInfo streamCodecInfo; + private Long mCreationTime; + + public ScreenVideoBroadcastStream(String name) + { + publishedStreamName = name; + livePipe = null; + log.trace("name: {}", name); + + // we want to create a video codec when we get our + // first video packet. + streamCodecInfo = new StreamCodecInfo(); + mCreationTime = null; + } + + public IProvider getProvider() + { + log.trace("getProvider()"); + return this; + } + + public String getPublishedName() + { + log.trace("getPublishedName()"); + return publishedStreamName; + } + + public String getSaveFilename() + { + log.trace("getSaveFilename()"); + throw new Error("unimplemented method"); + } + + public void addStreamListener(IStreamListener listener) + { + log.trace("addStreamListener(listener: {})", listener); + streamListeners.add(listener); + } + + public Collection<IStreamListener> getStreamListeners() + { + log.trace("getStreamListeners()"); + return streamListeners; + } + + public void removeStreamListener(IStreamListener listener) + { + log.trace("removeStreamListener({})", listener); + streamListeners.remove(listener); + } + + public void saveAs(String filePath, boolean isAppend) throws IOException, + ResourceNotFoundException, ResourceExistException + { + log.trace("saveAs(filepath:{}, isAppend:{})", filePath, isAppend); + throw new Error("unimplemented method"); + } + + public void setPublishedName(String name) + { + log.trace("setPublishedName(name:{})", name); + publishedStreamName = name; + } + + public void close() + { + log.trace("close()"); + } + + public IStreamCodecInfo getCodecInfo() + { + log.trace("getCodecInfo()"); + // we don't support this right now. + return streamCodecInfo; + } + + public String getName() + { + log.trace("getName(): {}", publishedStreamName); + // for now, just return the published name + return publishedStreamName; + } + + public void setScope(IScope scope) + { + mScope = scope; + } + + public IScope getScope() + { + log.trace("getScope(): {}", mScope); + return mScope; + } + + public void start() + { + log.trace("start()"); + } + + public void stop() + { + log.trace("stop"); + } + + public void onOOBControlMessage(IMessageComponent source, IPipe pipe, + OOBControlMessage oobCtrlMsg) + { + log.trace("onOOBControlMessage"); + } + + public void onPipeConnectionEvent(PipeConnectionEvent event) + { + log.trace("onPipeConnectionEvent(event:{})", event); + switch (event.getType()) + { + case PipeConnectionEvent.PROVIDER_CONNECT_PUSH: + log.trace("PipeConnectionEvent.PROVIDER_CONNECT_PUSH"); + if (event.getProvider() == this + && (event.getParamMap() == null || !event.getParamMap() + .containsKey("record"))) + { + log.trace("Creating a live pipe"); + System.out.println("Creating a live pipe"); + this.livePipe = (IPipe) event.getSource(); + } + break; + case PipeConnectionEvent.PROVIDER_DISCONNECT: + log.trace("PipeConnectionEvent.PROVIDER_DISCONNECT"); + if (this.livePipe == event.getSource()) + { + log.trace("PipeConnectionEvent.PROVIDER_DISCONNECT - this.mLivePipe = null;"); + System.out.println("PipeConnectionEvent.PROVIDER_DISCONNECT - this.mLivePipe = null;"); + this.livePipe = null; + } + break; + case PipeConnectionEvent.CONSUMER_CONNECT_PUSH: + log.trace("PipeConnectionEvent.CONSUMER_CONNECT_PUSH"); + System.out.println("PipeConnectionEvent.CONSUMER_CONNECT_PUSH"); + break; + case PipeConnectionEvent.CONSUMER_DISCONNECT: + log.trace("PipeConnectionEvent.CONSUMER_DISCONNECT"); + System.out.println("PipeConnectionEvent.CONSUMER_DISCONNECT"); + break; + default: + log.trace("PipeConnectionEvent default"); + System.out.println("PipeConnectionEvent default"); + break; + } + } + + public void dispatchEvent(IEvent event) + { + try { + log.trace("dispatchEvent(event:{})", event); + if (event instanceof IRTMPEvent) + { + IRTMPEvent rtmpEvent = (IRTMPEvent) event; + if (livePipe != null) + { + RTMPMessage msg = new RTMPMessage(); + + msg.setBody(rtmpEvent); + + if (mCreationTime == null) + mCreationTime = (long)rtmpEvent.getTimestamp(); + + try + { + IVideoStreamCodec videoStreamCodec = new ScreenVideo(); + streamCodecInfo.setHasVideo(true); + streamCodecInfo.setVideoCodec(videoStreamCodec); + videoStreamCodec.reset(); + videoStreamCodec.addData(((VideoData) rtmpEvent).getData()); + livePipe.pushMessage(msg); + + // Notify listeners about received packet + if (rtmpEvent instanceof IStreamPacket) + { + for (IStreamListener listener : getStreamListeners()) + { + try + { + listener.packetReceived(this, (IStreamPacket) rtmpEvent); + } + catch (Exception e) + { + log.error("Error while notifying listener " + listener, e); + } + } + } + + } + catch (IOException ex) + { + // ignore + log.error("Got exception: {}", ex); + } + } + } + } finally { + + } + } + + public long getCreationTime() + { + return mCreationTime != null ? mCreationTime : 0L; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamFactory.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..584ecdd14e27e57492b424d9cff6bff7dcbc009b --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamFactory.java @@ -0,0 +1,68 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import org.bigbluebutton.deskshare.server.streamer.DeskShareStream; +import org.bigbluebutton.deskshare.server.streamer.FlvStreamToFile; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.slf4j.Logger; + +public class StreamFactory { + final private Logger log = Red5LoggerFactory.getLogger(StreamFactory.class, "deskshare"); + + private DeskShareApplication app; + + public IDeskShareStream createStream(CaptureStartEvent event) { + String room = event.getRoom(); + int width = event.getWidth(); + int height = event.getHeight(); +// int frameRate = event.getFrameRate(); + +// IScope scope = app.getAppScope(); + + IScope scope = app.getAppScope().getScope(room); +/* + if (scope == null) { + if (app.getAppScope().createChildScope("testroom")) { + log.debug("create child scope testroom"); + scope = app.getAppScope().getScope("testroom"); + } else { + log.warn("Failed to create child scope testroom"); + } + } +*/ +// log.debug("Created stream {}", scope.getName()); + return new DeskShareStream(scope, room, width, height); + //return new FlvStreamToFile(); + } + + public void setDeskShareApplication(DeskShareApplication app) { + this.app = app; + if (app == null) { + log.error("DeskShareApplication is NULL!!!"); + } else { + log.debug("DeskShareApplication is NOT NULL!!!"); + } + + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamerGateway.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamerGateway.java new file mode 100644 index 0000000000000000000000000000000000000000..0b26beefc2c3a1fe318222831732c159467cab6c --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/StreamerGateway.java @@ -0,0 +1,103 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.so.ISharedObject; +import org.slf4j.Logger; + +public class StreamerGateway { + final private Logger log = Red5LoggerFactory.getLogger(StreamerGateway.class, "deskshare"); + + private final Map<String, IDeskShareStream> streamsMap; + private StreamFactory streamFactory; + private DeskShareApplication deskShareApp; + + public StreamerGateway() { + streamsMap = new ConcurrentHashMap<String, IDeskShareStream>(); + } + + public void onCaptureStartEvent(CaptureStartEvent event) { + log.debug("Creating stream " + event.getRoom()); + + IDeskShareStream stream = streamFactory.createStream(event); + + streamsMap.put(event.getRoom(), stream); + stream.start(); + + //notify the clients in the room that the stream has now started broadcasting. + ISharedObject deskSO = deskShareApp.getSharedObject(stream.getScope(), "deskSO"); + deskSO.sendMessage("appletStarted" , new ArrayList<Object>()); + + } + + public void onCaptureEndEvent(CaptureEndEvent event) { + IDeskShareStream ds = streamsMap.remove(event.getRoom()); + if (ds != null) { + ds.stop(); + ds = null; + } + } + + public void onCaptureEvent(CaptureUpdateEvent event) { + IDeskShareStream ds = streamsMap.get(event.getRoom()); + if (ds != null) { + ds.accept(event); + } + } + + public boolean isStreaming(String room){ + IDeskShareStream ds = streamsMap.get(room); + if (ds != null) { + return true; + } + return false; + } + + public int getRoomVideoWidth(String room){ + IDeskShareStream ds = streamsMap.get(room); + if (ds != null) { + return ds.getWidth(); + } + return 0; + } + + public int getRoomVideoHeight(String room){ + IDeskShareStream ds = streamsMap.get(room); + if (ds != null) { + return ds.getHeight(); + } + return 0; + } + + public void setStreamFactory(StreamFactory sf) { + this.streamFactory = sf; + } + + public void setDeskShareApplication(DeskShareApplication app) { + this.deskShareApp = app; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureEndBlockEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureEndBlockEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..0f33035e8200118a685708b9842ac9c1111d7aa4 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureEndBlockEvent.java @@ -0,0 +1,35 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.events; + +public class CaptureEndBlockEvent { + + private final String room; + + public CaptureEndBlockEvent(String room) { + this.room = room; + } + + public String getRoom() { + return room; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureStartBlockEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureStartBlockEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..6310e457ca078d587c3104cfe0c1726541433cc5 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureStartBlockEvent.java @@ -0,0 +1,49 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.events; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class CaptureStartBlockEvent { + + private final String room; + private final Dimension screenDim; + private final Dimension blockDim; + + public CaptureStartBlockEvent(String room, Dimension screen, Dimension block) { + this.room = room; + screenDim = screen; + blockDim = block; + } + + public Dimension getScreenDimension() { + return screenDim; + } + + public Dimension getBlockDimension() { + return blockDim; + } + + public String getRoom() { + return room; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureUpdateBlockEvent.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureUpdateBlockEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..fb85f6c6dd4256175fbace4d36744a106191aac9 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/events/CaptureUpdateBlockEvent.java @@ -0,0 +1,53 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.events; + +public class CaptureUpdateBlockEvent { + private final String room; + private final int position; + private final byte[] videoData; + private final boolean isKeyFrame; + + public CaptureUpdateBlockEvent(String room, int position, byte[] videoData, boolean isKeyFrame) { + this.room = room; + this.position = position; + this.videoData = videoData; + this.isKeyFrame = isKeyFrame; + } + + public String getRoom() { + return room; + } + + public int getPosition() { + return position; + } + + public byte[] getVideoData() { + return videoData; + } + + public boolean isKeyFrame() { + return isKeyFrame; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/HttpTunnelStreamController.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/HttpTunnelStreamController.java new file mode 100644 index 0000000000000000000000000000000000000000..485af8f544386d99df99e01a3d63c97dfdad7ce9 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/HttpTunnelStreamController.java @@ -0,0 +1,133 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.servlet; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.bigbluebutton.deskshare.common.Dimension; +import org.bigbluebutton.deskshare.server.CaptureEvents; +import org.bigbluebutton.deskshare.server.session.SessionManager; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.multiaction.MultiActionController; + +public class HttpTunnelStreamController extends MultiActionController { + private static Logger log = Red5LoggerFactory.getLogger(HttpTunnelStreamController.class, "deskshare"); + + private boolean hasSessionManager = false; + private SessionManager sessionManager; + + public ModelAndView screenCaptureHandler(HttpServletRequest request, HttpServletResponse response) throws Exception { + + String event = request.getParameterValues("event")[0]; + int captureRequest = Integer.parseInt(event); + + if (CaptureEvents.CAPTURE_START.getEvent() == captureRequest) { + handleCaptureStartRequest(request, response); + } else if (CaptureEvents.CAPTURE_UPDATE.getEvent() == captureRequest) { + handleCaptureUpdateRequest(request, response); + } else if (CaptureEvents.CAPTURE_END.getEvent() == captureRequest) { + handleCaptureEndRequest(request, response); + } else { + log.debug("Cannot handle screen capture event " + captureRequest); + System.out.println("Cannot handle screen capture event " + captureRequest); + } + return null; + } + + private void handleCaptureStartRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + String room = request.getParameterValues("room")[0]; + String screenInfo = request.getParameterValues("screenInfo")[0]; + String blockInfo = request.getParameterValues("blockInfo")[0]; + + String[] screen = screenInfo.split("x"); + String[] block = blockInfo.split("x"); + + Dimension screenDim, blockDim; + screenDim = new Dimension(Integer.parseInt(screen[0]), Integer.parseInt(screen[1])); + blockDim = new Dimension(Integer.parseInt(block[0]), Integer.parseInt(block[1])); + + if (! hasSessionManager) { + sessionManager = getSessionManager(); + hasSessionManager = true; + } + + sessionManager.createSession(room, screenDim, blockDim); + } + + private void handleCaptureUpdateRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; + // MultipartFile is a copy of file in memory, not in file system + MultipartFile multipartFile = multipartRequest.getFile("blockdata"); + + long startRx = System.currentTimeMillis(); + + byte[] blockData = multipartFile.getBytes(); + String room = request.getParameterValues("room")[0]; + String keyframe = request.getParameterValues("keyframe")[0]; + String position = request.getParameterValues("position")[0]; + + if (! hasSessionManager) { + sessionManager = getSessionManager(); + hasSessionManager = true; + } + + sessionManager.updateBlock(room, Integer.valueOf(position), blockData, Boolean.parseBoolean(keyframe)); + + long completeRx = System.currentTimeMillis(); +// System.out.println("Http receive and send took " + (completeRx - startRx) + "ms."); + } + + private void handleCaptureEndRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + String room = request.getParameterValues("room")[0]; + if (! hasSessionManager) { + sessionManager = getSessionManager(); + hasSessionManager = true; + } + System.out.println("HttpTunnel: Received Capture Enfd Event."); + sessionManager.removeSession(room); + } + + private SessionManager getSessionManager() { + //Get the servlet context + ServletContext ctx = getServletContext(); + log.info("Got the servlet context: {}", ctx); + + //Grab a reference to the application context + ApplicationContext appCtx = (ApplicationContext) ctx.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + log.info("Got the application context: {}", appCtx); + + //Get the bean holding the parameter + SessionManager manager = (SessionManager) appCtx.getBean("sessionManager"); + if (manager != null) { + log.info("Got the SessionManager context: {}", appCtx); + } + return manager; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/TunnelController.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/TunnelController.java new file mode 100644 index 0000000000000000000000000000000000000000..4eb0ebde4a4ab6dd474bbbbac113c4e8a335f952 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/servlet/TunnelController.java @@ -0,0 +1,161 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.servlet; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import javax.imageio.ImageIO; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.deskshare.server.CaptureEndEvent; +import org.bigbluebutton.deskshare.server.CaptureEvents; +import org.bigbluebutton.deskshare.server.CaptureStartEvent; +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.CapturedScreen; +import org.bigbluebutton.deskshare.server.StreamerGateway; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.multiaction.MultiActionController; + +public class TunnelController extends MultiActionController { + private static Logger log = Red5LoggerFactory.getLogger(TunnelController.class, "deskshare"); + + private StreamerGateway streamerGateway; + private boolean hasGateway = false; + + public ModelAndView screenCaptureHandler(HttpServletRequest request, HttpServletResponse response) throws Exception { + + String event = request.getParameterValues("event")[0]; + System.out.println("Got screenCaptureStart request for event " + event); + + int captureRequest = Integer.valueOf(request.getParameterValues("event")[0]).intValue(); + + if (CaptureEvents.CAPTURE_START.getEvent() == captureRequest) { + handleCaptureStartRequest(request, response); + } else if (CaptureEvents.CAPTURE_UPDATE.getEvent() == captureRequest) { + handleCaptureUpdateRequest(request, response); + } else if (CaptureEvents.CAPTURE_END.getEvent() == captureRequest) { + handleCaptureEndRequest(request, response); + } else { + log.debug("Cannot handle screen capture event " + captureRequest); + System.out.println("Cannot handle screen capture event " + captureRequest); + } + return null; + } + + private void handleCaptureStartRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + String room = request.getParameterValues("room")[0]; + log.warn("Got screenCaptureStart request for room {}", room); + + String videoInfo = request.getParameterValues("videoInfo")[0]; + log.warn("Received image from videoInfo {}", videoInfo); + + String[] screenDimensions = videoInfo.split("x"); + int width = Integer.parseInt(screenDimensions[0]); + int height = Integer.parseInt(screenDimensions[1]); + + if (! hasGateway) { + streamerGateway = getStreamerGateway(); + hasGateway = true; + } + + CaptureStartEvent event = new CaptureStartEvent(room, width, height); + streamerGateway.onCaptureStartEvent(event); + } + + private void handleCaptureUpdateRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; + // MultipartFile is a copy of file in memory, not in file system + MultipartFile multipartFile = multipartRequest.getFile("videodata"); + + long startRx = System.currentTimeMillis(); + + byte[] videoData = multipartFile.getBytes(); + IoBuffer buffer = IoBuffer.allocate(videoData.length, false); + buffer.put(videoData); + + /* Set the marker back to zero position so that "gets" start from the beginning. + * Otherwise, you get BufferUnderFlowException. + */ + buffer.rewind(); + + String room = request.getParameterValues("room")[0]; + log.debug("Received image from room {}", room); + String keyframe = request.getParameterValues("keyframe")[0]; + log.debug("Received keyframe {}", keyframe); + + System.out.println("Room " + room + " keyFrame " + Boolean.parseBoolean(keyframe) + + " data size " + videoData.length + " buffer size " + buffer.remaining()); + CaptureUpdateEvent cse = new CaptureUpdateEvent(room, buffer); + + if (! hasGateway) { + streamerGateway = getStreamerGateway(); + hasGateway = true; + } + + streamerGateway.onCaptureEvent(cse); + long completeRx = System.currentTimeMillis(); + System.out.println("Http receive and send took " + (completeRx - startRx) + "ms."); + } + + private void handleCaptureEndRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + String room = request.getParameterValues("room")[0]; + log.warn("Got screenCaptureEnd request for room {}", room); + + if (! hasGateway) { + streamerGateway = getStreamerGateway(); + hasGateway = true; + } + + CaptureEndEvent event = new CaptureEndEvent(room); + + streamerGateway.onCaptureEndEvent(event); + + } + + private StreamerGateway getStreamerGateway() { + //Get the servlet context + ServletContext ctx = getServletContext(); + log.info("Got the servlet context: {}", ctx); + + //Grab a reference to the application context + ApplicationContext appCtx = (ApplicationContext) ctx.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + log.info("Got the application context: {}", appCtx); + + //Get the bean holding the parameter + StreamerGateway streamerGateway = (StreamerGateway) appCtx.getBean("streamerGateway"); + if (streamerGateway != null) { + log.info("Got the streamerGateway context: {}", appCtx); + } + return streamerGateway; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Block.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Block.java new file mode 100644 index 0000000000000000000000000000000000000000..cd99a6568bbfa50a278ab74ab77fbf8f2ba48117 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Block.java @@ -0,0 +1,93 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.util.Random; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class Block { + Random random = new Random(); + private final Dimension dim; + private final int position; + + private boolean isKeyFrame = false; + private byte[] encodedBlock; + private int[] pixels; + + private boolean encoded = false; + private boolean hasChanged = false; + private static int FORCE_UPDATE_DURATION = 10000; + private int nextForceUpdate = 10000; + private static int MIN_DURATION = 5000; + private static int MAX_DURATION = 10000; + private long lastChanged; + + Block(Dimension dim, int position) { + this.dim = dim; + this.position = position; + } + + public synchronized void update(byte[] videoData, boolean isKeyFrame) + { + this.isKeyFrame = isKeyFrame; + encodedBlock = videoData; + hasChanged = true; + } + + public synchronized byte[] getEncodedBlock() { + hasChanged = false; + return encodedBlock; + } + + public Dimension getDimension() { + return dim; + } + + public boolean isKeyFrame() { + return isKeyFrame; + } + + public boolean hasChanged() { + return (hasChanged || forceUpdate()); + } + + private boolean forceUpdate() { + long now = System.currentTimeMillis(); + boolean update = ((now - lastChanged) > nextForceUpdate); + if (update) { + synchronized(this) { + nextUpdate(); + lastChanged = now; + } + } + return update; + } + + private void nextUpdate() { + //get the range, casting to long to avoid overflow problems + long range = (long)MAX_DURATION - (long)MIN_DURATION + 1; + // compute a fraction of the range, 0 <= frac < range + long fraction = (long)(range * random.nextDouble()); + nextForceUpdate = (int)(fraction + 5000); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockFactory.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..d9645c5c5e1c5fc3ac8a96836ee2f2cd1f8e8825 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockFactory.java @@ -0,0 +1,108 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class BlockFactory { + + public static int getNumberOfColumns(Dimension screenDim, Dimension blockDim) { + int columns = screenDim.getWidth() / blockDim.getWidth(); + if (hasPartialColumnBlock(screenDim, blockDim)) { + columns += 1; + } + return columns; + } + + private static boolean hasPartialColumnBlock(Dimension screenDim, Dimension blockDim) { + return (screenDim.getWidth() % blockDim.getWidth()) != 0; + } + + public static int getNumberOfRows(Dimension screenDim, Dimension blockDim) { + int rows = screenDim.getHeight() / blockDim.getHeight(); + if (hasPartialRowBlock(screenDim, blockDim)) { + rows += 1; + } + return rows; + } + + private static boolean hasPartialRowBlock(Dimension screenDim, Dimension blockDim) { + return (screenDim.getHeight() % blockDim.getHeight()) != 0; + } + + public static Block createBlock(Dimension screenDim, Dimension blockDim, int position) { + int numRows = getNumberOfRows(screenDim, blockDim); + int numColumns = getNumberOfColumns(screenDim, blockDim); + + int col = computeColumn(position, numColumns); + int row = computeRow(position, numRows, numColumns); + int w = computeTileWidth(col, screenDim, blockDim); + int h = computeTileHeight(row, screenDim, blockDim); + + Block t = new Block(new Dimension(w, h), position); + + return t; + } + + private static int computeRow(int position, int numRows, int numColumns) { + return -(position - (numRows * numColumns)) / numColumns; + } + + private static int computeColumn(int position, int numColumns) { + return (position - 1) % numColumns; + } + + private static int computeTileWidth(int col, Dimension screenDim, Dimension blockDim) { + int numColumns = getNumberOfColumns(screenDim, blockDim); + if (isLastColumnTile(col, numColumns)) { + if (hasPartialColumnBlock(screenDim, blockDim)) { + return partialTileWidth(screenDim, blockDim); + } + } + return blockDim.getWidth(); + } + + private static int partialTileWidth(Dimension screenDim, Dimension blockDim) { + return screenDim.getWidth() % blockDim.getWidth(); + } + + private static int computeTileHeight(int row, Dimension screenDim, Dimension blockDim) { + if (isTopRowTile(row)) { + if (hasPartialRowBlock(screenDim, blockDim)) { + return partialTileHeight(screenDim, blockDim); + } + } + return blockDim.getWidth(); + } + + private static int partialTileHeight(Dimension screenDim, Dimension blockDim) { + return screenDim.getHeight() % blockDim.getHeight(); + } + + private static boolean isLastColumnTile(int col, int numColumns) { + return ((col+1) % numColumns) == 0; + } + + private static boolean isTopRowTile(int row) { + return (row == 0); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockManager.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockManager.java new file mode 100644 index 0000000000000000000000000000000000000000..8fcbdf6d25b13f8662c58fea187499c87193d99e --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/BlockManager.java @@ -0,0 +1,134 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.bigbluebutton.deskshare.common.ScreenVideoEncoder; +import org.bigbluebutton.deskshare.common.Dimension; + +public class BlockManager { + + private final ConcurrentMap<Integer, Block> blocksMap; + + private final Dimension screenDim; + private final Dimension blockDim; + private final String room; + + private final int numberOfRows; + private final int numberOfColumns; + private long lastFrameTime = 0; + private long lastKeyFrameTime = 0; + private final static int KEYFRAME_INTERVAL = 20000; + private ByteArrayOutputStream screenVideoFrame; + + private FrameStreamer streamer; + + public BlockManager(String room, Dimension screen, Dimension block, FrameStreamer streamer) { + screenDim = screen; + blockDim = block; + this.room = room; + + blocksMap = new ConcurrentHashMap<Integer, Block>(); + + numberOfRows = BlockFactory.getNumberOfRows(screen, block); + numberOfColumns = BlockFactory.getNumberOfColumns(screen, block); + + screenVideoFrame = new ByteArrayOutputStream(); + + this.streamer = streamer; + } + + public void initialize() { + System.out.println("Initialize BlockManager"); + int numberOfBlocks = numberOfRows * numberOfColumns; + for (int position = 1; position <= numberOfBlocks; position++) { + Block block = BlockFactory.createBlock(screenDim, blockDim, position); + initializeBlock(block); + blocksMap.put(new Integer(position), block); + } + generateFrame(true); + } + + private void initializeBlock(Block block) { + Dimension dim = block.getDimension(); + int width = dim.getWidth(); + int height = dim.getHeight(); + int[] blankPixels = new int[width * height]; + for (int i = 0; i < blankPixels.length; i++) { + blankPixels[i] = 0xFFFF; + } + byte[] encodedPixels = ScreenVideoEncoder.encodePixels(blankPixels, width, height, false, true); + block.update(encodedPixels, true); + } + + public void updateBlock(int position, byte[] videoData, boolean keyFrame) { + Block block = blocksMap.get(new Integer(position)); + block.update(videoData, keyFrame); + long now = System.currentTimeMillis(); + if ((now - lastFrameTime) > 100) { + lastFrameTime = now; +// if ((now-lastKeyFrameTime) > KEYFRAME_INTERVAL){ +// generateFrame(true); +// } else { + // generateFrame(false); + // } + generateFrame(false); + } + } + + private void generateFrame(boolean genKeyFrame) { +// System.out.println("Generating frame " + genKeyFrame); + Map<Integer, Block> blocks = Collections.unmodifiableMap(new HashMap<Integer, Block>(blocksMap)); + + screenVideoFrame.reset(); + byte[] encodedDim = ScreenVideoEncoder.encodeBlockAndScreenDimensions(blockDim.getWidth(), screenDim.getWidth(), blockDim.getHeight(), screenDim.getHeight()); + + try { + int numberOfBlocks = numberOfRows * numberOfColumns; + byte videoDataHeader = ScreenVideoEncoder.encodeFlvVideoDataHeader(genKeyFrame); + + screenVideoFrame.write(videoDataHeader); + screenVideoFrame.write(encodedDim); + + for (int position = 1; position <= numberOfBlocks; position++) { + Block block = (Block) blocks.get(new Integer(position)); + byte[] encodedBlock = ScreenVideoEncoder.encodeBlockUnchanged(); + if (block.hasChanged() || genKeyFrame) { + encodedBlock = block.getEncodedBlock(); + } + screenVideoFrame.write(encodedBlock, 0, encodedBlock.length); + } + ScreenVideoFrame frame = new ScreenVideoFrame(room, screenVideoFrame); + streamer.sendFrame(frame); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvEncodeException.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvEncodeException.java new file mode 100644 index 0000000000000000000000000000000000000000..3ace8d69dd34848cce51c9f297f642df0bdfa5e6 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvEncodeException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +public class FlvEncodeException extends Exception { + + private static final long serialVersionUID = -3377474098350652011L; + + public FlvEncodeException(String message) { + super(message); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvStreamToFile.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvStreamToFile.java new file mode 100644 index 0000000000000000000000000000000000000000..ccaffb302cf88864ab1e485f7e0176fa9d94ea90 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FlvStreamToFile.java @@ -0,0 +1,112 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +public class FlvStreamToFile { + + private BlockingQueue<ScreenVideoFrame> screenQueue = new LinkedBlockingQueue<ScreenVideoFrame>(); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private FileOutputStream fo; + private ScreenVideoFlvEncoder svf = new ScreenVideoFlvEncoder(); + + private String flvFilename = "/tmp/screenvideostream.flv"; + + public void accept(ScreenVideoFrame frame) { + try { + screenQueue.put(frame); + } catch (InterruptedException e) { + System.out.println("InterruptedException while putting event into queue."); + } + } + + public void start() { + try { + fo = new FileOutputStream(flvFilename); + fo.write(svf.encodeHeader()); + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + sendCapturedScreen = true; + System.out.println("Starting stream"); + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + System.out.println("ScreenQueue size " + screenQueue.size()); + ScreenVideoFrame newScreen = screenQueue.take(); + sendCapturedScreen(newScreen); + } catch (InterruptedException e) { + System.out.println("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + } + + private void sendCapturedScreen(ScreenVideoFrame event) { +// System.out.println("ENABLE FlvStreamToFile:sendCapturedScreen"); + + try { + byte[] data = svf.encodeFlvData(event.getVideoData().array()); + System.out.println("Saving video data with length " + data.length); + fo.write(data); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (FlvEncodeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + public void stop() { + try { + System.out.println("Closing stream"); + fo.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void setFlvFilename(String filename) { + flvFilename = filename; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FrameStreamer.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FrameStreamer.java new file mode 100644 index 0000000000000000000000000000000000000000..cf48b9bc282c1e103a3e3ccd9df354a31d2426de --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/FrameStreamer.java @@ -0,0 +1,88 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.server.FrameStreamerGateway; + +public class FrameStreamer { + private BlockingQueue<ScreenVideoFrame> screenQ = new LinkedBlockingQueue<ScreenVideoFrame>(10); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private FrameStreamerGateway gateway; + + public void start() { + System.out.println("Starting FrameStreamer "); + sendCapturedScreen = true; + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + ScreenVideoFrame videoFrame = screenQ.take(); + if (screenQ.size() > 9) screenQ.clear(); + streamScreenVideoFrame(videoFrame); + + } catch (InterruptedException e) { + System.out.println("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + } + + public void sendFrame(ScreenVideoFrame frame) { + + try { + screenQ.put(frame); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void streamScreenVideoFrame(ScreenVideoFrame frame) { + //flvStreamer.accept(frame); + gateway.onCaptureEvent(frame); + } + + public void createNewStream(String room, int screenWidth, int screenHeight) { + System.out.println("Creating new Stream " + room); + gateway.createNewStream(room, screenWidth, screenHeight); + } + + public void endStream(String room) { + System.out.println("Stopping streamer"); + gateway.onCaptureEndEvent(room); + } + + public void setFrameStreamerGateway(FrameStreamerGateway gateway) { + System.out.println("Setting FrameStreamerGateway"); + this.gateway = gateway; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFlvEncoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFlvEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..9d28feaee6ff131e6245be108882230679fc079f --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFlvEncoder.java @@ -0,0 +1,106 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class ScreenVideoFlvEncoder { + private final static byte[] flvHeader = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09}; + private final static byte[] videoTagType = {0x09}; + private final static byte[] streamId = {0, 0, 0}; + private long startTimestamp = 0; + private boolean firstTag = true; + + private static byte FLV_TAG_HEADER_SIZE = 11; + + ByteArrayOutputStream flvDataStream = new ByteArrayOutputStream(); + + public byte[] encodeHeader() { + byte[] prevTagSize = encodePreviousTagSize(0); + byte[] header = new byte[flvHeader.length + prevTagSize.length]; + + System.arraycopy(flvHeader, 0, header, 0, flvHeader.length); + System.arraycopy(prevTagSize, 0, header, flvHeader.length, prevTagSize.length); + return header; + } + + private byte[] encodePreviousTagSize(long previousTagSize) { + int byte1 = (int)previousTagSize >> 24; + int byte2 = (int)previousTagSize >> 16; + int byte3 = (int)previousTagSize >> 8; + int byte4 = (int)previousTagSize & 0xff; + + return new byte[] {(byte)byte1, (byte)byte2, (byte)byte3, (byte)byte4}; + } + + public byte[] encodeFlvData (byte[] screenVideoData) throws FlvEncodeException { + + byte[] flvData; + try { + flvData = encodeFlvTag(screenVideoData); + } catch (IOException e) { + throw new FlvEncodeException("Failed to encode FLV data."); + } + return flvData; + } + + private byte[] encodeFlvTag(byte[] videoData) throws IOException { + + flvDataStream.reset(); + + flvDataStream.write(videoTagType); + flvDataStream.write(encodeDataSize(videoData.length)); + flvDataStream.write(encodeTimestamp()); + flvDataStream.write(streamId); + flvDataStream.write(videoData); + flvDataStream.write(encodePreviousTagSize(FLV_TAG_HEADER_SIZE + videoData.length)); + + return flvDataStream.toByteArray(); + } + + private byte[] encodeDataSize(int size) { + int byte1 = (size >> 16); + int byte2 = (size >> 8); + int byte3 = (size & 0x0ff); + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3}; + } + + private byte[] encodeTimestamp() { + long now = System.currentTimeMillis(); + + if (firstTag) { + startTimestamp = now; + firstTag = false; + } + + long elapsed = now - startTimestamp; + + int byte1 = (int)(elapsed & 0xff0000) >> 16; + int byte2 = (int)(elapsed & 0xff00) >> 8; + int byte3 = (int)(elapsed & 0xff); + int tsExtended = ((int)elapsed & 0xff000000) >> 24; + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3, (byte) tsExtended}; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFrame.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..92396fbc48e309a44af679c01e15a7329377f7e0 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/ScreenVideoFrame.java @@ -0,0 +1,55 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.io.ByteArrayOutputStream; + +import org.apache.mina.core.buffer.IoBuffer; + +public class ScreenVideoFrame { + + private final String room; + private final ByteArrayOutputStream encodedData; + + public ScreenVideoFrame(String room, ByteArrayOutputStream encodedData) { + this.room = room; + this.encodedData = encodedData; + } + + public String getRoom() { + return room; + } + + public IoBuffer getVideoData() { + byte[] data = (byte[]) encodedData.toByteArray(); + + IoBuffer buffer = IoBuffer.allocate(data.length, false); + buffer.put(data); + + /* Set the marker back to zero position so that "gets" start from the beginning. + * Otherwise, you get BufferUnderFlowException. + */ + buffer.rewind(); + return buffer; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Session.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Session.java new file mode 100644 index 0000000000000000000000000000000000000000..59f865a89f921747b83ee7de73a66b9d2755eeb7 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/Session.java @@ -0,0 +1,39 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class Session { + private BlockManager blockManager; + public Session(String room, Dimension screen, Dimension block, FrameStreamer streamer) { + blockManager = new BlockManager(room, screen, block, streamer); + } + + public void initialize() { + blockManager.initialize(); + } + public void updateBlock(int position, byte[] videoData, boolean keyFrame) { + blockManager.updateBlock(position, videoData, keyFrame); + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/SessionManager.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/SessionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..b9c85a007eb4569cecabae35e87d484b4440cac3 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/session/SessionManager.java @@ -0,0 +1,68 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.session; + +import java.util.concurrent.ConcurrentHashMap; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class SessionManager { + + private final ConcurrentHashMap<String, Session> sessions; + private FrameStreamer frameStreamer; + + public SessionManager() { + sessions = new ConcurrentHashMap<String, Session>(); + } + + public synchronized void createSession(String room, Dimension screen, Dimension block) { + if (! sessions.containsKey(room)) { + System.out.println("Created session " + room); + frameStreamer.createNewStream(room, screen.getWidth(), screen.getHeight()); + Session session = new Session(room, screen, block, frameStreamer); + if (sessions.putIfAbsent(room, session) == null) { + // Successfully inserted session. I.e. no previous session. + session.initialize(); + } + } else { + System.out.println("Session already exist for " + room); + } + } + + public synchronized void removeSession(String room) { + System.out.println("Removing session " + room); + sessions.remove(room); + frameStreamer.endStream(room); + } + + public void updateBlock(String room, int position, byte[] blockData, boolean keyframe) { + Session session = sessions.get(room); + if (session != null) + session.updateBlock(position, blockData, keyframe); + } + + public void setFrameStreamer(FrameStreamer streamer) { + System.out.println("Setting FrameStreamer"); + frameStreamer = streamer; + frameStreamer.start(); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamEventMessageHandler.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamEventMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..317aef0768960d39c0df34bf9422c74eec64b4e6 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamEventMessageHandler.java @@ -0,0 +1,91 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.bigbluebutton.deskshare.server.events.CaptureEndBlockEvent; +import org.bigbluebutton.deskshare.server.events.CaptureStartBlockEvent; +import org.bigbluebutton.deskshare.server.events.CaptureUpdateBlockEvent; +import org.bigbluebutton.deskshare.server.session.SessionManager; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +public class BlockStreamEventMessageHandler extends IoHandlerAdapter { + final private Logger log = Red5LoggerFactory.getLogger(BlockStreamEventMessageHandler.class, "deskshare"); + + private SessionManager sessionManager; + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) throws Exception + { + log.warn(cause.toString() + " \n " + cause.getMessage()); + cause.printStackTrace(); + } + + @Override + public void messageReceived( IoSession session, Object message ) throws Exception + { + if (message instanceof CaptureStartBlockEvent) { + System.out.println("Got CaptureStartBlockEvent"); + CaptureStartBlockEvent event = (CaptureStartBlockEvent) message; + sessionManager.createSession(event.getRoom(), event.getScreenDimension(), event.getBlockDimension()); + } else if (message instanceof CaptureUpdateBlockEvent) { +// System.out.println("Got CaptureUpdateBlockEvent"); + CaptureUpdateBlockEvent event = (CaptureUpdateBlockEvent) message; + sessionManager.updateBlock(event.getRoom(), event.getPosition(), event.getVideoData(), event.isKeyFrame()); + } else if (message instanceof CaptureEndBlockEvent) { + CaptureEndBlockEvent event = (CaptureEndBlockEvent) message; + sessionManager.removeSession(event.getRoom()); + } + } + + @Override + public void sessionIdle( IoSession session, IdleStatus status ) throws Exception + { + log.debug( "IDLE " + session.getIdleCount( status )); + } + + @Override + public void sessionCreated(IoSession session) throws Exception { + log.debug("Session Created"); + } + + @Override + public void sessionOpened(IoSession session) throws Exception { + log.debug("Session Opened."); + } + + @Override + public void sessionClosed(IoSession session) throws Exception { + log.debug("Session Closed."); + + String room = (String) session.getAttribute("ROOM"); + System.out.println("Session Closed for room " + room); + sessionManager.removeSession(room); + } + + public void setSessionManager(SessionManager sm) { + sessionManager = sm; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..99c7ce9f7691b29fc56dacaa5106272da10c1afb --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java @@ -0,0 +1,151 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.CumulativeProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.bigbluebutton.deskshare.common.Dimension; +import org.bigbluebutton.deskshare.server.CaptureStartEvent; +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.events.CaptureEndBlockEvent; +import org.bigbluebutton.deskshare.server.events.CaptureStartBlockEvent; +import org.bigbluebutton.deskshare.server.events.CaptureUpdateBlockEvent; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +public class BlockStreamProtocolDecoder extends CumulativeProtocolDecoder { + final private Logger log = Red5LoggerFactory.getLogger(BlockStreamProtocolDecoder.class, "deskshare"); + + private static final String ROOM = "ROOM"; + + private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'D', 'S'}; + private static final byte CAPTURE_START_EVENT = 0; + private static final byte CAPTURE_UPDATE_EVENT = 1; + private static final byte CAPTURE_END_EVENT = 2; + + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + + // Let's work with a buffer that contains header and the message length, + // ten (10) should be enough since header (6-bytes) plus length (4-bytes) + if (in.remaining() < 10) return false; + + byte[] header = new byte[HEADER.length]; + + int start = in.position(); + + in.get(header, 0, HEADER.length); + int messageLength = in.getInt(); +// System.out.println("Message Length " + messageLength); + if (in.remaining() < messageLength) { + in.position(start); + return false; + } + + decodeMessage(session, in, out); + + return true; + } + + private void decodeMessage(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + byte event = in.get(); + switch (event) { + case CAPTURE_START_EVENT: + System.out.println("Decoding CAPTURE_START_EVENT"); + decodeCaptureStartEvent(session, in, out); + break; + case CAPTURE_UPDATE_EVENT: +// System.out.println("Decoding CAPTURE_UPDATE_EVENT"); + decodeCaptureUpdateEvent(session, in, out); + break; + case CAPTURE_END_EVENT: + log.warn("Got CAPTURE_END_EVENT event: " + event); + System.out.println("Got CAPTURE_END_EVENT event: " + event); + decodeCaptureEndEvent(session, in, out); + break; + default: + log.error("Unknown event: " + event); + } + } + + private void decodeCaptureEndEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + String room = decodeRoom(session, in); + + if (! room.isEmpty()) { + CaptureEndBlockEvent event = new CaptureEndBlockEvent(room); + out.write(event); + } else { + System.out.println("Room is empty."); + } + } + + private void decodeCaptureStartEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + String room = decodeRoom(session, in); + + Dimension blockDim = decodeDimension(in); + Dimension screenDim = decodeDimension(in); + System.out.println("Block dim [" + blockDim.getWidth() + "," + blockDim.getHeight() + "]"); + System.out.println("Screen dim [" + screenDim.getWidth() + "," + screenDim.getHeight() + "]"); + + CaptureStartBlockEvent event = new CaptureStartBlockEvent(room, + screenDim, blockDim); + out.write(event); + } + + private Dimension decodeDimension(IoBuffer in) { + int width = in.getInt(); + int height = in.getInt(); + return new Dimension(width, height); + } + + private String decodeRoom(IoSession session, IoBuffer in) { + int roomLength = in.get(); +// System.out.println("Room length = " + roomLength); + String room = ""; + try { + room = in.getString(roomLength, Charset.forName( "UTF-8" ).newDecoder()); + session.setAttribute(ROOM, room); + } catch (CharacterCodingException e) { + e.printStackTrace(); + } + + return room; + } + + private void decodeCaptureUpdateEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + String room = decodeRoom(session, in); + int position = in.getShort(); + boolean isKeyFrame = (in.get() == 1) ? true : false; + int length = in.getInt(); + byte[] data = new byte[length]; + in.get(data, 0, length); + +// System.out.println("position=[" + position + "] keyframe=" + isKeyFrame + " length= " + length); + + CaptureUpdateBlockEvent event = new CaptureUpdateBlockEvent(room, position, data, isKeyFrame); + out.write(event); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DebugScreenCaptureMessageHandler.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DebugScreenCaptureMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..cecd4d2f61cd5fe95cb3532e03e720442717b921 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DebugScreenCaptureMessageHandler.java @@ -0,0 +1,98 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.bigbluebutton.deskshare.server.CaptureEndEvent; +import org.bigbluebutton.deskshare.server.CaptureStartEvent; +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.CapturedScreen; +import org.bigbluebutton.deskshare.server.StreamerGateway; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +public class DebugScreenCaptureMessageHandler extends IoHandlerAdapter { + final private Logger log = Red5LoggerFactory.getLogger(DebugScreenCaptureMessageHandler.class, "deskshare"); + + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) throws Exception + { + log.warn(cause.toString()); + cause.printStackTrace(); + } + + @Override + public void messageReceived( IoSession session, Object message ) throws Exception + { + log.debug("Message Received " + (String) message); +/* + CapturedScreen cs = (CapturedScreen) message; + String room = cs.getRoom(); + log.debug("Got room {}", room); + + if (session.containsAttribute("room")) { + sendCaptureEvent(cs); + } else { + session.setAttribute("room", room); + sendCaptureStartEvent(cs); + } +*/ + } + + @Override + public void sessionIdle( IoSession session, IdleStatus status ) throws Exception + { + log.debug( "IDLE " + session.getIdleCount( status )); + } + + @Override + public void sessionCreated(IoSession session) throws Exception { + log.debug("Session Created"); + } + + @Override + public void sessionOpened(IoSession session) throws Exception { + log.debug("Session Opened."); + } + + @Override + public void sessionClosed(IoSession session) throws Exception { + log.debug("Session Closed."); + String room = (String) session.getAttribute("room"); + sendCaptureEndEvent(room); + } + + private void sendCaptureEndEvent(String room) { + + } + + private void sendCaptureEvent(CapturedScreen cs) { + + } + + private void sendCaptureStartEvent(CapturedScreen cs) { + + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServer.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServer.java new file mode 100644 index 0000000000000000000000000000000000000000..28761109f6e0c30334e0d8a67b8b113bb299c6ab --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServer.java @@ -0,0 +1,66 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + + +public class DeskShareServer { + final private Logger log = Red5LoggerFactory.getLogger(DeskShareServer.class, "deskshare"); + + private static final int PORT = 9123; + + private IoHandlerAdapter screenCaptureHandler; + private NioSocketAcceptor acceptor; + + public void start() + { + acceptor = new NioSocketAcceptor(); + acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(new ScreenCaptureProtocolCodecFactory())); + + acceptor.setHandler( screenCaptureHandler); + acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 ); + acceptor.setReuseAddress(true); + try { + acceptor.bind( new InetSocketAddress(PORT) ); + } catch (IOException e) { + log.error("IOException while binding to port {}", PORT); + } + } + + public void setScreenCaptureHandler(IoHandlerAdapter screenCaptureHandler) { + this.screenCaptureHandler = screenCaptureHandler; + } + + public void stop() { + acceptor.unbind(); + acceptor.dispose(); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServerMain.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServerMain.java new file mode 100644 index 0000000000000000000000000000000000000000..021705fcd23671e3ef39644235c488a0fa46216f --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/DeskShareServerMain.java @@ -0,0 +1,36 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +public class DeskShareServerMain { + + + public static void main(String[] args) { + DeskShareServer server = new DeskShareServer(); + + server.setScreenCaptureHandler(new DebugScreenCaptureMessageHandler()); + server.start(); + System.out.println("Server starting"); + while (true) {} + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/NullProtocolEncoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/NullProtocolEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..dcf805078b58aa9053ce041170f095b651f9f99a --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/NullProtocolEncoder.java @@ -0,0 +1,41 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; + +public class NullProtocolEncoder implements ProtocolEncoder { + + public void dispose(IoSession in) throws Exception { + // TODO Auto-generated method stub + + } + + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) + throws Exception { + // TODO Auto-generated method stub + + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureMessageHandler.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..c08a33bf955e5a7768d89c91d6a92c5f501380eb --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureMessageHandler.java @@ -0,0 +1,100 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.bigbluebutton.deskshare.server.CaptureEndEvent; +import org.bigbluebutton.deskshare.server.CaptureMessage; +import org.bigbluebutton.deskshare.server.CaptureStartEvent; +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.ICaptureEvent; +import org.bigbluebutton.deskshare.server.StreamerGateway; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +public class ScreenCaptureMessageHandler extends IoHandlerAdapter { + final private Logger log = Red5LoggerFactory.getLogger(ScreenCaptureMessageHandler.class, "deskshare"); + + private StreamerGateway streamerGateway; + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) throws Exception + { + log.warn(cause.toString() + " \n " + cause.getMessage()); + cause.printStackTrace(); + } + + @Override + public void messageReceived( IoSession session, Object message ) throws Exception + { + CaptureMessage msgType = ((ICaptureEvent) message).getMessageType(); + + if (msgType == CaptureMessage.CAPTURE_START) { + log.debug("Received CAPTURE_START"); + sendCaptureStartEvent((CaptureStartEvent) message); + } else if (msgType == CaptureMessage.CAPTURE_UPDATE) { + // log.debug("Received CAPTURE_UPDATE"); + sendCaptureUpdateEvent((CaptureUpdateEvent) message); + } + } + + @Override + public void sessionIdle( IoSession session, IdleStatus status ) throws Exception + { + log.debug( "IDLE " + session.getIdleCount( status )); + } + + @Override + public void sessionCreated(IoSession session) throws Exception { + log.debug("Session Created"); + } + + @Override + public void sessionOpened(IoSession session) throws Exception { + log.debug("Session Opened."); + } + + @Override + public void sessionClosed(IoSession session) throws Exception { + log.debug("Session Closed."); + String room = (String) session.getAttribute("ROOM"); + sendCaptureEndEvent(room); + } + + private void sendCaptureEndEvent(String room) { + streamerGateway.onCaptureEndEvent(new CaptureEndEvent(room)); + } + + private void sendCaptureUpdateEvent(CaptureUpdateEvent event) { + streamerGateway.onCaptureEvent(event); + } + + private void sendCaptureStartEvent(CaptureStartEvent event) { + streamerGateway.onCaptureStartEvent(event); + } + + public void setStreamerGateway(StreamerGateway sg) { + streamerGateway = sg; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolCodecFactory.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolCodecFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..158a6ec4d35d3be9cb7ad5412c13e218c5681c77 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolCodecFactory.java @@ -0,0 +1,46 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFactory; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; + +public class ScreenCaptureProtocolCodecFactory implements ProtocolCodecFactory { + private ProtocolEncoder encoder; + private ProtocolDecoder decoder; + + public ScreenCaptureProtocolCodecFactory() { + encoder = new NullProtocolEncoder(); + //decoder = new ScreenCaptureProtocolDecoder(); + decoder = new BlockStreamProtocolDecoder(); + } + + public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception { + return encoder; + } + + public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception { + return decoder; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolDecoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..5df06afd29215f72b818effa5f49d2ca0e413135 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/ScreenCaptureProtocolDecoder.java @@ -0,0 +1,233 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.socket; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; + +import javax.imageio.ImageIO; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.CumulativeProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.bigbluebutton.deskshare.server.CaptureStartEvent; +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +public class ScreenCaptureProtocolDecoder extends CumulativeProtocolDecoder { + final private Logger log = Red5LoggerFactory.getLogger(ScreenCaptureProtocolDecoder.class, "deskshare"); + + private static final String ROOM = "ROOM"; + private static final String VIDEO_INFO = "VIDEO_INFO"; + + private static final byte CAPTURE_START_EVENT = 0; + private static final byte CAPTURE_UPDATE_EVENT = 1; + private static final byte CAPTURE_END_EVENT = 2; + + private static final String EVENT_TYPE = "EVENT_TYPE"; + private static final String IS_KEY_FRAME = "IS_KEY_FRAME"; + private static final String ENCODED_DATA = "ENCODED_DATA"; + + private static final int HEADER_LENGTH = 6; + + private long receiveStart; + + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + + if (session.containsAttribute(EVENT_TYPE)) { + + Integer eventType = (Integer) session.getAttribute(EVENT_TYPE); + + boolean receiveDone = false; + + // Now we know which event we are dealing with. + switch (eventType.intValue()) { + case CAPTURE_START_EVENT: + receiveDone = decodeCaptureStartEvent(session, in, out); + break; + case CAPTURE_UPDATE_EVENT: + receiveDone = decodeCaptureUpdateEvent(session, in, out); + break; + case CAPTURE_END_EVENT: + // decodeCaptureEnd(session, in, out); + break; + } + + if (receiveDone) { + long receiveComplete = System.currentTimeMillis(); + System.out.println("Receive took " + (receiveComplete - receiveStart) + "ms."); + } + + } else { + // Let's work with a buffer that contains header and the event and room + // ten (10) should be enough since header plus event is 8 bytes + if (in.remaining() < 10) return false; + + byte[] header = new byte[HEADER_LENGTH]; + in.get(header, 0, HEADER_LENGTH); + int event = in.get(); + System.out.println("Received " + header + " type " + event); + session.setAttribute(EVENT_TYPE, new Integer(event)); + receiveStart = System.currentTimeMillis(); + return true; + + } + return false; + } + + private boolean decodeCaptureStartEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + if (session.containsAttribute(ROOM)) { + if (decodeVideoInfo(session, in)) { + sendCaptureStartMessage(session, out); + return true; + } + } else { + decodeRoom(session, in); + } + return false; + } + + private void decodeRoom(IoSession session, IoBuffer in) { + if (in.remaining() < 200) return; + + int len = in.getInt(); + String room = decodeString(len, in); + if (room != "") { + System.out.println("Decoded ROOM " + room); + session.setAttribute(ROOM, room); + } + } + + private boolean decodeVideoInfo(IoSession session, IoBuffer in) { + if (in.remaining() < 200) return false; + + int len = in.getInt(); + String videoInfo = decodeString(len, in); + if (videoInfo != "") { + System.out.println("Decoded VIDEO_INFO " + videoInfo); + session.setAttribute(VIDEO_INFO, videoInfo); + return true; + } + return false; + } + + private String decodeString(int length, IoBuffer in) { + try { + if (in.remaining() >= length) { + return in.getString(length, Charset.forName( "UTF-8" ).newDecoder()); + } + } catch (CharacterCodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return ""; + } + + private void sendCaptureStartMessage(IoSession session, ProtocolDecoderOutput out) { + String room = (String) session.getAttribute(ROOM); + String videoInfo = (String) session.getAttribute(VIDEO_INFO); + log.debug("Room " + room + " videoInfo " + videoInfo); + System.out.println("Room " + room + " videoInfo " + videoInfo); + //Get the screen dimensions, i.e. the resolution of the video we need to create + String[] screenDimensions = videoInfo.split("x"); + int width = Integer.parseInt(screenDimensions[0]); + int height = Integer.parseInt(screenDimensions[1]); + + CaptureStartEvent cse = new CaptureStartEvent(room, width, height); + out.write(cse); + reset(session); + } + + private boolean decodeCaptureUpdateEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) { + if (session.containsAttribute(ROOM)) { + if (decodeScreenVideoData(session, in)) { + sendCaptureUpdateEvent(session, out); + return true; + } + } else { + decodeRoom(session, in); + } + return false; + } + + private boolean decodeScreenVideoData(IoSession session, IoBuffer in) { + int start = in.position(); + + byte isKeyFrame = in.get(); + int length = in.getInt(); + + if (in.remaining() < length) { + in.position(start); + return false; + } + + byte[] data = new byte[length]; + + in.get(data, 0, length); + + boolean keyFrame = false; + + if (isKeyFrame == 1) { + keyFrame = true; + } + + session.setAttribute(IS_KEY_FRAME, new Boolean(keyFrame)); + session.setAttribute(ENCODED_DATA, data); + + return true; + } + + + private void sendCaptureUpdateEvent(IoSession session, ProtocolDecoderOutput out) { + + String room = (String) session.getAttribute(ROOM); + Boolean isKeyFrame = (Boolean) session.getAttribute(IS_KEY_FRAME); + byte[] data = (byte[]) session.getAttribute(ENCODED_DATA); + + IoBuffer buffer = IoBuffer.allocate(data.length, false); + buffer.put(data); + + /* Set the marker back to zero position so that "gets" start from the beginning. + * Otherwise, you get BufferUnderFlowException. + */ + buffer.rewind(); + + System.out.println("Room " + room + " keyFrame " + isKeyFrame + + " data size " + data.length + " buffer size " + buffer.remaining()); + CaptureUpdateEvent cse = new CaptureUpdateEvent(room, buffer); + out.write(cse); + reset(session); + } + + private void reset(IoSession session) { + session.removeAttribute(EVENT_TYPE); + session.removeAttribute(ROOM); + session.removeAttribute(IS_KEY_FRAME); + session.removeAttribute(ENCODED_DATA); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/DeskShareStream.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/DeskShareStream.java new file mode 100644 index 0000000000000000000000000000000000000000..b589536b3c9c643230668c602790d264368a5ade --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/DeskShareStream.java @@ -0,0 +1,180 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.streamer; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.IDeskShareStream; +import org.bigbluebutton.deskshare.server.ScreenVideoBroadcastStream; +import org.bigbluebutton.deskshare.server.session.ScreenVideoFrame; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IContext; +import org.red5.server.api.IScope; +import org.red5.server.net.rtmp.event.VideoData; +import org.red5.server.stream.BroadcastScope; +import org.red5.server.stream.IBroadcastScope; +import org.red5.server.stream.IProviderService; +import org.slf4j.Logger; + + +/** + * The DeskShareStream class publishes captured video to a red5 stream. + * @author Snap + * + */ +public class DeskShareStream implements IDeskShareStream { + final private Logger log = Red5LoggerFactory.getLogger(DeskShareStream.class, "deskshare"); + + private BlockingQueue<CaptureUpdateEvent> screenQueue = new LinkedBlockingQueue<CaptureUpdateEvent>(10); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private ScreenVideoBroadcastStream broadcastStream; + + private long timestamp = 0, frameNumber = 0; + private int width, height, timestampBase; + private String outStreamName; + private IScope scope; + + public static final int LARGER_DIMENSION = 1280; + + + /** + * The default constructor + * The stream which gets published by the streamer has the same name as the scope. One stream allowed per room + */ + public DeskShareStream(IScope scope, String streamName, int width, int height) { + this.scope = scope; + scope.setName(streamName); + this.outStreamName = streamName; + this.width = width; + this.height = height; + } + + public void stop() { + sendCapturedScreen = false; + streamEnded(); + } + + public void start() { + startPublishing(scope); + sendCapturedScreen = true; + System.out.println("DeskShareStream Starting stream " + outStreamName); + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { +// System.out.println("ScreenQueue size " + screenQueue.size()); + CaptureUpdateEvent newScreen = screenQueue.take(); + if (screenQueue.size() > 9) screenQueue.clear(); + sendCapturedScreen(newScreen); + } catch (InterruptedException e) { + log.warn("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + } + + public void stream(ScreenVideoFrame frame) { + + + } + + public void accept(CaptureUpdateEvent event) { + // Make a copy so we can process safely on our own thread. + CaptureUpdateEvent copy = CaptureUpdateEvent.copy(event); + try { + screenQueue.put(copy); + } catch (InterruptedException e) { + log.warn("InterruptedException while putting event into queue."); + } + } + + private void sendCapturedScreen(CaptureUpdateEvent event) { + long now = System.currentTimeMillis(); + if ((now - event.getTimestamp()) > 3000) { + System.out.println("Discarding stale update event"); + return; + } + + long startRx = System.currentTimeMillis(); +// System.out.println("Sending " + event.getData().remaining() + " " +// + event.getData().capacity() + " " + event.getData().position() + " to stream"); + VideoData data = new VideoData(event.getData()); + broadcastStream.dispatchEvent(data); + data.release(); + long completeRx = System.currentTimeMillis(); +// System.out.println("Send took " + (completeRx - startRx) + "ms."); + } + + private void streamEnded() { + broadcastStream.stop(); + broadcastStream.close(); + log.debug("stopping and closing stream {}", outStreamName); + } + + /** + * Starts outputting captured video to a red5 stream + * @param aScope + */ + synchronized private void startPublishing(IScope aScope){ + System.out.println("started publishing stream in " + aScope.getName()); + + broadcastStream = new ScreenVideoBroadcastStream(outStreamName); + broadcastStream.setPublishedName(outStreamName); + broadcastStream.setScope(aScope); + + IContext context = aScope.getContext(); + + IProviderService providerService = (IProviderService) context.getBean(IProviderService.BEAN_NAME); + if (providerService.registerBroadcastStream(aScope, outStreamName, broadcastStream)){ + IBroadcastScope bScope = (BroadcastScope) providerService.getLiveProviderInput(aScope, outStreamName, true); + + bScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE, broadcastStream); + } else{ + log.error("could not register broadcast stream"); + throw new RuntimeException("could not register broadcast stream"); + } + + broadcastStream.start(); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public IScope getScope() { + return scope; + } + +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvEncodeException.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvEncodeException.java new file mode 100644 index 0000000000000000000000000000000000000000..0d3458412bf86b154cb7c49ef04f60e49553d111 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvEncodeException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.streamer; + +public class FlvEncodeException extends Exception { + + private static final long serialVersionUID = -3377474098350652011L; + + public FlvEncodeException(String message) { + super(message); + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvStreamToFile.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvStreamToFile.java new file mode 100644 index 0000000000000000000000000000000000000000..1ccb3e6c6b4194153feb9601497003f085b89431 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/FlvStreamToFile.java @@ -0,0 +1,135 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.streamer; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.server.CaptureUpdateEvent; +import org.bigbluebutton.deskshare.server.IDeskShareStream; +import org.bigbluebutton.deskshare.server.session.ScreenVideoFrame; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.slf4j.Logger; + +public class FlvStreamToFile implements IDeskShareStream { + final private Logger log = Red5LoggerFactory.getLogger(FlvStreamToFile.class, "deskshare"); + private BlockingQueue<CaptureUpdateEvent> screenQueue = new LinkedBlockingQueue<CaptureUpdateEvent>(); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private FileOutputStream fo; + private ScreenVideoFlvEncoder svf = new ScreenVideoFlvEncoder(); + + private String flvFilename = "/tmp/screenvideo.flv"; + + public void accept(CaptureUpdateEvent event) { + // Make a copy so we can process safely on our own thread. + CaptureUpdateEvent copy = CaptureUpdateEvent.copy(event); + try { + screenQueue.put(copy); + } catch (InterruptedException e) { + log.warn("InterruptedException while putting event into queue."); + } + } + + public int getHeight() { + // TODO Auto-generated method stub + return 0; + } + + public IScope getScope() { + // TODO Auto-generated method stub + return null; + } + + public int getWidth() { + // TODO Auto-generated method stub + return 0; + } + + public void stream(ScreenVideoFrame frame) {} + + public void start() { + try { + fo = new FileOutputStream(flvFilename); + fo.write(svf.encodeHeader()); + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + sendCapturedScreen = true; + System.out.println("Starting stream"); + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + System.out.println("ScreenQueue size " + screenQueue.size()); + CaptureUpdateEvent newScreen = screenQueue.take(); + sendCapturedScreen(newScreen); + } catch (InterruptedException e) { + log.warn("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + } + + private void sendCapturedScreen(CaptureUpdateEvent event) { + System.out.println("Sending screen captured - built aug 20 12:39PM"); + try { + byte[] data = svf.encodeFlvData(event.getData().array()); + fo.write(data); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (FlvEncodeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void stop() { + try { + System.out.println("Closing stream"); + fo.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void setFlvFilename(String filename) { + flvFilename = filename; + } +} diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/ScreenVideoFlvEncoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/ScreenVideoFlvEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..6bd97a5a8eed83b583ef58dad0f3e28fd1301863 --- /dev/null +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/streamer/ScreenVideoFlvEncoder.java @@ -0,0 +1,106 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.server.streamer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class ScreenVideoFlvEncoder { + private final static byte[] flvHeader = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09}; + private final static byte[] videoTagType = {0x09}; + private final static byte[] streamId = {0, 0, 0}; + private long startTimestamp = 0; + private boolean firstTag = true; + + private static byte FLV_TAG_HEADER_SIZE = 11; + + ByteArrayOutputStream flvDataStream = new ByteArrayOutputStream(); + + public byte[] encodeHeader() { + byte[] prevTagSize = encodePreviousTagSize(0); + byte[] header = new byte[flvHeader.length + prevTagSize.length]; + + System.arraycopy(flvHeader, 0, header, 0, flvHeader.length); + System.arraycopy(prevTagSize, 0, header, flvHeader.length, prevTagSize.length); + return header; + } + + private byte[] encodePreviousTagSize(long previousTagSize) { + int byte1 = (int)previousTagSize >> 24; + int byte2 = (int)previousTagSize >> 16; + int byte3 = (int)previousTagSize >> 8; + int byte4 = (int)previousTagSize & 0xff; + + return new byte[] {(byte)byte1, (byte)byte2, (byte)byte3, (byte)byte4}; + } + + public byte[] encodeFlvData (byte[] screenVideoData) throws FlvEncodeException { + + byte[] flvData; + try { + flvData = encodeFlvTag(screenVideoData); + } catch (IOException e) { + throw new FlvEncodeException("Failed to encode FLV data."); + } + return flvData; + } + + private byte[] encodeFlvTag(byte[] videoData) throws IOException { + + flvDataStream.reset(); + + flvDataStream.write(videoTagType); + flvDataStream.write(encodeDataSize(videoData.length)); + flvDataStream.write(encodeTimestamp()); + flvDataStream.write(streamId); + flvDataStream.write(videoData); + flvDataStream.write(encodePreviousTagSize(FLV_TAG_HEADER_SIZE + videoData.length)); + + return flvDataStream.toByteArray(); + } + + private byte[] encodeDataSize(int size) { + int byte1 = (size >> 16); + int byte2 = (size >> 8); + int byte3 = (size & 0x0ff); + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3}; + } + + private byte[] encodeTimestamp() { + long now = System.currentTimeMillis(); + + if (firstTag) { + startTimestamp = now; + firstTag = false; + } + + long elapsed = now - startTimestamp; + + int byte1 = (int)(elapsed & 0xff0000) >> 16; + int byte2 = (int)(elapsed & 0xff00) >> 8; + int byte3 = (int)(elapsed & 0xff); + int tsExtended = ((int)elapsed & 0xff000000) >> 24; + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3, (byte) tsExtended}; + } +} diff --git a/deskshare/app/src/main/webapp/WEB-INF/logback-deskshare.xml b/deskshare/app/src/main/webapp/WEB-INF/logback-deskshare.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8997fd3546e70bd68b9aad57e89983d4e8ff677 --- /dev/null +++ b/deskshare/app/src/main/webapp/WEB-INF/logback-deskshare.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <appender name="DESKSHARE" class="ch.qos.logback.core.FileAppender"> + <File>log/deskshare.log</File> + <Append>false</Append> + <Encoding>UTF-8</Encoding> + <BufferedIO>false</BufferedIO> + <ImmediateFlush>true</ImmediateFlush> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern> + %date [%thread] %-5level %logger{35} - %msg%n + </Pattern> + </layout> + </appender> + <root> + <level value="DEBUG" /> + <appender-ref ref="DESKSHARE" /> + </root> +</configuration> diff --git a/deskshare/app/src/main/webapp/WEB-INF/red5-web.properties b/deskshare/app/src/main/webapp/WEB-INF/red5-web.properties new file mode 100644 index 0000000000000000000000000000000000000000..5684bccda86d48ee89b165a3aa0fb67653c9c34d --- /dev/null +++ b/deskshare/app/src/main/webapp/WEB-INF/red5-web.properties @@ -0,0 +1,2 @@ +webapp.contextPath=/deskShare +webapp.virtualHosts=*, localhost, localhost:8088, 127.0.0.1:8088 diff --git a/deskshare/app/src/main/webapp/WEB-INF/red5-web.xml b/deskshare/app/src/main/webapp/WEB-INF/red5-web.xml new file mode 100644 index 0000000000000000000000000000000000000000..d674a6ebab92c64ff3bfcb1f16ddb040e1de2462 --- /dev/null +++ b/deskshare/app/src/main/webapp/WEB-INF/red5-web.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:lang="http://www.springframework.org/schema/lang" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd"> + + <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> + <property name="location" value="/WEB-INF/red5-web.properties" /> + </bean> + + <bean id="web.context" class="org.red5.server.Context" + autowire="byType" /> + + <bean id="web.scope" class="org.red5.server.WebScope" + init-method="register"> + <property name="server" ref="red5.server" /> + <property name="parent" ref="global.scope" /> + <property name="context" ref="web.context" /> + <property name="handler" ref="web.handler" /> + <property name="contextPath" value="${webapp.contextPath}" /> + <property name="virtualHosts" value="${webapp.virtualHosts}" /> + </bean> + + <bean id="web.handler" class="org.bigbluebutton.deskshare.server.DeskShareApplication"> + <property name="deskShareServer" ref="deskShareServer"/> + </bean> + + <bean id="deskshare.service" class="org.bigbluebutton.deskshare.server.DeskShareService"> + <property name="streamerGateway" ref="streamerGateway"/> + </bean> + + <bean id="deskShareServer" class="org.bigbluebutton.deskshare.server.socket.DeskShareServer" > + <property name="screenCaptureHandler" ref="screenCaptureHandler"/> + </bean> + + <!-- The IoHandler implementation --> + <bean id="screenCaptureHandler" class="org.bigbluebutton.deskshare.server.socket.BlockStreamEventMessageHandler"> + <property name="sessionManager" ref="sessionManager"/> + </bean> + + <bean id="sessionManager" class="org.bigbluebutton.deskshare.server.session.SessionManager"> + <property name="frameStreamer" ref="frameStreamer"/> + </bean> + + <bean id="frameStreamer" class="org.bigbluebutton.deskshare.server.session.FrameStreamer"> + <property name="frameStreamerGateway" ref="streamerGateway"/> + </bean> + + <bean id="streamerGateway" class="org.bigbluebutton.deskshare.server.FrameStreamerGateway"> + <property name="streamFactory" ref="streamFactory"/> + <property name="deskShareApplication" ref="web.handler"/> + </bean> + + <bean id="streamFactory" class="org.bigbluebutton.deskshare.server.FrameStreamFactory"> + <property name="deskShareApplication" ref="web.handler"/> + </bean> + +</beans> diff --git a/deskshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml b/deskshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml new file mode 100644 index 0000000000000000000000000000000000000000..6b5352c0e2a97ad2eadfe848004b9cacafdce800 --- /dev/null +++ b/deskshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:lang="http://www.springframework.org/schema/lang" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd"> + + <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> + + <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> + <property name="defaultHandler" ref="httpTunnelController"/> + </bean> + + <bean id="tunnelController" class="org.bigbluebutton.deskshare.server.servlet.TunnelController"> + <property name="methodNameResolver"> + <bean class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver"> + <property name="mappings"> + <props> + <prop key="/screenCapture">screenCaptureHandler</prop> + </props> + </property> + </bean> + </property> + </bean> + + <bean id="httpTunnelController" class="org.bigbluebutton.deskshare.server.servlet.HttpTunnelStreamController"> + <property name="methodNameResolver"> + <bean class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver"> + <property name="mappings"> + <props> + <prop key="/screenCapture">screenCaptureHandler</prop> + </props> + </property> + </bean> + </property> + </bean> +</beans> \ No newline at end of file diff --git a/deskshare/app/src/main/webapp/WEB-INF/web.xml b/deskshare/app/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000000000000000000000000000000000..081a2a861f4b0eeb13e82f5abdbec97cae9f0ec7 --- /dev/null +++ b/deskshare/app/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<web-app + xmlns="http://java.sun.com/xml/ns/j2ee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" + version="2.4"> + + <display-name>deskShare</display-name> + + <context-param> + <param-name>webAppRootKey</param-name> + <param-value>/deskshare</param-value> + </context-param> + + <listener> + <listener-class>org.red5.logging.ContextLoggingListener</listener-class> + </listener> + + <filter> + <filter-name>LoggerContextFilter</filter-name> + <filter-class>org.red5.logging.LoggerContextFilter</filter-class> + </filter> + + <filter-mapping> + <filter-name>LoggerContextFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <!--listener> + <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> + </listener--> + + <servlet> + <servlet-name>tunnel</servlet-name> + <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> + <load-on-startup>1</load-on-startup> + </servlet> + + <!-- maps the sample dispatcher to *.form --> + <servlet-mapping> + <servlet-name>tunnel</servlet-name> + <url-pattern>/tunnel/*</url-pattern> + </servlet-mapping> + + <security-constraint> + <web-resource-collection> + <web-resource-name>Forbidden</web-resource-name> + <url-pattern>/streams/*</url-pattern> + </web-resource-collection> + <auth-constraint/> + </security-constraint> + +</web-app> diff --git a/deskshare/applet/build.gradle b/deskshare/applet/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..80fac877811fbc714c152d39ca47fefcd87d4cad --- /dev/null +++ b/deskshare/applet/build.gradle @@ -0,0 +1,25 @@ +ant.importBuild 'build.xml' + +archivesBaseName = 'bbb-deskshare-applet' + +dependencies { + testRuntime 'org.testng:testng:5.8@jar' + compile project(':common') + compile 'net/jcip:jcip-annotations:1.0@jar' +} + +test { + useTestNG() +} + + +jar.doFirst { + println ''' + /** + * Combine the common classes into the applet's jar because we + * do not want to sign and manage 2 jar files. + **/''' + + jar.merge('../common/build/libs/bbb-deskshare-common-0.62.jar') +} + diff --git a/deskshare/applet/build.xml b/deskshare/applet/build.xml new file mode 100644 index 0000000000000000000000000000000000000000..001eecd42d20959719c142f4ff5cbe751badd83d --- /dev/null +++ b/deskshare/applet/build.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" ?> +<project name="bbb-deskshare-applet" basedir="."> + + <!-- How to sign the applet. From Ant in Action book --> +<!-- + <target name="getPassword" depends="initSecurity" description="Prompts for password for keystore"> + <input addproperty="keystore.password" >password for keystore:</input> + <echo level="verbose">password = ${keystore.password}</echo> + </target> + + <target name="initSecurity"> + <property name="keystore.dir" location="${user.home}/.secret" /> + <mkdir dir="${keystore.dir}" /> + <chmod file="${keystore.dir}" perm="700"/> + <property name="keystore" location="${keystore.dir}/local.keystore" /> + <property name="keystore.properties" location="${keystore.dir}/keystore.properties" /> + <property name="keystore.alias" value="code.signer"/> + </target> + + <target name="createSigningKey" depends="getPassword"> + <genkey alias="${keystore.alias}" keystore="${keystore}" storepass="${keystore.password}" validity="366" > + <dname> + <param name="CN" value="BigBlueButton"/> + <param name="OU" value="BigBlueButton Project"/> + <param name="O" value="BigBlueButton"/> + <param name="C" value="CA"/> + </dname> + </genkey> + </target> + + <target name="signJar" depends="getPassword"> + <signjar jar="build/libs/bbb-deskshare-applet-0.62.jar" + alias="${keystore.alias}" + keystore="${keystore}" + storepass="${keystore.password}" /> + </target> +--> + <!-- How to sign the applet. From Ant in Action book --> + + <target name="get-password" depends="init-security" description="Prompts for password for keystore"> + <input addproperty="keystore.password" >password for keystore:</input> + <echo level="verbose">password = ${keystore.password}</echo> + </target> + + <target name="init-security"> + <property name="keystore.dir" location="${user.home}/.secret" /> + <mkdir dir="${keystore.dir}" /> + <chmod file="${keystore.dir}" perm="700"/> + <property name="keystore" + location="${keystore.dir}/local.keystore" /> + <property file="${keystore.dir}/keystore.properties" /> + <property name="keystore.alias" value="code.signer"/> + </target> + + <target name="create-signing-key" depends="get-password"> + <genkey alias="${keystore.alias}" keystore="${keystore}" storepass="${keystore.password}" validity="366" > + <dname> + <param name="CN" value="BigBlueButton"/> + <param name="OU" value="BigBlueButton Project"/> + <param name="O" value="BigBlueButton"/> + <param name="C" value="CA"/> + </dname> + </genkey> + </target> + + <target name="sign-jar" depends="get-password"> + <signjar jar="build/libs/bbb-deskshare-applet-0.62.jar" + alias="${keystore.alias}" + keystore="${keystore}" + storepass="${keystore.password}" /> + </target> + +</project> diff --git a/deskshare/applet/src/main/java/com/myjavatools/web/ClientHttpRequest.java b/deskshare/applet/src/main/java/com/myjavatools/web/ClientHttpRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..1dff7c9f7e0aff3b70601d801cd3d3422aa369d9 --- /dev/null +++ b/deskshare/applet/src/main/java/com/myjavatools/web/ClientHttpRequest.java @@ -0,0 +1,492 @@ +package com.myjavatools.web; + +import java.net.URLConnection; +import java.net.URL; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.io.File; +import java.io.InputStream; +import java.util.Random; +import java.io.OutputStream; +import java.io.FileInputStream; +import java.util.Iterator; + +/** + * <p>Title: Client HTTP Request class</p> + * <p>Description: this class helps to send POST HTTP requests with various form data, + * including files. Cookies can be added to be included in the request.</p> + * + * @author Vlad Patryshev + * @version 1.0 + */ +public class ClientHttpRequest { + URLConnection connection; + OutputStream os = null; + Map cookies = new HashMap(); + + protected void connect() throws IOException { + if (os == null) os = connection.getOutputStream(); + } + + protected void write(char c) throws IOException { + connect(); + os.write(c); + } + + protected void write(String s) throws IOException { + connect(); + os.write(s.getBytes()); + } + + protected void newline() throws IOException { + connect(); + write("\r\n"); + } + + protected void writeln(String s) throws IOException { + connect(); + write(s); + newline(); + } + + private static Random random = new Random(); + + protected static String randomString() { + return Long.toString(random.nextLong(), 36); + } + + String boundary = "---------------------------" + randomString() + randomString() + randomString(); + + private void boundary() throws IOException { + write("--"); + write(boundary); + } + + /** + * Creates a new multipart POST HTTP request on a freshly opened URLConnection + * + * @param connection an already open URL connection + * @throws IOException + */ + public ClientHttpRequest(URLConnection connection) throws IOException { + this.connection = connection; + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", + "multipart/form-data; boundary=" + boundary); + } + + /** + * Creates a new multipart POST HTTP request for a specified URL + * + * @param url the URL to send request to + * @throws IOException + */ + public ClientHttpRequest(URL url) throws IOException { + this(url.openConnection()); + } + + /** + * Creates a new multipart POST HTTP request for a specified URL string + * + * @param urlString the string representation of the URL to send request to + * @throws IOException + */ + public ClientHttpRequest(String urlString) throws IOException { + this(new URL(urlString)); + } + + + private void postCookies() { + StringBuffer cookieList = new StringBuffer(); + + for (Iterator i = cookies.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)(i.next()); + cookieList.append(entry.getKey().toString() + "=" + entry.getValue()); + + if (i.hasNext()) { + cookieList.append("; "); + } + } + if (cookieList.length() > 0) { + connection.setRequestProperty("Cookie", cookieList.toString()); + } + } + + /** + * adds a cookie to the requst + * @param name cookie name + * @param value cookie value + * @throws IOException + */ + public void setCookie(String name, String value) throws IOException { + cookies.put(name, value); + } + + /** + * adds cookies to the request + * @param cookies the cookie "name-to-value" map + * @throws IOException + */ + public void setCookies(Map cookies) throws IOException { + if (cookies == null) return; + this.cookies.putAll(cookies); + } + + /** + * adds cookies to the request + * @param cookies array of cookie names and values (cookies[2*i] is a name, cookies[2*i + 1] is a value) + * @throws IOException + */ + public void setCookies(String[] cookies) throws IOException { + if (cookies == null) return; + for (int i = 0; i < cookies.length - 1; i+=2) { + setCookie(cookies[i], cookies[i+1]); + } + } + + private void writeName(String name) throws IOException { + newline(); + write("Content-Disposition: form-data; name=\""); + write(name); + write('"'); + } + + /** + * adds a string parameter to the request + * @param name parameter name + * @param value parameter value + * @throws IOException + */ + public void setParameter(String name, String value) throws IOException { + boundary(); + writeName(name); + newline(); newline(); + writeln(value); + } + + private static void pipe(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[500000]; + int nread; + int navailable; + int total = 0; + synchronized (in) { + while((nread = in.read(buf, 0, buf.length)) >= 0) { + out.write(buf, 0, nread); + total += nread; + } + } + out.flush(); + buf = null; + } + + /** + * adds a file parameter to the request + * @param name parameter name + * @param filename the name of the file + * @param is input stream to read the contents of the file from + * @throws IOException + */ + public void setParameter(String name, String filename, InputStream is) throws IOException { + boundary(); + writeName(name); + write("; filename=\""); + write(filename); + write('"'); + newline(); + write("Content-Type: "); + String type = connection.guessContentTypeFromName(filename); + if (type == null) type = "application/octet-stream"; + writeln(type); + newline(); + pipe(is, os); + newline(); + } + + /** + * adds a file parameter to the request + * @param name parameter name + * @param file the file to upload + * @throws IOException + */ + public void setParameter(String name, File file) throws IOException { + setParameter(name, file.getPath(), new FileInputStream(file)); + } + + /** + * adds a parameter to the request; if the parameter is a File, the file is uploaded, otherwise the string value of the parameter is passed in the request + * @param name parameter name + * @param object parameter value, a File or anything else that can be stringified + * @throws IOException + */ + public void setParameter(String name, Object object) throws IOException { + if (object instanceof File) { + setParameter(name, (File) object); + } else { + setParameter(name, object.toString()); + } + } + + /** + * adds parameters to the request + * @param parameters "name-to-value" map of parameters; if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request + * @throws IOException + */ + public void setParameters(Map parameters) throws IOException { + if (parameters == null) return; + for (Iterator i = parameters.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)i.next(); + setParameter(entry.getKey().toString(), entry.getValue()); + } + } + + /** + * adds parameters to the request + * @param parameters array of parameter names and values (parameters[2*i] is a name, parameters[2*i + 1] is a value); if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request + * @throws IOException + */ + public void setParameters(Object[] parameters) throws IOException { + if (parameters == null) return; + for (int i = 0; i < parameters.length - 1; i+=2) { + setParameter(parameters[i].toString(), parameters[i+1]); + } + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added + * @return input stream with the server response + * @throws IOException + */ + public InputStream post() throws IOException { + boundary(); + writeln("--"); + os.close(); + return connection.getInputStream(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public InputStream post(Map parameters) throws IOException { + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public InputStream post(Object[] parameters) throws IOException { + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + * @see setCookies + */ + public InputStream post(Map cookies, Map parameters) throws IOException { + setCookies(cookies); + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + * @see setCookies + */ + public InputStream post(String[] cookies, Object[] parameters) throws IOException { + setCookies(cookies); + setParameters(parameters); + return post(); + } + + /** + * post the POST request to the server, with the specified parameter + * @param name parameter name + * @param value parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name, Object value) throws IOException { + setParameter(name, value); + return post(); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2) throws IOException { + setParameter(name1, value1); + return post(name2, value2); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException { + setParameter(name1, value1); + return post(name2, value2, name3, value3); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @param name4 fourth parameter name + * @param value4 fourth parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException { + setParameter(name1, value1); + return post(name2, value2, name3, value3, name4, value4); + } + + /** + * posts a new request to specified URL, with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public static InputStream post(URL url, Map parameters) throws IOException { + return new ClientHttpRequest(url).post(parameters); + } + + /** + * posts a new request to specified URL, with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public static InputStream post(URL url, Object[] parameters) throws IOException { + return new ClientHttpRequest(url).post(parameters); + } + + /** + * posts a new request to specified URL, with cookies and parameters that are passed in the argument + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setCookies + * @see setParameters + */ + public static InputStream post(URL url, Map cookies, Map parameters) throws IOException { + return new ClientHttpRequest(url).post(cookies, parameters); + } + + /** + * posts a new request to specified URL, with cookies and parameters that are passed in the argument + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setCookies + * @see setParameters + */ + public static InputStream post(URL url, String[] cookies, Object[] parameters) throws IOException { + return new ClientHttpRequest(url).post(cookies, parameters); + } + + /** + * post the POST request specified URL, with the specified parameter + * @param name parameter name + * @param value parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1) throws IOException { + return new ClientHttpRequest(url).post(name1, value1); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @param name4 fourth parameter name + * @param value4 fourth parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3, name4, value4); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/DeskShareApplet.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/DeskShareApplet.java new file mode 100644 index 0000000000000000000000000000000000000000..63ab51bfb0972663ed531dd8b2c1a7f63d4051e8 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/DeskShareApplet.java @@ -0,0 +1,147 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client; + +import java.applet.Applet; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; + + +import org.bigbluebutton.deskshare.client.blocks.BlockManager; +import org.bigbluebutton.deskshare.client.blocks.ChangedBlocksListener; +import org.bigbluebutton.deskshare.client.net.ConnectionException; +import org.bigbluebutton.deskshare.client.net.NetworkStreamSender; +import org.bigbluebutton.deskshare.common.Dimension; + +public class DeskShareApplet extends Applet implements IScreenCaptureListener, ChangedBlocksListener { + + private static final long serialVersionUID = 1L; + private ScreenCaptureTaker captureTaker; + private ScreenCapture capture; + private Thread captureTakerThread; +// private ScreenCaptureSender captureSender; + + private int screenWidth = 1680; //1536; + private int screenHeight = 1050; //1024; + private int x = 0; + private int y = 0; + private boolean httpTunnel = false; + private BlockManager blockManager; + private int blockWidth = 64; + private int blockHeight = 64; + private String room = "testroom"; + private String host = "192.168.0.182"; + + boolean connected = false; + private boolean senderStarted = false; + private NetworkStreamSender sender; + + public void init() { + + System.out.println("Applet built oct 7, 2009 at 7:53PM"); + screenWidth = Integer.parseInt(getParameter("CAPTURE_WIDTH")); + screenHeight = Integer.parseInt(getParameter("CAPTURE_HEIGHT")); + + x = Integer.parseInt(getParameter("X")); + y = Integer.parseInt(getParameter("Y")); + room = getParameter("ROOM"); + host = getParameter("IP"); + + httpTunnel = Boolean.parseBoolean(getParameter("TUNNEL")); + System.out.println("Tunnel " + httpTunnel); + String t = getParameter("TUNNEL"); + System.out.println("Tunnel param " + t); + } + + public void stop() { + System.out.println("Stopping applet"); + captureTaker.setCapture(false); + if (connected) { + try { + if (senderStarted) + sender.stop(); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public void start() { + System.out.println("RunnerApplet start()"); + capture = new ScreenCapture(x, y, screenWidth, screenHeight); + captureTaker = new ScreenCaptureTaker(capture); + + Dimension screenDim = new Dimension(screenWidth, screenHeight); + Dimension tileDim = new Dimension(blockWidth, blockHeight); + blockManager = new BlockManager(); + blockManager.addListener(this); + blockManager.initialize(screenDim, tileDim); + + sender = new NetworkStreamSender(blockManager, host, room); + connected = sender.connect(); + if (connected) { + captureTaker.addListener(this); + captureTaker.setCapture(true); + + captureTakerThread = new Thread(captureTaker, "ScreenCaptureTaker"); + captureTakerThread.start(); + } + } + + /** + * This method is called when the user closes the browser window containing the applet + * It is very important that the connection to the server is closed at this point. That way the server knows to + * close the stream. + */ + public void destroy(){ + stop(); + } + + public void setScreenCoordinates(int x, int y){ + capture.setX(x); + capture.setY(y); + } + + public void onScreenCaptured(BufferedImage screen, boolean isKeyFrame) { + blockManager.processCapturedScreen(screen, isKeyFrame); + } + + + public void screenCaptureStopped() { + System.out.println("Screencapture stopped"); + destroy(); + } + + public void onChangedTiles(ByteArrayOutputStream pixelData, boolean isKeyFrame) { + if (! senderStarted) { + sender.start(); + senderStarted = true; + } + } + + static public void main (String argv[]) { + final Applet applet = new DeskShareApplet(); + applet.start(); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/FlvFileWriter.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/FlvFileWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..ab6c597869b6d01a2c01a0f50020d4b650dff571 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/FlvFileWriter.java @@ -0,0 +1,158 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class FlvFileWriter { + + private FileOutputStream fo; + + private long previousTagSize = 0; + private final static byte[] header = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09}; + private static final byte[] videoTagType = {0x09}; + private static final byte[] streamId = {0, 0, 0}; + private int dataSize = 0; + private long startTimestamp = 0; + private boolean firstTag = true; + + private static byte FLV_TAG_HEADER_SIZE = 11; + + public void init() { + setupFlvFile(); + } + + public void writeDataToFile (ByteArrayOutputStream videoData) { + + int size = videoData.size(); + byte[] blockData = videoData.toByteArray(); + + try { + writePreviousTagSize(); + writeFlvTag(blockData, size); + } catch(Exception e) { + System.out.println("exception: "+e.getMessage()); + } + } + + private void writeFlvTag(byte[] videoData, int size) throws IOException { + writeTagType(); + writeDataSize(size); + writeTimestamp(); + writeStreamId(); + writeVideoData(videoData); + } + + private void setupFlvFile() { + try { + fo = new FileOutputStream("D://temp/"+"ApiDemo.flv"); + fo.write(header); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void writePreviousTagSize() throws IOException { + + int byte1 = (int)previousTagSize >> 24; + int byte2 = (int)previousTagSize >> 16; + int byte3 = (int)previousTagSize >> 8; + int byte4 = (int)previousTagSize & 0xff; + + // System.out.println("PrevTagSize: dec=" + previousTagSize + " hex=" + Long.toHexString(previousTagSize) + " bytes= " + // + Integer.toHexString(byte1) + " " + Integer.toHexString(byte2) + // + " " + Integer.toHexString(byte3) + " " + Integer.toHexString(byte4)); + + fo.write(byte1); + fo.write(byte2); + fo.write(byte3); + fo.write(byte4); + } + + private void writeTagType() throws IOException { + fo.write(videoTagType); + } + + private void writeDataSize(int size) throws IOException { + int byte1 = (size >> 16); + int byte2 = (size >> 8); + int byte3 = (size & 0x0ff); + +// System.out.println("DataSize: dec=" + size + " hex=" + Integer.toHexString(size) + " bytes= " +//// + Integer.toHexString(byte1) + " " + Integer.toHexString(byte2) + // + " " + Integer.toHexString(byte3)); + + fo.write(byte1); + fo.write(byte2); + fo.write(byte3); + + previousTagSize = FLV_TAG_HEADER_SIZE + size; + } + + private void writeTimestamp() throws IOException { + long now = System.currentTimeMillis(); + + if (firstTag) { + startTimestamp = now; + firstTag = false; + } + + long elapsed = now - startTimestamp; + + int fb = (int)(elapsed & 0xff0000) >> 16; + int sb = (int)(elapsed & 0xff00) >> 8; + int tb = (int)(elapsed & 0xff); + int ub = ((int)elapsed & 0xff000000) >> 24; + +// System.out.println("timestamp: dec=" + elapsed + " hex=" + Long.toHexString(elapsed) + " bytes=" + +// Integer.toHexString(fb) + " " + Integer.toHexString(sb) + " " + Integer.toHexString(tb) + " " + Integer.toHexString(ub)); + + fo.write(fb); + fo.write(sb); + fo.write(tb); + + fo.write(ub ); + + } + + private void writeStreamId() throws IOException { + fo.write(streamId); + } + + private void writeVideoData(byte[] videoData) throws IOException { + fo.write(videoData); + } + + public void stop() { + try { + System.out.println("Closing stream"); + fo.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/IScreenCaptureListener.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/IScreenCaptureListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4875d98d06e492b9cd6584e0bddac2b2d75e3176 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/IScreenCaptureListener.java @@ -0,0 +1,29 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client; + +import java.awt.image.BufferedImage; + +public interface IScreenCaptureListener { + public void screenCaptureStopped(); + public void onScreenCaptured(BufferedImage screen, boolean isKeyFrame); +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCapture.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCapture.java new file mode 100644 index 0000000000000000000000000000000000000000..60d7a9d48590eb3cb71af60630dad3c426bf2676 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCapture.java @@ -0,0 +1,126 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client; + +import java.awt.AWTException; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; + +/** + * The Capture class uses the java Robot class to capture the screen + * The image captured is scaled down. This is done because of bandwidth issues and because it + * is unnecessary for the Flex Client to be able to see the full screen, in fact it is undesirable + * to do so. + * The image can then be sent for further processing + * @author Snap + * + */ +public class ScreenCapture { + private Robot robot; + private Toolkit toolkit; + private Rectangle screenBounds; + private int width, height, x,y, videoWidth, videoHeight; + + public ScreenCapture(int x, int y, int screenWidth, int screenHeight){ + this.width = screenWidth; + this.height = screenHeight; + try{ + robot = new Robot(); + }catch (AWTException e){ + System.out.println(e.getMessage()); + } + this.toolkit = Toolkit.getDefaultToolkit(); + this.screenBounds = new Rectangle(x, y, this.width, this.height); + } + + public BufferedImage takeSingleSnapshot(){ + return robot.createScreenCapture(this.screenBounds); + } + + public int getScreenshotWidth(){ + return toolkit.getScreenSize().width; + } + + public int getScreenshotHeight(){ + return toolkit.getScreenSize().height; + } + + public void setWidth(int width){ + int screenWidth = toolkit.getScreenSize().width; + if (width > screenWidth) this.width = screenWidth; + else this.width = width; + updateBounds(); + } + + public void setHeight(int height){ + int screenHeight = toolkit.getScreenSize().height; + if (height > screenHeight) { + this.height = screenHeight; + } + else { + this.height = height; + } + updateBounds(); + } + + public void setX(int x){ + this.x = x; + updateBounds(); + } + + public void setY(int y){ + this.y = y; + updateBounds(); + } + + public void updateBounds(){ + this.screenBounds = new Rectangle(x,y,width,height); + } + + public int getWidth(){ + return this.width; + } + + public int getHeight(){ + return this.height; + } + + public int getProperFrameRate(){ + long area = width*height; + if (area > 1000000) return 1; + else if (area > 600000) return 2; + else if (area > 300000) return 4; + else if (area > 150000) return 8; + else return 10; + } + + public int getVideoWidth(){ + return videoWidth; + } + + public int getVideoHeight(){ + return videoHeight; + } + +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCaptureTaker.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCaptureTaker.java new file mode 100644 index 0000000000000000000000000000000000000000..ea564cc8a3166baf03f50cdc471fa6173a1e7b96 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/ScreenCaptureTaker.java @@ -0,0 +1,100 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client; + +import java.awt.image.BufferedImage; + +public class ScreenCaptureTaker implements Runnable { + + private ScreenCapture capture; + private int timeBase; + private int frameCount = 0; + private IScreenCaptureListener listeners; + + private volatile boolean startCapture = false; + + public ScreenCaptureTaker(ScreenCapture capture){ + System.out.println("Capture thread constructor."); + this.capture = capture; + this.timeBase = 1000 / capture.getProperFrameRate(); + } + + public void run(){ + while (startCapture){ + System.out.println("----- Taking screen capture -----"); + long snapshotTime = System.currentTimeMillis(); + BufferedImage image = capture.takeSingleSnapshot(); + long snapshotEnd = System.currentTimeMillis(); +// System.out.println("Snapshot time = " + (snapshotEnd - snapshotTime) + "ms."); + notifyListeners(image); + long completeTime = System.currentTimeMillis(); +// System.out.println("Processing time = " + (completeTime - snapshotTime) + "ms."); + try{ + //Thread.sleep(timeBase); + Thread.sleep(500); + } catch (Exception e){ + System.out.println("Exception sleeping."); + System.exit(0); + } + } + + System.out.println("Stopping screen capture."); + + listeners.screenCaptureStopped(); + } + + private void notifyListeners(BufferedImage image) { + listeners.onScreenCaptured(image, isKeyFrame()); + } + + private boolean isKeyFrame() { + + if (frameCount == 0) { +// System.out.println("Is Key Frame " + frameCount); + frameCount++; + + return true; + } else { +// System.out.println("Is Not Key Frame " + frameCount); + if (frameCount < 20) { + frameCount++; + } else { + frameCount = 0; + } + + return false; + } + } + + public void addListener(IScreenCaptureListener listener) { +// listeners.add(listener); + listeners = listener; + } + + public void removeListener(IScreenCaptureListener listener) { +// listeners.remove(listener); + } + + public void setCapture(boolean capture) { + startCapture = capture; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/Block.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/Block.java new file mode 100644 index 0000000000000000000000000000000000000000..5dc6af60e4a8a6a1809a7007aa1b8a4c5059425e --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/Block.java @@ -0,0 +1,151 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.zip.Adler32; + +import org.bigbluebutton.deskshare.client.net.EncodedBlockData; +import org.bigbluebutton.deskshare.common.PixelExtractException; +import org.bigbluebutton.deskshare.common.ScreenVideoEncoder; +import org.bigbluebutton.deskshare.common.Dimension; + +public final class Block { + private final Adler32 checksum; + private final Dimension dim; + private final int position; + private final Point location; + + private boolean isKeyFrame = false; + private int[] pixels; + private EncodedBlockData encodedBlockData; + private long lastChanged; + private static int FORCE_UPDATE_DURATION = 10000; + + Block(Dimension dim, int position, Point location) { + checksum = new Adler32(); + this.dim = dim; + this.position = position; + this.location = location; + } + + public void updateBlock(BufferedImage capturedScreen, boolean isKeyFrame) + { + synchronized(this) { + this.isKeyFrame = isKeyFrame; + try { + pixels = ScreenVideoEncoder.getPixels(capturedScreen, getX(), getY(), getWidth(), getHeight()); + } catch (PixelExtractException e) { + System.out.println(e.toString()); + } + } + + } + + public EncodedBlockData encode() { + int[] pixelsCopy = new int[pixels.length]; + synchronized (this) { + System.arraycopy(pixels, 0, pixelsCopy, 0, pixels.length); + } + + byte[] encodedBlock; + boolean hasChanged = false; + boolean forceUpdate = forceUpdate(); + //if (!checksumSame(pixelsCopy) || forceUpdate) { + if (!checksumSame(pixelsCopy) || isKeyFrame) {// || forceUpdate()) { + encodedBlock = ScreenVideoEncoder.encodePixels(pixelsCopy, getWidth(), getHeight(), false, isKeyFrame); + hasChanged = true; + } else { + encodedBlock = ScreenVideoEncoder.encodeBlockUnchanged(); + hasChanged = false; + } + + EncodedBlockData data = new EncodedBlockData(position, hasChanged, encodedBlock, isKeyFrame); + return data; + } + + private boolean checksumSame(int[] pixelsCopy) { + long oldsum; + oldsum = checksum.getValue(); + calcChecksum(pixelsCopy); + return (oldsum == checksum.getValue()); + } + + private boolean forceUpdate() { + long now = System.currentTimeMillis(); + boolean update = ((now - lastChanged) > FORCE_UPDATE_DURATION); + if (update) { + synchronized(this) { + lastChanged = now; + } + } + return update; + } + + public synchronized EncodedBlockData getEncodedBlockData() { + return encodedBlockData; + } + + private synchronized void calcChecksum(int pixelsCopy[]) + { + checksum.reset(); + int height = getHeight(); + int width = getWidth(); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if ((i * width + i) % 13 == 0) + checksum.update(pixelsCopy[i * width + j]); + } + } + } + + public int getWidth() + { + return new Integer(dim.getWidth()).intValue(); + } + + public int getHeight() + { + return new Integer(dim.getHeight()).intValue(); + } + + public int getPosition() { + return new Integer(position).intValue(); + } + + public int getX() { + return new Integer(location.x).intValue(); + } + + public int getY() { + return new Integer(location.y).intValue(); + } + + Dimension getDimension() { + return new Dimension(dim.getWidth(), dim.getHeight()); + } + + Point getLocation() { + return new Point(location.x, location.y); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockFactory.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2f4a92bd77cc1affe9c98f6eae1fdb9684a7e8da --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockFactory.java @@ -0,0 +1,153 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +import java.awt.Point; + +import org.bigbluebutton.deskshare.common.Dimension; + +class BlockFactory { + private int numColumns; + private int numRows; + private Dimension screenDim; + private Dimension tileDim; + + public BlockFactory(Dimension screen, Dimension tile) { + this.screenDim = screen; + this.tileDim = tile; + + numColumns = computeNumberOfColumnTiles(); + numRows = computeNumberOfRowTiles(); + } + + private int computeNumberOfColumnTiles() { + int columns = screenDim.getWidth() / tileDim.getWidth(); + if (hasPartialColumnTile()) { + columns += 1; + } + return columns; + } + + private boolean hasPartialColumnTile() { + return (screenDim.getWidth() % tileDim.getWidth()) != 0; + } + + private int computeNumberOfRowTiles() { + int rows = screenDim.getHeight() / tileDim.getHeight(); + if (hasPartialRowTile()) { + rows += 1; + } + return rows; + } + + private boolean hasPartialRowTile() { + return (screenDim.getHeight() % tileDim.getHeight()) != 0; + } + + Block createBlock(int position) { + int col = computeColumn(position); + int row = computeRow(position); + int w = computeTileWidth(col); + int h = computeTileHeight(row); + int x = computeTileXLocation(col); + int y = computeTileYLocation(row); + int pos = computeTilePosition(row, col); + + Block t = new Block(new Dimension(w, h), pos, new Point(x,y)); + + return t; + } + + private int computeTilePosition(int row, int col) { + return (((numRows - (row+1)) * numColumns) + (col + 1)); + } + + private int computeTileXLocation(int col) { + return col * tileDim.getWidth(); + } + + private int computeTileYLocation(int row) { + if (isTopRowTile(row)) return 0; + return screenDim.getHeight() - ((numRows - row) * tileDim.getHeight()); + } + + Point positionToIndex(int position) { + return new Point(computeRow(position), computeColumn(position)); + } + + int indexToPosition(int row, int col) { + return computeTilePosition(row, col); + } + + private int computeRow(int position) { + return -(position - (getRowCount() * getColumnCount())) / getColumnCount(); + } + + private int computeColumn(int position) { + return (position - 1) % getColumnCount(); + } + + private int computeTileWidth(int col) { + if (isLastColumnTile(col)) { + if (hasPartialColumnTile()) { + return partialTileWidth(); + } + } + return tileDim.getWidth(); + } + + private int partialTileWidth() { + return screenDim.getWidth() % tileDim.getWidth(); + } + + private int computeTileHeight(int row) { + if (isTopRowTile(row)) { + if (hasPartialRowTile()) { + return partialTileHeight(); + } + } + return tileDim.getWidth(); + } + + private int partialTileHeight() { + return screenDim.getHeight() % tileDim.getHeight(); + } + + private boolean isLastColumnTile(int col) { + return ((col+1) % numColumns) == 0; + } + + private boolean isTopRowTile(int row) { + return (row == 0); + } + + + int getRowCount() + { + return numRows; + } + + int getColumnCount() + { + return numColumns; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockImp.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockImp.java new file mode 100644 index 0000000000000000000000000000000000000000..f30bc2757fadeb9bacb09128f714e81571e1dffb --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockImp.java @@ -0,0 +1,135 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.zip.Adler32; + +import org.bigbluebutton.deskshare.common.PixelExtractException; +import org.bigbluebutton.deskshare.common.ScreenVideoEncoder; +import org.bigbluebutton.deskshare.common.Dimension; + +public final class BlockImp implements IBlock { + private final Adler32 checksum; + private final Dimension dim; + private final int position; + private final Point location; + + private boolean isKeyFrame = false; + private byte[] encodedBlock; + private int[] pixels; + + private boolean encoded = false; + + BlockImp(Dimension dim, int position, Point location) { + checksum = new Adler32(); + this.dim = dim; + this.position = position; + this.location = location; + } + + public synchronized void updateBlock(BufferedImage capturedScreen, boolean isKeyFrame) + { + this.isKeyFrame = isKeyFrame; + + try { + pixels = ScreenVideoEncoder.getPixels(capturedScreen, getX(), getY(), getWidth(), getHeight()); + } catch (PixelExtractException e) { + System.out.println(e.toString()); + encodedBlock = ScreenVideoEncoder.encodeBlockUnchanged(); + } + } + + public synchronized byte[] encode() { + long oldsum; + oldsum = checksum.getValue(); + calcChecksum(pixels); + + if ((oldsum == checksum.getValue()) && !isKeyFrame) { + encodedBlock = ScreenVideoEncoder.encodeBlockUnchanged(); + } + else { + encodedBlock = ScreenVideoEncoder.encodePixels(pixels, getWidth(), getHeight(), false, isKeyFrame); + } + return encodedBlock; + } + + public byte[] getEncodedBlock() { + return encodedBlock; + } + + private synchronized void calcChecksum(int pixels[]) + { + checksum.reset(); + int height = getHeight(); + int width = getWidth(); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if ((i * width + i) % 13 == 0) + checksum.update(pixels[i * width + j]); + } + } + } + + public synchronized boolean isKeyFrame() { + return isKeyFrame; + } + + public synchronized boolean hasChanged() { + long oldsum; + oldsum = checksum.getValue(); + calcChecksum(pixels); + + return ((oldsum == checksum.getValue()) && !isKeyFrame); + } + + public int getWidth() + { + return new Integer(dim.getWidth()).intValue(); + } + + public int getHeight() + { + return new Integer(dim.getHeight()).intValue(); + } + + public int getPosition() { + return new Integer(position).intValue(); + } + + public int getX() { + return new Integer(location.x).intValue(); + } + + public int getY() { + return new Integer(location.y).intValue(); + } + + Dimension getDimension() { + return new Dimension(dim.getWidth(), dim.getHeight()); + } + + Point getLocation() { + return new Point(location.x, location.y); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockManager.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockManager.java new file mode 100644 index 0000000000000000000000000000000000000000..4866034b53bca336423df8e0252631e069533f99 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/BlockManager.java @@ -0,0 +1,172 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.bigbluebutton.deskshare.client.encoder.BlockEncodeException; +import org.bigbluebutton.deskshare.client.encoder.ScreenVideoBlockEncoder; +import org.bigbluebutton.deskshare.common.ScreenVideoEncoder; +import org.bigbluebutton.deskshare.common.Dimension; + +public class BlockManager { + private final Map<Integer, Block> blocksMap; + private final Map<Integer, Block> unmodifiableBlocksMap; + private int numColumns; + private int numRows; + + private BlockFactory factory; + private ChangedBlocksListener listeners; + + private ByteArrayOutputStream encodedPixelStream = new ByteArrayOutputStream(); + private Dimension screenDim, blockDim; + +// private ScreenVideoBlockEncoder encoder; + + public BlockManager() { + blocksMap = new HashMap<Integer, Block>(); + unmodifiableBlocksMap = Collections.unmodifiableMap(blocksMap); + // encoder = new ScreenVideoBlockEncoder(); + } + + public void initialize(Dimension screen, Dimension tile) { + screenDim = screen; + blockDim = tile; + + factory = new BlockFactory(screen, tile); + + numColumns = factory.getColumnCount(); + numRows = factory.getRowCount(); + int numberOfBlocks = numColumns * numRows; + + for (int position = 1; position <= numberOfBlocks; position++) { + Block block = factory.createBlock(position); + blocksMap.put(new Integer(position), block); + } + } + + public void processCapturedScreen(BufferedImage capturedScreen, boolean isKeyFrame) + { + long start = System.currentTimeMillis(); +// Block[] blocks = new Block[numRows * numColumns]; + int index = 0; + int numberOfBlocks = numColumns * numRows; + for (int position = 1; position <= numberOfBlocks; position++) { + Block block = blocksMap.get(new Integer(position)); + block.updateBlock(capturedScreen, isKeyFrame); + + +// blocks[index++] = block; + } + + long qEncode = System.currentTimeMillis(); +// System.out.println("Grabbing pixels for blocks[" + numberOfBlocks + "] took " + (qEncode-start) + " ms."); + +// try { +// encoder.encode(blocks); +// sendEncodedData(isKeyFrame); +// } catch (BlockEncodeException e) { +// e.printStackTrace(); +// } + notifyChangedTilesListener(null, isKeyFrame); + long end = System.currentTimeMillis(); + System.out.println("ProcessCapturedScreen took " + (end-start) + " ms."); + } +/* + private void sendEncodedData(boolean isKeyFrame) { + long start = System.currentTimeMillis(); + encodedPixelStream.reset(); + byte[] encodedDim = ScreenVideoEncoder.encodeBlockDimensionsAndGridSize(blockDim.getWidth(), screenDim.getWidth(), blockDim.getHeight(), screenDim.getHeight()); + byte videoDataHeader = ScreenVideoEncoder.encodeFlvVideoDataHeader(isKeyFrame); + try { + encodedPixelStream.write(videoDataHeader); + encodedPixelStream.write(encodedDim); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + int numberOfBlocks = numColumns * numRows; + for (int position = 1; position <= numberOfBlocks; position++) { + Block block = blocksMap.get(new Integer(position)); + byte[] data = block.getEncodedBlock(); + encodedPixelStream.write(data, 0, data.length); + } + +// System.out.println("Encoded data length = " + encodedPixelStream.size()); + notifyChangedTilesListener(encodedPixelStream, isKeyFrame); + long end = System.currentTimeMillis(); + System.out.println("Sending encoded data took " + (end-start) + " ms."); + } +*/ + private void notifyChangedTilesListener(ByteArrayOutputStream encodedPixelStream, boolean isKeyFrame) { + listeners.onChangedTiles(encodedPixelStream, isKeyFrame); + } + + + public void addListener(ChangedBlocksListener listener) { + listeners = listener; + } + + + public void removeListener(ChangedBlocksListener listener) { + //listeners.remove(listener); + listeners = null; + } + + public Block getBlock(int position) { + return (Block) blocksMap.get(Integer.valueOf(position)); + } + + public int getRowCount() + { + return numRows; + } + + public int getColumnCount() + { + return numColumns; + } + + public Dimension getScreenDim() { + return screenDim; + } + + public Dimension getBlockDim() { + return blockDim; + } + + /* + * Returns a read-only "live" view of the blocks; + */ + public Map<Integer, Block> getBlocks() { + return unmodifiableBlocksMap; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/ChangedBlocksListener.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/ChangedBlocksListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c2d130e69bc80718f58a21cc2fe4fa4d8c1db14d --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/ChangedBlocksListener.java @@ -0,0 +1,29 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +import java.io.ByteArrayOutputStream; + +public interface ChangedBlocksListener { + + public void onChangedTiles(ByteArrayOutputStream pixelData, boolean isKeyFrame); +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/IBlock.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/IBlock.java new file mode 100644 index 0000000000000000000000000000000000000000..c385bd72b7bfc1934924a8424941f8a307a90ceb --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/blocks/IBlock.java @@ -0,0 +1,29 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.blocks; + +public interface IBlock { + + public boolean hasChanged(); + + public byte[] encode(); +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/BlockEncodeException.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/BlockEncodeException.java new file mode 100644 index 0000000000000000000000000000000000000000..d39e7ec5f2c142ce754c381b48d9ffccb1e002a2 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/BlockEncodeException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.encoder; + +public class BlockEncodeException extends Exception { + + private static final long serialVersionUID = -3377474098350652011L; + + public BlockEncodeException(String message) { + super(message); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/FlvEncodeException.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/FlvEncodeException.java new file mode 100644 index 0000000000000000000000000000000000000000..56f9b1e93be7b79cdc9071db3fa3aad0ed5ecf78 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/FlvEncodeException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.encoder; + +public class FlvEncodeException extends Exception { + + private static final long serialVersionUID = -3377474098350652011L; + + public FlvEncodeException(String message) { + super(message); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoBlockEncoder.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoBlockEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..123d22bcd5ab8bd845ffdd2fb66ca75e7ce129e3 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoBlockEncoder.java @@ -0,0 +1,74 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.encoder; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.bigbluebutton.deskshare.client.blocks.Block; + +public class ScreenVideoBlockEncoder { + private ExecutorService executor; + private CompletionService<Block> completionService; + + public ScreenVideoBlockEncoder() { + int numThreads = Runtime.getRuntime().availableProcessors(); + System.out.println("Starting up " + numThreads + " threads."); + executor = Executors.newFixedThreadPool(numThreads); + completionService = new ExecutorCompletionService<Block>(executor); + } + + public void encode(Block[] blocks) throws BlockEncodeException { +/* long start = System.currentTimeMillis(); + for (int i = 0; i < blocks.length; i++) { + final Block block = blocks[i]; + completionService.submit(new Callable<Block>() { + public Block call() { + return block.encode(); + } + }); + } + + for (int t = 0; t < blocks.length; t++) { + Future<Block> f; + try { + f = completionService.take(); + Block block = f.get(); + block.encodeDone(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new BlockEncodeException("InterruptedException while encoding block."); + } catch (ExecutionException e) { + e.printStackTrace(); + throw new BlockEncodeException("ExecutionException while encoding block."); + } + } + long end = System.currentTimeMillis(); + System.out.println("Encoding blocks[" + blocks.length + "] took " + (end-start) + " ms."); +*/ + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoder.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..948c463bac9703ca5ea34b1a4a04a32c06efe9ed --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoder.java @@ -0,0 +1,107 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.encoder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class ScreenVideoFlvEncoder { + private final static byte[] flvHeader = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09}; + private final static byte[] videoTagType = {0x09}; + private final static byte[] streamId = {0, 0, 0}; + private long startTimestamp = 0; + private boolean firstTag = true; + + private static byte FLV_TAG_HEADER_SIZE = 11; + + ByteArrayOutputStream flvDataStream = new ByteArrayOutputStream(); + + public byte[] encodeHeader() { + byte[] prevTagSize = encodePreviousTagSize(0); + byte[] header = new byte[flvHeader.length + prevTagSize.length]; + + System.arraycopy(flvHeader, 0, header, 0, flvHeader.length); + System.arraycopy(prevTagSize, 0, header, flvHeader.length, prevTagSize.length); + return header; + } + + private byte[] encodePreviousTagSize(long previousTagSize) { + int byte1 = (int)previousTagSize >> 24; + int byte2 = (int)previousTagSize >> 16; + int byte3 = (int)previousTagSize >> 8; + int byte4 = (int)previousTagSize & 0xff; + + return new byte[] {(byte)byte1, (byte)byte2, (byte)byte3, (byte)byte4}; + } + + public byte[] encodeFlvData (ByteArrayOutputStream screenVideoData) throws FlvEncodeException { + byte[] blockData = screenVideoData.toByteArray(); + + byte[] flvData; + try { + flvData = encodeFlvTag(blockData); + } catch (IOException e) { + throw new FlvEncodeException("Failed to encode FLV data."); + } + return flvData; + } + + private byte[] encodeFlvTag(byte[] videoData) throws IOException { + + flvDataStream.reset(); + + flvDataStream.write(videoTagType); + flvDataStream.write(encodeDataSize(videoData.length)); + flvDataStream.write(encodeTimestamp()); + flvDataStream.write(streamId); + flvDataStream.write(videoData); + flvDataStream.write(encodePreviousTagSize(FLV_TAG_HEADER_SIZE + videoData.length)); + + return flvDataStream.toByteArray(); + } + + private byte[] encodeDataSize(int size) { + int byte1 = (size >> 16); + int byte2 = (size >> 8); + int byte3 = (size & 0x0ff); + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3}; + } + + private byte[] encodeTimestamp() { + long now = System.currentTimeMillis(); + + if (firstTag) { + startTimestamp = now; + firstTag = false; + } + + long elapsed = now - startTimestamp; + + int byte1 = (int)(elapsed & 0xff0000) >> 16; + int byte2 = (int)(elapsed & 0xff00) >> 8; + int byte3 = (int)(elapsed & 0xff); + int tsExtended = ((int)elapsed & 0xff000000) >> 24; + + return new byte[] {(byte) byte1, (byte) byte2, (byte) byte3, (byte) tsExtended}; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoder.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..f6cf7086a22b67bd793715ddb74ed32f6ba6504a --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoder.java @@ -0,0 +1,88 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.bigbluebutton.deskshare.common.Dimension; + +public class BlockStreamProtocolEncoder { + + private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'D', 'S'}; + private static final byte CAPTURE_START_EVENT = 0; + private static final byte CAPTURE_UPDATE_EVENT = 1; + private static final byte CAPTURE_END_EVENT = 2; + + public static void encodeStartStreamMessage(String room, Dimension screen, Dimension block, + ByteArrayOutputStream data) throws IOException { + data.write(CAPTURE_START_EVENT); + data.write(room.length()); + data.write(room.getBytes()); + data.write(intToBytes(block.getWidth())); + data.write(intToBytes(block.getHeight())); + data.write(intToBytes(screen.getWidth())); + data.write(intToBytes(screen.getHeight())); + } + + public static void encodeBlock(BlockVideoData block, ByteArrayOutputStream data) throws IOException { + data.write(CAPTURE_UPDATE_EVENT); + data.write(block.getRoom().length()); + data.write(block.getRoom().getBytes()); + + byte[] position = new byte[2]; + int pos = block.getPosition(); + position[0] = (byte)((pos >> 8) & 0xff); + position[1] = (byte)(pos & 0xff); + + data.write(position); + data.write(block.isKeyFrame() ? 1:0); + + int length = block.getVideoData().length; +// System.out.println("position=" + pos + " keyframe=" + block.isKeyFrame() + " data length=" + length); + data.write(intToBytes(length)); + + data.write(block.getVideoData()); + } + + private static byte[] intToBytes(int i) { + byte[] data = new byte[4]; + data[0] = (byte)((i >> 24) & 0xff); + data[1] = (byte)((i >> 16) & 0xff); + data[2] = (byte)((i >> 8) & 0xff); + data[3] = (byte)(i & 0xff); + return data; + } + + public static byte[] encodeHeaderAndLength(ByteArrayOutputStream data) throws IOException { + ByteArrayOutputStream header = new ByteArrayOutputStream(); + header.write(HEADER); + header.write(intToBytes(data.size())); + return header.toByteArray(); + } + + public static void encodeEndStreamMessage(String room, ByteArrayOutputStream data) throws IOException { + data.write(CAPTURE_END_EVENT); + data.write(room.length()); + data.write(room.getBytes()); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamSender.java new file mode 100644 index 0000000000000000000000000000000000000000..afbf995747fd125548a89d2ae860f5679e1ee8bd --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockStreamSender.java @@ -0,0 +1,198 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.client.blocks.Block; +import org.bigbluebutton.deskshare.client.blocks.BlockManager; +import org.bigbluebutton.deskshare.common.Dimension; + +public class BlockStreamSender implements ScreenCaptureSender { + private static final int PORT = 9123; + + private Socket socket = null; + + private DataOutputStream outStream = null; + private String room; + + private BlockingQueue<BlockVideoData> screenQ = new LinkedBlockingQueue<BlockVideoData>(500); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private BlockManager blockManager; + + private ByteArrayOutputStream dataToSend; + + private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'D', 'S'}; + private static final byte CAPTURE_START_EVENT = 0; + private static final byte CAPTURE_UPDATE_EVENT = 1; + private static final byte CAPTURE_END_EVENT = 2; + + public BlockStreamSender(BlockManager blockManager) { + this.blockManager = blockManager; + dataToSend = new ByteArrayOutputStream(); + } + + public void connect(String host, String room, int width, int height) throws ConnectionException { + this.room = room; + + System.out.println("Starting capturedScreenSender "); + try { + socket = new Socket(host, PORT); + outStream = new DataOutputStream(socket.getOutputStream()); + sendStartStreamMessage(room, blockManager.getScreenDim(), blockManager.getBlockDim()); + outStream.flush(); + } catch (UnknownHostException e) { + e.printStackTrace(); + throw new ConnectionException("UnknownHostException: " + host); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException: " + host + ":" + PORT); + } + + sendCapturedScreen = true; + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + BlockVideoData block = screenQ.take(); + +// long now = System.currentTimeMillis(); +// if ((now - block.getTimestamp()) < 500) { + sendBlock(block); +// if (screenQ.size() == 500) screenQ.clear(); +// } else { +// System.out.println("Discarding stale block."); +// } + } catch (InterruptedException e) { + System.out.println("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + } + + private void sendStartStreamMessage(String room, Dimension screen, Dimension block) { + dataToSend.reset(); + + try { + dataToSend.write(CAPTURE_START_EVENT); + dataToSend.write(room.length()); + dataToSend.write(room.getBytes()); + dataToSend.write(intToByte(block.getWidth())); + dataToSend.write(intToByte(block.getHeight())); + dataToSend.write(intToByte(screen.getWidth())); + dataToSend.write(intToByte(screen.getHeight())); + sendToStream(dataToSend); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void sendBlock(BlockVideoData block) { + long start = System.currentTimeMillis(); + dataToSend.reset(); + try { + dataToSend.write(CAPTURE_UPDATE_EVENT); + dataToSend.write(block.getRoom().length()); + dataToSend.write(block.getRoom().getBytes()); + + byte[] position = new byte[2]; + int pos = block.getPosition(); + position[0] = (byte)((pos >> 8) & 0xff); + position[1] = (byte)(pos & 0xff); + + dataToSend.write(position); + dataToSend.write(block.isKeyFrame() ? 1:0); + + int length = block.getVideoData().length; +// System.out.println("position=" + pos + " keyframe=" + block.isKeyFrame() + " data length=" + length); + dataToSend.write(intToByte(length)); + + dataToSend.write(block.getVideoData()); + sendToStream(dataToSend); + } catch (IOException e) { + e.printStackTrace(); + } + long end = System.currentTimeMillis(); + if ((end - start) > 200) { + System.out.println("Sending " + dataToSend.size() + " bytes took " + (end-start) + " ms."); + } + } + + private byte[] intToByte(int i) { + byte[] data = new byte[4]; + data[0] = (byte)((i >> 24) & 0xff); + data[1] = (byte)((i >> 16) & 0xff); + data[2] = (byte)((i >> 8) & 0xff); + data[3] = (byte)(i & 0xff); + return data; + } + + private void sendToStream(ByteArrayOutputStream data) throws IOException { +// System.out.println("Sending length " + data.size()); + outStream.write(HEADER); + outStream.writeInt(data.size()); + //outStream.write(data.toByteArray()); + data.writeTo(outStream); + } + + public void send(ByteArrayOutputStream videoData, boolean isKeyFrame) throws ConnectionException { + int totalBlocks = blockManager.getColumnCount() * blockManager.getRowCount(); + for (int i = 1; i <= totalBlocks; i++) { + Block block = blockManager.getBlock(i); +// if (block.getEncodedBlock().length < 5) continue; +// BlockVideoData blockData = new BlockVideoData(room, block.getPosition(), block.getEncodedBlock(), block.isKeyFrame()); +// try { +// screenQ.put(blockData); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + } + } + + public void disconnect() throws ConnectionException { + System.out.println("Closing connection."); + sendCapturedScreen = false; + + dataToSend.reset(); + try { + dataToSend.write(CAPTURE_END_EVENT); + dataToSend.write(room.length()); + dataToSend.write(room.getBytes()); + sendToStream(dataToSend); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockVideoData.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockVideoData.java new file mode 100644 index 0000000000000000000000000000000000000000..962e7a40939b8746e6a1fc0b19683dd34f786be4 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/BlockVideoData.java @@ -0,0 +1,59 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +public class BlockVideoData { + + private final String room; + private final byte[] videoData; + private final boolean keyFrame; + private final int position; + private final long timestamp; + + public BlockVideoData(String room, int position, byte[] videoData, boolean keyFrame) { + this.room = room; + this.position = position; + this.videoData = videoData; + this.keyFrame = keyFrame; + timestamp = System.currentTimeMillis(); + } + + public String getRoom() { + return room; + } + + public int getPosition() { + return position; + } + + public byte[] getVideoData() { + return videoData; + } + + public boolean isKeyFrame() { + return keyFrame; + } + + public long getTimestamp() { + return timestamp; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ConnectionException.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ConnectionException.java new file mode 100644 index 0000000000000000000000000000000000000000..6791f52492f448d22feb1882faf5da08b962e261 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ConnectionException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +public class ConnectionException extends Exception { + + private static final long serialVersionUID = -8836714569259091334L; + + public ConnectionException(String message) { + super(message); + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/EncodedBlockData.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/EncodedBlockData.java new file mode 100644 index 0000000000000000000000000000000000000000..b30a9cffb02b2520c9fc476192304f3738a98493 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/EncodedBlockData.java @@ -0,0 +1,59 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +public class EncodedBlockData { + + private final byte[] videoData; + private final boolean keyFrame; + private final int position; + private final long timestamp; + private final boolean hasChanged; + + public EncodedBlockData(int position, boolean hasChanged, byte[] videoData, boolean keyFrame) { + this.position = position; + this.videoData = videoData; + this.keyFrame = keyFrame; + this.hasChanged = hasChanged; + timestamp = System.currentTimeMillis(); + } + + public int getPosition() { + return position; + } + + public byte[] getVideoData() { + return videoData; + } + + public boolean isKeyFrame() { + return keyFrame; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean hasChanged() { + return hasChanged; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/FileScreenCaptureSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/FileScreenCaptureSender.java new file mode 100644 index 0000000000000000000000000000000000000000..6b11d732a545787412bddfae5f4b1420c777a113 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/FileScreenCaptureSender.java @@ -0,0 +1,69 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.bigbluebutton.deskshare.client.encoder.FlvEncodeException; +import org.bigbluebutton.deskshare.client.encoder.ScreenVideoFlvEncoder; + +public class FileScreenCaptureSender implements ScreenCaptureSender { + + private FileOutputStream fo; + private ScreenVideoFlvEncoder svf = new ScreenVideoFlvEncoder(); + + public void connect(String host, String room, int width, int height) throws ConnectionException { + try { + fo = new FileOutputStream("D://temp/"+"ScreenVideo.flv"); + + fo.write(svf.encodeHeader()); + } catch (Exception e) { + e.printStackTrace(); + throw new ConnectionException("Failed to open file."); + } + } + + public void disconnect() throws ConnectionException { + try { + System.out.println("Closing stream"); + fo.close(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("Fail to clode file."); + } + } + + public void send(ByteArrayOutputStream videoData, boolean isKeyFrame) throws ConnectionException { + try { + fo.write(svf.encodeFlvData(videoData)); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("Fail to write to file."); + } catch (FlvEncodeException e) { + e.printStackTrace(); + throw new ConnectionException("Fail to encode FLV."); + } + } + +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/HttpScreenCaptureSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/HttpScreenCaptureSender.java new file mode 100644 index 0000000000000000000000000000000000000000..dcf6e4063c402784b89cb6a6d3436dc44a154301 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/HttpScreenCaptureSender.java @@ -0,0 +1,199 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.common.CaptureEvents; + +import com.myjavatools.web.ClientHttpRequest; + +public class HttpScreenCaptureSender implements ScreenCaptureSender { + private String host = "localhost"; + private String room; + private int videoWidth; + private int videoHeight; + private int frameRate; + private static final String SCREEN_CAPTURE__URL = "/deskshare/tunnel/screenCapture"; + private URL url; + URLConnection conn; + private String videoInfo; + + private BlockingQueue<ScreenVideo> screenQ = new LinkedBlockingQueue<ScreenVideo>(); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + public void connect(String host, String room, int width, int height) throws ConnectionException { + this.host = host; + this.room = room; + this.videoWidth = videoWidth; + this.videoHeight = videoHeight; + + openConnection(); + sendCaptureStartEvent(); + +/* + System.out.println("Starting capturedScreenSender "); + sendCapturedScreen = true; + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + System.out.println("ScreenQueue size " + screenQ.size()); + ScreenVideo newScreen = screenQ.take(); + try { + sendCapturedScreen(newScreen); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (InterruptedException e) { + System.out.println("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); +*/ + } + + private void sendCapturedScreen(ScreenVideo video) throws ConnectionException { + + long snapshotTime = System.currentTimeMillis(); + ByteArrayOutputStream videoData = video.getVideoData(); + openConnection(); + sendVideoData(videoData, video.isKeyFrame()); + long completeTime = System.currentTimeMillis(); + System.out.println("Sending took " + (completeTime - snapshotTime) + "ms."); + + } + + private void openConnection() throws ConnectionException { + /** + * Need to re-establish connection each time, otherwise, + * we get java.net.ProtocolException: Cannot write output after reading input. + * + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4382944 + * + */ + try { + url = new URL("http://" + host + SCREEN_CAPTURE__URL); + conn = url.openConnection(); + } catch (MalformedURLException e) { + e.printStackTrace(); + throw new ConnectionException("MalformedURLException " + url.toString()); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while connecting to " + url.toString()); + } + } + + private void sendCaptureStartEvent() throws ConnectionException { + ClientHttpRequest chr; + try { + chr = new ClientHttpRequest(conn); + chr.setParameter("room", room); + + videoInfo = Integer.toString(videoWidth) + + "x" + Integer.toString(videoHeight); + // + "x" + Integer.toString(frameRate); + //StringBuilder sb = new StringBuilder(videoWidth); + //sb.append("x").append(videoHeight); + + chr.setParameter("videoInfo", videoInfo); + chr.setParameter("event", CaptureEvents.CAPTURE_START.getEvent()); + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending capture start event."); + } + + } + + public void disconnect() throws ConnectionException { + openConnection(); + sendCaptureEndEvent(); + } + + private void sendCaptureEndEvent() throws ConnectionException { + ClientHttpRequest chr; + try { + chr = new ClientHttpRequest(conn); + chr.setParameter("room", room); + + chr.setParameter("event", CaptureEvents.CAPTURE_END.getEvent()); + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending capture end event."); + } + } + + public void send(ByteArrayOutputStream videoData, boolean isKeyFrame) throws ConnectionException { +/* + ScreenVideo sv = new ScreenVideo(videoData, isKeyFrame); + try { + screenQ.put(sv); + } catch (InterruptedException e) { + e.printStackTrace(); + } +*/ + long snapshotTime = System.currentTimeMillis(); + openConnection(); + sendVideoData(videoData, isKeyFrame); + long completeTime = System.currentTimeMillis(); + System.out.println("Sending took " + (completeTime - snapshotTime) + "ms."); + } + + private void sendVideoData(ByteArrayOutputStream videoData, boolean isKeyFrame) throws ConnectionException { + ClientHttpRequest chr; + try { + chr = new ClientHttpRequest(conn); + chr.setParameter("room", room); + + chr.setParameter("event", CaptureEvents.CAPTURE_UPDATE.getEvent()); + chr.setParameter("keyframe", isKeyFrame); + + ByteArrayInputStream cap = new ByteArrayInputStream(videoData.toByteArray()); + + chr.setParameter("videodata", "screen", cap); + System.out.println("Video data length = " + videoData.toByteArray().length); + + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending video data."); + } + + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkHttpStreamSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkHttpStreamSender.java new file mode 100644 index 0000000000000000000000000000000000000000..07ccfac63a7345b930d8557fb7f0cba14c92c870 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkHttpStreamSender.java @@ -0,0 +1,177 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +import org.bigbluebutton.deskshare.client.blocks.Block; +import org.bigbluebutton.deskshare.common.CaptureEvents; +import org.bigbluebutton.deskshare.common.Dimension; + +import com.myjavatools.web.ClientHttpRequest; + +public class NetworkHttpStreamSender implements Runnable { + private String host = "localhost"; + private static final String SCREEN_CAPTURE__URL = "/deskshare/tunnel/screenCapture"; + private URL url; + URLConnection conn; + private String room; + private final NextBlockRetriever retriever; + private volatile boolean processBlocks = false; + + + public NetworkHttpStreamSender(NextBlockRetriever retriever) { + this.retriever = retriever; + } + + public void connect(String host) throws ConnectionException { + this.host = host; + System.out.println("Starting NetworkHttpStreamSender to " + host); + openConnection(); + } + + private void openConnection() throws ConnectionException { + /** + * Need to re-establish connection each time, otherwise, + * we get java.net.ProtocolException: Cannot write output after reading input. + * + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4382944 + * + */ + try { + url = new URL("http://" + host + SCREEN_CAPTURE__URL); + conn = url.openConnection(); + } catch (MalformedURLException e) { + e.printStackTrace(); + throw new ConnectionException("MalformedURLException " + url.toString()); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while connecting to " + url.toString()); + } + } + + public void sendStartStreamMessage(String room, Dimension screen, Dimension block) { + this.room = room; + try { + openConnection(); + sendCaptureStartEvent(screen, block); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void sendCaptureStartEvent(Dimension screen, Dimension block) throws ConnectionException { + ClientHttpRequest chr; + try { + chr = new ClientHttpRequest(conn); + chr.setParameter("room", room); + + String screenInfo = Integer.toString(screen.getWidth()) + + "x" + Integer.toString(screen.getHeight()); + + chr.setParameter("screenInfo", screenInfo); + + String blockInfo = Integer.toString(block.getWidth()) + + "x" + Integer.toString(block.getHeight()); + + chr.setParameter("blockInfo", blockInfo); + + chr.setParameter("event", CaptureEvents.CAPTURE_START.getEvent()); + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending capture start event."); + } + + } + + public void disconnect() throws ConnectionException { + try { + openConnection(); + sendCaptureEndEvent(); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw e; + } finally { + processBlocks = false; + } + } + + private void sendCaptureEndEvent() throws ConnectionException { + System.out.println("Disconnecting http stream"); + ClientHttpRequest chr; + try { + chr = new ClientHttpRequest(conn); + chr.setParameter("room", room); + + chr.setParameter("event", CaptureEvents.CAPTURE_END.getEvent()); + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending capture end event."); + } + } + + public void run() { + processBlocks = true; + + while (processBlocks) { + Block block = retriever.fetchNextBlockToSend(); + EncodedBlockData ebd = block.encode(); + if (ebd.hasChanged()) { + BlockVideoData bv = new BlockVideoData(room, ebd.getPosition(), ebd.getVideoData(), ebd.isKeyFrame()); + + sendBlockData(bv); + } + } + } + + private void sendBlockData(BlockVideoData blockData) { + ClientHttpRequest chr; + try { + openConnection(); + chr = new ClientHttpRequest(conn); + chr.setParameter("room", blockData.getRoom()); + chr.setParameter("position", blockData.getPosition()); + chr.setParameter("keyframe", blockData.isKeyFrame()); + chr.setParameter("event", CaptureEvents.CAPTURE_UPDATE.getEvent()); + ByteArrayInputStream block = new ByteArrayInputStream(blockData.getVideoData()); + + chr.setParameter("blockdata", "block", block); + chr.post(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkSocketStreamSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkSocketStreamSender.java new file mode 100644 index 0000000000000000000000000000000000000000..fcb863eb342f677cadb1172c3ce799a0f501eb99 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkSocketStreamSender.java @@ -0,0 +1,133 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.bigbluebutton.deskshare.client.blocks.Block; +import org.bigbluebutton.deskshare.common.Dimension; + +public class NetworkSocketStreamSender implements Runnable { + private static final int PORT = 9123; + + private Socket socket = null; + + private DataOutputStream outstream = null; + private String room; + private Dimension screenDim; + private Dimension blockDim; + private final NextBlockRetriever retriever; + private volatile boolean processBlocks = false; + + public NetworkSocketStreamSender(NextBlockRetriever retriever) { + this.retriever = retriever; + } + + public void connect(String host) throws ConnectionException { + System.out.println("Starting NetworkSocketStreamSender "); + try { + socket = new Socket(host, PORT); + outstream = new DataOutputStream(socket.getOutputStream()); + } catch (UnknownHostException e) { + e.printStackTrace(); + throw new ConnectionException("UnknownHostException: " + host); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException: " + host + ":" + PORT); + } + } + + public void sendStartStreamMessage(String room, Dimension screen, Dimension block) { + this.room = room; + screenDim = screen; + blockDim = block; + try { + ByteArrayOutputStream dataToSend = new ByteArrayOutputStream(); + dataToSend.reset(); + BlockStreamProtocolEncoder.encodeStartStreamMessage(room, screenDim, blockDim, dataToSend); + sendHeader(BlockStreamProtocolEncoder.encodeHeaderAndLength(dataToSend)); + sendToStream(dataToSend); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void sendBlock(BlockVideoData block) { + long start = System.currentTimeMillis(); + try { + ByteArrayOutputStream dataToSend = new ByteArrayOutputStream(); + dataToSend.reset(); + BlockStreamProtocolEncoder.encodeBlock(block, dataToSend); + sendHeader(BlockStreamProtocolEncoder.encodeHeaderAndLength(dataToSend)); + sendToStream(dataToSend); + long end = System.currentTimeMillis(); + if ((end - start) > 200) + System.out.println("Sending " + dataToSend.size() + " bytes took " + (end - start) + "ms"); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + private void sendHeader(byte[] header) throws IOException { + outstream.write(header); + } + + private void sendToStream(ByteArrayOutputStream dataToSend) throws IOException { + dataToSend.writeTo(outstream); + } + + public void disconnect() throws ConnectionException { + System.out.println("Disconnecting socket stream"); + try { + ByteArrayOutputStream dataToSend = new ByteArrayOutputStream(); + dataToSend.reset(); + BlockStreamProtocolEncoder.encodeEndStreamMessage(room, dataToSend); + sendHeader(BlockStreamProtocolEncoder.encodeHeaderAndLength(dataToSend)); + sendToStream(dataToSend); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + processBlocks = false; + } + } + + public void run() { + processBlocks = true; + while (processBlocks) { + Block block = retriever.fetchNextBlockToSend(); + EncodedBlockData ebd = block.encode(); + if (ebd.hasChanged()) { + BlockVideoData bv = new BlockVideoData(room, ebd.getPosition(), ebd.getVideoData(), ebd.isKeyFrame()); + sendBlock(bv); + } + } + + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkStreamSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkStreamSender.java new file mode 100644 index 0000000000000000000000000000000000000000..9b09b838d751ff58a6c366f48f3c9de9dc79eb84 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NetworkStreamSender.java @@ -0,0 +1,136 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.util.Map; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; + +import org.bigbluebutton.deskshare.client.blocks.Block; +import org.bigbluebutton.deskshare.client.blocks.BlockManager; + +@ThreadSafe +public class NetworkStreamSender implements NextBlockRetriever { + private BlockManager blockManager; + private ExecutorService executor; + + @GuardedBy("this") private int nextBlockToSend = 1; + private final Map<Integer, Block> blocksMap; + private final int numThreads; + private final String host; + private final String room; + private NetworkSocketStreamSender[] senders; + private NetworkHttpStreamSender httpSender; + private boolean tunneling = false; + private int numRunningThreads = 0; + + public NetworkStreamSender(BlockManager blockManager, String host, String room) { + this.blockManager = blockManager; + blocksMap = blockManager.getBlocks(); + this.host = host; + this.room = room; + + numThreads = Runtime.getRuntime().availableProcessors(); + System.out.println("Starting up " + numThreads + " sender threads."); + executor = Executors.newFixedThreadPool(numThreads); + } + + public boolean connect() { + senders = new NetworkSocketStreamSender[numThreads]; + int failedAttempts = 0; + for (int i = 0; i < numThreads; i++) { + try { + createSender(i); + numRunningThreads++; + } catch (ConnectionException e) { + failedAttempts++; + } + } + + if (failedAttempts == numThreads) { + System.out.println("Trying http tunneling"); + if (tryHttpTunneling()) { + tunneling = true; + return true; + } + } else { + return true; + } + System.out.println("Http tunneling failed."); + return false; + } + + private void createSender(int i) throws ConnectionException { + senders[i] = new NetworkSocketStreamSender(this); + senders[i].connect(host); + } + + public void start() { + if (tunneling) { + httpSender.sendStartStreamMessage(room, blockManager.getScreenDim(), blockManager.getBlockDim()); + executor.execute(httpSender); + } else { + for (int i = 0; i < numRunningThreads; i++) { + senders[i].sendStartStreamMessage(room, blockManager.getScreenDim(), blockManager.getBlockDim()); + executor.execute(senders[i]); + } + } + } + + public void stop() throws ConnectionException { + System.out.println("Stopping network sender"); + if (tunneling) { + httpSender.disconnect(); + } else { + for (int i = 0; i < numRunningThreads; i++) { + senders[i].disconnect(); + } + } + + executor.shutdownNow(); + } + + private boolean tryHttpTunneling() { + httpSender = new NetworkHttpStreamSender(this); + try { + httpSender.connect(host); + return true; + } catch (ConnectionException e) { + System.out.println("Problem connecting to " + host); + } + return false; + } + + public Block fetchNextBlockToSend() { + synchronized(this) { + //Block block = blocksMap.get(new Integer(nextBlockToSend)); + Block block = blockManager.getBlock(nextBlockToSend); +// System.out.println("Fetched block " + nextBlockToSend); + nextBlockToSend++; + if (nextBlockToSend > blocksMap.size()) nextBlockToSend = 1; + return block; + } + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NextBlockRetriever.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NextBlockRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..3894e2b141253bf45fa96da3ac3ba8ad9c4ec91a --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/NextBlockRetriever.java @@ -0,0 +1,29 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import org.bigbluebutton.deskshare.client.blocks.Block; + +public interface NextBlockRetriever { + + public Block fetchNextBlockToSend(); +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenCaptureSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenCaptureSender.java new file mode 100644 index 0000000000000000000000000000000000000000..f78e9462bb699ea8fe3c804fca1ef98b79e08e66 --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenCaptureSender.java @@ -0,0 +1,32 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; + +public interface ScreenCaptureSender { + + public void connect(String host, String room, int width, int height) throws ConnectionException; + public void send(ByteArrayOutputStream pixelData, boolean isKeyFrame) throws ConnectionException; + public void disconnect() throws ConnectionException; + +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenVideo.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenVideo.java new file mode 100644 index 0000000000000000000000000000000000000000..d306194f49ff389fa5bf249a53acc113bf25e42b --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/ScreenVideo.java @@ -0,0 +1,43 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; + +public class ScreenVideo { + + private ByteArrayOutputStream videoData; + private boolean isKeyFrame; + + public ScreenVideo(ByteArrayOutputStream videoData, boolean isKeyFrame) { + this.videoData = videoData; + this.isKeyFrame = isKeyFrame; + } + + public ByteArrayOutputStream getVideoData() { + return videoData; + } + + public boolean isKeyFrame() { + return isKeyFrame; + } +} diff --git a/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/SocketScreenVideoSender.java b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/SocketScreenVideoSender.java new file mode 100644 index 0000000000000000000000000000000000000000..de89058b6b5fb5937b45442276b60c53f080c7da --- /dev/null +++ b/deskshare/applet/src/main/java/org/bigbluebutton/deskshare/client/net/SocketScreenVideoSender.java @@ -0,0 +1,180 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.bigbluebutton.deskshare.common.CaptureEvents; + +public class SocketScreenVideoSender implements ScreenCaptureSender { + + private static final int PORT = 9123; + + private Socket socket = null; + + private DataOutputStream outStream = null; + private String room; + private int width, height; + + private BlockingQueue<ScreenVideo> screenQ = new LinkedBlockingQueue<ScreenVideo>(2); + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable capturedScreenSender; + private volatile boolean sendCapturedScreen = false; + + private static final byte[] HEADER = new byte[] {0x42, 0x42, 0x42, 0x2D, 0x44, 0x53}; /* BBB-DS */ + + public void connect(String host, String room, int width, int height) throws ConnectionException { + this.room = room; + this.width = width; + this.height = height; + + try { + socket = new Socket(host, PORT); + outStream = new DataOutputStream(socket.getOutputStream()); + sendHeaderEventAndRoom(CaptureEvents.CAPTURE_START.getEvent()); + sendScreenCaptureInfo(width, height); + outStream.flush(); + } catch (UnknownHostException e) { + e.printStackTrace(); + throw new ConnectionException("UnknownHostException: " + host); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException: " + host + ":" + PORT); + } + + System.out.println("Starting capturedScreenSender "); + sendCapturedScreen = true; + capturedScreenSender = new Runnable() { + public void run() { + while (sendCapturedScreen) { + try { + //System.out.println("ScreenQueue size " + screenQ.size()); + ScreenVideo newScreen = screenQ.take(); + try { + sendCapturedScreen(newScreen); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (InterruptedException e) { + System.out.println("InterruptedExeption while taking event."); + } + } + } + }; + exec.execute(capturedScreenSender); + + } + + private void sendScreenCaptureInfo(int videoWidth, int videoHeight) throws IOException { + String videoInfo = Integer.toString(videoWidth) + "x" + Integer.toString(videoHeight); + System.out.println("Sending video info " + videoInfo); + outStream.writeInt(videoInfo.length()); + outStream.writeBytes(videoInfo); + } + + private void sendCapturedScreen(ScreenVideo video) throws ConnectionException { + + try { + long snapshotTime = System.currentTimeMillis(); + ByteArrayOutputStream videoData = video.getVideoData(); + sendHeaderEventAndRoom(CaptureEvents.CAPTURE_UPDATE.getEvent()); + sendKeyFrame(video.isKeyFrame()); + System.out.println("Sending videoData [length=" + videoData.size() + ", keyframe=" + video.isKeyFrame() + "]"); + sendDataLength(videoData.size()); + sendData(videoData); + outStream.flush(); + long completeTime = System.currentTimeMillis(); + System.out.println("Sending took " + (completeTime - snapshotTime) + "ms."); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending video data."); + } + + } + + public void send(ByteArrayOutputStream videoData, boolean isKeyFrame) throws ConnectionException { + + ScreenVideo sv = new ScreenVideo(videoData, isKeyFrame); + try { + screenQ.put(sv); + } catch (InterruptedException e) { + e.printStackTrace(); + } + +/* + try { + long snapshotTime = System.currentTimeMillis(); + sendHeaderEventAndRoom(CaptureEvents.CAPTURE_UPDATE.getEvent()); + sendKeyFrame(isKeyFrame); + System.out.println("Sending videoData [" + videoData.size() + "," + isKeyFrame + "]"); + sendDataLength(videoData.size()); + sendData(videoData); + long completeTime = System.currentTimeMillis(); + System.out.println("Sending took " + (completeTime - snapshotTime) + "ms."); + } catch (IOException e) { + e.printStackTrace(); + throw new ConnectionException("IOException while sending video data."); + } +*/ + } + + private void sendHeaderEventAndRoom(int event) throws IOException { + outStream.write(HEADER); + outStream.writeByte(event); + outStream.writeInt(room.length()); + outStream.writeBytes(room); + } + + private void sendKeyFrame(boolean isKeyFrame) throws IOException { + outStream.writeBoolean(isKeyFrame); + } + + private void sendDataLength(int length) throws IOException { + outStream.writeInt(length); + } + + private void sendData(ByteArrayOutputStream pixels) throws IOException { + //outStream.write(pixels.toByteArray()); + pixels.writeTo(outStream); + } + + public void disconnect() throws ConnectionException { + System.out.println("Closing connection."); + sendCapturedScreen = false; + try{ + sendHeaderEventAndRoom(CaptureEvents.CAPTURE_END.getEvent()); + socket.close(); + } catch(IOException e){ + e.printStackTrace(System.out); + throw new ConnectionException("IOException while disconnecting from server."); + } + } +} diff --git a/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encode/ScreenVideoFlvEncoderTest.java b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encode/ScreenVideoFlvEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..20ebc7bed6fe7c00c45936f84deb0e4564141ca5 --- /dev/null +++ b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encode/ScreenVideoFlvEncoderTest.java @@ -0,0 +1,48 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ + +package org.bigbluebutton.deskshare.client.encode; + +import org.bigbluebutton.deskshare.client.encoder.ScreenVideoFlvEncoder; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class ScreenVideoFlvEncoderTest { + + @BeforeMethod + public void setUp() { + + } + + @Test + public void testEncodeFlvHeader() { + final byte[] flvHeaderPlusPrevTagSize = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09,0,0,0,0}; + ScreenVideoFlvEncoder svf = new ScreenVideoFlvEncoder(); + + byte[] header = svf.encodeHeader(); + Assert.assertEquals(header, flvHeaderPlusPrevTagSize); + } + + + +} diff --git a/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoderTest.java b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f806965953658fd2c071def7e11a26613817aeed --- /dev/null +++ b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/encoder/ScreenVideoFlvEncoderTest.java @@ -0,0 +1,48 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ + +package org.bigbluebutton.deskshare.client.encoder; + +import org.bigbluebutton.deskshare.client.encoder.ScreenVideoFlvEncoder; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class ScreenVideoFlvEncoderTest { + + @BeforeMethod + public void setUp() { + + } + + @Test + public void testEncodeFlvHeader() { + final byte[] flvHeaderPlusPrevTagSize = {'F','L','V',0x01,0x01,0x00,0x00,0x00,0x09,0,0,0,0}; + ScreenVideoFlvEncoder svf = new ScreenVideoFlvEncoder(); + + byte[] header = svf.encodeHeader(); + Assert.assertEquals(header, flvHeaderPlusPrevTagSize); + } + + + +} diff --git a/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoderTest.java b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e3604c79a5c37bbd642b0d99a9bcb0172cc38547 --- /dev/null +++ b/deskshare/applet/src/test/java/org/bigbluebutton/deskshare/client/net/BlockStreamProtocolEncoderTest.java @@ -0,0 +1,48 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.client.net; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.bigbluebutton.deskshare.common.Dimension; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class BlockStreamProtocolEncoderTest { + + @BeforeMethod + public void setUp() { + + } + + @Test + public void testEncodeFlvHeader() throws IOException { + ByteArrayOutputStream data = new ByteArrayOutputStream(); + data.reset(); + BlockStreamProtocolEncoder.encodeStartStreamMessage("testroom", new Dimension(64,64), new Dimension(32,32), data); + Assert.assertEquals(data.size(), 26); +// BlockStreamProtocolEncoder.encodeHeaderAndLength(data); + + } +} diff --git a/deskshare/applet/tstcert.crt b/deskshare/applet/tstcert.crt new file mode 100644 index 0000000000000000000000000000000000000000..b3c6a7657645c50d8dd5396bbb5a7f44769ca3df Binary files /dev/null and b/deskshare/applet/tstcert.crt differ diff --git a/deskshare/build.gradle b/deskshare/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..2727b858156a52c3975aca2721d936facae2ea0b --- /dev/null +++ b/deskshare/build.gradle @@ -0,0 +1,62 @@ +usePlugin 'java' +usePlugin 'eclipse' + +task copyToLib(dependsOn: configurations.default.buildArtifacts, type: Copy) { + into('lib') + from configurations.default + from configurations.default.allArtifacts*.file +} + +dependencies { + compile ':log4j-over-slf4j:1.5.6@jar', 'spring:spring-web:2.5.6@jar', 'javax.servlet:servlet-api:2.5@jar', 'org.apache.mina:mina-core:2.0.0-M6@jar' + compile 'spring:spring-aop:2.5.6@jar', 'spring:spring-beans:2.5.6@jar', 'spring:spring-context:2.5.6@jar', 'spring:spring-core:2.5.6@jar' + compile 'org/red5:red5:0.8@jar' + compile 'commons-fileupload:commons-fileupload:1.2.1@jar', 'commons-io:commons-io:1.4@jar' + compile ':logback-core:0.9.14@jar', ':logback-classic:0.9.14@jar', ':slf4j-api:1.5.6@jar' + compile 'spring:spring-webmvc:2.5.6@jar', 'org.apache.mina:mina-integration-spring:1.1.7@jar' + compile 'org.testng:testng:5.8@jar', 'net/jcip:jcip-annotations:1.0@jar' +} + +subprojects { + usePlugin 'java' + usePlugin 'eclipse' + + repositories { + add(new org.apache.ivy.plugins.resolver.ChainResolver()) { + name = 'remote' + returnFirst = true + add(new org.apache.ivy.plugins.resolver.URLResolver()) { + name = "googlecode" + addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]" + addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]" + } + add(new org.apache.ivy.plugins.resolver.URLResolver()) { + name = "blindside-repos" + addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]" + addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]" + } + add(new org.apache.ivy.plugins.resolver.URLResolver()) { + name = "maven2-central" + m2compatible = true + addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]" + addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]" + } + add(new org.apache.ivy.plugins.resolver.URLResolver()) { + name = "testng_ibiblio_maven2" + m2compatible = true + addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision])-jdk15.[ext]" + addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision])-jdk15.[ext]" + } + } + } + + dependencies { + testCompile 'org.testng:testng:5.8' + } + + group = 'org.bigbluebutton' + version = '0.62' + manifest.mainAttributes(provider: 'bigbluebutton') +} + +dependsOnChildren() diff --git a/deskshare/common/build.gradle b/deskshare/common/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..0e18ceb9a7a92d36f888a27ea98bf08206761dd7 --- /dev/null +++ b/deskshare/common/build.gradle @@ -0,0 +1,2 @@ + +archivesBaseName = 'bbb-deskshare-common' \ No newline at end of file diff --git a/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/CaptureEvents.java b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/CaptureEvents.java new file mode 100644 index 0000000000000000000000000000000000000000..e058456b9cdc7a247beb5876462c93e6a558b5b6 --- /dev/null +++ b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/CaptureEvents.java @@ -0,0 +1,54 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.common; + +public enum CaptureEvents { + /** + * WARNING: Must match corresponding values with deskshare-app on the server. + * org.bigbluebutton.deskshare.CaptureEvents + */ + CAPTURE_START(0), CAPTURE_UPDATE(1), CAPTURE_END(2); + + private final int event; + + CaptureEvents(int event) { + this.event = event; + } + + public int getEvent() { + return event; + } + + @Override + public String toString() { + switch (event) { + case 0: + return "Capture Start Event"; + case 1: + return "Capture Update Event"; + case 2: + return "Capture End Event"; + } + + return "Unknown Capture Event"; + } +} diff --git a/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/Dimension.java b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/Dimension.java new file mode 100644 index 0000000000000000000000000000000000000000..52ffd64277cb53ebeced28d271d0d5420b31ca22 --- /dev/null +++ b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/Dimension.java @@ -0,0 +1,43 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.common; + +public final class Dimension { + + private final int width; + private final int height; + + public Dimension(int width, int height) { + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + +} diff --git a/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/PixelExtractException.java b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/PixelExtractException.java new file mode 100644 index 0000000000000000000000000000000000000000..c637c0251476ad47c65ae139901f35eb6241bc4e --- /dev/null +++ b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/PixelExtractException.java @@ -0,0 +1,31 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.common; + +public class PixelExtractException extends Exception { + + private static final long serialVersionUID = -8836714569259091334L; + + public PixelExtractException(String message) { + super(message); + } +} diff --git a/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/ScreenVideoEncoder.java b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/ScreenVideoEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..5956ccccf88df19aae6cf8b28b36fd1a5a250d50 --- /dev/null +++ b/deskshare/common/src/main/java/org/bigbluebutton/deskshare/common/ScreenVideoEncoder.java @@ -0,0 +1,233 @@ +/* + * BigBlueButton - http://www.bigbluebutton.org + * + * Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + * + * BigBlueButton is free software; you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + * + * Author: Richard Alam <ritzalam@gmail.com> + * + * $Id: $x + */ +package org.bigbluebutton.deskshare.common; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.util.zip.Deflater; + +public final class ScreenVideoEncoder { + + private static byte FLV_KEYFRAME = 0x10; + private static byte FLV_INTERFRAME = 0x20; + private static byte SCREEN_VIDEO_CODEC_ID = 0x03; + + public static int[] getPixelsFromImage(BufferedImage image) { + int[] picpixels = ((DataBufferInt)(image).getRaster().getDataBuffer()).getData(); + + return picpixels; + } + + public static byte encodeFlvVideoDataHeader(boolean isKeyFrame) { + if (isKeyFrame) return (byte) (FLV_KEYFRAME + SCREEN_VIDEO_CODEC_ID); + return (byte) (FLV_INTERFRAME + SCREEN_VIDEO_CODEC_ID); + } + + public static byte[] encodeBlockAndScreenDimensions(int blockWidth, int imageWidth, int blockHeight, int imageHeight) { + byte[] dims = new byte[4]; + + int bw = (((blockWidth/16) - 1) & 0xf) << 12; + int iw = (imageWidth & 0xfff); + int ew = (bw | iw); + + int bh = (((blockHeight/16) - 1) & 0xf) << 12; + int ih = (imageHeight & 0xfff); + int eh = (bh | ih); + + dims[0] = (byte) ((ew & 0xff00) >> 8); + dims[1] = (byte) (ew & 0xff); + dims[2] = (byte) ((eh & 0xff00) >> 8); + dims[3] = (byte) (eh & 0xff); + + return dims; + } + + public static int[] getPixels(BufferedImage image, int x, int y, int width, int height) throws PixelExtractException { + long start = System.currentTimeMillis(); + + /* Use this!!! Fast!!! (ralam Oct. 14, 2009) */ + int[] pixels = image.getRGB(x, y, width, height, null, 0, width); + + /* DO NOT user this. Slow. 10 times slower than the other one. */ +/* + PixelGrabber pg = new PixelGrabber(image, x, y, width, height, pixels, 0, width); + try { + pg.grabPixels(); + } catch (InterruptedException e) { + throw new PixelExtractException("Interrupted waiting for pixels!"); + } + if ((pg.getStatus() & ImageObserver.ABORT) != 0) { + throw new PixelExtractException("Aborted or error while fetching image."); + } +*/ + long end = System.currentTimeMillis(); +// System.out.println("Grabbing pixels[" + pixels.length + "] took " + (end-start) + " ms."); + return pixels; + } + + public static byte[] encodePixels(int pixels[], int width, int height, boolean isRedTile, boolean isKeyFrame) { + + changePixelScanFromBottomLeftToTopRight(pixels, width, height); + + byte[] bgrPixels = convertFromRGBtoBGR(pixels, isRedTile, isKeyFrame); + + byte[] compressedPixels = compressUsingZlib(bgrPixels); + + byte[] encodedDataLength = ScreenVideoEncoder.encodeCompressedPixelsDataLength(compressedPixels.length); + byte[] encodedData = new byte[encodedDataLength.length + compressedPixels.length]; + + System.arraycopy(encodedDataLength, 0, encodedData, 0, encodedDataLength.length); + + System.arraycopy(compressedPixels, 0, encodedData, encodedDataLength.length, compressedPixels.length); + + return encodedData; + } + + private static byte[] encodeCompressedPixelsDataLength(int length) { + int byte1 = ((length & 0xFF00) >> 8); + int byte2 = (length & 0x0FFF); +// System.out.println("Block size = " + length + " hex=" + Integer.toHexString(length) + " bytes= " + Integer.toHexString(byte1) + " " + Integer.toHexString(byte2)); + return new byte[] {(byte)byte1 , (byte) (byte2 &0xFFF) }; + } + + public static byte[] encodeBlockUnchanged() { + return new byte[] { (byte) 0, (byte) 0 }; + } + + /** + * Screen capture pixels are arranged top-left to bottom-right. ScreenVideo encoding + * expects pixels are arranged bottom-left to top-right. + * @param pixels - contains pixels of the image + * @param width - width of the image + * @param height - height of the image + */ + private static void changePixelScanFromBottomLeftToTopRight(int[] pixels, int width, int height) { + int[] swap = new int[pixels.length]; + + long start = System.currentTimeMillis(); + for (int i = 0; i < height; i++) { + int sourcePos = i * width; + int destPos = (height - (i+1)) * width; + System.arraycopy(pixels, sourcePos, swap, destPos, width); + } + + System.arraycopy(swap, 0, pixels, 0, pixels.length); + long end = System.currentTimeMillis(); +// System.out.println("Scanning pixels[" + pixels.length + "] took " + (end-start) + " ms."); + } + + + /** + * Compress the byte array using Zlib. + * @param pixels + * @return a byte array of compressed data + */ + private static byte[] compressUsingZlib(byte[] pixels) { + long start = System.currentTimeMillis(); + // Create the compressed stream + byte[] output = new byte[pixels.length]; + Deflater compresser = new Deflater(Deflater.BEST_COMPRESSION); + compresser.setInput(pixels); + compresser.finish(); + int compressedDataLength = compresser.deflate(output); + + byte[] zData = new byte[compressedDataLength] ; + System.arraycopy(output, 0, zData, 0, compressedDataLength); + + long end = System.currentTimeMillis(); +// System.out.println("Compressing pixels[" + pixels.length + "] took " + (end-start) + " ms."); + + // set the byte array to the newly compressed data + return zData; + } + + /** + * Extracts the RGB bytes from a pixel represented by a 4-byte integer (ARGB). + * @param pixels + * @return pixels in BGR order + */ + private static byte[] convertFromRGBtoBGR(int[] pixels, boolean isRedTile, boolean isKeyFrame) { + long start = System.currentTimeMillis(); + byte[] rgbPixels = new byte[pixels.length * 3]; + int position = 0; + + for (int i = 0; i < pixels.length; i++) { + byte red = (byte) ((pixels[i] >> 16) & 0xff); + byte green = (byte) ((pixels[i] >> 8) & 0xff); + byte blue = (byte) (pixels[i] & 0xff); + + if (isRedTile && !isKeyFrame) { + // Sequence should be BGR + rgbPixels[position++] = 0; + rgbPixels[position++] = 0; + rgbPixels[position++] = (byte) 0xff; + } else if (isRedTile && isKeyFrame) { + rgbPixels[position++] = (byte) 0xff; + rgbPixels[position++] = 0; + rgbPixels[position++] = 0; + } else { + // Sequence should be BGR + rgbPixels[position++] = blue; + rgbPixels[position++] = green; + rgbPixels[position++] = red; + } + } + + long end = System.currentTimeMillis(); +// System.out.println("Extracting pixels[" + pixels.length + "] took " + (end-start) + " ms."); + return rgbPixels; + } + + public static String toStringBits( int value ) + { + int displayMask = 1 << 31; + StringBuffer buf = new StringBuffer( 35 ); + + for ( int c = 1; c <= 32; c++ ) + { + buf.append( ( value & displayMask ) == 0 ? '0' : '1' ); + value <<= 1; + + if ( c % 8 == 0 ) + buf.append( ' ' ); + } + + return buf.toString(); + } + + public static String toStringBits( byte value ) + { + int displayMask = 1 << 7; + StringBuffer buf = new StringBuffer( 8 ); + + for ( int c = 1; c <= 8; c++ ) + { + buf.append( ( value & displayMask ) == 0 ? '0' : '1' ); + value <<= 1; + + if ( c % 8 == 0 ) + buf.append( ' ' ); + } + + return buf.toString(); + } +} diff --git a/deskshare/settings.gradle b/deskshare/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..4b7f7a310cf41f42f8c33e6eb3f11f67af19d770 --- /dev/null +++ b/deskshare/settings.gradle @@ -0,0 +1 @@ +include 'applet', 'app', 'common'