iOS硬編解碼相關(guān)知識

一碱璃、軟編與硬編概念

1、軟編碼:使用CPU進(jìn)行編碼饭入。

實(shí)現(xiàn)直接嵌器、簡單,參數(shù)調(diào)整方便谐丢,升級易爽航,但CPU負(fù)載重,性能較硬編碼低乾忱,低碼率下質(zhì)量通常比硬編碼要好一點(diǎn)讥珍。

2、硬編碼:不使用CPU進(jìn)行編碼窄瘟,使用顯卡GPU,專用的DSP串述、FPGA、ASIC芯片等硬件進(jìn)行編碼寞肖。

性能高纲酗,低碼率下通常質(zhì)量低于軟編碼器,但部分產(chǎn)品在GPU硬件平臺移植了優(yōu)秀的軟編碼算法(如X264)的新蟆,質(zhì)量基本等同于軟編碼觅赊。

蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能琼稻,不過Mac OS系統(tǒng)一直有吮螺,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0(即WWDC 2014 513)后帕翻,蘋果將該框架引入iOS系統(tǒng)鸠补。

二、H.264編碼原理

H.264是新一代的編碼標(biāo)準(zhǔn)嘀掸,以高壓縮高質(zhì)量和支持多種網(wǎng)絡(luò)的流媒體傳輸著稱紫岩,在編碼方面,我理解的理論依據(jù)是:參照一段時間內(nèi)圖像的統(tǒng)計(jì)結(jié)果表明睬塌,在相鄰幾幅圖像畫面中泉蝌,一般有差別的像素只有10%以內(nèi)的點(diǎn),亮度差值變化不超過2%,而色度差值的變化只有1%以內(nèi)揩晴。所以對于一段變化不大圖像畫面勋陪,我們可以先編碼出一個完整的圖像幀A,隨后的B幀就不編碼全部圖像硫兰,只寫入與A幀的差別诅愚,這樣B幀的大小就只有完整幀的1/10或更小劫映!B幀之后的C幀如果變化不大违孝,我們可以繼續(xù)以參考B的方式編碼C幀涤妒,這樣循環(huán)下去贵扰。這段圖像我們稱為一個序列(序列就是有相同特點(diǎn)的一段數(shù)據(jù))弛随,當(dāng)某個圖像與之前的圖像變化很大抄谐,無法參考前面的幀來生成,那我們就結(jié)束上一個序列筹燕,開始下一段序列轧飞,也就是對這個圖像生成一個完整幀A1,隨后的圖像就參考A1生成撒踪,只寫入與A1的差別內(nèi)容过咬。

需要注意的是:

在H264協(xié)議里定義了三種幀,完整編碼的幀叫I幀制妄,參考之前的I幀生成的只包含差異部分編碼的幀叫P幀掸绞,還有一種參考前后的幀編碼的幀叫B幀。

H264采用的核心算法是幀內(nèi)壓縮和幀間壓縮耕捞,幀內(nèi)壓縮是生成I幀的算法衔掸,幀間壓縮是生成B幀和P幀的算法。

三敞映、序列的說明

在H264中圖像以序列為單位進(jìn)行組織,一個序列是一段圖像編碼后的數(shù)據(jù)流磷斧,以I幀開始振愿,到下一個I幀結(jié)束。

一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像)弛饭,IDR 圖像都是 I 幀圖像冕末。H.264 引入 IDR 圖像是為了解碼的重同步,當(dāng)解碼器解碼到 IDR 圖像時侣颂,立即將參考幀隊(duì)列清空档桃,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集横蜒,開始一個新的序列胳蛮。這樣,如果前一個序列出現(xiàn)重大錯誤丛晌,在這里可以獲得重新同步的機(jī)會。IDR圖像之后的圖像永遠(yuǎn)不會使用IDR之前的圖像的數(shù)據(jù)來解碼斗幼。

一個序列就是一段內(nèi)容差異不太大的圖像編碼后生成的一串?dāng)?shù)據(jù)流澎蛛。當(dāng)運(yùn)動變化比較少時,一個序列可以很長蜕窿,因?yàn)檫\(yùn)動變化少就代表圖像畫面的內(nèi)容變動很小谋逻,所以就可以編一個I幀呆馁,然后一直P幀、B幀了毁兆。當(dāng)運(yùn)動變化多時浙滤,可能一個序列就比較短了,比如就包含一個I幀和3气堕、4個P幀纺腊。

四、對三種幀的簡單介紹

I茎芭、B揖膜、P各幀是根據(jù)壓縮算法的需要,是人為定義的,它們都是實(shí)實(shí)在在的物理幀梅桩。一般來說壹粟,I幀的壓縮率是7(跟JPG差不多),P幀是20宿百,B幀可以達(dá)到50趁仙。可見使用B幀能節(jié)省大量空間垦页,節(jié)省出來的空間可以用來保存多一些I幀雀费,這樣在相同碼率下,可以提供更好的畫質(zhì)外臂。

B-012.png

說明:

I幀:紅色坐儿;P幀:藍(lán)色;B幀:綠色宋光。

五貌矿、H264壓縮算法的說明

1、分組:把幾幀圖像分為一組(GOP罪佳,也就是一個序列),為防止運(yùn)動變化,幀數(shù)不宜取多逛漫。

2、定義幀:將每組內(nèi)各幀圖像定義為三種類型,即I幀赘艳、B幀和P幀;

3酌毡、預(yù)測幀:以I幀做為基礎(chǔ)幀,以I幀預(yù)測P幀,再由I幀和P幀預(yù)測B幀;

4、數(shù)據(jù)傳輸:最后將I幀數(shù)據(jù)與預(yù)測的差值信息進(jìn)行存儲和傳輸蕾管。

5枷踏、幀內(nèi)(Intraframe)壓縮也稱為空間壓縮(Spatial compression)。

當(dāng)壓縮一幀圖像時掰曾,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息旭蠕,這實(shí)際上與靜態(tài)圖像壓縮類似。幀內(nèi)一般采用有損壓縮算法,由于幀內(nèi)壓縮是編碼一個完整的圖像掏熬,所以可以獨(dú)立的解碼佑稠、顯示。幀內(nèi)壓縮一般達(dá)不到很高的壓縮旗芬,跟編碼jpeg差不多舌胶。

6、幀間(Interframe)壓縮疮丛。

相鄰幾幀的數(shù)據(jù)有很大的相關(guān)性幔嫂,或者說前后兩幀信息變化很小的特點(diǎn)。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性这刷,壓縮相鄰幀之間的冗余量就可以進(jìn)一步提高壓縮量婉烟,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporal compression)暇屋,它通過比較時間軸上不同幀之間的數(shù)據(jù)進(jìn)行壓縮似袁。幀間壓縮一般是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法咐刨,它通過比較本幀與相鄰幀之間的差異昙衅,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量定鸟。

7而涉、有損(Lossy)壓縮和無損(Lossy less)壓縮。

無損壓縮也即壓縮前和解壓縮后的數(shù)據(jù)完全一致联予。多數(shù)的無損壓縮都采用RLE行程編碼算法啼县。

有損壓縮意味著解壓縮后的數(shù)據(jù)與壓縮前的數(shù)據(jù)不一致。在壓縮的過程中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,而且丟失的信息不可恢復(fù)沸久。幾乎所有高壓縮的算法都采用有損壓縮,這樣才能達(dá)到低數(shù)據(jù)率的目標(biāo)季眷。丟失的數(shù)據(jù)率與壓縮比有關(guān),壓縮比越小,丟失的數(shù)據(jù)越多,解壓縮后的效果一般越差卷胯。此外,某些有損壓縮算法采用多次重復(fù)壓縮的方式,這樣還會引起額外的數(shù)據(jù)丟失子刮。

六、DTS與PTS的區(qū)別

DTS主要用于視頻的解碼,在解碼階段使用.

PTS主要用于視頻的同步和輸出.

在display的時候使用.在沒有B frame的情況下.DTS和PTS的輸出順序是一樣的窑睁。

下面給出一個GOP為15的例子,其解碼的參照frame及其解碼的順序都在里面:

B-001.png

如上圖:

I frame 的解碼不依賴于任何的其它的幀.而p frame的解碼則依賴于其前面的I frame或者P frame.B frame的解碼則依賴于其前的最近的一個I frame或者P frame 及其后的最近的一個P frame.

七挺峡、iOS系統(tǒng) H.264視頻硬件編解碼說明

1、VideoToolbox的介紹

在iOS中担钮,與視頻相關(guān)的Framework庫有5個橱赠,從頂層開始分別是 AVKit -> AVFoundation -> VideoToolbox -> Core Media -> Core Video

其中VideoToolbox可以將視頻解壓到CVPixelBuffer,也可以壓縮到CMSampleBuffer
但是我們常用的是CMSampleBuffer.

2箫津、VideoToolbox中的對象

1)CVPixelBuffer

編碼前和解碼后的圖像數(shù)據(jù)結(jié)構(gòu)(未壓縮光柵圖像緩存區(qū)-Uncompressed Raster Image Buffer)

B-002.png

2)CVPixelBufferPool

存放CVPixelBuffer

B-003.png

3)pixelBufferAttributes

CFDictionary對象病线,可能包含了視頻的寬高吓著,像素格式類型(32RGBA, YCbCr420)鲤嫡,是否可以用于OpenGL ES等相關(guān)信息

4)CMTime

時間戳相關(guān)送挑。時間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時間值暖眼,分母是32-bit的時標(biāo)(time scale)

5)CMClock

時間戳相關(guān)惕耕。時間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時間值诫肠,分母是32-bit的時標(biāo)(time scale)司澎。它封裝了時間源,其中CMClockGetHostTimeClock()封裝了mach_absolute_time()

6)CMTimebase

時間戳相關(guān)栋豫。時間以 64-big/32-bit形式出現(xiàn)挤安。CMClock上的控制視圖。提供了時間的映射:CMTimebaseSetTime(timebase, kCMTimeZero); 速率控制:
CMTimebaseSetRate(timebase, 1.0);

7)CMBlockBuffer

編碼后丧鸯,結(jié)果圖像的數(shù)據(jù)結(jié)構(gòu)

8)CMVideoFormatDescription

編解碼前后的視頻圖像均封裝在CMSampleBuffer中蛤铜,如果是編碼后的圖像,以CMBlockBuffe方式存儲丛肢;解碼后的圖像围肥,以CVPixelBuffer存儲。

9)CMSampleBuffer

存放編解碼前后的視頻圖像的容器數(shù)據(jù)結(jié)構(gòu)蜂怎。如圖所示穆刻,編解碼前后的視頻圖像均封裝在CMSampleBuffer中,如果是編碼后的圖像杠步,以CMBlockBuffer方式存儲氢伟;解碼后的圖像,以CVPixelBuffer存儲幽歼。CMSampleBuffer里面還有另外的時間信息CMTime和視頻描述信息CMVideoFormatDesc朵锣。

B-004.png

八、硬解碼

目標(biāo):如何將從網(wǎng)絡(luò)處傳來H.264編碼后的視頻碼流顯示在手機(jī)屏幕上试躏?

實(shí)現(xiàn)步驟如下:

1猪勇、將 H.264碼流轉(zhuǎn)換為 CMSampleBuffer

CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer

需要從H.264的碼流里面提取出以上的三個信息。最后組合成CMSampleBuffer颠蕴,提供給硬解碼接口來進(jìn)行解碼工作泣刹。

在H.264的語法中,有一個最基礎(chǔ)的層犀被,叫做Network Abstraction Layer, 簡稱為NAL椅您。H.264流數(shù)據(jù)正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。

B-005.png

H264的碼流由NALU單元組成,一個NALU可能包含有:

  1. 視頻幀,視頻幀也就是視頻片段寡键,具體有 P幀, I幀掀泳,B幀


    B-006.png

2)H.264屬性合集-FormatDesc(包含 SPS和PPS),即流數(shù)據(jù)中,屬性集合可能是這樣的:


B-007.png

經(jīng)過處理之后,在Format Description中則是:

B-008.png

需要注意的是:

要從基礎(chǔ)的流數(shù)據(jù)將SPS和PPS轉(zhuǎn)化為Format Desc中的話员舵,需要調(diào)用CMVideoFormatDescriptionCreateFromH264ParameterSets()方法脑沿。

3)NALU header

對于流數(shù)據(jù)來說,一個NAUL的Header中马僻,可能是0x00 00 01或者是0x00 00 00 01作為開頭(兩者都有可能庄拇,下面以0x00 00 01作為例子)。0x00 00 01因此被稱為開始碼(Start code).


B-009.png

總結(jié)以上知識韭邓,我們知道H264的碼流由NALU單元組成措近,NALU單元包含視頻圖像數(shù)據(jù)和H264的參數(shù)信息。其中視頻圖像數(shù)據(jù)就是CMBlockBuffer女淑,而H264的參數(shù)信息則可以組合成FormatDesc瞭郑。具體來說參數(shù)信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個H.264碼流結(jié)構(gòu):

B-010.png
(1)提取sps和pps生成FormatDesc
  • 每個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
  • 通過類型信息找到sps和pps并提取鸭你,開始碼后第一個byte的后5位屈张,7代表sps,8代表pps
  • 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數(shù)來構(gòu)建CMVideoFormatDescriptionRef
(2)提取視頻圖像數(shù)據(jù)生成CMBlockBuffer
  • 通過開始碼苇本,定位到NALU
  • 確定類型為數(shù)據(jù)后袜茧,將開始碼替換成NALU的長度信息(4 Bytes)
  • 使用CMBlockBufferCreateWithMemoryBlock接口構(gòu)造CMBlockBufferRef
(3)根據(jù)需要,生成CMTime信息瓣窄。

(實(shí)際測試時笛厦,加入time信息后,有不穩(wěn)定的圖像俺夕,不加入time信息反而沒有裳凸,需要進(jìn)一步研究,這里建議不加入time信息)

根據(jù)上述得到CMVideoFormatDescriptionRef劝贸、CMBlockBufferRef和可選的時間信息姨谷,使用CMSampleBufferCreate接口得到CMSampleBuffer數(shù)據(jù)這個待解碼的原始的數(shù)據(jù)。如下圖所示的H264數(shù)據(jù)轉(zhuǎn)換示意圖映九。

B-011.png

2梦湘、將 CMSampleBuffer顯示出來

顯示的方式有兩種:

1)、將CMSampleBuffers提供給系統(tǒng)的AVSampleBufferDisplayLayer 直接顯示

使用方式和其它CALayer類似件甥。該層內(nèi)置了硬件解碼功能捌议,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上面,非常的簡單方便引有。

2)瓣颅、利用OPenGL渲染

通過VTDecompression接口來,將CMSampleBuffer解碼成圖像譬正,將圖像通過UIImageView或者OpenGL上顯示宫补。

初始化VTDecompressionSession檬姥,設(shè)置解碼器的相關(guān)信息。初始化信息需要CMSampleBuffer里面的FormatDescription粉怕,以及設(shè)置解碼后圖像的存儲方式健民。demo里面設(shè)置的CGBitmap模式,使用RGB方式存放斋荞。編碼后的圖像經(jīng)過解碼后荞雏,會調(diào)用一個回調(diào)函數(shù),將解碼后的圖像交個這個回調(diào)函數(shù)來進(jìn)一步處理平酿。我們就在這個回調(diào)里面,將解碼后的圖像發(fā)給control來顯示悦陋,初始化的時候要將回調(diào)指針作為參數(shù)傳給create接口函數(shù)蜈彼。最后使用create接口對session來進(jìn)行初始化。

上所述的回調(diào)函數(shù)可以完成CGBitmap圖像轉(zhuǎn)換成UIImage圖像的處理俺驶,將圖像通過隊(duì)列發(fā)送到Control來進(jìn)行顯示處理幸逆。

調(diào)用VTDecompresSessionDecodeFrame接口進(jìn)行解碼操作。解碼后的圖像會交由以上兩步驟設(shè)置的回調(diào)函數(shù)暮现,來進(jìn)一步的處理还绘。

九、硬解碼

硬編碼的使用也通過一個典型的應(yīng)用場景來描述栖袋。首先拍顷,通過攝像頭來采集圖像,然后將采集到的圖像塘幅,通過硬編碼的方式進(jìn)行編碼昔案,最后編碼后的數(shù)據(jù)將其組合成H264的碼流通過網(wǎng)絡(luò)傳播。

1电媳、攝像頭采集數(shù)據(jù)

攝像頭采集踏揣,iOS系統(tǒng)提供了AVCaptureSession來采集攝像頭的圖像數(shù)據(jù)。設(shè)定好session的采集解析度匾乓。再設(shè)定好input和output即可捞稿。output設(shè)定的時候,需要設(shè)置delegate和輸出隊(duì)列拼缝。在delegate方法娱局,處理采集好的圖像。

圖像輸出的格式珍促,是未編碼的CMSampleBuffer形式铃辖。

2、使用VTCompressionSession進(jìn)行硬編碼

1)初始化VTCompressionSession

VTCompressionSession初始化的時候猪叙,一般需要給出width寬娇斩,height長仁卷,編碼器類型kCMVideoCodecType_H264等。然后通過調(diào)用VTSessionSetProperty接口設(shè)置幀率等屬性犬第,demo里面提供了一些設(shè)置參考锦积,測試的時候發(fā)現(xiàn)幾乎沒有什么影響,可能需要進(jìn)一步調(diào)試歉嗓。最后需要設(shè)定一個回調(diào)函數(shù)丰介,這個回調(diào)是視頻圖像編碼成功后調(diào)用。全部準(zhǔn)備好后鉴分,使用VTCompressionSessionCreate創(chuàng)建session

2)提取攝像頭采集的原始圖像數(shù)據(jù)給VTCompressionSession來硬編碼

攝像頭采集后的圖像是未編碼的CMSampleBuffer形式哮幢,利用給定的接口函數(shù)CMSampleBufferGetImageBuffer從中提取出CVPixelBufferRef,使用硬編碼接口VTCompressionSessionEncodeFrame來對該幀進(jìn)行硬編碼志珍,編碼成功后橙垢,會自動調(diào)用session初始化時設(shè)置的回調(diào)函數(shù)。

3)利用回調(diào)函數(shù)伦糯,將因編碼成功的CMSampleBuffer轉(zhuǎn)換成H264碼流柜某,通過網(wǎng)絡(luò)傳播。

基本上是硬解碼的一個逆過程敛纲。解析出參數(shù)集SPS和PPS喂击,加上開始碼后組裝成NALU。提取出視頻數(shù)據(jù)淤翔,將長度碼轉(zhuǎn)換成開始碼翰绊,組長成NALU。將NALU發(fā)送出去办铡。


本文主要為轉(zhuǎn)載學(xué)習(xí)辞做,部分細(xì)節(jié)有刪改。


相關(guān)資料傳送:

iOS8系統(tǒng)H264視頻硬件編解碼說明

簡單談?wù)動簿幋a和軟編碼

I,P寡具,B幀和PTS秤茅,DTS的關(guān)系

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市童叠,隨后出現(xiàn)的幾起案子框喳,更是在濱河造成了極大的恐慌,老刑警劉巖厦坛,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件五垮,死亡現(xiàn)場離奇詭異,居然都是意外死亡杜秸,警方通過查閱死者的電腦和手機(jī)放仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撬碟,“玉大人诞挨,你說我怎么就攤上這事莉撇。” “怎么了惶傻?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵棍郎,是天一觀的道長。 經(jīng)常有香客問我银室,道長涂佃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任蜈敢,我火速辦了婚禮辜荠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扶认。我一直安慰自己侨拦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布辐宾。 她就那樣靜靜地躺著,像睡著了一般膨蛮。 火紅的嫁衣襯著肌膚如雪叠纹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天敞葛,我揣著相機(jī)與錄音誉察,去河邊找鬼。 笑死惹谐,一個胖子當(dāng)著我的面吹牛持偏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氨肌,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼鸿秆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怎囚?” 一聲冷哼從身側(cè)響起卿叽,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恳守,沒想到半個月后考婴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡催烘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年沥阱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伊群。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡考杉,死狀恐怖策精,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奔则,我是刑警寧澤蛮寂,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站易茬,受9級特大地震影響酬蹋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抽莱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一范抓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧食铐,春花似錦匕垫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斟叼,卻和暖如春偶惠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背朗涩。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工忽孽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谢床。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓兄一,卻偏偏與公主長得像,于是被迫代替她去往敵國和親识腿。 傳聞我的和親對象是個殘疾皇子出革,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容