Matlab电话按键拨号器设计

news2024/12/25 12:18:28

前言

这篇文章是目前最详细的 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.coloradmin.cn/o/1829030.html

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

相关文章

Python | 中心极限定理介绍及实现

统计学是数据科学项目的重要组成部分。每当我们想从数据集的样本中对数据集的总体进行任何推断&#xff0c;从数据集中收集信息&#xff0c;或者对数据集的参数进行任何假设时&#xff0c;我们都会使用统计工具。 中心极限定理 定义&#xff1a;中心极限定理&#xff0c;通俗…

【Liunx】基础开发工具的使用介绍-- yum / vim / gcc / gdb / make

前言 本章将介绍Linux环境基础开发工具的安装及使用&#xff0c;在Linux下安装软件&#xff0c;编写代码&#xff0c;调试代码等操作。 目录 1. yum 工具的使用1.1 什么是软件包&#xff1a;1.2 如何下载软件&#xff1a;1.3 配置国内yum源&#xff1a; 2. vim编辑器2.1 vim的安…

NetSuite Saved Search 之 Filter By Summary

在某些业务场景中&#xff0c;用户需要一个TOP X的报表。例如&#xff0c;过去一段时间内&#xff0c;最多数量的事务处理类型。这就需要利用Saved Search中的Filter By Summary功能。 这在Criteria下的Summary页签里可以定义。其作用是对Result中Summary类型的结果进行过滤。也…

【论文速读,找找启发点】2024/6/16

ICME 2023 End-To-End Part-Level Action Parsing With Transformer 类似 DETR&#xff0c;通过 加 query的方式实现 端到端 ELAN: Enhancing Temporal Action Detection with Location Awareness 如何实现位置感知&#xff1f; > 重叠的卷积核&#xff1f; Do we really …

解决MacOS docker 拉取镜像慢的问题

docker官网&#xff1a;https://docker.p2hp.com/get-started/index.html 下载完成之后&#xff0c;拉取镜像速度慢&#xff0c;问题如下&#xff1a; 解决方法 配置阿里源&#xff1a;https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors在docker desktop里面设置…

代码随想录二刷DAY1~3

Day1 704 二分查找&#xff0c;简单 我也有自己写题解的能力了&#xff0c;而且思维很清晰&#xff1a; 找什么就在if里写什么。 class Solution {public: int search(vector<int>& nums, int target) { int l0,rnums.size()-1; while(l<r){ …

基于C++、MFC和Windows套接字实现的简单聊天室程序开发

一、一个简单的聊天室程序 该程序由服务器端和客户端两个项目组成&#xff0c;这两个项目均基于对话框的程序。服务器端项目负责管理客户端的上线、离线状态&#xff0c;以及转发客户端发送的信息。客户端项目则负责向服务器发送信息&#xff0c;并接收来自服务器的信息&#…

不一样的SYSTEM APP(SYSTEM flag和system_prop区别)

1.问题引入 在Android开发中, 1)Framework中PackageManager扫包后,会把app归类为SYSTEM, SYSTEM_EXT, PRIVILEGED 类别. 2)同样的, SeAndroid也会把APP归类程platform_app, system_app, untrusted_app(甚至还有其他,mediaprovider,gmscore_app). flag SYSTEM和system_app我们…

IDEA配置JavaFX

一、下载SDK &#x1f4ce;javafx-sdk-18.zip 二、配置依赖包 三、复制一个javafx代码 import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.VBox; import javafx.scene.shape.Line; import javafx.stage.Stage;public class Java…

基于Java和SSM框架的多人命题系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果你对多人命题系统感兴趣或者有相关开发需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java SSM框架 工具&#xff1a;Eclipse、MySQL Workbench、…

树莓派4B_OpenCv学习笔记10:调整视频帧大小

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 今日学习一下如何降低视频帧像素 文章提供测试代码讲解…

USB转I2C转SPI芯片CH341与CH347比较

1. 芯片中文资料&#xff1a; USB转I2C转SPI芯片CH341 高速USB转接芯片CH347转9M双串口转I2C转SPI转JTAG转SWD USB2.0高速转接芯片CH347应用开发手册 2. CH341与CH347比较&#xff1a; 类别CH341CH347备注串口速度2M9MCH347的串口速度更快设置CH341的I2C或SPI不能与串口同…

DAY24 回溯算法part01 77. 组合 216.组合总和III 17.电话号码的字母组合

理论基础 #什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 在二叉树系列中&#xff0c;我们已经不止一次&#xff0c;提到了回溯&#xff0c;例如二叉树&#xff1a;以为使用了递归&#xff0c;其实还隐藏着回溯 (opens new window)。 回溯是递…

系统集成项目管理工程师第9章思维导图发布

今天发布系统集成项目管理工程师新版第9章脑图的图片版

移动硬盘接入mac无法复制文件进去怎么办,mac里的文件如何存进移动硬盘

如果要传输的文件数据量比较大&#xff0c;相比于使用U盘&#xff0c;移动硬盘是更多的选择。移动硬盘可存储量大、传输速度快&#xff0c;是实用性比较强的储存设备。不仅是Windows设备&#xff0c;Mac电脑也经常使用到移动硬盘。但有时候&#xff0c;移动硬盘在Mac上不能传文…

Python合并文件(dat、mdf、mf4)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

揭秘最强气象武器的库,SPEI-Python不可思议之处.

spei-python是一个专门用于计算标准化降水蒸散指数&#xff08;Standardized Precipitation Evapotranspiration Index,SPEI&#xff09;的Python库.SPEI是一种综合考虑降水和潜在蒸散发的干旱指数,用于评估干旱的严重程度和持续时间. 安装 ## 可以使用 pip 来安装 spei-pyth…

C | 在ubuntu22下开发的一些配置

目录 VScode设置 要下载的插件&#xff1a; 卸载VScode的话就是哪装的哪删。 浅用gcc 预处理指令 使用gcc 语言编译过程 1. 预处理&#xff08;Preprocessing&#xff09; 2. 编译&#xff08;Compilation&#xff09; 3. 汇编&#xff08;Assembly&#xff09; 4. …

光明网发稿投稿流程与要求,光明日报如何投稿?附光明网多少钱(价格表)

对于想要在光明网发稿的作者来说&#xff0c;媒介多多网发稿平台是一个绝佳的投稿选择。光明网作为国内一流的新闻媒体平台&#xff0c;其严谨的文章审核标准和广泛的读者基础吸引着无数作者。然而&#xff0c;由于其严格的发稿标准&#xff0c;一些作者可能会遇到一些困难&…

盛世古董乱世金-数据库稳定到底好不好?

是不是觉得这个还用问&#xff1f; 是的要问。因为这个还是一个有争议的问题。但是争议双方都没有错。这就像辩论&#xff0c;有正反双方。大家都说的有道理&#xff0c;但是很难说谁对谁错。 正方观点&#xff1a;数据库稳定好 其实这个是用户的观点&#xff0c;应用开发人…