/* * Mobicents, Communications Middleware * * Copyright (c) 2008, Red Hat Middleware LLC or third-party * contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Middleware LLC. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 Lesser General Public License * for more details. * * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * * Boston, MA 02110-1301 USA */ package org.mobicents.media.server.impl.resource.dtmf; import java.io.IOException; import org.mobicents.media.Buffer; import org.mobicents.media.Format; import org.mobicents.media.server.impl.resource.GoertzelFilter; import org.mobicents.media.server.spi.dsp.Codec; /** * Implements inband DTMF detector. * * Inband means that DTMF is transmitted within the audio of the phone * conversation, i.e. it is audible to the conversation partners. Therefore only * uncompressed codecs like g711 alaw or ulaw can carry inband DTMF reliably. * Female voice are known to once in a while trigger the recognition of a DTMF * tone. For analog lines inband is the only possible means to transmit DTMF. * * Though Inband DTMF detection may work for other codecs like SPEEX, GSM, G729 * as DtmfDetector is using DSP in front of InbandDetector there is no guarantee * that it will always work. In future MMS may not have DSP in front of * InbandDetector and hence Inband detection for codecs like SPEEX, GSM, G729 * may completely stop * * @author Oleg Kulikov * @author amit bhayani */ public class InbandDetectorImpl extends DtmfBuffer { /** * The default duration of the DTMF tone. */ private final static int TONE_DURATION = 50; private final static Format[] FORMATS = new Format[]{Codec.LINEAR_AUDIO}; public final static String[][] events = new String[][]{ {"1", "2", "3", "A"}, {"4", "5", "6", "B"}, {"7", "8", "9", "C"}, {"*", "0", "#", "D"} }; private final static int[] lowFreq = new int[]{697, 770, 852, 941}; private final static int[] highFreq = new int[]{1209, 1336, 1477, 1633}; private GoertzelFilter[] lowFreqFilters = new GoertzelFilter[4]; private GoertzelFilter[] highFreqFilters = new GoertzelFilter[4]; private double threshold; private int level; private int offset = 0; private int toneDuration = TONE_DURATION; private int N = 8 * toneDuration; private double scale = (double) toneDuration / (double) 1000; private double p[] = new double[4]; private double P[] = new double[4]; private double[] signal; private double maxAmpl; /** * Creates new instance of Detector. */ public InbandDetectorImpl(String name) { super(name); signal = new double[N]; for (int i = 0; i < 4; i++) { lowFreqFilters[i] = new GoertzelFilter(lowFreq[i], N, scale); highFreqFilters[i] = new GoertzelFilter(highFreq[i], N, scale); } setVolume(DEFAULT_SIGNAL_LEVEL); } public void setDuration(int duartion) { this.toneDuration = duartion; } public int getDuration() { return this.toneDuration; } public void setVolume(int level) { this.level = level; threshold = Math.pow(Math.pow(10, level), 0.1) * Short.MAX_VALUE; } public int getVolume() { return level; } /** * (Non Java-doc). * * @see org.mobicents.media.protocol.BufferTransferHandler.transferData(). */ public void onMediaTransfer(Buffer buffer) throws IOException { byte[] data = (byte[]) buffer.getData(); int M = buffer.getOffset() + buffer.getLength(); int k = buffer.getOffset(); while (k < M) { while (offset < N && k < M - 1) { double s = ((data[k++] & 0xff) | (data[k++] << 8)); double sa = Math.abs(s); if (sa > maxAmpl) { maxAmpl = sa; } signal[offset++] = s; } //if dtmf buffer full check signal if (offset == N) { offset = 0; //and if max amplitude of signal is greater theshold //try to detect tone. if (maxAmpl >= threshold) { maxAmpl = 0; getPower(lowFreqFilters, signal, 0, p); getPower(highFreqFilters, signal, 0, P); String tone = getTone(p, P); if (tone != null) { push(tone); } } } } } private void getPower(GoertzelFilter[] filters, double[] data, int offset, double[] power) { for (int i = 0; i < 4; i++) { //power[i] = filter.getPower(freq[i], data, 0, data.length, (double) TONE_DURATION / (double) 1000); power[i] = filters[i].getPower(data, offset); } } /** * Searches maximum value in the specified array. * * @param data[] * input data. * @return the index of the maximum value in the data array. */ private int getMax(double data[]) { int idx = 0; double max = data[0]; for (int i = 1; i < data.length; i++) { if (max < data[i]) { max = data[i]; idx = i; } } return idx; } /** * Searches DTMF tone. * * @param f * the low frequency array * @param F * the high frequency array. * @return DTMF tone. */ private String getTone(double f[], double F[]) { int fm = getMax(f); boolean fd = true; for (int i = 0; i < f.length; i++) { if (fm == i) { continue; } double r = f[fm] / (f[i] + 1E-15); if (r < threshold) { fd = false; break; } } if (!fd) { return null; } int Fm = getMax(F); boolean Fd = true; for (int i = 0; i < F.length; i++) { if (Fm == i) { continue; } double r = F[Fm] / (F[i] + 1E-15); if (r < threshold) { Fd = false; break; } } if (!Fd) { return null; } return events[fm][Fm]; } public Format[] getFormats() { return FORMATS; } public boolean isAcceptable(Format format) { return format.matches(Codec.LINEAR_AUDIO); } }