Matlab电话按键拨号器设计

前言

这篇文章是目前最详细的 Matlab 电话按键拨号器设计开源教程。如果您在做课程设计或实验时需要参考本文章,请注意避免与他人重复,小心撞车。博主做这个也是因为实验所需,我在这方面只是初学者,但实际上,从完全不懂 DTMF 和 Matlab 的 App 设计,到功能设计完备,也不过花了两个下午而已。在这个过程中,我也尝试搜索资料,发现可选的资源不仅有限,还需要付费。因此,我只能从仅有的资料和视频中推测该做些什么。在此,希望大家在跟随这篇文章学习时,能够以学习的态度面对。

DTMF原理与实现

一、DTMF简介

DTMF是一种信号系统,广泛应用于电话按键音的传输。它是由两个不同频率的音调组合而成,每个按键(0-9,*,#)对应一个唯一的频率组合,这样可以通过按键发出的声音来传输数据。

按键和频率对应表: 

按键低频组高频组
1697 Hz1209 Hz
2697 Hz1336 Hz
3697 Hz1477 Hz
A697 Hz1631 Hz
4770 Hz1209 Hz
5770 Hz1336 Hz
6770 Hz1477 Hz
B770 Hz1631 Hz
7852 Hz1209 Hz
8852 Hz1336 Hz
9852 Hz1477 Hz
C852 Hz1631 Hz
*941 Hz1209 Hz
0941 Hz1336 Hz
#941 Hz1477 Hz
D941 Hz1631 Hz

下方图更加具体形象一点: 

c2e7273323904a218503f51bb9e1ba0b.png

工作过程

  • 按键识别:当用户按下电话按键时,电话生成相应的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' 时,会生成如下信号:

eq?s%28t%29%20%3D%20sin%282%20%5Cpi%20%5Ccdot697%5Ccdot%20t%29+sin%282%5Cpi%5Ccdot%201209%5Ccdot%20t%29

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音中。

滤波器的设计如下:

eq?h%5Bn%5D%20%3D%20%5Cfrac%7B2%7D%7BL%7Dcos%28%5Cfrac%7B2%5Cpi%20f_%7Bb%7Dn%7D%7Bf_%7Bs%7D%7D%29

这里,L表示滤波器长度,eq?f_%7Bs%7D表示采样频率,eq?f_%7Bb%7D表示带通滤波器的中心频率。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一样。

a836f9d834c54729a6f75867b63751a6.png

按钮的回调

这里以按钮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解码,输出解码得到的电话号码。

运行截图如下所示:

a79be64bd8c744f0a7305ed5d2735991.png

解码的回调

这里可以通过直接在文字框中输入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

动图演示

12f466970fac4aca8774adba58e71889.gif

项目资源

请通过GitHub下载,你的Start就是对我最大的帮助:

Auorui/Design-of-Matlab-Phone-Key-Dialer: Matlab电话按键拨号器设计 (github.com)

本人matlab版本为2024a,低版本可能会出ColorPicker报错,直接删除包含的字段即可。 

其中也可以下载exe版本

按键按的太快声音会卡顿,可以将sound函数换成soundsc即可解决。

参考文章

DTMF_百度百科 (baidu.com)

数字信号处理综合实验——Matlab实现DTMF信号的产生与提取_dtmf信号的产生及检测matlab-CSDN博客

【数字信号】基于matlab GUI DTMF电话模拟系统(频谱图+时域图+语谱图)【含Matlab源码 2092期】_用matlab程序设计电话拨键的gui页面,当按键被输进去以后,会显示时域或频域波形,之-CSDN博客

 

 

 

 

 

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天是冰红茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值