安卓端58直播SDK接入降噪方案6 l( y O: b) b
通过上章节的优缺点对比以及58直播中已经在使用了WebRTC相关代码逻辑,综合调研和处理结果验证工作之后,最终选择了WebRTC降噪方案。$ Z, `9 A$ v/ T7 T( T+ ?8 K, ?
. A7 r- H% C! ^5 l/ ], f7 I5 C; E- A
APM模块集成WebRTC降噪功能) D1 I& y$ N u6 k8 k) n- v' K
在58多媒体整体架构上选择把降噪模块单独解耦提取一个APM module,方便58视频编辑、58直播等需要降噪业务统一调用。对外暴露工具类AudioNoiseHelp方便业务接入。APM module的规划以后会接入更多的音频处理模块,现在已经接入降噪、增益模块。
" x- ?* a7 M9 [: i' {3 W由于58直播SDK支持音频采样率种类大于WebRTC支持的种类,因此需要对数据进行最优音频重采样处理。
( P# S& [! l; K' P# p/**
& G0 A% I w3 a9 a, M * 音频重采样: Q7 h* r- P) f0 x' e% G$ {
*
. e( F0 l4 w! V2 Y. X+ t * @param sourceData 原始数据
5 V* Y: x$ p: I1 X9 A2 X * @param sampleRate 原始采样率 U: d. z6 j8 k& ]& _
* @param srcSize 原始数据长度
2 W5 |) ^" U }' k1 q# o' { * @param destinationData 重采样之后的数据
- ^3 ?2 v3 {6 j& S * @param newSampleRate 重采样之后的数据长度 P# ?( `. `2 W4 _3 d1 d
*/: \4 U, E) S7 I; h4 H1 z) i
void resampleData(const int16_t *sourceData, int32_t sampleRate, uint32_t srcSize,' \- D" _( ^ q. R
int16_t *destinationData,int32_t newSampleRate){
_3 F) b) C3 b if (sampleRate == newSampleRate) {) l; y1 R& U/ M, i! B7 n; z
memcpy(destinationData, sourceData, srcSize * sizeof(int16_t));
3 [* o" L3 e3 J return;8 l' ~, t, d" \/ B' E% ~1 N6 ^
}. s2 j- d; f, {: j8 P7 k" D
1 [6 ^. r" O& r1 q1 O, `+ d
uint32_t last_pos = srcSize - 1;
: X9 l( S O2 V7 t6 @$ ]+ U/ @ uint32_t dstSize = (uint32_t) (srcSize * ((float) newSampleRate / sampleRate));$ K0 W4 n2 h" Q
for (uint32_t idx = 0; idx < dstSize; idx++) {$ P4 R) w1 R2 Q: G9 _; _
float index = ((float) idx * sampleRate) / (newSampleRate);5 ]1 W4 i! F2 X4 u% |2 q& p
uint32_t p1 = (uint32_t) index;
@6 Y: p+ U f0 } float coef = index - p1;
, ?7 }3 D% ?8 u' B uint32_t p2 = (p1 == last_pos) ? last_pos : p1 + 1;
$ G; P3 r2 r" t! X+ h destinationData[idx] = (int16_t) ((1.0f - coef) * sourceData[p1] + coef * sourceData[p2]);7 d, }; J6 [6 A3 m
}
' |6 C m) x0 I: F p t}: r, {$ g$ F( t$ t2 h3 v$ W
由于WebRTC只能处理320byte长度正整数倍的数据,但是58直播的音频采集数据在不同手机、不同采样率上得到的音频数据长度是不固定的,需要对数据进行切分处理。录音采集数据如果是byte格式的,假如长度是4096,那么直接把4096数据传入到WebRTC\_NS里处理会出现杂音出现,所以在交给WebRTC\_NS模块之前需要用个缓冲区来处理下,一次最多可以传入(4096/320)*320=3840长度数据,并且在数据处理完毕之后还需要用另外一个缓冲区来保证处理之后的长度仍然是4096个。. l. V5 G( w* Z D g4 ]- J
//部分逻辑代码如下所示:
& c2 i H4 {- R# y( G6 B//这个数据拆分和缓冲区数据逻辑可以由业务方自行出$ P: c- @1 Q; a' Y
/**( j' x" j" v- n# \' G' R
* 降噪处理方法,数据异步处理之后通过回调方法通知给调用方。5 D0 C1 L/ y8 v' d! ?/ M y& }
*, }4 L" O+ ?4 r
* @param bytes 音频数据
/ [; s A' |! v: K# ?' h1 h; V3 e1 j; M * @param nsProcessListener 异步方法回调
: E# q, R3 D5 O/ y" m */
) }5 S6 `0 }: [/ X5 q4 {* L% X& kpublic void webRtcNsProcess(byte[] bytes, INsProcessListener nsProcessListener) {
`+ S1 B7 z5 M if (isAudioNsInitOk) {
5 U9 J( V4 f8 q! f: D synchronized (TAG) {; M$ L9 ?5 }8 G( T8 }' Z
if (null == bytes || bytes.length == 0) {
% O% ~/ ?6 A% W/ A8 e; e3 }% p return;
( J; O |, ~* O2 ]/ t% P }8 E& ?; g/ _6 P7 n$ P& i
p: e X& I* V/ c9 } .... x5 m3 Q6 w, `8 `5 j+ c$ O
u+ @* [3 _$ G, c4 C+ {
int byteLen = bytes.length;
7 y7 N+ J- |9 c& s" { if (inByteLen != byteLen) {+ u( [! X3 `7 I( w2 e" ?) i
inByteLen = byteLen;
* ]+ N( g8 g7 q7 J$ ^ webRtcNsInit(byteLen);
$ G* s% n9 G7 z9 s4 r2 U }6 z' m3 C( |0 r; |0 m, ^
8 q3 d0 ]/ t' k# \6 K5 C( A# k1 a
int frames = byteLen / FRAME_SIZE;: T" T$ ^. f+ j9 y% R
int lastFrame = byteLen % FRAME_SIZE;
7 a; _" \& N$ K8 u+ X) o6 Y1 z int frameBufferLen = frames * FRAME_SIZE;
- E {$ N0 w9 g- w. l$ e" e/ i& { byte[] buf = new byte[frameBufferLen];
) H) P- p! W3 Y) L7 @9 X% j Log.d(TAG, "webRtcNsProcess inBufferSize:" + inBufferSize);
; u$ E3 w9 r: ` if (inBufferSize >= frameBufferLen) {" Z, U6 \+ q6 M# z
Log.d(TAG, "webRtcNsProcess mInByteBuffer full");. p/ \, T) q ?) t! Z, V6 b6 a2 Z
nsProcessInner(buf, nsProcessListener);
, p/ u; k4 i8 P6 {* c, O8 ? }
/ B. K3 L6 q5 ~4 f0 ^) Z( q2 ^+ b4 W: x* l; T
...& K( j# s; L* c" w
! Z4 i( O3 o" `6 l* \+ g3 o1 H
nsProcessInner(buf, nsProcessListener);
8 L+ p& `. P( E$ S1 C }
! \3 P# U, r8 X } else {8 p5 l7 W0 p* ]$ y7 ^
if (null != nsProcessListener) {
, a: j9 k6 U6 q' {, h# h: o+ x nsProcessListener.onProcess(bytes);* Z$ f# d. c2 i4 S/ {
}! f+ h( N$ e j) H/ w: C9 C+ K4 `* ^
} l1 r$ g3 Z* t' F
}$ I6 J0 d3 Z2 I
1 @) G4 _/ }! O! _8 C( \
private void nsProcessInner(byte[] buf, INsProcessListener nsProcessListener) {
" E: T( i; ?) O7 O; e) I mInByteBuffer.rewind();
& }$ M* s! n p% G) E0 E! L mInByteBuffer.get(buf, 0, buf.length);
Y. Z* J, {! }! t byte[] inBufferLeft = new byte[inBufferSize - mInByteBuffer.position()];
# Y# ], e. ]0 e; P0 j* {& D
3 a: }% z4 }7 u7 T ...
! B! g0 G' ` L1 N. C
( D6 |0 X- h& b! w% _ byte[] nsProcessData = AudioNoiseUtils.webRtcNsProcess(buf);
: h& P7 G5 F; ^+ r" k7 y( O byte[] outBuf = new byte[inByteLen];
4 b3 h; h" _+ b- T$ a
: w( d+ X$ }2 ? I8 b, n ...- J# A \& @( C; L2 c4 |0 P6 ]
L: f" x. s8 r4 E& K# T if (outBufferSize >= inByteLen) {
4 d2 e+ T8 q' I' M7 c) Z. i( d. J- z! S$ I! _
...
) ^1 M8 a: r6 F3 R
7 }2 M( _* u3 [$ b7 \0 @& e byte[] outBufferLeft = new byte[outBufferSize - mOutByteBuffer.position()];
0 f. ]! s7 y/ Q' q& o6 L
! M8 h/ w. z o/ H* p8 U1 s ...8 g! O$ x6 C9 }. A. e8 c
, ~ `/ T5 a: x! G" ?2 g mOutByteBuffer.put(outBufferLeft);) X' y( K+ {; I: ^$ h; |. x
outBufferSize += outBufferLeft.length;
# L1 A( X d' c$ o8 ^: K! L6 T if (null != nsProcessListener) {* V1 t4 G+ a n4 s. U e
nsProcessListener.onProcess(outBuf);
. t9 w1 z. v+ H0 T2 w) X1 F }
" y1 ~& M: T' X4 t6 e& B; l }4 v0 B/ F2 D8 _7 C F j7 I
}& \- s, W% Z. f# v
/ e! v3 F$ U. C6 g( B5 T. N5 @6 Z
|