JCapi
experience
by Jean-Marc
Autexier, October 2004
Index:
- Environment
- Capi Info
- Capi Call
- DTMF dump
- Recording and playback (to the phone)
- Performance
- A-Law audio playback (on sound card)
- More sound stuff
Environment
All tests have been done with the following equipment:- My laptop: Compaq n800, Windows XP
- Fritz
USB ISDN Adapter V2.1, with CAPI
- SUN Java 1.4.2_02
- Eclipse 3.0 with Visual Editor
- JCapi (architecture) - GPL
- LawInputStream from http://www.mms-computing.co.uk/uk/co/mmscomputing/device/capi/sound/
Capi Info
First application. It retrieves information about installed controllers.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.
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.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();Output:
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());
}
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.
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:
- jcapi.dll: 9kb
- jcapi classes: 200 kb: this includes Law conversion (see swapped
bytes), high level API/framework (CallCenter), PCM, few examples, ...
- SWT stuff: 340 kb dll's, 1MB java library. Only needed for MMI.
- Running Recorder, the memory used size is 5-8 MB. This is the minimum a Java application takes, so the CAPI access itself is only few kb.
- Only observed using Windows Task Manager: during call accept and
recording (with law conversion) the process never used more than 1%
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:
- Get an Audio input stream from the file. The format must be defined (ALaw, 8000Hz, ...) as automatic detection fails
- Convert stream to PCM, as it is always easier to work on PCM (positioning ...). Doing this, you do all you are used to do on PCM stream (positioning ....) without taking care of underlying encoding
- Get a line which can play PCM, this is your sound card.
- Copy pcm input stream to the line
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;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.
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();
More sound stuff
Interesting links for sound manipulation:- Sound tutorials:
- Java Sound programmer guide
- Java Sound Ressouces:
plenty of code samples
- Record in another fomat: as you now can access the a-law input
stream (which is recorded from ISDN line), you can use any conversion
stream and store the result on a file.
- MP3: encoding (tritonus has lame based solution...), playback is easy using JavaLayer MP3 Sercice Porvider
- Formats
conversion: Ogg vorbis, mp3,
GSM,
ALaw, my-law, speex,
- Sample
rate conversion, mono
to stereo, ...