声网 Aogra 自研的编解码器 SOLO,已向所有音视频、WebRTC 开发者开源。本系列源码解读将讲解底层核心技术,并分享如何集成到自己的 WebRTC 应用中。本文为此系列的第一篇。本文作者:赵晓涵,声网 Agora 音频算法工程师。
SOLO 在 Silk 的基础上扩展了带宽扩展模块,用来分别处理低频信息(0-8kHz 采样部分)和高频信息(8-16kHz 采样部分),在编码端,两者使用两套耦合的分析编码系统进行码流生成。在解码端,利用低频信号和高频信息,SOLO 可以解码出宽带信号。SOLO 使用带宽扩展主要有两个原因,首先,带宽扩展可以让更多的码率分配到更重要的低频部分,提升编码效率;第二个原因是带宽扩展可以减少进入到信号分析模块的采样点数,从而减少信号分析部分的复杂度(之前需要分析全部的信号,现在只需要分析低频部分)。在减少了原有复杂度的前提下,SOLO 才能够在低频部分额外增加较多计算以选取最佳的多描述编码状态(详细内容可见下一篇源码解析),让编解码音质达到预期。
编码端
SOLO 编码端的大部分操作都是在下述函数中完成的:
SKP_int32 AGR_Sate_encode_process(
SATEEncCtl
*sateCtl,
/* I/O SATE Encoder state */
const
SKP_int16 *vin,
/* I input signal */
NovaBits
*bits,
/* I bitstream operator */
void
*skctrl,
void
*hbctrl,
SKP_int16 *nBytesOut
/* I encoded bits */
)
复制代码
首先,输入的 16kHz 采样率的语音帧会先进入到一个正交镜像滤波器组(QMF)里进行频带的划分:
void
AGR_Sate_qmf_decomp(
const
spx_word16_t
*xx,
/* I Input signal */
const
spx_word16_t
*aa,
/* I Qmf coefficients */
spx_word16_t
*y1,
/* O Output low band signal */
spx_word16_t
*y2,
/* O Output high band signal */
SKP_int32 N,
/* I frame size */
SKP_int32 M,
/* I Qmf order */
spx_word16_t
*mem,
/* I/O Qmf state */
SKP_int8 *stack
)
复制代码
该函数的输出的是两个时域帧,分别包含低频信息和高频信息。低频信息和高频信息会在后续分别进行处理,其中,低频信息会通过函数 SKP_Silk_SDK_Encode 进行分析和编码,这部分内容我们会在下一期 SOLO 代码解析里进行详细解读。
SKP_int SKP_Silk_SDK_Encode(
void
*encState,
/* I/O: State */
const
SKP_SILK_SDK_EncControlStruct *encControl,
/* I: Control structure */
const
SKP_int16 *samplesIn,
/* I: Input samples */
SKP_int nSamplesIn,
/* I: Number of samples */
SKP_uint8 *outData,
/* O: Encoded output */
SKP_int16 *nBytesOut
/* I/O: I: Max bytes O:out bytes */
)
复制代码
高频信息的编码以线性滤波分析为基础,同时为了减少码率,部分依赖于低频信号的残差信息,因此在进行高频信息编码之前,需要通过下述函数提取低频编码信息中的残差信息:
SKP_int SKP_Silk_SDK_Get_Encoder_Residue(
void
*encState,SKP_int32 *r )
复制代码
高频信息的分析和编码在函数 AGR_Bwe_encode_frame_FLP 中进行:
SKP_int32 AGR_Bwe_encode_frame_FLP(
AGR_Sate_HB_encoder_control_FLP *hbEncCtrl,
AGR_Sate_encoder_hb_state_FLP *psHBEnc,
NovaBits
*bits,
/* I bitstream operator */
SKP_float *high,
SKP_int32 *residue,
SKP_int16 *nBytesOut
/* I/O: Number of bytes in outData (input: Max bytes) */
)
复制代码
首先高频信息通过 AGR_Sate_find_HB_LPC_FLP 进行分析得到自身的 8 阶 LPC 系数,并将其转化为编码误差较小的 LSP 系数:
SKP_int32 AGR_Sate_find_HB_LPC_FLP(
AGR_Sate_encoder_hb_state_FLP *psEnc,
/* I/O Encoder state FLP */
AGR_Sate_HB_encoder_control_FLP *hbEncCtrl,
/* I/O HB Encoder control FLP */
SKP_int32 hb_subfr_length,
/* I subframe length */
SKP_int32 hb_lpc_order,
/* I high band lpc order */
SKP_int32 first
/* I */
)
复制代码
随后通过 AGR_Sate_lsp_quant_highband 进行双码本量化:
SKP_int32 AGR_Sate_lsp_quant_highband(
SKP_float *lsp,
/* I/O lsp coefficients */
SKP_int32 order
/* I lpc order */
)
复制代码
量化后,编码器会将 LSP 系数转化为 1 个 index 来表示:
idx1
=
lsp_weight_quant
(
qlsp
,
quant_weight1
,
AGR_Sate_highband_lsp_cdbk1
,
HB_LSP_CB1
,
order
);
idx2
=
lsp_weight_quant
(
qlsp
,
quant_weight2
,
AGR_Sate_highband_lsp_cdbk2
,
HB_LSP_CB2
,
order
);
idx
=
(
idx2
<<
8
)+
idx1
;
复制代码
随后,该帧被分为 4 个子帧,计算各个子帧的残差信号,并计算其对应窄带残差信号子帧的增益,共计 4 个,使用单码本量化。量化后的 LSP index 和 gain 使用下述函数写入独立码流。
void
AGR_Sate_bits_pack(
NovaBits
*bits,
int
data,
int
nbBits)
复制代码
其中,LSP index 使用 12 bits 编码,每个子帧 gain 使用 5 bits 编码,所以高频信息的码流共计 12+4*5=32 bits,即 4 bytes,该段码流位于窄带码流之后,和窄带码流中的第二组多描述码流绑定在一起组包。
解码端
解码器可以看做编码器的镜像,解码器收到码流后,首先会通过下述函数解码得到 0-8kHz 采样率的低频信息,这部分也会在下一期 SOLO 代码解析里进行详细解读。
SKP_int SKP_Silk_SDK_Decode(
void
* decState,
/* I/O: State */
SKP_SILK_SDK_DecControlStruct* decControl,
/* I/O: Control structure */
SKP_int lostFlag,
/* I: 0: no loss, 1 loss */
const
SKP_uint8 *inData,
/* I: Encoded input vector */
const
SKP_int16 nBytesIn[],
/* I: Number of input Bytes */
SKP_int16 *samplesOut,
/* O: Decoded output */
SKP_int16 *nSamplesOut
/* I/O: Number of samples */
)
复制代码
随后,解码器通过下述函数得到低频残差信息以用来解码 8-16kHz 的高频信息。
SKP_int SKP_Silk_SDK_Get_Decoder_Residue(
void
*decState, SKP_int32 *r)
复制代码
同时,解码器会使用以下函数来进行高频信息的解码。
SKP_int32 AGR_Bwe_decode_frame_FLP(
AGR_Sate_HB_decoder_control_FLP *hbDecCtrl,
AGR_Sate_decoder_hb_state_FLP *psHBDec,
NovaBits
*bits,
/* I bitstream operator */
SKP_float *
OutHigh
,
SKP_int32 *residue_Q10,
SKP_int32 lostflag
/* I lost falg */
)
复制代码
该函数内的处理整体上可以分成两种 case,第一种是没有正常接收到包含高频信息的多描述码流,这种情况下会复用上一帧解码出的 LSP index 和子帧增益;如果正常接收到了包含高频信息的多描述码流,则会从 4 bytes 的高频信息中解码、反量化出所需的 LPC 滤波器系数和 4 个子帧增益。
恢复高频信号使用的残差信号是乘上子帧增益后的低频残差信号。使用高频残差再加上高频 LPC 系数,通过以下函数就可以解码得到高频信号。
void
AGR_Sate_LPC_synthesizer(
SKP_float *output,
/* O output signal */
SKP_float *ipexc,
/* I excitation signal */
SKP_float *sLPC,
/* I/O state vector */
SKP_float *a_tmp,
/* I filter coefficients */
SKP_int32 LPC_order,
/* I filter order */
SKP_int32 subfr_length
/* I signal length */
)
复制代码
随后,低频信息和高频信息会进入到以下函数中,进行高低频的合成,函数输出的是 16kHz 采样率的宽带信号。
void
AGR_Sate_qmf_synth(
const
spx_word16_t
*x1,
/* I Low band signal */
const
spx_word16_t
*x2,
/* I High band signal */
const
spx_word16_t
*a,
/* I Qmf coefficients */
spx_word16_t
*y,
/* O Synthesised signal */
SKP_int32 N,
/* I Signal size */
SKP_int32 M,
/* I Qmf order */
spx_word16_t
*mem1,
/* I/O Qmf low band state */
spx_word16_t
*mem2,
/* I/O Qmf high band state */
SKP_int8 *stack
)
复制代码
至此,解码端就完成了将窄带信号扩展成宽带信号的操作。
本文转载自 声网 Agora 公众号。
原文链接:https://mp.weixin.qq.com/s/maJjXYCzmcqyXWToIB1N_g
评论