Saturday, February 6, 2010

Live Internet Streaming - So what does it take?





Alright. So here are some answers. We still have a long way to go in terms of quality, performance, making it mobile compatible etc, but here are the basics and they are "Rock" solid.
Oh and a warning in advance - this is a technical post.

We need the following components:
1. Recorder
2. Media Converter
3. Transmitter
4. Receiver

Although these components can be technology agnostic, for the sake of our understanding, let us assume that we build these components in java.
In this post I will be addressing the core challenge which is of seamless communication lines between the transmitter and the receiver. In my next posts I shall talk about the first 2 components viz Recorder and Media Converter. Why you ask? Cos I haven't really figured out the best way to address those, and besides getting that done is the easy part. Getting the stream transmitted seamlessly over the socket by a protocol of our choosing ensuring that we have minimal packet loss and having a reasonably good number of receivers hooking onto the stream without noticeable impact on performance is the tough part.

The primary requirement would be for the transmitter as well as the receiver to be able to talking to each other, either over the LAN or over some socket of communication.

The Transmitter

Let's jump straightaway into a little bit of pseudo code:

private String createMediaTransmitter() {
DataSink transmitter;
Datasource dataGen;
        String transURL = "rtp://" + ipAddress + ":" + port + "/video";
MediaLocator outputLocator = new MediaLocator(transURL);
//create implementation of dataGen before transmitting
        transmitter = Manager.createDataSink(dataGen, outputLocator);
//provide implementation of transmitting
// for eg- transmitter needs to be opened and started. Then the dataGen needs to be generated from a source
    }

The transURL is of particular interest. Notice two things. The first is "rtp" implying that we will be using the RTP protocol to provide transmission. The second is "video" indicating that the transmission will happen for video content only. Of course that is hardly sufficient for us, unless we transmit audio content also. Silent movies aren't exactly a 21st century thing. What we need to do is send Video and Radio as sessions. As a result the transmitter cannot be of type DataSink but needs to be of type RTPManager. The RTPManager works well with sessions and can be used to keep track of individual video and audio streams.


private String createMediaTransmitter() {
private RTPManager rtpMgrs[];
DataSource dataGen;
PushBufferStream objpbss[] = dataGen.getStreams(); //video and audio or only audio
rtpMgrs = new RTPManager[objpbss.length];
SessionAddress srcAddr, destAddr;
InetAddress ipAddr;
SendStream sendStream;
int port;
SourceDescription srcDesList[];
for (int i = 0; i < pbss.length; i++) {
   try {
rtpMgrs[i] = RTPManager.newInstance();    
                 port = portBase + 2*i;
ipAddr = InetAddress.getByName(ipAddress);
                 localAddr = new SessionAddress( InetAddress.getLocalHost(),
port);
destAddr = new SessionAddress( ipAddr, port);
rtpMgrs[i].initialize( localAddr);
rtpMgrs[i].addTarget( destAddr);
System.err.println( "Created RTP session: " + ipAddress + " " + port);
sendStream = rtpMgrs[i].createSendStream(dataGen, i);
sendStream.start();
   } catch (Exception  e) {
return e.getMessage();
   }
}


return null;
    }


In this case there would be 2 rtpMgrs one for video and one for audio. The SessionAddress object is used to create RTP sessions. Also in this case the time complexity is O(n). However n is finite in this case which means there would not too much latency in terms of the video and audio being received at the receiver end.


The Receiver


The implementation of the reciever is fairly straightforward. One has to create a RTP session via the RTP Manager and keep listening for any new streams that come in. Also a Media Player needs to be instantiated which will then stream the media content. 

Following is a pseudo code


public synchronized void startReceiving(ReceiveStreamEvent objRSE) {


     rtpMgrs= new RTPManager[sessions.length];
         for (int i = 0; i < sessions.length; i++) {
         rtpMgrs[i] = (RTPManager) RTPManager.newInstance();
rtpMgrs[i].addSessionListener(this);
rtpMgrs[i].addReceiveStreamListener(this);
        rtpMgrs[i].initialize( localAddr);
        rtpMgrs[i].addTarget(destAddr);


     }
   if (objRSE instanceof NewReceiveStreamEvent) {
   stream = ((NewReceiveStreamEvent)objRSE).getReceiveStream();
DataSource ds = stream.getDataSource();
        RTPControl rtpctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
// create a player by passing datasource to the Media Manager
Player pl = javax.media.Manager.createPlayer(ds);
pl.addControllerListener(this);
pl.realize();
PlayerWindow pw = new PlayerWindow(pl, stream);
playerWindows.addElement(pw);


}