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

声网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 开发者社区与我们交流

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容