SoX是语音识别领域常用的一个很强大的工具,我最近的一个项目中也应用了SoX工具对语音数据进行重采样,但每次都在程序里通过系统调用SoX,并且因为SoX的输入输出都是文件,所以处理每一条语音数据都要进行好几次读写文件,严重影响了性能。SoX作为开源工具,它提供了libsox库给我们用,省去了系统调用造成的开销,同时,也提供了内存作为输入输出的接口函数。
SoX的大致工作流程是输Input→Combiner→Effects→Output。我用到的核心就是Effect,需要首先指定输入输出格式和数据,然后构建效果器,并添加进效果器链,通过flow实现对语音的处理,最后输出。
我用C++写了一个函数,应用场景是对已解码的8K采样率、16bit采样深度、无文件头的raw格式语音数据进行重采样,将采样率变为16K,其余参数不变。目的是为了不在应用程序里进行系统调用,不写文件,以提高效率。
以下代码参考了SoX源码里的examples,单独写的Demo也能工作,集成进项目的工程里,Debug模式编译之后可以正常工作,但Release就不行了,可能是处理时序有差别,但是我通过堆栈调用完全看不出头绪……堆栈显示上一层调用就是这个Resample函数,目前还是毫无头绪。
#include "sox.h"
bool Resample (short* pWavBuf, int wavLen, short* pWav16k, int wavLen16k) {
//pWavBuf为输入的raw格式语音数据,采样率8K,采样深度16bit;
//wavLen为输入数据的长度,单位为样点数,并非数据长度的字节数
//pWav16k为输出的缓存
//wavLen16k为输出数据的长度,单位依然为样点数
assert(sox_init() == SOX_SUCCESS); //初始化,若已经初始化过未调用sox_quit时再次调用sox_init会出错
sox_format_t *in, *out; //sox format handlers
sox_effects_chain_t *chain; //效果器链
sox_effecta_t* e;
char* args[10]; //这个参数我没看懂,是直接抄的example,好像是跟option有关的
sox_signalinfo_t in_signal; //输入音频的格式信息,如果是raw格式数据必须指定signal,其他格式的可以从文件头自己提取
sox_encodinginfo_t in_encoding; //输入音频的编码信息
sox_signalinfo_t interm_signal; //效果器链的一个中间节点
sox_signalinfo_t out_signal; //指定的输出格式
sox_encodinginfo_t out_encoding; //指定的输出编码
//输入音频数据的信息
in_signal.rate = 8000; //采样率
in_signal.channels = 1; //声道数
in_signal.precision = 16; //采样深度bits per sample
in_signal.length = wavLen; //数据的总长度,单位为样点数,长度 = 样点数 * 声道数(samples * channels),这里为单声道
in_signal.mult = NULL;
in_encoding.encoding = SOX_ENCODING_SIGN2; //编码格式
in_encoding.bits_per_sample = 16;
in_encoding.compression = 0;
in_encoding.reverse_bytes = sox_option_default;
in_encoding.reverse_nibbles = sox_option_default;
in_encoding.reverse_bits = sox_option_default;
in_encoding.opposite_endian = sox_false;
//输入数据
assert(in = sox_open_mem_read(pWavBuf, wavLen * 2, &in_signal, &in_encoding, "raw")); //参数分别为输入缓冲区,输入长度(字节数),已知的输入数据格式,输入数据编码,输入数据类型,有文件头的格式可自动识别,raw不可以。
wavLen16k = wavLen * 2; //输出数据长度(采样点数)
//输出数据的信息
out_signal = in->signal;
out_signal.rate = 16000; //重采样的采样率
out_signal.length = wavLen16k; //长度,单位为点数
out_encoding = in->encoding;
//指定输出
assert(out = sox_open_men_write(pWav16k, wavLen16k * 2, &out_signal, &out_encoding, "raw", NULL)); //参数定义类似sox_open_mem_read
//构建效果器链
chain = sox_create_effects_chain(&in_encoding, &out->encoding);
interm_signal = in->signal;
//添加效果器到效果器链
//输入
e = sox_create_effect(sox_find_effect("input"));
args[0] = (char*)in, assert(sox_effect_options(e, 1, args) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &in->signal) == SOX_SUCCESS);
free(e);
//修改采样率
e = sox_create_effect(sox_find_effect("rate"));
assert(sox_effect_options(e, 0, NULL) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &out->signal) == SOX_SUCCESS);
free(e);
//20200208增加dither效果,提高识别准确率。因为是16bit。
e = sox_create_effect(sox_find_effect("dither"));
assert(sox_effect_options(e, 0, NULL) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &out->signal) == SOX_SUCCESS);
free(e);
//输出
e = sox_create_effect(sox_find_effect("output"));
args[0] = (char*)out, assert(sox_effect_options(e, 1, args) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &out->signal) == SOX_SUCCESS);
free(e);
//flow
sox_flow_effects(chain, NULL, NULL);
fflush((FILE*)out->fp); //这里不知为何,如果不fflush,数据尾部会丢失一部分
//释放资源
sox_delete_effect_chain(chain);
sox_close(out);
sox_close(in);
sox_quit();
return true;
}
Release编译后执行到这个函数里总是执行到ffulsh时in和out的句柄就失效了,值变成了0,如果去掉fflush就是在close时变0,gdb也看不到是什么时候变化的。问题可能出在sox_open_mem_read和sox_open_mem_write上,但目前我找到的参考多数都是从文件读入并输出到文件的,没怎么见过直接操作内存的,也没见过我遇到的这个问题。如果有人有相关经验,望指点一二。
20210119更新
破案了,debug和release的问题可能是assert导致的,不知道是不是因为我没有加assert.h?我去掉了全部的assert就能运行了……最后还是看了这篇讲VS的文章受到的启发,之前一直没往那想……虽然是讲Windows环境的 https://blog.csdn.net/weixin_...
其实编译的时候警告信息也给出了提示,只是我没想到这层,输出信息显示好几个变量没有初始化,我还没看明白为啥,后来一对都是assert里面的,可能release模式下,又没引用assert.h,就根本没起作用……而单步又能跟进去,in和out什么都有值,我就没怀疑过这方面的问题。教训就是不要忽视任何警告信息!顺便我又清理了一下其他的警告。
20210412更新
最近使用相同架构的工程中,libsox重采样这部分却因为多线程不断出现段错误,关闭重采样即不出错,单线程也不出错,因此我怀疑多线程时有不安全的因素存在。看了sox的代码,全局变量似乎只是一些常值或者函数入口,我看不出来会导致共享内存出现问题。但又没什么别的思路,只好给调用libsox的部分加上了互斥锁,段错误是不再出现了,但依旧无法确认是共享内存导致的问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。