H.264難點問題分析


2011年4月23日22:22:12

H.264編碼后碼流的生成

H.264 比較全的編碼框架

 

2011年4月23日22:23:35

H.264中的PB幀編碼

在針對連續動態圖像編碼時,將連續若干幅圖像分成P,B,I三種類型,P幀由在它前面的P幀或者I幀預測而來,它比較與它前面的P幀或者I幀之間的相同信 息或數據,也即考慮運動的特性進行幀間壓縮。P幀法是根據本幀與相鄰的前一幀(I幀或P幀)的不同點來壓縮本幀數據。采取P幀和I幀聯合壓縮的方法可達到 更高的壓縮且無明顯的壓縮痕跡。

在H.264編碼中,I幀是內部編碼幀,不需要參考其它幀,P幀需要前向的I幀作為參考,B是雙向預測幀,需要前 向和后向的I或者P幀作為其參考幀。由於幀與幀之間的參考關系比較復雜,彼此之間互相關聯,對幀編碼的簡單並行是行不通的。因此,尋找共用參考幀的可編碼 幀成為實現幀級並行的關鍵。

假設編碼序列中設置的B幀個數為2,其具體視頻編碼序列為IBBPBBPBBP…,依照I、P和B幀之間的參考關系, 可以把連續的視頻序列按照BBP的樣式分割成一個個單元序列。經過分析可以看出一個BBP單元序列中的兩個B幀由於共用前后的兩個P幀作為參考幀,可以實 施並行。同時,這個BBP單元序列中的P幀又作為下一個BBP單元序列中P幀的參考,因此前一單元序列中的兩個B幀加上下一個單元序列中的P幀就可以實施 三幀同時並行。以上是B幀設置參數為2時幀級並行的基本思路,可以得出原本執行一幀的時間現在可以用來執行三幀,理論加速比基本可以達到3,等於B幀設置 參數加1。

當設置編碼序列中B幀個數可變時,幀級並行的線程數取決於B幀的個數,B幀數越大,並行加速比越高,在處理器足夠的情況下,理論上獲得 的最大加速比等於B幀設置參數加1。由於在並行過程中,創建編碼線程需要分配內存,線程之間的數據傳輸也會消耗資源,實際加速比會小於理論加速比,再加上 處理器個數資源的限制,會在某個B幀的個數上獲得一個峰值加速比.

 

2011年4月23日22:26:49

H.264中運動矢量和多參考幀運動估計

一個宏塊中的運動矢量

每一個塊具有一個運動矢量,這句話我們可以這么理解, 塊的大小不定, 從16x16~4X4, 所以,最多可以有16個不同的MV,但是,所以對於一個宏塊肯定是攜帶16個MV, 只是可能其中一些MV是相同的.

多參考幀運動估計

 

2011年4月23日22:34:25

關於H264幀間色度塊預測的問題

老畢書上在第6章這么寫的:

所以幀間色度塊不用預測,

 

2011年4月23日22:36:14

JM86如何設置編碼器分片參數:

 

2011年4月23日22:43:52

關於SliceMode中的CALLBACK模式的解釋

 

2011年4月23日22:44:02

JM8.6中I幀,P幀的理解

通過下面的代碼我們可以發現

對於一幀, 如果設置這一幀進行幀內編碼,即intra=1,那么這一幀不會再進行幀間預測,這一幀即為I幀, 但是, 如果設置某一幀要進行幀間預測, 即intra=0,那么這個時候幀內預測和幀間預測都要進行, 從上面的代碼可以看出, 進行幀間預測是包含着一個if判斷語句中的, 而幀內預測是沒有條件的, 是任何情況下都要進行的. 之后jm代碼會比較幀內預測與幀間預測兩者之間的優劣, 選擇一種使率率失真函數最優的方法. 換句話說, 可以認為intra其實是一個標志(flag), 用來標示是否要進行幀間預測. 同樣我們也可以看到, 對於p幀中的宏塊, 幀內預測和幀間預測都要進行的, 最后也有可能使用幀內預測. 所以p幀內的宏塊包括幀內預測和幀間預測的宏塊.

對於上面需要一些更正

 

2011年4月20日15:16:43

JM8.6中對數據分割的一點解釋

分析currslice->partarr[partmap[SE_MVD]] (關於數據分割的實現)

Currslice指當前slice

Partarr是一個datapartition數組

Partmap :const int* partmap = assignse2partition[input->partition_mode];

Int * assignse2partition[2] ;

Static int assignse2partition_nodp[SE_MAX_ELEMENTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

Static int assignse2partition_DP[SE_MAX_ELEMENTS] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 2, 2, 2, 2, 0, 0, 0, 0 } ;

 

Typedef enum {

SE_HEADER,

SE_PTYPE,

SE_MBTYPE,

SE_REFFRAME,

SE_INTRAPREDMODE,

SE_MVD,

SE_CBP_INTRA,

SE_LUM_DC_INTRA,

SE_CHR_DC_INTRA,

SE_LUM_AC_INTRA,

SE_CHR_AC_INTRA,

SE_CBP_INTER,

SE_LUM_DC_INTER,

SE_CHR_DC_INTER,

SE_LUM_AC_INTER,

SE_CHR_AC_INTER,

SE_DELTA_QUANT_INTER,

SE_DELTA_QUANT_INTRA,

SE_BFRAME, SE_EOS,

SE_MAX_ELEMENTS //!< number of maximum syntax elements

} SE_type; // substituting the definitions in elements.h

從上面可以看出SE_type枚舉類型里定義了20種句法元素,若存在數據分割,則根據assignse2partition_DP數組來定義句法元素的重要性; 若不存在數據分割情況,則assignse2partition_nodp數組可知把這些句法元素歸於一類。

Partmap的作用是把當前的句法元素映射到某一種數據分割類型中,其值為0,1,2,這樣就可以形成一個partarr的datapartition數組

當沒有數據分割時,這些元素實際全存到了currslice->partarr[0]中。

 

 

2011年4月20日15:18:50

關於寫比特流的問題

 

通過上面的對比, 我們可以發現store_coding_state函數和reset_coding_state函數基本上完全一致, 對於cs_mb, store_coding_state函數將img->currentslice變量中的一些需要保存的量存儲在cs_mb中, 然后等到進行編碼完成后, 要恢復現場, 利用reset_coding_state函數將cs_mb中保存的相關量恢復到變量img->currentslice中, 便於下面的利用.

從上面的截圖我們也可以看出, 對於非CABAC編碼的狀況, 主要是保存的bitstream

而對於CABAC編碼的狀況, 還需要保存一些相關的上下文信息.

所以, 我們需要看看bitstream結構中所包含的一些量

 

從上面的截圖, 我們可以看到cs_mb和cs_b8應該分別是一個宏塊和一個8x8塊對應的一些bitstream信息, cs_cm更像一個中間量, 其他的量都沒有使用過. 一般cs_cm和cs_mb和cs_b8這些存儲當前編碼狀態的變量, 使用的主要原因就是在RDO方式下需要真正的進行一遍編碼, 所以需要保存一下當前狀態, 等代價計算完畢后,需要恢復現場, 再計算其他模式的代價.

 

If (valid[P8x8])

{

Cost8x8 = 0;

//===== store coding state of macroblock =====

Store_coding_state (cs_mb); //第一次出現

Store_coding_state

假設現在是第二幀, 現在用來存儲比特流的結構體里數據情況如下

Img->currentslice->partarr[0].bitstream->byte_pos 0x00000003

Img->currentslice->partarr[0].bitstream->bits_to_go 0x00000007

在這時候執行store_coding_state (cs_mb),把

Img->currentslice->partarr[0].bitstream的內容賦給了cs_mb->bitstream[0]

接着進入for (cbp8x8=cbp_blk8x8=cnt_nonz_8x8=0, block=0; block<4; block++)循環 1

進入for (min_cost8x8=(1<<20), min_rdcost=1e30, index=(bframe?0:1); index<5; index++)循環 2

在循環2里

執行函數rdcost_for_8x8blocks之前,

執行store_coding_state (cs_cm) 把img->currentslice->partarr[0].bitstream的內容賦給了cs_cm->bitstream[0]

 

執行rdcost_for_8x8blocks函數之后 //這個函數包含了編碼與寫比特到img->currentslice->partarr[0].bitstream中

Img->currentslice->partarr[0].bitstream->byte_pos 0x000000012

Img->currentslice->partarr[0].bitstream->bits_to_go 0x00000008

 

若本模式的率失真更小, 則store_coding_state (cs_b8), 把img->currentslice->partarr[0].bitstream的內容賦給了cs_b8->bitstream[0]

然后reset_coding_state (cs_cm); 把cs_cm->bitstream[0]內容賦給img->currentslice->partarr[0].bitstream 即保證每次比較8*8塊的率失真時,將比特流情況設置成

Img->currentslice->partarr[0].bitstream->byte_pos 0x00000003

Img->currentslice->partarr[0].bitstream->bits_to_go 0x00000007

(可以這樣看, 在進行rdcost_for_8x8blocks之前, 要保存一下當前的img->currentslice->partarr[0].bitstream到cs_cm中去, 因為在這個函數中有對img->currentslice->partarr[0].bitstream的修改, 所以從rdcost_for_8x8blocks函數返回后, 由於需要計算下一個P8x8模式的rdcost, 所以要恢復成之前的狀態, 這樣保證了對每種P8x8模式進行計算rdcost之前的img->currentslice->partarr[0].bitstream狀態是一樣的. 同時, 如果發現當前模式的rdcost比較小, 則需要保存一下bitstream, 保存在了cs_b8中)

 

由此可見循環2完成后, 本8*8塊的最佳比特流將存在cs_b8->bitstream[0]中(其實還包括slice頭部比特流和其他的前面的最佳8*8塊比特流)

循環2完成后,執行reset_coding_state (cs_b8);

將cs_b8->bitstream[0]內容賦給img->currentslice->partarr[0].bitstream, 保證在把下個8*8 的最佳比特流放在前一個8*8的最佳比特流之后

如此下來,1和2都循環完之后,一個宏塊的最佳編碼比特流就存到了img->currentslice->partarr[0].bitstream中

 

接下來在for (ctr16x16=0, index=0; index<7; index++)循環中

在這個循環之前,執行reset_coding_state (cs_mb); 把cs_mb->bitstream[0]的內容

賦給了img->currentslice->partarr[0].bitstream

此時

Img->currentslice->partarr[0].bitstream->byte_pos 0x00000003

Img->currentslice->partarr[0].bitstream->bits_to_go 0x00000007

即回到未寫宏塊比特流之前

 

(for (ctr16x16=0, index=0; index<7; index++)的循環里比較的是0, 1, 2, 3, P8x8, I16MB, I4MB這幾種模式

而P8*8的最佳rdcost在for (min_cost8x8=(1<<20), min_rdcost=1e30, index=(bframe?0:1); index<5; index++)里已經比較得到 所以for (ctr16x16=0, index=0; index<7; index++)它實際上是比較Skip,16*16,16*8,8*16,8*8,8*4,4*8,4*4,I4MB,I16MB這些模式的rdcost從而選擇出有最小rdcost的模式)

執行if (rdcost_for_macroblocks (lambda_mode, mode, &min_rdcost))

在rdcost_for_macroblocks (lambda_mode, mode, &min_rdcost)里對Skip,16*16,16*8,8*16模式編碼(每次循環對不同的模式編碼)

編碼完成之后, 寫比特流之前store_coding_state (cs_cm); 把

Img->currentslice->partarr[0].bitstream的內容賦給了cs_cm->bitstream[0] (a)

之后writembheader (1); writemotioninfo2nal ();writecbpandlumacoeff ();writechromacoeff ();此時

Img->currentslice->partarr[0].bitstream->byte_pos 0x000000037

Img->currentslice->partarr[0].bitstream->bits_to_go 0x00000003

即編完一個宏塊模式后的比特流情況

然后reset_coding_state (cs_cm);

把cs_cm->bitstream[0]內容賦給img->currentslice->partarr[0].bitstream (b)

由上可看出,實際上(a)和(b)操作時相反的,最后img->currentslice->partarr[0].bitstream里面實際上只存有slice頭部信息

所有store_coding_state和reset_coding_state操作都是為比較率失真而設置的,

在encode_one_macroblock 結束后,img->currentslice->partarr[0].bitstream里實際上只有slice頭部信息,但在這個函數里得到了最佳的宏塊編碼模式,

將宏塊編碼信息寫入img->currentslice->partarr[0].bitstream的是在write_one_macroblock函數

Writembheader (0);

// Do nothing more if copy and inter mode

If ((IS_INTERMV (currmb) || IS_INTRA (currmb) ) ||

((img->type==B_SLICE) && currmb->cbp != 0) )

{

Writemotioninfo2nal ();

Writecbpandlumacoeff ();

Writechromacoeff ();

}

實際上是用到的img-> mv[block_x][block_y][list_idx][refindex][mv_mode][X/Y]

在求每種分割模式的最佳運動矢量時,將他們保存到了best8x8fwref[mode][k]中,k=0~3 即best8x8fwref[mode][k]保存了每種模式的最佳參考幀

在對各種模式進行比較時

For (ctr16x16=0, index=0; index<7; index++)

If (valid[mode])

{

Setmodesandrefframeforblocks (mode);

 

Setmodesandrefframeforblocks

Enc_picture->ref_idx[LIST_0][img->block_x+i][img->block_y+j] = (IS_FW ? Best8x8fwref[mode][k] : -1);

將本模式的最佳參考幀賦值給enc_picture->ref_idx

 

在rdcost_for_macroblocks的lumaresidualcoding ()中,將會用到enc_picture->ref_idx求出預測值 [具體來說是利用enc_picture->ref_idx給出的參考幀,可以得到預測值]

store_macroblock_parameters (mode);中將率失真小的那個模式的最佳參考幀存到frefframe[j][i]中

而后有一個函數set_stored_macroblock_parameters ()

enc_picture->ref_idx[LIST_0][img->block_x+i][img->block_y+j] = frefframe[j][i];

也就是說最終的最佳模式的最佳參考幀就存到了enc_picture->ref_idx中

Enc_picture->mv[LIST_0][img->block_x+i][img->block_y+j][0] =

Img->all_mv[i][j][LIST_0][frefframe[j][i]][currmb->b8mode[i/2+(j/2)*2]][0]; enc_picture->mv[LIST_0][img->block_x+i][img->block_y+j][1] =

Img->all_mv[i][j][LIST_0][frefframe[j][i]][currmb->b8mode[i/2+(j/2)*2]][1];

最佳的運動矢量也存到了enc_picture->mv中

 

在write_one_macroblock()中每8x8塊的最佳模式是由currmb->b8mode[i]參數來傳遞的[具體說是函數writemotioninfo2nal()中調用的writereferenceframe的參數],這個參數的值是在void encode_one_macroblock ()中的 set_stored_macroblock_parameters函數中的

For (i=0; i<4; i++)

{

Currmb->b8mode[i] = b8mode[i];求得的,

而b8mode[i]是在

If (rdcost_for_macroblocks (lambda_mode, mode, &min_rdcost))

{

Store_macroblock_parameters (mode);

中記錄的那個具有最小的率失真的模式

 

Distortion的計算是綜合了色度和亮度的distortion, 色度部分的計算也是在rdcost_for_macroblocks函數和rdcost_for_8x8blocks函數中的,

但JM8.5中得到色度分量的分像素運動矢量時好像有點問題,在 onecomponentchromaprediction4x4函數中!!

色度分量的運動矢量不需要搜索判決, 是根據亮度分量的運動矢量乘以2得到的(JM85中好像沒乘2?)

色度分量的最佳模式是對應亮度分量的最佳模式除以2。 如亮度最佳模式是8*16,則色度最佳模式是4*8。

 

2011年4月24日9:28:20

2011年4月20日21:22:53

H.264中最優運動矢量殘差的輸出

最優運動矢量的求解是在encode_one_macroblock函數里面,因此該函數執行完畢運動矢量及分割模式也就相應的確定了,這里我們對這一塊作一下簡要的分析。

運動矢量的寫碼流是在write_one_macroblock里的,它的一個大致的函數調用關系是:

write_one_macroblock----->writeMotionInfo2NAL----->writeMotionVector8x8,就是這樣了,在writeMotionInfo2NAL里:


這里的兩個循環主要是遍歷一個宏塊里的大的分割塊,分割模式一共有1,2,3,(4,5,5,7)這些種,后四種又統稱為P8*8模式,每個宏塊的運動矢量數目是這樣的,模式1有一個運動矢量,模式2,3有兩個,P8*8則根據8*8子塊的分割模式細分。我們進writeMotionVector8x8函數去看看。


上面被選中的那一句就是求運動矢量殘差了,等號右邊的兩個也就是相應的最佳運動矢量和預測運動矢量,這里也有兩個循環,是循環P8*8模式里的小分塊的。

我們只要在這里把curr_mvd輸出來就能得到運動矢量殘差數據了,若想得到最優運動矢量則輸出等號右邊一項即可。

這里存在一個問題,如果在writeMotionVector8x8函數里直接輸出運動矢量信息當然也是可以的,但在encode_one_macroblock函數里進行模式判決的時候調用了RDCost_for_macroblock以及RDCost_for_8*8block,而這兩個函數里也分別調用了writeMotionInfo2NAL,因此在進行模式選擇時也把一些其它模式求解出的運動矢量殘差數據輸出來,這個問題怎么解決呢。

其實在函數RDCost_for_macroblock和函數RDCost_for_8*8block中調用writeMotionInfo2NAL后也進行過寫碼流的操作(主要是為了得到真正的編碼比特數), 但是從總體上看, 利用store_coding_state和reset_coding_state兩個函數, 其實最后還是恢復到沒有寫碼流之前的狀態.

 

2011年4月25日20:06:43

JM8.6中對MV的預測

BlockMotionSearch函數對不同幀間模式block type (1-16x16 ... 7-4x4)進行運動搜索.

從該函數中,我們可以發現,有一個局部變量,通過下面的語句將img->pred_mvpred_mv聯系了起來,

這樣其實通過調用函數SetMotionVectorPredictor來計算運動矢量的預測值(MVpred), 代碼中向SetMotionVectorPredictor函數傳遞了pred_mv這個整型指針(指向img->pred_mv具體要保存的地方), 這樣在函數SetMotionVectorPredictor中求出運動矢量的預測值,其實就存在了img->pred_mv中了.

具體在函數SetMotionVectorPredictor中預測MV的求解如下:

對水平和垂直兩個方向進行循環

具體的計算如下

根據不同的mvPredType計算得出相應的pred_vec, 然后存在pmv[hv]中.

 

 

 

2011年4月21日11:01:18

JM8.6中對上層塊模式預測的實現

下面的幾個變量是定義的保存上層塊預測的結果

上面這個三個數組應該都是和快速搜索有關的.

具體的pred_MV_uplayer是在函數BlockMotionSearch,如代碼:

但是看代碼可以發現上層塊預測只在FMEable的情況下才使用的.在函數BlockMotionSearch中有對各個搜索函數的調用.

由於pred_MV_uplayer是一個全局變量, 所以在函數FastIntegerPelBlockMotionSearch中有使用pred_MV_uplayer,如下

對於模式1[16x16]是沒有上層塊預測的,所以只有在模式2-7的情況下可以使用的

在函數FastSubPelBlockMotionSearch中也有使用

總結上面的,我們可以發現,在進行求MVD的時候, 我們是使用利用中值預測求出來的MV的預測值, 沒有使用上層塊預測的結果,而上層塊預測的結果是只在運動搜索中使用的.

20114209:26:54

dct_luma_16x16流程的問題

 

201142010:03:04

JM8.6T264碼率控制的不同

2011年4月24日17:38:36

率失真曲線

率失真(RD)曲線 反映了不同編碼器的編碼性能好壞。

一般RD曲線都是以碼率(Kbps)做為橫坐標,以PSNR(dB)作為縱坐標做出來一條曲線,曲線上的點一般是采用QP=28,32,36,40這四個QP下的編碼碼率和編碼質量 (QPi, butrate,psnr)。

曲線點越高,表明性能越好。

2011年4月25日20:24:30

H.264中的ASO與FMO

ASO指的是片在傳輸時的先后順序可以隨意變化, 而FMo是指在一幀圖像中宏塊的屬於不同的片的次序比較靈活

 

2011年5月7日11:08:22

對於listX可以這么理解StorablePicture *listX[6][ref_frame]; 66個參考列表,ref_frame是參考幀的數目,每一個listX[i][j]中存的是StorablePicture*指針

所以我們可以這么理解,listX是6個列表, 每一個列表中存放的是一個指針(StorablePicture**), 該指針指向一個內存區域, 這個內存區域存儲是該表中參考幀, 但是我們不直接存參考幀, 我們在這兒存儲的是參考幀的指針(StorablePicture*)

至於6個列表中到底存放了多少個參考幀, 這個是由數組lsitXsize決定的, 這個lsitXsize[6]數組就是用來存儲對應列表大小的

 

2011年5月9日22:06:22

H.264中DCT變換中所使用的蝶形算法

然而,4X4的矩陣運算如果按常規算法的話仍要進行64次乘法運算和48次加法,這將耗費較多的時間,於是在H.264中,有一種改進的算法(蝶形算法)可以減少運算的次數。這種矩陣運算算法構造非常巧妙,利用構造的矩陣的整數性質和對稱性,可完全將乘法運算轉化為加法運算。

 

    

    

變換過程在JM中代碼實現如下:

// Horizontal transform水平變換,其實就是左乘Cf,

for (j=0; j < BLOCK_SIZE && !lossless_qpprime; j++)

{

    for (i=0; i < 2; i++)

{

i1=3-i;

m5[i]=img->m7[i][j]+img->m7[i1][j];

m5[i1]=img->m7[i][j]-img->m7[i1][j];

}

img->m7[0][j]=(m5[0]+m5[1]);

img->m7[2][j]=(m5[0]-m5[1]);

img->m7[1][j]=m5[3]*2+m5[2];

img->m7[3][j]=m5[3]-m5[2]*2;

}

// Vertical transform垂直變換,其實就是右乘CfT

for (i=0; i < BLOCK_SIZE && !lossless_qpprime; i++)

{

    for (j=0; j < 2; j++)

{

j1=3-j;

m5[j]=img->m7[i][j]+img->m7[i][j1];

m5[j1]=img->m7[i][j]-img->m7[i][j1];

}

img->m7[i][0]=(m5[0]+m5[1]);

img->m7[i][2]=(m5[0]-m5[1]);

img->m7[i][1]=m5[3]*2+m5[2];

img->m7[i][3]=m5[3]-m5[2]*2;

}

 

2011年5月16日10:03:48

YUV420

Y'UV420p (and Y'V12 or YV12)

Y'UV420p is a planar format, meaning that the Y', U, and V values are grouped together instead of interspersed. The reason for this is that by grouping the U and V values together, the image becomes much more compressible. When given an array of an image in the Y'UV420p format, all the Y' values come first, followed by all the U values, followed finally by all the V values.

The Y'V12 format is essentially the same as Y'UV420p, but it has the U and V data reversed: the Y' values are followed by the V values, with the U values last. As long as care is taken to extract U and V values from the proper locations, both Y'UV420p and Y'V12 can be processed using the same algorithm.

As with most Y'UV formats, there are as many Y' values as there are pixels. Where X equals the height multiplied by the width, the first X indices in the array are Y' values that correspond to each individual pixel. However, there are only one fourth as many U and V values. The U and V values correspond to each 2 by 2 block of the image, meaning each U and V entry applies to four pixels. After the Y' values, the next X/4 indices are the U values for each 2 by 2 block, and the next X/4 indices after that are the V values that also apply to each 2 by 2 block.

Translating Y'UV420p to RGB is a more involved process compared to the previous formats. Lookup of the Y', U and V values can be done using the following method:

 

size.total = size.width * size.height; 

 

 

y = yuv[position.y * size.width + position.x]; 

 

 

u = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total]; 

 

 

v = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total + (size.total / 4)]; 

 

 

rgb = Y'UV444toRGB888(y, u, v); 

 

Here "/" is Div not division.

As shown in the above image, the Y', U and V components in Y'UV420 are encoded separately in sequential blocks. A Y' value is stored for every pixel, followed by a U value for each 2×2 square block of pixels, and finally a V value for each 2×2 block. Corresponding Y', U and V values are shown using the same color in the diagram above. Read line-by-line as a byte stream from a device, the Y' block would be found at position 0, the U block at position x×y (6×4 = 24 in this example) and the V block at position x×y + (x×y)/4 (here, 6×4 + (6×4)/4 = 30).

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM