JCapi experience
by Jean-Marc Autexier, October 2004

Index:


Environment

All tests have been done with the following equipment:

Capi Info

First application. It retrieves information about installed controllers.
capiinfo image
Code:
// Initialize
JCapi capi = new JCapi();

// get number of controllers -> fill combobox
int c=capi.getNumberOfControllers();
int controllerId = 0;

// Get manufacturer, version and serial number
String manufacturer = capi.getManufacturer(controllerId);
int[] v =capi.getVersion(controllerId);
String serialNumber = capi.getSerialNumber(controllerId);

Capi Call

This application shows incoming call date/time, called and calling number, service type (speech, digital, audio, video...) and CAPI message type.
Capi call image

Code:
...
// Get application identifier
int appID = capi.register(4096, 2, 2, 128);

// Register at capi. capiEventRaised() will be called
capi.addListener(appID, this); ... // called when acpi event is raised public void capiEventRaised()
{
// get the message
JcapiMessage m = (JcapiMessage) capi.getMessage(appID);

// Depending on message type, switch
switch (m.getType())
{
// See JcapiMessage for event types ... there are plenty
case JcapiMessage.CONNECT_IND:
byte[] cdn = m.getStructValue("Called party number");
byte[] cln = m.getStructValue("Calling party number");
if (cdn.length > 1)
called = new String(ByteArray.getBytes(cdn, 1, cdn.length - 1));
if (cln.length > 2)
calling = new String(ByteArray.getBytes(cln, 2, cln.length - 2));

byte[] bc = m.getStructValue("BC") ;
if (bc.length>0)
switch(bc[0] & 0x1f)
{
case 0x00 : info="speech"; break;
case 0x08 : info="digital (unrestricted)"; break;
case 0x09 : info="digital (restricted)"; break;
case 0x10 : info="3.1 kHz audio"; break;
case 0x11 : info="7 kHz audio"; break;
case 0x18 : info="video"; break;
}
...
}
}

Capi Trace

This application just print incoming CAPI events (type, message id and bytes). The bottom contains a web page with CAPI type description, so you can quickly check what type numbers mean.

capi trace image

The interesting code is more or less the same that above, so no code here.


DTMF dump

This application use a higher level API of jcapi: CallCenter. CallCenter handles low level communication. Application register for incoming events and get callEvent (new call) and dtmfEvent(new DTMF).
It initialize all CallCenter, register for events. When it get a call, it display incoming number and accept it. When it receive a DTMF, the signal is printed out.

private CallCenter center = center = new CallCenter();
center.registerCallEventListener(this); // this implements CallHandlerEventListener interface

public void callEvent(CallEvent event)
{
final CallHandler handler = event.getHandler();
...
handler.acceptCall();
...
}

public void dtmfEvent(DtmfEvent event)
{
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()) + " - DTMF signal: "+event.getDtmf());
}
Output:
Incoming call from: 06813905514
Call accepted
17:32:51 - DTMF signal: 0
17:32:51 - DTMF signal: 1
17:32:52 - DTMF signal: 2
17:32:52 - DTMF signal: 3
17:32:52 - DTMF signal: 4
17:32:53 - DTMF signal: 5
17:32:53 - DTMF signal: 6
17:32:54 - DTMF signal: 7
17:32:54 - DTMF signal: 8
17:32:54 - DTMF signal: 9

Recording and playback

Recording and playback is very very simple.

Recording:

Assuming you use CallCenter, just startListen() on the CallHandler (which you get from CallEvent). You get back a CapiInputStream which you can use as any other stream.
Recording is just copying from CapiInputStream to a FileOutputStream.
// Call Event, get Handler
CallHandler handler = event.getHandler();
FileOutputStream fout = new FileOutputStream("output.raw");
CapiInputStream cin = handler.startListen(); // copy from cin to fout as usual // Everybody has an inputstream to outputstream copy class, if not you can use the one from jcapi project StreamTools.mapStreams(cin, fout); // it copies byte buffers (size=32768, why?) from in to out until in return -1

Playback:

Same as for recording, just ask your CallHanlder to give you the CapiOutputStream.
Then copy from any InputStream to the CapiOutputStream.
// Call Event, get Handler
CallHandler handler = event.getHandler();
FileInputStream fin = new FileInputStream("input.raw");
OutputStream cout = handler.getOutputStream();

// copy from fin to cout as usual
StreamTools.mapStreams(fin, cout);

What format is stored/required for playback?

See http://www.mms-computing.co.uk/uk/co/mmscomputing/device/capi/sound/ for detailed information.

ISDN stream is encoded as my.law or a-law with bit order swapped. To get a 'normal' a-law stream, swap every byte.

Hex
Bin
Bin result
Hex result
0x00
00000000
00000000
0x00
0x01
00000001
10000000
0x80
0x02
00000010
01000000
0x40
....



0x09
00001001
10010000
0x90
0x0A
00001010
01010000
0x50
...



0xC0
11000000
00000011
0x03
...



0xFF
11111111
11111111
0xFF

The page http://www.mms-computing.co.uk/uk/co/mmscomputing/device/capi/sound/ contains an LawInputStream and LawOutputStream which do this conversion..

Using LawInputStream(handler.startListen()), you get a standard a-law 8000Hz input stream which can be played back. Here how I import it in Audacity.
alaq_import
alaq imported file
Now that you have a well known InputStream, you can use it and convert it to any other format using Java Sound API, for example to PCM for playback.
TBD: describe how immediate playback or conversion works with Java Sound API.

Performance, size and memory

This are some observations done while running "Recording and Playback" software.
Files size: Memory: CPU:

A-Law audio playback

This chapter explains how to playback recorded files using Java sound API.
The code is inspired from jressource.org SimpleAudioPlayer. The main difference is that the audio format is not recognized, so it must be defined. The code is document ,so it's easy to understand how it works.

In short, this is what it is doing:

Rermark: the exampe assumes that files are recorded with swapped bits swapped, so you have a regular alaw file. If not, just add another LawInputStream arround the FileInputStream: PcmStream(ALawStream(LawStream(isdnFile))).
private static final int    EXTERNAL_BUFFER_SIZE = 512;

public static void main(String[] args)
{
String strFilename = "C:\\dev\\test\\jcapi\\output.raw";
File soundFile = new File(strFilename);

// Get the AudioInputStream: as automatic recognition fails,
// hard define the AudioFormat
AudioInputStream audioInputStream = null;

try
{
AudioFormat format = new AudioFormat(
AudioFormat.Encoding.ALAW,
8000, // sample rate: nr of samples per second
8, // sample size: nr of bits in each sample
1, // number of channels
1, // framesize: nr of bytes in each frame, number of bytes*number of channels
8000, // framerate: nr of frames per second, fo alaw, same than sample rate
true // big/little enfian
);

long lLengthInFrames = soundFile.length() / format.getFrameSize();
audioInputStream = new AudioInputStream(new FileInputStream(soundFile),format,lLengthInFrames); } catch (Exception e) ... // We will always work on PCM, so get a PCM converted AudioInputStream from initial stream AudioInputStream pcmAIS = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioInputStream); // Get a line which can playback PCM (sound card...) SourceDataLine line = null; DataLine.Info info = new DataLine.Info(SourceDataLine.class, pcmAIS.getFormat()); try { line = (SourceDataLine) AudioSystem.getLine(info); line.open(pcmAIS.getFormat()); } catch (Exception e) ... // Activate it, so the line give data's to sound card line.start(); // Now read pcm input stream and copy buffers on the line int nBytesRead = 0; byte[] abData = new byte[EXTERNAL_BUFFER_SIZE]; while (nBytesRead != -1) { try { nBytesRead = pcmAIS.read(abData, 0, abData.length); } catch (IOException e) ... if (nBytesRead >= 0) { int nBytesWritten = line.write(abData, 0, nBytesRead); } } // Wait until all data are played. line.drain(); // close the line line.close();
Of course you can do this directly while recording and store your ISDN data in another format, or send them in specific format over network.

More sound stuff

Interesting links for sound manipulation: enjoy.