双音多频

双音多频信号（Dual-Tone Multi-Frequency，简称：DTMF），电话系统中电话机与交换机之间的一种信令，最常用于拨号时发送被叫号码。不过双音多频的发明，除了缩短拨号时间，也扩展了拨号之外的功能，例如自动总机、互动式语音应答。

在双音多频信号普及之前，电话系统中使用一连串的断续脉冲来传送被叫号码，称为脉冲拨号。脉冲拨号需要电信局中的操作员手工完成长途接续。由脉冲拨号到双音复频这几年转换期当中，新设计的电话机为了能相容于两种系统，大都设计为复频/脉冲(TONE / PULSE)两用。

双音多频信号是贝尔实验室发明的，其目的是为了自动完成长途呼叫。

因为传统的脉冲拨号，只能把信号送到电话接上的这一台电信交换机，但长距通话时，往往必须经由多台电信交换机才能完成，双音多频信号可以克服这个障碍，利用本来就用来传送声音的电话线传送使用者的按键信号。

因为双音多频的发明，让自动总机与互动式语音应答得以发展，此类设备可以在接听电话后自动宣读预录的语音，再依据发话端的按键信号做相对回应。

## 按键

<image>

<image>

双音多频的拨号键盘是${\color{Red}4*4}$的矩阵，有${\color{RoyalBule}10}$个数字键和${\color{RubineRed}6}$个字符键，每个数字或字符都是由两个单频信号的组合来进行传输，因此键盘上每个按键所对应的信号都可以表示为$\mathcal{x}(t)=\mathrm{A}\sin (2 \pi f_{\color{Maroon}L}t)+\mathrm{B}\cos (2 \pi f_{\color{Maroon}H}t)$，其中$f_{\color{Maroon}L}$和$f_{\color{Maroon}H}$分别表示按键所在的行和列对应的频率值。换句话说每一行代表一个高频，每一列代表一个低频。也就是说每按一个键就发送一个高频和低频的正弦信号组合，比如'1'相当于697和1209赫兹（Hz）。交换机可以解码这些频率组合并确定所对应的按键。

AT&T贝尔实验室提出用DTMF信号作为音频电话的拨号信号，因为这种方式可以提供更高的拨号速率，且容易被自动检测和识别。但反之，DTMF信号的这个优点也很容易变成致命的缺点，因为容易被交换机检测和识别，也就意味着容易被意图为之第三方破解。破解的原理很简单，只要能估计出DTMF信号中两个单频信号的频率值，再根据底下电话机键盘的频率阵列表格的对应关系就可以反推出按键值。

1209 Hz｜1336 Hz｜1477 Hz｜1633 Hz

697 Hz｜1｜2｜3｜A

770 Hz｜4｜5｜6｜B

852 Hz｜7｜8｜9｜C

941 Hz｜、｜0｜#｜D

注意：ABCD四个字母并不使用于拨号键盘，但常做为机器对机器的控制信号，例如DTMF格式的来话显示。

## 按键识别实验

根据拨号音识别号码的关键就在于准确估计出DTMF信号的频率值。有很多种方法可以估计DTMF信号的频率值，在此举例两种实现方法，一种是基于"带通滤波器"的方法，另一种方法为基于"格策尔算法(Goertzel algorithm)"。

1.带通滤波器算法:

滤波器算法识别按键的关键是设计8个带通滤波器，每个带通滤波器的中心频率对应着低/高频组的各个频率点。将待识别的拨号音也就是DTMF的信号波型依次通过这8个带通滤波器。理论上只有频率成分与滤波器中心频率一致的信号才会通过，在滤波器输出端检测能量最大者即可判断出低/高频序号，最后再通过电话机键盘的频率阵列表格的对应关系即可反推出按键值。

以下展示使用带通滤波器算法应用于MATLAB程式码:

```matlab
clear
clc
fs=8000;
t=(0:800)/fs;
fcolumns1 = 697;fcolumns2 = 770;fcolumns3 = 852;fcolumns4 = 941;
frow1 = 1209;frow2 = 1336;frow3 = 1477;
num0 = sin(2*pi*fcolumns4*t)+sin(2*pi*frow2*t); %數字0
num1 = sin(2*pi*fcolumns1*t)+sin(2*pi*frow1*t); %數字1
num2 = sin(2*pi*fcolumns1*t)+sin(2*pi*frow2*t); %數字2
num3 = sin(2*pi*fcolumns1*t)+sin(2*pi*frow3*t); %數字3
num4 = sin(2*pi*fcolumns2*t)+sin(2*pi*frow1*t); %數字4
num5 = sin(2*pi*fcolumns2*t)+sin(2*pi*frow2*t); %數字5
num6 = sin(2*pi*fcolumns2*t)+sin(2*pi*frow3*t); %數字6
num7 = sin(2*pi*fcolumns3*t)+sin(2*pi*frow1*t); %數字7
num8 = sin(2*pi*fcolumns3*t)+sin(2*pi*frow2*t); %數字8
num9 = sin(2*pi*fcolumns3*t)+sin(2*pi*frow3*t); %數字9
numStar = sin(2*pi*fcolumns4*t)+sin(2*pi*frow1*t); %符號*
numJin = sin(2*pi*fcolumns4*t)+sin(2*pi*frow3*t); %符號#
blank = zeros(size(num1)); %間隔
f = [fcolumns1 fcolumns2 fcolumns3 fcolumns4 frow1 frow2 frow3];
f_Low = [fcolumns1 fcolumns2 fcolumns3 fcolumns4];
f_High = [frow1 frow2 frow3];

%設計帶通濾波器(low frequency)
N = 400;
Bandwidth = 70;
B_Low = zeros(4,N+1); %存放低頻組的濾波器係數
for i = 1:4
    Wo = f_Low(i);
    wc1 = ( Wo - Bandwidth/2 ) * 2* pi/fs;
    wc2 = ( Wo + Bandwidth/2 ) * 2* pi/fs;
    B_Low(i, :) = fir2(N,[0,wc1/pi,wc2/pi,1],[0,1,1,0]);
end

%設計帶通濾波器(high frequency)
N = 200;
Bandwidth = 110;
B_High = zeros(3,N+1); %存放高頻組的濾波器係數
for i = 1:3
    Wo = f_High(i);
    wc1 = ( Wo - Bandwidth/2 ) * 2* pi/fs;
    wc2 = ( Wo + Bandwidth/2 ) * 2* pi/fs;
    B_High(i, :) = fir2(N,[0,wc1/pi,wc2/pi,1],[0,1,1,0]);
end

DialNum = num1; %DTMF訊號
sound(DialNum);

%計算當前信號與各個頻率點的距離(low frequency)
Diatance_Low = zeros(1,4);
for i = 1:4
    Output = filter(B_Low(i, :),1,DialNum);
    Diatance_Low(1,i) = max(abs(fft(Output)));
end
[maxnum_low,index_low] = max(Diatance_Low(1, :));

%計算當前信號與各個頻率點的距離(high frequency)
Diatance_High = zeros(1,3);
for i = 1:3
    Output = filter(B_High(i, :),1,DialNum);
    Diatance_High(1,i) = max(abs(fft(Output)));
end
[maxnum_high,index_high] = max(Diatance_High(1, :));

%判斷按鍵
if index_low == 1 && index_high == 1
    keynum = '1';
elseif index_low == 1 && index_high == 2
    keynum = '2';
elseif index_low == 1 && index_high == 3
    keynum = '3';
elseif index_low == 2 && index_high == 1
    keynum = '4';
elseif index_low == 2 && index_high == 2
    keynum = '5';
elseif index_low == 2 && index_high == 3
    keynum = '6';
elseif index_low == 3 && index_high == 1
    keynum = '7';
elseif index_low == 3 && index_high == 2
    keynum = '8';
elseif index_low == 3 && index_high == 3
    keynum = '9';
elseif index_low == 4 && index_high == 2
    keynum = '0';
elseif index_low == 4 && index_high == 1
    keynum = '*';
elseif index_low == 4 && index_high == 3
    keynum = '#';
end
keynum
```

2.格策尔算法(Goertzel algorithm):

理论上DTMF信号只会在两个固定的频率点上出现能量，如何准确又高效地估计这两个频率值(低/高频组)是识别拨号音的关键。传统的频谱估计方法得到的是一个频率区间内所有频率点的估计结果，而对于DTMF信号我们只在乎那8个固定频率点上的功率谱估计值。而Goertzel算法就是估计DTMF信号功率谱最经典又实用的方法，这个算法只估计DTMF信号特定频率点上的功率谱。

以下展示MATLAB利用Goertzel算法估计DTMF信号的范例:

```matlab
clear
clc
fs=8000;
t=(0:2000)/fs;
fcolumns1=697;fcolumns2=770;fcolumns3=852;fcolumns4=941;
frow1=1209;frow2=1336;frow3=1477;
num0=sin(2*pi*fcolumns4*t)+sin(2*pi*frow2*t); %數字0
num1=sin(2*pi*fcolumns1*t)+sin(2*pi*frow1*t); %數字1
num2=sin(2*pi*fcolumns1*t)+sin(2*pi*frow2*t); %數字2
num3=sin(2*pi*fcolumns1*t)+sin(2*pi*frow3*t); %數字3
num4=sin(2*pi*fcolumns2*t)+sin(2*pi*frow1*t); %數字4
num5=sin(2*pi*fcolumns2*t)+sin(2*pi*frow2*t); %數字5
num6=sin(2*pi*fcolumns2*t)+sin(2*pi*frow3*t); %數字6
num7=sin(2*pi*fcolumns3*t)+sin(2*pi*frow1*t); %數字7
num8=sin(2*pi*fcolumns3*t)+sin(2*pi*frow2*t); %數字8
num9=sin(2*pi*fcolumns3*t)+sin(2*pi*frow3*t); %數字9
numStar=sin(2*pi*fcolumns4*t)+sin(2*pi*frow1*t); %符號*
numJin=sin(2*pi*fcolumns4*t)+sin(2*pi*frow3*t); %符號#
blank=zeros(size(num1)); %間隔
f=[fcolumns1 fcolumns2 fcolumns3 fcolumns4 frow1 frow2 frow3];
freq_indices=round(f/fs*length(t))+1;

%DTMF信號%
CellPhoneNum=[num1 blank num3 blank num9 blank num8 blank num0 blank num7 blank num6 blank num6 blank num5 blank num2 blank num4];
CellPhoneNum=0.2*randn(size(CellPhoneNum))+CellPhoneNum;
sound(CellPhoneNum);

for i=1:2:21
    DialNumber=CellPhoneNum(((i-1)*length(t)+1):i*length(t));
    dft_data=goertzel(DialNumber,freq_indices);
    temp = sort(abs(dft_data),'descend'); %找最大的兩個頻率點數值
    temp_index1 = find(abs(dft_data) == temp(1));
    temp_inedx2 = find(abs(dft_data) == temp(2));
    if temp_index1 < temp_inedx2 %保證 temp_index1代表低頻,temp_index2代表高頻
        index_low = temp_index1;
        index_high = temp_inedx2;
    else
        index_low = temp_inedx2;
        index_high = temp_index1;
    end
    %判斷按鍵
    if index_low == 1 && index_high == 5
        keynum = '1';
    elseif index_low == 1 && index_high == 6
         keynum = '2';
    elseif index_low == 1 && index_high == 7
         keynum = '3';
    elseif index_low == 2 && index_high == 5
         keynum = '4';
    elseif index_low == 2 && index_high == 6
         keynum = '5';
    elseif index_low == 2 && index_high == 7
         keynum = '6';
    elseif index_low == 3 && index_high == 5
         keynum = '7';
    elseif index_low == 3 && index_high == 6
         keynum = '8';
    elseif index_low == 3 && index_high == 7
         keynum = '9';
    elseif index_low == 4 && index_high == 6
         keynum = '0';
    elseif index_low == 4 && index_high == 5
         keynum = '*';
    elseif index_low == 4 && index_high == 7
         keynum = '#';
    end
    PhoneCell_Indent(round(i/2)) = keynum;
end
display(PhoneCell_Indent) %顯示識別結果
```

## 特殊音频

事件｜低频｜高频｜周期(on / off)

忙音｜480 Hz｜620 Hz｜0.5 s / 0.5 s

回铃音（美及英式）｜440 Hz｜480 Hz｜1 s / 4 s

拨号音（美及英式）｜350 Hz｜440 Hz｜持续长音

欧式音调：

事件｜低频｜高频

忙音（欧洲大部）｜425 Hz｜----

回铃音（英国和爱尔兰）｜400 Hz｜450 Hz

回铃音（欧洲大部）｜425 Hz｜----

拨号音（英国）｜350 Hz｜450 Hz

拨号音（欧洲大部）｜425 Hz｜----