Wednesday, October 20, 2010

JMF JVM Crashes



Recently I encountered following strange error which I got when running my VideoChat.

#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0cd32890, pid=2128, tid=1668
#
# JRE version: 6.0_17-b04
# Java VM: Java HotSpot(TM) Client VM (14.3-b01 mixed mode, sharing windows-x86 )
# Problematic frame:
# C [jmjpeg.dll+0x12890]

At first I thought this was caused by the camera driver, or a bug in the native c code.
I could track the error down to the reception part of an incoming RTP Stream.

Following code was used to display the video stream from a datasource:

Component comp;
JPanel video = new JPanel();
try {
receptionPlayer = Manager.createRealizedPlayer(clientReceptionDS);
receptionPlayer.start();

if ((comp = receptionPlayer.getVisualComponent()) != null) {

video.add(comp);
}

Everything fine, except the random crashes from the JVM.
So I started to debug the code and uncommented the video.add(cmp) and voila no crashes.

That led me to the conclusion there might be a timing problem with the player and its different states.

The solution then was to create just a Player with the incoming DataSource and add a ControllerListener who listens for state changes and tells me when the player is really realized.

Following new code was then used:

If a new stream is received we get informed via the ReceiveStreamListener which offers an Update method

public synchronized void update(ReceiveStreamEvent evt)

Inside this method we can retrive our DataSource with:

ReceiveStream stream = evt.getReceiveStream();
dataSource = stream.getDataSource();

And initialise the Player in the following way:

Player p = null;

try {
p = Manager.createPlayer(dataSource);
} catch (NoPlayerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

if (p == null)

return;

p.addControllerListener(this);

p.realize();

The ControllerListener:

public void controllerUpdate(ControllerEvent ce) {
Player p = (Player)ce.getSourceController();
if (p == null)
return;

if (ce instanceof RealizeCompleteEvent) {
vsc.startReceptionVideo(dataSource, p);
}

if (ce instanceof ControllerErrorEvent) {
p.removeControllerListener(this);
}
}

You see when there is a RealizeCompleteEvent I hand the realized Player and the DataSource over to the GUI where it will be displayed with this code:

if ((comp = receptionPlayer.getVisualComponent()) != null) {
video.add(comp);
}

receptionPlayer.start();

Result: It does not crash anymore and you have full control over the Player and its states.

Wednesday, October 13, 2010

Update: JMF Video Chat New Features



New Release of the video chat:


Added Features:
  • Fixed some bugs
  • Improved error handling
  • Added NAT Holepunch Capability (explained below)
  • Added HTTP Tunneling Capability (explained below)
It may be now possible to stream RTP video data over the internet if you either choose the method
NAT Holepunch or HTTP Tunneling.

The techniques worked in following situations:
  1. Direct connection from a host (me) to a virtual machine on the host and vice versa
  2. Software Firewalls enabled
  3. In case of NAT Holepunch, I ran the relay server on the host and used two virtual machines which communicated with each other
Short explanation of added features:

NAT Holepunch:

Participants:
  • A relay server with open udp ports
  • One computer behind a NAT
  • Another computer behind a NAT
How it works:

NAT: Network address translation
--> means ports are forwarded to the device behind it and vice versa from the device to other devices

Problem: A lot of NAT devices permit receiving UDP packets, but sending them.

That's why we can send packets to a server who can listen on all UDP ports and accept our request.

Principle:

At first the relay server is listening on a pre defined port for incoming udp packets.
Computer A tries to send a UDP packet to this server.
The packet contains information about connection details of the according peer Computer A would like to connect to later.



The relay server gets the packet and remembers the senders IP + UDP port and the packet content for later requests by Computer B.

Now Computer B sends a UDP packet to the relay server with connection details (e.g. IP) of his according peer, namely Computer A.

The relay server again gets this request and compares in his list of requests whether the new connection details from the packet match one from earlier saved requests.



If thats the case the server sends out to both peers each others connection details to the open ports earlier received of them.



Now Computer A sends UDP Packets on this open port to the direct peer on his open port and vice versa.

Now both can communicate.

HTTP Tunneling and HTTP Streaming

If you are behind a firewall your only way to communicate with the outer world is to use port 80, which is used for all http traffic (when you are browsing the internet).

In this case HTTP Tunneling comes into play.

Principle:

Short version:

We use HTTP to send the video stream to the other peer and vice versa. That means both parties have to have a listening webserver on port 80.

Long version:

Originally RTP Packets are sent via UDP as underlying protocoll. Due to the nature of firewalls blocking most traffic in general it is not possible to stream your UDP packets to another location.

The solution is to use the only free port (80). This opens another problem. In contrast to RTP HTTP works with TCP/IP as underlying protocoll. This means we have to deal with packet retransmission and therefore delays in the reception of the data.

HTTP Streaming

One solution would be to implement the common known HTTP Streaming. This works the way that you have split your stream in time equavilant segments and send these to a webserver, which serves these files.

Now the client connects to the webserver and reads one file after another.
Bad side of this method: It is not real time anymore. Due to the creation of the files the client has to wait at least the time the server needs to capture one segment and load it onto the webserver.

So what to do now?
Well, I tried to implement my own HTTP Streaming solution which roughly works like this:

  1. start capturing from the webcam
  2. while capturing read out the streaming bytes
  3. wait until a certain buffer is filled with video data
  4. now send this buffered data over http to the opponent webserver
  5. on this webserver we read out the received data and create UDP packets which are sent to the local RTP Receiver
  6. steps 1-5 on both sides

Result:
Depending on your webcam it will work!

Pictures:

Hosting peer: Left side --> received stream over HTTP Tunneling
Right side-->webcam not working ;-)

Virtual Machine Client peer: Right side: local webcam stream, left: no reception due to bad webcam of other peer




Download

VideoChat1.2.jar

Environment
JRE 1.6_U20 32 Bit

Windows XP 32 Bit
Windows 7 64 Bit