Clase “Transmisor”:
	
Esta clase se utiliza para ser heredada por otra clase que implemente una transmisión RTP ya que implementa los métodos necesarios y que son independientes de las particularidades de cada transmisor (origen, destino, tipo de datos).

Constantes de la clase:
	private static final float JPEGQ = 0.1f;
		- Almacena el valor de la calidad de la imagen del compresor JPEG.

Variables miembro:
	protected Target destino;
- Guarda la configuración de puertos del programa, dependiendo del tipo de recepción, se utilizará unos puertos u otros.
  	protected MediaLocator locator;
- Guarda el localizador del dispositivo de captura.
 	protected Processor procesador;
- Guarda el procesador que devuelve la secuencia a enviar.
  	protected boolean fallo;
- Indica si ha habido un fallo.
  	protected Integer stateLock;
- Guarda el estado de bloqueo del procesador.
	protected DataSource salida;
- Guarda la fuente de datos de salida.
	protected RTPManager rtpManagers[];
- Tabla que guarda los manejadores RTP, en caso de haber varios.
	protected int puertosLocales[];
- Tabla que guarda una lista de los puertos locales, en caso de haber varios.

Constructor de la clase:
	Inicializa las variables miembro de la clase.
public Transmisor(Target destino)
{
	//inicializamos las variables
	System.out.println(Misc.ind() + "Inicializando las variables");
	this.destino = destino;
	procesador = null;
	locator = null;
	fallo = false;
	stateLock = new Integer(0);
	salida = null;
	rtpManagers = null;
}

Métodos de la clase:
stop():
	Este método detiene la transmisión: 
public void stop()
{
	synchronized(this)
	{
		//si existe el procesador
		if (procesador != null)
		{
			//detenemos el procesador
			System.out.println(Misc.ind() + "Detenemos el procesador");
			procesador.stop();
			System.out.println(Misc.ind() + "Cerramos el procesador");
			//cerramos el procesador
			procesador.close();
			procesador = null;
			//eliminamos los manejadores RTP
			System.out.print(Misc.ind() + "Eliminamos los destinos");
			for (int i = 0; i < rtpManagers.length; i++)
			{
				//eliminamos el destino
				System.out.print(".");
				rtpManagers[i].removeTargets("Session finalizada.");
				//eliminamos el manejador
				rtpManagers[i].dispose();
			}
			System.out.println();
		}
	}
}

crearProcesador():
	Este método crea el procesador que será el encargado de procesar la entrada de datos y proporcionar una salida válida para la comunicación:
protected String crearProcesador()
{
	//comprobamos que existe un locator
	System.out.println(Misc.ind() + "Comprobando que existe el locator");
	if (locator == null)
		return "No hay seleccionado ningun dispositivo de captura de audio";
	DataSource ds;
	DataSource clone;
	//creamos una fuente de datos
	System.out.println(Misc.ind() + "Creando una fuente de datos para audio");
	try
	{
		ds = javax.media.Manager.createDataSource(locator);
	}
	//capturamos la excepcion
	catch (Exception e)
	{
		return "No se pudo encontrar una fuente de datos para audio";
	}
	//llamamos al metodo que crea el procesador a partir de la fuente de datos
	System.out.println(Misc.ind() + "Creando el procesador");
	try
	{
		procesador = javax.media.Manager.createProcessor(ds);
		procesador.addControllerListener(this);
	}
	//capturamos la excepcion
	catch (NoProcessorException npe)
	{
		return "No se pudo crear el procesador";
	}
	//capturamos otra excepcion
	catch (IOException ioe)
	{
		return "Excepcion de E/S al crear el procesador";
	}
	System.out.println(Misc.ind() + "Configurando el procesador");
	Misc.aumentarInd();
	//esperamos a que el procesador esté creado
	if (!waitForState(procesador, Processor.Configured))
	{
		Misc.reducirInd();
		return "No se pudo configurar el procesador";
	}
	Misc.reducirInd();
	//obtenemos la lista de las pistas
	System.out.println(Misc.ind() + "Obteniendo las pistas");
	TrackControl [] tracks = procesador.getTrackControls();
	//comprobamos que haya pistas
	System.out.println(Misc.ind() + "Comprobando que hay pistas");
	if (tracks == null || tracks.length < 1)
		return "No se pudo encontrar ninguna pista en el procesador";
	//creamos un descriptor de contenido
	System.out.println(Misc.ind() + "Creando un descriptor de contenido");
	ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
	//aplicamos el descriptor de contenido
	procesador.setContentDescriptor(cd);
	Format supported[];
	Format chosen;
	boolean atLeastOneTrack = false;
	//recorremos las pistas
	System.out.println(Misc.ind() + "Comprobando las pistas");
	Misc.aumentarInd();
	for (int i = 0; i < tracks.length; i++)
	{
		//obtenemos el formato de la pista
		System.out.println(Misc.ind() + "Obteniendo formato de la pista " + i);
		Format format = tracks[i].getFormat();
		//si la pista está activa
		if (tracks[i].isEnabled())
		{
			//obtenemos un formato válido
			System.out.println(Misc.ind() + "Comprobando que tenga un formato valido");
			supported = tracks[i].getSupportedFormats();
			//comprobamos que contenga datos
			System.out.println(Misc.ind() + "Comprobando que contenga datos");
			if (supported.length > 0)
			{
				if (supported[0] instanceof VideoFormat)
				{
					//comprobamos las dimensiones del video
					chosen = checkForVideoSizes(tracks[i].getFormat(), supported[0]);
				}
				else
					chosen = supported[0];
				//aplicamos el formato a la pista
				tracks[i].setFormat(chosen);
				System.err.println(Misc.ind() + "Pista " + i + " transmitiendo como:");
				Misc.aumentarInd();
				System.err.println(Misc.ind() + chosen);
				Misc.reducirInd();
				atLeastOneTrack = true;
			}
			else
				tracks[i].setEnabled(false);
		}
		else
			tracks[i].setEnabled(false);
	}
	Misc.reducirInd();
	//comprobamos que haya al menos una pista válida
	System.out.println(Misc.ind() + "Comprobando que hay alguna pista utilizable para RTP");
	if (!atLeastOneTrack)
		return "No se pudo utilizar ninguna de las pistas para una transmisión RTP";
	//esperamos a que termine la configuración del procesador
	System.out.println(Misc.ind() + "Finalizando la creacion del procesador");
	Misc.aumentarInd();
	if (!waitForState(procesador, Controller.Realized))
	{
		Misc.reducirInd();
		return "No se pudo llevar a cabo el procesador...";
	}
	Misc.reducirInd();

	//cambiamos la calidad del formato JPG
	setJPEGQuality(procesador, JPEGQ);

	//obtenemos la salida del procesador
	System.out.println(Misc.ind() + "Obteniendo salida de datos");
	salida = procesador.getDataOutput();

	return null;
}

checkForVideoSizes():
	Este método comprueba, en el caso de una secuencia de video, si tiene unas dimensiones adecuadas para la transmisión RTP:
private Format checkForVideoSizes(Format original, Format supported)
{
	int width, height;
	//obtenemos las dimensiones originales
	Dimension size = ((VideoFormat)original).getSize();
	Format jpegFmt = new Format(VideoFormat.JPEG_RTP);
	Format h263Fmt = new Format(VideoFormat.H263_RTP);
	//si se trata del formato JPG
	if (supported.matches(jpegFmt))
	{
		//el alto y el ancho han de ser divisibles por 8
		width = (size.width % 8 == 0 ? size.width : (int)(size.width / 8) * 8);
		height = (size.height % 8 == 0 ? size.height : (int)(size.height / 8) * 8);
	}
	//si se trata del formato H263
	else if (supported.matches(h263Fmt))
	{
		//seleccionamos un tamaño valido
		if (size.width < 128)
		{	width = 128;
			height = 96;
		}
		else if (size.width < 176)
		{	width = 176;
			height = 144;
		}
		else
		{	width = 352;
			height = 288;
		}
	}
	//en otro caso
	else
		return supported;
	//devolvemos el nuevo formato de video
	return (new VideoFormat(null, new Dimension(width, height), Format.NOT_SPECIFIED, null, Format.NOT_SPECIFIED)).intersects(supported);
}

setJPEGQuality():
	Este método ajusta el nivel de calidad para el formato JPEG, a menor calidad, menos cantidad de información a transmitir:
protected void setJPEGQuality(Player p, float val)
{	//obtenemos los controles
	Control cs[] = p.getControls();
	QualityControl qc = null;
	//generamos un formato de video JPEG
	VideoFormat jpegFmt = new VideoFormat(VideoFormat.JPEG);
	//recorremos todos los controles
	for (int i = 0; i < cs.length; i++)
	{	//si estamos en el control de calidad
		if (cs[i] instanceof QualityControl && cs[i] instanceof Owned)
		{	Object owner = ((Owned)cs[i]).getOwner();
			//comprobamos si se trata de un codec
			if (owner instanceof Codec)
			{	//generamos una tabla de formatos soportados
				Format fmts[] = ((Codec)owner).getSupportedOutputFormats(null);
				//recorremos la tabla
				for (int j = 0; j < fmts.length; j++)
				{	//si el formato es JPG
					if (fmts[j].matches(jpegFmt))
					{	//obtenemos el control de calidad
						qc = (QualityControl)cs[i];
						//ajustamos el control de calidad
						qc.setQuality(val);
						System.err.println("  - Ajustando calidad a " + val + " sobre " + qc);
						break;
					}
				}
			}
			if (qc != null)
				break;
		}
	}
}

waitForState():
	Este método crea una espera que dura hasta que finaliza la tarea de crear o iniciar el procesador:
protected synchronized boolean waitForState(Processor p, int state)
{
	//añadimos un evento escucha
	p.addControllerListener(new StateListener());
	fallo = false;
	//miramos si el procesador se está configurando
	if (state == Processor.Configured)
	{
		System.out.println(Misc.ind() + "Configurando...");
		//configuramos el procesador
		p.configure();
	}
	//miramos si el procesador se está realizando
	else if (state == Processor.Realized)
	{
		System.out.println(Misc.ind() + "Finalizando configuracion...");
		//realizamos el procesador
		p.realize();
	}
	//esperamos a que termine
	System.out.print(Misc.ind() + "Esperando");
	while (p.getState() < state && !fallo)
	{
		//comprobamos el estado de bloqueo
		System.out.print(".");
		synchronized (getStateLock())
		{
			//hacemos una pausa
			try
			{
				getStateLock().wait();
			}
			catch (InterruptedException ie)
			{
				return false;
			}
		}
	}
	System.out.println();
	if (fallo)
		return false;
	else
		return true;
}

getStateLock():
	Este método devuelve el estado de bloqueo del procesador:
private Integer getStateLock()
{
	return stateLock;
}

setFailed():
	Este método produce la activación de la variable que indica si ha habido un fallo:
void setFailed()
{
	fallo = true;
}

addTarget():
	Este método añade un nuevo destino para el manejador RTP:
public void addTarget(int localPort, RTPManager mgr, String ip, int port)
{
	try
	{
		//creamos la sesion
		SessionAddress addr = new SessionAddress(InetAddress.getByName(ip), new Integer( port).intValue());
		//añadimos la sesion
		mgr.addTarget(addr);
	}
	//capturamos la excepcion
	catch (Exception e)
	{
		e.printStackTrace();
	}
}

controllerUpdate():
Este método se ejecuta cada vez que se actualiza el controlador e indica si se ha producido un cambio en la longitud de la transmisión o un fin de esta:
public void controllerUpdate(ControllerEvent ce)
{
	//si se actualiza la duracion
	if (ce instanceof DurationUpdateEvent)
	{
		//obtenemos la duracion
		Time duration = ((DurationUpdateEvent) ce).getDuration();
		System.out.println(Misc.ind() + "duracion: " + duration.getSeconds());
	}
	//si obtenemos el final de la secuencia
	else
	if (ce instanceof EndOfMediaEvent)
	{
		System.out.println(Misc.ind() + "Fin de la comunicacion");
	}
}

update():
	Hay 2 métodos update(), el primero se ejecuta al producirse una recepción mostrando la información en el log:
public void update(ReceiveStreamEvent event)
{
	//creamos un buffer
	StringBuffer sb = new StringBuffer();
	//si se recibe una cadena de inactividad
	if (event instanceof InactiveReceiveStreamEvent)
	{
		sb.append("Cadena recivida inactiva");
	}
	//si se recibe un adios
	else if (event instanceof ByeEvent)
	{
		sb.append("Hasta luego");
	}
	else
	{
		System.out.println(Misc.ind() + "Recibido evento de cadena: "+ event);
	}
	System.out.println(Misc.ind() + sb.toString());
}

	El segundo se ejecuta al recibir un evento remoto:
public void update(RemoteEvent event)
{
	//si se recibe un informe de recepcion
	if (event instanceof ReceiverReportEvent)
	{
		//obtenemos el informe de recepcion
		ReceiverReport rr = ((ReceiverReportEvent) event).getReport();
		//creamos un buffer
		StringBuffer sb = new StringBuffer();
		//vamos dando formato al buffer
		sb.append("Recepcion");
		//si el informe existe
		if (rr != null)
		{
			//obtenemos el participante
			Participant participant = rr.getParticipant();
			//concatenamos diversa informacion
			if (participant != null)
			{
				sb.append(" desde " + participant.getCNAME());
				sb.append(" ssrc = " + rr.getSSRC());
			}
			else
			{
				sb.append(" ssrc = " + rr.getSSRC());
			}
			System.out.println(Misc.ind() + sb.toString());
		}
	}
	else
	{
		System.out.println(Misc.ind() + "Evento remoto: " + event);
	}
}