org.speechforge.cairo.demo.tts.SpeechSynthClient.java Source code

Java tutorial

Introduction

Here is the source code for org.speechforge.cairo.demo.tts.SpeechSynthClient.java

Source

/*
 * Cairo - Open source framework for control of speech media resources.
 *
 * Copyright (C) 2005-2006 SpeechForge - http://www.speechforge.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Contact: ngodfredsen@users.sourceforge.net
 *
 */
package org.speechforge.cairo.demo.tts;

import org.speechforge.cairo.rtp.NativeMediaClient;
import org.speechforge.cairo.rtp.RTPConsumer;
import org.speechforge.cairo.server.resource.ResourceImpl;
import org.speechforge.cairo.sip.SimpleSipAgent;
import org.speechforge.cairo.sip.SipAgent;
import org.speechforge.cairo.sip.SdpMessage;
import org.speechforge.cairo.sip.SipSession;
import org.speechforge.cairo.util.CairoUtil;

import java.awt.Toolkit;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Vector;

import javax.sdp.MediaDescription;
import javax.sdp.SdpConstants;
import javax.sdp.SdpException;
import javax.sip.SipException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.log4j.Logger;
import org.mrcp4j.MrcpEventName;
import org.mrcp4j.MrcpMethodName;
import org.mrcp4j.MrcpRequestState;
import org.mrcp4j.MrcpResourceType;
import org.mrcp4j.client.MrcpChannel;
import org.mrcp4j.client.MrcpEventListener;
import org.mrcp4j.client.MrcpFactory;
import org.mrcp4j.client.MrcpInvocationException;
import org.mrcp4j.client.MrcpProvider;
import org.mrcp4j.message.MrcpEvent;
import org.mrcp4j.message.MrcpResponse;
import org.mrcp4j.message.header.IllegalValueException;
import org.mrcp4j.message.request.MrcpRequest;

/**
 * Demo MRCPv2 client application that utilizes a {@code speechsynth} resource to play a TTS prompt.
 *
 * @author Niels Godfredsen {@literal <}<a href="mailto:ngodfredsen@users.sourceforge.net">ngodfredsen@users.sourceforge.net</a>{@literal >}
 */
public class SpeechSynthClient implements MrcpEventListener {

    private static Logger _logger = Logger.getLogger(SpeechSynthClient.class);

    private static final String BEEP_OPTION = "beep";
    private static final String REPETITIONS_OPTION = "reps";
    private static final String URL_OPTION = "url";

    private static boolean _beep = false;
    private static Toolkit _toolkit = null;
    private static int _repetitions = 1;
    private static boolean _url;

    private MrcpChannel _ttsChannel;
    private int _rep = 1;
    private static boolean sentBye = false;
    private static SimpleSipAgent sipAgent;
    private static NativeMediaClient _mediaClient;

    private static int _myPort = 5070;
    private static String _host = null;
    private static int _peerPort = 5050;
    private static String _mySipAddress = "sip:speechSynthClient@speechforge.org";
    private static String _cairoSipAddress = "sip:cairo@speechforge.org";

    /**
     * TODOC
     * @param ttsChannel 
     * @param recogChannel 
     * @param recordChannel 
     */
    public SpeechSynthClient(MrcpChannel ttsChannel) {
        _ttsChannel = ttsChannel;
        _ttsChannel.addEventListener(this);
    }

    /* (non-Javadoc)
     * @see org.mrcp4j.client.MrcpEventListener#eventReceived(org.mrcp4j.message.MrcpEvent)
     */
    public void eventReceived(MrcpEvent event) {
        if (_logger.isDebugEnabled()) {
            _logger.debug("MRCP event received:\n" + event.toString());
        }

        try {
            switch (event.getChannelIdentifier().getResourceType()) {
            case SPEECHSYNTH:
                ttsEventReceived(event);
                break;

            default:
                _logger.warn("Unexpected value for event resource type!");
                break;
            }
        } catch (IllegalValueException e) {
            _logger.warn("Illegal value for event resource type!", e);
        }
    }

    private void ttsEventReceived(MrcpEvent event) {
        if (event.getEventName().equals(MrcpEventName.SPEAK_COMPLETE) && _rep++ >= _repetitions) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                _logger.debug("InterruptedException encountered!", e);
            }
            System.exit(0);
        }
    }

    public MrcpRequestState playPrompt(String promptText)
            throws IOException, MrcpInvocationException, InterruptedException {

        // speak request
        MrcpRequest request = _ttsChannel.createRequest(MrcpMethodName.SPEAK);
        request.setContent("text/plain", null, promptText);
        MrcpResponse response = _ttsChannel.sendRequest(request);

        if (_beep) {
            _toolkit.beep();
        }

        if (_logger.isDebugEnabled()) {
            _logger.debug("MRCP response received:\n" + response.toString());
        }

        return response.getRequestState();
    }

    public MrcpRequestState playOnePrompt(String url)
            throws IOException, MrcpInvocationException, InterruptedException {

        // speak request
        MrcpRequest request = _ttsChannel.createRequest(MrcpMethodName.SPEAK);
        request.setContent("text/uri-list", null, url);
        MrcpResponse response = _ttsChannel.sendRequest(request);

        if (_beep) {
            _toolkit.beep();
        }

        if (_logger.isDebugEnabled()) {
            _logger.debug("MRCP response received:\n" + response.toString());
        }

        return response.getRequestState();
    }

    public MrcpRequestState playMultiplePrompts(String[] urls)
            throws IOException, MrcpInvocationException, InterruptedException {

        // speak request
        MrcpRequest request = _ttsChannel.createRequest(MrcpMethodName.SPEAK);
        String content = urls[0];
        for (int i = 1; i < urls.length; i++) {
            content = content + "\r\n" + urls[i];
        }

        request.setContent("text/uri-list", null, content);
        MrcpResponse response = _ttsChannel.sendRequest(request);

        if (_beep) {
            _toolkit.beep();
        }

        if (_logger.isDebugEnabled()) {
            _logger.debug("MRCP response received:\n" + response.toString());
        }

        return response.getRequestState();
    }

    ////////////////////////////////////
    // static methods
    ////////////////////////////////////
    private static SdpMessage constructResourceMessage(int localRtpPort, Vector format)
            throws UnknownHostException, SdpException {
        SdpMessage sdpMessage = SdpMessage.createNewSdpSessionMessage(_mySipAddress, _host, "The session Name");
        MediaDescription rtpChannel = SdpMessage.createRtpChannelRequest(localRtpPort, format);
        MediaDescription mrcpChannel = SdpMessage.createMrcpChannelRequest(MrcpResourceType.SPEECHSYNTH);
        Vector v = new Vector();
        v.add(mrcpChannel);
        v.add(rtpChannel);
        sdpMessage.getSessionDescription().setMediaDescriptions(v);
        return sdpMessage;
    }

    private static Options getOptions() {
        Options options = ResourceImpl.getOptions();

        Option option = new Option(BEEP_OPTION, "play response/event timing beep");
        options.addOption(option);

        option = new Option(URL_OPTION, "play the wave or text file at the given url");
        options.addOption(option);

        option = new Option(REPETITIONS_OPTION, true, "number of times to repeat the TTS prompt");
        option.setArgName("repetitions");
        options.addOption(option);

        return options;
    }

    ////////////////////////////////////
    // main method
    ////////////////////////////////////

    public static void main(String[] args) throws Exception {

        // setup a shutdown hook to cleanup and send a SIP bye message even if there is a 
        // unexpected crash (ie ctrl-c)
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                if (!sentBye && sipAgent != null) {
                    try {
                        _mediaClient.shutdown();
                        sipAgent.sendBye();
                        sipAgent.dispose();
                    } catch (SipException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
            }
        });

        CommandLineParser parser = new GnuParser();
        Options options = getOptions();
        CommandLine line = parser.parse(options, args, true);
        args = line.getArgs();

        if (args.length != 2 || line.hasOption(ResourceImpl.HELP_OPTION)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("SpeechSynthClient [options] <local-rtp-port> <prompt-text>", options);
            return;
        }

        if (line.hasOption(REPETITIONS_OPTION)) {
            try {
                _repetitions = Integer.parseInt(line.getOptionValue(REPETITIONS_OPTION));
            } catch (NumberFormatException e) {
                _logger.debug("Could not parse repetitions parameter to int!", e);
                HelpFormatter formatter = new HelpFormatter();
                formatter.printHelp("SpeechSynthClient [options] <prompt-text> <local-rtp-port>", options);
                return;
            }
        }

        _beep = line.hasOption(BEEP_OPTION);
        if (_beep) {
            _toolkit = Toolkit.getDefaultToolkit();
        }

        _url = line.hasOption(URL_OPTION);

        int localRtpPort = -1;
        try {
            localRtpPort = Integer.parseInt(args[0]);
        } catch (Exception e) {
            _logger.debug(e, e);
        }

        if (localRtpPort < 0 || localRtpPort >= RTPConsumer.TCP_PORT_MAX || localRtpPort % 2 != 0) {
            throw new Exception("Improper format for first command line argument <local-rtp-port>,"
                    + " should be even integer between 0 and " + RTPConsumer.TCP_PORT_MAX);
        }

        String text = args[1];
        InetAddress rserverHost = line.hasOption(ResourceImpl.RSERVERHOST_OPTION)
                ? InetAddress.getByName(line.getOptionValue(ResourceImpl.RSERVERHOST_OPTION))
                : CairoUtil.getLocalHost();

        InetAddress localHost = line.hasOption(ResourceImpl.LOCALHOST_OPTION)
                ? InetAddress.getByName(line.getOptionValue(ResourceImpl.LOCALHOST_OPTION))
                : CairoUtil.getLocalHost();

        _host = localHost.getHostAddress();
        String peerAddress = rserverHost.getHostAddress();

        // Construct a SIP agent to be used to send a SIP Invitation to the cairo server
        sipAgent = new SimpleSipAgent(_mySipAddress, "Synth Client Sip Stack", _host, null, _myPort, "UDP");

        // Construct the SDP message that will be sent in the SIP invitation
        Vector format = new Vector();
        format.add(SdpConstants.PCMU); //PCMU
        SdpMessage message = constructResourceMessage(localRtpPort, format);

        // Send the sip invitation (This method on the SipAgent blocks until a response is received or a timeout occurs) 
        _logger.info("Sending a SIP invitation to the cairo server.");
        SdpMessage inviteResponse = sipAgent.sendInviteWithoutProxy(_cairoSipAddress, message, peerAddress,
                _peerPort);

        if (inviteResponse != null) {
            _logger.info("Received the SIP Response.");

            // Get the MRCP media channels (need the port number and the channelID that are sent
            // back from the server in the response in order to setup the MRCP channel)
            List<MediaDescription> xmitterChans = inviteResponse.getMrcpTransmitterChannels();
            int port = xmitterChans.get(0).getMedia().getMediaPort();
            String channelId = xmitterChans.get(0).getAttribute(SdpMessage.SDP_CHANNEL_ATTR_NAME);

            //Construct the MRCP Channel
            String protocol = MrcpProvider.PROTOCOL_TCP_MRCPv2;
            MrcpFactory factory = MrcpFactory.newInstance();
            MrcpProvider provider = factory.createProvider();
            MrcpChannel ttsChannel = provider.createChannel(channelId, rserverHost, port, protocol);

            //Setup a media client to receive and play the sythesized voice data streamed over the RTP channel
            _logger.debug("Starting NativeMediaClient for receive only...");
            _mediaClient = new NativeMediaClient(localHost, localRtpPort);

            SpeechSynthClient client = new SpeechSynthClient(ttsChannel);

            // Use the MRCP channel to instruct the cairo server to sythesize voice data and send it over the
            // RTP channel as specified in teh SIP invitation
            try {
                for (int i = 0; i < _repetitions; i++) {
                    if (_url) {
                        client.playOnePrompt(text);
                    } else {
                        client.playPrompt(text);
                    }
                }
            } catch (Exception e) {
                if (e instanceof MrcpInvocationException) {
                    MrcpResponse response = ((MrcpInvocationException) e).getResponse();
                    _logger.warn("MRCP response received:\n" + response);
                }
                _logger.warn(e, e);
                sipAgent.sendBye();
                sipAgent.dispose();
                sentBye = true;
                System.exit(1);
            }

        } else {
            //Invitation Timeout
            _logger.info("Sip Invitation timed out or failed.  Is server running?");
        }
        Thread.sleep(1000000);
    }
}