首先先说明一下 DTMF 的定义:Dual-Tone Multi-Frequency, 常见的应用就是电话按键的编码及解码. 如电话按键 4 * 4 的矩阵对应表.

由一组低频与一组高频的 SINE 波形相加组成.

我们可以函数来表示:

def sine_sine_wave(f1, f2, length, rate):s1=sine_wave(f1,length,rate)s2=sine_wave(f2,length,rate)ss=s1+s2sa=numpy.divide(ss, 2.0)return sa

其中 f1, f2, 分别表示两个频率, length 表示这个 tone 波形的时间, rate 表示采样率(每秒采样的样本点数量)

而 sin_wave 的波形节点可以如下表达:

def sine_wave(frequency, length, rate):length = int(length * rate)factor = float(frequency) * (math.pi * 2) / ratereturn numpy.sin(numpy.arange(length) * factor)

如 sine_wave(690, 0.2, 44100) 就会得到 8820 个 sine 波形节点.

要生成 tone 的 wave 资料, 建立 digits 对应表 -

dtmf_freqs = {'1': (1209,697), '2': (1336, 697), '3': (1477, 697), 'A': (1633, 697),'4': (1209,770), '5': (1336, 770), '6': (1477, 770), 'B': (1633, 770),'7': (1209,852), '8': (1336, 852), '9': (1477, 852), 'C': (1633, 852),'*': (1209,941), '0': (1336, 941), '#': (1477, 941), 'D': (1633, 941)}
dtmf_digits = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#', 'A', 'B', 'C', 'D']

以下的函数用于表达一个 tone 集合所产生的波形资料, 每个 tone 产生 0.2 秒, tone与tone 间隔 0.2 秒.

def play_dtmf_tone(stream, digits, length=0.20, rate=44100):dtmf_freqs = {'1': (1209,697), '2': (1336, 697), '3': (1477, 697), 'A': (1633, 697),'4': (1209,770), '5': (1336, 770), '6': (1477, 770), 'B': (1633, 770),'7': (1209,852), '8': (1336, 852), '9': (1477, 852), 'C': (1633, 852),'*': (1209,941), '0': (1336, 941), '#': (1477, 941), 'D': (1633, 941)}dtmf_digits = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#', 'A', 'B', 'C', 'D']if type(digits) is not type(''):digits=str(digits)[0]digits = ''.join ([dd for dd in digits if dd in dtmf_digits])for digit in digits:digit=digit.upper()frames = []frames.append(sine_sine_wave(dtmf_freqs[digit][0], dtmf_freqs[digit][1],\length, rate))chunk = numpy.concatenate(frames) * 0.25stream.write(chunk.astype(numpy.float32).tostring())time.sleep(0.2)

为了验证的目的, 以下利用 threading 模块实现即播即录(担心时间上衔接不好, 录长一点, 录10秒)的功能

录音的函数:

def recorder():CHUNK = 1024FORMAT = pyaudio.paInt16CHANNELS = 1RATE = 44100RECORD_SECONDS = 10WAVE_OUTPUT_FILENAME = "output.wav"p = pyaudio.PyAudio()stream = p.open(format=FORMAT,channels=CHANNELS,rate=RATE,input=True,frames_per_buffer=CHUNK)print("* recording")frames = []for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):data = stream.read(CHUNK)frames.append(data)print("* done recording")stream.stop_stream()stream.close()p.terminate()wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')wf.setnchannels(CHANNELS)wf.setsampwidth(p.get_sample_size(FORMAT))wf.setframerate(RATE)wf.writeframes(b''.join(frames))wf.close()return

播放波形声音的函数

def play_tone(digits):p = pyaudio.PyAudio()stream = p.open(format=pyaudio.paFloat32,channels=1, rate=44100, output=1)play_dtmf_tone(stream, digits)stream.close()p.terminate()

主函数 threading 的应用

if __name__ == '__main__':threads = []t = threading.Thread(target=recorder)threads.append(t)t.start()# give some time to bufertime.sleep(1)if len(sys.argv) != 2:digits = "12"else:digits = sys.argv[1]    play_tone(digits)

如此即完成 DTMF 编码并生成10秒的 wave 档案.

编码与解码的规则是相应的, 以下用解码的结果来验证编码的算法.

解码:从某些连续段落的波形资料中找到是某两个已定义的高频与低频的组合, 即可对应出 tone digit. 我们使用的是 goertzel 算法,

这个算法的效率比 DFT 或是 FFT 都高. 一组波形资料经过 Goertzel 算法后都会得到该频率的匹配度.

def goertzel_mag(numSamples, target_freq, sample_rate, data):'''int k, ifloat floatnumSamplesfloat omega, sine, cosine, coeff, q0, q1, r2, magnitude, real, imag'''#floatnumSamples = (float)numSamplesscalingFactor = numSamples / 2.0k = (int) (0.5 + ((numSamples * target_freq)/sample_rate))omega = (2.0 * math.pi * k)/numSamplessine = math.sin(omega)cosine = math.cos(omega)coeff = 2.0 * cosineq0 = 0q1 = 0q2 = 0for i in range(0, (int)(numSamples)):#print("Hello")q0 = (coeff * q1) - q2 + data[i]q2 = q1q1 = q0#real = (q1 - (q2 * cosine)) / scalingFactor#imag = (q2 * sine) / scalingFactor#magnitude = math.sqrt((real * real) + (imag * imag))magnitude = (q2*q2) + (q1*q1) - (coeff * q1 * q2)return magnitude

为了提高效率, 我们可以先过滤静音段落(silent chunk), 根据经验将门限设定为 328. 而每一个段落大小我们设定为 1K = 1024 bytes.

THRESHOLD = 328
CHUNK = 1024
def IsSilent(samples):if max(np.absolute(samples)) <= THRESHOLD:return Truereturn False

一样是使用编码时所用的 dtmf_freqs, 并定义找到 tone_digit 的函数

dtmf_freqs = {'1': (1209,697), '2': (1336, 697), '3': (1477, 697), 'A': (1633, 697),'4': (1209,770), '5': (1336, 770), '6': (1477, 770), 'B': (1633, 770),'7': (1209,852), '8': (1336, 852), '9': (1477, 852), 'C': (1633, 852),'*': (1209,941), '0': (1336, 941), '#': (1477, 941), 'D': (1633, 941)}def find_digit(hi, lo):for key in dtmf_freqs:if (dtmf_freqs[key][0] == hi) and (dtmf_freqs[key][1] == lo):return key

回忆一下前面所描述解码的思想 - 连续的段落出现 tone digit, 可以再加上出现的次数即 chunk 数量, 也就是 tone 的波形时间与编码是相互呼应(0.2秒).

于是如下定义 -

NOISE_CHUNK_COUNT = 4 #出现时间太短,需忽略
MAX_CHUNK_COUNT = 16  #满足波形资料0.2秒,若 chunk count 为 30, 代表出现2次

而整个解码的过程,主程式表达如下 -

hi_freqs = [1209, 1336, 1477, 1633]
lo_freqs = [697, 770, 852, 941]if __name__ == '__main__':sample_rate = 44100CHUNK_SIZE = 1024NOISE_CHUNK_COUNT = 4MAX_CHUNK_COUNT = 16wav = wave.open('output.wav', 'rb')n = wav.getnframes()# n = 440320# 样本数print("样本数: %d" % n)#debug = wav.readframes(1)c = 0 # 计数channels = wav.getnchannels()sample_width = wav.getsampwidth()chunk_sample_count = (int)(CHUNK_SIZE / channels / sample_width)digits = [] # 记录digit出现的次数及顺序,每个元素由{digit: count}组成pre_digit = '' # 记录前一次出现的digitwhile(c * CHUNK_SIZE < n * channels * sample_width):data_chunk_string = wav.readframes(chunk_sample_count)chunk_data = np.fromstring(data_chunk_string, dtype=np.int16)if (not IsSilent(chunk_data)):max_mag_hi_freq = 0maxvalue_mag_hi_freq = 0for freq in hi_freqs:mag = goertzel_mag(chunk_sample_count, freq, sample_rate, chunk_data)#if c == 277:#    print("(%d)CHUNK %d magnitude with higher frequency (%d) = %f" % \#          (max_mag_hi_freq, c, freq, mag))if(mag > maxvalue_mag_hi_freq):maxvalue_mag_hi_freq = magmax_mag_hi_freq = freq#if c == 277:#    print("CHUNK %d maximal magnitude located high frequency(%d) " % \#              (c, max_mag_hi_freq))max_mag_lo_freq = 0maxvalue_mag_lo_freq = 0for freq in lo_freqs:mag = goertzel_mag(chunk_sample_count, freq, sample_rate, chunk_data)#print("CHUNK %d magnitude with lower frequency (%d) = %f" % \#      (c, freq, mag))if(mag > maxvalue_mag_lo_freq):maxvalue_mag_lo_freq = magmax_mag_lo_freq = freq#print("CHUNK %d maximal magnitude located (%d, %d) " % \#          (c, max_mag_hi_freq, max_mag_lo_freq))digit = find_digit(max_mag_hi_freq, max_mag_lo_freq)# 判断 digit 是否前一次出现过,若是则相对应次数加一, 否则新增元素digits_element = dict()if(pre_digit != digit):digits_element[digit] = 1else:digits_element = digits.pop()digits_element[digit] += 1digits.append(digits_element)pre_digit = digit#print("CHUNK %d: %c" % (c, digit))c = c+1wav.close()for i in range(0, len(digits)):for k in digits[i]:if digits[i][k] > NOISE_CHUNK_COUNT:t = 0while(t < digits[i][k]):t += MAX_CHUNK_COUNTprint(k)

测试解码结果:

可以看见前面编码的 tone digits. Done. ^_^.

DTMF 编码及解码相关推荐

  1. 【DSP】DTMF 信号的编码和解码

    一.实验内容  1)把您的联系电话号码 通过DTMF 编码生成为一个 .wav 文件. 技术指标: 根据 ITU Q.23 建议,DTMF 信号的技术指标是:传送/接收率为每秒 10 个号码,或每个号 ...

  2. Python 对图像进行base64编码及解码读取为numpy、opencv、matplot需要的格式

    Python 对图像进行base64编码及解码读取为numpy.opencv.matplot需要的格式 1. 效果图 2. 源码 参考 这篇博客将介绍Python如何对图像进行base64编解码及读取 ...

  3. 二叉树:二叉搜索树的编码和解码

    二叉搜索树的编码和解码描述: 编码:即将一个二叉搜索树编码,节点数值转换为字符串 解码:即将一个字符串解码,数值转换为对应的二叉搜索树的节点 过程导图如下: 针对性编码实现如下: /*数字转字符串*/ ...

  4. js php base64,JavaScript实现Base64编码与解码的代码详解

    本篇文章给大家分享的是jJavaScript实现Base64编码与解码的代码详解,内容挺不错的,希望可以帮助到有需要的朋友 一.加密解密方法使用//1.加密 var str = '124中文内容'; ...

  5. php base64解码,PHP Base64 中英文编码 JavaScript 解码

    最新PHP Base64 中英文编码 JavaScript 解码 以下是三零网为大家整理的最新PHP Base64 中英文编码 JavaScript 解码的文章,希望大家能够喜欢! function ...

  6. android Java BASE64编码和解码一:基础

    今天在做Android项目的时候遇到一个问题,需求是向服务器上传一张图片,要求把图片转化成图片流放在 json字符串里传输. 类似这样的: {"name":"jike&q ...

  7. 字符串工具类、数组工具类、集合工具类、转型操作工具类、编码与解码操作工具类...

    package hjp.smart4j.framework.util;import org.apache.commons.lang3.StringUtils;/*** 字符串工具类*/ public ...

  8. Base64编码和解码

    Base64编码和解码 DES加密后密文长度是8个整数倍 加密后比明文长度变长,所以编码表找不到对应字符,乱码 使用Base64编码和解密:从Apache现在 1.加密后密文使用Base64编码 2. ...

  9. 如何编码和解码base64字符串?

    本文翻译自:How do I encode and decode a base64 string? How do I return a base64 encoded string given a st ...

最新文章

  1. 超棒整理 | Python 关键字知识点大放送
  2. pb 执行存储过程带参数_数据库存储过程
  3. FileLocator Pro:强大高效的无索引全文搜索软件
  4. 南科大计算机系:将开源和企业引入计算机课程教学
  5. 【SVN】Eclipse SVN插件下载安装
  6. SVN修改服务器中的文件夹名称
  7. 比亚迪半导体IPO再生波折:又被中止审核 红杉小米是股东
  8. HTML页面浏览历史,浏览历史记录功能
  9. Android解决手机图片压缩后旋转问题
  10. linux 切换gnome kde桌面,科学网—openSUSE15.1切换桌面环境(从Gnome至KDE Plasma) - 潘林的博文...
  11. 医学方面的创业计划书_医学生创业计划书怎么写
  12. 2019年8月2 星期五 今日计划
  13. c语言程序设计小球弹跳,C++Dos游戏设计——弹跳小球
  14. 《IDSSIM:基于改进的疾病语义相似度方法的lncRNA功能相似度计算模型》论文梳理
  15. HTML一条线匀速一定区域运动,通用的匀速运动框架如何打造
  16. Android面试总结-中高级
  17. 常见电机分类和驱动原理动画
  18. 【网页图标】favicon.ico文件的设置
  19. Criteria用法的小结
  20. python基础八:集合

热门文章

  1. 台湾印象: 太平洋的风
  2. 怎么写好一份数据分析报告
  3. Windows XP常见进程列表
  4. 安装GitHub代码所需包
  5. C++优化三板斧:Three Optimization Tips for C++
  6. Oracle——获取当前系统时间以及插入日期型数据(to_date的用法)
  7. linux查看固态硬盘寿命,CentOS下查看 ssd 寿命
  8. Jmeter之事务控制器
  9. docker一个镜像启动多个容器的操作
  10. Parent directory of crack_capcha.model doesn't exist, can't save. (tensorflow 报错)