Android原生編解碼接口 MediaCodec 之——完全解析

Android 官方的 MediaCodec API

MediaCodec 是Android 4.1(api 16)版本引入的編解碼接口氮凝,Developer 官網(wǎng)上描述的已經(jīng)很清楚了窥淆。可以配合中文翻譯一起看碎连。理解更深刻。

MediaCodec 基本介紹

  • MediaCodec類可用于訪問Android底層的多媒體編解碼器,例如综苔,編碼器/解碼器組件。它是Android底層多媒體支持基礎(chǔ)架構(gòu)的一部分(通常與MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 以及AudioTrack一起使用)位岔。

  • Android 底層多媒體模塊采用的是 OpenMax 框架如筛,任何 Android 底層編解碼模塊的實(shí)現(xiàn),都必須遵循 OpenMax 標(biāo)準(zhǔn)抒抬。Google 官方默認(rèn)提供了一系列的軟件編解碼器:包括:OMX.google.h264.encoder杨刨,OMX.google.h264.encoder, OMX.google.aac.encoder瞧剖, OMX.google.aac.decoder 等等拭嫁,而硬件編解碼功能,則需要由芯片廠商依照 OpenMax 框架標(biāo)準(zhǔn)來完成抓于,所以做粤,一般采用不同芯片型號(hào)的手機(jī),硬件編解碼的實(shí)現(xiàn)和性能是不同的

  • Android 應(yīng)用層統(tǒng)一由 MediaCodec API 來提供各種音視頻編解碼功能捉撮,由參數(shù)配置來決定采用何種編解碼算法怕品、是否采用硬件編解碼加速等

MediaCodec的工作流程:


這里寫圖片描述

從上圖可以看出 MediaCodec 架構(gòu)上采用了2個(gè)緩沖區(qū)隊(duì)列,異步處理數(shù)據(jù)巾遭,并且使用了一組輸入輸出緩存肉康。
你請(qǐng)求或接收到一個(gè)空的輸入緩存(input buffer),向其中填充滿數(shù)據(jù)并將它傳遞給編解碼器處理灼舍。編解碼器處理完這些數(shù)據(jù)并將處理結(jié)果輸出至一個(gè)空的輸出緩存(output buffer)中吼和。最終,你請(qǐng)求或接收到一個(gè)填充了結(jié)果數(shù)據(jù)的輸出緩存(output buffer)骑素,使用完其中的數(shù)據(jù)炫乓,并將其釋放給編解碼器再次使用。
具體工作如下:

  1. Client 從 input 緩沖區(qū)隊(duì)列申請(qǐng) empty buffer [dequeueInputBuffer]
  2. Client 把需要編解碼的數(shù)據(jù)拷貝到 empty buffer献丑,然后放入 input 緩沖區(qū)隊(duì)列 [queueInputBuffer]
  3. MediaCodec 模塊從 input 緩沖區(qū)隊(duì)列取一幀數(shù)據(jù)進(jìn)行編解碼處理
  4. 編解碼處理結(jié)束后末捣,MediaCodec 將原始數(shù)據(jù) buffer 置為 empty 后放回 input 緩沖區(qū)隊(duì)列,將編解碼后的數(shù)據(jù)放入到 output 緩沖區(qū)隊(duì)列
  5. Client 從 output 緩沖區(qū)隊(duì)列申請(qǐng)編解碼后的 buffer [dequeueOutputBuffer]
  6. Client 對(duì)編解碼后的 buffer 進(jìn)行渲染/播放
  7. 渲染/播放完成后创橄,Client 再將該 buffer 放回 output 緩沖區(qū)隊(duì)列 [releaseOutputBuffer]

MediaCodec的基本調(diào)用流程是:

createEncoderByType/createDecoderByType
configure
start
while(true) {
     dequeueInputBuffer  //從輸入流隊(duì)列中取數(shù)據(jù)進(jìn)行編碼操作 
     getInputBuffers     //獲取需要編碼數(shù)據(jù)的輸入流隊(duì)列箩做,返回的是一個(gè)ByteBuffer數(shù)組 
     queueInputBuffer    //輸入流入隊(duì)列 
     dequeueOutputBuffer //從輸出隊(duì)列中取出編碼操作之后的數(shù)據(jù)
     getOutPutBuffers    // 獲取編解碼之后的數(shù)據(jù)輸出流隊(duì)列,返回的是一個(gè)ByteBuffer數(shù)組
     releaseOutputBuffer //處理完成妥畏,釋放ByteBuffer數(shù)據(jù)
}
stop
release

1.初始化MediaCodec邦邦,方法有兩種安吁,分別是通過名稱和類型來創(chuàng)建,對(duì)應(yīng)的方法為:

MediaCodec createByCodecName (String name);
MediaCodec createDecoderByType (String type);
  • 選擇第一種創(chuàng)建方式
    根據(jù) mineType 以及是否為編碼器圃酵,選擇出一個(gè) MediaCodecInfo柳畔,然后使用第一種方式初始化MediaCodec;
    private MediaCodecInfo selectSupportCodec(String mimeType) {
        int numCodecs = MediaCodecList.getCodecCount();
        for (int i = 0; i < numCodecs; i++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            // 判斷是否為編碼器郭赐,否則直接進(jìn)入下一次循環(huán)
            if (!codecInfo.isEncoder()) {
                continue;
            }
            // 如果是編碼器薪韩,判斷是否支持Mime類型
            String[] types = codecInfo.getSupportedTypes();
            for (int j = 0; j < types.length; j++) {
                if (types[j].equalsIgnoreCase(mimeType)) {
                    return codecInfo;
                }
            }
        }
        return null;
    }

  MediaCodecInfo codecInfo = selectSupportCodec(config.mMime);
  if (codecInfo == null) return;
  mMediaCodec = MediaCodec.createByCodecName(codecInfo.getName());
  • 第二種方式比較簡(jiǎn)單
mMediaCodec = MediaCodec.createDecoderByType (MIME_TYPE);

2.配置編碼器,設(shè)置各種編碼器參數(shù)(MediaFormat)捌锭,這個(gè)類包含了比特率俘陷、幀率、關(guān)鍵幀間隔時(shí)間等观谦。然后再調(diào)用 mMediaCodec .configure拉盾,對(duì)于 API 19 以上的系統(tǒng),我們可以選擇 Surface 輸入:mMediaCodec .createInputSurface豁状,

format= MediaFormat.createVideoFormat(MIME_TYPE, width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);  
format.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);     
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //關(guān)鍵幀間隔時(shí)間 單位s
mMediaCodec .configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mMediaCodec.createInputSurface();

3.打開編碼器捉偏,獲取輸入輸出緩沖區(qū)

mMediaCodec .start();
mInputBuffers = mMediaCodec .getInputBuffers();
mOutputBuffers = mMediaCodec .getOutputBuffers();

獲取輸入輸出緩沖區(qū)在api19 上是以上方式獲取,api21以后 可以使用直接獲取ByteBuffer

ByteBuffer intputBuffer = mMediaCodec.getOutputBuffer(inputBufferIndex);
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);

4.輸入數(shù)據(jù)泻红,有2種方式夭禽,一種是普通輸入,一種是Surface 輸入
普通輸入又可區(qū)分為兩種情況谊路,一種是配合MediaExtractor 讹躯,一種是取原數(shù)據(jù);

  • 獲取可使用的緩沖區(qū)索引
 int outputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMES_OUT);

返回一個(gè)填充了有效數(shù)據(jù)的input buffer的索引缠劝,如果沒有可用的buffer則返回-1潮梯,參數(shù)為超時(shí)時(shí)間(TIMES_OUT),單位是微秒惨恭,當(dāng)timeoutUs==0時(shí)秉馏,該方法立即返回;當(dāng)timeoutUs<0時(shí)脱羡,無限期地等待一個(gè)可用的input buffer沃饶,當(dāng)timeoutUs>0時(shí),
等待時(shí)間為傳入的微秒值轻黑。

  • 普通輸入之獲取原數(shù)據(jù)方式
ByteBuffer inputBuffer = mInputBuffers[inputbufferindex];
inputBuffer.clear();//清除原來的內(nèi)容以接收新的內(nèi)容
inputBuffer.put(bytes, 0, len);//len是傳進(jìn)來的有效數(shù)據(jù)長(zhǎng)度
mMediaCodec .queueInputBuffer(inputbufferindex, 0, len, timestamp, 0);

上面輸入緩存的index,通過getInputBuffers()得到的是輸入緩存數(shù)組琴昆,通過index和輸入緩存數(shù)組可以得到當(dāng)前請(qǐng)求的輸入緩存氓鄙,在使用之前要clear一下,避免之前的緩存數(shù)據(jù)影響當(dāng)前數(shù)據(jù)业舍,接著就是把數(shù)據(jù)添加到輸入緩存中抖拦,并調(diào)用queueInputBuffer(...)把緩存數(shù)據(jù)入隊(duì)升酣;

  • 普通輸入之配合MediaExtractor 解碼其他的音視頻數(shù)據(jù)
ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
int chunkSize = SDecoder.extractor.readSampleData(inputBuf, 0);
if (chunkSize < 0) {
   SDecoder.decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
   SDecoder.decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, SDecoder.extractor.getSampleTime(), 0);                
   SDecoder.extractor.advance();
}
  • 使用Surface輸入
    Surface輸入是Android 4.3(api 18)引入。但用在某些 API 18 的機(jī)型上會(huì)導(dǎo)致編碼器輸出數(shù)據(jù)量特別小态罪,畫面是黑屏噩茄,所以 Surface 輸入模式從 API 19 啟用比較好。
//Requests a Surface to use as the input to an encoder, in place of input buffers. This may only be 
//called after configure(MediaFormat, Surface, MediaCrypto, int) and before start().
//調(diào)用此方法复颈,官方有這么一段話绩聘,意思是必須在configure之后 start()之前調(diào)用。
mInputSurface =  mMediaCodec.createInputSurface();

5.輸出數(shù)據(jù)
通常編碼傳輸時(shí)每個(gè)關(guān)鍵幀頭部都需要帶上編碼配置數(shù)據(jù)(PPS耗啦,SPS)凿菩,但 MediaCodec 會(huì)在首次輸出時(shí)專門輸出編碼配置數(shù)據(jù),后面的關(guān)鍵幀里是不攜帶這些數(shù)據(jù)的帜讲,所以需要我們手動(dòng)做一個(gè)拼接衅谷;

  • 獲取可使用的緩沖區(qū)
    獲取輸出緩存和獲取輸入緩存類似,首先通過dequeueOutputBuffer(BufferInfo info, long timeoutUs)來請(qǐng)求一個(gè)輸出緩存似将,這里需要傳入一個(gè)BufferInfo對(duì)象获黔,用于存儲(chǔ)ByteBuffer的信息,TIMES_OUT為超時(shí)時(shí)間在验。TIMES_OUT傳的是 0玷氏,表示不會(huì)等待,由于這里并沒有一個(gè)單獨(dú)的線程不停調(diào)用译红,所以這樣沒什么問題预茄,反倒可以防止阻塞,但如果我們單獨(dú)起了一個(gè)線程專門取輸出數(shù)據(jù)侦厚,那這就會(huì)導(dǎo)致 CPU 資源的浪費(fèi)了耻陕,可以加上一個(gè)合適的值,例如 3~10ms刨沦;
BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo,TIMES_OUT);
  • 獲取數(shù)據(jù)
ByteBuffer outputBuffer = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
    outputBuffer = outputBuffers[outputBufferIndex];
} else {
    outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
    MediaFormat format = mMediaCodec.getOutputFormat();
    format.setByteBuffer("csd-0",outputBuffer);
    mBufferInfo.size = 0;
}

// 如果API<=19诗宣,需要根據(jù)BufferInfo的offset偏移量調(diào)整ByteBuffer的位置
// 并且限定將要讀取緩存區(qū)數(shù)據(jù)的長(zhǎng)度,否則輸出數(shù)據(jù)會(huì)混亂
if (mBufferInfo.size != 0) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        outputBuffer.position(mBufferInfo.offset);
        outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
    }
    // mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
}
  • 釋放緩沖區(qū)
mMediaCodec.releaseOutputBuffer(outputBufferIndex,false);

6.使用完MediaCodec后釋放資源
要告知編碼器我們要結(jié)束編碼想诅,Surface 輸入的話調(diào)用 mMediaCodec .signalEndOfInputStream召庞,普通輸入則可以為在 queueInputBuffer 時(shí)指定 MediaCodec.BUFFER_FLAG_END_OF_STREAM 這個(gè) flag;告知編碼器后我們就可以等到編碼器輸出的 buffer 帶著 MediaCodec.BUFFER_FLAG_END_OF_STREAM 這個(gè) flag 了来破,等到之后我們調(diào)用 mMediaCodec .release 銷毀編碼器

if (mMediaCodec != null) {
    mMediaCodec.stop();
    mMediaCodec.release();
    mMediaCodec = null;
}

MediaCodec 流控

流控就是流量控制篮灼。為什么要控制,就是為了在一定的限制條件下徘禁,收益最大化诅诱!
涉及到了 TCP 和視頻編碼:
對(duì) TCP 來說就是控制單位時(shí)間內(nèi)發(fā)送數(shù)據(jù)包的數(shù)據(jù)量,對(duì)編碼來說就是控制單位時(shí)間內(nèi)輸出數(shù)據(jù)的數(shù)據(jù)量送朱。

TCP 的限制條件是網(wǎng)絡(luò)帶寬娘荡,流控就是在避免造成或者加劇網(wǎng)絡(luò)擁塞的前提下干旁,盡可能利用網(wǎng)絡(luò)帶寬。帶寬夠炮沐、網(wǎng)絡(luò)好争群,我們就加快速度發(fā)送數(shù)據(jù)包,出現(xiàn)了延遲增大大年、丟包之后换薄,就放慢發(fā)包的速度(因?yàn)槔^續(xù)高速發(fā)包,可能會(huì)加劇網(wǎng)絡(luò)擁塞鲜戒,反而發(fā)得更慢)专控。

視頻編碼的限制條件最初是解碼器的能力,碼率太高就會(huì)無法解碼遏餐,后來隨著 codec 的發(fā)展伦腐,解碼能力不再是瓶頸,限制條件變成了傳輸帶寬/文件大小失都,我們希望在控制數(shù)據(jù)量的前提下柏蘑,畫面質(zhì)量盡可能高。
一般編碼器都可以設(shè)置一個(gè)目標(biāo)碼率粹庞,但編碼器的實(shí)際輸出碼率不會(huì)完全符合設(shè)置咳焚,因?yàn)樵诰幋a過程中實(shí)際可以控制的并不是最終輸出的碼率,而是編碼過程中的一個(gè)量化參數(shù)(Quantization Parameter庞溜,QP)革半,它和碼率并沒有固定的關(guān)系,而是取決于圖像內(nèi)容流码。 這一點(diǎn)不在這里展開又官,感興趣的朋友可以閱讀視頻壓縮編碼和音頻壓縮編碼的基本原理。

無論是要發(fā)送的 TCP 數(shù)據(jù)包漫试,還是要編碼的圖像六敬,都可能出現(xiàn)“尖峰”,也就是短時(shí)間內(nèi)出現(xiàn)較大的數(shù)據(jù)量驾荣。TCP 面對(duì)尖峰外构,可以選擇不為所動(dòng)(尤其是網(wǎng)絡(luò)已經(jīng)擁塞的時(shí)候),這沒有太大的問題播掷,但如果視頻編碼也對(duì)尖峰不為所動(dòng)审编,那圖像質(zhì)量就會(huì)大打折扣了。如果有幾幀數(shù)據(jù)量特別大歧匈,但仍要把碼率控制在原來的水平割笙,那勢(shì)必要損失更多的信息,因此圖像失真就會(huì)更嚴(yán)重。這種情況通常的表現(xiàn)是畫面出現(xiàn)很多小方塊伤溉,看上去像是打了馬賽克一樣,導(dǎo)致畫面的局部或者整體看不清楚的情況

  • Android 硬編碼流控
    MediaCodec 流控相關(guān)的接口并不多妻率,一是配置時(shí)設(shè)置目標(biāo)碼率和碼率控制模式乱顾,二是動(dòng)態(tài)調(diào)整目標(biāo)碼率(Android 19+)。

配置時(shí)指定目標(biāo)碼率和碼率控制模式:

mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

碼率控制模式有三種:
碼率控制模式在 MediaCodecInfo.EncoderCapabilities類中定義了三種宫静,在 framework 層有另一套名字和它們的值一一對(duì)應(yīng):

  • CQ 對(duì)應(yīng)于 OMX_Video_ControlRateDisable走净,它表示完全不控制碼率,盡最大可能保證圖像質(zhì)量孤里;
  • CBR 對(duì)應(yīng)于 OMX_Video_ControlRateConstant囊榜,它表示編碼器會(huì)盡量把輸出碼率控制為設(shè)定值铃芦,即我們前面提到的“不為所動(dòng)”;
  • VBR 對(duì)應(yīng)于 OMX_Video_ControlRateVariable,它表示編碼器會(huì)根據(jù)圖像內(nèi)容的復(fù)雜度(實(shí)際上是幀間變化量的大芯苍 )來動(dòng)態(tài)調(diào)整輸出碼率,圖像復(fù)雜則碼率高只怎,圖像簡(jiǎn)單則碼率低啡专;

動(dòng)態(tài)調(diào)整目標(biāo)碼率:

Bundle param = new Bundle();
param.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
mediaCodec.setParameters(param);

Android 流控策略選擇

  • 質(zhì)量要求高、不在乎帶寬霍衫、解碼器支持碼率劇烈波動(dòng)的情況下候引,可以選擇 CQ 碼率控制策略。
  • VBR 輸出碼率會(huì)在一定范圍內(nèi)波動(dòng)敦跌,對(duì)于小幅晃動(dòng)澄干,方塊效應(yīng)會(huì)有所改善,但對(duì)劇烈晃動(dòng)仍無能為力柠傍;連續(xù)調(diào)低碼率則會(huì)導(dǎo)致碼率急劇下降麸俘,如果無法接受這個(gè)問題,那 VBR 就不是好的選擇携兵。

編碼栗子

下面展示使用MediaExtractor獲取數(shù)據(jù)后疾掰,用MediaMuxer重新寫成一個(gè)MP4文件的簡(jiǎn)單栗子

private void doExtract() throws IOException {
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    boolean outputDone = false;
    boolean inputDone = false;
    while (!outputDone) {
        if (!inputDone) {
            int inputBufIndex = mMediaCodec.dequeueInputBuffer(10000);
            if (inputBufIndex >= 0) {
                ByteBuffer inputBuf = mMediaCodec.getInputBuffers()[inputBufIndex];
                int chunkSize = mMediaExtractor.readSampleData(inputBuf, 0);
                if (chunkSize < 0) {
                    mMediaCodec.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    inputDone = true;
                } else {
                    mMediaCodec.queueInputBuffer(inputBufIndex, 0, chunkSize, mMediaExtractor.getSampleTime(), 0);
                    mMediaExtractor.advance();
                }
            }
        }

        if (!outputDone) {
            int decoderStatus =mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000);
            if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                 Log.d(TAG, "no output from decoder available");
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                Log.d(TAG, "decoder output buffers changed");
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                MediaFormat newFormat = SDecoder.decoder.getOutputFormat();
                Log.d(TAG, "decoder output format changed: " + newFormat);
            } else {
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    mMediaCodec.releaseOutputBuffer(outputBufferIndex,false);
                    outputDone = true;
                    break;
                }
                // 獲取一個(gè)只讀的輸出緩存區(qū)inputBuffer ,它包含被編碼好的數(shù)據(jù)
                ByteBuffer outputBuffer = null;
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    outputBuffer = mMediaCodec.getOutputBuffers()[outputBufferIndex];
                } else {
                    outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
                }
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    MediaFormat format = mMediaCodec.getOutputFormat();
                    format.setByteBuffer("csd-0",outputBuffer);
                    mBufferInfo.size = 0;
                }

                // 如果API<=19徐紧,需要根據(jù)BufferInfo的offset偏移量調(diào)整ByteBuffer的位置
                // 并且限定將要讀取緩存區(qū)數(shù)據(jù)的長(zhǎng)度静檬,否則輸出數(shù)據(jù)會(huì)混亂
                if (mBufferInfo.size != 0) {
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                        outputBuffer.position(mBufferInfo.offset);
                        outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
                    }
                    // mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
                }
                mMediaCodec.releaseOutputBuffer(decoderStatus, false);
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市并级,隨后出現(xiàn)的幾起案子拂檩,更是在濱河造成了極大的恐慌,老刑警劉巖嘲碧,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稻励,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)望抽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門加矛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人煤篙,你說我怎么就攤上這事斟览。” “怎么了辑奈?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵苛茂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鸠窗,道長(zhǎng)妓羊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任稍计,我火速辦了婚禮躁绸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丙猬。我一直安慰自己涨颜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布茧球。 她就那樣靜靜地躺著庭瑰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抢埋。 梳的紋絲不亂的頭發(fā)上弹灭,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音揪垄,去河邊找鬼穷吮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛饥努,可吹牛的內(nèi)容都是我干的捡鱼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼酷愧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼驾诈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起溶浴,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤乍迄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后士败,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闯两,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漾狼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重慢。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逊躁,靈堂內(nèi)的尸體忽然破棺而出伤锚,到底是詐尸還是另有隱情,我是刑警寧澤志衣,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站猛们,受9級(jí)特大地震影響念脯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弯淘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一绿店、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧庐橙,春花似錦假勿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至浆竭,卻和暖如春浸须,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邦泄。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工删窒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顺囊。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓肌索,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親特碳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诚亚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355