前言
这篇文章是目前最详细的 Matlab 电话按键拨号器设计开源教程。如果您在做课程设计或实验时需要参考本文章,请注意避免与他人重复,小心撞车。博主做这个也是因为实验所需,我在这方面只是初学者,但实际上,从完全不懂 DTMF 和 Matlab 的 App 设计,到功能设计完备,也不过花了两个下午而已。在这个过程中,我也尝试搜索资料,发现可选的资源不仅有限,还需要付费。因此,我只能从仅有的资料和视频中推测该做些什么。在此,希望大家在跟随这篇文章学习时,能够以学习的态度面对。
DTMF原理与实现
一、DTMF简介
DTMF是一种信号系统,广泛应用于电话按键音的传输。它是由两个不同频率的音调组合而成,每个按键(0-9,*,#)对应一个唯一的频率组合,这样可以通过按键发出的声音来传输数据。
按键和频率对应表:
按键 | 低频组 | 高频组 |
---|---|---|
1 | 697 Hz | 1209 Hz |
2 | 697 Hz | 1336 Hz |
3 | 697 Hz | 1477 Hz |
A | 697 Hz | 1631 Hz |
4 | 770 Hz | 1209 Hz |
5 | 770 Hz | 1336 Hz |
6 | 770 Hz | 1477 Hz |
B | 770 Hz | 1631 Hz |
7 | 852 Hz | 1209 Hz |
8 | 852 Hz | 1336 Hz |
9 | 852 Hz | 1477 Hz |
C | 852 Hz | 1631 Hz |
* | 941 Hz | 1209 Hz |
0 | 941 Hz | 1336 Hz |
# | 941 Hz | 1477 Hz |
D | 941 Hz | 1631 Hz |
下方图更加具体形象一点:
工作过程
- 按键识别:当用户按下电话按键时,电话生成相应的DTMF信号。
- 信号传输:DTMF信号通过电话线路传输。
- 信号解码:接收端(例如电话交换机)接收到DTMF信号,并通过滤波器和检测器识别出对应的按键。
每个按键只需两个频率,信号生成和检测简单,且具有较高的抗干扰能力,即使在嘈杂的环境中也能准确传输信息。
二、DTMF编码实现
我们首先需要的输入一个1~12以内组成的一个号码序列,其中1~9对应键盘数字1~9对应键盘数字1~9,而对于0、*、#我们分别将其映射为数字10、11、12。
每当按下键盘时候会发声音,采样频率为8kHz,每个拨音持续0.5s,拨音之间间隔0.1s停顿。
这里要做映射的内容只有下面的部分
1209 Hz 1336 Hz 1477 Hz
697 Hz 1 2 3
770 Hz 4 5 6
852 Hz 7 8 9
941 Hz * 0 #
通过生成这两个频率的正弦波,并将它们相加,可以得到一个 DTMF 信号。例如,按下 '1' 时,会生成如下信号:
function tones = dtmfdial(nums)
% @ 夏天是冰红茶
% DTMFDIAL Create a vector of tones which will dial
% a DTMF (Touch Tone) telephone system
% usage: tones = dtmfdial(nums)
% nums = vector of numbers ranging from 1 to 12
% tones = vector containing the corresponding tones
if nargin < 1
error('DTMFDIAL requires one input');
end
output_signal = [];
% 定义DTMF音调的频率
low_freqs = [697, 770, 852, 941];
high_freqs = [1209, 1336, 1477, 1633];
% 数字序列行列索引
dtmf_map = [1, 1; 1, 2; 1, 3; % 1, 2, 3
2, 1; 2, 2; 2, 3; % 4, 5, 6
3, 1; 3, 2; 3, 3; % 7, 8, 9
4, 2; 4, 1; 4, 3]; % 0, *, #
% Define parameters
fs = 8000;
duration = 0.5;
pause_time = 0.1;
t_tone = 0:1/fs:duration - 1/fs;
t_pause = 0:1/fs:pause_time - 1/fs;
% 暂停静音
silence = zeros(size(t_pause));
% 给每个号码生成DTMF音调
for i = 1:length(nums)
num = nums(i);
if num < 1 || num > 12
error('Number sequence must contain values between 1 and 12');
end
% 获取DTMF映射的相应行、列索引
row = dtmf_map(num, 1);
col = dtmf_map(num, 2);
% 生成DTMF音调
tone = sin(2*pi*low_freqs(row)*t_tone) + sin(2*pi*high_freqs(col)*t_tone);
output_signal = [output_signal, tone, silence];
end
tones = output_signal;
end
三、DTMF解码实现
DTMF解码有两个部分组成,分别是由一个带通滤波器和一个检测器组成的。
其中带通滤波器用于分离各频率成分,检测器用于检测所有带通滤波器输出信号的大小,从而判断在每个时间段中存在哪两个频率分量,检测器用于确定哪两个频率最有可能包含在这个DTMF音中。
滤波器的设计如下:
这里,L表示滤波器长度,表示采样频率,表示带通滤波器的中心频率。L越大,带宽越窄。
这个实现非常简单
function h = Zjr_Bandpass_Filter(fb, L, fs)
% @ 夏天是冰红茶
% Zjr_Bandpass_Filter Generate a bandpass filter based on given parameters
% fb: Center frequency of the bandpass filter
% L: Length of the filter
% fs: Sampling frequency
if nargin < 3
% 如果没有提供fs,则使用默认值8000
fs = 8000;
end
n = 0:L-1;
h = (2 / L) * cos(2 * pi * fb * n / fs);
end
DTMF检测器设计
function ss = dtmfscor(xx, freq, L, fs)
% @ 夏天是冰红茶
% DTMFSCOR
% ss = dtmfscor(xx, freq, L, [fs])
% return 1(true) if freq is present in xx
% 0(false) if freq is not present in xx
% xx = input DTMF signal
% freq = test frequency
% L = length of FIR bandpass filter
% fs = sampling frequency (default is 8k)
% The signal detection is done by filtering xx with a length-L
% BPF, hh, squaring the output, and comparing with an arbitrary
% set point based on the average power of xx
if nargin < 4
fs = 8000;
end
hh = Zjr_Bandpass_Filter(freq, L, fs);
filtered_signal = conv(xx, hh, 'same');
% 计算平方滤波信号的平均功率
squared_signal = filtered_signal .^ 2;
mean_squared_signal = mean(squared_signal);
% 计算原始信号的平均功率
mean_original_signal = mean(xx .^ 2);
% 滤波信号的平均功率与阈值进行比较
threshold = mean_original_signal / 5;
ss = (mean_squared_signal > threshold);
end
DTFM编码部分的实现基于以上两个部分完成,它的基本原理就是通过检测信号中存在的特定频率来确定按下的键。每个 DTMF 按键对应两个频率,一个低频和一个高频。通过检测这些频率的存在,可以确定按下的按键。
function key = dtmfdeco(xx, L, fs)
% @ 夏天是冰红茶
% DTMFDECO key = dtmfdeco(xx, [fs])
% returns the key number corresponding to the DTMF waveform, xx
% fs = sampling freq (default = 8k Hz if not specified)
if nargin < 2
fs = 8000;
end
% 定义DTMF音调的频率
low_freqs = [697, 770, 852, 941];
high_freqs = [1209, 1336, 1477, 1633];
% 数字序列行列索引
dtmf_map = [1, 1; 1, 2; 1, 3; % 1, 2, 3
2, 1; 2, 2; 2, 3; % 4, 5, 6
3, 1; 3, 2; 3, 3; % 7, 8, 9
4, 2; 4, 1; 4, 3]; % 0, *, #
% 初始化检测结果
low_detected = false(length(low_freqs), 1);
high_detected = false(length(high_freqs), 1);
% 检测低频分量
for i = 1:length(low_freqs)
if dtmfscor(xx, low_freqs(i), L, fs)
low_detected(i) = true;
end
end
% 检测高频分量
for i = 1:length(high_freqs)
if dtmfscor(xx, high_freqs(i), L, fs)
high_detected(i) = true;
end
end
% 找到检测到的低频和高频索引
low_idx = find(low_detected);
high_idx = find(high_detected);
% 确保每次只检测到一个低频和一个高频
if isscalar(low_idx) && isscalar(high_idx)
key = find(ismember(dtmf_map, [low_idx, high_idx], 'rows'));
else
key = [];
end
end
四、DTMF程序验证
接下来我们需要对我们前面所写的函数进行验证。
使用 dtmfdial 函数生成拨号音序列,并使用 sound 函数播放这些音调,通过遍历 input_keys,我们逐个解码每个拨号音:
- 确定当前拨号音的起始和结束索引。
- 提取当前的拨号音段。
- 使用 dtmfdeco 函数解码当前的拨号音段。
- 将解码结果存储在 decoded_keys 数组中。
- 更新起始索引,以处理下一个拨号音。
clc;
L=64;
input_keys = [1, 2, 3, 10, 11, 12];
encoded_tones = dtmfdial(input_keys);
sound(encoded_tones, 8000);
decoded_keys = [];
sample_duration = 0.5; % 每个拨号音的持续时间
gap_duration = 0.1; % 拨号音之间的停顿时间
fs = 8000; % 采样频率
% 按照编码的音序列的格式解析每个拨号音
start_index = 1;
for i = 1:length(input_keys)
end_index = start_index + sample_duration * fs - 1;
current_tone = encoded_tones(start_index:end_index);
decoded_key = dtmfdeco(current_tone, L, fs);
decoded_keys = [decoded_keys, decoded_key];
start_index = end_index + gap_duration * fs + 1;
end
% 输出解码结果
fprintf('Decoded keys: ');
disp(decoded_keys);
% 验证解码结果是否与输入的按键序列一致
if isequal(input_keys, decoded_keys)
fprintf('The decoded keys match the input keys.\n');
else
fprintf('The decoded keys do not match the input keys.\n');
end
打印结果如下所示:
Decoded keys: 1 2 3 10 11 12
The decoded keys match the input keys.
验证成功!
Matlab的app设计
这个部分理应用你自己完成,这里我只是打个样。接下来我之会讲解一下其中回调函数中重要的一些地方,建议每个部件都应该有自己的名字,就像是使用Qt或者PyQt一样。
按钮的回调
这里以按钮1为例,我重命名为:app.Key_1,后面按钮均按照这样的规律。我们需要在按下键1时可以发出声音,并且将内容显示在其上方的文字框(app.Text_Dialing)当中,而且要让频谱图显示在左侧的坐标当中。
% Button pushed function: Key1
function Key1ButtonPushed(app, event)
% 按键1的回调函数,按下后在文本框中显示
currentText = app.Text_Dialing.Value; % 当前文本区域的值
if isempty(currentText)
newText = '1';
else
newText = strcat(currentText{1}, '1');
end
app.Text_Dialing.Value = {newText};
encoded_tones = dtmfdial([1]);
sound(encoded_tones, 8000);
displaySpectrum(app, encoded_tones);
end
displaySpectrum为本路径下写的一个功能函数,即显示当前按钮的频谱图,每次点击都会被刷新,该功能的实现很简单,请自行在下面的资源中查找。
这个接下来就是复制粘贴到我们每个按钮的回调了。
拨号与挂断的回调
当点击拨号时,将会对之前输入的电话序号进行发音,发音结束后询问是否要保存音频。当我点击挂断时候,刷新我们的文字框以及坐标轴。需要注意的是,这里的文字框显示的是*、#、0,所以一定要在传入函数前进行映射。
% Value changed function: Key_Dialing
function Key_DialingValueChanged(app, event)
value = app.Key_Dialing.Value;
currentText = app.Text_Dialing.Value;
% 将当前文本区域的值转换为字符数组
if ~isempty(currentText)
currentText = currentText{1}; % 转换为字符串
dialedNumbers = [];
% 遍历当前文本的每个字符
for i = 1:length(currentText)
char = currentText(i);
if ismember(char, ['0':'9', '*', '#'])
switch char
case '0'
num = 10;
case '*'
num = 11;
case '#'
num = 12;
otherwise
num = str2double(char);
end
dialedNumbers(end+1) = num;
end
end
disp(dialedNumbers);
encoded_tones = dtmfdial(dialedNumbers);
sound(encoded_tones, 8000);
duration = length(encoded_tones) / 8000;
% 暂停等待拨号音结束
pause(duration);
choice = questdlg('是否保存该音调?', ...
'保存音调', ...
'是', '否', '否');
switch choice
case '是'
[file, path] = uiputfile('*.wav', '保存音调为');
if ischar(file) && ischar(path)
filename = fullfile(path, file);
normalized_tones = encoded_tones / max(abs(encoded_tones));
audiowrite(filename, normalized_tones, 8000);
msgbox('音调已保存', '保存成功');
else
msgbox('保存已取消', '取消');
end
case '否'
% 不做任何处理
end
end
end
音频转为数字序号
这部分可以讲一讲,下面的代码是我写的测试草稿,app中用到的具体的函数名叫convert_wav2num。
clc;
filename = 'test.wav';
[y, fs] = audioread(filename);
L = 64; % DTMF 解码的长度参数
sample_duration = 0.5; % 每个拨号音的持续时间
gap_duration = 0.1; % 拨号音之间的停顿时间
decoded_numbers = [];
start_index = 1;
while start_index <= length(y)
end_index = start_index + round(sample_duration * fs) - 1;
if end_index > length(y)
end_index = length(y);
end
current_tone = y(start_index:end_index);
decoded_key = dtmfdeco(current_tone, L, fs);
if ~isempty(decoded_key)
decoded_numbers = [decoded_numbers, decoded_key];
end
start_index = end_index + round(gap_duration * fs);
end
fprintf('Decoded phone numbers: ');
disp(decoded_numbers);
首先参数的定义要与前面保存一致。从指定的音频文件中读取音频数据,并获取采样率。遍历音频数据,将其分割成独立的拨号音段,并对每个音段进行DTMF解码,输出解码得到的电话号码。
运行截图如下所示:
解码的回调
这里可以通过直接在文字框中输入wav文件的路径,也可以通过上面菜单栏选项当中的打开资源管理器选择。然后直接点击解码,通过弹窗显示解码的电话号码。
% Value changed function: Key_Dialing_Decoding
function Key_Dialing_DecodingValueChanged(app, event)
wavPath = app.Decoding_path.Value;
if isempty(wavPath) || ~isfile(wavPath)
msgbox('请选择有效的 WAV 文件路径');
return;
end
decoded_numbers = convert_wav2num(wavPath, 64, 0.5, 0.1);
encoded_tones = dtmfdial(decoded_numbers);
sound(encoded_tones, 8000);
decoded_numbers_str = {};
for i = 1:length(decoded_numbers)
switch decoded_numbers(i)
case 10
decoded_numbers_str{end+1} = '0';
case 11
decoded_numbers_str{end+1} = '*';
case 12
decoded_numbers_str{end+1} = '#';
otherwise
decoded_numbers_str{end+1} = num2str(decoded_numbers(i));
end
end
if ~isempty(decoded_numbers_str)
msgbox(['解码结果: ', strjoin(decoded_numbers_str)], '解码结果');
else
msgbox('解码失败', '解码结果');
end
end
动图演示
项目资源
请通过GitHub下载,你的Start就是对我最大的帮助:
Auorui/Design-of-Matlab-Phone-Key-Dialer: Matlab电话按键拨号器设计 (github.com)
本人matlab版本为2024a,低版本可能会出ColorPicker报错,直接删除包含的字段即可。
其中也可以下载exe版本
参考文章
DTMF_百度百科 (baidu.com)
数字信号处理综合实验——Matlab实现DTMF信号的产生与提取_dtmf信号的产生及检测matlab-CSDN博客
【数字信号】基于matlab GUI DTMF电话模拟系统(频谱图+时域图+语谱图)【含Matlab源码 2092期】_用matlab程序设计电话拨键的gui页面,当按键被输进去以后,会显示时域或频域波形,之-CSDN博客