开源编解码器 SOLO 源码解读(一):带宽扩展

avatar

声网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
)

至此,解码端就完成了将窄带信号扩展成宽带信号的操作。

如有疑问,可在 RTC 开发者社区与我们交流