Java Telephony API
Media Extension Package





Introduction


This document provides an overview of the Media Extension Package to the Java Telephony API. This package provides applications access to the media on the telephone line. A wide variety of media-centric telephony applications exist today, including workstation answering machines, IVR systems, PPP daemons, and fax applications. The Media Package aims to supports virtually all media-based telephony applications.

This overview document is separated into the following sections. A brief description of each section follows.

    Media Package Architecture. This section describes the overall architecture of the Media Extension Package. The Media Package has a base media API which supports all types of media applications. Layered on top of this base layer is a voice-specific API. This high-level voice-specific API supports the most basic and common desktop voice-telephony applications.
    The Voice API Specification This section describes the voice API part of the Media Extension Package. The Voice API supports simple voice-based desktop telephony applications. The methods associated with this part of the package specification are described here including a sample application.


Media Package Architecture


The following diagram shows the bi-level architecture of the Media Package. The lowest or base layer API provides robust support for all types of media applications including desktop voice and answering machines, PPP, IVR, speech systems, and fax-based applications. The smaller top-layer is a voice-specific API, designed to support only the simplest and most common desktop voice API. It is meant as a simple means for application developers to quickly integrate media into their desktop voice-telephony applications. Typically, application developers choose only a single layer to use.

Base Media API

The base media API supports a wide range of media-based telephony applications. This specification does not include details about this base-level API yet. This specification will be available in several months time. This API supports two other major media-related APIs: the ECTF's S.100 API and the Java Media Framework API. The S.100 specification supports building advanced IVR systems, while the Java Media Framework supports advanced desktop media applications. The Media Package API will support the neccessary functionality from both these media APIs and provide the neccessary hooks to co-exist in their environments.

Voice API

The Media Package specification available now describes the upper-level voice API. This API supports the most simple voice-based telephony applications. In the future, it may be implemented as a high-level wrapper around the robust base media API.

The Voice API Specification


The Voice API supports the following functionality: directing voice media from the telephone line to a disk file or the workstation's speaker, directing voice media from a disk file or the workstation's microphone to the telephone line, and detecting and generating DTMF tones on the telephone line. The Voice API supports the following simple desktop-voice applications: routing the voice in a telephone call through a workstation's multimedia hardware, simple workstation-based voice answering machines, and simple IVR DTMF-based applications.

The Voice API Terminology

The Voice API contains only a half-dozen methods to perform these simple tasks. Many methods are defined in terms of either "playing" or "recording". Depending upon one's perspective, these terms can mean either of two things. For the purpose of this API, "playing" refers to sending (i.e. writing) voice data to the telephone line. The term "recording" refers to receiving (i.e. reading) voice data from the telephone line.

The Voice API Playing Methods

The application may route data from the workstation's microphone or from a disk file to the telephone line. Applications use the useDefaultMicrophone() to direct the voice data from the default workstation microphone to the telephone line. In the case of several different microphones existing on the workstations at once, the application uses the Phone package to control which microphone is active. The Phone package also provides the ability to control the gain of the microphone.

Applications use the usePlayURL() to direct the voice data from a URL source to the telephone line. Voice data can come from either one of these locations at a single time, but not both at the same time. If an application uses a URL, then the implementation is limited by the availability of that URL and its permissions to access that URL. Also, the implementation makes no guarantees about the real-time behavior if the URL resource is located over the network. The implementation determines which audio file formats it can read. It should at the very least read in the same audio file format it writes out. It is the responsibility of the implementation to determine the audio file format of the URL provided, and implementation should be able to read the most common format for their particular implementation.

Applications may begin playing with the startPlaying() method and may stop the playing via the stopPlaying() method. Playing may cease for other reasons, for example, when the end of the URL-source has been reached. In either case, events are delivered to the applications to indicate this state change, i.e. the MediaTermConnStateEv. If an application calls startPlaying() after it has called stopPlaying(), the implementation attempts to read the source media and play it from where it last left off, if possible. If the application wants to "rewind" the media, it should re-issue the usePlayURL() method.

The Voice API Recording Methods

The application may route data to the workstation's speaker or to a URL-destination from the telephone line. Applications use the useDefaultSpeaker() to direct the voice data to the default workstation speaker from the telephone line. In the case of several different speakers existing on the workstations at once, the application uses the Phone package to control which speaker is active. The Phone package also provides the ability to control the gain of the speaker.

Application use the useRecordURL() to direct the voice data to a URL-destination from the telephone line. Voice data can go to either one of these locations at a single time, but not both at the same time. If an application uses a URL, then the implementation is limited by the availability of that URL and its permissions to access that URL. Also, the implementation makes no guarantees about the real-time behavior if the URL resource is located over the network. The audio file format is determined by the implementation and should be the a common audio file format for that particular platform. The one constraint imposed on implementation is they must be able to read in the same audio file formats which they write out.

Applications may begin recording with the startRecording() method and may stop the recording via the stopRecording() method. Recording may cease for other reasons, for example, when the telephone call ends. In either case, events are delivered to the applications to indicate this state change, i.e. the MediaTermConnStateEv. If an application calls startRecording() after it has called stopRecording(), the implementation attempts to write to the media from where it last left off, if possible. If the application wants to "overwrite" the media, it should re-issue the useRecordURL() method.

The Voice API DTMF Methods

The Voice API provides several methods for applications to detect and generate DTMF-tones on the telephone line. Applications use the setDtmfDetection() method to turn on and off DTMF-tone detection. Applications receive event notification of all DTMF-tones detected via the MediaTermConnDtmfEv event. Applications use the generateDtmf() method to generate a DTMF-tone on the telephone line. It takes a string of characters to generate. These characters may be the digits zero (0) through nine (9), the letters A through D, the asterisk (*), or the pound(#).

The Voice API Events

There are several events associated with the voice media package. All of these events are reported via the MediaCallObserver interface. This interface does not define any new methods, all events are reported via the callChangedEvent() method defined in the CallObserver interface. By implementing the empty MediaCallObserver interface, the application is signalling to the implementation that it desires media-related call events.

The media channel associated with the TerminalConnection can either be available or unavailable. These state changes are reported via the MediaTermConnAvailableEv and MediaTermConnUnavailableEv events. Applications may query for the current channel availability state via the getMediaAvailability() method

There can be either playing, recording, both of these, or no activity on the media channel. A change in this state is indicated by the MediaTermConnStateEv event. The current state is given by a bit-mask. Applications may query for the current playing or recording activity state via the getMediaState method.

If an application has chosen to detect DTMF-tones on the media channel, it is informed of all DTMF-tones detected via the MediaTermConnDtmfEv event. The digit which was detected may be obtained via the getDtmfDigit() method on that event.



Example Voice API Applications


This section presents three application examples which use the voice API aspect of the Media Extension Package. The first application routes the voice media from the telephone line to the workstation's default audio hardware, therefore, using the workstation as a telephone. The second application answers incoming telephone calls, plays a store greeting to the caller, and records a message from the calller, similar to traditional answering machines. The third application answers incoming calls and monitors the telephone line for DTMF-tones (touch-tones). Applications may choose to perform a certain action based upon this touch-tone input.


A Desktop Telephone Application

The following code example shows how application route the media on the telephone line to and from the workstation's default audio hardware. This application places a telephone call and the user can converse with the other party via the workstation.


import java.telephony.*;
import java.telephony.events.*;
import java.telephony.media.*;
import java.telephony.media.events.*;


class DesktopCallObserver implements MediaCallObserver {

  /*
   * This CallObserver will be notified of Call events.
   */
  public void callChangedEvent(CallEv evlist[]) {

    for (int i = 0; i < evlist.length; i++) {
      CallEv ev = evlist[i];

      if (ev instanceof MediaTermConnAvailableEv) {
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        /* Route the media to/from the workstation's hardware. */
        try {
          mtc.useDefaultSpeaker();
          mtc.useDefaultMicrophone();

          mtc.startPlaying();
          mtc.startRecording();
        } catch (Exception excp) {
          // Handle all Exceptions
        }
      }
      else if (ev instance MediaTermConnUnavailableEv) {

        /* Stop the playing and recording */
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.stopRecording();
          mtc.stopPlaying();
        } catch (Exception excp) {
          // Handle all Exceptions
        }
      }
    }
  }
}


public class DesktopCall {

  public static final void main(String args[]) {

    /*
     * Create a provider by first obtaining the default implementation of
     * JTAPI and then the default provider of that implementation.
     */
    Provider myprovider = null;
    try {
      JtapiPeer peer = JtapiPeerFactory.getJtapiPeer(null);
      myprovider = peer.getProvider(null);
    } catch (Exception excp) {
      System.out.println("Can't get Provider: " + excp.toString());
      System.exit(0);
    }


    /*
     * Create a new telephone call object and place an observer on it.
     */
    Call mycall = null;
    try {
      mycall = myprovider.createCall();
      mycall.addObserver(new DesktopCallObserver());
    } catch (Exception excp) {
      // Handle all Exceptions
    }

    /*
     * Locate the Terminal and Address associated with our near end and place
     * a telephone call to a destination address. We are assuming that
     * Terminals are named after the primary telephone number on that Terminal.
     */
    try {
      Address address = myprovider.getAddress("4761111");
      Terminal terminal = address.getTerminal("4761111");

      mycall.connect(terminal, address, "5551212");
    } catch (Exception excp) {
      // Handle all Exceptions
    }
  }
}


A Voice Answering Machine Application

The following code example shows how an application developer would write a voice-answering machine using the Java Telephony API call control methods to answer an incoming telephone call and the Voice API to play a prompt and record a message into another file.

import java.telephony.*;
import java.telephony.events.*;
import java.telephony.media.*;
import java.telephony.media.events.*;


class MachineCallObserver implements MediaCallObserver {

  /*
   * This CallObserver will be notified of Call events. We want to first
   * answer the incoming telephone call, and then perform the neccessary
   * media actions for the answering machine.
   */
  public void callChangedEvent(CallEv evlist[]) {

    for (int i = 0; i < evlist.length; i++) {
      CallEv ev = evlist[i];

      /*
       * When the TerminalConnection associated with the local Terminal is
       * create we want to set it up for source and destinations of the
       * voice media.
       */
      if (ev instanceof TermConnCreatedEv) {

        TerminalConnection tc = ((TermConnEv)ev).getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.usePlayURL(new URL("file:/audio/prompt.au"));
          mtc.useRecordURL(new URL("file:/audio/message1.au"));
        } catch (Exception excp) {
          // Handle exceptions
        }
      }
      else if (ev instanceof TermConnRingingEv) {
        /*
        * We want to answer the telephone.
        */
        TerminalConnection tc = ((TermConnEv)ev).getTerminalConnection();
        try {
          tc.answer();
        } catch (Exception excp) {
          // Handle Exceptions
        }
      }
      else if (ev instanceof MediaTermConnAvailableEv) {

        /* We want to start playing of the prompt. */
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.startPlaying();
        } catch (Exception excp) {
          // Handle exceptions
        }
      }
      else if (ev instanceof MediaTermConnStateEv) {

        /* When the playing stops, start the recording of the message. */

        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          /* Want to find out when playing is done */
          int state = mtc.getMediaState();

          if (state & MediaTerminalConnection.PLAYING == 0) {
            mtc.startRecording();
          }
        } catch (Exception excp) {
          // Handl exceptions
        }
      }
      else if (ev instance MediaTermConnUnavailableEv) {

        /* When the channel on the telephone line becomes unavailable, then
         * stop all activity.
         */
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.stopRecording();
          mtc.stopPlaying();
        } catch (Exception excp) {
          // Handle exceptions
        }
      }
    }
  }
}


public class MachineCall {

  public static final void main(String args[]) {

    /*
     * Create a provider by first obtaining the default implementation of
     * JTAPI and then the default provider of that implementation.
     */
    Provider myprovider = null;
    try {
      JtapiPeer peer = JtapiPeerFactory.getJtapiPeer(null);
      myprovider = peer.getProvider(null);
    } catch (Exception excp) {
      System.out.println("Can't get Provider: " + excp.toString());
      System.exit(0);
    }

    /*
     * Locate the Terminal associated with our near end and place a call
     * observer on it. This will instruct the implementation to add a call
     * observer once a telephone call comes to the Terminal. We are assuming
     * Terminals are named after a primary telephone number on that Terminal.
     */
    Terminal terminal;

    try {
      terminal = myprovider.getTerminal("4761111");
      terminal.addCallObserver(new MachineCallObserver());
    } catch (Exception excp) {
      // Handle all Exceptions
    }
  }
}


A DTMF-digit Collector

The final code example shows how applications detect DTMF-tones from the telephone line. This application answers an incoming telephone line and simply prints out the detect tones to the screen.

import java.telephony.*;
import java.telephony.events.*;
import java.telephony.media.*;
import java.telephony.media.events.*;


class DtmfCallObserver implements  MediaCallObserver {

  /*
   * This CallObserver will be notified of Call events. We want to first
   * answer the incoming telephone call, and then perform the neccessary
   * media actions to perform DTMF.
   */
  public void callChangedEvent(CallEv evlist[]) {

    for (int i = 0; i < evlist.length; i++) {
      CallEv ev = evlist[i];

      if (ev instanceof TermConnRingingEv) {

        /* Answer the phone. */
        TerminalConnection tc = ((TermConnEv)ev).getTerminalConnection();
        try {
          tc.answer();
        } catch (Exception excp) {
          // Handle Exceptions
        }
      }
      else if (ev instanceof MediaTermConnAvailableEv) {

        /* We want to start DTMF-detection
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.setDtmfDetection(true);
        } catch (Exception excp) {
          // Handle exceptions
        }
      }
      else if (ev instanceof MediaTermConnUnavailableEv) {

        /* Turn off DTMF-detection */
        TerminalConnection tc = (TermConnEv)ev.getTerminalConnection();
        MediaTerminalConnection mtc = (MediaTerminalConnection)tc;

        try {
          mtc.setDtmfDetection(false);
        } catch (Exception excp) {
          // Handle exceptions
        }
      }
      else if (ev instanceof MediaTermConnDtmfEv) {

        /* Print out the DTMF digits */
        char digit = ((MediaTermConnDtmfEv)ev).getDigit();
        System.out.println("detected DTMF: " + digit);
      }
    }
  }
}


public class DtmfCall {

  public static final void main(String args[]) {

    /*
     * Create a provider by first obtaining the default implementation of
     * JTAPI and then the default provider of that implementation.
     */
    Provider myprovider = null;
    try {
      JtapiPeer peer = JtapiPeerFactory.getJtapiPeer(null);
      myprovider = peer.getProvider(null);
    } catch (Exception excp) {
      System.out.println("Can't get Provider: " + excp.toString());
      System.exit(0);
    }


    /*
     * Locate the Terminal associated with our near end and place a call
     * observer on it. This will instruct the implementation to add a call
     * observer once a telephone call comes to the Terminal. We are assuming
     * Terminals are named after a primary telephone number on that terminal.
     */
    Terminal terminal;

    try {
      terminal = myprovider.getTerminal("4761111");
      terminal.addCallObserver(new MachineCallObserver());
    } catch (Exception excp) {
      // Handle all Exceptions
    }
  }
}

Back to Codesamples