天天看點

關于TS流的解析

TS即是"TransportStream"的縮寫。他是分包發送的,每一個包長為188位元組。在TS流裡可以填入很多類型的資料,如視訊、音頻、自定義資訊等。他的包的結構為,標頭為4個位元組,負載為184個位元組(這184個位元組不一定都是有效資料,有一些可能為填充資料)。

工作形式:

因為在TS流裡可以填入很多種東西,是以有必要有一種機制來确定怎麼來辨別這些資料。制定TS流标準的機構就規定了一些資料結構來定義。比如: PSI(ProgramSpecific Information)表,是以解析起來就像這樣: 先接收一個負載裡為PAT的資料包,在整個資料包裡找到一個PMT包的ID。然後再接收一個含有PMT的資料包,在這個資料包裡找到有關填入資料類型的ID。之後就在接收到的TS包裡找含有這個ID的負載内容,這個内容就是填入的資訊。根據填入的資料類型的ID的不同,在TS流複合多種資訊是可行的。關鍵就是找到辨別的ID号。

現在以一個例子來說明具體的操作:

在開始之前先給出一片實際TS流例子:

0000f32ch: 47 40 00 17 00 00 B0 0D 00 01 C1 00 00 00 01 E0 ; G@....?..?...?

0000f33ch: 20 A2 C3 29 41 FF FF FF FF FF FF FF FF FF FF FF ;  ⒚)A

0000f34ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f35ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f36ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f37ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f38ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f39ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f3ach: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f3bch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f3cch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 

0000f3dch: FF FF FF FF FF FF FF FF FF FF FF FF 47 40 20 17 ; G@ .

0000f3ech: 00 02 B0 1B 00 01 C1 00 00 E0 21 F0 00 1B E0 21 ; ..?..?.??.?

0000f3fch: F0 04 2A 02 7E 1F 03 E0 22 F0 00 5D 16 BD 48    ;?*.~..??].紿

具體的分析就以這個例子來分析。

// Adjust TS packet header

void adjust_TS_packet_header(TS_packet_header* pheader)

{

    unsigned char buf[4];

    memcpy(buf, pheader, 4);

   pheader->transport_error_indicator       = buf[1] >> 7;

    pheader->payload_unit_start_indicator    =buf[1] >> 6 & 0x01;

   pheader->transport_priority               = buf[1] >> 5 & 0x01;

   pheader->PID                           = (buf[1] & 0x1F) << 8 | buf[2];

    pheader->transport_scrambling_control    =buf[3] >> 6;

    pheader->adaption_field_control           = buf[3] >> 4 & 0x03;

   pheader->continuity_counter               = buf[3] & 0x03;

}

這是一個調整TS流資料標頭的函數,這裡牽扯到位段調整的問題。現在看一下TS流資料標頭的結構的定義:

// Transport packet header

typedef struct TS_packet_header

    unsignedsync_byte                       : 8;

    unsignedtransport_error_indicator        : 1;

    unsigned payload_unit_start_indicator    : 1;

    unsignedtransport_priority               : 1;

    unsignedPID                           : 13;

    unsigned transport_scrambling_control    : 2;

    unsignedadaption_field_control           : 2;

    unsignedcontinuity_counter               : 4;

} TS_packet_header;

下面我們來分析,在ISO/IEC 13818-1裡有說明,PAT(ProgramAssociation Table)的PID值為0x00,TS包的辨別(即sync_byte)為0x47,并且為了確定這個TS包裡的資料有效,是以我們一開始查找47 40 00這三組16進制數,為什麼這樣?具體的奧秘在TS包的結構上,前面已經說了sync_byte固定為0x47。現在往下看transport_error_indicator、payload_unit_start_indicator、transport_priority和PID這四個元素,PID為0x00,這是PAT的辨別。transport_error_indicator為0,transport_priority為0。把他們看成是兩組8位16進制數就是:4000。現在看看我們的TS流片斷例子,看來正好是47

40 00開頭的,一個TS流的頭部占據了4個位元組。剩下的負載部分的内容由PID來決定,例子看來就是一個PAT表。在這裡有個地方需要注意一下,payload_unit_start_indicator為1時,在前4個位元組之後會有一個調整位元組,它的數值決定了負載内容的具體開始位置。現在看例子中的資料47 40 00 17 00第五個位元組是00,說明緊跟着00之後就是具體的負載内容。

下面給出PAT表的結構體:

// PAT table

// Programm Association Table

typedef struct TS_PAT

    unsignedtable_id                       : 8;

    unsignedsection_syntax_indicator        : 1;

    unsigned zero                           : 1;

    unsignedreserved_1                       : 2;

    unsignedsection_length                   : 12;

    unsignedtransport_stream_id           : 16;

    unsignedreserved_2                       : 2;

    unsigned version_number                   : 5;

    unsignedcurrent_next_indicator           : 1;

    unsignedsection_number                   : 8;

    unsignedlast_section_number           : 8;

    unsignedprogram_number                   : 16;

    unsignedreserved_3                       : 3;

    unsignednetwork_PID                   : 13;

    unsignedprogram_map_PID               : 13;

    unsignedCRC_32                           : 32;

} TS_PAT;

再給出PAT表字段調整函數:

// Adjust PAT table

void adjust_PAT_table ( TS_PAT * packet, char * buffer )

    int n = 0, i = 0;

    int len = 0;

   packet->table_id                   = buffer[0];

    packet->section_syntax_indicator    =buffer[1] >> 7;

   packet->zero                       = buffer[1] >> 6 & 0x1;

   packet->reserved_1                   = buffer[1] >> 4 & 0x3;

   packet->section_length               = (buffer[1] & 0x0F) << 8 | buffer[2];   

   packet->transport_stream_id           = buffer[3] << 8 | buffer[4];

    packet->reserved_2                   = buffer[5] >> 6;

   packet->version_number               = buffer[5] >> 1 &  0x1F;

   packet->current_next_indicator        =(buffer[5] << 7) >> 7;

   packet->section_number               = buffer[6];

    packet->last_section_number           = buffer[7];

    // Get CRC_32

    len = 3 + packet->section_length;

   packet->CRC_32                       = (buffer[len-4] & 0x000000FF) << 24

                                         | (buffer[len-3] & 0x000000FF) << 16

                                         | (buffer[len-2] & 0x000000FF) << 8

                                         | (buffer[len-1] & 0x000000FF);

    // Parse network_PID or program_map_PID

    for ( n = 0; n < packet->section_length - 4; n ++ )

    {

        packet->program_number           = buffer[8] << 8 | buffer[9];

       packet->reserved_3               = buffer[10] >> 5;

        if ( packet->program_number ==0x0 )

           packet->network_PID = (buffer[10] << 3) << 5 | buffer[11];

        else

        {

           packet->program_map_PID = (buffer[10] << 3) << 5 | buffer[11];

        }

        n += 5;

    }

通過上面的分析,例子中的資料00 B0 0D 00 01 C1 0000 00 01 E0 20 A2 C3 29 41就是具體的PAT表的内容,然後根據PAT結構體來具體分析PAT表。但是我們需要注意的是在PAT表裡有program_number、network_PID的元素不隻有一個,這兩個元素是通過循環來确定的。循環的次數通過section_length元素的确定。在這個例子中program_map_PID為20,是以下面來PMT分析時,就是查找47 40 20的開頭的TS包。

下面來分析PMT表,先給出PMT(ProgramMap Table)的結構體:

// PMT table

// Program Map Table

typedef struct TS_PMT

    unsignedzero                           : 1;

    unsignedversion_number                   : 5;

    unsignedPCR_PID                       : 13;

    unsignedreserved_4                       : 4;

    unsignedprogram_info_length           : 12;

    unsignedstream_type                   : 8;

    unsignedreserved_5                       : 3;

    unsignedelementary_PID                   : 13;

    unsignedreserved_6                       : 4;

    unsignedES_info_length                   : 12;

} TS_PMT;

在給出調整字段函數:

// Adjust PMT table

void adjust_PMT_table ( TS_PMT * packet, char * buffer )

    int pos = 12, len = 0;

    int i = 0;

   packet->table_id                           = buffer[0];

    packet->section_syntax_indicator           = buffer[1] >> 7;

   packet->zero                               = buffer[1] >> 6;

   packet->reserved_1                           = buffer[1] >> 4;

   packet->section_length                       = (buffer[1] & 0x0F) << 8 | buffer[2];   

   packet->program_number                       = buffer[3] << 8 | buffer[4];

   packet->reserved_2                           = buffer[5] >> 6;

   packet->version_number                       = buffer[5] >> 1 & 0x1F;

    packet->current_next_indicator               = (buffer[5] << 7) >> 7;

   packet->section_number                       = buffer[6];

   packet->last_section_number                   = buffer[7];

   packet->reserved_3                           = buffer[8] >> 5;

    packet->PCR_PID                               = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;

   packet->reserved_4                           = buffer[10] >> 4;

   packet->program_info_length                   = (buffer[10] & 0x0F) << 8 | buffer[11];

    len = packet->section_length + 3;   

   packet->CRC_32               = (buffer[len-4] & 0x000000FF) << 24

                                 | (buffer[len-3] & 0x000000FF) << 16

                                 | (buffer[len-2] & 0x000000FF) << 8

                                 | (buffer[len-1] & 0x000000FF);

    // program info descriptor

    if ( packet->program_info_length != 0 )

        pos +=packet->program_info_length;   

    // Get stream type and PID   

    for ( ; pos <= (packet->section_length + 2 ) - 4; )

       packet->stream_type                           = buffer[pos];

       packet->reserved_5                           = buffer[pos+1] >> 5;

       packet->elementary_PID                       = ((buffer[pos+1] << 8) | buffer[pos+2]) & 0x1FFF;

       packet->reserved_6                           = buffer[pos+3] >> 4;

       packet->ES_info_length                       = (buffer[pos+3] & 0x0F) << 8 | buffer[pos+4];

        // Store in es

        es[i].type = packet->stream_type;

        es[i].pid =packet->elementary_PID;

        if ( packet->ES_info_length != 0)

            pos = pos+5;

            pos +=packet->ES_info_length;

            pos += 5;

        i++;

TS流可以複合很多的節目的視訊和音頻,但是解碼器是怎麼來區分的呢?答案就在PMT表裡,如其名節目映射表。他就是來解決這個問題的。現在看PMT結構體裡的stream_type、elementary_PID這兩個元素,前一個用來确定後一個作為辨別PID的内容具體是什麼,音頻或視訊等。還有要注意他們不隻有一個,是以他們是通過循環讀取來確定所有的值都被讀取了,當然循環也是有規定的(具體看調整函數上)。從例子上來看,我們在倒數第三行找到了上面分析來的PMT表的PID為0x20的TS包。然後就可以把資料是用調整函數填入結構中。然後得到具體節目的PID為視訊0x21,音頻0x22。

PS. 文章裡的PID是用來判斷具體TS包是什麼包的。分析每個包得到的PID值,都可以複合在TS頭部結構體的PID裡。

根據前一篇中各資料的定義及資料結構,對資料進行分别解析如下:

TS標頭定義:

unsigned sync_byte : 8; //同步位元組, 固定為0x47,表示後面的是一個TS分組

unsigned transport_error_indicator : 1; //傳輸誤碼訓示符

unsigned payload_unit_start_indicator : 1; //有效荷載單元起始訓示符

unsigned transport_priority : 1; //傳輸優先, 1表示高優先級,傳輸機制可能用到,解碼用不着

unsigned PID : 13; //PID

unsigned transport_scrambling_control : 2; //傳輸加擾控制

unsigned adaption_field_control : 2; //自适應控制 01僅含有效負載,10僅含調整字段,11含有調整字段和有效負載。為00解碼器不進行處理

unsigned continuity_counter : 4; //連續計數器 一個4bit的計數器,範圍0-15

TS標頭解析代碼:

HRESULT CTS_Stream_Parse::adjust_TS_packet_header( TS_packet_header*TS_header )

unsigned char buf[4];

memcpy(buf, TS_header, 4);

TS_header->transport_error_indicator = buf[1] >> 7;

TS_header->payload_unit_start_indicator = buf[1] >> 6 & 0x01;

TS_header->transport_priority = buf[1] >> 5 & 0x01;

TS_header->PID = (buf[1] & 0x1F) << 8 | buf[2];

TS_header->transport_scrambling_control = buf[3] >> 6;

TS_header->adaption_field_control = buf[3] >> 4 & 0x03;

TS_header->continuity_counter = buf[3] & 0x0F; // 四位資料,應為0x0F xyy 09.03.18

return 0;

如下為一個TS包資料:

0x47 0x40 0x00 0x12 0x00 0x00 0xb0 0x0d 0x00 0x00 0xc1 0x00 0x00 0x00 0x010xe3 0xe8 0xf0 0x0b 0xd7 0x79 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff

0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff

0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff

0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff

分析知道前四位0x47 0x40 0x00 0x12TS頭部即為TS標頭資料,解析如下:

sync_byte :0x47

transport_error_indicator: 0x00

payload_unit_start_indicator: 0x01

transport_priority : 0x00

PID :0x0000

transport_scrambling_control :0x00

adaptation_field_control :0x01

continuity_counter :0x02

PID = 0x0000,表示此TS包的内容為PSI資訊表格的PAT表格資料,在4位元組的TS標頭之後的第一個位元組的Point_field = 0x00, 表示偏移量為0,即緊随其後的即為PAT的資料資訊。

PAT表格定義如下:

typedef struct TS_PAT_Program

unsigned program_number :16; //節目号

unsigned program_map_PID :13; //節目映射表的PID,節目号大于0時對應的PID,每個節目對應一個

}TS_PAT_Program;

//PAT表結構體

unsigned table_id : 8; //固定為0x00 ,标志是該表是PAT

unsigned section_syntax_indicator : 1; //段文法标志位,固定為1

unsigned zero : 1; //0

unsigned reserved_1 : 2; // 保留位

unsigned section_length : 12; //表示這個位元組後面有用的位元組數,包括CRC32

unsigned transport_stream_id : 16; //該傳輸流的ID,差別于一個網絡中其它多路複用的流

unsigned reserved_2 : 2;// 保留位

unsigned version_number : 5; //範圍0-31,表示PAT的版本号

unsigned current_next_indicator : 1; //發送的PAT是目前有效還是下一個PAT有效

unsigned section_number : 8; //分段的号碼。PAT可能分為多段傳輸,第一段為00,以後每個分段加1,最多可能有256個分段

unsigned last_section_number : 8; //最後一個分段的号碼

std::vector<TS_PAT_Program> program;

unsigned reserved_3 : 3; // 保留位

unsigned network_PID : 13; //網絡資訊表(NIT)的PID,節目号為0時對應的PID為network_PID

unsigned CRC_32 : 32; //CRC32校驗碼

解析代碼如下:

HRESULT CTS_Stream_Parse::adjust_PAT_table( TS_PAT * packet, unsigned char* buffer)

packet->table_id = buffer[0];

packet->section_syntax_indicator = buffer[1] >> 7;

packet->zero = buffer[1] >> 6 & 0x1;

packet->reserved_1 = buffer[1] >> 4 & 0x3;

packet->section_length = (buffer[1] & 0x0F) << 8 | buffer[2];

packet->transport_stream_id = buffer[3] << 8 | buffer[4];

packet->reserved_2 = buffer[5] >> 6;

packet->version_number = buffer[5] >> 1 & 0x1F;

packet->current_next_indicator = (buffer[5] << 7) >> 7;

packet->section_number = buffer[6];

packet->last_section_number = buffer[7];

int len = 0;

len = 3 + packet->section_length;

packet->CRC_32 = (buffer[len-4] & 0x000000FF) << 24

| (buffer[len-3] & 0x000000FF) << 16

| (buffer[len-2] & 0x000000FF) << 8

| (buffer[len-1] & 0x000000FF);

int n = 0;

for ( n = 0; n < packet->section_length - 12; n += 4 )

unsigned program_num = buffer[8 + n ] << 8 | buffer[9 + n ];

packet->reserved_3 = buffer[10 + n ] >> 5;

packet->network_PID = 0x00;

if ( program_num == 0x00)

packet->network_PID = (buffer[10 + n ] & 0x1F) << 8 | buffer[11 +n ];

TS_network_Pid = packet->network_PID; //記錄該TS流的網絡PID

TRACE(" packet->network_PID %0x \n\n", packet->network_PID);

else

TS_PAT_Program PAT_program;

PAT_program.program_map_PID = (buffer[10 + n] & 0x1F) << 8 |buffer[11 + n];

PAT_program.program_number = program_num;

packet->program.push_back( PAT_program );

TS_program.push_back( PAT_program );//向全局PAT節目數組中添加PAT節目資訊

是以,PAT資料解析結果如下:

PAT資料

table_id :0x00 //8

section_syntax_indicator :0x01 // 1

'0' :0x00 // 1

reserved 0x03 // 2

section_length :0x00d // 12

transport_stream_id :0x0000 // 16

reserved :0x03 // 2

version_number :0x00 // 5

current_next_indicator :0x01 // 1

section_number :0x00 // 8

last_section_number :0x00 // 8

program_number :0x0001 // 16

reserved :0x07 // 3

program_map_PID :0x03e8 // 13

CRC :0x f0 0b d7 79

由解析結構可知,該PAT表格中沒有網絡資訊包資訊,隻包含一個節目,其PID為0x03e8

PMT結構定義:

typedef struct TS_PMT_Stream

unsigned stream_type : 8; //訓示特定PID的節目元素包的類型。該處PID由elementary PID指定

unsigned elementary_PID : 13; //該域訓示TS包的PID值。這些TS包含有相關的節目元素

unsigned ES_info_length : 12; //前兩位bit為00。該域訓示跟随其後的描述相關節目元素的byte數

unsigned descriptor;

}TS_PMT_Stream;

//PMT 表結構體

unsigned table_id : 8; //固定為0x02, 表示PMT表

unsigned section_syntax_indicator : 1; //固定為0x01

unsigned zero : 1; //0x01

unsigned reserved_1 : 2; //0x03

unsigned section_length : 12;//首先兩位bit置為00,它訓示段的byte數,由段長度域開始,包含CRC。

unsigned program_number : 16;// 指出該節目對應于可應用的Program map PID

unsigned reserved_2 : 2; //0x03

unsigned version_number : 5; //指出TS流中Program map section的版本号

unsigned current_next_indicator : 1; //當該位置1時,目前傳送的Program map section可用;

//當該位置0時,訓示目前傳送的Program map section不可用,下一個TS流的Program map section有效。

unsigned section_number : 8; //固定為0x00

unsigned last_section_number : 8; //固定為0x00

unsigned reserved_3 : 3; //0x07

unsigned PCR_PID : 13; //指明TS包的PID值,該TS包含有PCR域,

//該PCR值對應于由節目号指定的對應節目。

//如果對于私有資料流的節目定義與PCR無關,這個域的值将為0x1FFF。

unsigned reserved_4 : 4; //預留為0x0F

unsigned program_info_length : 12; //前兩位bit為00。該域指出跟随其後對節目資訊的描述的byte數。

std::vector<TS_PMT_Stream> PMT_Stream; //每個元素包含8位, 訓示特定PID的節目元素包的類型。該處PID由elementary PID指定

unsigned reserved_5 : 3; //0x07

unsigned reserved_6 : 4; //0x0F

unsigned CRC_32 : 32;

解析代碼為:

HRESULT CTS_Stream_Parse::adjust_PMT_table ( TS_PMT * packet, unsigned char* buffer )

packet->zero = buffer[1] >> 6 & 0x01;

packet->reserved_1 = buffer[1] >> 4 & 0x03;

packet->program_number = buffer[3] << 8 | buffer[4];

packet->reserved_3 = buffer[8] >> 5;

packet->PCR_PID = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;

PCRID = packet->PCR_PID;

packet->reserved_4 = buffer[10] >> 4;

packet->program_info_length = (buffer[10] & 0x0F) << 8 |buffer[11];

// Get CRC_32

len = packet->section_length + 3;

int pos = 12;

// program info descriptor

if ( packet->program_info_length != 0 )

pos += packet->program_info_length;

// Get stream type and PID

for ( ; pos <= (packet->section_length + 2 ) - 4; )

TS_PMT_Stream pmt_stream;

pmt_stream.stream_type = buffer[pos];

packet->reserved_5 = buffer[pos+1] >> 5;

pmt_stream.elementary_PID = ((buffer[pos+1] << 8) | buffer[pos+2]) &0x1FFF;

packet->reserved_6 = buffer[pos+3] >> 4;

pmt_stream.ES_info_length = (buffer[pos+3] & 0x0F) << 8 |buffer[pos+4];

pmt_stream.descriptor = 0x00;

if (pmt_stream.ES_info_length != 0)

pmt_stream.descriptor = buffer[pos + 5];

for( int len = 2; len <= pmt_stream.ES_info_length; len ++ )

pmt_stream.descriptor = pmt_stream.descriptor<< 8 | buffer[pos + 4 +len];

pos += pmt_stream.ES_info_length;

pos += 5;

packet->PMT_Stream.push_back( pmt_stream );

TS_Stream_type.push_back( pmt_stream );

舉例如下:

0x47 0x43 0xe8 0x12 0x00 0x02 0xb0 0x12 0x00 0x01 0xc1 0x00 0x00 0xe3 0xe90xf0 0x00 0x1b 0xe3 0xe9 0xf0 0x00 0xf0 0xaf 0xb4 0x4f 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff

TS頭部

PID :0x03e8

PMT資料               

table_id :0x02 // 8

section_syntax_indicator :0x01 // 1

'0' :0x00 // 1

reserved :0x03 // 2

section_length : 0x012 // 12

program_number :0x00 01 // 16

version_number :0x00 // 5

current_next_indicator 0x01 // 1

section_number :0x00 // 8

last_section_number :0x00 // 8

reserved 0x07 // 3

PCR_PID :0x03 e9 // PCR(節目參考時鐘)所在TS分組的PID // 13

reserved :0x0f //4

program_info_length :0x000 // 12

stream_type :0x1b // 8

elementary_PID :0x03 e9 // 13//該節目中包括的視訊流,音頻流等對應的TS分組的PID

reserved :0x0f // 4

ES_info_length :0x000 // 12

CRC : 0xf0 af b4 4f

下一篇: 條件接收(CA)

繼續閱讀