《语音信号处理试验教程》(梁瑞宇等)的代码主要是Matlab实现的,现在Python比较热门,所以把这个项目大部分内容写成了Python实现,大部分是手动写的。使用CSDN博客查看帮助文件:
Python语音基础操作–2.1语音录制,播放,读取
Python语音基础操作–2.2语音编辑
Python语音基础操作–2.3声强与响度
Python语音基础操作–2.4语音信号生成
Python语音基础操作–3.1语音分帧与加窗
Python语音基础操作–3.2短时时域分析
Python语音基础操作–3.3短时频域分析
Python语音基础操作–3.4倒谱分析与MFCC系数
Python语音基础操作–4.1语音端点检测
Python语音基础操作–4.2基音周期检测
Python语音基础操作–4.3共振峰估计
Python语音基础操作–5.1自适应滤波
Python语音基础操作–5.2谱减法
Python语音基础操作–5.4小波分解
Python语音基础操作–6.1PCM编码
Python语音基础操作–6.2LPC编码
Python语音基础操作–6.3ADPCM编码
Python语音基础操作–7.1帧合并
Python语音基础操作–7.2LPC的语音合成
Python语音基础操作–10.1基于动态时间规整(DTW)的孤立字语音识别试验
Python语音基础操作–10.2隐马尔科夫模型的孤立字识别
Python语音基础操作–11.1矢量量化(VQ)的说话人情感识别
Python语音基础操作–11.2基于GMM的说话人识别模型
Python语音基础操作–12.1基于KNN的情感识别
Python语音基础操作–12.2基于神经网络的情感识别
Python语音基础操作–12.3基于支持向量机SVM的语音情感识别
Python语音基础操作–12.4基于LDA,PCA的语音情感识别
代码可在Github上下载:busyyang/python_sound_open
从人类的发音器官的机理来看,发不同性质的声音时,声道的情况是不同的。此外,声门和声道的相互藕合会形成语音信号的非线性特性。但语音信号特性随着时间变化是很缓慢的,所以可以作出一些合理的假设,将语音信号分为一些相继的短段进行处理,在这些短段中可以认为语音信号特性是不随时间变化的平稳随机过程。通过对发音器官和语音产生机理的分析,语音生成系统理论上分成三个部分,在声门(声带)以下,称为"声门子系统",它负责产生激励震动,是"激励系统";从声门到嘴唇的呼气通道是声道,是"声道系统";话音从嘴唇辐射出去,因此嘴唇以外部分称为"辐射系统" 。
发浊音时,由于声带不断张开与关闭,产生间歇的脉冲波,类似与一个斜三角形的脉冲:
g ( n ) = { 1 2 [ 1 − cos ( π n / N 1 ) ] , 0 ⩽ n ⩽ N 1 cos [ π ( n − N 1 ) / ( 2 N 2 ) ] , N 1 ⩽ n ⩽ N 1 + N 2 0 , o t h e r g(n)=\left\{\begin{array}{lc}\frac12\lbrack1-\cos(\pi n/N_1)\rbrack,&0\leqslant n\leqslant N_1\\\cos\lbrack\pi(n-N_1)/(2N_2)\rbrack,&N_1\leqslant n\leqslant N_1+N_2\\0,&other\end{array}\right. g(n)=⎩⎨⎧21[1−cos(πn/N1)],cos[π(n−N1)/(2N2)],0,0⩽n⩽N1N1⩽n⩽N1+N2other
其中 N 1 N_1 N1为斜三角上升部分的时间, N 1 N_1 N1是其下降部分的时间。单个斜三角波波形的频谱 G ( e j w ) G(e^{jw}) G(ejw)其z变换的全级模型为:
G ( z ) = 1 ( 1 − e − c T z − 1 ) 2 G(z)=\frac{1}{(1-e^{-cT}z^{-1})^2} G(z)=(1−e−cTz−1)21
是一个低通滤波器, c c c是一个频率常数。由于G(z)是一个二极点的模型,因此斜三角波形可视为加权的单位脉冲串激励上述单个斜三角波模型的结果。而单位脉冲串及幅值因子则可表示成下面的 z z z变换形式:
E ( z ) = A v 1 − z − 1 E(z)=\frac{A_v}{1-z^{-1}} E(z)=1−z−1Av
所以整个浊音激励模型为:
U ( z ) = G ( z ) E ( z ) = A v 1 − z − 1 ⋅ 1 ( 1 − e − c T z − 1 ) 2 U(z)=G(z)E(z)=\frac{A_v}{1-z^{-1}}·\frac{1}{(1-e^{-cT}z^{-1})^2} U(z)=G(z)E(z)=1−z−1Av⋅(1−e−cTz−1)21
发清音时,元论是发阻塞音或摩擦音,声道都被阻碍形成揣流。所以,可把清音激励模拟为白噪声。
共振峰模型把声监视为一个谐振腔,共振峰就是这个腔体的谐振频率。由于人耳昕觉的柯替氏器官的纤毛细胞是按频率感受而排列其位置的,所以这种共振峰的声道模型方法是非常有效的。一般来说,一个元音用前三个共振峰来表示就足够了;而对于较复杂的辅音或鼻音,大概要用到前五个以上的共振峰才行。
从物理声学观点,可以很容易推导出均匀断面的声管的共振频率。一般成人的声道约为17cm 长,因此算出其开口时的共振频率为
F i = ( 2 i − 1 ) c 4 L F_i=\frac{(2i-1)c}{4L} Fi=4L(2i−1)c
这里, i = 1 , 2 , . . . i=1,2,... i=1,2,...为正整数,表示共振峰的序号; C 为声速; L 为声管长度。可以计算出元音 e e e前3个振锋为 F 1 = 500 H z , F 2 = 1500 H z , F 3 = 2500 H z F_1=500Hz,F_2=1500Hz,F_3=2500Hz F1=500Hz,F2=1500Hz,F3=2500Hz。另外,除了共振峰频率之外,共振峰模型还包括共振峰带宽和幅度等参数。
对于级联型的共振峰模型来说,声道可以视为一组串联的二阶谐振器。从共振峰理论来看,整个声道具有多个谐振频率和反谐振频率,所以可被模拟为一个包含零极点的数学模型;但对于一般元音,则用全极点模型即可,其传输函数可表示为
V ( z ) = G 1 − ∑ k = 1 N a k z − k V(z)=\frac{G}{1-\sum\limits_{k=1}^Na_kz^{-k}} V(z)=1−k=1∑Nakz−kG
式巾, N N N是极点个数; G G G是幅值因子; a k a_k ak是常系数。此时可将它分解为多个二阶极点的网络的串联,即
V ( z ) = ∏ i = 1 N a i 1 − b i z − 1 − c i z − 2 V(z)=\prod_{i=1}^N\frac{a_i}{1-b_iz^{-1}-c_iz^{-2}} V(z)=i=1∏N1−biz−1−ciz−2ai
其中,
c i = − exp ( − 2 π B i T ) c_i=-\exp (-2\pi B_iT) ci=−exp(−2πBiT)
b i = 2 exp ( − π B i T ) cos ( 2 π F i T ) b_i=2\exp(-\pi B_iT)\cos(2\pi F_iT) bi=2exp(−πBiT)cos(2πFiT)
a i = 1 − b i − c i a_i=1-b_i-c_i ai=1−bi−ci
G = a 1 ⋅ a 2 ⋅ . . . a M G=a_1·a_2·...a_M G=a1⋅a2⋅...aM
并且 M M M是小于 ( N + 1 ) / 2 (N+1)/2 (N+1)/2的整数,若 z k z_k zk是第 k k k个极点,有 z k = e − B k T e − 2 π F k T z_k=e^{-B_kT}e^{-2\pi F_kT} zk=e−BkTe−2πFkT, T T T是采样周期。
import pyaudio
import wave
import librosa
import librosa.display
import matplotlib.pyplot as plt
from scipy.io import wavfile
import numpy as np
import pandas as pd
from scipy.signal import lfilter
class soundBase:
def __init__(self, path):
self.path = path
def audiorecorder(self, len=2, formater=pyaudio.paInt16, rate=16000, frames_per_buffer=1024, channels=2):
"""
使用麦克风进行录音
2020-2-25 Jie Y. Init
:param len: 录制时间长度(秒)
:param formater: 格式
:param rate: 采样率
:param frames_per_buffer:
:param channels: 通道数
:return:
"""
p = pyaudio.PyAudio()
stream = p.open(format=formater, channels=channels, rate=rate, input=True, frames_per_buffer=frames_per_buffer)
print("start recording......")
frames = []
for i in range(0, int(rate / frames_per_buffer * len)):
data = stream.read(frames_per_buffer)
frames.append(data)
print("stop recording......")
stream.stop_stream()
stream.close()
p.terminate()
wf = wave.open(self.path, 'wb')
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(formater))
wf.setframerate(rate)
wf.writeframes(b''.join(frames))
wf.close()
def audioplayer(self, frames_per_buffer=1024):
"""
播放语音文件
2020-2-25 Jie Y. Init
:param frames_per_buffer:
:return:
"""
wf = wave.open(self.path, 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(frames_per_buffer)
while data != b'':
stream.write(data)
data = wf.readframes(frames_per_buffer)
stream.stop_stream()
stream.close()
p.terminate()
def audiowrite(self, data, fs, binary=True, channel=1, path=[]):
"""
信息写入到.wav文件中
:param data: 语音信息数据
:param fs: 采样率(Hz)
:param binary: 是否写成二进制文件(只有在写成二进制文件才能用audioplayer播放)
:param channel: 通道数
:param path: 文件路径,默认为self.path的路径
:return:
"""
if len(path) == 0:
path = self.path
if binary:
wf = wave.open(path, 'wb')
wf.setframerate(fs)
wf.setnchannels(channel)
wf.setsampwidth(2)
wf.writeframes(b''.join(data))
else:
wavfile.write(path, fs, data)
def audioread(self, formater='sample'):
"""
读取语音文件
2020-2-26 Jie Y. Init
这里的wavfile.read()函数修改了里面的代码,返回项return fs, data 改为了return fs, data, bit_depth
如果这里报错,可以将wavfile.read()修改。
:param formater: 获取数据的格式,为sample时,数据为float32的,[-1,1],同matlab同名函数. 否则为文件本身的数据格式
指定formater为任意非sample字符串,则返回原始数据。
:return: 语音数据data, 采样率fs,数据位数bits
"""
fs, data, bits = wavfile.read(self.path)
if formater == 'sample':
data = data / (2 ** (bits - 1))
return data, fs, bits
def soundplot(self, data=[], sr=16000, size=(14, 5)):
"""
将语音数据/或读取语音数据并绘制出来
2020-2-25 Jie Y. Init
:param data: 语音数据
:param sr: 采样率
:param size: 绘图窗口大小
:return:
"""
if len(data) == 0:
data, fs = self.audioread()
plt.figure(figsize=size)
x = [i / sr for i in range(len(data))]
plt.plot(x, data)
plt.xlim([0, len(data) / sr])
plt.xlabel('s')
plt.show()
def sound_add(self, data1, data2):
"""
将两个信号序列相加,若长短不一,在短的序列后端补零
:param data1: 序列1
:param data2: 序列2
:return:
"""
if len(data1) < len(data2):
tmp = np.zeros([len(data2)])
for i in range(len(data1)):
tmp[i] += data1[i]
return tmp + data2
elif len(data1) > len(data2):
tmp = np.zeros([len(data1)])
for i in range(len(data2)):
tmp[i] += data2[i]
return tmp + data1
else:
return data1 + data2
def SPL(self, data, fs, frameLen=100, isplot=True):
"""
计算声压曲线
2020-2-26 Jie Y. Init
:param data: 语音信号数据
:param fs: 采样率
:param frameLen: 计算声压的时间长度(ms单位)
:param isplot: 是否绘图,默认是
:return: 返回声压列表spls
"""
def spl_cal(s, fs, frameLen):
"""
根据数学公式计算单个声压值
$y=\sqrt(\sum_{i=1}^Nx^2(i))$
2020-2-26 Jie Y. Init
:param s: 输入数据
:param fs: 采样率
:param frameLen: 计算声压的时间长度(ms单位)
:return: 单个声压数值
"""
l = len(s)
M = frameLen * fs / 1000
if not l == M:
exit('输入信号长度与所定义帧长不等!')
# 计算有效声压
pp = 0
for i in range(int(M)):
pp += (s[i] * s[i])
pa = np.sqrt(pp / M)
p0 = 2e-5
spl = 20 * np.log10(pa / p0)
return spl
length = len(data)
M = fs * frameLen // 1000
m = length % M
if not m < M // 2:
# 最后一帧长度不小于M的一半
data = np.hstack((data, np.zeros(M - m)))
else:
# 最后一帧长度小于M的一半
data = data[:M * (length // M)]
spls = np.zeros(len(data) // M)
for i in range(length // M - 1):
s = data[i * M:(i + 1) * M]
spls[i] = spl_cal(s, fs, frameLen)
if isplot:
plt.subplot(211)
plt.plot(data)
plt.subplot(212)
plt.step([i for i in range(len(spls))], spls)
plt.show()
return spls
def iso226(self, phon, isplot=True):
"""
绘制等响度曲线,输入响度phon
2020-2-26 Jie Y. Init
:param phon: 响度值0~90
:param isplot: 是否绘图,默认是
:return:
"""
## 参数来源: 语音信号处理试验教程,梁瑞宇P36-P37
f = [20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, \
1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500]
af = [0.532, 0.506, 0.480, 0.455, 0.432, 0.409, 0.387, 0.367, 0.349, 0.330, 0.315, \
0.301, 0.288, 0.276, 0.267, 0.259, 0.253, 0.250, 0.246, 0.244, 0.243, 0.243, \
0.243, 0.242, 0.242, 0.245, 0.254, 0.271, 0.301]
Lu = [-31.6, - 27.2, - 23.0, - 19.1, - 15.9, - 13.0, - 10.3, - 8.1, - 6.2, - 4.5, - 3.1, \
- 2.0, - 1.1, - 0.4, 0.0, 0.3, 0.5, 0.0, - 2.7, - 4.1, - 1.0, 1.7, \
2.5, 1.2, - 2.1, - 7.1, - 11.2, - 10.7, - 3.1]
Tf = [78.5, 68.7, 59.5, 51.1, 44.0, 37.5, 31.5, 26.5, 22.1, 17.9, 14.4, \
11.4, 8.6, 6.2, 4.4, 3.0, 2.2, 2.4, 3.5, 1.7, - 1.3, - 4.2, \
- 6.0, - 5.4, - 1.5, 6.0, 12.6, 13.9, 12.3]
if phon < 0 or phon > 90:
print('Phon value out of range!')
spl = 0
freq = 0
else:
Ln = phon
# 从响度级计算声压级
Af = 4.47E-3 * (10 ** (0.025 * Ln) - 1.15) + np.power(0.4 * np.power(10, np.add(Tf, Lu) / 10 - 9), af)
Lp = np.multiply(np.divide(10, af), np.log10(Af)) - Lu + 94
spl = Lp
freq = f
if isplot:
plt.semilogx(freq, spl, ':k')
plt.axis([20, 20000, -10, 130])
plt.title('Phon={}'.format(phon))
plt.grid()
plt.show()
return spl, freq
def vowel_generate(self, len, pitch=100, sr=16000, f=[730, 1090, 2440]):
"""
生成一个元音片段
2020-2-26 Jie Y. Init
:param len: 长度,点数
:param pitch:
:param sr: 采样率
:param f: 前3个共振峰,默认为元音a的
:return: 生成的序列
"""
f1, f2, f3 = f[0], f[1], f[2]
y = np.zeros(len)
points = [i for i in range(0, len, sr // pitch)]
indices = np.array(list(map(int, np.floor(points))))
y[indices] = (indices + 1) - points
y[indices + 1] = points - indices
a = np.exp(-250 * 2 * np.pi / sr)
y = lfilter([1], [1, 0, -a * a], y)
if f1 > 0:
cft = f1 / sr
bw = 50
q = f1 / bw
rho = np.exp(-np.pi * cft / q)
theta = 2 * np.pi * cft * np.sqrt(1 - 1 / (4 * q * q))
a2 = -2 * rho * np.cos(theta)
a3 = rho * rho
y = lfilter([1 + a2 + a3], [1, a2, a3], y)
if f2 > 0:
cft = f2 / sr
bw = 50
q = f2 / bw
rho = np.exp(-np.pi * cft / q)
theta = 2 * np.pi * cft * np.sqrt(1 - 1 / (4 * q * q))
a2 = -2 * rho * np.cos(theta)
a3 = rho * rho
y = lfilter([1 + a2 + a3], [1, a2, a3], y)
if f3 > 0:
cft = f3 / sr
bw = 50
q = f3 / bw
rho = np.exp(-np.pi * cft / q)
theta = 2 * np.pi * cft * np.sqrt(1 - 1 / (4 * q * q))
a2 = -2 * rho * np.cos(theta)
a3 = rho * rho
y = lfilter([1 + a2 + a3], [1, a2, a3], y)
plt.plot(y)
plt.show()
return y
from soundBase import soundBase
sb = soundBase('a.wav')
y = sb.vowel_generate(16000)
sb.audiowrite(y, 16000)
sb.soundplot()
sb.audioplayer()