Dolby AC-3 and EAC-3 bit 和 TS 封装格式

AC-3/EAC-3文档

AC-3 语法

bit stream 语法 :
AC-3流由一连串的同步帧(synchronization frames)组成。 如下图:

每个同步帧包含6个编码的音频块(audio blocks: AB), 每个AB 表示每个channel 256个新的audio sample。

  • 同步帧由同步信息(synchronization information: SI)开始,其包含了获取和维护同步的必要信息。
  • bit stream information(BSI)跟随SI,包含了描述编码音频服务的参数信息。
  • AB 后面可能跟着一个辅助信息块(Aux)。
  • 每个同步帧的最后是循环校验码(CRC),用于校验错误。SI头中有一个附加的CRC字,解码器可以选择(可选)使用它。

连续的audio bit stream 语法(类c语言)如下:

  AC-c_bitstream(){
    while(true){
        syncframe()
    }
  } /*end of AC-3 bit stream*/

AC-3 的 syncframe() 语法如下:

syncframe()
{
    syncinfo() ;
    bsi() ;
    for (blk = 0; blk < 6; blk++)
    {
    audblk() ;
    }
    auxdata() ;
    errorcheck() ;
} /* end of syncframe */

E-AC-3的syncframe()

syncframe()
{
    syncinfo() ;
    bsi() ;
    audfrm() ;
    for (blk = 0; blk < 6; blk++)
    {
    audblk() ;
    }
    auxdata() ;
    errorcheck() ;
} /* end of syncframe */
注意:E-AC-3 后向兼容 AC-3,但是反之则不行。

syncinfo: Synchronization Information

syncinfo(){
	bit(16) syncword
	bit(16) crc1
	bit(2) fscod
	bit(6) frmsizecod
}
  • syncword: 16 bits. 0x0B77 (0b0000 1011 0111 0111). 同步点

  • crc1 : 16 bits. Cyclic Redundancy Check 1(循环校验码)。其校验syncframe的前 5/8。

  • fscod :2 bits. Sample Rate Code.

fscod Sample Rate, kHz
00 48
01 44
10 32
11 reserved.(忽略当前 syncframe)

frmsizecod : 6 bits. Frame Size Code. 与 fscod一起用于确定下个同步点之前的长度(word为单位, 1 word == 16 bits). 参见AC-3/EAC-3文档 Table 5.18.

bsi: Bit Stream Information

bsi()
{
    bit(5) bsid
    bit(3) bsmod
    bit(3) acmod
    bit(2) if ((acmod & 0x1) && (acmod != 0x1)) /* if 3 front channels */ {cmixlev}
    bit(2) if (acmod & 0x4) /* if a surround channel exists */ {surmixlev}
    bit(2) if (acmod == 0x2) /* if in 2/0 mode */ {dsurmod}
    bit(1) ifeon
    bit(5) dialnorm
    bit(1) compare
    bit(8) if(compare){compr}
    bit(1) langcode
    bit(8) if(langcode){langcod}
    
    bit(1) audprodie
    if(audprodie){
    	bit(5) mixlevel
    	bit(2) roomtyp
    }
    
    if(acmod == 8){/* if 1+1 mode (dual mono, so some items need a second value) */
    	bit(5) dialnorm2
    	bit(1) compr2e
    	bit(8) if(compr2e){compr2}
    	bit(1) langcod2e
    	bit(8) if(langcod2e){langcod2}
    	
    	bit(1) audprodi2e
    	if(audprodi2e){
    		bit(5) mixlevel2
    		bit(2) roomtyp2
    	}    	
    }
    bit(1) copyrightb
    bit(1) origbs
    
    bit(1) timecod1e
    bit(14) if(timecod1e){ timecod1}
    
    bit(1) timecod2e
    bit(14) if(timecod2e){ timecod2}
    
    bit(1) addbsie
    if(addbsie){
    	bit(6) addbsil
    	bit( (addbsil+1)*8 ) addbsi
    }
} /*end of bsi*/
  • bsid: Bit Stream Identification, 5 Bits

stream_type是0x81的时候,该字段为'01000'(=8),除非该流是根据文档附件的标准构建的。该标准的附件定义了其他值所表示的含义,以及与为解码bsid = 8的流而构建的解码器的兼容程度。因此,如果“ bsid”的值大于8(除非该解码器是按照附件E的可选规定构建的),则按照本标准构建的解码器应静音,并且如果“ bsid”的值小于或等于8应解码并再现音频。

  • bsmod: Bit Stream Mode, 3 Bits
    服务类型

    bsmod acmod Type of Service
    000 any main audio service: complete main (CM)
    001 any main audio service: music and effects (ME)
    010 any associated service: visually impaired (VI)
    011 any associated service: hearing impaired (HI)
    100 any associated service: dialogue (D)
    101 any associated service: commentary (C)
    110 any associated service: emergency (E)
    111 001 associated service: voice over (VO)
    111 010-111 main audio service: karaoke
    • acmod: Audio Coding Mode, 3 Bits
      表明使用了哪个main service channel (range 3/2 to 1/0)。如果 acmod的msb是1,则表示使用了环绕channel 且surminlev 随后出现在 bit stream中。反之则未使用和未出现。如果acmod的lsb是0,表示center channel 未被使用;为1则使用。Note: acmod的状态设置了满带宽channel 参数的数量, nfchans(e.g. 对于3/2 模式,nfchans = 5; 对于 2/1 模式, nfchans = 3; etc.)。nchans, 是全部channel的数量,等于 lfe channel 关闭时的 nfchans,等于 lfe channel 打开时的1 + nfchans. 如果 acmod为0, 两个互相独立的program channel(dual mono)被编码到一个 bit stream中,作为 Ch1, Ch2. 在这种情况下,一些附加项会出现在BSI或者 audblk中以完全描述Ch2.
    acmod Audio Coding Mode nfchans Channel Array Ordering
    000 1+1 2 Ch1, Ch2
    001 1/0 1 C
    010 2/0 2 L, R
    011 3/0 3 L, C, R
    100 2/1 3 L, R, S
    101 3/1 4 L, C, R, S
    110 2/2 4 L, R, SL, SR
    111 3/2 5 L, C, R, SL, SR
  • cmixlev: Center Mix Level, 2 Bits
    前置声道使用时,如表. 说明了名义上的 相对于左右声道而言的中央声道的down mix level。如果cmixlev设置为reserved代码,则解码器仍应重现音频。 在这种情况下,可以使用cmixlev的中间值(-4.5 dB)。

    cmixlev clev
    00 0.707 (–3.0 dB)
    01 0.595 (–4.5 dB)
    10 0.500 (–6.0 dB)
    11 reserved
  • surmixlev: Surround Mix Level, 2 Bits
    如果使用环绕声道,则下表所示的2位代码表示环绕声道的标称下混音电平。 如果将surmixlev设置为reserved代码,则解码器仍应重现音频。 在这种情况下,可以使用surmixlev的中间值(–6 dB)。

    surmixlev clev
    00 0.707 (–3.0 dB)
    01 0.500 (–4.5 dB)
    10 0
    11 reserved
  • dsurmod: Dolby Surround Mode, 2 Bits

    如下表,在2通道模式下工作时,此2位代码指示该程序是否已在Dolby Surround中进行了编码。 该信息不会被AC-3解码器使用,但可能会被音频再现设备的其他部分使用。 如果将dsurmod设置为reserved代码,则解码器仍应重现音频。 保留的代码可以被解释为“未指示”。
    dB)。

    dsurmod Indication
    00 not indicated
    01 Not Dolby Surround encoded
    10 Dolby Surround encoded
    11 reserved
  • lfeon: Low Frequency Effects Channel on, 1 Bit

​ lfe (sub woofer) channel on, 此值为1; 否则为0

  • dialnorm: Dialogue Normalization, 5 Bits

    表示平均对话级别低于数字100%的程度。 有效值为1–31。 值为0保留。 相对于数字100%,值1到31被解释为-1 dB到-31dB。如果接收到保留值0,则解码器应使用–31 dB。 dialnorm的值将影响声音的再现水平。 如果该值未被AC-3解码器本身使用,则该值应由音频再现设备的其他部分使用。

  • compre: Compression Gain Word Exists, 1 Bit
    如果为1, 接下来8 bit 表示compression control word.

  • compr: Compression Gain Word, 8 Bits

    该编码器生成的增益字可能出现在比特流中。 如果是这样,则可以使用它来缩放所再现的音频电平,以便再现非常窄的动态范围,并确保单声道缩混中瞬时峰值再现信号电平的上限

  • langcode: Language Code Exists, 1 Bit
    为1, 接下来8 bit 表示 langcod 出现。

  • langcod: Language Code, 8 Bits
    这是一个8位保留值,如果存在,应将其设置为0xFF。

  • audprodie: Audio Production Information Exists, 1 Bit
    为1, mixlevelroomtyp 字段存在。指示有关音频制作环境(混音室)的信息

  • mixlevel: Mixing Level, 5 Bits
    表示在最终音频混合会话期间单个通道的绝对声压级。其是一个介于0到31之间的值。峰值混合电平为80加上混合电平dB SPL的值或80至111 dB SPL。 峰值混合电平是单个通道中正弦波的声级,其峰值在PCM表示中达到100%。相对于峰值RMS正弦波电平,绝对SPL值通常是通过粉红噪声来测量的,RMS值为-20或-30 dB。 混合电平的值通常不在AC-3解码器中使用,但可以由音频再现设备的其他部分使用。

  • roomtyp: Room Type, 2 Bits
    表示用于最终音频混合会话的混合室的类型和校准。 Roomtyp的值通常不由AC-3解码器使用,但可以由音频再现设备的其他部分使用。 如果将roomtyp设置为reversed代码,则解码器仍应重现音频。 保留的代码可以被解释为“未指示”。

    roomtyp Type of Mixing Room
    00 not indicated
    01 large room, X curve monitor
    10 small room, flat monitor
    11 reserved
  • dialnorm2: Dialogue Normalization, ch2, 5 Bits
    dialnorm的含义相同,不同之处在于,当acmod指示两个独立的通道(双单声道1 + 1模式)时,它适用于第二个音频通道。

  • compr2e: Compression Gain Word Exists, ch2, 1 Bit
    以下8位代表Ch2的压缩增益word

  • compr2: Compression Gain Word, ch2, 8 Bits
    compr相同,不同之处在于,当acmod指示两个独立通道(双单声道1 + 1模式)时,它适用于第二个音频通道。

  • langcod2e: Language Code Exists, ch2, 1 Bit
    为1 则随后是langcod2, 否则不是

  • langcod2: Language Code, ch2, 8 Bits
    reserved。 应该置为0xFF

  • audprodi2e: Audio Production Information Exists, ch2, 1 Bit
    为1, 随后是表示 Ch2 的信息数据块

  • mixlevel2: Mixing Level, ch2, 5 Bits
    mixlevel一样的意义,不同之处在于,当acmod指示两个独立的通道(双单声道1 + 1模式)时,它适用于第二个音频通道。

  • roomtyp2: Room Type, ch2, 2 Bits
    roomtyp一样的意义,不同之处在于,当acmod指示两个独立的通道(双单声道1 + 1模式)时,它适用于第二个音频通道。

  • copyrightb: Copyright Bit, 1 Bit
    如果该位的值为“ 1”,则表明该位流中的信息受版权保护。 如果该信息未表示为受保护,则其值为“ 0”。

  • origbs: Original Bit Stream, 1 Bit
    如果这是原始位流,则该位的值为“ 1”。 如果这是另一个位流的副本,则此位的值为“ 0”。

  • timecod1e, timcode2e: Time Code (first and second) Halves Exist, 2 Bits
    如下表所示,这些值指示在位流中是否跟随时间码。 时间码的分辨率为一帧的1/64(一帧= 1/30的秒)。 由于只需要时间代码的高分辨率部分即可进行精细同步,因此28位时间代码分为两个14位半部分。

    timecod2e,timecod1e Time Code Present
    ‘0’ ‘0’ not present
    ‘0’ ‘1’ first half (14 bits) present
    ‘1’,’0’ second half (14 bits) present
    ‘1’,’1’ both halves (28 bits) present
  • timecod1: Time Code First Half, 14 Bits
    这个14位字段的前5位代表时间,以小时为单位,有效值为0-23。 接下来的6位以分钟为单位表示时间,有效值为0-59。 最后的3位以8秒为增量表示时间,有效值为0-7(表示0、8、16,... 56秒)。

  • timecod2: Time Code Second Half, 14 Bits
    这个14位字段的前3位代表时间,以秒为单位,有效值为0-7(代表0-7秒)。 接下来的5位以帧为单位表示时间,有效值为0-29。 最后的6位代表一帧的1/64的分数,有效值为0-63。

  • addbsie: Additional Bit Stream Information Exists, 1 Bit
    如果该比特的值为“ 1”,则还有其他比特流信息,其长度由下一个字段指示。 如果该位的值为“ 0”,则没有其他位流信息。

  • addbsil: Additional Bit Stream Information Length, 6 Bits
    仅当addbsie为“ 1”时,该6位代码才存在,它表示附加位流信息的字节长度。 addbsil的有效范围是0–63,分别指示1–64个附加字节。 不需要解码器解释此信息,因此解码器将跳过数据流中紧随其后的该字节数。

  • addbsi: Additional Bit Stream Information, [(addbsil+1) × 8] Bits
    该字段包含1到64字节的位流信息结构中包含的任何其他信息。

audblk: Audio Block

略了略了

Annex A: AC-3 Elementary Streams in the MPEG-2 Multiplex (AC-3用于 TS (ISO/IEC 13818-1)中)

​ 在TS中,AC-3 基本的比特流打包放在 PES 中。这种TS流在 System A 和 System B中被 STD模型约束。AC-3流必须被明确指出属于哪个流。由于MPEG-2 Systems没有为AC-3流指定代码,所以定义了 stream_type。这个值在不同的系统中会发生变化,下面给出了两个系统中的说明。又因为 MPEG-2 Systems 在 PSI 表中没有包含满足表述AC-3流内容的 audio descriptor,此附录旨在解决该问题。

​ AC-3 音频 access unit(AU) 或者 presentation unit(PU)是一个 AC-3 syncframe. 其包含了1536(256 x 6)个 audio sample.

Sample Rate, kHz duration of AU/PU, ms
48 32
44.1 34.83
32 48

为了在TS 中使用 AC-3, 下述几项需要明确:

  • stream_type

  • steam_id

  • AC-3 audio descriptor: In System A,AC-3_audio_stream_descriptor. In system B, AC- 3_descriptor. 这个描述符的语法在两个系统中是不同的

  • MPEG-2 registration descriptor

在能够复制具体帧同步的 multi-audio-stream场景下的一些强制要求被放置在PES层。

GENERIC IDENTIFICATION OF AN AC-3 STREAM

选择唯一标识多路复用中的AC-3流的方法是那些定义如何构建多路复用的人员的责任。 本节提供了为此目的使用MPEG-2 [1]注册描述符的标准方法。

如果使用MPEG-2注册描述符提供唯一标识,则format_identifier应为0x41432D33(“ AC-3”),如表所示:

Syntax No. of bits Mnemonic Value
registration_descriptor(){
descriptor_tag 8 uimsbf 0x05
descriptor_length 8 uimsbf 0x04
format_identifier 32 uimsbf 0x41432D33
}

请注意,系统A(ATSC)选择使用stream_type分配的值(请参阅下面的A4部分)来唯一地标识AC-3流,而系统B(DVB)选择使用所分配的descriptor tag(请参阅下面的A5部分)来 唯一标识AC-3流。

System A 细节规定

Stream Type

​ For AC-3,shall be 0x81

Stream ID

位于PES中的stream_id的值应为 0xBD (表示private_stream_1)。 多个 AC-3 流可共享一个stream_id, 因为每个流承载在TS数据包中,该数据包由该TS中的唯一PID值标识。每个流的PID与stream_type的关系在 PMT 中定义。

AC-3 Audio Descriptor

AC-3_audio_stream_descriptor应该实现下表。此描述符允许将有关单个AC-3基本流的信息包含在节目特定信息(PSI)表中。此信息有助于决定将当前广播中存在的适当的AC-3码流引导到音频解码器的决策,也有助于公布未来广播中包含的音频流的特性。注意,表中的横线表示该描述符的允许终止点,但须受使用该描述符的其他标准的限制。使用此描述符的标准规定了要使用哪些字段。

Syntax No. of Bits Mnemonic
AC-3_audio_stream_descriptor() {
descriptor_tag
descriptor_length
sample_rate_code
bsid
bit_rate_code
surround_mode
bsmod
num_channels
full_svc

8
8
3
5
6
2
3
4
1

uimsbf
uimsbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
langcod 8 bslbf
if(num_channel == 0) /* 1 + 1 mod*/
langcod2
8 bslbf
if(bsmod < 2){
mainid
priority
reserved
}
else asvcflags

3
2
3

8

uimsbf
bslbf
‘111’

bslbf
textlen
text_code
for(i = 0; i < M; i++){
text[i]
}
7
1

8
nimsbf
bslbf

bslbf

language_flag
language_flag_2
reserved
1
1
6
bslbf
bslbf
'111111'
if(language_flag == 1){
language
}

3*8

uimsbf
if(language_flag2 == 1){
language_2
}

3*8

uimsbf
for(i = 0; i < N; i++){
additional_info[i]
}

Nx8

bslbf

注: bslbf: bit string, left bit first ; uimsbf: unsigned integer, most significant bit first

  • descriptor_tag - 0x81 for AC-3

  • descriptor_length - 在descriptor_length之后的字节(byte)数

  • sample_rate_code – encoded audio的采样率。

    Sample_rate_code Sample Rate, kHz
    000 48
    001 44.1
    010 32
    011 Reversed
    100 48 or 44.1
    101 48 or 32
    110 44.1 or 32
    111 48 or 44.1 or 32
  • bsid — 与 AC-3 基本流中值相同

  • bit_rate_code — 低5位是象征性比特率。 MSB(最高位)为0表示比特率是准确的,为1则是上限。图参考 文档 TableA4.3

  • surround_mode — 与 AC-3中的dsurmod可能一样。

    Surround_mode Meaning
    00 Not indicated
    01 Not Dolby surround encoded
    10 Dolby surround encoded
    11 Reserved
  • bsmod — 与 AC-3中的bsmod一样。

  • num_channels — 表示channel 个数。当MSB 为0,低三位与AC-3中的acmod一样. 当 MSB为1,低三位表示编码音频信道最大的个数。 参考文档 TableA4.5

  • full_svc — 表示此audio service是否能够完整演示,或者此audio service是否是部分服务(在演示前需要另一个audio service)。1表示此audio service足够完整,并不需要与另一个audio service结合才能演示。0则相反。

  • langcod — Deprecated. 废弃。如果字段 langcod 出现,置为0xFF.

  • langcod2 — Deprecated. 废弃。如果字段 langcod2 出现,置为0xFF.

  • mainid — 3-bit 字段,包含了0-7之间的一位数,该数定义了一个主要的audio service。每个主要的audio service 应该打上唯一的tag。此值用作将关联服务与特定主要服务链接的标识符。

  • priority - 表明audio service的优先级。该字段允许一个main audio service(bsmod 等于1 或者0)被标记为primary audio service。其他audio service 可能不会被明确标识或指定。

    Bit Field Meaning
    00 reserved
    01 Primary Audio
    10 Other Audio
    11 Not specified
  • asvcflags - 这是一个8位字段。 每个位(0-7)指示此关联的服务与哪些主要服务相关联。 最左边的位(位7)指示是否可以与主要服务编号7一起再现此关联服务。如果此bit为1,此服务与main service 7 关联。如果是0,与7不关联。

  • textlen - 接下来text 字段相关的长度(byte为单位)

  • text_code- 表示后续text filed 如何编码。如果为1,则是使用了ISO Latin-1字母表编码为1字节的字符。如果为0, 则编码为2byte的Unicode 字符。

  • text[i] - 可能包含关于audio service的简单文字描述

  • language_flag - 1 bit 表示 language字段是否出现。1 为出现。 0 则反之。

  • language_flag_2 - 1 bit 表示 language2字段是否出现。1 为出现。 0 则反之。该bit应该始终设置为0,除非num_channels字段设置为'0000',这表示音频编码模式为1+1(dual mono)。

  • language - 使用的注册语言(ISO639-2)

  • language_2 - 只出现在音频编码模式为1+1(dual mono)的情况下。 使用的注册语言(ISO639-2)

  • additional_info[j] - 附加字节,尚未定义。提供该字段的目的是允许ATSC扩展此描述符,不允许做其他用途。

STD Audio Buffer Size

对于一个 MPEG-2 TS 而言,T-STD模型定义了 main audio buffer size BSn 为:BSn = BSmux + BSdec + BSoh
其中:
BSmux = 736 bytes
BSoh = PES 头部开销
BSdec = access unit buffer

ISO/IEC 13818-1 为BSn 定义了固定的值,3584 bytes. 表明任何多余的缓冲区都可以用于其他多路复用。当AC-3流被TS流承载,TS流有如下关系:
BSn = BSmux + BSdec + BSpad
其中,
BSmux = 736 bytes
BSpad = 64 bytes

所用的BSdec值应为系统支持的最高比特率的值。BSpad 的64 bytes 允许BSoh 和附加复用。该约束使得有可能以最小的可能的存储缓冲器来实现解码器。

SYSTEM B 细节规定

stream type

stream_type = 0x06(表明 PES 包含private data)

stream id

stream_id = 0xBD(表明 private_stream_1). 多个 AC-3 流可共享一个stream_id, 因为每个流承载在TS数据包中,该数据包由该TS中的唯一PID值标识。每个流的PID与stream_type的关系在 PMT 中定义。

service Information

AC-3_descriptor标识已根据本节进行编码的AC-3音频基本流。 预期目的是为解码器提供配置信息。
描述符通常位于PSIPMT中,并且在程序映射部分中针对包含AC-3的任何流,在相关ES_info_length字段之后使用一次。
描述符标签提供AC-3基本流存在的唯一标识。 描述符中的其他可选字段可用于提供对流中编码的AC-3音频的组件类型模式的标识(AC-3_type字段),并指示该流是否是主要的AC-3音频服务(mainid字段) 或关联的AC-3服务(asvc字段)。
描述符的最小长度为一个字节,但可能会更长,具体取决于标志的状态和其他信息循环。 表格中的水平线表示描述符的允许终止点,但要受到使用该描述符的其他标准的约束。

AC-3 Descriptor Syntax

AC-3描述符(根据表A5.1构造)应用于识别承载按系统B信号发送的AC-3音频的流。描述符通常位于相关ES_info_length字段后的程序映射部分中一次。

Syntax No. of Bits Mnemonic
AC-3_audio_stream_descriptor() {
descriptor_tag
descriptor_length
AC-3_type_flag
bsid_flag
mainid_flag
asvc_flag
reserved
reserved
reserved
reserved

8
8
1
1
1
1
1
1
1
1

uimsbf
uimsbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
bslbf
if(AC-3_type_flag == 1){
AC-3_type
}

8
uimsbf
if(bsid_flag == 1) {
bsid
}
8 uimsbf
if(mainid_flag == 1){
mainid
}

8
uimsbf
if(asvc_flag == 1){
asvc
}

8
uimsbf
for(i=0;i < N; i++){
additional_info[i]
}
Nx8 uimsbf
}
  • descriptor_tag - 0x6A for AC-3
  • descriptor_length - 后续字段的长度。最小为1 byte
  • AC-3_type_flag - 强制。如果包含可选 AC-3_type, 其应设置为1
  • bsid_flag - 强制。如果包含可选的bsid字段,其为1
  • mainid_flag - 强制。如果包含可选的mainid字段,其应为1
  • asvc_flag - 强制。 如果包含可选的asvc字段,其应为1
  • reserved flags - 留待未来使用。置为0
  • AC-3_type - 可选,8 bits 指定audio 类型
  • bsid - 可选, 8 bits, 前三个MSB应始终为0,剩余的5个LSB应该与AC-3基本流中的bsid字段一样,目前对于AC-3是‘01000’(=8)
  • mainid - 可选, 8 bits,表明一个 main audio service 且包含一个表明 main audio service的数值(在0-7之间)。每个main audio service的标签数字应唯一。该值用于将关联服务和特定的main service 连接的标识符。
  • asvc - 可选, 8 bits,每个位(0-7)标识此关联服务与哪些主要服务相关联。 最左边的位(位7)指示是否可以与主要服务编号7一起再现此关联的服务。如果该位的值为1,则该服务与主服务号7相关联。如果该位的值为0,则该服务与主服务号7不相关联。
  • additional_info - 可选bytes, 留待未来使用
STD Audio Buffer Size

The main audio buffer size (BSn)大小固定,为5696 bytes。更多参见ISO/IEC 13818-1。

原文地址:https://www.cnblogs.com/gardenofhu/p/12695964.html