HEXA娛樂(lè)開(kāi)發(fā)日志番外000——Gstreamer編程入門(mén)

HEXA開(kāi)發(fā)日志目錄


番外篇從此開(kāi)始忙厌,番外用于講一些HEXA開(kāi)發(fā)過(guò)程中的一些在日志中不便展開(kāi)的一些點(diǎn)點(diǎn)滴滴。

前言

  • 如題,本篇要講的是Gstreamer的簡(jiǎn)單使用卸奉,最好是看官方文檔,除非看英文文檔對(duì)你來(lái)說(shuō)太痛苦颖御,那就來(lái)看這篇好了榄棵。
  • 本篇雖然是講編程凝颇,不講命令行,但是在編程或編寫(xiě)命令行之前的準(zhǔn)備工作都是類(lèi)似的疹鳄,可以作為參考拧略。

Gstreamer簡(jiǎn)介

它的來(lái)歷我還沒(méi)有了解,目前我只知道它是一個(gè)在Linux環(huán)境上很容易制作流媒體應(yīng)用的程序框架瘪弓。
單說(shuō)Gstreamer的話(huà)垫蛆,它只是個(gè)框架,如果把框架比喻為骨架的話(huà)腺怯,插件就是它的血肉了袱饭,框架+插件就可以構(gòu)成一個(gè)處理流媒體的流水線(xiàn),基于Gstreamer的應(yīng)用可以通過(guò)對(duì)流水線(xiàn)的控制實(shí)現(xiàn)各種流媒體處理的功能呛占。
應(yīng)用開(kāi)發(fā)者只要在豐富的插件中找到適合自己應(yīng)用的虑乖,再把他們連成流水線(xiàn),立刻就可以構(gòu)建好一個(gè)流媒體處理模塊晾虑。關(guān)于使這個(gè)過(guò)程變得容易的原因疹味,一方面是插件非常豐富,大多數(shù)情況下帜篇,沒(méi)有必要自己制作插件糙捺;另一方面原因是插件的連接非常方便,在五花八門(mén)的插件中笙隙,有很簡(jiǎn)單的方法可以確定插件之間是否可以連接洪灯。

Gstreamer編程模型

我理解的Gstreamer編程要分為以下幾步

  1. 構(gòu)想流水線(xiàn)
    在編程之前,首先要在腦中構(gòu)造一個(gè)流程竟痰,即輸入是什么婴渡,大概要經(jīng)過(guò)哪些處理,最后輸出什么凯亮。
    輸入要想好輸入的形式或設(shè)備边臼,比如處理視頻的話(huà),可以是攝像頭假消、某種格式的視頻文件或是網(wǎng)絡(luò)流等柠并,音頻可以是話(huà)筒或某種格式的音頻文件等。
    處理過(guò)程可以大致考慮一下富拗,應(yīng)該需要哪種編解碼器或是格式轉(zhuǎn)換器臼予,需要哪種muxer或demuxer。
    輸出也是要想好形式或設(shè)備啃沪,比如視頻的話(huà)粘拾,可以是顯示器、液晶屏创千、某種格式的文件或是網(wǎng)絡(luò)流等缰雇,音頻可以是揚(yáng)聲器或某種格式的音頻文件等入偷。
  2. 插件選型
    要在插件庫(kù)中找出能夠滿(mǎn)足構(gòu)想的那些插件,并確認(rèn)它們能夠連接起來(lái)械哟,構(gòu)成我們想要的流水線(xiàn)疏之。那就需要確認(rèn)以下問(wèn)題
    • 當(dāng)前開(kāi)發(fā)環(huán)境下哪些插件是可用的?
    • 怎么知道那些從名字上看似乎可用的插件是不是能滿(mǎn)足需求暇咆?
    • 怎么知道這些看上去能用的插件能不能接起來(lái)锋爪?
  3. 寫(xiě)代碼
    把構(gòu)想中的對(duì)象和代碼對(duì)應(yīng)起來(lái),實(shí)現(xiàn)流水線(xiàn)爸业。

經(jīng)過(guò)上述過(guò)程后其骄,基本的編程就算完成了。下面以我寫(xiě)的simple.c為例扯旷,用上述過(guò)程復(fù)盤(pán)一下拯爽。

構(gòu)想流水線(xiàn)

官方例子

下圖來(lái)自官方文檔,看起來(lái)這是一個(gè)簡(jiǎn)單的ogg視頻文件播放器薄霜。整個(gè)大方框是一個(gè)pipeline,大方框中的小方框(例如file-source)是構(gòu)成pipeline的一個(gè)個(gè)插件中的元件纸兔,有src/sink字樣的藍(lán)色小方框是用來(lái)對(duì)接元件的東西惰瓜,其中src表示輸出,sink表示輸入汉矿。



單從字面上也不難理解這些元件

  • file-source
    即以文件作為源崎坊,這里具體就是某個(gè)ogg視頻文件了。
  • ogg-demuxer
    demuxer即分離器洲拇,從最后的結(jié)果看奈揍,它是把ogg文件中音/視頻分開(kāi)了。
  • vorbis-decoder
    很明顯赋续,這應(yīng)該是一個(gè)音頻解碼器
  • audio-sink
    看圖標(biāo)男翰,這應(yīng)該就是揚(yáng)聲器的驅(qū)動(dòng)之類(lèi)的了
  • theora-decoder
    很明顯+1,這應(yīng)該是一個(gè)視頻解碼器
  • video-sink
    看圖標(biāo)+1纽乱,這應(yīng)該就是顯示器的驅(qū)動(dòng)之類(lèi)的了

上面這個(gè)例子的輸入是ogg視頻文件蛾绎,中間要demuxer分離音視頻,然后分別解碼鸦列,最后輸出給音視頻設(shè)備播放租冠。其中的每個(gè)元件都是可以替換的,比如想把輸入源換成rtmp流薯嗤,只要把file-source換成rtmp-src元件就行了顽爹;再比如想做一個(gè)編碼器只要把整個(gè)圖左右翻轉(zhuǎn)一下,播放設(shè)備sink換成采集設(shè)備src骆姐,xxx-decoder換成xxx-encoder镜粤,xxx-demuxer換成xxx-encoder就可以了捏题。

我的流水線(xiàn)

  • 輸入
    視頻方面是機(jī)器人攝像頭采集的原始圖像,音頻方面反正還沒(méi)有合適的采集設(shè)備繁仁,先不考慮
  • 輸出
    rtmp網(wǎng)絡(luò)流
  • 處理
    在上面的輸入和輸出之間肯定要有個(gè)編碼器
    后面加音頻的話(huà)涉馅,音視頻需要合成為一個(gè)流,所以必然需要一個(gè)合成器(muxer)

攝像頭==>編碼器==>合成器==>rtmp流黄虱,我的流水線(xiàn)就這么簡(jiǎn)單稚矿,然后我們來(lái)插件選型。

插件選型

  • 當(dāng)前開(kāi)發(fā)環(huán)境下哪些插件是可用的捻浦?
    這很簡(jiǎn)單晤揣,只要使用Gstreamer的命令行工具gst-inspect,如果是1.x版本在終端中輸入以下命令就能列出所有插件的名字
gst-inspect-1.0
很多插件......
rtmp:  rtmpsink: RTMP output sink
rtmp:  rtmpsrc: RTMP Source
很多插件......

這里我只列了兩個(gè)比較好認(rèn)的朱灿,rtmp是插件名字昧识,rtmpsinkrtmpsrc是這個(gè)插件的2個(gè)元件類(lèi)型,第二個(gè)冒號(hào)后面是對(duì)這個(gè)元件的說(shuō)明盗扒。

  • 怎么知道那些從名字上看似乎可用的插件是不是能滿(mǎn)足需求跪楞?
    這個(gè)問(wèn)題隱含一個(gè)已知,我們要先大概知道一些名字侣灶,然后才能從名字上過(guò)濾掉大量的無(wú)關(guān)插件甸祭,比如關(guān)于攝像頭,需要知道v4l褥影、uvc等池户。
    首先,找攝像頭信號(hào)源凡怎,可以用正則表達(dá)式搜索video.*source校焦,然后再用搜索引擎大概了解一下就能初步選出來(lái)了。把那些比較像的找出來(lái)后统倒,只要再用用搜索引擎寨典,很容易確定其中的video4linux2: v4l2src: Video (video4linux2) Source是大概率可用的。
    其次房匆,找輸出插件凝赛,正則表達(dá)式搜rtmp.*sink,和找輸入類(lèi)似的坛缕,rtmp: rtmpsink: RTMP output sink看起來(lái)比較像墓猎,先就是它了。
    最后赚楚,要把中間那些插件找到毙沾,先找編碼器,正則表達(dá)式搜索video.*encoder宠页,在我的機(jī)器人上有3個(gè)插件中的6個(gè)元件備選左胞,如果在開(kāi)發(fā)環(huán)境里裝了Gstreamer的libav類(lèi)插件寇仓,就搜出更多了。
    此處篩選可以基于這樣一個(gè)原則烤宙,基于已知的知識(shí)找出最像的那個(gè)作為備選就好遍烦。
    因?yàn)楣俜浇o我推薦過(guò)libimxvpu,所以我就用名字叫imxvpu的插件就行了躺枕,它有4個(gè)元件服猪,分別是imxvpuenc_mjpegimxvpuenc_mpeg4拐云,imxvpuenc_h264罢猪,imxvpuenc_h263,看起來(lái)就是選一種編碼格式就行了叉瘩,先隨便選一個(gè)膳帕,就先選h264吧。如果沒(méi)有些先驗(yàn)只是薇缅,關(guān)于合成器(mux)就不是那么好找了危彩,如果知道rtmp和flash有關(guān),這一步可以先確定一個(gè)泳桦,不知道也沒(méi)關(guān)系汤徽,就直接進(jìn)行下一步好了。
  • 怎么知道這些看上去能用的插件能不能接起來(lái)蓬痒?
    • 為什么會(huì)有不能連接的情況泻骤?
      所有插件不是一個(gè)人或一隊(duì)人寫(xiě)的漆羔,這就好像一個(gè)國(guó)際公司的兩個(gè)部門(mén)要對(duì)接一樣梧奢,兩個(gè)部門(mén)至少要各出一個(gè)人作為接口與對(duì)方部門(mén)交流,如果這倆部門(mén)處在不同的國(guó)家演痒,那么這兩個(gè)接口很可能講著不同的語(yǔ)言亲轨,他們之間就無(wú)法有效傳達(dá)信息了。
      因此鸟顺,兩個(gè)元件要想連接惦蚊,必須知道講話(huà)的接口講得是什么語(yǔ)言,聽(tīng)話(huà)的接口聽(tīng)得懂什么語(yǔ)言讯嫂,如果語(yǔ)言一致蹦锋,就是能連接的。
    • 怎么看元件能“講或聽(tīng)得懂哪種語(yǔ)言”欧芽?
      我們可以用gst-inspect-1.0 <元件名>進(jìn)一步看一下元件詳細(xì)信息莉掂,以便對(duì)元件有進(jìn)一步了解,下面看一下rtmpsink的信息千扔。
# gst-inspect-1.0 rtmpsink
Factory Details:
  Rank                     primary (256)
  Long-name                RTMP output sink
  Klass                    Sink/Network
  Description              Sends FLV content to a server via RTMP
  Author                   Jan Schmidt <thaytan@noraisin.net>

Plugin Details:
  Name                     rtmp
  Description              RTMP source and sink
  Filename                 /usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so
  Version                  1.2.4
  License                  LGPL
  Source module            gst-plugins-bad
  Source release date      2014-04-18
  Binary package           GStreamer Bad Plugins (Ubuntu)
  Origin URL               https://launchpad.net/distros/ubuntu/+source/gst-plugins-bad1.0

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstBaseSink
                         +----GstRTMPSink
...
Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      video/x-flv
...

Element Properties:
...
  location            : RTMP url
                        flags: readable, writable
                        String. Default: null

稍微解讀一下

  1. Factory Details和Plugin Details都是一些更詳盡的描述憎妙,比如Source module gst-plugins-bad是說(shuō)這個(gè)插件屬于gst-plugins-bad插件庫(kù)的库正,如果你想看代碼,可以去github搜這個(gè)名字厘唾。/usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so是這個(gè)插件庫(kù)文件的位置褥符。
  2. Pad Templates
    Pad概念后面再討論,總之這里是描述這個(gè)元件可以“講什么語(yǔ)言”或“聽(tīng)什么語(yǔ)言”的抚垃。這里有個(gè)SINK template: 'sink'是說(shuō)它有“聽(tīng)”的能力喷楣,如果能“說(shuō)”,就會(huì)有SRC template了讯柔。
    Capabilities是對(duì)SINK template: 'sink'的能力的具體描述抡蛙,即video/x-flv,可以理解為一種視頻格式魂迄,也就是說(shuō)這個(gè)元件能接收video/x-flv格式的視頻輸入粗截,如果對(duì)方的SRC template能輸出這個(gè)格式,那么就可以連接捣炬。
    這里的flv字眼說(shuō)明熊昌,這個(gè)元件前面的合成器可以搜索mux、flv或flash找到哦湿酸。
  3. Element Properties
    這里是屬性列表婿屹,是插件自己定制的,所以別的插件可能有不同的屬性列表推溃。location屬性的值就是rtmp流推送的地址昂利。如果是官方例子中的file-source,應(yīng)該也會(huì)有location屬性铁坎,它代表著要播放的視頻文件路徑蜂奸,即同樣的屬性名在不同插件中的意思可能不同,屬性名和含義完全是插件設(shè)計(jì)者決定的硬萍。

至此扩所,判斷插件能否對(duì)接的方法有了,即對(duì)比SINK templateSRC templateCapabilities朴乖,如果有重疊的部分祖屏,就是可以對(duì)接的
我完成插件選型后設(shè)計(jì)的流水線(xiàn)如下买羞,下一步就是用代碼實(shí)現(xiàn)它了袁勺。

我的流水線(xiàn)

寫(xiě)代碼

最簡(jiǎn)單的實(shí)現(xiàn),只要遵循以下套路就能滿(mǎn)足大多數(shù)需求了畜普。

#include <gst/gst.h>
#include <glib.h>

int main (int argc,char *argv[])
{
    GMainLoop *loop;
    GstElement *pipeline, *元件0, *元件1, ..., *元件n;

    /* Initialisation */
    gst_init (&argc, &argv);

    loop = g_main_loop_new (NULL, FALSE);

    /* 創(chuàng)建流水線(xiàn) */
    pipeline = gst_pipeline_new ("自己起的流水線(xiàn)名");
    /* 創(chuàng)建元件 */
    元件0 = gst_element_factory_make ("元件名0", "自己起的元件實(shí)例名0");
    元件1 = gst_element_factory_make ("元件名1", "自己起的元件實(shí)例名1");
    //其他元件...

    /* 如果需要給一些原件設(shè)置屬性 */
    g_object_set (G_OBJECT (元件0), "屬性名", 屬性值, NULL);

    /* 把元件與流水線(xiàn)綁定 */
    gst_bin_add_many (GST_BIN (pipeline), 元件0, 元件1, ..., 元件n, NULL);
    /* 連接元件期丰,也可以用gst_element_link_many一句連接 */
    if (gst_element_link (元件0, 元件1)){
        g_print ("link success %d\n", __LINE__);
    }
    else{
        return -1;
    }
    if (gst_element_link (元件1, 元件2)){
        g_print ("link success %d\n", __LINE__);
    }
    else{
        return -1;
    }
    //......
    if (gst_element_link (元件n-1, 元件n)){
        g_print ("link success %d\n", __LINE__);
    }
    else{
        return -1;
    }

    /* 啟動(dòng)播放*/
    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    /* 運(yùn)行主循環(huán),ctrl+c可以退出這個(gè)循環(huán) */
    g_main_loop_run (loop);

    /* 釋放內(nèi)存之類(lèi)的收尾動(dòng)作 */
    //略...
    return 0;
}

套路是不是很簡(jiǎn)單,也就是以下幾個(gè)步驟

  1. 創(chuàng)建流水線(xiàn)pipeline咐汞,想好名字就行了盖呼;
  2. 創(chuàng)建元件元件0~元件n,第一個(gè)參數(shù)是元件類(lèi)型化撕,第二個(gè)參數(shù)是給這個(gè)元件的實(shí)例起的名字几晤,兩者就像類(lèi)和對(duì)象、數(shù)據(jù)類(lèi)型和變量的關(guān)系一樣植阴;
  3. 對(duì)一些需要配置的元件設(shè)置屬性(g_object_set)蟹瘾;
  4. gst_bin_add_many相當(dāng)于把元件裝配到流水線(xiàn)上,準(zhǔn)備連接掠手;
  5. 按順序連接元件(gst_element_linkgst_element_link_many)憾朴;
  6. 設(shè)置流水線(xiàn)狀態(tài)為GST_STATE_PLAYING,這個(gè)狀態(tài)就意味著流水線(xiàn)開(kāi)始播放了喷鸽;
  7. g_main_loop_run運(yùn)行一個(gè)死循環(huán)众雷,只要流水線(xiàn)正常運(yùn)行,這個(gè)循環(huán)就不會(huì)退出做祝,如果用戶(hù)ctrl+c或者播放出錯(cuò)了砾省,這個(gè)循環(huán)就退出了;
  8. 在循環(huán)退出后進(jìn)行收尾工作混槐。

我的代碼比這個(gè)套路多了一點(diǎn)點(diǎn)東西编兄,不過(guò)要是沒(méi)有多這一點(diǎn)點(diǎn),也是可以運(yùn)作的声登。
上面套路中涉及的概念和數(shù)據(jù)類(lèi)型如下表狠鸳,其中GstElementFactory并沒(méi)有出現(xiàn)。這是因?yàn)?code>gst_element_factory_make把使用GstElementFactory的過(guò)程封裝了悯嗓,官方講elements的文檔里有對(duì)此說(shuō)明件舵。

概念 數(shù)據(jù)類(lèi)型
元件類(lèi)型 GstElementFactory
元件實(shí)例 GstElement
流水線(xiàn) GstElement

工廠(factory)?元件(element)?這些名字是不是很形象?其設(shè)計(jì)思想就是乒验,先請(qǐng)不同的工廠制造出各種元件往毡,然后把元件組裝成一條流水線(xiàn),這條流水線(xiàn)上流動(dòng)的就是流媒體的不同形態(tài)洪乍,包括文件或內(nèi)存等不同形式眯杏、編/解碼前/后等不同狀態(tài)。

總結(jié)

Gstreamer的編程模型還有很多其他的機(jī)制來(lái)滿(mǎn)足更復(fù)雜的需求壳澳。例如岂贩,當(dāng)兩個(gè)元件有多種方式對(duì)接時(shí),如何指定用哪種方式對(duì)接巷波,再比如萎津,如何對(duì)流水線(xiàn)甚至單個(gè)元件進(jìn)行更細(xì)致的控制等卸伞,這些東西也許會(huì)繼續(xù)出文章說(shuō)明。


下一篇 也許會(huì)有锉屈。荤傲。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颈渊,一起剝皮案震驚了整個(gè)濱河市遂黍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俊嗽,老刑警劉巖雾家,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绍豁,居然都是意外死亡芯咧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)竹揍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)唬党,“玉大人,你說(shuō)我怎么就攤上這事鬼佣∈还埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵晶衷,是天一觀的道長(zhǎng)蓝纲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晌纫,這世上最難降的妖魔是什么税迷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮锹漱,結(jié)果婚禮上箭养,老公的妹妹穿的比我還像新娘。我一直安慰自己哥牍,他們只是感情好毕泌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著嗅辣,像睡著了一般撼泛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澡谭,一...
    開(kāi)封第一講書(shū)人閱讀 52,166評(píng)論 1 308
  • 那天愿题,我揣著相機(jī)與錄音,去河邊找鬼。 笑死潘酗,一個(gè)胖子當(dāng)著我的面吹牛杆兵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仔夺,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拧咳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了囚灼?” 一聲冷哼從身側(cè)響起骆膝,我...
    開(kāi)封第一講書(shū)人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灶体,沒(méi)想到半個(gè)月后阅签,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝎抽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年政钟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片樟结。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡养交,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓢宦,到底是詐尸還是另有隱情碎连,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布驮履,位于F島的核電站鱼辙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏玫镐。R本人自食惡果不足惜倒戏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恐似。 院中可真熱鬧杜跷,春花似錦、人聲如沸矫夷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)口四。三九已至孵运,卻和暖如春秦陋,著一層夾襖步出監(jiān)牢的瞬間蔓彩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赤嚼,地道東北人旷赖。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像更卒,于是被迫代替她去往敵國(guó)和親等孵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

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