该项目要求学生完成自定义语音类,实现读取语音信号并做简单时域分析,初步掌握语音信号处理前
端的基本流程。
读取文件为wav格式的文件
相关基础
功能要求和设计步骤
(1)给定 WAVE 格式音频数据文件(以采样频率 8000Hz,采样精度 16 bits 为例),读取数据。实现 Matlab函数 wavread 载入数字化音频信号的功能。数据信息包括
1)原始采样数据
2)采样频率
3)每个样点编码位数
(2)获得指定的窗函数。
1)窗长(即帧长,如 20 ms) 2)窗函数类型,如矩形窗、Hanning 窗、Hamming 窗等
(3)对原始采样数据分帧、加窗得到短时语音帧。
① 必要参数
1)窗函数
2)帧长
3)帧移(如 10 ms)
② 设计提示
将用时间(ms)表示的帧长、帧移参数转换成采样点数。
(4)计算短时语音帧时域参数。
1)短时能量
2)短时平均幅度
3)短时过零率
(5)编程实现输出(以 2 s 为例)语音波形、短时能量、短时平均幅度、短时过零率结果图。同时,实验报告要求,
1)以表格形式分别给出典型浊音段、清音段 15 帧语音短时能量、短时平均幅度、短时过零率数值。
2)讨论短时能量、短时平均幅度、短时过零率等时域参数的用途。
实验代码
主程序
// An highlighted block
#include <iostream>
#include <fstream>//open files
#include <string.h>
#include<math.h>
#include<cmath>//caculate
#include<stdlib.h>
#include <bitset>
#include <iomanip>
#include "Lwc_wav.h"
#include "Lwc_winfun.h"
#include "Lwc_anlys.h"
#define PI 3.1415926535897932
using namespace std;
int main(int argc, char** argv)
{
Lwc_wav wav("test.wav");
Lwc_winfun fun(15, Hanning);//第一个参数为窗长(帧数),第二个参数为枚举类的窗类。
vector<long long>Egys;
vector<double> Mnts;
vector<int> Zros;
for (double i = 2; i < 96000; i++) {
vector<double> rst = fun.caculate(wav.data0, i);
Lwc_anlys fnl(rst);
Egys.push_back(fnl.Egy);//输出能量
Mnts.push_back(fnl.Mnt);//短时平均幅度
Zros.push_back(fnl.Zro);//短时过零率
cout << endl << fnl.Egy << endl << fnl.Mnt << endl << fnl.Zro << endl;
}
system("pause");
return 0;
}
wav文件的音频类,主要为读取音频的函数
类外定义代码段,实际操作中最好将类的定义放到头文件中,类外定义函数。
实际操作中由于wav文件头文件格式不同,不能从固定地方读出文件大小。
文件大小有的是在4a,有的是在22左右的位置,如果位置没读对会导致读错音频长度。
最好以二进制格式打开一下wav文件,看看data字段在哪里,读取data字段后面的文件具体大小。具体可以参考下wav格式的说明。
#include <iostream>
#include <fstream>//open files
#include <string.h>
#include<math.h>
#include<cmath>//caculate
#include<stdlib.h>
#include <bitset>
#include <iomanip>
#include <vector>
class Lwc_wav
{
public:
std::vector<double> data0, data1; //左右声道,单声道则相等
unsigned long frequency; //采样频率
unsigned short sample_num_bit; //一个样本的位数
Lwc_wav(const char* filename);
~Lwc_wav();
private:
unsigned char* data; //音频数据 ,这里要定义什么就看样本位数了,我这里只是单纯的复制数据
unsigned long file_size; //文件大小
unsigned short channel; //通道数
unsigned long Bps; //Byte率
unsigned long data_size; //数据大小
};
//------------------------------------------------------------
#include <iostream>
#include <fstream>//open files
#include <string.h>
#include<math.h>
#include<cmath>//caculate
#include<stdlib.h>
#include <bitset>
#include <iomanip>
#include "Lwc_wav.h"
using namespace std;
//读取之构造函数
Lwc_wav::Lwc_wav(const char* filename) {
fstream fs;
fs.open(filename, ios::binary | ios::in);
if (!fs)
{
cout << "fail to open the file" << endl;
system("pause");
}
else
{
cout << "open the file successfully" << endl;
}
fs.seekg(0x04); //从文件数据中获取文件大小
fs.read((char*)&file_size,sizeof(file_size));
file_size+=8;
fs.seekg(0x16);
fs.read((char*)&channel, sizeof(channel));
fs.seekg(0x18);
fs.read((char*)&frequency, sizeof(frequency));
fs.seekg(0x1c);
fs.read((char*)&Bps, sizeof(Bps));
fs.seekg(0x22);
fs.read((char*)&sample_num_bit, sizeof(sample_num_bit));
fs.seekg(0x4a);//注意!!实际操作中由于wav文件头文件格式不同,不能从固定地方读出文件大小。
//文件大小有的是在4a,有的是在22左右的位置,如果位置没读对会导致读错音频长度。
//最好以二进制格式打开一下wav文件,看看data字段在哪里,读取data字段后面的文件具体大小。
fs.read((char*)&data_size, sizeof(data_size));
data = new unsigned char[data_size];//数据指针在这儿
fs.seekg(0x2c);
fs.read((char*)data, sizeof(char) * data_size);
cout << "文件大小为 :" << file_size << endl;
cout << "音频通道数 :" << channel << endl;
cout << "采样频率 :" << frequency << endl;
cout << "Byte率 :" << Bps << endl;
cout << "样本位数 :" << sample_num_bit << endl;
cout << "音频数据大小:" << data_size << endl;
cout << "最后10个数据:" << endl;
bool is_1 = true;
for (unsigned long i = 0; i < data_size; i = i + 2)//十六字节为例
{
//右边为大端
unsigned long data_low = data[i];
unsigned long data_high = data[i + 1];
double data_true = (data_high * 256) + data_low;//调换高低位
long data_complement = 0;
//取大端的最高位(符号位)
int my_sign = (int)(data_high /128);
if (my_sign == 1) { data_complement = data_true - 65536; }//数据调转
else { data_complement = data_true; }
//printf("%d ",data_complement);
if (channel == 2) {
if (is_1) {
data0.push_back(double(data_complement));
is_1 = false;
}
else {
data1.push_back(double(data_complement));
is_1 = true;
}
}
else {
data0.push_back(double(data_complement));
data1 = data0;
}//单声道相同
}
fs.close();
printf("声道对象完成分离");
delete[] data;
}
Lwc_wav::~Lwc_wav() {
}
第二个类,为窗函数类,主要做三个窗函数、
#include<vector>
using namespace std;
enum Wintype {
Rect, Hanning, Hamming
};//枚举类定义,三重窗函数名称在这
class Lwc_winfun
{
public:
vector<double> caculate(vector<double>targ, long vary);
Lwc_winfun(long set_length, Wintype set_type);
protected:
double length;
Wintype type;
private:
vector<double> params;
};
//--------------------------------------------------------------------------
#include <iostream>
#include <fstream>//open files
#include <string.h>
#include<math.h>
#include<cmath>//caculate
#include<stdlib.h>
#include <bitset>
#include <iomanip>
#include "Lwc_winfun.h"
#define PI 3.1415926535897932
using namespace std;
Lwc_winfun::Lwc_winfun(long set_length, Wintype set_type) {
//矩形窗
length = set_length;
type = set_type;
if (type == Rect) {
for (int i = 0; i < length; i++) {
params.push_back(1);
}
}
//汉宁窗
if (type == Hanning) {
for (int i = 0; i < length; i++) {
params.push_back(0.5 - 0.5 * cos(2 * PI * i / (length - double(1))));
}
}
//汉明窗
if (type == Hanning) {
for (int i = 0; i < length; i++) {
params.push_back(0.54 - 0.46 * cos(2 * PI * i / (length - double(1))));
}
}
}
//计算短时语音帧,vart为偏移量,targ目标音频
vector<double> Lwc_winfun::caculate(vector<double>targ, long vary) {
//检测是否越界
/*if (targ[vary + length]==NULL) {
printf("Error! overload!,length:%d\n",targ.size());
system("pause");
}*/
//printf("%d", targ.size());
vector<double> rst;
for (int i = 0; i < length; i++) {
rst.push_back(targ[vary + i] * params[i]);
}
return rst;
};
计算通过窗类计算瞬时能量的类。实际均以STL库中的vector算的。
#include<vector>
using namespace std;
enum Wintype {
Rect, Hanning, Hamming
};//枚举类定义,三重窗函数名称在这
class Lwc_winfun
{
public:
vector<double> caculate(vector<double>targ, long vary);
Lwc_winfun(long set_length, Wintype set_type);
protected:
double length;
Wintype type;
private:
vector<double> params;
};
//----Lwc_anlys.h----------------------------------------------
#pragma once
#include<vector>
class Lwc_anlys
{
public:
long long Egy;
double Mnt;
int Zro;
Lwc_anlys(std:: vector<double> data);
};
//----------------------------------------------------------------------
#include "Lwc_anlys.h"
Lwc_anlys::Lwc_anlys(std::vector<double> data) {
Mnt = 0;
Egy = 0;
Zro = 0;
for (int i = 0; i < data.size(); i++) {
//计算短时能量
Egy += long long(data[i]) * long long(data[i]);
//计算平均幅度
Mnt += (data[i])/ double(data.size());
//计算过零率
if (i > 0 && long long(data[i]) * long long(data[i - 1] < -0.00001)) Zro++;
}
}
实验心得
我做这个东西时主要是踩了没有从正确位置读取wav文件长度的坑。Wav文件的文件头并不是都像实验指导书上说的长度和格式都完全固定的。另外BinaryViewer这款软件真的很好用,很强烈推荐,可以用来读二进制。这个程序与第三个程序对大部分同学来说还是很困难的,我在指导实验的时候几乎很少有同学能够独立完成,难点主要在wav文件的读取上,实验指导书给的指示不够,同学本身在学习C语言时也没有重点学习文件和异常处理。