/* The demo application for the RTP/I lib, version 0.1 alpha.
*
* Copyright (C) 2000 Juergen Vogel, Martin Mauve
* University of Mannheim / Germany
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Juergen Vogel
* Martin Mauve
*
* e-mail:
* {vogel,mauve}@informatik.uni-mannheim.de
*
* paper mail:
*
* Juergen Vogel
* Martin Mauve
* University of Mannheim
* Lehrstuhl Praktische Informatik IV
* L15, 16
* 68131 Mannheim
* Germany
*/
package rtpi.demoRtpi;
import rtpi.util.SyncQueue;
import rtpi.Rtpi;
import rtpi.RtpiEvent;
import rtpi.RtpiState;
import rtpi.RtpiDeltaState;
import rtpi.RtpiStateQuery;
import rtpi.RtpiSourceInfo;
import rtpi.RtpiRecipient;
import rtpi.reliability.Reliable;
import rtpi.reliability.unreliableUdpMulticast.UnreliableUdpMulticast;
import rtpi.transport.Transport;
import rtpi.transport.ipmc.IPMCTransport;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Enumeration;
/**
* This is a demo application that shows how RTP/I can be used.
* The demo can be used to see how the API works.
* It is important to realize that it is missing
* several pieces of functionality which are vital for a real application.
* In particular it does not ensure consistency. There are two things that
* could destroy the consistency for this application. (1) When an event
* gets lost. (2) When an event overtakes the state transmission.
* Furthermore the allocation of participant IDs and subcomponent IDs is
* rather simplistic. The IP address is used as participant ID and as base
* value for subcomponent IDs. For each created subcomponent the subcomponent
* ID counter is increase.
* In short please use this to see how the API works but don't take it as a
* working example for a distributed interactive medium.
*/
public class Application implements RtpiRecipient {
private static final int BYTE_MASK = 0x000000FF;
static final boolean debug = true;
private Rtpi rtpi; // The main RTP/I session
private RtpiSourceInfo localParticipant; // The source information that is to be used for the main RTP/I session
private long subIDBase; // The lower 32 bits for sub componenets that are created by this participant
private long subcomponentNumber; // The number of the last subcomponent that has been created by this participant;
private Reliable reliabilityService; // The reliability service for the main RTP/I session
private SyncQueue messages = new SyncQueue(); // The message queue for message passing
private ApplicationView view; // The gui
private Hashtable subcomponents = new Hashtable(); // The subcomponents
/**
* This creates a new application.
*
* @param appmcAddress The multicast address for the main RTP/I session.
* @param port The port for RTP/I in the main RTP/I session. port+1 is used for RTCP/I in the
* main RTCP/I session.
* @param ttl The time to live.
*/
public Application(String appmcAddress, int port, int ttl) {
String cname="";
InetAddress myIPAddress=null;
try {
myIPAddress = InetAddress.getLocalHost();
} catch(UnknownHostException e) {
System.out.println("Application::Application - Could not get local InetAddress - exiting!");
System.exit(0);
}
try {
if ((cname=System.getProperty("user.name"))==null) {
cname="unknown";
}
cname += "@"+myIPAddress.getHostName(); // construct the cname for the local participant
} catch (Exception ex) {
System.err.println("Controler: Unable to construct RTP cname for local participant - exiting! "+ex);
System.exit(0);
}
byte[] baseBytes = myIPAddress.getAddress();
subIDBase=(baseBytes[3]&BYTE_MASK) |
((baseBytes[2]&BYTE_MASK) << 8) |
((baseBytes[1]&BYTE_MASK) << 16) |
((baseBytes[0]&BYTE_MASK) << 24);
subIDBase=subIDBase & 0xFFFFFFFFl; /* subIDBase == the IP address. We use this also as Participant ID
* it would be better to get the participant ID from somewhere else.
* However, for this simple example it should suffice.
*/
try {
localParticipant = new RtpiSourceInfo((int)subIDBase, cname);
} catch (Exception ex) {
System.err.println("Controler: Could not create local participant - exiting!");
System.exit(0);
}
InetAddress group=null;
try {
group = InetAddress.getByName(appmcAddress);
if (!group.isMulticastAddress()) {
System.out.println("Usage: java rtpi.demoRtpi.Application " +
"<multicast address> <port number> <ttl>");
System.exit(0);
}
} catch (Exception ex) {
System.err.println("Invalid multicast address - exiting!");
System.exit(0);
}
UnreliableUdpMulticast reliabilityService=null;
Transport rtcpTransport=null;
try {
reliabilityService=new UnreliableUdpMulticast(group, port, ttl, 5000000, 10000);
rtcpTransport = new IPMCTransport(group, port+1, ttl, 5000000);
} catch (Exception ex) {
System.out.println("Could not create Transport and reliability service instances - exiting!");
System.exit(0);
}
System.out.println("reliablitiyService " + reliabilityService);
rtpi = new Rtpi(reliabilityService, // The reliability service used for the RTP/I session
localParticipant, // The source info of the local source
rtcpTransport, // The transport instnce for RTCP/I
2000, // The maximum available bandwidth for RTCP/I
true); // We will use application level names
rtpi.setRtpiRecipient(this);
try {
rtpi.joinGroup(); // We are responsible for joining and leaving the main RTP/I session
} catch (Exception ex) {
System.err.println("Controler: could not join session - exiting!");
System.exit(0);
}
view = new ApplicationView(this, cname); // Init the GUI
}
/**
* This creates a new subcomponent. The state of the subcomponent is immediately transmitted.
*
* @return The new subcomponent.
*/
Subcomponent createSubcomponent() {
long id = subIDBase | ((subcomponentNumber++)<<32); // create a 'unique' subcomponent ID
String name = "Sub"+id; // the application level name
Subcomponent comp = new Subcomponent(this, name, id);
comp.setStateReceived();
subcomponents.put(new Long(id), comp);
// register new SubComponent and transmit its state
try {
System.out.println("trying to add new subcomponent to RTPI");
rtpi.addSubcomponent(id, comp.getNameBytes(), true); // this tells RTP/I that we are tracking the state of this subcomponent
// and that it is active. This is used for RTCP/I subreps
} catch (Exception ex) {
System.err.println("Application: Unable to add subcomponent to RTP/I - exiting!");
System.exit(0);
}
RtpiState state=null;
try {
state = new RtpiState(localParticipant.getParticipantID(), // The participant ID of the local source
id, // The subcomponent ID
comp.getNextStateSequenceNumber(), // The sequence number of this state ADU
10, // The payload type
3, // The priority (3=max priority, this should force everyone to reset the state of the subcomponent)
System.currentTimeMillis(), // The timestamp of the ADU
rtpi.getCombinedHeaderSize()); // The headersize of RTP/I and the reliability service.
} catch (Exception ex) {
System.err.println("Application: Could not create rtpi state for transmission - exiting!");
System.exit(0);
}
try {
OutputStream os = state.getOutputStream(); // Get the output stream for this state
os.write(comp.getNameBytes()); // this is already an UTF encoded string - see constructor of Subcomponent!
// The recipient of the state should be able to derive the application level name of
// the subcomponent.
os.write(comp.getState());
} catch(Exception e) {
System.out.println("Application::createComponent - exception while writing packet "+e);
System.exit(0);
}
state.outputComplete(); // Finish the output
rtpi.transmitState(state); // Let RTP/I do the rest.
return comp;
}
Subcomponent getSubcomponent(long id) {
return (Subcomponent)subcomponents.get(new Long(id));
}
/**
* This transmits an increment event for a subcomponent.
*
* @param id The subcomponent ID of the affected subcomponent.
*/
void sendIncrEvent(long id) {
Subcomponent sub = (Subcomponent) subcomponents.get(new Long(id));
if (sub==null) {
System.err.println("Received event for non existing subcomponent - ignoring event!");
return;
}
RtpiEvent event=null;
try {
event = new RtpiEvent(localParticipant.getParticipantID(), // The participant ID of the local source
id, // The subcomponent ID
sub.getNextEventSequenceNumber(), // The sequence number of this event ADU
10, // The payload type
System.currentTimeMillis(), // The timestamp of this event
rtpi.getCombinedHeaderSize()); // The combined header size of the RTP/I and the relibaility service headers
} catch (Exception ex) {
System.err.println("Could not create rtpi event for transmission - exiting!");
System.exit(0);
}
try {
event.getOutputStream().write(1); // Write the event data
} catch(IOException e) {
System.out.println("Application::SendIncrEvent - exception while writing packet "+e);
System.exit(0);
}
event.outputComplete(); // Finish the output to the event
rtpi.transmitEvent(event); // Use RTP/I to transmit the event.
}
/**
* This is called by the GUI when the user activates a formerly deactivated subcomponent.
*
* @param id The subcomponent ID of the affected subcomponent.
*/
void activateSubcomponent(long id) {
System.out.println("Activating subid: "+id);
rtpi.activateSubcomponent(id); // Tell RTP/I that this subcomponent is now active for this participant
}
/**
* This is called by the GUI when the user deactivates a formerly activated subcomponent.
*
* @param id The subcomponent ID of the affected subcomponent.
*/
void deactivateSubcomponent(long id) {
System.out.println("Deactivating subid: "+id);
rtpi.deactivateSubcomponent(id); // Tell RTP/I that this subcomponent is no longer active for this participant
}
/**
* This function is used for message passing between the application and other threads. It should prevent
* race conditions and deadlocks. Does anyone have a better solution for this? Other threads place messages
* into the message queue. The application simply gets those messages.
*/
public void work() {
ApplicationMessage message = null;
while (true) {
try {
message = (ApplicationMessage)messages.get();
} catch (Exception ex) {
System.err.println("Application::work - could not get next message - exiting!");
System.exit(0);
}
switch(message.opCode) {
case ApplicationMessage.RECEIVE_EVENT: {
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveEvent(args.rtpi, (RtpiEvent)args.data);
break;
}
case ApplicationMessage.RECEIVE_STATE: {
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveState(args.rtpi, (RtpiState)args.data);
break;
}
case ApplicationMessage.RECEIVE_DELTA_STATE: {
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveDeltaState(args.rtpi, (RtpiDeltaState)args.data);
break;
}
case ApplicationMessage.RECEIVE_STATE_QUERY: {
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveStateQuery(args.rtpi, (RtpiStateQuery)args.data);
break;
}
}
}
}
/**
* This is called by RTP/I when an event has been received. We put this message in the message
* queue and execute the event in the application thread.
*
* @param r The RTP/I instance from which the event has been received.
* @param event The RTP/I event.
*/
public void receiveEvent(Rtpi r, RtpiEvent event) { // this is called by the RTP/I thread
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_EVENT,
new ApplicationMessageArgs(r, event)));
}
private void execReceiveEvent(Rtpi r, RtpiEvent event) { // this is called in the application thread
Subcomponent comp = getSubcomponent(event.getSubcomponentID());
if (comp == null) { // If we have no state yet we ignore the event.
System.out.println("Application::receiveEvent - unknown subcomponent !");
return;
}
comp.setState((byte)(comp.getState() + 1)); // set the new state. We don't need to examine the event more closely since we have only
// one possible event.
}
public void receiveState(Rtpi r, RtpiState state) {
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_STATE,
new ApplicationMessageArgs(r, state)));
}
private void execReceiveState(Rtpi r, RtpiState state) {
System.out.println("Application: State received!");
long id = state.getSubcomponentID();
Subcomponent comp = getSubcomponent(id);
byte stateValue=0;
String subcomponentName=null;
DataInputStream din = new DataInputStream (state.getInputStream()); // Get the data input stream of the state
try {
subcomponentName = din.readUTF(); // Read the application level name
stateValue = (byte) din.read(); // Read the state
} catch (Exception ex) {
System.out.println("Application: Error while reading data from received state - exiting!");
System.exit(0);
}
if (comp == null) { // new Component
comp = new Subcomponent(this, subcomponentName, id);
subcomponents.put(new Long(id), comp);
comp.setState(stateValue);
comp.setStateReceived();
try {
rtpi.addSubcomponent(id, comp.getNameBytes(), true); // Tell RTP/I that the new subcomponent is now tracked by this participant.
} catch (Exception ex) {
System.err.println("Application could not add subcomponent to rtpi: "+ex+" - exiting!");
System.exit(0);
}
view.addSubcomponent(comp);
if (debug) {
System.out.println("Application::receiveState - " +
"received State for new Component with ID " + id);
}
return;
}
if (comp.getStateReceived()) { // We have received a state that we do not need (since we already have it)
if (debug)
System.out.println("Application::receiveState - " +
"Component with ID " + id + " already exists "
+ System.currentTimeMillis());
return;
}
comp.setState(stateValue);
comp.setStateReceived();
try {
rtpi.addSubcomponent(id, comp.getNameBytes(), true);
} catch (Exception ex) {
System.err.println("Application could not add subcomponent to rtpi: "+ex);
System.exit(0);
}
view.addSubcomponent(comp);
if (debug) {
System.out.println("Application::receiveState - received State for Component with ID " + id);
}
}
public void receiveDeltaState(Rtpi r, RtpiDeltaState state) { // We don't use these!
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_DELTA_STATE,
new ApplicationMessageArgs(r, state)));
}
private void execReceiveDeltaState(Rtpi r, RtpiDeltaState state) {
}
public void receiveStateQuery(Rtpi r, RtpiStateQuery query) { // We do not answer state queries, since we are not
// concerned with consistency for this demo application.
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_STATE_QUERY,
new ApplicationMessageArgs(r, query)));
}
private void execReceiveStateQuery(Rtpi r, RtpiStateQuery query) {
}
public void close() {
rtpi.leaveGroup();
System.exit(0);
}
public static void main(String[] args){
if (args.length!=3) {
System.out.println("Usage: java rtpi.demoRtpi.Application " +
"<multicast address> <port number> <ttl>");
System.exit(0);
}
String address1 = args[0];
int port = 0;
int ttl=0;
try {
port = Integer.parseInt(args[1]);
} catch (Exception ex) {
System.out.println("Usage: java rtpi.demoRtpi.Application " +
"<multicast address> <port number> <ttl>");
System.exit(0);
}
try {
ttl = Integer.parseInt(args[2]);
} catch (Exception ex) {
System.out.println("Usage: java rtpi.demoRtpi.Application " +
"<multicast address> <port number> <ttl>");
System.exit(0);
}
new Application(address1, port, ttl).work();
}
public void rtpiAduLost(Rtpi rtpi, int participantID, long subID, int type, int sequenceNumber, int timestamp) {
System.out.println("Application: rtpiAduLost called!");
}
public void rtpiCouldNotRecover(Rtpi rtpi, int participantID, long subID, int type, int sequenceNumber, int timestamp) {
System.out.println("Application: rtpiCouldNotRecover called!");
}
public void connectionClosed(Rtpi rtpi) {
System.out.println("Application: connectionClosed called - exiting!");
System.exit(0);
}
public void changeSourceInfo(Rtpi rtpi, RtpiSourceInfo participant) {
System.out.println("Application: changeSourceInfo called!");
}
public void removeSource(Rtpi rtpi, RtpiSourceInfo participant) {
System.out.println("Application: removeSource called!");
}
public void addSubcomponent(Rtpi rtpi, long subcomponentID, boolean active, byte[] name) {
System.out.println("Application: addSubcomponent called!");
}
public void removeSubcomponent(Rtpi rtpi, long subcomponentID) {
System.out.println("Application: removeSubcomponent called!");
}
public void activateSubcomponent(Rtpi rtpi, long subcomponentID) {
System.out.println("Application: activateSubcomponent called!");
}
public void deactivateSubcomponent(Rtpi rtpi, long subcomponentID) {
System.out.println("Application: deactivateSubcomponent called!");
}
}