WebRTC GCC基于丟包動態(tài)閾值的帶寬估計原理

前言

  • WebRTC GCC-基于丟包的碼率估計原理一文發(fā)布已有三年之久腰耙,隨著webrtc 代碼的不斷更新办绝,擁塞控制部分谷歌也一直在更新著觉。
  • 當(dāng)前基于丟包的碼率估計部分已經(jīng)額外新拓展了兩個分支,總共已有三套算法著角。
  • 其中最原始的算法基于靜態(tài)丟包閾值進行計算敲长,本文分析分析LossBasedBandwidthEstimation簡稱為V1版本,從代碼來看主要是對丟包閾值進行了動態(tài)化處理。
  • 本文首先簡單總結(jié)基于Base版本基于丟包的碼率估計原理堕花。

Base版本基于丟包的碼率估計原理總結(jié)

  • 在分析之前先簡單回顧最原始的版本的核心原理


    001.png
  • Gcc-analysis論文中有定義如上策略。
  • 當(dāng)丟包率小于%2的時候碼率按照1.05倍遞增,其中代碼實現(xiàn)中的遞增系數(shù)為1.08,代碼實現(xiàn)中基準(zhǔn)值是在一秒內(nèi)的最小碼率的基礎(chǔ)上進行遞增粥鞋。
  • 而當(dāng)丟包率大于%10的時候按照newRate = rate * (1 - 0.5*lossRate)進行衰減,代碼實現(xiàn)中需要考慮每次遞減的時間間隔300ms+RTT
  • 當(dāng)丟包率在%2~10%之間則維持不變缘挽。
  • 此類策略在高碼率、且低延遲的場景中呻粹,存在十分大的缺陷壕曼,比如說20Mbps的實時碼率,假設(shè)丟包率達到5%等浊,并且RTT超過30Ms以上腮郊,壓根無法保證低延遲,類似云游戲場景筹燕,對丟包的容忍度十分低轧飞。

LossBasedBandwidthEstimation 丟包率和Ack碼率更新

002.png
  • 通過tcc feedback報文對LossBasedBandwidthEstimation模塊中的average_loss_(平均丟包率)、average_loss_max_(平均最大丟包率)撒踪、以及acknowledged_bitrate_max_(最大應(yīng)答)碼率進行實時更新过咬。

LossBasedBandwidthEstimation 平均丟包率更新

void LossBasedBandwidthEstimation::UpdateLossStatistics(
    const std::vector<PacketResult>& packet_results,
    Timestamp at_time) {
  // 無反饋直接返回
  if (packet_results.empty()) {
    RTC_DCHECK_NOTREACHED();
    return;
  }
  int loss_count = 0;
  for (const auto& pkt : packet_results) {
    loss_count += !pkt.IsReceived() ? 1 : 0;
  }
  // 計算丟包率(當(dāng)前丟包個數(shù)/當(dāng)前反饋總個數(shù))
  last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size();
  // 計算距離上次tcc反饋所流逝的時間間隔
  const TimeDelta time_passed = last_loss_packet_report_.IsFinite()
                                    ? at_time - last_loss_packet_report_
                                    : TimeDelta::Seconds(1);
  // 更新上次丟包反饋時間為當(dāng)前tcc反饋時間
  last_loss_packet_report_ = at_time;
  has_decreased_since_last_loss_report_ = false;
  // 對丟包率進行指數(shù)平滑,默認(rèn)平滑窗口為800ms,其中time_passed(兩次tcc feedback的間隔越大)
  // 則當(dāng)前的平均丟包率越逼近與當(dāng)前的丟包率
  average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) *
                   (last_loss_ratio_ - average_loss_);
  if (average_loss_ > average_loss_max_) {
    average_loss_max_ = average_loss_;
  } else {
    // 對最大平均丟包率進行指數(shù)平滑,同理兩次tcc feedback的間隔越大,平均最大丟包率越逼近于當(dāng)前的平均丟包率
    average_loss_max_ +=
        ExponentialUpdate(config_.loss_max_window, time_passed) *
        (average_loss_ - average_loss_max_);
  }
}
  • 其中ExponentialUpdate()函數(shù)的實現(xiàn)如下:
/**
 *參數(shù)interval:為兩次tcc feedback的時間間隔
 *參數(shù)window:默認(rèn)800ms
 */
double ExponentialUpdate(TimeDelta window, TimeDelta interval) {
  // Use the convention that exponential window length (which is really
  // infinite) is the time it takes to dampen to 1/e.
  if (window <= TimeDelta::Zero()) {
    return 1.0f;
  }
  return 1.0f - exp(interval / window * -1.0);
}
  • 首先回顧已e^x次方的函數(shù)圖像:
    003.png
  • 很明顯ExponentialUpdate函數(shù)為一個指數(shù)遞減函數(shù),當(dāng)interval越大(表示兩次tcc feedback的間隔越大)制妄,則該函數(shù)的返回值會越大掸绞,則平均丟包率越逼近于本次tcc feedback計算出來的丟包率。

LossBasedBandwidthEstimation Ack碼率更新

void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate(
    DataRate acknowledged_bitrate,
    Timestamp at_time) {
  const TimeDelta time_passed =
      acknowledged_bitrate_last_update_.IsFinite()
          ? at_time - acknowledged_bitrate_last_update_
          : TimeDelta::Seconds(1);
  acknowledged_bitrate_last_update_ = at_time;
  // 更新最大ack碼率
  if (acknowledged_bitrate > acknowledged_bitrate_max_) {
    acknowledged_bitrate_max_ = acknowledged_bitrate;
  } else {
    // 同理當(dāng)time_passed越大的時候這個ack碼率的最大值會越逼近當(dāng)前tcc feedback的碼率值
    acknowledged_bitrate_max_ -=
        ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) *
        (acknowledged_bitrate_max_ - acknowledged_bitrate);
  }
}

LossBasedBandwidthEstimation 計算基于丟包的碼率

  • 如果把該模塊當(dāng)初一個小黑盒,那么基于Ack碼率,調(diào)用Update()函數(shù)最終會輸出一個lost丟包率的碼率耕捞。
    004.png
void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
  ...
  if (LossBasedBandwidthEstimatorV1ReadyForUse()) {
    DataRate new_bitrate = loss_based_bandwidth_estimator_v1_.Update(
        at_time, min_bitrate_history_.front().second, delay_based_limit_,
        last_round_trip_time_);
    UpdateTargetBitrate(new_bitrate, at_time);
    return;
  }
  ...
}
/**
 * min_bitrate:為1秒內(nèi)最小碼率
 * wanted_bitrate: 為基于延遲delay_based算出來的碼率信息(也是基于twcc+aimd)模塊估算出來的
 */
DataRate LossBasedBandwidthEstimation::Update(Timestamp at_time,
                                              DataRate min_bitrate,
                                              DataRate wanted_bitrate,
                                              TimeDelta last_round_trip_time) {
  // 這里應(yīng)該為初始狀態(tài),未收到feedback之前
  if (loss_based_bitrate_.IsZero()) {
    loss_based_bitrate_ = wanted_bitrate;
  }
  // Only increase if loss has been low for some time.
  // 是否增加帶寬使用平均最大丟包率和閾值進行比較
  const double loss_estimate_for_increase = average_loss_max_;
  // Avoid multiple decreases from averaging over one loss spike.
  // 降碼率的條件取當(dāng)前丟包率和平均碼率的最小值
  const double loss_estimate_for_decrease =
      std::min(average_loss_, last_loss_ratio_);
  // 允許降低碼率的條件為首先:上一次feedback未降低碼率衔掸、其次:兩次feedback之間的間隔為當(dāng)前rtt + 300ms
  // 這個300毫秒個人覺得對于高碼率的應(yīng)用場景有點太高了
  const bool allow_decrease =
      !has_decreased_since_last_loss_report_ &&
      (at_time - time_last_decrease_ >=
       last_round_trip_time + config_.decrease_interval);
  // If packet lost reports are too old, dont increase bitrate.
  // 兩次twcc feedback的反饋間隔在6秒內(nèi),則認(rèn)為這個lost_report是有效的(6秒內(nèi)對于高碼率場景是不是太久了點?)
  const bool loss_report_valid =
      at_time - last_loss_packet_report_ < 1.2 * kMaxRtcpFeedbackInterval;
  // 1) 平均丟包率的最大值比reset閾值要小,則認(rèn)為網(wǎng)絡(luò)可能不擁塞了,這里直接取delay_based的碼率
  if (loss_report_valid && config_.allow_resets &&
  if (loss_report_valid && config_.allow_resets &&
      loss_estimate_for_increase < loss_reset_threshold()) {
    loss_based_bitrate_ = wanted_bitrate;
  } else if (loss_report_valid &&
             loss_estimate_for_increase < loss_increase_threshold()) {
    // Increase bitrate by RTT-adaptive ratio.
    //2)平均丟包率最大值比loss_increase_threshold閾值小則增加碼率,以GetIncreaseFactor()作為系數(shù)
    //  當(dāng)前1秒內(nèi)最小碼率作為base進行遞增,并且遞增規(guī)則是和RTT相關(guān)的,其中這個GetIncreaseFactor(config_, last_round_trip_time)
    //  的返回值在[1.02,1.08]之間砸脊,當(dāng)RTT越大,這個因子越逼近1.02,也就是緩慢增加,當(dāng)RTT越小則越逼近1.08,也就是快速增加
    //  而config_.increase_offset為1kbps纬霞,是一個補償
    DataRate new_increased_bitrate =
        min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) +
        config_.increase_offset;
    // The bitrate that would make the loss "just high enough".
    // 確保遞增的碼率在預(yù)設(shè)的范圍內(nèi)new_increased_bitrate_cap = 0.5kbps * (1/average_loss_max_)^2
    // 這個丟包率越小凌埂,可遞增到的碼率值會越大,假設(shè)0.001的丟包率,那么能增加到的碼率值為500Mbps...
    const DataRate new_increased_bitrate_cap = BitrateFromLoss(
        loss_estimate_for_increase, config_.loss_bandwidth_balance_increase/*0.5kbps*/,
        config_.loss_bandwidth_balance_exponent/*0.5*/);
    // 所以這里會限制最大能增加的范圍,也就是說丟包率越低诗芜,會越接近于GetIncreaseFactor計算出來的結(jié)果
    new_increased_bitrate =
        std::min(new_increased_bitrate, new_increased_bitrate_cap);

    loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_);
  } else if (loss_estimate_for_decrease > loss_decrease_threshold() &&
             allow_decrease) {
    // The bitrate that would make the loss "just acceptable".
    //3)當(dāng)前最小丟包值比loss_decrease_threshold閾值大則進行帶寬遞減
    //  new_decreased_bitrate_floor = 4kbps * (1/loss_estimate_for_decrease)^2
    //  假設(shè)10%的丟包率,那么最低能降到400Kbps,丟包率越大能降低到的程度就會越大瞳抓,最終new_decreased_bitrate_floor就會越小
    const DataRate new_decreased_bitrate_floor = BitrateFromLoss(
        loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease/*4kbps*/,
        config_.loss_bandwidth_balance_exponent/*0.5*/);
    // decreased_bitrate()為0.99倍的ack最大碼率,這里是取0.99 * ack_max和new_decreased_bitrate_floor的最大值
    DataRate new_decreased_bitrate =
        std::max(decreased_bitrate(), new_decreased_bitrate_floor);
    // 如果新遞減后的碼率比loss_based_bitrate_要小,設(shè)置loss_based_bitrate_為最小值
    if (new_decreased_bitrate < loss_based_bitrate_) {
      time_last_decrease_ = at_time;
      has_decreased_since_last_loss_report_ = true;
      loss_based_bitrate_ = new_decreased_bitrate;
    }
  }
  return loss_based_bitrate_;
}
  • 原理上事實上和Base版本基本一致。
  • 當(dāng)平均丟包的最大值小于loss_increase_threshold()的時候進行碼率遞增伏恐。
  • 當(dāng)平均丟包的最大值小于loss_reset_threshold()的時候碼率維持不變孩哑。
  • 當(dāng)平均最小丟包率大于loss_decrease_threshold()的時候進行碼率遞減。

GetIncreaseFactor遞增因子計算原理

// Increase slower when RTT is high.
double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) {
  // Clamp the RTT
  // 如果當(dāng)前rtt小于200ms翠桦,則取rtt為200Ms
  if (rtt < config.increase_low_rtt) {
    rtt = config.increase_low_rtt;
  } else if (rtt > config.increase_high_rtt) {//800ms
    // 如果當(dāng)前rtt大于200ms横蜒,則取rtt為800ms
    rtt = config.increase_high_rtt;
  }
  // 這里其實就是限制rtt的范圍為[increase_low_rtt, increase_high_rtt]
  
  // 默認(rèn)實現(xiàn)不成立,假設(shè)強制設(shè)置不成立,則返回config.min_increase_factor胳蛮,默認(rèn)為1.02
  auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt;
  if (rtt_range <= TimeDelta::Zero()) {
    RTC_DCHECK_NOTREACHED();  // Only on misconfiguration.
    return config.min_increase_factor;
  }
  // modify rtt - 200
  auto rtt_offset = rtt - config.increase_low_rtt;
  // relative_offset限制在[0,1.0]之間
  auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0));
  // 1.08 - 1.02 = 0.06
  auto factor_range = config.max_increase_factor - config.min_increase_factor;
  // 1.02 +  0.06 * (1 - relative_offset) ,其中relative_offset為rtt_offset / rtt_range小于1
  return config.min_increase_factor + (1 - relative_offset) * factor_range;
}
  • config.min_increase_factor默認(rèn)為1.02config.max_increase_factor默認(rèn)為1.08丛晌。
  • 從上述實現(xiàn)來看仅炊,遞增的規(guī)則為,最小系數(shù)為1.02澎蛛,最大為1.08,當(dāng)RTT越小這個增加因子會越逼近于1,08也就是碼率增加得越快抚垄。
  • 當(dāng)RTT越大則越逼近1.02,也就是碼率增加得相對越緩慢一些谋逻。

BitrateFromLoss帶寬增加或減少閾值計算原理

DataRate BitrateFromLoss(double loss,
                         DataRate loss_bandwidth_balance,
                         double exponent) {
  if (exponent <= 0) {
    RTC_DCHECK_NOTREACHED();
    return DataRate::Infinity();
  }
  // 這里注意,如果丟包率小于十萬分之1,那么返回正無窮呆馁,這樣每次帶寬增加會按照[1.02,1.08]*(一秒內(nèi)最小碼率)遞增
  if (loss < 1e-5)
    return DataRate::Infinity();
  return loss_bandwidth_balance * pow(loss, -1.0 / exponent);
}
  • loss為丟包率。
  • loss_bandwidth_balance為因丟包導(dǎo)致的帶寬損耗毁兆,舉個例子假設(shè)loss0.05浙滤,當(dāng)前帶寬為bitrate,那么重傳導(dǎo)致的帶寬損耗為bitrate * 0.05荧恍。
  • 這里的思想就是每次重傳引入的帶寬損耗為loss_bandwidth_balance = bitate * loss瓷叫。
  • 那么N次重傳引入的帶寬損耗為loss_bandwidth_balance = bitate * loss^N次方。
  • 有了上述的思想送巡,再來反推摹菠,已知loss(丟包率)loss_bandwidth_balance(帶寬損耗)骗爆、exponent(重傳次數(shù)的倒數(shù))來求當(dāng)前的bitrate(當(dāng)前帶寬信息)次氨。
  • 反推公式就為bitate = loss_bandwidth_balance * pow(loss, -1.0 / exponent) = loss_bandwidth_balance * (1/loss)^(1/exponent)
  • 有了如上的推導(dǎo)和思路的理解后再回過頭分析摘投,代碼增加和降低的邏輯就不難分析了煮寡。

LossBasedBandwidthEstimation 動態(tài)丟包率閾值的計算原理

double LossFromBitrate(DataRate bitrate,
                       DataRate loss_bandwidth_balance,
                       double exponent) {
  if (loss_bandwidth_balance >= bitrate)
    return 1.0;
  return pow(loss_bandwidth_balance / bitrate, exponent);
}
  • 已知bitrate(目標(biāo)碼率)loss_bandwidth_balance(帶寬損耗碼率)犀呼、exponent損耗次數(shù)的倒數(shù)幸撕,求丟包率
  • 上節(jié)提到N次重傳引入的帶寬損耗為loss_bandwidth_balance = bitate * loss^N次方。
  • 反過來loss = (loss_bandwidth_balance / bitate)^(1/N) = std::pow(loss_bandwidth_balance / bitrate, 1/N)外臂,其中1/N = exponent坐儿。
double LossBasedBandwidthEstimation::loss_reset_threshold() const {
  // (0.1 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為0.1kbps情況下的損耗率(丟包率)
  return LossFromBitrate(loss_based_bitrate_,
                         config_.loss_bandwidth_balance_reset,
                         config_.loss_bandwidth_balance_exponent);
}
  • 假設(shè)按照30fps,每幀一個包,每個包的大小1000字節(jié)宋光,也就是8000bit來算貌矿,也就是目標(biāo)碼率為8000 * 30 = 240kbps,這樣算出來的丟包率大約為0.02041241452也就是2%的丟包率。意思是當(dāng)平均最大丟包率小于這個值的時候罪佳,保持碼率不變逛漫。
  • 而從上述公式來看,當(dāng)實時碼率也就是loss_based_bitrate_越大赘艳,這個丟包率的閾值越小酌毡,對丟包率的容忍度越低克握,這個看上起是符合預(yù)期的
double LossBasedBandwidthEstimation::loss_increase_threshold() const {
  //(0.5 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為0.5kbps情況下的損耗率(丟包率)
  return LossFromBitrate(loss_based_bitrate_,
                         config_.loss_bandwidth_balance_increase,
                         config_.loss_bandwidth_balance_exponent);
}
  • 對于碼率增加的動態(tài)丟包閾值,也是一樣的阔馋,當(dāng)實時碼率越大玛荞,那么要想增加碼率,則期望的丟包率越小越有可能呕寝,同樣是碼率越大勋眯,丟包的容忍度越低。
double LossBasedBandwidthEstimation::loss_decrease_threshold() const {
  //(4 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為4kbps情況下的損耗率(丟包率)
  return LossFromBitrate(loss_based_bitrate_,
                         config_.loss_bandwidth_balance_decrease,
                         config_.loss_bandwidth_balance_exponent);
}
  • 而對于遞減邏輯來看下梢,實時碼率越大客蹋,算出來的丟包率閾值也同樣是越小,也就是說當(dāng)碼率越高孽江,那丟包率稍微上去就有可能觸發(fā)帶寬遞減邏輯讶坯,同樣是碼率越大,丟包容忍度越低岗屏。

總結(jié)

  • LossBasedBandwidthEstimation模塊實現(xiàn)來看辆琅,其根本的碼率升降和維持邏輯和原始Base版本是差不多的。
  • 不同點在于在丟包率閾值的決策上使用了動態(tài)計算來進行處理这刷,同時這個動態(tài)丟包率閾值的計算借助了每次重傳帶寬損失的思想婉烟。
  • 在目標(biāo)碼率為bitrate的情況下,假設(shè)丟包率為lost暇屋,那么第一次重傳的帶寬損失為bitrate * lost似袁,而第二次重傳是基于上次丟失包的情況下進行重傳,所以第N次的重傳帶寬損失為bitrate * lost^N咐刨。
  • 了解重傳對帶寬損失的思想后再去分析該模塊相對就容易理解昙衅。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者定鸟。
  • 序言:七十年代末而涉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子联予,更是在濱河造成了極大的恐慌啼县,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躯泰,死亡現(xiàn)場離奇詭異谭羔,居然都是意外死亡华糖,警方通過查閱死者的電腦和手機麦向,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來客叉,“玉大人诵竭,你說我怎么就攤上這事话告。” “怎么了卵慰?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵沙郭,是天一觀的道長。 經(jīng)常有香客問我裳朋,道長病线,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任鲤嫡,我火速辦了婚禮送挑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暖眼。我一直安慰自己惕耕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布诫肠。 她就那樣靜靜地躺著司澎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栋豫。 梳的紋絲不亂的頭發(fā)上挤安,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機與錄音笼才,去河邊找鬼漱受。 笑死,一個胖子當(dāng)著我的面吹牛骡送,可吹牛的內(nèi)容都是我干的昂羡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摔踱,長吁一口氣:“原來是場噩夢啊……” “哼虐先!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起派敷,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛹批,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篮愉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腐芍,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年试躏,在試婚紗的時候發(fā)現(xiàn)自己被綠了猪勇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡颠蕴,死狀恐怖泣刹,靈堂內(nèi)的尸體忽然破棺而出助析,到底是詐尸還是另有隱情,我是刑警寧澤椅您,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布外冀,位于F島的核電站,受9級特大地震影響掀泳,放射性物質(zhì)發(fā)生泄漏雪隧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一员舵、第九天 我趴在偏房一處隱蔽的房頂上張望膀跌。 院中可真熱鬧,春花似錦固灵、人聲如沸捅伤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丛忆。三九已至,卻和暖如春仍秤,著一層夾襖步出監(jiān)牢的瞬間熄诡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工诗力, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凰浮,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓苇本,卻偏偏與公主長得像袜茧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓣窄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

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