实验报告

实验名称:实验四 综合实验 班级:通信214 姓名及学号:王峤宇214022

一、实验目的

  1. 通过对DTMF信号产生、提取、检测系统的设计与仿真,培养学生利用数字信号、数字系统分析与设计的理论和方法,来解决实际工程问题的能力。
  2. 培养学生根据实验目的、任务和要求,自行分析实验原理、设计实验方法和实验步骤、完成实验仿真测试、对实验结果进行合理分析并得到有效结论的能力。
  3. 进一步熟悉数字滤波器设计和应用。
  4. 初步掌握使用Matlab GUI或Labview等软件平台进行数字系统模拟仿真的方法。
  5. 培养学生综合利用所学知识分析解决问题的能力以及自学能力,使学生了解拓展知识和能力的途径。

二、实验主要内容

  1. 查阅资料,自学DTMF信号原理。
  2. 通过matlab实现DTMF信号的产生,并设计数字滤波器获分离其频率分量。
  3. 掌握戈泽尔算法的原理和实现方法,通过戈泽尔算法实现DTMF信号的识别。
  4. 掌握音频信号的端点检测,通过matlab实现对长串接DTMF加噪信号的识别。
  5. 利用matlab图形界面开发工具appdesigner设计DTMF拨号控制信号生成并自动检测的操作界面。

三、实验主要仪器、设备及软件

  1. 计算机
  2. Matlab2021a以上版本

四、实验步骤

基本实验内容

1.通过查阅资料,自学电话系统中DTMF信号的原理及其产生与检测方法。

双音多频(Dual Tone Multi Frequency,DTMF)信号是音频电话中的拨号信号,每一个数字(0-9)由两个不同频率单音组成(每个单音用正弦信号表示),所用频率分为高频带和低频带两组,每个数字由高、低频带中各一个频率组成,例如数字9使用852Hz和1477Hz两个频率。数字与符号对应频率关系如下表所示。

1209Hz 1336Hz 1477Hz 1633Hz
697Hz 1 2 3 A
770Hz 4 5 6 B
852Hz 7 8 9 C
941Hz * 0 # D

DTMF信号的检测就是对频谱的分析,但是DTMF的检测中无需得知相位信息也无需计算所有的频点。因为DTMF的频率是已知的,直接针对已知的8种频率的频点进行计算即可。这就是戈泽尔算法(Goertzel)。区分不同DTMF信号的频率组成通过查表就可以得知DTMF信号的结果。

2.任意选择某个按键,产生该按键对应的DTMF信号,并用8000Hz的采样频率转换为数字信号。根据该信号的频率特性,确定滤波器类型及技术指标,基于Matlab平台设计滤波器(滤波器种类、设计方法等自定)和信号处理程序,将此DTMF信号的两个单音(即两个频率分量)分别提取出来。要求所设计滤波器能屏蔽其他按键的DTMF信号。

需要分别提取该DTMF信号的两个单频,取DTMF按键A作为测试,其频率组成为697Hz加上1633Hz,使用低通滤波器保证通带边界频率大于697Hz,阻带边界频率小于770Hz就能在保证屏蔽其他DTMF按键信号条件下滤出按键A的低频单频信号。 使用高通滤波器,保证通带边界频率小于1633Hz,阻带边界频率大于1477Hz就可以实现屏蔽其他DTMF按键信号条件下滤出按键A的高频单频信号。 对于DTMF信号,无论是其产生还是检测,频率组成的相位都不是主要考虑的目标,线性相位对滤波器来说并不是重要的影响因素,为了降低滤波器阶数,使用IIR滤波器。

使用filterDesigner设计数字滤波器,滤波器设计如下 低通滤波器设计如图2-1所示:

Alt text图2-1 低通滤波器

其中,采样频率8000Hz,通带边界频率700Hz,阻带边界频率750Hz,阻带最大衰减1dB,通带最大衰减40dB。 高通滤波器设计如图2-2所示:

Alt text图2-2 高通滤波器
其中,采样频率8000Hz,通带边界频率700Hz,阻带边界频率750Hz,阻带最大衰减1dB,通带最大衰减40dB。

DTMF信号产生函数

% 依照采样率,产生50ms的DTMF信号,char输入字符
function x = dtmf_generate(char, fs)
    T = 50e-3;      % 持续50ms
    N = T*fs;       % 采样点数
    n = 0:N-1;      % 离散变量
    t = n/fs;       % 离散时间变量

    frow = [697, 770, 852, 941];        % 行频率向量
    fcol = [1209, 1336, 1477, 1633];    % 列频率向量

    tm=[                                % DTMF按键16个ascii码
        '1','2','3','A';
        '4','5','6','B';
        '7','8','9','C';
        '*','0','#','D';
    ];
    
    [i, j] = find(tm == char);          % 寻找ascill码值对应得索引
    x = sin(2*pi*frow(i) * t) + sin(2*pi*fcol(j) * t);    % 构成双频信号
end

验证程序如下:

% 添加路径
addpath(".\lib\dtmf");                      % 添加路径
addpath(".\lib\filter");                    % 添加路径
% 产生DTMF信号
fs = 8000;                                  % 指定采样频率
x = dtmf_generate('A', fs);                 % 生成DTMF信号
N = length(x);                              % 获取数据长度
% 滤波获取单频分量
lowFreq = filter(filter_lowpass_700, x);    % 通过低通,获取低频
highFreq = filter(filter_highpass_1600, x); % 通过高通,获取高频
% 绘图
figure(1);
NN = round((N-1)/2)
freq = (0:NN-1)/N*fs;

subplot(3, 1, 1);
fftResult = fft(x);
plot(freq, abs(fftResult(1:NN))/N, 'k');
title("产生的DTMF信号");xlabel("freq(Hz)");
subplot(3, 1, 2);
fftResult = fft(lowFreq);
plot(freq, abs(fftResult(1:NN))/N, 'k');
title("滤得的DTMF低频");xlabel("freq(Hz)");
subplot(3, 1, 3);
fftResult = fft(highFreq);
plot(freq, abs(fftResult(1:NN))/N, 'k');
title("滤得的DTMF高频");xlabel("freq(Hz)");

滤得单频程序的程序流程图如图2-3所示:

graph LR
    DTMF信号生成 --> 低通滤波器;
    DTMF信号生成 --> 高通滤波器;
    低通滤波器-->仅含低频分量的信号;
    高通滤波器-->仅含高频放量的信号;
Loading
图2-3

其中DTMF信号生成的程序流程图如图2-4所示:

flowchart LR
    输入字符-->字符表中寻找索引-->|行坐标|低频分量
    字符表中寻找索引-->|列坐标|高频分量
    低频分量-->生成DTMF信号
    高频分量-->生成DTMF信号
Loading
图2-4

实验结果如图2-5所示:

Alt text图2-5 滤得DTMF双频结果

结果分析:通过一个低通和一个高通成功分别滤出DTMF信号的两个单频信号,其中频谱结果值得注意的是频谱峰值对应的频率结果,因为该频谱是通过FFT计算的离散DFT频谱,采样率为8000,采样点数为50ms即400个点,对应的频率分辨率是20Hz,本应由697Hz和1633Hz组成的频谱由于频谱分辨率的问题,其对应了频谱上接近的700Hz和1640Hz, 可以在生成50ms的DTMF信号后通过时域补零方式进行频域内插改善。

3.掌握戈泽尔算法的原理和实现方法,设计利用该算法对DTMF信号进行频谱分析的Matlab函数(只分析DTMF信号对应的8个频点,要求函数具有一定的通用性,可以适用不同点数、不同采样率信号的频谱分析),并验证所设计函数的正确性。注意:不得调用Matlab自带的goertzel函数,要求按照戈泽尔算法原理,自行编程实现!

戈泽尔格策尔算法把离散傅立叶转换看成是一组滤波器,将输入的讯号与滤波器中的脉冲响应做卷积运算,求得滤波器的输出,即得到频率域其中一点的频率X(k)。此算法利用旋转因子${\displaystyle {\omega }{N}^{k}}$的周期性,将离散傅立叶转换转换为线性的滤波运算。 因为旋转因子 $$ {\displaystyle {\omega }{N}^{-kN}=e^{-j(2{\pi /N})(-kN)}=1} \tag{1} $$ 转换后第k点的频率为 $$ {\displaystyle X(k)={\omega }{N}^{-kN}\sum {m=0}^{N-1}x(m){\omega }{N}^{km}=\sum {m=0}^{N-1}x(m){\omega }{N}^{-k(N-m)}\qquad \quad ,k=0,1,2,...,N-1} \tag{2} $$ 定义$y_k(n)$ $$ {\displaystyle y{k}(n)=\sum {m=0}^{N-1}x(m){\omega }{N}^{-k(n-m)}} \tag{3.1} $$ 将$y_k(n)$理解为两个信号的卷积 $$ {\displaystyle y_{k}(n)=x(n)\otimes h_{k}(n)} \tag{3.2} $$ 其中,x是输入信号,$h_k(n)$则可被看作是IIR滤波器 $$ {\displaystyle h_{k}(n)={\omega }{N}^{-kn}\ u(n)} \tag{4} $$ 对比(2)和(3)式,可推知(3.1)进行卷积运算,当n=N时,滤波器的输出${\displaystyle y{k}(N)}$即为$X(k)$: $$ {\displaystyle X(k)=y_{k}(n)\lfloor {n=N}} \tag{5} $$ 对式(4)进行Z变换,得到一阶的戈泽尔算法系统函数: $$ {\displaystyle H{k}(z)={\frac {1}{1-{{\omega }^{-k}\ z^{-1}}}}} \tag{6} $$ 系统流程图如图3-1所示:

Alt text图3-1 一阶戈泽尔算法

一阶算法需要${\displaystyle 4N^{2}}$次实数乘法运算和${\displaystyle N(4N-2)}$次加/减法,加/减法与乘法运算皆为${\displaystyle 4N^{2}}$次,对该算法进一步改进,将式(6)上下同乘${\displaystyle 1-{\omega }{N}^{k}\ z^{-1}}$可得第k点的频率响应转移函数为 $$ {\displaystyle {\begin{alignedat}{2}H{k}(z)&={\frac {1-{{\omega }{N}^{k}\ z^{-1}}}{(1-{{\omega }{N}^{-k}\ z^{-1}})(1-{{\omega }{N}^{k}\ z^{-1})}}}\&={\frac {1-{{\omega }{N}^{k}\ z^{-1}}}{1-2\ \cos((2{\pi }/N)k)z^{-1}+z^{-2})}}\\end{alignedat}} \tag{7}} $$ 系统流程图如图3-2所示:

Alt text图3-2 二阶戈泽尔算法

戈泽尔算法实现程序如下:

function result = dtmf_goertzel(xn, freq_index)
    N = length(xn);
    NN = length(freq_index);
    ssin = sin(2*pi*(freq_index-1)/N);
    ccos = cos(2*pi*(freq_index-1)/N);
    coeff = 2*ccos;
    result = zeros(1, NN);                      
    for i = 1:NN                                % 遍历所有待计算频点
        q0 = 0;q1 = 0;q2 = 0;                   % 初始化变量
        for j = 1:N                             % 遍历数据位
            q0 = xn(j) + coeff(i) * q1 - q2;    % 计算差分方程
            q2 = q1;                            % 差分方程递进
            q1 = q0;                            % 差分方程递进
        end
        result(i) = (q2 - q1 * ccos(i)) + 1i*(-q1 * ssin(i));
    end
end

戈泽尔算法程序流程图如图3-3所示:

flowchart LR
    a(初始化变量);
    b{for i=1:NN};
    c{for j=1:N};
    d(计算输出值);
    e[迭代差分方程];
    a-->b-->c-->e-->储存结果-->d
    c-->|end|b
    b-->|end|d
Loading
图3-3

验证算法效果程序:

% 步骤三,测试戈泽尔算法的实现效果
addpath(".\lib\dtmf");                          % 添加路径

figure(2);
Fs = 8000;
N = 512;

f = randi(Fs/2, [1, 8]);                        % 生成供戈泽尔算法计算的随机频点
data = randn([1, N]);                           % 生成高斯信号作为测试信号

freq_indices = round(f/Fs*N) + 1;   
dft_data = dtmf_goertzel(data, freq_indices);   % 使用自行编写的戈泽尔算法计算结果
dft_data_ref = goertzel(data, freq_indices);    % 使用matlab自带的戈泽尔算法作为标准答案

stem(f,abs(dft_data), 'o');
hold on
stem(f, abs(dft_data_ref), '*');
hold off
legend('本地实现算法计算结果', 'matlab自带算法计算结果');
ax = gca;
f = sort(f);
ax.XTick = f;
xlabel('Frequency (Hz)')
ylabel('DFT Magnitude')

实验结果如图3-4所示:

Alt text图3-4 戈泽尔算法效果验证
*结果分析:经过测试,本地实现的算法效果与matlab自带函数自己算结果一致,确认无误。通过程序实现进一步加深二阶改进型戈泽尔算法计算量的理解,总共的计算量为${\displaystyle 2N+4}$次实数乘法运算以及${\displaystyle 4N+4}$次实数加法运算,极大的减轻了对特定频率分析的算力负担*

4.基于戈泽尔算法和DTMF信号的检测原理,在Matlab平台设计DTMF信号的检测程序。要求该程序能够自动识别任意按键对应的DTMF信号(即输入任意按键的DTMF采样信号,该程序能自动判断该信号对应的按键),并给出识别结果。

利用戈泽尔算法实现通用的DTMF信号检测函数,为了进一步减少计算量和提高检测精确度,在8kHz采样频率下,选择将信号截取205长度后进行戈泽尔算法效率最高,利用戈泽尔算法对数据进行分析。考虑到输入可能是经过端点检测后的数据,其中可能含有静音段和噪声,为了提高识别的准确度进一步改进,将输入数据按照语音信号处理的思路分帧处理,保证戈泽尔算法的分析能覆盖整个输入信号,取每一帧的检测结果然后统计得到最终检测结果。

DTMF按键信号检测代码如下:

% 输入含有DTMF信号的一段序列,并指明采样频率,检测输出对应的按键
function char = dtmf_detect(x, fs)
    f = [697 770 852 941 1209 1336 1477 1633];  % 需要检测得频率值
    tm=[                                        % DTMF按键16个ascii码
        '1','2','3','A';
        '4','5','6','B';
        '7','8','9','C';
        '*','0','#','D';
    ];

    N = 205;                                    % 采样点数
    freq_indices = round(f/fs*N) + 1;           % 计算需要监测频率值对应的索引
    TH = 50;

    % 分帧处理信号x, 统计检验结果
    X = enframe(x, N);        % 将输入信号按205长度分帧
    X = X';
    fn = size(X, 2);          % 帧数``
    charResult = zeros(1, 16);% 数组,用于统计结果
    for i = 1:fn
        xn = X(:, i);         % 获取第i帧数据
        dft_data = dtmf_goertzel(xn, freq_indices);      % 利用哥泽尔算法计算对应频点的幅度结果
        dft_data = abs(dft_data);
        [value1, index1] = max(dft_data(1:4));           % 统计低频最大值
        [value2, index2] = max(dft_data(5:end));         % 统计高频分量最大值
        if value1 > sum(dft_data(1:4))/4 && value2 > sum(dft_data(5:end))/4  % 大于门限允许判定 
            index = index1 + (index2-1)*4;                                   % index转化
            charResult(index) = charResult(index) + 1;                       % 对应统计结果加1
        end
    end
    [value, index] = max(charResult);
    if value > 0
        char = tm(index);
    else
        char = '';
    end
end

DTMF信号检测算法程序流程图如图4-1所示:

flowchart LR
    a(初始化变量);
    a-->信号分帧-->b;
    b[逐帧检测频谱];
    c[满足条件]
    b-->c-->|不满足条件|b;
    c-->|满足条件|统计结果-->b;
    e[分析统计结果];
    b-->|end|e-->|满足条件|返回统计频率最大的字符
    e-->|不满足条件|返回空字符
Loading
图4-1

测试程序如下:

% 添加路径
addpath(".\lib\dtmf");                      % 添加路径
% 产生DTMF信号
fs = 8000;                                  % 指定采样频率
tm=[                                        % DTMF按键16个ascii码
    '1','2','3','A';
    '4','5','6','B';
    '7','8','9','C';
    '*','0','#','D';
];
fprintf("生成与检测结果如下:");
for i = 1:16
    x = dtmf_generate(tm(i), fs);
    y = dtmf_detect(x, fs);
    if mod(i-1, 4) == 0   % 每四个输出换行
        fprintf('\r\n');
    end
    fprintf("%c -> %c; ", tm(i), y);
end

输出结果如下:

生成与检测结果如下:
1 -> 1; 4 -> 4; 7 -> 7; * -> *;
2 -> 2; 5 -> 5; 8 -> 8; 0 -> 0;
3 -> 3; 6 -> 6; 9 -> 9; # -> #;
A -> A; B -> B; C -> C; D -> D;

结果分析:当前程序对于没有噪声和静音段的情况下,检测正确率达到了100%,考虑到后续检测过程中由于端点检测不准确会引入静音段,导致信号长度大于50ms,同时考虑到噪声引入,为了提高准确率,将程序设计为多次判断取对应频谱最大值,同时要求最大的对应DTMF频率分量的能量占比要达到50%以上才识别为对应信号。

创新训练拓展内容

1. 按照CCITT规范,产生由若干个(至少8个)不同按键DTMF信号串接产生的信号(即模拟在电话拨号时产生的连续按键信号),并叠加适量随机噪声(信噪比自定)。每个DTMF信号持续50ms左右,每两个按键之间的间隔在50ms~1s之间随机设置。注意:必须先把多个按键的DTMF串接成一个长信号(两个按键信号之间间隔适当数量的采样点,这些采样点只存在噪声信号),而不得单独针对每个按键信号分别识别!设计程序,完成连串拨号按键的识别(提示:先把串接信号分割、截取成单个DTMF信号,再进行识别,要求同时设计能自动进行信号分割与截取的程序)。

串接信号的分割需要借用端点检测。为了实现端点检测需要对串接信号进行时域短时分析,将串接信号分帧后,分别计算每一帧短时能量、短时平均幅度、短时平均过零率或短时自相关函数法等,或是进行短时频域分析通过倒谱或者谱熵的方法检测话声段(非静音段), 此时,话声段即为50ms左右的DTMF信号。 考虑到DTMF信号中频率分量单一且稳定,其静音段和信号段的自相关函数会有很大的差距,DTMF信号段的自相关函数会产生很大的峰值。 利用短时自相关函数法进行端点函数:

% 自相关法端点检测函数
function [voiceseg,vsl,SF,NF,Rum]=vad_corr(y,wnd,inc,NIS,th1,th2)
    x=enframe(y,wnd,inc)';                  % 分帧
    fn=size(x,2);                           % 求帧数
    for k=2 : fn                            % 计算自相关函数
        u=x(:,k);
        ru=xcorr(u);
        Ru(k)=max(ru);
    end
    Rum=Ru/max(Ru);                         % 归一化
    thredth=max(Rum(1:NIS));                % 计算阈值
    T1=th1*thredth;
    T2=th2*thredth;
    [voiceseg,vsl,SF,NF] = vad_forw(Rum,T1,T2);

程序中,分别计算每一帧信号的短时自相关函数,并取其最大值,其最大值是和是否有信号正相关的,其中vad_forw函数实现了利用双门限比较计算结果的功能。 端点检测程序流程图如图所示:

flowchart LR
    a(初始化变量);
    a-->信号分帧-->b;
    b[逐帧获取数据];
    b-->c[计算该帧自相关函数峰值]
    c-->统计该帧峰值结果-->b;
    e[分析统计结果];
    b-->|end|e-->|峰值超过门限|判定为话声段
    e-->|峰值不足门限|判定为静音段
Loading
图5-1

综合程序如下:

% 添加路径
clear all; clc; close all;
addpath('.\lib\dtmf');                      % 添加路径
addpath('.\lib\vad');                       % 添加端点检测程序
% 产生DTMF信号
fs = 8000;                                  % 指定采样频率
tm=[                                        % DTMF按键16个ascii码
    '1','2','3','A';
    '4','5','6','B';
    '7','8','9','C';
    '*','0','#','D';
];
% 产生串接信号
numChar = 11;                           % 生成长度为11的DTMF串接信号
char = randi(16, [1, numChar]);         % 随机生成11个DTMF信号索引
snr = 20;                               % 设置信噪比
generatedStr = '';                      % 生成字符串

len = randi([round(0.25*fs), round(0.5*fs)]); % 生成250ms~500ms的前导零
x = zeros(1, len);                      % 放置前导0
for i = 1:numChar                       % 拼接DTMF信号
    generatedStr = [generatedStr, tm(char(i))];
    x = [x, dtmf_generate(tm(char(i)), fs)];% 生成DTMF信号
    len = randi([round(0.05*fs), fs]);  % 生成50ms~1s的间隔对应的N长
    x = [x, zeros(1, len)];             % 放置0
end
x = awgn(x, snr, 'measured');           % 添加噪声

% 自相关法端点检测
IS=0.25;                                % 设置前导无话段长度50ms
wlen=round(fs*0.025);                   % 设置帧长为25ms
inc=round(fs*0.01);                     % 设置帧移位10ms
x=x/max(abs(x));                        % 幅值归一化
N=length(x);                            % 取信号长度
time=(0:N-1)/fs;                        % 设置时间
wnd=hamming(wlen);                      % 设置窗函数
NIS=fix((IS*fs-wlen)/inc +1);           % 求前导无话段帧数

th1=1.1;
th2=1.3;

[voiceseg,vsl,SF,NF, Rum]=vad_corr(x,wnd,inc,NIS,th1,th2);% 自相关函数的端点检测
fn = length(SF);
frameTime=FrameTimeC(fn, wlen, inc, fs);  % 计算各帧对应的时间
[~, frameNn] = enframe(x, wlen, inc);
% 检测DTMF信号:
detectStr = '';
for i = 1:vsl
    nx1=frameNn(voiceseg(i).begin);
    nx2=frameNn(voiceseg(i).end);
    detectStr = [detectStr, dtmf_detect(x(nx1:nx2), fs)];
end

% 输出生成及检测结果
fprintf("生成结果:%s\r\n", generatedStr);
fprintf("检测结果:%s\r\n", detectStr);
fprintf("测试信噪比:%ddB", snr);

程序流程图如图所示:

flowchart LR
    a(随机生成字符串);

    a-->b[添加前导零]-->c[逐个生成DTMF信号]-->|生成随机数|d[添加随机时长间隔]-->c;
    c-->|end|添加噪声-->进行端点检测-->f[话声段逐段DTMF检测]-->保存结果-->f
    
Loading
图5-2

实验结果:

生成结果:538100*00BB
检测结果:538100*00BB
测试信噪比:20dB

生成结果:79CA*80A#20
检测结果:79CA*80A#20
测试信噪比:10dB

生成结果:6715*462CCC
检测结果:6715*462CCC
测试信噪比:5dB

生成结果:72*8997A250
检测结果:72*8997A250
测试信噪比:1dB

其中部分端点检测效果如图5-3和5-4所示: 信噪比20dB端点检测如图5-3所示:

Alt text图5-3 DTMF串接信号端点检测
信噪比1dB端点检测如图5-4所示:
Alt text图5-4 DTMF串接信号端点检测
*结果分析:正如编写程序前对DTMF串接信号的分析,由于DTMF信号由两个单频组成,其短时自相关函数在DTMF信号部分会产生很大的峰值,与静音段形成鲜明的对比,实验结果也表明,通过自相关函数的方式能高效快速的对DTMF串接信号进行端点检测。在步骤四中对噪声的考虑而进行的检测改进效果同样得到了证明,信噪比1dB的情况下依然能够正常检测信号,没有丢失和误码。* 静音段一帧的自相关函数如图5-5所示:
Alt text图5-5 静音段一帧的自相关函数
DTMF信号段一帧的自相关函数如图5-6所示:
Alt text图5-6 DTMF段一帧的自相关函数

2. 利用Matlab的图形界面开发工具(建议选择新的图形界面开发工具appdesigner,不建议采用老旧的GUIDE,appdesigner的使用方法,请自行查阅相关资料)或者Labview软件,设计16键电话拨号的模拟界面。由图形界面的按键产生DTMF信号,自动对产生的信号进行检测和识别,并在图形界面显示检测结果(可以包括按键字符显示、DTMF信号的波形、戈泽尔算法分析的频谱等,自行确定GUI要显示的内容和显示的方式)。

采用appdesigner进行GUI设计,设计的GUI界面如图6-1所示:

Alt text图6-1 GUI应用主界面
如图6-1所展示的,该应用具备波形结果视图,接收数据和参数设置视图以及拨号和发送视图。其中波形结果视图在拨号并发送后,会显示添加了随机间隔的DTMF串接信号,并默认显示该串接信号对DTMF信号端点检测的结果。之后拨号和发送视图中,实现了按键拨号以及拨号后的声音反馈,并添加了回退和清空方便使用。在结果和参数设置视图中,支持对信号添加指定信噪比的白噪声,并添加了微调选择器,在接受数据后通过调整该选择器,选择显示哪一个字符的DTMF信号和其戈泽尔算法下的频谱。 应用使用效果如图6-2所示:
Alt text图6-2 应用使用效果

设置不同的发送序列,并设置不同的信噪比,结果如图6-3所示:

Alt text图6-3 应用使用效果

其中app对象的私有属性如下:

dtmfNoised;     % 加噪DTMF信号
dtmfVoiceSeg    % 端点检测结果
dtmfTxStr;      % 发送字符串
dtmfRxStr;      % 接收字符串
dtmfFrameNn     % 帧序列

字符按键的回调统一调用app的一个私有方法进行字符串管理,该函数如下

function dtmfTxUpgrade(app, char)
    if char == 0       % 退格
        app.dtmfTxStr = app.dtmfTxStr(1:end-1); % 删除最新一个字符
    elseif char == 1   % 清空
        app.dtmfTxStr = ''; % 清空字符串
    else
        app.dtmfTxStr = [app.dtmfTxStr, char];  % 末尾添加一个字符
        x = dtmf_generate(char, 8000);
        sound(x, 8000);
    end
    app.EditField.Value = app.dtmfTxStr;    % 对文本框内容进行更新
end

通过RxStr和TxStr两个字符串的操作进行输入字符框和接收字符框的管理,发送按键的回调函数如下:

% 生成DTMF信号
fs = 8000;
strLen = length(app.dtmfTxStr);
if strLen == 0
    return;
end
% 产生串接信号
snr = app.dBEditField.Value;            % 获取信噪比
len = randi([round(0.25*fs), round(0.5*fs)]); % 生成250ms~500ms的前导零
x = zeros(1, len);                      % 放置前导0
for i = 1:strLen                        % 拼接DTMF信号
    x = [x, dtmf_generate(app.dtmfTxStr(i), fs)];% 生成DTMF信号
    len = randi([round(0.05*fs), fs]);  % 生成50ms~1s的间隔对应的N长
    x = [x, zeros(1, len)];             % 放置0
end
x = awgn(x, snr, 'measured');           % 添加噪声
app.dtmfNoised = x;

% 自相关法端点检测
IS=0.25;                                % 设置前导无话段长度250ms
wlen=round(fs*0.025);                   % 设置帧长为25ms
inc=round(fs*0.01);                     % 设置帧移位10ms
x=x/max(abs(x));                        % 幅值归一化
N=length(x);                            % 取信号长度
time=(0:N-1)/fs;                        % 设置时间
wnd=hamming(wlen);                      % 设置窗函数
NIS=fix((IS*fs-wlen)/inc +1);           % 求前导无话段帧数

th1=1.1;
th2=1.3;
[voiceseg, vsl, SF, NF, Rum]=vad_corr(x,wnd,inc,NIS,th1,th2);% 自相关函数的端点检测
fn = length(SF);
frameTime=FrameTimeC(fn, wlen, inc, fs);  % 计算各帧对应的时间
[~, frameNn] = enframe(x, wlen, inc);
% 检测DTMF信号:
app.dtmfVoiceSeg = voiceseg;            % 保存话声段信息
app.dtmfFrameNn = frameNn;              % 保存帧序列号信息
app.dtmfRxStr = '';                     % 初始化接收字符串
for i = 1:vsl
    nx1=frameNn(voiceseg(i).begin);
    nx2=frameNn(voiceseg(i).end);
    app.dtmfRxStr = [app.dtmfRxStr, dtmf_detect(x(nx1:nx2), fs)];
end
app.dtmfRxUpgrade();
% 绘制端点检测结果
plot(app.UIAxes, time,x,'k');
title(app.UIAxes, '串接DTMF信号');
xlabel(app.UIAxes, 'time(s)');
ylabel(app.UIAxes, '幅值'); axis(app.UIAxes, [0 max(time) -1 1]);

if app.CheckBox.Value ~= 0          % 绘制端点检测结果
    for k=1 : vsl                   % 标出语音端点
        nx1=voiceseg(k).begin; nx2=voiceseg(k).end;
        line(app.UIAxes, [frameTime(nx1) frameTime(nx1)],[-1 1],'color','r','LineStyle','-');
        line(app.UIAxes, [frameTime(nx2) frameTime(nx2)],[-1 1],'color','b','LineStyle','--');
    end
end

只要将先前实现的对串接DTMF信号检测的代码进行适当的更改就可以使其成为app的一个案件回调,将结果保存在app的属性中,方便不同函数的共同调用。 app全部程序见code文件夹。

结果分析:该应用需要实现的功能为DTMF信号生成与检测的集成,GUI设计过程中主要是对已实现算法的统一调用,并为用户提供使用方便,需要考虑人机交互的直观性和稳定性,比如在接受和发送的文本框组件需要设置为不可编辑,仅可通过程序内部编辑,避免非法字符的出现,在通过步进按钮设置显示内容或是通过数值框设置信噪比时也要对输入数据做好充足的非法检测,提高程序的稳定性。

五、实验总结

实验总结:本次实验是对课程所学内容的一次综合应用,同时学习了双音多频信号的应用及其原理,通过matlab程序的方式对DTMF信号的产生、传输以及接收后的检测进行了一次仿真,同时拓展了一些对于DTMF信号的应用,比如除了16个按键字符对应的频率组成外,还有由480Hz低频和620Hz高频组成的忙音。对于串接后的DTMF信号,由于其间隔并不均匀,有接触了端点检测,了解到语音信号短时分析的多种参数意义及其计算原理,最终运用自相关函数法成功地完成了信号的端点检测, 分离了串接起来的DTMF信号, 最终完成连续DTMF信号的检测。利用appdesigner设计GUI界面的任务是对所实现算法的整理, 在设计过程中掌握了appdesigner的使用方法和一些加速开发的技巧,并且加深了面向对象编程的能力。