ffmpeg播放器開發(fā) 詳細記錄+代碼實現3

請接上一個簡書內容觀看,會對上一章的代碼進行改進和修改~~~

ffmpeg播放器3-音頻解碼與opensl es播放

1.ffmpeg 解碼音頻并播放的開發(fā)

實現  DNFFmpeg的 start()方法

//cmake中加入OpenSLES庫
target_link_libraries(XXX OpenSLES)

2.相關知識整理

簡書鏈接:
http://www.reibang.com/p/e94652ee371c

Android 播放 PCM 有:
1.Java SDK : AudioTrack (AudioRecorder錄音)
2.NDK : OPenSL ES (效率比較好)

RGB、YUV:圖像原始格式
PCM: 聲音的原始格式

采樣率:1s 采集多少次聲音 采集頻率 HZ
采樣位:16bit 2字節(jié)
聲道數:單聲道、雙聲道

大端: 0xab 0xcd
小端: 0xcd 0xab

3.代碼實現:

...之前的創(chuàng)建類:
創(chuàng)建 DNPlayer.java,用于調用so中的方法
創(chuàng)建 mylog.h, 用于定義一些宏
創(chuàng)建 DNFFmpeg.h & .cpp 宋列,用于提供方法給 native-lib.cpp 調用 
創(chuàng)建 JavaCallHelper.h & .cpp ,用于so調用java的方法(即回調)
創(chuàng)建 AudioChannel.h & .cpp ,用于音頻開發(fā)
創(chuàng)建 VideoChannel& .cpp 庐扫,用于視頻開發(fā)
... 新創(chuàng)建類:
safeQueue.h 用戶創(chuàng)建線程安全的隊列
BaseChannel.h 用于音頻和視頻開發(fā)類的父類,存放一些共用的方法和信息

1. DNPlayer.java:

在surfaceChanged回調中調用 native_setSurface(Surface surface)方法傳入 Surface 
編寫start()方法 ->調用 native_start()方法
...

2. BaseChannel.h

#ifndef MYFFMPEGPLAYER_BASECHANNEL_H
#define MYFFMPEGPLAYER_BASECHANNEL_H

extern "C"{
#include <libavcodec/avcodec.h>
};
#include "safe_queue.h"

class BaseChannel {
public:
    BaseChannel(int i,AVCodecContext *avCodecContext) : index(i),avCodecContext(avCodecContext) {
        // 設置釋放隊列中的AVPacket 對象數據的方法回調
        packets.setReleaseCallBack(BaseChannel::releaseAVPacket);
        // 設置釋放隊列中的 AVFrame 對象數據的方法回調
        frames.setReleaseCallBack(BaseChannel::releaseAVFrame);
    };

    virtual ~BaseChannel() {
        packets.clear();
        frames.clear();
    };
    // 釋放 AVPacket
    static void releaseAVPacket(AVPacket*& packet){
        if(packet){
            av_packet_free(&packet);
            packet = 0;
        }
    }

    // 釋放 AVFrame
    static void releaseAVFrame(AVFrame*& frame){
        if(frame){
            av_frame_free(&frame);
            frame = 0;
        }
    }


    // 抽象方法 解碼+播放
    virtual void start() = 0;


    int index;
    // 線程安全的隊列  用于存放壓縮后的包
    SafeQueue<AVPacket*> packets;

    // 線程安全的隊列  用于存放解碼后的數據
    SafeQueue<AVFrame*> frames;

    // 解碼器上下文
    AVCodecContext *avCodecContext = 0;

    bool isPlaying;// 是否工作

};

#endif //MYFFMPEGPLAYER_BASECHANNEL_H



3.safe_queue.h

#ifndef DNRECORDER_SAFE_QUEUE_H
#define DNRECORDER_SAFE_QUEUE_H

#include <queue>
#include <pthread.h>

using namespace std;
//線程安全的隊列
template<typename T>
class SafeQueue {
    typedef void (*ReleaseCallBack)(T &);

    typedef void (*SyncHandle)(queue<T> &);

public:
    SafeQueue() {
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);
    }

    ~SafeQueue() {
        pthread_cond_destroy(&cond);
        pthread_mutex_destroy(&mutex);
    }

    void push(const T new_value) {
        pthread_mutex_lock(&mutex);
        if (work) {
            q.push(new_value);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }
        pthread_mutex_unlock(&mutex);
    }


    int pop(T &value) {
        int ret = 0;
        pthread_mutex_lock(&mutex);
        //在多核處理器下 由于競爭可能虛假喚醒 包括jdk也說明了
        while (work && q.empty()) {
            pthread_cond_wait(&cond, &mutex);
        }
        if (!q.empty()) {
            value = q.front();
            q.pop();
            ret = 1;
        }
        pthread_mutex_unlock(&mutex);
        return ret;
    }

    void setWork(int work) {
        pthread_mutex_lock(&mutex);
        this->work = work;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }

    int empty() {
        return q.empty();
    }

    int size() {
        return q.size();
    }

    void clear() {
        pthread_mutex_lock(&mutex);
        int size = q.size();
        for (int i = 0; i < size; ++i) {
            T value = q.front();
            releaseCallBack(value);
            q.pop();
        }
        pthread_mutex_unlock(&mutex);
    }

    void sync() {
        pthread_mutex_lock(&mutex);
        // 同步代碼塊 能在線程安全的背景下操作 queue :例如 主動丟包
        syncHandle(q);
        pthread_mutex_unlock(&mutex);
    }

    void setReleaseCallBack(ReleaseCallBack r) {
        releaseCallBack = r;
    }

    void setSyncHandle(SyncHandle s) {
        syncHandle = s;
    }

private:

    pthread_cond_t cond;
    pthread_mutex_t mutex;

    queue<T> q;
    // 是否工作  1工作  0不工作
    int work;
    ReleaseCallBack releaseCallBack;
    SyncHandle syncHandle;

};


#endif //DNRECORDER_SAFE_QUEUE_H

4.AudioChannel.h & AudioChannel.cpp
AudioChannel.h:

#ifndef MYFFMPEGPLAYER_AUDIOCHANNEL_H
#define MYFFMPEGPLAYER_AUDIOCHANNEL_H


#include "BaseChannel.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

extern  "C"{
#include <libswresample/swresample.h>
};

class AudioChannel : public BaseChannel{
public:
    AudioChannel(int i, AVCodecContext *avCodecContext);
    ~AudioChannel();
    // 解碼+播放
    void start();
    // 解碼
    void decode();
    // 播放
    void play();

    int getPcm();

    uint8_t *data = 0;
    int out_channels;
    int out_samplesize;
    int out_sample_rate;

private:
    /**
     * OpenSL ES
     */
    // 引擎與引擎接口
    SLObjectItf engineObject = 0;
    SLEngineItf engineInterface = 0;
    //混音器
    SLObjectItf outputMixObject = 0;
    //播放器
    SLObjectItf bqPlayerObject = 0;
    //播放器接口
    SLPlayItf bqPlayerInterface = 0;

    SLAndroidSimpleBufferQueueItf bqPlayerBufferQueueInterface =0;


    //重采樣
    SwrContext *swrContext = 0;

    // 解碼線程
    pthread_t pid_decode;
    // 播放線程
    pthread_t pid_play;
};


#endif //MYFFMPEGPLAYER_AUDIOCHANNEL_H

AudioChannel.cpp:

// 解碼線程
void *task_decodes(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->decode();
    return 0;
}

// 播放線程
void *task_plays(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->play();
    return 0;
}


AudioChannel::AudioChannel(int i, AVCodecContext *avCodecContext) : BaseChannel(i,
                                                                                avCodecContext) {
    out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
    out_samplesize = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    out_sample_rate = 44100;
    //44100個16位 44100 * 2
    // 44100*(雙聲道)*(16位)
    data = static_cast<uint8_t *>(malloc(out_sample_rate * out_channels * out_samplesize));
    memset(data,0,out_sample_rate * out_channels * out_samplesize);
}

AudioChannel::~AudioChannel(){
    if(data){
        free(data);
        data = 0;
    }
}

//返回獲取的pcm數據大小
int AudioChannel::getPcm() {
    int data_size = 0;
    AVFrame *frame;
    int ret = frames.pop(frame);
    if (!isPlaying) {
        if (ret) {
            releaseAVFrame(frame);
        }
        return data_size;
    }
    //48000HZ 8位 =》 44100 16位
    //重采樣
    // 假設我們輸入了10個數據 仗哨,swrContext轉碼器 這一次處理了8個數據
    // 那么如果不加delays(上次沒處理完的數據) , 積壓
    int64_t delays = swr_get_delay(swrContext, frame->sample_rate);
    // 將 nb_samples 個數據 由 sample_rate采樣率轉成 44100 后 返回多少個數據
    // 10  個 48000 = nb 個 44100
    // AV_ROUND_UP : 向上取整 1.1 = 2
    int64_t max_samples = av_rescale_rnd(delays + frame->nb_samples, out_sample_rate,
                                         frame->sample_rate, AV_ROUND_UP);
    //上下文+輸出緩沖區(qū)+輸出緩沖區(qū)能接受的最大數據量+輸入數據+輸入數據個數
    //返回 每一個聲道的輸出數據
    int samples = swr_convert(swrContext, &data, max_samples, (const uint8_t **) frame->data,
                              frame->nb_samples);
    //獲得   samples 個   * 2 聲道 * 2字節(jié)(16位)
    data_size = samples * out_samplesize * out_channels;
    return data_size;
}

void AudioChannel::start() {
    //0+輸出聲道+輸出采樣位+輸出采樣率+  輸入的3個參數
    swrContext = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
                                    avCodecContext->channel_layout, avCodecContext->sample_fmt,
                                    avCodecContext->sample_rate, 0, 0);
    //初始化
    swr_init(swrContext);

    isPlaying = 1;
    packets.setWork(1);
    frames.setWork(1);
    //1.解碼
    pthread_create(&pid_decode, NULL, task_decodes, this);
    //2.播放
    pthread_create(&pid_play, NULL, task_plays, this);
}

// 解碼
void AudioChannel::decode() {
    AVPacket *avPacket = 0;
    while (isPlaying) {
        //取出一個數據包
        int ret = packets.pop(avPacket);
        if (!isPlaying) {
            break;
        }
        if (!ret) {
            continue;
        }
        // 把包丟給解碼器
        ret = avcodec_send_packet(avCodecContext, avPacket);
        releaseAVPacket(avPacket);
        if (ret != 0) {
            break;
        }
        AVFrame *avFrame = av_frame_alloc();
        // 從解碼器中讀取解碼后的數據包
        ret = avcodec_receive_frame(avCodecContext, avFrame);
        if (ret == AVERROR(EAGAIN)) {
            // 讀取失敗  需要重試
            continue;
        } else if (ret != 0) {
            break;
        }

        frames.push(avFrame);
    }
    releaseAVPacket(avPacket);
}


//回調函數 用于 播放
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
    //獲得pcm 數據 多少個字節(jié) data
    int dataSize = audioChannel->getPcm();
    if(dataSize > 0 ){
        // 接收16位數據
        (*bq)->Enqueue(bq,audioChannel->data,dataSize);
    }
}

// 播放
void AudioChannel::play() {
    /**
     * 1形庭、創(chuàng)建引擎并獲取引擎接口
     */
    SLresult result;
    // 1.1 創(chuàng)建引擎 SLObjectItf engineObject
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 1.2 初始化引擎  init
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 1.3 獲取引擎接口SLEngineItf engineInterface
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
                                           &engineInterface);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

    /**
     * 2、設置混音器
     */
    // 2.1 創(chuàng)建混音器SLObjectItf outputMixObject
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0,
                                                 0, 0);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 2.2 初始化混音器outputMixObject
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

    /**
     * 3厌漂、創(chuàng)建播放器
     */
    //3.1 配置輸入聲音信息
    //創(chuàng)建buffer緩沖類型的隊列 2個隊列
    SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                            2};
    //pcm數據格式
    //pcm+2(雙聲道)+44100(采樣率)+ 16(采樣位)+16(數據的大小)+LEFT|RIGHT(雙聲道)+小端數據
    SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                            SL_BYTEORDER_LITTLEENDIAN};

    //數據源 將上述配置信息放到這個數據源中
    SLDataSource slDataSource = {&android_queue, &pcm};

    //3.2  配置音軌(輸出)
    //設置混音器
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};
    //需要的接口  操作隊列的接口
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    //3.3 創(chuàng)建播放器
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &slDataSource,
                                          &audioSnk, 1,
                                          ids, req);
    //初始化播放器
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);

    //得到接口后調用  獲取Player接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);


    /**
     * 4萨醒、設置播放回調函數
     */
    //獲取播放器隊列接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                    &bqPlayerBufferQueueInterface);
    //設置回調
    (*bqPlayerBufferQueueInterface)->RegisterCallback(bqPlayerBufferQueueInterface,
                                                      bqPlayerCallback, this);
    /**
     * 5、設置播放狀態(tài)
     */
    (*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);
    /**
     * 6苇倡、手動激活一下這個回調
     */
    bqPlayerCallback(bqPlayerBufferQueueInterface, this);
}

5.DNFFmpeg.h & DNFFmpeg.cpp
DNFFmpeg.h:

#ifndef MYFFMPEGPLAYER_DNFFMPEG_H
#define MYFFMPEGPLAYER_DNFFMPEG_H

#include "mylog.h"
#include <cstring>
#include <pthread.h>
#include "DNFFmpeg.h"
#include "JavaCallHelper.h"
#include "AudioChannel.h"
#include "VideoChannel.h"

extern  "C" {
#include <libavformat/avformat.h>
}

class DNFFmpeg {
public:
    DNFFmpeg(JavaCallHelper* callHelper,const char* dataSource);
    ~DNFFmpeg();
    // 播放器準備工作
    void prepare();
    // 線程中調用該方法,用于實現具體解碼音視頻代碼
    void _prepare();

    // 播放
    void start();
    // 線程中調用該方法 用于實現具體的播放代碼
    void _start();

    void setRenderFrameCallback(RenderFrameCallback callback);

private:
    // 音視頻地址
    char *dataSource;
    // 解碼線程
    pthread_t pid;
    // 解碼器上下文
    AVFormatContext *formatContext = 0;
    // 播放線程
    pthread_t pid_start;

    // ...
    JavaCallHelper* callHelper = 0;
    AudioChannel *audioChannel = 0;
    VideoChannel *videoChannel = 0;
    //是否正在播放
    bool isPlaying = 0;
    // 開始播放的回調 解碼器解碼完成后調用 native_window
    RenderFrameCallback callback;
};


#endif //MYFFMPEGPLAYER_DNFFMPEG_H

DNFFmpeg.cpp:

#include "DNFFmpeg.h"

// 解碼線程
void* task_prepare(void* args){
    DNFFmpeg *dnfFmpeg = static_cast<DNFFmpeg *>(args);
    //調用解碼方法
    dnfFmpeg->_prepare();
    return 0;
}

// 播放線程
void* task_start(void* args){
    DNFFmpeg *dnfFmpeg = static_cast<DNFFmpeg *>(args);
    //調用解碼方法
    dnfFmpeg->_start();
    return 0;
}

DNFFmpeg::DNFFmpeg(JavaCallHelper *callHelper, const char *dataSource){
    this->callHelper = callHelper;
    //防止 dataSource參數 指向的內存被釋放
    this->dataSource = new char[strlen(dataSource)+1];
    strcpy(this->dataSource,dataSource);
}

DNFFmpeg::~DNFFmpeg() {
    //釋放
    DELETE(dataSource);
    DELETE(callHelper);
}

void DNFFmpeg::prepare() {
    // 創(chuàng)建一個解碼的線程
    pthread_create(&pid,NULL,task_prepare,this);
}

// 解碼器打開的實現方法
void DNFFmpeg::_prepare() {
    // 初始化網絡 讓ffmpeg能夠使用網絡
    avformat_network_init();
    //1富纸、打開媒體地址(文件地址、直播地址)
    // AVFormatContext  包含了 視頻的 信息(寬旨椒、高等)
    formatContext = 0;
    //文件路徑不對 手機沒網
    int ret = avformat_open_input(&formatContext,dataSource,0,0);
    //ret不為0表示 打開媒體失敗
    if(ret != 0){
        LOGFFE("打開媒體失敗:%s",av_err2str(ret));
        callHelper->onPreError(THREAD_CHILD,FFMPEG_CAN_NOT_OPEN_URL);
        return;
    }
    //2晓褪、查找媒體中的 音視頻流 (給 contxt里的 streams等成員賦)
    ret =  avformat_find_stream_info(formatContext,0);
    // 小于0 則失敗
    if (ret < 0){
        LOGFFE("查找流失敗:%s",av_err2str(ret));
        callHelper->onPreError(THREAD_CHILD,FFMPEG_CAN_NOT_FIND_STREAMS);
        return;
    }
    //nb_streams :幾個流(幾段視頻/音頻)
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        //可能代表是一個視頻 也可能代表是一個音頻
        AVStream *stream = formatContext->streams[i];
        //包含了 解碼 這段流 的各種參數信息(寬、高综慎、碼率涣仿、幀率)
        AVCodecParameters  *codecpar =  stream->codecpar;

        //無論視頻還是音頻都需要干的一些事情(獲得解碼器)
        // 1、通過 當前流 使用的 編碼方式寥粹,查找解碼器
        AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
        if(dec == NULL){
            LOGFFE("查找解碼器失敗:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_FIND_DECODER_FAIL);
            return;
        }
        //2变过、獲得解碼器上下文
        AVCodecContext *context = avcodec_alloc_context3(dec);
        if(context == NULL){
            LOGFFE("創(chuàng)建解碼上下文失敗:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            return;
        }
        //3、設置上下文內的一些參數 (context->width)
//        context->width = codecpar->width;
//        context->height = codecpar->height;
        ret = avcodec_parameters_to_context(context,codecpar);
        //失敗
        if(ret < 0){
            LOGFFE("設置解碼上下文參數失敗:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            return;
        }
        // 4涝涤、打開解碼器
        ret = avcodec_open2(context,dec,0);
        if (ret != 0){
            LOGFFE("打開解碼器失敗:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_OPEN_DECODER_FAIL);
            return;
        }
        //音頻
        if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
            audioChannel = new AudioChannel(i,context);
        } else if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoChannel = new VideoChannel(i,context);
            // 設置播放的回調
            videoChannel->setRenderFrameCallBack(callback);
        }
    }
    //沒有音視頻  (很少見)
    if(!audioChannel && !videoChannel){
        LOGFFE("沒有音視頻");
        callHelper->onPreError(THREAD_CHILD,FFMPEG_NOMEDIA);
        return;
    }
    // 準備完了 通知java 你隨時可以開始播放
    callHelper->onPrepare(THREAD_CHILD);
}



void DNFFmpeg::start() {
    //正在播放
    isPlaying = 1;
    if(videoChannel){
        // 設置 videoChannel工作狀態(tài)
        videoChannel->start();
    }
    if(audioChannel){
        // 設置 audioChannel工作狀態(tài)
        audioChannel->start();
    }
    pthread_create(&pid_start,0,task_start,this);
}

void DNFFmpeg::_start() {
    // 1.讀取媒體數據包(音視頻數據包)
    int ret;
    while(isPlaying){
        // 創(chuàng)建一個AVPacket
        AVPacket *packet = av_packet_alloc();
        // 讀取流數據并塞入 AVPacket
        ret = av_read_frame(formatContext,packet);
        // ret == 0 成功  其他  失敗
        if(ret == 0){
            // 通過 packet->stream_index 判斷 是音頻還是視頻
            // packet->stream_index 可以通過 解碼循環(huán)中存放的序號做對比
            if(audioChannel && packet->stream_index == audioChannel->index){
                audioChannel->packets.push(packet);
            }else if(videoChannel && packet->stream_index == videoChannel->index){
                videoChannel->packets.push(packet);
            }
        }else if(ret == AVERROR_EOF){
            // 讀取完成 但是還沒有播放完

        }else{

        }
    }
    // 2.解碼
}


void DNFFmpeg::setRenderFrameCallback(RenderFrameCallback callback) {
    this->callback = callback;
}

...

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末媚狰,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子阔拳,更是在濱河造成了極大的恐慌崭孤,老刑警劉巖类嗤,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異辨宠,居然都是意外死亡,警方通過查閱死者的電腦和手機嗤形,發(fā)現死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赋兵,“玉大人,你說我怎么就攤上這事霹期∫蹲椋” “怎么了甩十?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侣监。 經常有香客問我,道長垮刹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任酪劫,我火速辦了婚禮,結果婚禮上寺董,老公的妹妹穿的比我還像新娘。我一直安慰自己遮咖,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布御吞。 她就那樣靜靜地躺著麦箍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陶珠。 梳的紋絲不亂的頭發(fā)上挟裂,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音揍诽,去河邊找鬼诀蓉。 笑死栗竖,一個胖子當著我的面吹牛,可吹牛的內容都是我干的渠啤。 我是一名探鬼主播狐肢,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沥曹!你這毒婦竟也來了份名?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤架专,失蹤者是張志新(化名)和其女友劉穎同窘,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體部脚,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年裤纹,在試婚紗的時候發(fā)現自己被綠了委刘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹰椒,死狀恐怖锡移,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情漆际,我是刑警寧澤淆珊,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站奸汇,受9級特大地震影響施符,放射性物質發(fā)生泄漏。R本人自食惡果不足惜擂找,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一戳吝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贯涎,春花似錦听哭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隘马。三九已至,卻和暖如春祟霍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沸呐。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工崭添, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棘伴。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓屁置,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阱穗。 傳聞我的和親對象是個殘疾皇子使鹅,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容