- 转载请注明出处,谢谢您!https://blog.youkuaiyun.com/ljrsunshine/article/details/88996755
- 使用HTK进行语音处理时,会用到HCopy工具提取wav音频文件的MFCC特征。
- HCopy还支持其他形式的输入和输出,具体请参考《HTK book 》“17.4节HCopy 之 Table. 17.1 Valid Parameter Conversions”
- 本文仅是笔者调试HCopy源码过程中,对提取MFCC特征向量的部分代码调用关系的简要记录
- 对于MFCC和wav音频的概念,您可以参考以下博客:
前期准备
- HTK代码开源,http://htk.eng.cam.ac.uk/, 注册账号,阅读并同意许可协议,申请下载源码即可。
- 笔者下载的是3.4.1版本。使用VS2013编译源码,具体步骤请参考这篇博客:HTK3.4.1在VS2013建立工程编译(在此感谢博主)。
配置文件
- config文件,指定所有转换参数
#[MODULE] PARAMETER = VALUE SOURCEKIND = WAVEFORM TARGETKIND = MFCC_0_D_A TARGETRATE = 100000.0 SOURCEFORMAT = WAV SAVECOMPRESSSED = T SAVEWITHCRC = T ZMEANSOURCE = T WINDOWSIZE = 250000.0 USEHAMMING = T PREEMCOEF = 0.97 NUMCHANS = 26 CEPLIFTER = 22 NUMCEPS = 12 ENORMALISE = F
- 源文件及其相应输出文件的列表
S0001.wav S0001.mfc S0002.wav S0002.mfc S0003.wav S0003.mfc (etc.)
源码之函数调用关系浅析
1. HCopy的main函数可以划分成3部分
- 初始化
InitMem(); InitLabel();
………… ………… (etc.)
CreateHeap(&tStack, "Trace", MSTAK, 1, 0.0, 100, 200);
- 根据命令行参数做相应处理
while (NextArg() == SWITCHARG)
{
s = GetSwtArg();
switch (s[0])
{
case 'a':
…………(etc.)
…………(etc.)
default:
}
}
- 根据命令行参数,对输入进行处理(这是本文解析的重点)及收尾处理
while (NumArgs() > 1)
{ /* process group S1 + S2 + ... TGT */
off = 0.0;
s = GetStrArg();
OpenSpeechFile(s);
…………(etc.)
if (trans != NULL)
{
trans = NULL;
ResetHeap(&lStack);
}
ResetHeap(&iStack);
ResetHeap(&oStack);
if (chopF) ResetHeap(&cStack);
}
if (useMLF) CloseMLFSaveFile();
if (NumArgs() != 0) HError(-1019, "HCopy: Unused args ignored");
Exit(0);
return (0);
2. HCopy对输入音频文件的处理
- 在main函数 “3. 根据命令行参数,对输入进行处理” 中调用OpenSpeechFile(char *s)函数,处理输入的wav文件或参数文件。char *s是文件名,即在config文件中罗列的输入文件名。
- OpenSpeechFile调用OpenParmFile
- OpenParmFile调用OpenBuffer
- OpenBuffer调用OpenAsChannel
- OpenAsChannel函数的作用是打开并创建一个音频输入缓冲区。
- OpenAsChannel调用OpenWaveInput函数,该函数的作用是打开输入的wav文件并读取文件信息。
- OpenWaveInput函数
- 调用GetWAVHeaderInfo读取wav文件头信息(编码方式、采样率、位深etc.),并返回音频数据的长度。
wav文件的具体格式可以参考我的另一篇博客:wav文件分析(实例+代码)
typedef enum { DoCVT = 1, /* input conversion needed */ DoBSWAP = 2, /* byte swap needed */ DoSPACK = 4, /* SHORT PACK decompression needed */ DoSHORT = 8, /* SHORTEN decompression needed */ DoMULAW = 16, /* 8 bit Mu-Law expansion needed */ DoALAW = 32, /* 8 bit A-Law expansion needed */ Do8_16 = 64, /* 8 bit PCM expansion needed */ DoSTEREO = 128 /* Convert stereo to mono*/ }InputAction; InputAction ia = (InputAction)0; /* flags to enable conversions etc */ /* GetWAVHeaderInfo: get header for Microsoft WAVE format sound file */ static long GetWAVHeaderInfo(FILE *f, Wave w, InputAction *ia) { char magic[4]; int32 len, lng, numBytes; char c; short sht, sampSize, type, chans; if (MustSwap(VAXSO)) *ia = (InputAction)(*ia | DoBSWAP); fread(magic, 4, 1, f); if (strncmp("RIFF", magic, 4)) { HRError(6251, "Input file is not in RIFF format"); return -1; } fread(&lng, 4, 1, f); fread(magic, 4, 1, f); if (strncmp("WAVE", magic, 4)) { HRError(6251, "Input file is not in WAVE format"); return -1; } /* Look for "fmt " before end of file */ while (1) { if (feof(f)) { HRError(6251, "No data portion in WAVE file"); return -1; } fread(magic, 4, 1, f); // fmt fread(&len, 4, 1, f); if (*ia & DoBSWAP) SwapInt32(&len); /* Check for data chunk */ if (strncmp("data", magic, 4) == 0) break; if (strncmp("fmt ", magic, 4) == 0) { fread(&type, 2, 1, f); // 十进制是1 线性的PCM编码 if (*ia & DoBSWAP) SwapShort(&type); if (type != WAVE_FORMAT_PCM && type != WAVE_FORMAT_MULAW && type != WAVE_FORMAT_ALAW) { HRError(6251, "Only standard PCM, mu-law & a-law supported"); return -1; } if (type == WAVE_FORMAT_MULAW) *ia = (InputAction)(*ia | (DoMULAW | DoCVT)); else if (type == WAVE_FORMAT_ALAW) *ia = (InputAction)(*ia | (DoALAW | DoCVT)); fread(&chans, 2, 1, f); /* Number of Channels */ if (*ia & DoBSWAP) SwapShort(&chans); if (chans != 1 && chans != 2) { HRError(6251, "Neither mono nor stereo!"); return -1; } if (chans == 2) *ia = (InputAction)(*ia | (DoSTEREO | DoCVT)); fread(&lng, 4, 1, f); /* Sample Rate */ if (*ia & DoBSWAP) SwapInt32(&lng); w->sampPeriod = 1.0E7 / (float)lng; fread(&lng, 4, 1, f); /* Average bytes/second */
- 调用GetWAVHeaderInfo读取wav文件头信息(编码方式、采样率、位深etc.),并返回音频数据的长度。
- OpenWaveInput函数
- OpenAsChannel调用OpenWaveInput函数,该函数的作用是打开输入的wav文件并读取文件信息。