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

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

工作过程

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

s(t) = sin(2 \pi \cdot697\cdot t)+sin(2\pi\cdot 1209\cdot t)

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

滤波器的设计如下:

h[n] = \frac{2}{L}cos(\frac{2\pi f_{b}n}{f_{s}})

这里,L表示滤波器长度,f_{s}表示采样频率,f_{b}表示带通滤波器的中心频率。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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/714460.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【chatbot-api开源项目】开发文档

chatbot-api 1. 需求分析1-1. 需求分析1-2. 系统流程图 2. 技术选型3. 项目开发3-1. 项目初始化3-2. 爬取接口获取问题接口回答问题接口创建对应对象 3-3. 调用AI3-4. 定时自动化回答 4. Docker部署5. 扩展5-1. 如果cookie失效了怎么处理5-2. 如何更好的对接多个回答系统 Gitee…

Web渗透信息收集进阶

网站敏感目录与文件 网站敏感目录表示网站目录中容易被恶意人员利用的一些目录。通常恶意人员都是通过工具扫描&#xff0c;来扫出网站的敏感目录&#xff0c;敏感目录是能够得到其他网页的信息&#xff0c;从而找到后台管理页面&#xff0c;尝试进入后台等&#xff0c;扫描网…

在Ubuntu中创建Ruby on Rails项目并搭建数据库

新建Rails项目 先安装bundle Ruby gem依赖项工具&#xff1a; sudo apt install bundle 安装Node.js: sudo apt install nodejs 安装npm 包管理器&#xff1a; sudo apt install npm 安装yarn JavaScript包管理工具&#xff1a; sudo apt install yarn 安装webpacker: …

leetcode236. 二叉树的最近公共祖先

一、题目描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 二、输入输出实例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;root [3,5,1,6,2,0,8,null,null,7,4], p 5, q 1 输出&#xff1a;3 解释&#xff1a;节点 5 和节点 1 的最近公共祖先…

Ps:脚本事件管理器

Ps菜单&#xff1a;文件/脚本/脚本事件管理器 Scripts/Script Events Manager 脚本事件管理器 Script Events Manager允许用户将特定的事件&#xff08;如打开、存储或导出文件&#xff09;与 JavaScript 脚本或 Photoshop 动作关联起来&#xff0c;以便在这些事件发生时自动触…

按键输入消抖

按键输入是人机对话不可缺少的一部分&#xff0c;对于消抖设计&#xff0c;一种是软件消抖&#xff0c;一种是硬件消抖。但在单片机电路设计中&#xff0c;采用电容消抖才是最佳的选择&#xff0c;其次才是定时器消抖。 1、按键输入采用软件消抖 1)、通过定时器方式定时读取按…

【Android面试八股文】请你描述一下JVM的内存模型

文章目录 JVM内存模型1. 方法区(Method Area)运行时常量池(Runtime Constant Pool)2. 堆(Heap)3. 栈(Stack)4. 本地方法栈(Native Method Stack)5. 程序计数器(Program Counter Register)6. 直接内存(Direct Memory)JVM内存溢出的情况Java的口号是: “Write onc…

生产者消费者模型的同步与互斥:C++代码实现

文章目录 一、引言二、生产者消费者模型概述1、基本概念和核心思想2、生产者消费者模型的优点 三、消费者和生产者之间的同步与互斥四、代码实现1、事前准备2、环形队列的实现3、阻塞队列的实现4、两种实现方式的区别 一、引言 在现代计算机系统中&#xff0c;很多任务需要同时…

稀疏矩阵是什么 如何求

稀疏矩阵是一种特殊类型的矩阵&#xff0c;其中大多数元素都是零。由于稀疏矩阵中非零元素的数量远少于零元素&#xff0c;因此可以使用特定的数据结构和算法来高效地存储和处理它们&#xff0c;从而节省存储空间和计算时间。 RowPtr 数组中的每个元素表示对应行的第一个非零元…

FreeRTOS队列(queue)

队列(queue)可以用于"任务到任务"、 "任务到中断"、 "中断到任务"直接传输信息。 1、队列的特性 1、1常规操作 队列的简化操如下图所示&#xff0c;从此图可知&#xff1a; 队列中可以包含若干数据&#xff1a;队列中有若干项&#xff0c;这…

PostgreSql中使用to_char函数、date()函数可能会导致索引无法充分利用,导致查询速度无法提升

今天在处理接口请求速度慢的问题&#xff0c;惊奇的发现加了索引&#xff0c;但还是请求很忙。由于card_stop_info表有300w条数据&#xff0c;这时候关联查询非常慢&#xff0c;于是我加上匹配项索引&#xff0c;但是发现依然没有改变速度。。这时候去搜了一下才知道pgsql的to_…

javaweb 期末复习

1. JDBC数据库连接的实现逻辑与步骤以及JDBC连接配置&#xff08;单列模式&#xff09; public class JDBCUtil {// 这些换成自己的数据库 private static final String DB_URL "jdbc:mysql://localhost:3306/你的数据库名称";private static final String USER &q…

辛弃疾,笔墨剑影的一生

辛弃疾&#xff0c;字幼安&#xff0c;号稼轩&#xff0c;生于南宋高宗赵构绍兴十年&#xff08;公元1140年&#xff09;&#xff0c;卒于南宋宁宗赵扩嘉泰元年&#xff08;公元1207年&#xff09;&#xff0c;享年67岁。他是中国南宋时期著名的爱国词人&#xff0c;与苏轼并称…

Unity贪吃蛇改编【详细版】

Big and small greedy snakes 游戏概述 游戏亮点 通过对称的美感&#xff0c;设置两条贪吃蛇吧&#xff0c;其中一条加倍成长以及加倍减少&#xff0c;另一条正常成长以及减少&#xff0c;最终实现两条蛇对整个界面的霸占效果。 过程中不断记录两条蛇的得分情况&#xff0c…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 部门项目任务分配(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 部门项目任务分配(100分) 🌍 评测功能需要订阅专栏后私信联…

【eMTC】eMTC PBCH与LTE PBCH有什么不同

1 概述 eMTC是基于LTE演进的物联网技术&#xff0c;在R12中叫Low-Cost MTC&#xff0c;在R13中被称为LTE enhanced MTC &#xff0c;即eMTC&#xff0c;旨在基于现有的LTE载波满足物联网设备需求。eMTC基于蜂窝网络进行部署&#xff0c;支持上下行最大1Mbps的峰值速率&#xff…

lxml库在爬虫领域的贡献及应用

重头戏lxml库里面的xpath 一段代码给各位开开胃 这段代码首先导入了lxml库中的etree模块&#xff0c;然后定义了一个包含HTML内容的字符串html。接着&#xff0c;我们使用etree.HTML()函数解析这个HTML字符串&#xff0c;得到一个表示整个HTML文档的树形结构。最后&#xff0c;…

《大数据分析》期末考试整理

一、单项选择题&#xff08;1*9&#xff09; 1.大数据发展历程&#xff1a;出现阶段、热门阶段和应用阶段 P2 2.大数据影响 P3 1&#xff09;大数据对科学活动的影响 2&#xff09;大数据对思维方式的影响 3&#xff09;大数据对社会发展的影响 4&#xff09;大数…

C语言---------深入理解指针

目录 一、字符指针 二、指针数组&#xff1a; 三、数组指针&#xff1a; 1、定义&#xff1a; 2、&数组名和数组名区别&#xff1a; 3、数组指针的使用&#xff1a; 四、数组参数&#xff0c;指针参数&#xff1a; 1、一维数组传参&#xff1a; 2、二维数组传参&am…

单列集合顶层接口Collection及五类遍历方式(迭代器)

collection add方法细节&#xff1a; remove方法细节&#xff1a; contains方法细节&#xff1a; 如果集合中存储的是自定义对象, student之类的, 也想通过contains进行判断, 就必须在javaBean中重写equals方法 contains在arrayList中源代码&#xff1a;在底层调用了equals方…