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的部分加上了互斥锁,段错误是不再出现了,但依旧无法确认是共享内存导致的问题。


locking
4 声望1 粉丝