/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.restcomm.media.resource.dtmf;
import org.restcomm.media.ComponentType;
import org.restcomm.media.component.AbstractSource;
import org.restcomm.media.component.audio.AudioInput;
import org.restcomm.media.component.oob.OOBInput;
import org.restcomm.media.scheduler.PriorityQueueScheduler;
import org.restcomm.media.spi.dtmf.DtmfGenerator;
import org.restcomm.media.spi.dtmf.DtmfGeneratorEvent;
import org.restcomm.media.spi.dtmf.DtmfGeneratorListener;
import org.restcomm.media.spi.format.AudioFormat;
import org.restcomm.media.spi.format.FormatFactory;
import org.restcomm.media.spi.format.Formats;
import org.restcomm.media.spi.listener.Listeners;
import org.restcomm.media.spi.listener.TooManyListenersException;
import org.restcomm.media.spi.memory.Frame;
import org.restcomm.media.spi.memory.Memory;
import org.restcomm.media.spi.pooling.PooledObject;
/**
* InbandGenerator generates Inband DTMF Tone only for uncompressed LINEAR
* codec. After creating instance of InbandGenerator, it needs to be initialized
* so that all the Tones are generated and kept ready for transmission once
* start is called.
*
* By default the Tone duration is 80ms. This is suited for Tone Detector who
* has Tone duration of greater than 40 and less than 80ms. For Tone Detector
* who's Tone duration is set greater than 80ms may fail to detect Tone
* generated by InbandGenerator(with duration 80ms). In that case increase the
* duration here too.
*
* @author yulian oifa
* @author amit bhayani
* @author Henrique Rosa (henrique.rosa@telestax.com)
*/
public class GeneratorImpl extends AbstractSource implements DtmfGenerator, PooledObject {
private final static AudioFormat linear = FormatFactory.createAudioFormat("linear", 8000, 16, 1);
private long period = 20000000L;
private int packetSize = (int)(period / 1000000) * linear.getSampleRate()/1000 * linear.getSampleSize() / 8;
private final static Formats formats = new Formats();
static {
formats.add(linear);
}
public final static String[][] events = new String[][]{
{"1", "2", "3", "A"},
{"4", "5", "6", "B"},
{"7", "8", "9", "C"},
{"*", "0", "#", "D"}
};
private int[] lowFreq = new int[]{697, 770, 852, 941};
private int[] highFreq = new int[]{1209, 1336, 1477, 1633};
private String digit = null; // Min duration = 40ms and max = 500ms
private String oobDigit = null;
private int oobDigitValue=-1;
private int toneDuration = 50;
private short A = Short.MAX_VALUE / 2;
private int volume = 0;
private int f1, f2;
private double dt;
private int pSize;
private double time = 0;
private AudioInput input;
private OOBInput oobInput;
private OOBGenerator oobGenerator;
private final Listeners<DtmfGeneratorListener> listeners;
DtmfGeneratorEvent event=new DtmfGeneratorEvent(GeneratorImpl.this,DtmfGeneratorEvent.COMPLETED);
public GeneratorImpl(String name, PriorityQueueScheduler scheduler) {
super(name, scheduler,scheduler.INPUT_QUEUE);
dt = 1.0 / linear.getSampleRate();
this.input=new AudioInput(ComponentType.DTMF_GENERATOR.getType(),packetSize);
this.connect(this.input);
this.oobInput=new OOBInput(ComponentType.DTMF_GENERATOR.getType());
this.oobGenerator=new OOBGenerator(scheduler,oobInput);
this.listeners = new Listeners<DtmfGeneratorListener>();
}
public void addListener(final DtmfGeneratorListener listener)
{
try
{
listeners.add(listener);
}
catch(final TooManyListenersException ignored)
{
// This exception is never thrown by Listeners.add();
}
}
public void removeListener(final DtmfGeneratorListener listener)
{
listeners.remove(listener);
}
public void clearAllListeners()
{
listeners.clear();
}
public AudioInput getAudioInput()
{
return this.input;
}
public OOBInput getOOBInput()
{
return this.oobInput;
}
@Override
public void activate() {
if(oobDigit!=null) {
oobGenerator.index=0;
oobGenerator.activate();
}
if (digit != null) {
time = 0;
start();
}
}
public void setOOBDigit(String digit) {
if(digit.charAt(0)>='0' && digit.charAt(0)<='9')
oobDigitValue=(digit.charAt(0)-'0');
else if(digit.charAt(0)=='*')
oobDigitValue=10;
else if(digit.charAt(0)=='#')
oobDigitValue=11;
else if(digit.charAt(0)>='A' && digit.charAt(0)<='D')
oobDigitValue=12+digit.charAt(0)-'A';
else if(digit.charAt(0)>='a' && digit.charAt(0)<='d')
oobDigitValue=12+digit.charAt(0)-'a';
else
return;
oobGenerator.index=0;
this.oobDigit=digit;
this.digit=null;
}
public void setDigit(String digit) {
this.oobDigit=null;
this.digit = digit;
this.time=0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (events[i][j].equalsIgnoreCase(digit)) {
f1 = lowFreq[i];
f2 = highFreq[j];
}
}
}
}
@Override
public void completed()
{
super.completed();
listeners.dispatch(event);
}
public String getDigit() {
return this.digit;
}
public String getOOBDigit() {
return this.oobDigit;
}
public void setToneDuration(int duration) {
if (duration < 40) {
throw new IllegalArgumentException("Duration cannot be less than 40ms");
}
this.toneDuration = duration;
}
public int getToneDuration() {
return toneDuration;
}
public int getVolume() {
return this.volume;
}
public void setVolume(int volume) {
if (volume > 0) {
throw new IllegalArgumentException("Volume has to be negative value expressed in dBm0");
}
this.volume = volume;
A = (short) (Math.pow(Math.pow(10, volume), 0.1) * (Short.MAX_VALUE / 2));
}
private short getValue(double t) {
return (short) (A * (Math.sin(2 * Math.PI * f1 * t) + Math.sin(2 * Math.PI * f2 * t)));
}
public Formats getNativeFormats() {
return formats;
}
@Override
public Frame evolve(long timestamp) {
if(time > (double) toneDuration / 1000.0)
return null;
int k = 0;
int frameSize = (int) ((double) 20 / 1000.0 / dt);
Frame frame = Memory.allocate(2* frameSize);
byte[] data = frame.getData();
for (int i = 0; i < frameSize; i++) {
short v = getValue(time + dt * i);
data[k++] = (byte) v;
data[k++] = (byte) (v >> 8);
}
frame.setOffset(0);
frame.setLength(2* frameSize);
frame.setTimestamp(getMediaTime());
frame.setDuration(20000000L);
time += ((double) 20) / 1000.0;
if(time >= (double)toneDuration / 1000.0)
listeners.dispatch(event);
return frame;
}
@Override
public void deactivate() {
stop();
oobGenerator.deactivate();
}
@Override
public void wakeup() {
if(this.oobDigit!=null)
oobGenerator.wakeup();
else if(this.digit!=null)
super.wakeup();
}
private class OOBGenerator extends AbstractSource {
int index=0;
int eventDuration=0;
int oobVolume;
public OOBGenerator(PriorityQueueScheduler scheduler,OOBInput input) {
super("oob generator", scheduler,scheduler.INPUT_QUEUE);
this.connect(input);
}
@Override
public Frame evolve(long timestamp) {
if(index > ((toneDuration / 20)+2))
return null;
Frame frame = Memory.allocate(4);
byte[] data=frame.getData();
data[0]=(byte)oobDigitValue;
oobVolume=0-volume;
if(index > (toneDuration / 20))
//with end of event flag
data[1]=(byte)(0xBF & oobVolume);
else
//without end of event flag
data[1]=(byte)(0x3F & oobVolume);
eventDuration=(short)(160*index);
data[2]=(byte)((eventDuration>>8) & 0xFF);
data[3]=(byte)(eventDuration & 0xFF);
frame.setOffset(0);
frame.setLength(4);
frame.setTimestamp(getMediaTime());
frame.setDuration(20000000L);
index++;
if(index == ((toneDuration / 20) + 2))
listeners.dispatch(event);
return frame;
}
@Override
public void activate() {
start();
}
@Override
public void deactivate() {
stop();
}
}
@Override
public void checkIn() {
// TODO Auto-generated method stub
}
@Override
public void checkOut() {
// TODO Auto-generated method stub
}
}