ffplay.c源碼閱讀之音頻颁湖、字幕宣蠕、視頻渲染原理(一)

前言

之前陸續(xù)學(xué)習(xí)了視頻渲染相關(guān)技術(shù)opengl es,視頻編解碼相關(guān)技術(shù)(基于ffmpeg封裝接口的使用)甥捺,雖然擁有了這些基礎(chǔ)知識(shí)抢蚀,但是離寫(xiě)出一個(gè)功能完善的播放器還有一段距離,我覺(jué)得可以先學(xué)學(xué)ffplay.c镰禾,ijkplayer等等開(kāi)源播放器他們是如何實(shí)現(xiàn)的皿曲,然后學(xué)以致用。所以從今天開(kāi)始將逐步學(xué)習(xí)ffplay.c的實(shí)現(xiàn)方式吴侦。我認(rèn)為閱讀源碼的方式如果帶著問(wèn)題舉一反三的思維去理解代碼思路屋休,這樣可能印象更加深刻。首先拋出問(wèn)題”如果讓我實(shí)現(xiàn)我會(huì)怎么做备韧,開(kāi)源是怎么實(shí)現(xiàn)的劫樟,它為什么這么做,這么做的優(yōu)缺點(diǎn)是什么?“ 所以我讀ffplay.c的源碼也會(huì)按照這樣的思路來(lái)

拋出問(wèn)題

在實(shí)現(xiàn)一個(gè)播放器時(shí)织堂,會(huì)涉及到如下三個(gè)部分


image.png

拉流:從本地或者遠(yuǎn)程讀取壓縮音視頻數(shù)據(jù)
解碼:將讀取到的音視頻數(shù)據(jù)進(jìn)行解碼得到未壓縮音視頻數(shù)據(jù)
渲染:將解碼得到的未壓縮視頻渲染到屏幕叠艳、未壓縮音頻通過(guò)揚(yáng)聲器播放

那么應(yīng)該創(chuàng)建幾個(gè)線(xiàn)程來(lái)做這些事情?首先拉流肯定要單獨(dú)的線(xiàn)程易阳,解碼和渲染是否應(yīng)該放到一個(gè)線(xiàn)程里面呢附较,我覺(jué)得應(yīng)該分開(kāi),因?yàn)椴环珠_(kāi)的話(huà)流程就是潦俺,解碼成功->渲染-->解碼下一幀-->渲染 一直循環(huán)拒课。對(duì)于I、B事示、P幀解碼所花時(shí)間會(huì)不一樣早像,那么渲染時(shí)間非等間隔的就會(huì)導(dǎo)致播放不那么流暢。綜上很魂,應(yīng)該有拉流一個(gè)線(xiàn)程(音視頻共用)扎酷,解碼兩個(gè)(音視頻獨(dú)立),渲染兩個(gè)(音視頻獨(dú)立)

音視頻要分開(kāi)的原因是音視頻渲染間隔是是不同的遏匆。

今天先從渲染部分開(kāi)始閱讀和學(xué)習(xí)
對(duì)于線(xiàn)程的創(chuàng)建實(shí)際上ffplay.c也是這樣實(shí)現(xiàn)的法挨,它的整個(gè)架構(gòu)設(shè)計(jì)圖如下:
截止到ffmpeg 4.2版本,ffplay.c大概有近四千行代碼幅聘。整體的流程圖架構(gòu)設(shè)計(jì)如下:


image.png

ffplay.c的實(shí)現(xiàn)

實(shí)際上ffplay.c也是按照這樣

  • 視頻渲染線(xiàn)程的代碼

這里只貼出關(guān)鍵代碼凡纳。這段代碼的主要工作流程如下:
1、取視頻幀帝蒿;沒(méi)有可渲染視頻幀就返回睡眠荐糜,有則進(jìn)入步驟2
2、取出上一次已渲染視頻幀和當(dāng)前待渲染視頻幀
3、根據(jù)音視頻同步規(guī)則決定當(dāng)前待渲染視頻幀是否立即渲染暴氏,即:如果本幀的播放時(shí)刻(即上一幀的播放時(shí)刻+上一幀的時(shí)長(zhǎng))大于當(dāng)前時(shí)刻延塑,代表本幀的播放時(shí)刻還未到來(lái),渲染時(shí)間未到來(lái)則繼續(xù)播放上一幀(由如下goto語(yǔ)句進(jìn)行跳轉(zhuǎn))答渔,然后將remain_time賦值為本幀的播放時(shí)刻與當(dāng)前時(shí)刻的時(shí)間差值(睡眠remain_time時(shí)間后)下一個(gè)流程再渲染此幀关带;否則進(jìn)入步驟4
4、更新視頻幀F(xiàn)rameQueue隊(duì)列相關(guān)數(shù)據(jù)沼撕,然后渲染本幀(真正執(zhí)行視頻渲染工作的代碼在video_display()函數(shù)中)

步驟1-4循環(huán)調(diào)用

/** 1宋雏、用于控制視頻的顯示
 *  2、如果音視頻同步方式為視頻同步到音頻务豺,則這塊邏輯在此實(shí)現(xiàn)
 */
static void video_refresh(void *opaque, double *remaining_time)
{
    VideoState *is = opaque;
    double time;

    Frame *sp, *sp2;
    ....省略代碼.....

    // rtsp 等實(shí)時(shí)流則is->realtime的值為1磨总,本地文件的播放則為0
    if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
        check_external_clock_speed(is);


    if (is->video_st) {
retry:
        if (frame_queue_nb_remaining(&is->pictq) == 0) { // 步驟1、取視頻幀笼沥;沒(méi)有可渲染視頻幀就返回睡眠蚪燕,
            // nothing to do, no picture to display in the queue
        } else {
            double last_duration, duration, delay;
            Frame *vp, *lastvp;

            /* dequeue the picture */
            // 步驟2、取出上一次已渲染視頻幀和當(dāng)前待渲染視頻幀
            // 首次進(jìn)入此方法敬拓,由于f->rindex_shown是默認(rèn)值邻薯,所以得到的lastvp和vp是同一個(gè)Frame
            lastvp = frame_queue_peek_last(&is->pictq);
            vp = frame_queue_peek(&is->pictq);

            if (vp->serial != is->videoq.serial) {
                frame_queue_next(&is->pictq);
                goto retry;
            }

            if (lastvp->serial != vp->serial)
                is->frame_timer = av_gettime_relative() / 1000000.0;

            if (is->paused)
                goto display;

            
            /* compute nominal last_duration */
            last_duration = vp_duration(is, lastvp, vp);
            /** 這里實(shí)現(xiàn)了視頻同步音頻或者同步系統(tǒng)時(shí)鐘的關(guān)鍵代碼
             *  本幀視頻是否能夠播放的條件為 is->frame_timer (上一幀視頻的播放時(shí)間) + delay(本幀視頻的播放延遲) >= 當(dāng)前系統(tǒng)時(shí)間
             *  delay是基于音頻時(shí)鐘或者系統(tǒng)時(shí)鐘計(jì)算出來(lái)的播放延遲時(shí)間(它的理論值就是本幀pts-上幀pts)
             *  delay>=0 值越小代表視頻播的太慢了,本幀越需要盡快播放乘凸,越大則代表視頻播的太快了,本幀需要延后播放 為0 則視頻有可能慢了音頻至少一個(gè)幀
             */
            delay = compute_target_delay(last_duration, is);
            
            // frame_timer表示上一幀的播放時(shí)刻(這個(gè)時(shí)刻比非實(shí)際顯示到屏幕的時(shí)刻提前一點(diǎn)點(diǎn)時(shí)間)
            time= av_gettime_relative()/1000000.0;
            if (time < is->frame_timer + delay) {   // 步驟3累榜、 如果本幀的播放時(shí)刻(即上一幀的播放時(shí)刻+上一幀的時(shí)長(zhǎng))大于當(dāng)前時(shí)刻营勤,代表本幀的播放時(shí)刻還未到來(lái)
                // 渲染時(shí)間未到來(lái)則繼續(xù)播放上一幀(由如下goto語(yǔ)句進(jìn)行跳轉(zhuǎn)),然后將remain_time賦值為本幀的播放時(shí)刻與當(dāng)前時(shí)刻的時(shí)間差值
                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
                goto display;   //繼續(xù)播放上一幀
            }
            
            // 同步上一幀的播放時(shí)間
            /** 疑問(wèn):這里is->frame_timer += delay;為什么是這樣寫(xiě)的壹罚,而不是直接is->frame_timer = time;呢葛作?
             *  分析:如果直接用is->frame_timer = time;進(jìn)行賦值,那么視頻幀因?yàn)槟撤N原因累積了很多幀未播放時(shí)猖凛,那么會(huì)導(dǎo)致多出來(lái)的視頻幀無(wú)法丟棄
             *  實(shí)際上ffplay有兩條時(shí)鐘赂蠢,一條時(shí)鐘音視頻時(shí)鐘,用于音視頻同步用辨泳,即計(jì)算這里的delay值虱岂,另一條時(shí)鐘frame_timer用來(lái)記錄上一幀的播放時(shí)間
             *  同時(shí)用于計(jì)算是否滿(mǎn)足丟幀的條件
             */
            is->frame_timer += delay;
            if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)    // 處理首幀播放和音視頻幀同時(shí)出現(xiàn)解碼時(shí)間過(guò)程導(dǎo)致的抖動(dòng),這里就需要重新更新上一幀播放時(shí)間為當(dāng)前時(shí)間了
                is->frame_timer = time;
            
            // 步驟4菠红、以下都是
            /** ffplay.c里面有三個(gè)時(shí)間
             *  1第岖、frame_timer:保存在VideoState結(jié)構(gòu)體里面,用以記錄視頻播放的時(shí)間點(diǎn)试溯,該時(shí)間點(diǎn)基于系統(tǒng)時(shí)鐘
             *  2蔑滓、pts:保存在視頻Clock結(jié)構(gòu)體里面,等同于視頻幀的pts
             *  3、pts_drift:視頻幀的pts與視頻播放時(shí)刻的時(shí)間差
             */
            SDL_LockMutex(is->pictq.mutex);
            if (!isnan(vp->pts))
                update_video_pts(is, vp->pts, vp->pos, vp->serial);
            SDL_UnlockMutex(is->pictq.mutex);

            // 如果本幀的pts+duration < time(當(dāng)前時(shí)間)則丟棄該幀(說(shuō)明隊(duì)列中有大量還未渲染的視頻幀键袱,必須得丟掉一些了)
            if (frame_queue_nb_remaining(&is->pictq) > 1) {
                Frame *nextvp = frame_queue_peek_next(&is->pictq);
                duration = vp_duration(is, vp, nextvp);
                if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
                    is->frame_drops_late++;
                    frame_queue_next(&is->pictq);
                    goto retry;
                }
            }

            // FrameQueue隊(duì)列的當(dāng)前讀取指針rindex的值+1(即指向本幀的索引),并且刪除上一幀的Frame數(shù)據(jù)(因?yàn)橐呀?jīng)不需要了)
            frame_queue_next(&is->pictq);
            is->force_refresh = 1;

            if (is->step && !is->paused)
                stream_toggle_pause(is);
        }
display:
        /* display picture */
        // 執(zhí)行渲染本幀的工作
        if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
            video_display(is);
    }
    is->force_refresh = 0;
    }
    ....省略代碼.....
}

/* display the current picture, if any */
static void video_display(VideoState *is)
{
    if (!is->width)
        video_open(is);

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO)
        video_audio_display(is);
    else if (is->video_st)
        video_image_display(is);
    SDL_RenderPresent(renderer);
}

還有一個(gè)很重要的就是如何保證前面的video_refresh()函數(shù)循環(huán)調(diào)用呢燎窘,通過(guò)如下的refresh_loop_wait_event()函數(shù),它通過(guò)SDL庫(kù)檢測(cè)是否有鼠標(biāo)和鍵盤(pán)等事件蹄咖,如果沒(méi)有則一直循環(huán)荠耽,有則退出循環(huán)去處理事件。處理事件又在event_loop()函數(shù)中比藻,它是在main()函數(shù)中啟動(dòng)的铝量。

static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
    double remaining_time = 0.0;
    SDL_PumpEvents();
    while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) { // 沒(méi)有捕捉到事件就去渲染視頻,捕捉到了視頻則先處理事件
        if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
            SDL_ShowCursor(0);
            cursor_hidden = 1;
        }
        if (remaining_time > 0.0)
            av_usleep((int64_t)(remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(is, &remaining_time);
        SDL_PumpEvents();
    }
}

static void event_loop(VideoState *cur_stream)
{
    SDL_Event event;
    double incr, pos, frac;
    /** 學(xué)習(xí):線(xiàn)程的事件循環(huán)隊(duì)列
     *  分析:時(shí)間循環(huán)隊(duì)列的組成一定是 for/while循環(huán)+usleep()休眠+事件捕捉器組成银亲,如果沒(méi)有usleep()休眠那么會(huì)導(dǎo)致cpu空轉(zhuǎn)慢叨,造成大量浪費(fèi)
     *  這里休眠時(shí)間由播放視頻的幀率(間隔)根據(jù)一定的規(guī)則計(jì)算而來(lái),然后如果捕捉到事件則優(yōu)先處理事件务蝠,接著再去播放視頻
     */
    for (;;) {
        double x;
        refresh_loop_wait_event(cur_stream, &event);
        switch (event.type) {
        case SDL_KEYDOWN:
         .......各種鍵盤(pán)和鼠標(biāo)事件....省略
        .....
    }
}

學(xué)習(xí):線(xiàn)程的事件循環(huán)隊(duì)列
分析:事件循環(huán)隊(duì)列的組成一定是 for/while循環(huán)+usleep()休眠+事件捕捉器組成拍谐,如果沒(méi)有usleep()休眠那么會(huì)導(dǎo)致cpu空轉(zhuǎn),造成大量浪費(fèi)這里休眠時(shí)間由播放視頻的幀率(間隔)根據(jù)一定的規(guī)則計(jì)算而來(lái)馏段,然后如果捕捉到事件則優(yōu)先處理事件轩拨,接著再去播放視頻

這一套事件循環(huán)隊(duì)列機(jī)制就保證了視頻持續(xù)播放又能響應(yīng)用戶(hù)鍵盤(pán)和鼠標(biāo)事件。以上就是視頻渲染線(xiàn)程的工作流程和機(jī)制

  • 音頻渲染線(xiàn)程的代碼
    音頻渲染線(xiàn)程院喜,它是通過(guò)SDL內(nèi)部自驅(qū)動(dòng)的一個(gè)回調(diào)函數(shù)亡蓉,被周期性的回調(diào),只需要不停的往里面填充音頻即可進(jìn)行音頻的渲染了喷舀。每一次調(diào)用稱(chēng)為一個(gè)音頻渲染周期
    len:代表需要填充的音頻數(shù)據(jù)長(zhǎng)度;stream代表填充音頻的buffer地址
/* prepare a new audio buffer */
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
    VideoState *is = opaque;
    int audio_size, len1;

    audio_callback_time = av_gettime_relative();
    
    /** 學(xué)習(xí):音頻渲染線(xiàn)程砍濒,它是通過(guò)SDL內(nèi)部自驅(qū)動(dòng)的一個(gè)回調(diào)函數(shù),被周期性的回調(diào)硫麻,只需要不停的往里面填充音頻即可進(jìn)行音頻的渲染了爸邢。每一次調(diào)用稱(chēng)為一個(gè)音頻渲染周期
     *  len:代表需要填充的音頻數(shù)據(jù)長(zhǎng)度;stream代表填充音頻的buffer地址
     *
     *  is->audio_buf_index:表示當(dāng)前渲染周期內(nèi)已拷貝的音頻數(shù)據(jù)字節(jié)的索引,即下一塊音頻數(shù)據(jù)放入stream+is->audio_buf_index的位置
     *  is->audio_buf_size:表示當(dāng)前音頻Frame的字節(jié)數(shù)
     */
    while (len > 0) {
        if (is->audio_buf_index >= is->audio_buf_size) {
           audio_size = audio_decode_frame(is);
           if (audio_size < 0) {
                /* if error, just output silence */
               is->audio_buf = NULL;
               is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
           } else {
               if (is->show_mode != SHOW_MODE_VIDEO)
                   update_sample_display(is, (int16_t *)is->audio_buf, audio_size);
               is->audio_buf_size = audio_size;
           }
           is->audio_buf_index = 0;
        }
        len1 = is->audio_buf_size - is->audio_buf_index;
        if (len1 > len)
            len1 = len;
        if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
            memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        else {
            memset(stream, 0, len1);
            if (!is->muted && is->audio_buf)
                SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, AUDIO_S16SYS, len1, is->audio_volume);
        }
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;
    }
    is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index;
    /* Let's assume the audio driver that is used by SDL has two periods. */
    if (!isnan(is->audio_clock)) {
        set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);
        // 如果音視頻同步方式為同步外部時(shí)鐘拿愧,則調(diào)用此方法會(huì)有用
        sync_clock_to_slave(&is->extclk, &is->audclk);
    }
}

該函數(shù)的開(kāi)啟過(guò)程是隨著拉流線(xiàn)程開(kāi)啟的杠河,在拉流線(xiàn)程內(nèi)通過(guò)stream_component_open()開(kāi)啟音頻流處理

static int read_thread(void *arg){
.....省略代碼.....
/* open the streams */
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
    }
.....省略代碼.....
}

接下來(lái)在音頻流處理流程中開(kāi)啟音頻渲染相關(guān)代碼

static int stream_component_open(VideoState *is, int stream_index){
.....省略代碼.....
 /* prepare audio output */
        if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
            goto fail;
....省略代碼......
}

然后就是初始化SDL音頻渲染相關(guān),在這里指定音頻渲染回調(diào)

static int stream_component_open(VideoState *is, int stream_index)
{
....省略代碼......
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.silence = 0;
wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
wanted_spec.callback = sdl_audio_callback;
wanted_spec.userdata = opaque;
....省略代碼......
}

  • 字幕渲染線(xiàn)程的代碼
    實(shí)際上字幕渲染沒(méi)有單獨(dú)的線(xiàn)程浇辜,它與視頻共用一個(gè)線(xiàn)程券敌,可以看到這段代碼和視頻渲染在同一個(gè)函數(shù)中的,前面說(shuō)道奢赂,視頻渲染流程到了步驟4之后陪白,就代表著即將渲染本幀視頻,而字幕的渲染則是在本幀視頻渲染之前進(jìn)行渲染膳灶,即下面這段代碼是在視頻幀渲染之前執(zhí)行
static void video_refresh(void *opaque, double *remaining_time){
    .....省略代碼.....
    if (is->subtitle_st) {
                // 步驟1咱士、字幕FrameQueue隊(duì)列中是否有字幕幀立由,沒(méi)有則退出循環(huán)
                while (frame_queue_nb_remaining(&is->subpq) > 0) {
                    // 步驟2、獲取當(dāng)前待渲染字幕幀sp以及下一個(gè)待渲染字幕幀sp2(如果有的話(huà))
                    sp = frame_queue_peek(&is->subpq);

                    if (frame_queue_nb_remaining(&is->subpq) > 1)
                        sp2 = frame_queue_peek_next(&is->subpq);
                    else
                        sp2 = NULL;
                    
                    // 步驟3序厉、決定當(dāng)前字幕幀是否需要被渲染锐膜。一幀字幕開(kāi)始顯示時(shí)間=pts+start_display_time,結(jié)束顯示時(shí)間=pts+end_display_time
                    /** 學(xué)習(xí):視頻和字幕同步
                     *  分析:視頻幀和字幕幀的同步主要以視頻的時(shí)鐘為準(zhǔn)進(jìn)行同步,這里is->vidclk.pts表示即將要渲染的視頻幀的時(shí)間,當(dāng)它大于(晚于)當(dāng)前要渲染字
                     *  幕幀結(jié)束時(shí)間或者下一個(gè)要渲染字幕幀開(kāi)始時(shí)間表示字幕顯示已經(jīng)落后于視頻了弛房,趕緊渲染當(dāng)前字幕幀道盏;否則就退出字幕幀渲染循環(huán)
                     */
                    // sp->serial != is->subtitleq.serial 用于首幀字幕渲染
                    /** 疑問(wèn):既然當(dāng)前字幕幀都落后于即將要渲染的字幕幀了直接丟棄不就好了么?為撒要渲染上去呢文捶?
                     *  分析:知悉分析就發(fā)現(xiàn)荷逞,下面這個(gè)if語(yǔ)句寫(xiě)法保證字幕幀在其顯示時(shí)間內(nèi)只被渲染一次。這樣有利于效率提升
                     */
                    if (sp->serial != is->subtitleq.serial
                            || (is->vidclk.pts > (sp->pts + ((float) sp->sub.end_display_time / 1000)))
                            || (sp2 && is->vidclk.pts > (sp2->pts + ((float) sp2->sub.start_display_time / 1000))))
                    {
                        if (sp->uploaded) {
                            int i;
                            for (i = 0; i < sp->sub.num_rects; i++) {
                                AVSubtitleRect *sub_rect = sp->sub.rects[i];
                                uint8_t *pixels;
                                int pitch, j;

                                if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)&pixels, &pitch)) {
                                    for (j = 0; j < sub_rect->h; j++, pixels += pitch)
                                        memset(pixels, 0, sub_rect->w << 2);
                                    SDL_UnlockTexture(is->sub_texture);
                                }
                            }
                        }
                        frame_queue_next(&is->subpq);
                    } else {
                        break;
                    }
                }
            }
    .....省略代碼.....
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粹排,一起剝皮案震驚了整個(gè)濱河市种远,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌顽耳,老刑警劉巖坠敷,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異射富,居然都是意外死亡膝迎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)胰耗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)限次,“玉大人,你說(shuō)我怎么就攤上這事宪郊〉嗨。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵弛槐,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我依啰,道長(zhǎng)乎串,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任速警,我火速辦了婚禮叹誉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闷旧。我一直安慰自己长豁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布忙灼。 她就那樣靜靜地躺著匠襟,像睡著了一般钝侠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酸舍,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天帅韧,我揣著相機(jī)與錄音,去河邊找鬼啃勉。 笑死忽舟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淮阐。 我是一名探鬼主播叮阅,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泣特!你這毒婦竟也來(lái)了浩姥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤群扶,失蹤者是張志新(化名)和其女友劉穎及刻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竞阐,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缴饭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骆莹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颗搂。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幕垦,靈堂內(nèi)的尸體忽然破棺而出丢氢,到底是詐尸還是另有隱情,我是刑警寧澤先改,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布疚察,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饺藤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一术奖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氯庆。三九已至蹭秋,卻和暖如春扰付,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背感凤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工悯周, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陪竿。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓禽翼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親族跛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闰挡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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