由于想要分享的内容较多,为避免读者姥爷们失去看下去的耐心,分享将以连载的方式进行。感谢润和软件提供的硬件支持。
下期预告:OAT开源扫描和三方库上仓基本要求
本期为移植speexdsp到OpenHarmony标准系统
的第⑤期,主要内容如下:
speexdsp移植后已提交至openhamrony sig仓库:https://gitee.com/openharmony-sig/contest/tree/master/2022_OpenHarmony_thirdparty/speexdsp
七、Speexdsp功能分析
- 将speexdsp加入openharmony编译体系后,能成功编译出来动态链接库和测试用的可执行文件,并不代表移植三方库成功。还要在开发板上运行测试其功能是否正常。
speexdsp核心库分析
1.库实现方式
2.依赖分析
3.license以及版权
- 根据speex官网https://speex.org/的信息,speexdsp使用的开源协议为
revised BSD license
(revised BSD license
又称为3-clause BSD License
或者BSD 3-Clause License
,一般使用BSD 3-Clause)
- OpenHamorny第三方开源软件许可证类型必须是
OSI
明确定义的。
OSI
明确定义的开源许可证有:https://opensource.org/licenses/alphabetical
BSD 3-Clause License
是OSI
明确定义的开源许可。
如下类型许可证可以引入到OpenHarmony项目中:
Apache License 2.0
Mulan Permissive Software License, Version 2
BSD 2-clause
BSD 3-clause
DOM4J License
PostgreSQL License
Eclipse Distribution License 1.0
MIT
ISC
ICU
University of Illinois/NCSA
W3C Software License
zlib/libpng
Academic Free License 3.0
Python Software Foundation License
Python Imaging Library Software License
Boost Software License Version 1.0
WTF Public License
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
Zope Public License 2.0
如下类型许可证不建议引入到OpenHarmony项目中:
GNU GPL 1, 2, 3
GNU Affero GPL 3
GNU LGPL 2, 2.1, 3
QPL
Sleepycat License
Server Side Public License (SSPL) version 1
Code Project Open License (CPOL)
BSD-4-Clause/BSD-4-Clause (University of California-Specific)
Facebook BSD+Patents license
NPL 1.0/NPL 1.1
The Solipsistic Eclipse Public License
The "Don't Be A Dick" Public License
JSON License
Binary Code License (BCL)
Intel Simplified Software License
JSR-275 License
Microsoft Limited Public License
Amazon Software License (ASL)
Java SDK for Satori RTM license
Redis Source Available License (RSAL)
Booz Allen Public License
Creative Commons Non-Commercial
Sun Community Source License 3.0
Common Development and Distribution Licenses: CDDL 1.0 and CDDL 1.1
Common Public License: CPL 1.0
Eclipse Public License: EPL 1.0
IBM Public License: IPL 1.0
Mozilla Public Licenses: MPL 1.0, MPL 1.1, and MPL 2.0
Sun Public License: SPL 1.0
Open Software License 3.0
Erlang Public License
UnRAR License
SIL Open Font License
Ubuntu Font License Version 1.0
IPA Font License Agreement v1.0
Ruby License
Eclipse Public License 2.0: EPL 2.0
speexdsp的license以及版权内容如下:
Copyright 2002-2008 Xiph.org Foundation
Copyright 2002-2008 Jean-Marc Valin
Copyright 2005-2007 Analog Devices Inc.
Copyright 2005-2008 Commonwealth Scientific and Industrial Research
Organisation (CSIRO)
Copyright 1993, 2002, 2006 David Rowe
Copyright 2003 EpicGames
Copyright 1992-1994 Jutta Degener, Carsten Bormann
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4.最新一次版本
5.代码规模
linux系统提供了wc命令来统计文件的行数,统计当前目录下的所有文件行数,终端输入如下命令:
wc -l *
文件名 |
代码行数 |
arch.h |
232 |
bfin.h |
15 |
buffer.c |
176 |
fftwrap.c |
448 |
fftwrap.h |
58 |
filterbank.c |
227 |
filterbank.h |
66 |
fixed_arm4.h |
135 |
fixed_arm5e.h |
160 |
fixed_bfin.h |
141 |
fixed_debug.h |
497 |
fixed_generic.h |
106 |
jitter.c |
839 |
kiss_fft.c |
523 |
_kiss_fft_guts.h |
160 |
kiss_fft.h |
108 |
kiss_fftr.c |
297 |
kiss_fftr.h |
51 |
math_approx.h |
332 |
mdf.c |
1279 |
misc_bfin.h |
56 |
os_support.h |
169 |
preprocess.c |
1215 |
pseudofloat.h |
379 |
resample.c |
1239 |
resample_neon.h |
339 |
resample_sse.h |
128 |
scal.c |
293 |
smallft.c |
1261 |
smallft.h |
46 |
vorbis_psy.h |
97 |
speex_buffer.h |
68 |
speexdsp_config_types.h |
12 |
speexdsp_types.h |
126 |
speex_echo.h |
170 |
speex_jitter.h |
197 |
speex_preprocess.h |
219 |
speex_resampler.h |
343 |
总行数 |
12207 |
功能分析
可以参考speexdsp提供的文档分析功能
预处理器
预处理器被设计为在运行编码器之前在音频上使用。预处理器提供三个主要功能:
- 噪声抑制
- 自动增益控制(AGC)
- 语音活动检测(V AD)
自适应抖动缓冲区
- 当通过UDP或RTP传输语音(或任何相关内容)时,包可能会丢失,以不同的延迟到达,甚至乱序。
- 抖动缓冲区的目的是重新排序数据包,并缓冲足够长的时间,以便它们可以被发送以进行解码。
声学回声消除器
回声消除是为了提高远端质量。
- 在任何免提通信系统中,远端语音通过本地扬声器播放。音频在房间内传播,并被麦克风捕获。如果从麦克风捕获的音频被直接发送到远端,那么用户就会听到远端语音的回声。声学回声消除器设计用于在声学回声发送到远端之前消除它。
重采样器
这个重采样器可以用于在任意两个速率之间进行转换(比率必须是有理数),并且可以控制质量/复杂性的权衡。
- 重采样器在某些情况下将音频从一个采样率转换到另一个采样率。
- 它可以用于混合具有不同采样率的流、用于支持声卡不支持的采样率、用于转码等。
八、Speexdsp功能测试
测试逻辑
- 在speexdsp原生库的libspeexdsp目录下有原生的测试源文件testresample.c、testresample2.c、testecho.c、testdenoise.c、testjitter.c。
- 进行功能测试需要对比pc端和开发板的运行效果,因此需要在pc端编译出测试用的可执行文件(开发板上的已经编译出来了)。
重采样功能
- 查看源码可知,运行测试重采样率的可执行文件时,输入一份音频文件的同时需要指定处理后输出的音频文件。
- 测试重采样功能的源文件为testresample.c和testresample2.c。
testresample.c源码分析如下:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "speex/speex_resampler.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define NN 256
int main()
{
spx_uint32_t i;
short *in;
short *out;
float *fin, *fout;
int count = 0;
SpeexResamplerState *st = speex_resampler_init(1, 8000, 12000, 10, NULL);
speex_resampler_set_rate(st, 96000, 44100);
speex_resampler_skip_zeros(st);
in = malloc(NN*sizeof(short));
out = malloc(2*NN*sizeof(short));
fin = malloc(NN*sizeof(float));
fout = malloc(2*NN*sizeof(float));
while (1)
{
spx_uint32_t in_len;
spx_uint32_t out_len;
fread(in, sizeof(short), NN, stdin);
if (feof(stdin))
break;
for (i=0;i<NN;i++)
fin[i]=in[i];
in_len = NN;
out_len = 2*NN;
speex_resampler_process_float(st, 0, fin, &in_len, fout, &out_len);
for (i=0;i<out_len;i++)
out[i]=floor(.5+fout[i]);
fwrite(out, sizeof(short), out_len, stdout);
count++;
}
speex_resampler_destroy(st);
空,不然你的这个指针就会变成野指针)
free(in);
free(out);
free(fin);
free(fout);
return 0;
}
可以得知:
- 输入音频的采样率要求为96000Hz、声道为单声道。
- 输出音频的采样率为44100。
回声消除功能
- 运行测试回声消除的可执行文件时,需要输入两段音频文件,分别为一份麦克风的音频、一份speaker的音频。另外需要指定一份处理后输出的音频文件。采样率都为8000Hz
- 测试回声消除功能的源文件为testecho.c。
testecho.c源码分析如下:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "speex/speex_echo.h"
#include "speex/speex_preprocess.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define NN 128
#define TAIL 1024
int main(int argc, char **argv)
{
FILE *echo_fd, *ref_fd, *e_fd;
short echo_buf[NN], ref_buf[NN], e_buf[NN];
SpeexEchoState *st;
SpeexPreprocessState *den;
int sampleRate = 8000;
if (argc != 4)
{
fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n");
exit(1);
}
echo_fd = fopen(argv[2], "rb");
ref_fd = fopen(argv[1], "rb");
e_fd = fopen(argv[3], "wb");
·
st = speex_echo_state_init(NN, TAIL);
den = speex_preprocess_state_init(NN, sampleRate);
speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);
speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st);
while (!feof(ref_fd) && !feof(echo_fd))
{
fread(ref_buf, sizeof(short), NN, ref_fd);
fread(echo_buf, sizeof(short), NN, echo_fd);
speex_echo_cancellation(st, ref_buf, echo_buf, e_buf);
speex_preprocess_run(den, e_buf);
fwrite(e_buf, sizeof(short), NN, e_fd);
}
speex_echo_state_destroy(st);
speex_preprocess_state_destroy(den);
fclose(e_fd);
fclose(echo_fd);
fclose(ref_fd);
return 0;
}
预处理功能
- 运行测试预处理功能的可执行文件时,输入一份音频文件的同时需要指定处理后输出的音频文件。(采样率为8000Hz)
- 测试源文件testdenoise.c测试的是预处理功能中的噪声抑制。
testdenoise.c源码分析如下:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "speex/speex_preprocess.h"
#include <stdio.h>
#define NN 160
int main()
{
short in[NN];
int i;
SpeexPreprocessState *st;
int count=0;
float f;
st = speex_preprocess_state_init(NN, 8000);
i=1;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DENOISE, &i);
i=0;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC, &i);
i=8000;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC_LEVEL, &i);
i=0;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB, &i);
f=.0;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f);
f=.0;
speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f);
while (1)
{
int vad;
fread(in, sizeof(short), NN, stdin);
if (feof(stdin))
break;
vad = speex_preprocess_run(st, in);
fwrite(in, sizeof(short), NN, stdout);
count++;
}
speex_preprocess_state_destroy(st);
return 0;
}
抖动缓冲功能
当通过UDP或RTP传输语音(或任何相关内容)时,包可能会丢失,以不同的延迟到达,甚至乱序。
1.抖动缓冲区的目的是重新排序数据包,并缓冲足够长的时间,以便它们可以被发送以进行解码。
2.测试源文件testjitter.c,这个去抖动测试需要接收来自udp/rtp的网络语音数据,原生测试程序没有做到真正意义上的测试,从程序上看是告诉用户如何使用接口。
testjitter.c源码分析:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "speex/speex_jitter.h"
#include <stdio.h>
union jbpdata {
unsigned int idx;
unsigned char data[4];
};
void synthIn(JitterBufferPacket *in, int idx, int span) {
union jbpdata d;
d.idx = idx;
in->data = d.data;
in->len = sizeof(d);
in->timestamp = idx * 10;
in->span = span * 10;
in->sequence = idx;
in->user_data = 0;
}
void jitterFill(JitterBuffer *jb) {
char buffer[65536];
JitterBufferPacket in, out;
int i;
out.data = buffer;
jitter_buffer_reset(jb);
for(i=0;i<100;++i) {
synthIn(&in, i, 1);
jitter_buffer_put(jb, &in);
out.len = 65536;
if (jitter_buffer_get(jb, &out, 10, NULL) != JITTER_BUFFER_OK) {
printf("Fill test failed iteration %d\n", i);
}
if (out.timestamp != i * 10) {
printf("Fill test expected %d got %d\n", i*10, out.timestamp);
}
jitter_buffer_tick(jb);
}
}
int main()
{
char buffer[65536];
JitterBufferPacket in, out;
int i;
JitterBuffer *jb = jitter_buffer_init(10);
out.data = buffer;
jitterFill(jb);
for(i=0;i<100;++i) {
out.len = 65536;
jitter_buffer_get(jb, &out, 10, NULL);
jitter_buffer_tick(jb);
}
synthIn(&in, 100, 1);
jitter_buffer_put(jb, &in);
out.len = 65536;
if (jitter_buffer_get(jb, &out, 10, NULL) != JITTER_BUFFER_OK) {
printf("Failed frozen sender resynchronize\n");
} else {
printf("Frozen sender: Jitter %d\n", out.timestamp - 100*10);
}
return 0;
}
测试步骤
编译pc端上的测试用的可执行程序
编译pc端的可执行程序:
gcc testdenoise.c -L /home/jiajiahao/Desktop/speexdsp-SpeexDSP-1.2.1/build/lib -o testdenoise -I /home/jiajiahao/Desktop/speexdsp-SpeexDSP-1.2.1/include -lm -lspeexdsp
其中:
- gcc后面是测试用的源文件
- -L 后指定so库所在的文件夹
- -l+so库的名字(除去lib和后缀)
- -o 后面是可执行文件的名字,
- -l 后面是测试用的源文件要用到的头文件的路径
将编译生成的库以及测试用的可执行文件推送到开发板上
- hdc工具的使用 请参考用hdc工具在OpenHarmony3.2 上安装应用中的相关内容
- 获取了ohos源码,也可以执行
./build.sh --product-name ohos-sdk
编译sdk。
- 如果有这样的编译报错,说明依赖的库没在linux上安装完整。
- 安转完整才能成功编译。
sudo apt-get update
sudo apt-get install binutils binutils-dev git git- lfs gnupg flex bison gperf build-essential zip
curl zlib1g-dev gcc-multilib g+±multilib gcc-arm-linux-gnueabi libc6-dev-i386 libc6-dev-amd64 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev ccache libgl1-mesa-dev libxml2-utils
xsltproc unzip m4 bc gnutls-bin python3.8 python3-pip ruby genext2fs device-tree-compiler make
libffi-dev e2fsprogs pkg-config perl openssl libssl-dev libelf-dev libdwarf-dev u-boot-tools mtd-
utils cpio doxygen liblz4-tool openjdk-8-jre gcc g++ texinfo dosfstools mtools default-jre default-
jdk libncurses5 apt-utils wget scons python3.8-distutils tar rsync git-core libxml2-dev lib32z-dev
grsync xxd libglib2.0-dev libpixman-1-dev kmod jfsutils reiserfsprogs xfsprogs squashfs-tools
pcmciautils quota ppp libtinfo-dev libtinfo5 libncurses5-dev libncursesw5 libstdc++6 gcc-arm-none-
eabi vim ssh locales
1.通过与ohos版本匹配的hdc_std工具,将编译生成的库以及测试用的可执行文件推送到开发板上
hdc_std shell
mount -o remount,rw /
mkdir speexdsp
exit
2.将压缩包push到开发板
hdc_std file send speexdsp.tar /speexdsp
3.解压压缩包并将库文件拷贝到对应的目录
本次移植是基于openharmony标准系统3.2Beta1版本,是arm64位系统
hdc_std shell
cd speexdsp
tar -xvf speexdsp.tar
cp libspeexdsp_share.z.so /system/lib64/
执行测试程序、分析测试结果
分析测试结果需要使用到的音频处理软件是ocenaudio
,下载地址:https://www.ocenaudio.com/en/
①执行testresample可执行文件
通过分析testresample.c源码可知:执行测试程序时输入一份采样率为96000Hz并且为单声道的音频时,经过重采样输出的音频采样率为44100Hz。
在pc端运行:
输入的音频为input.pcm,把它拷贝到testresample同目录下,并且新建空白文档命名为output.pcm。
打开终端执行如下命令:
chmod 777 testresample
./testresample < input.pcm > output.pcm
使用重定向<
符号指定输入文件、>
符号指定输出文件
输出的output.pcm采样率变为44100Hz,音频的波形图
和声谱图
如下:
在rk3568上运行:
这里测试testresample时,将一份与pc端同样的input.pcm和output.pcm拷贝至开发板speexdsp目录。
在开发板speexdsp目录执行语句如下
chmod 777 testresample
./testresample < input.pcm > output.pcm
将开发板输出的output.pcm拷贝至pc端
hdc_std file recv /speexdsp/output.pcm C:\Users\jjh\Desktop\
测试结果:经过重采样,pc端和rk3568开发板输出的output.pcm音频采样率都为44100Hz,两者运行测试程序testresample结果一致。
②执行testresample2可执行文件
通过分析testresample2.c源码可知,执行测试程序时需要指定一份空白的单声道音频文件output.pcm。
测试testresample2时,需要把空白的单声道音频文件output.pcm拷贝至开发板speexdsp目录。
pc端运行:
执行语句如下:
./testresample2 > output.pcm
输出结果如下:
终端打印信息
输出音频output.pcm波形图和声谱图如下:
rk3568开发板上运行:
执行语句如下:
./testresample2 > output.pcm
测试结果:输出一份不为空的output.pcm音频文件,并且在终端输出文本如下:
1000 0 1024 22 -> 1024 0
1100 0 1024 24 -> 1024 15
1200 0 1024 26 -> 926 26
1300 926 1122 31 -> 1048 31
1400 950 1098 33 -> 1032 33
...
...
...
127600 1024 1024 2723 -> 1024 2723
...
...
127900 1024 1024 2729 -> 1024 2729
128000 1024 1024 2731 -> 1024 2730
以最后一行"128000 1024 1024 2731 -> 1024 2730“”为例,其中128000为采样率(Hz);1024为一个编码单元采样点数(帧);1024为输入音频理论帧长;2731为输出音频理论帧长。"->"符号后的1024为经过重采样处理输入音频实际帧长,2730为输出音频实际帧长。
- pc端和rk3568开发板运行testresample2可执行程序效果一致。
③执行testecho可执行文件
测试testecho时,需要输入两份音频文件,同时需要指定一份输出的音频文件。
输入的两份音频一份为speaker.wav(麦克风收录的说话人语音信号+在房间多径反射的语音),另一份为micin.wav(麦克风收录的房间多径反射的语音)。
- speaker.wav正常房间环境下收录说话人说话声音即可,mic2.pcm在正常环境收录时说话人不说话即可。
- 同时需要指定一份testecho_output.wav输出文件。
执行语句如下:
./testecho speaker.wav micin.wav testecho_output.wav
测试结果:对比输入的speaker.wav和输出testecho_output.wav的波形图和声谱图,回声已经被消除。pc端和rk3568开发板运行testecho可执行程序效果一致。
④执行testdenoise可执行文件
通过分析testdenoise.c源码,执行测试程序时需要指定一份输入的不为空的8000Hz的input.pcm音频,并且需要指定一份空的输出的output.pcm音频。
rk3568上运行:
执行语句如下:
./testdenoise < input.pcm > output.pcm
测试结果:对比输入的input.pcm和输出的outpu.pcm的波形图和声谱图,噪声已经被消除。pc端和rk3568开发板运行testdenoise可执行程序效果一致。
⑤执行testjitter可执行文件
通过分析testjitter.c源码,测试需要接收来自udp/rtp的网络语音数据,原生测试程序没有做到真正意义上的测试,从程序上看是告诉用户如何使用接口。
执行语句如下
./testjitter
测试结果:终端打印出语句
Frozen sender: Jitter 0
pc端和rk3568开发板运行testjitter可执行程序效果一致。
下期预告:OAT开源扫描和三方库上仓基本要求