Android藍(lán)牙協(xié)議棧fluoride(十) - 音樂播放(3)

上一篇文章介紹了btif層中A2DP角色管理以及狀態(tài)機(jī)肉盹,本文將介紹A2DP音頻相關(guān)的內(nèi)容罕扎,包括音頻流、解碼等沪么。

概述

音頻流向如下圖:


建立AVDTP協(xié)議連接之后,當(dāng)Source端需要播放時(shí)會(huì)通過AVDTP協(xié)議發(fā)送通過RTP格式封裝的音頻數(shù)據(jù)包锌半,收到數(shù)據(jù)包之后協(xié)議棧中選用連接時(shí)約定的編碼器以及參數(shù)進(jìn)行解碼禽车,解碼成PCM數(shù)據(jù)之后寫入到音頻模塊進(jìn)行播放。A2DP Profile連接建立過程如下:

  1. Source端會(huì)獲取Sink端支持幾個(gè)解碼器(SEP, Stream End Point)刊殉。
  2. Source端獲取每個(gè)SEP的配置(Capabilites)殉摔。
  3. 根據(jù)Source端支持的配置情況選擇一個(gè)配置設(shè)置給Sink端。
    此時(shí)记焊,已經(jīng)為后面?zhèn)鬏斠纛l流做好了準(zhǔn)備钦勘,需要播放時(shí)即可發(fā)送音頻流進(jìn)行播放。


編解碼器

管理

在Fluoride協(xié)議棧中亚亲,解碼器配置彻采、解碼等的實(shí)現(xiàn)在stack/a2dp目錄中。A2DP Profile中規(guī)定藍(lán)牙播放必須支持SBC編解碼器捌归,其他的可以選肛响,現(xiàn)在常見的有編解碼器有:SBC、AAC惜索、APTx特笋、LDAC、LHDC等巾兆,除SBC和AAC在profile協(xié)議中有定義猎物,其他都都是自定義編解碼器。不同編解碼器由不同的人/廠商實(shí)現(xiàn)角塑,因此在A2DP協(xié)議上使用以下類型區(qū)分不同的編解碼器:

// SBC codec
#define A2DP_MEDIA_CT_SBC 0x00
// AAC codec
#define A2DP_MEDIA_CT_AAC 0x02
// 自定義codec
#define A2DP_MEDIA_CT_NON_A2DP 0xFF

所有自定義的codec都是FF蔫磨,具體細(xì)分在自定義的capabilities中用vendor id和codec id區(qū)分,capabilities結(jié)構(gòu)中圃伶,前面兩個(gè)字段為vendor id和codec id堤如,如下圖:


其中每個(gè)廠商的vendor id不能重復(fù),一個(gè)廠商多個(gè)codec的codec id也不能重復(fù)窒朋,如LDAC:

static const tA2DP_LDAC_CIE a2dp_ldac_source_caps = {
    A2DP_LDAC_VENDOR_ID,  // vendorId
    A2DP_LDAC_CODEC_ID,   // codecId
    ...
};

在代碼邏輯中為了區(qū)分不同角色的編解碼器搀罢,使用以下枚舉表示不同的codec(每個(gè)codec的index的值應(yīng)該是不同的):

typedef enum {
  BTAV_A2DP_CODEC_INDEX_SOURCE_MIN = 0,
  // Add an entry for each source codec here.
  // NOTE: The values should be same as those listed in the following file:
  //   BluetoothCodecConfig.java
  BTAV_A2DP_CODEC_INDEX_SOURCE_SBC = 0,
  BTAV_A2DP_CODEC_INDEX_SOURCE_AAC,
  BTAV_A2DP_CODEC_INDEX_SOURCE_APTX,
  BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD,
  BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC,
  BTAV_A2DP_CODEC_INDEX_SOURCE_MAX,

  BTAV_A2DP_CODEC_INDEX_SINK_MIN = BTAV_A2DP_CODEC_INDEX_SOURCE_MAX,
  // Add an entry for each sink codec here
  BTAV_A2DP_CODEC_INDEX_SINK_SBC = BTAV_A2DP_CODEC_INDEX_SINK_MIN,
  BTAV_A2DP_CODEC_INDEX_SINK_AAC,
  BTAV_A2DP_CODEC_INDEX_SINK_LDAC,
  BTAV_A2DP_CODEC_INDEX_SINK_MAX,

  BTAV_A2DP_CODEC_INDEX_MIN = BTAV_A2DP_CODEC_INDEX_SOURCE_MIN,
  BTAV_A2DP_CODEC_INDEX_MAX = BTAV_A2DP_CODEC_INDEX_SINK_MAX
} btav_a2dp_codec_index_t;

核心的類之間的關(guān)系如下:


A2dpCodecs是管理編解碼器的核心數(shù)據(jù)結(jié)構(gòu),管理所有編輯器的配置侥猩,初始化A2dpCodecs時(shí)榔至,根據(jù)btav_a2dp_codec_index_t中定義的枚舉調(diào)用A2dpCodecConfig中的工廠方法createCodec創(chuàng)建各個(gè)編解碼器的配置,然后添加列表中欺劳,其中indexed_codecs_是所有未被禁用的編解碼器的配置唧取,ordered_source_codecs_是所有source使用的編碼器的配置铅鲤,ordered_sink_codecs_是所有sink使用的解碼器的配置。

API接口

不同編解碼器的接口都不相同兵怯,為了調(diào)用方的接口統(tǒng)一彩匕,fluoride中定義了編解碼器的API,有新的編解碼器需要適配時(shí)只需要實(shí)現(xiàn)這些API即可媒区,API原型定義如下:

// 編碼器API
typedef struct {
  // 編碼器初始化
  void (*encoder_init)(const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, A2dpCodecConfig* a2dp_codec_config, a2dp_source_read_callback_t read_callback, a2dp_source_enqueue_callback_t enqueue_callback);
  // 清理編碼器資源
  void (*encoder_cleanup)(void);
  // 重置編碼器
  void (*feeding_reset)(void);
  // 刷新編碼器驼仪,丟掉讀取的數(shù)據(jù)包
  void (*feeding_flush)(void);
  // 獲取編碼間隔
  uint64_t (*get_encoder_interval_ms)(void);
  // 編碼并通過enqueue_callback上報(bào)編碼后數(shù)據(jù)幀
  void (*send_frames)(uint64_t timestamp_us);
  // 設(shè)置 A2DP 編碼器的傳輸隊(duì)列長度
  void (*set_transmit_queue_length)(size_t transmit_queue_length);
} tA2DP_ENCODER_INTERFACE;

//解碼器API
typedef struct {
  // 初始化解碼器
  bool (*decoder_init)(decoded_data_callback_t decode_callback);
  // 清理解碼器
  void (*decoder_cleanup)();
  // 解碼并通過decode_callback上報(bào)解碼后的數(shù)據(jù)幀
  bool (*decode_packet)(BT_HDR* p_buf);
  // 啟動(dòng)解碼器
  void (*decoder_start)();
  // 暫停解碼器
  void (*decoder_suspend)();
  // 配置解碼器
  void (*decoder_configure)(const uint8_t* p_codec_info);
} tA2DP_DECODER_INTERFACE;

從源碼中可以看到,分別實(shí)現(xiàn)了sbc袜漩、aac绪爸、aptx、ldac的接口(具體的接口實(shí)現(xiàn)參考源碼)宙攻,并可以根據(jù)當(dāng)前A2DP連接協(xié)商的編解碼器類型獲取接口:

const tA2DP_ENCODER_INTERFACE* A2DP_GetEncoderInterface(const uint8_t* p_codec_info) {
  tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(p_codec_info);
  LOG_VERBOSE("%s: codec_type = 0x%x", __func__, codec_type);
  switch (codec_type) {
    case A2DP_MEDIA_CT_SBC:
      return A2DP_GetEncoderInterfaceSbc(p_codec_info);
#if !defined(EXCLUDE_NONSTANDARD_CODECS)
    case A2DP_MEDIA_CT_AAC:
      return A2DP_GetEncoderInterfaceAac(p_codec_info);
    case A2DP_MEDIA_CT_NON_A2DP:
      return A2DP_VendorGetEncoderInterface(p_codec_info);
#endif
    default:
      break;
  }
  LOG_ERROR("%s: unsupported codec type 0x%x", __func__, codec_type);
  return NULL;
}

const tA2DP_DECODER_INTERFACE* A2DP_GetDecoderInterface(const uint8_t* p_codec_info) {
  tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(p_codec_info);
  LOG_VERBOSE("%s: codec_type = 0x%x", __func__, codec_type);
  switch (codec_type) {
    case A2DP_MEDIA_CT_SBC:
      return A2DP_GetDecoderInterfaceSbc(p_codec_info);
#if !defined(EXCLUDE_NONSTANDARD_CODECS)
    case A2DP_MEDIA_CT_AAC:
      return A2DP_GetDecoderInterfaceAac(p_codec_info);
    case A2DP_MEDIA_CT_NON_A2DP:
      return A2DP_VendorGetDecoderInterface(p_codec_info);
#endif
    default:
      break;
  }

如果有新增編解碼器的需求奠货,可以在stack/a2dp目錄中參考vendor相關(guān)的文件適配對(duì)應(yīng)的API桩引。

配置

同樣的匿垄,不同編碼器的配置不相同,管理起來十分混亂佑惠,因此在代碼中抽象出了A2dpCodecConfig接口用于統(tǒng)一管理編解碼器的配置溢陪。在fluoride中有3中形式的編碼器配置萍虽,分別是btav_a2dp_codec_config_ttA2DP_SBC_CIE形真、byte sequence杉编,它們之間的關(guān)系如下圖:

圖中具體編碼器相關(guān)的都以SBC為例,其他編碼器將其中的sbc替換為相應(yīng)的編碼器名稱即可咆霜,如:tA2DP_AAC_CIE邓馒、A2DP_BuildInfoAacA2DP_ParseInfoAac蛾坯,以此類推)光酣,往后不再特別說明

三種形式用于不同地方,btav_a2dp_codec_config_t用于音頻相關(guān)配置以及更應(yīng)用層偿衰,它們主要關(guān)注音頻格式挂疆,如采樣率、位寬下翎、聲道數(shù)等信息,它們不需要關(guān)注具體編碼器相關(guān)的配置宝当;tA2DP_SBC_CIE用于fluoride協(xié)議棧代碼中對(duì)編碼器配置的描述视事,以結(jié)構(gòu)體的形式表示可讀性更高更方便計(jì)算,它表示了具體某個(gè)編碼器的詳細(xì)的配置庆揩,每個(gè)編碼器的這個(gè)結(jié)構(gòu)體是不同的俐东;byte sequence用于A2DP協(xié)議通信時(shí)對(duì)編解碼器配置的描述跌穗,每個(gè)編解碼器的字節(jié)序都在A2DP協(xié)議中有規(guī)定(具體可參考A2DP profile spec),代碼中通過A2DP_BuildInfoSbcA2DP_ParseInfoSbc實(shí)現(xiàn)tA2DP_SBC_CIEbyte sequence的相互轉(zhuǎn)換虏辫, 在A2dpCodecConfigSbcBase::setCodecConfig中實(shí)現(xiàn)btav_a2dp_codec_config_ttA2DP_SBC_CIE的轉(zhuǎn)換蚌吸。
A2dpCodecConfig的核心成員和方法如下:

class A2dpCodecConfig {
 public:
  // 根據(jù)codec_index創(chuàng)建對(duì)應(yīng)的codec實(shí)例
  static A2dpCodecConfig* createCodec(btav_a2dp_codec_index_t codec_index,
                                    btav_a2dp_codec_priority_t codec_priority = BTAV_A2DP_CODEC_PRIORITY_DEFAULT);
  // 獲取codec的特定配置
  bool getCodecSpecificConfig(tBT_A2DP_OFFLOAD* p_a2dp_offload);
  // 獲取codec的配置
  btav_a2dp_codec_config_t getCodecConfig();
  // 獲取codec的能力
  btav_a2dp_codec_config_t getCodecCapability();
  // 獲取本地編碼器的能力
  btav_a2dp_codec_config_t getCodecLocalCapability();
  // 獲取codec可供選擇的能力,本地codec和對(duì)端codec能力的交集
  btav_a2dp_codec_config_t getCodecSelectableCapability();
  // 獲取codec用戶配置
  btav_a2dp_codec_config_t getCodecUserConfig();
  // 獲取編碼器音頻配置
  btav_a2dp_codec_config_t getCodecAudioConfig();
 protected:
  // 設(shè)置Source和Sink共同使用的codec的配置
  virtual bool setCodecConfig(const uint8_t* p_peer_codec_info, 
                bool is_capability,
                uint8_t* p_result_codec_config) = 0;
  // 設(shè)置用戶首選的codec的配置
  virtual bool setCodecUserConfig(
      const btav_a2dp_codec_config_t& codec_user_config,
      const btav_a2dp_codec_config_t& codec_audio_config,
      const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
      const uint8_t* p_peer_codec_info, bool is_capability,
      uint8_t* p_result_codec_config, bool* p_restart_input,
      bool* p_restart_output, bool* p_config_updated);
  // 用用戶首選編解碼器配置更新編碼器
  virtual bool updateEncoderUserConfig(
      const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
      bool* p_restart_input, bool* p_restart_output,
      bool* p_config_updated) = 0;
  // 設(shè)置對(duì)端設(shè)備的編解碼能力砌庄。
  virtual bool setPeerCodecCapabilities(
      const uint8_t* p_peer_codec_capabilities) = 0;
  // codec index
  const btav_a2dp_codec_index_t codec_index_; 
  // codec配置
  btav_a2dp_codec_config_t codec_config_;
  // codec能力
  btav_a2dp_codec_config_t codec_capability_;
  // 本地codec的能力
  btav_a2dp_codec_config_t codec_local_capability_;
  // codec可供選擇的能力
  btav_a2dp_codec_config_t codec_selectable_capability_;
  // 用戶配置羹唠。當(dāng)有選擇時(shí),這些值(如果已設(shè)置)將作為首選項(xiàng)使用娄昆。如果本地或遠(yuǎn)程設(shè)備不支持某一特定值佩微,該值將被忽略。
  btav_a2dp_codec_config_t codec_user_config_;
  // 協(xié)商的音頻配置
  btav_a2dp_codec_config_t codec_audio_config_;
  // 雙方協(xié)商的codec配置萌焰,A2DP協(xié)議中使用的配置
  uint8_t ota_codec_config_[AVDT_CODEC_SIZE];
  // 對(duì)端codec的能力
  uint8_t ota_codec_peer_capability_[AVDT_CODEC_SIZE];
  // 對(duì)端codec的配置
  uint8_t ota_codec_peer_config_[AVDT_CODEC_SIZE];
};

A2dpCodecConfig中看到有configcapability相關(guān)成員哺眯,它們關(guān)系是:capability表示codec可以支持的所有config,以采樣率為例扒俯,codec可以支持44.1KHz奶卓、48KHz,因此采樣率的capabilityA2DP_SBC_IE_SAMP_FREQ_44 | BTAV_A2DP_CODEC_SAMPLE_RATE_48000撼玄,但codec在編碼時(shí)夺姑,采樣率要么為44.1KHz(A2DP_SBC_IE_SAMP_FREQ_44)要么是48KHz(BTAV_A2DP_CODEC_SAMPLE_RATE_48000),兩個(gè)不能同時(shí)存在互纯。以下是SBC 的capability和config:

/* SBC Source codec capabilities */
static const tA2DP_SBC_CIE a2dp_sbc_source_caps = {
    (A2DP_SBC_IE_SAMP_FREQ_44),                         /* samp_freq */
    (A2DP_SBC_IE_CH_MD_MONO | A2DP_SBC_IE_CH_MD_JOINT), /* ch_mode */
    (A2DP_SBC_IE_BLOCKS_16 | A2DP_SBC_IE_BLOCKS_12 | A2DP_SBC_IE_BLOCKS_8 |
     A2DP_SBC_IE_BLOCKS_4),            /* block_len */
    A2DP_SBC_IE_SUBBAND_8,             /* num_subbands */
    A2DP_SBC_IE_ALLOC_MD_L,            /* alloc_method */
    A2DP_SBC_IE_MIN_BITPOOL,           /* min_bitpool */
    A2DP_SBC_MAX_BITPOOL,              /* max_bitpool */
    BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 /* bits_per_sample */
};

/* SBC Sink codec capabilities */
static const tA2DP_SBC_CIE a2dp_sbc_sink_caps = {
    (A2DP_SBC_IE_SAMP_FREQ_48 | A2DP_SBC_IE_SAMP_FREQ_44), /* samp_freq */
    (A2DP_SBC_IE_CH_MD_MONO | A2DP_SBC_IE_CH_MD_STEREO | A2DP_SBC_IE_CH_MD_JOINT | A2DP_SBC_IE_CH_MD_DUAL), /* ch_mode */
    (A2DP_SBC_IE_BLOCKS_16 | A2DP_SBC_IE_BLOCKS_12 | A2DP_SBC_IE_BLOCKS_8 | A2DP_SBC_IE_BLOCKS_4),                            /* block_len */
    (A2DP_SBC_IE_SUBBAND_4 | A2DP_SBC_IE_SUBBAND_8),   /* num_subbands */
    (A2DP_SBC_IE_ALLOC_MD_L | A2DP_SBC_IE_ALLOC_MD_S), /* alloc_method */
    A2DP_SBC_IE_MIN_BITPOOL,                           /* min_bitpool */
    A2DP_SBC_MAX_BITPOOL,                              /* max_bitpool */
    BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16                 /* bits_per_sample */
};

/* Default SBC codec configuration */
const tA2DP_SBC_CIE a2dp_sbc_default_config = {
    A2DP_SBC_IE_SAMP_FREQ_44,          /* samp_freq */
    A2DP_SBC_IE_CH_MD_JOINT,           /* ch_mode */
    A2DP_SBC_IE_BLOCKS_16,             /* block_len */
    A2DP_SBC_IE_SUBBAND_8,             /* num_subbands */
    A2DP_SBC_IE_ALLOC_MD_L,            /* alloc_method */
    A2DP_SBC_IE_MIN_BITPOOL,           /* min_bitpool */
    A2DP_SBC_MAX_BITPOOL,              /* max_bitpool */
    BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 /* bits_per_sample */
};

參數(shù)協(xié)商

想要Source和Sink可以正確的編解碼數(shù)據(jù)瑟幕,那么需要雙方的編碼器類型和編碼器參數(shù)配置相同,因此在傳輸音頻流之前會(huì)協(xié)商編解碼器參數(shù)留潦,即Source獲取Sink的codec的capabilities只盹,然后計(jì)算出自己codec和對(duì)端codec capabilities的交集,最后從交集中選擇合適的config設(shè)置到Sink兔院。這個(gè)過程中會(huì)使用的一個(gè)關(guān)鍵的函數(shù)A2dpCodecConfig::setCodecConfig殖卑,它的作用是選擇出Source和Sink共同的codec的配置,其步驟如下:

  1. 判斷用戶是否設(shè)置了參數(shù)坊萝,如有且本地和對(duì)端都支持這些參數(shù)孵稽,則使用該參數(shù),否則進(jìn)入第2步十偶;
  2. 本地和對(duì)端都設(shè)置了內(nèi)部默認(rèn)值菩鲜,則使用該參數(shù),否則進(jìn)入第3步惦积;
  3. 在本地和對(duì)段都支持的配置中選擇最佳匹配值接校,到此參數(shù)選擇完成。
    參數(shù)選擇完成后需要同步更新編解碼器內(nèi)部參數(shù)狮崩,同時(shí)向應(yīng)用層以及媒體框架上報(bào)該參數(shù)蛛勉,如果用戶設(shè)置了參數(shù)鹿寻,則只會(huì)上報(bào)用戶設(shè)置的參數(shù),否則將上報(bào)所有可以選擇的參數(shù)(本地和對(duì)端都支持的配置)诽凌。
    雖然每個(gè)codec協(xié)商參數(shù)時(shí)都是執(zhí)行這些步驟毡熏,但由于各個(gè)編碼器的配置不一樣,所以這個(gè)函數(shù)會(huì)在具體的codec中實(shí)現(xiàn)侣诵。下面是SBC中(在A2dpCodecConfigSbcBase::setCodecConfig中實(shí)現(xiàn))該函數(shù)的程序流程:
流程圖中只畫出了一個(gè)config項(xiàng)痢法,實(shí)際上每個(gè)codec都有多個(gè)config項(xiàng),但它們執(zhí)行的流程完全一樣窝趣,因此在圖中沒有話出來疯暑。支持用戶設(shè)置的config(如:采樣率、位寬哑舒、聲道)會(huì)加入計(jì)算中妇拯,而其他不支持用戶設(shè)置的config(SBC中如:sub-band
bitpool等)則會(huì)直接跳過對(duì)用戶設(shè)置的計(jì)算。在這個(gè)函數(shù)中除了執(zhí)行上面的動(dòng)作外洗鸵,還會(huì)更新`A2dpCodecConfig`中定義的字段越锈,如:
  • ota_codec_config_: 保存計(jì)算出codec參數(shù),即雙方編解碼器最終使用的配置膘滨。
  • ota_codec_peer_config_: 保存對(duì)端的編解碼器的配置甘凭,如果setCodecConfig中參數(shù)is_capabilityfalse,這個(gè)字段會(huì)更新為參數(shù)p_peer_codec_info中的內(nèi)容
  • ota_codec_peer_capability_: 保存對(duì)端的編解碼器的能力火邓,如果setCodecConfig中參數(shù)is_capabilitytrue丹弱,這個(gè)字段會(huì)更新為參數(shù)p_peer_codec_info中的內(nèi)容
  • 其他的如:codec_config_codec_capability_铲咨、codec_selectable_capability_都會(huì)在計(jì)算過程中更新
    如果計(jì)算過程中發(fā)現(xiàn)參數(shù)不合法或者沒有共同的配置躲胳,則還原成計(jì)算前的配置。

除了setCodecConfig這個(gè)公共的函數(shù)外纤勒,還有一個(gè)公共的函數(shù)A2dpCodecConfig::setCodecUserConfig坯苹,它的內(nèi)部也是調(diào)用setCodecConfig來實(shí)現(xiàn)的,函數(shù)流程如下:

基于這兩個(gè)函數(shù)摇天,A2dpCodecs中分別實(shí)現(xiàn)了用于各個(gè)場景下設(shè)置codec參數(shù)的接口:A2dpCodecs::setCodecConfig粹湃、A2dpCodecs::setSinkCodecConfigA2dpCodecs::setCodecUserConfig泉坐、A2dpCodecs::setCodecAudioConfig为鳄、A2dpCodecs::setCodecOtaConfig,應(yīng)這幾個(gè)函數(shù)相對(duì)比較簡單腕让,這里就不一一贅述济赎,詳細(xì)實(shí)現(xiàn)可以參考源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末记某,一起剝皮案震驚了整個(gè)濱河市司训,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌液南,老刑警劉巖壳猜,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滑凉,居然都是意外死亡统扳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門畅姊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咒钟,“玉大人,你說我怎么就攤上這事若未≈熳欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵粗合,是天一觀的道長萍嬉。 經(jīng)常有香客問我,道長隙疚,這世上最難降的妖魔是什么壤追? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮供屉,結(jié)果婚禮上行冰,老公的妹妹穿的比我還像新娘。我一直安慰自己伶丐,他們只是感情好悼做,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撵割,像睡著了一般贿堰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啡彬,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天羹与,我揣著相機(jī)與錄音,去河邊找鬼庶灿。 笑死纵搁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的往踢。 我是一名探鬼主播腾誉,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了利职?” 一聲冷哼從身側(cè)響起趣效,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猪贪,沒想到半個(gè)月后跷敬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡热押,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年西傀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桶癣。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拥褂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牙寞,到底是詐尸還是另有隱情饺鹃,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布碎税,位于F島的核電站尤慰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雷蹂。R本人自食惡果不足惜伟端,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匪煌。 院中可真熱鬧责蝠,春花似錦、人聲如沸萎庭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驳规。三九已至肴敛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吗购,已是汗流浹背医男。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捻勉,地道東北人镀梭。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像踱启,于是被迫代替她去往敵國和親报账。 傳聞我的和親對(duì)象是個(gè)殘疾皇子研底,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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