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 Hz697 Hz    1         2         3770 Hz    4         5         6852 Hz    7         8         9941 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 tonesif nargin < 1error('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, 32, 1; 2, 2; 2, 3;  % 4, 5, 63, 1; 3, 2; 3, 3;  % 7, 8, 94, 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 > 12error('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 xxif nargin < 4fs = 8000;
endhh = 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 < 2fs = 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, 32, 1; 2, 2; 2, 3;  % 4, 5, 63, 1; 3, 2; 3, 3;  % 7, 8, 94, 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'));
elsekey = [];
endend

四、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');
elsefprintf('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: Key1function Key1ButtonPushed(app, event)% 按键1的回调函数,按下后在文本框中显示currentText = app.Text_Dialing.Value; % 当前文本区域的值if isempty(currentText)newText = '1';elsenewText = strcat(currentText{1}, '1'); endapp.Text_Dialing.Value = {newText}; encoded_tones = dtmfdial([1]);sound(encoded_tones, 8000);displaySpectrum(app, encoded_tones);end

displaySpectrum为本路径下写的一个功能函数,即显示当前按钮的频谱图,每次点击都会被刷新,该功能的实现很简单,请自行在下面的资源中查找。

这个接下来就是复制粘贴到我们每个按钮的回调了。

拨号与挂断的回调

当点击拨号时,将会对之前输入的电话序号进行发音,发音结束后询问是否要保存音频。当我点击挂断时候,刷新我们的文字框以及坐标轴。需要注意的是,这里的文字框显示的是*、#、0,所以一定要在传入函数前进行映射。

        % Value changed function: Key_Dialingfunction 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 charcase '0'num = 10;case '*'num = 11;case '#'num = 12;otherwisenum = str2double(char); enddialedNumbers(end+1) = num;end enddisp(dialedNumbers);encoded_tones = dtmfdial(dialedNumbers);sound(encoded_tones, 8000);duration = length(encoded_tones) / 8000; % 暂停等待拨号音结束pause(duration);choice = questdlg('是否保存该音调?', ...'保存音调', ...'是', '否', '否');switch choicecase '是'[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('音调已保存', '保存成功');elsemsgbox('保存已取消', '取消');endcase '否'% 不做任何处理endendend

音频转为数字序号

这部分可以讲一讲,下面的代码是我写的测试草稿,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);endcurrent_tone = y(start_index:end_index);decoded_key = dtmfdeco(current_tone, L, fs);if ~isempty(decoded_key)decoded_numbers = [decoded_numbers, decoded_key];endstart_index = end_index + round(gap_duration * fs);
endfprintf('Decoded phone numbers: ');
disp(decoded_numbers);

首先参数的定义要与前面保存一致。从指定的音频文件中读取音频数据,并获取采样率。遍历音频数据,将其分割成独立的拨号音段,并对每个音段进行DTMF解码,输出解码得到的电话号码。

运行截图如下所示:

解码的回调

这里可以通过直接在文字框中输入wav文件的路径,也可以通过上面菜单栏选项当中的打开资源管理器选择。然后直接点击解码,通过弹窗显示解码的电话号码。

        % Value changed function: Key_Dialing_Decodingfunction Key_Dialing_DecodingValueChanged(app, event)wavPath = app.Decoding_path.Value;if isempty(wavPath) || ~isfile(wavPath)msgbox('请选择有效的 WAV 文件路径');return;enddecoded_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 10decoded_numbers_str{end+1} = '0';case 11decoded_numbers_str{end+1} = '*';case 12decoded_numbers_str{end+1} = '#';otherwisedecoded_numbers_str{end+1} = num2str(decoded_numbers(i));endendif ~isempty(decoded_numbers_str)msgbox(['解码结果: ', strjoin(decoded_numbers_str)], '解码结果');elsemsgbox('解码失败', '解码结果');endend

动图演示

项目资源

请通过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.mzph.cn/web/28338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

USB2.0高速转接芯片CH347应用开发手册

CH347应用开发手册 V1.3 一、简介 CH347是一款USB2.0高速转接芯片&#xff0c;以实现USB-UART(HID串口/VCP串口)、USB-SPI、USB-I2C、USB-JTAG以及USB-GPIO等接口&#xff0c;分别包含在芯片的四种工作模式中。 CH347DLL用于为CH347芯片提供操作系统端的UART/SPI/I2C/JTAG/B…

Linux_应用篇(17) FrameBuffer 应用编程

本章学习 Linux 下的 Framebuffer 应用编程&#xff0c; 通过对本章内容的学习&#xff0c; 大家将会了解到 Framebuffer 设备究竟是什么&#xff1f;以及如何编写应用程序来操控 FrameBuffer 设备。 本章将会讨论如下主题。 ⚫ 什么是 Framebuffer 设备&#xff1f; ⚫ LCD 显…

N32G031 ADC初始化

目录 1. ADC初始化概述 2. ADC初始化详细步骤 2.1 ADC配置 2.2 ADC初始化函数调用 2.3 DMA配置&#xff08;可选&#xff09; 3. 初始化结果验证 4. 注意事项 ADC采样注意事项 1. ADC初始化概述 在N32G031单片机中&#xff0c;ADC的初始化是确保ADC模块能够正常工作的…

安卓在Fragment控制状态栏显示隐藏

废话不多上效果 隐藏 显示 核心代码 首先是Framgrent package com.zx.tab;import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button;impor…

【redis】Redis的经典使用场景

目录 1.最常见——缓存2.数据共享分布式3.分布式锁4.全局ID5.计数器6.限流7.位统计8.购物车9.用户消息时间线timeline10.消息队列11.抽奖点赞、签到、打卡13.商品标签14.商品筛选15.用户关注、推荐模型16排行榜 1.最常见——缓存 数据类型&#xff1a;string例如&#xff1a;热…

给Windows软件添加异常捕获模块生成dump文件(附源码)

软件在运行过程中会时常发生内存越界、内存访问为例、stack overflow线程栈溢出、空指针与野指针等异常崩溃,仅仅是依靠Debug和Release下的调试是远远不够的,因为有些崩溃不是必现的,或者是Debug下很难出现的。所以我们需要在软件中添加异常捕获的模块,在捕获到异常时生成包…

C 语言连接MySQL 数据库

前提条件 本机安装MySQL 8 数据库 整体步骤 第一步&#xff1a;开启Windows 子系统安装Ubuntu 22.04.4&#xff0c;安装MySQL 数据库第三方库执行 如下命令&#xff1a; sudo aptitude install libmysqlclient-dev wz2012LAPTOP-8R0KHL88:/mnt/e/vsCode/cpro$ sudo aptit…

鸿蒙求职面试内容总结——6月3日ZR的FS项目

最近接到了一些公司的入职面试邀约&#xff0c;这里略去公司的和项目的名字&#xff0c;做一些整理分享。 一、长列表如何实现部分渲染&#xff0c;使用的是哪一个API 在鸿蒙系统中&#xff0c;可以使用List组件来实现长列表的部分渲染。List组件支持使用条件渲染、循环渲染、…

docker一些常用命令以及镜像构建完后部署到K8s上

docker一些常用命令以及镜像构建完后部署到K8s上 1.创建文件夹2.删除文件3.复制现有文件内容到新建文件4.打开某个文件5.查看文件列表6.解压文件&#xff08;tar格式&#xff09;7.解压镜像8.查看镜像9.删除镜像10.查看容器11.删除容器12.停止运行容器13.构建镜像14.启动容器15…

英伟达开源最强通用模型Nemotron-4 340B

英伟达的通用大模型 Nemotron&#xff0c;开源了最新的 3400 亿参数版本。 本周五&#xff0c;英伟达宣布推出 Nemotron-4 340B。它包含一系列开放模型&#xff0c;开发人员可以使用这些模型生成合成数据&#xff0c;用于训练大语言模型&#xff08;LLM&#xff09;&#xff0…

分布式系统中的经典思想实验——两将军问题和拜占庭将军问题

文章目录 一、两将军问题1.1 问题描述1.2 深入理解两将军问题1.3 实验结论 二、拜占庭将军问题2.1 问题描述2.2 深入理解拜占庭将军问题2.3 解决方案 三、两将军和拜占庭问题的关系3.1 区别和联系3.2 应用与现实意义 参考资料 一、两将军问题 1.1 问题描述 两将军问题描述的是…

el-pagination 切换分页条数,会出现两次请求

文章目录 前言一、问题展示二、源码展示 前言 继上一次发现el-pagination在删除的时候pageNum不更新的问题。这次又发现了&#xff0c;切换分页条数&#xff0c;会出现两次请求。网上有很多解决方案&#xff0c;我就不多说了&#xff0c;我就简单记一下为啥会出现两次请求的问…

21. 第21章 算法分析

21. 算法分析 这个附录选自OReilly Media出版的Alen B.Downey的Think Complexity(2012)一书. 当你读完本书之后, 可能会像继续读读那本书.算法分析是计算机科学的一个分支, 研究算法的性能, 尤其是他们的运行时间和空间需求. 参见http://en.wikipedia.org/wiki/Analysis_of_al…

Vue51-插件

一、插件的定义 vue里面的插件&#xff0c;类似于游戏的外挂。 vue中插件的本质&#xff1a;一个对象&#xff0c;里面必须包含install方法。 二、插件的使用 2-1、创建一个插件js文件&#xff08;写在src中plugins.js&#xff09; 2-2、应用插件&#xff1a;Vue.use(插件) …

机器真的能思考、学习和智能地行动吗?

In this post, were going to define what machine learning is and how computers think and learn. Were also going to look at some history relevant to the development of the intelligent machine. 在这篇文章中&#xff0c;我们将定义机器学习是什么&#xff0c;以及…

【Java03】Java中数组在内存中的机制

1. 内存中的数组 Java中的数组是一种引用类型&#xff0c;数组变量&#xff08;引用&#xff09;和数组元素在内存中是分开的。 Java中的数组变量其实就是指针。 如果想要访问数组元素&#xff0c;只能通过这个数组的引用变量&#xff08;指针&#xff09;来访问。 实际数组对…

【stm32-新建工程】

stm32-新建工程 ■ 下载相关STM32Cube官方固件包&#xff08;F1&#xff0c;F4&#xff0c;F7&#xff0c;H7&#xff09;■ 1. ST官方搜索STM32Cube■ 2. 搜索 STM32Cube■ 3. 点击获取软件■ 4. 选择对应的版本下载■ 5. 输入账号信息■ 6. 出现下载弹框&#xff0c;等待下载…

刚入职,写接口用了PUT和DELETE方法,结果被同事喷了,感觉自己被针对了

事情是这样&#xff0c;某社交平台上有个兄弟发帖&#xff0c;说自己刚入职国企&#xff0c;写了个借口&#xff0c;用了PUT和DELETE方法&#xff0c;前段说不能用这两个&#xff0c;这位仁兄感觉很委屈&#xff0c;特地发帖吐槽。 其实站在安全的角度来说&#xff0c;真没冤枉…

MySQL 示例数据库大全

前言&#xff1a; 我们练习 SQL 时&#xff0c;总会自己创造一些测试数据或者网上找些案例来学习&#xff0c;其实 MySQL 官方提供了好几个示例数据库&#xff0c;在 MySQL 的学习、开发和实践中具有非常重要的作用&#xff0c;能够帮助初学者更好地理解和应用 MySQL 的各种功…

云计算【第一阶段(14)】Linux的目录和结构

一、Liunx目录结构 1.1、linux目录结构 linux目录结构是树形目录结构 根目录&#xff08;树根&#xff09; 所有分区&#xff0c;目录&#xff0c;文件等的位置起点整个树形目录结构中&#xff0c;使用独立的一个"/",表示 1.2、常见的子目录 必须知道 目录路径目…