Android AVDemo(4):音頻解封裝,從 MP4 中解封裝出 AAC丨音視頻工程示例

vx 搜索『gjzkeyframe』 關(guān)注『關(guān)鍵幀Keyframe』來及時獲得最新的音視頻技術(shù)文章损离。

塞尚《河流》 .jpeg

這個公眾號會路線圖 式的遍歷分享音視頻技術(shù)音視頻基礎(chǔ)(完成)音視頻工具(完成)音視頻工程示例(進行中) → 音視頻工業(yè)實戰(zhàn)(準(zhǔn)備)盯蝴。

iOS/Android 客戶端開發(fā)同學(xué)如果想要開始學(xué)習(xí)音視頻開發(fā),最絲滑的方式是對音視頻基礎(chǔ)概念知識有一定了解后率寡,再借助 iOS/Android 平臺的音視頻能力上手去實踐音視頻的采集 → 編碼 → 封裝 → 解封裝 → 解碼 → 渲染過程,并借助音視頻工具來分析和理解對應(yīng)的音視頻數(shù)據(jù)倚搬。

音視頻工程示例這個欄目冶共,我們將通過拆解采集 → 編碼 → 封裝 → 解封裝 → 解碼 → 渲染流程并實現(xiàn) Demo 來向大家介紹如何在 iOS/Android 平臺上手音視頻開發(fā)。

這里是 Android 第四篇:Android 音頻解封裝 Demo每界。這個 Demo 里包含以下內(nèi)容:

  • 1)實現(xiàn)一個音頻解封裝模塊捅僵;
  • 2)實現(xiàn)對 MP4 文件中音頻部分的解封裝邏輯并將解封裝后的編碼數(shù)據(jù)存儲為 AAC 文件;
  • 3)詳盡的代碼注釋眨层,幫你理解代碼邏輯和原理庙楚。

如果你想獲得全部源碼和參與音視頻技術(shù)討論,可以知識星球搜索『關(guān)鍵幀的音視頻開發(fā)圈』加入我們趴樱,當(dāng)然也可以跳過直接看后續(xù)的內(nèi)容馒闷。

1酪捡、音頻解封裝模塊

首先,實現(xiàn)一個 KFDemuxerConfig 類用于定義音頻解封裝參數(shù)的配置窜司。這里包括了:視頻路徑沛善、解封裝類型這幾個參數(shù)。這樣設(shè)計是因為這個配置類不僅會用于音頻解封裝塞祈,后續(xù)的視頻解封裝也會使用金刁。

KFDemuxerConfig.java

public class KFDemuxerConfig {
    ///< 輸入路徑。
    public String path;
    ///< 音視頻解封裝類型(僅音頻议薪、僅視頻尤蛮、音視頻)。
    public KFMediaBase.KFMediaType demuxerType = KFMediaBase.KFMediaType.KFMediaAV;
}

其中用到的 KFMediaType 是定義在 KFMediaBase 中的一個枚舉:

KFMediaBase.java

public class KFMediaBase {
    public enum KFMediaType{
        KFMediaUnkown(0),
        KFMediaAudio (1 << 0),
        KFMediaVideo  (1 << 1),
        KFMediaAV ((1 << 0) | (1 << 1));
        private int index;
        KFMediaType(int index) {
            this.index = index;
        }

        public int value() {
            return index;
        }
    }
}

接下來斯议,我們實現(xiàn)一個 KFMP4Demuxer 類來實現(xiàn) MP4 的解封裝产捞。它能從符合 MP4 標(biāo)準(zhǔn)的文件中解封裝出音頻編碼數(shù)據(jù)。

KFMP4Demuxer.java

public class KFMP4Demuxer {
    public static final int KFDemuxerErrorAudioSetDataSource = -2300;
    public static final int KFDemuxerErrorVideoSetDataSource = -2301;
    public static final int KFDemuxerErrorAudioReadData = -2302;
    public static final int KFDemuxerErrorVideoReadData = -2303;

    private static final String TAG = "KFDemuxer";
    private KFDemuxerConfig mConfig = null; ///< 解封裝配置
    private KFDemuxerListener mListener = null; ///< 回調(diào)
    private MediaExtractor mAudioMediaExtractor = null; ///< 音頻解封裝器
    private MediaFormat mAudioMediaFormat = null; ///< 音頻格式描述
    private MediaExtractor mVideoMediaExtractor = null; ///< 視頻解封裝器
    private MediaFormat mVideoMediaFormat = null; ///< 視頻格式描述
    private MediaMetadataRetriever mRetriever = null; ///< 視頻信息獲取實例
    private Handler mMainHandler = new Handler(Looper.getMainLooper()); ///< 主線程

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public KFMP4Demuxer(KFDemuxerConfig config, KFDemuxerListener listener) {
        mConfig = config;
        mListener = listener;
        if (mRetriever == null) {
            mRetriever = new MediaMetadataRetriever();
            mRetriever.setDataSource(mConfig.path);
        }

        ///< 初始化音頻解封裝器哼御。
        if (hasAudio() && (config.demuxerType.value() & KFMediaBase.KFMediaType.KFMediaAudio.value()) != 0) {
            _setupAudioMediaExtractor();
        }

        ///< 初始化視頻解封裝器坯临。
        if (hasVideo() && (config.demuxerType.value() & KFMediaBase.KFMediaType.KFMediaVideo.value()) != 0) {
            _setupVideoMediaExtractor();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public void release() {
        ///< 釋放音視頻解封裝器、視頻信息獲取實例恋昼。
        if (mAudioMediaExtractor != null) {
            mAudioMediaExtractor.release();
            mAudioMediaExtractor = null;
        }

        if (mVideoMediaExtractor != null) {
            mVideoMediaExtractor.release();
            mVideoMediaExtractor = null;
        }

        if (mRetriever != null) {
            mRetriever.release();
            mRetriever = null;
        }
    }

    public boolean hasVideo() {
        ///< 是否包含視頻看靠。
        if (mRetriever == null) {
            return false;
        }
        String value = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
        return value != null && value.equals("yes");
    }

    public boolean hasAudio() {
        ///< 是否包含音頻。
        if (mRetriever == null) {
            return false;
        }
        String value = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
        return value != null && value.equals("yes");
    }

    public int duration() {
        ///< 文件時長液肌。
        if (mRetriever == null) {
            return 0;
        }
        return Integer.parseInt(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int rotation() {
        ///< 視頻旋轉(zhuǎn)挟炬。
        if (mVideoMediaFormat == null) {
            return 0;
        }
        return mVideoMediaFormat.getInteger(MediaFormat.KEY_ROTATION);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public boolean isHEVC() {
        ///< 是否為 H.265。
        if (mVideoMediaFormat == null) {
            return false;
        }
        String mime = mVideoMediaFormat.getString(MediaFormat.KEY_MIME);
        return mime.contains("hevc") || mime.contains("dolby-vision");
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int width() {
        ///< 視頻寬度嗦哆。
        if (mVideoMediaFormat == null) {
            return 0;
        }
        return mVideoMediaFormat.getInteger(MediaFormat.KEY_WIDTH);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int height() {
        ///< 視頻高度谤祖。
        if (mVideoMediaFormat == null) {
            return 0;
        }
        return mVideoMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int samplerate() {
        ///< 音頻采樣率。
        if (mAudioMediaFormat == null) {
            return 0;
        }
        return mAudioMediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int channel() {
        ///< 音頻聲道數(shù)老速。
        if (mAudioMediaFormat == null) {
            return 0;
        }
        return mAudioMediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int audioProfile() {
        ///< AAC粥喜、HEAAC 等。
        if (mAudioMediaFormat == null) {
            return 0;
        }
        return mAudioMediaFormat.getInteger(MediaFormat.KEY_PROFILE);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public int videoProfile() {
        ///< 視頻畫質(zhì)級別 BaseLine Main High 等橘券。
        if (mVideoMediaFormat == null) {
            return 0;
        }
        return mVideoMediaFormat.getInteger(MediaFormat.KEY_PROFILE);
    }

    public MediaFormat audioMediaFormat() {
        return mAudioMediaFormat;
    }

    public MediaFormat videoMediaFormat() {
        return mVideoMediaFormat;
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public ByteBuffer readAudioSampleData(MediaCodec.BufferInfo bufferInfo) {
        ///< 音頻數(shù)據(jù)讀取容客。
        if (mAudioMediaExtractor == null) {
            return null;
        }

        ByteBuffer buffer = ByteBuffer.allocateDirect(500 * 1024);
        try {
            bufferInfo.size = mAudioMediaExtractor.readSampleData(buffer, 0);
        } catch (Exception e) {
            Log.e(TAG, "readSampleData" + e);
            return null;
        }

        if (bufferInfo.size > 0) {
            bufferInfo.flags = mAudioMediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
            bufferInfo.presentationTimeUs = mAudioMediaExtractor.getSampleTime();
            mAudioMediaExtractor.advance();
            return buffer;
        } else {
            bufferInfo.flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
            return null;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public ByteBuffer readVideoSampleData(MediaCodec.BufferInfo bufferInfo) {
        ///< 視頻數(shù)據(jù)讀取
        if (mVideoMediaExtractor == null) {
            return null;
        }

        ByteBuffer buffer = ByteBuffer.allocateDirect(1000 * 1024);
        try {
            bufferInfo.size = mVideoMediaExtractor.readSampleData(buffer, 0);
        } catch (Exception e) {
            Log.e(TAG, "readVideoData" + e);
            return null;
        }

        if (bufferInfo.size > 0) {
            bufferInfo.flags = mVideoMediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
            bufferInfo.presentationTimeUs = mVideoMediaExtractor.getSampleTime();
            mVideoMediaExtractor.advance();
            return buffer;
        } else {
            bufferInfo.flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
            return null;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void _setupAudioMediaExtractor() {
        ///< 初始化音頻解封裝器。
        if (mAudioMediaExtractor == null) {
            mAudioMediaExtractor = new MediaExtractor();
            try {
                mAudioMediaExtractor.setDataSource(mConfig.path);
            } catch (Exception e) {
                Log.e(TAG, "setDataSource" + e);
                _callBackError(KFDemuxerErrorAudioSetDataSource,e.getMessage());
                return;
            }

            ///< 查找音頻軌道與格式描述约郁。
            int numberTracks = mAudioMediaExtractor.getTrackCount();
            for(int index = 0; index < numberTracks; index ++) {
                MediaFormat format = mAudioMediaExtractor.getTrackFormat(index);
                String mime = format.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("audio/")) {
                    mAudioMediaFormat = format;
                    mAudioMediaExtractor.selectTrack(index);
                    mAudioMediaExtractor.seekTo(0,MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
                }
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void _setupVideoMediaExtractor() {
        ///< 初始化視頻解封裝器。
        if (mVideoMediaExtractor == null) {
            mVideoMediaExtractor = new MediaExtractor();
            try {
                mVideoMediaExtractor.setDataSource(mConfig.path);
            } catch (Exception e) {
                Log.e(TAG, "setDataSource" + e);
                _callBackError(KFDemuxerErrorVideoSetDataSource,e.getMessage());
                return;
            }

            ///< 查找視頻軌道與格式描述但两。
            int numberTracks = mVideoMediaExtractor.getTrackCount();
            for(int index = 0; index < numberTracks; index++) {
                MediaFormat format = mVideoMediaExtractor.getTrackFormat(index);
                String mime = format.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("video/")) {
                    mVideoMediaFormat = format;
                    mVideoMediaExtractor.selectTrack(index);
                    mVideoMediaExtractor.seekTo(0,MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
                }
            }
        }
    }

    private void _callBackError(int error, String errorMsg) {
        if (mListener != null) {
            mMainHandler.post(()->{
                mListener.demuxerOnError(error,TAG + errorMsg);
            });
        }
    }
}

上面是 KFMP4Demuxer 的實現(xiàn)鬓梅,從代碼上可以看到主要有這幾個部分:

  • 1)構(gòu)造方法創(chuàng)建解封裝器實例及獲取視頻信息實例。
    • _setupAudioMediaExtractor 方法中初始化音頻解封裝器實例以及設(shè)置數(shù)據(jù)源 setDataSource谨湘,查找音頻軌道下標(biāo)與格式描述绽快。
    • _setupVideoMediaExtractor 方法中初始化視頻解封裝器實例以及設(shè)置數(shù)據(jù)源 setDataSource芥丧,查找視頻軌道下標(biāo)與格式描述。
    • 初始化獲取視頻信息實例坊罢,mRetriever 初始化視頻獲取信息實例以及設(shè)置數(shù)據(jù)源 setDataSource续担。
  • 2)從音視頻輸入源讀取數(shù)據(jù)。
    • 音頻讀取方法 readAudioSampleData活孩,讀取完一幀移動下一幀 advance物遇。
    • 視頻讀取方法 readVideoSampleData,讀取完一幀移動下一幀 advance憾儒。
  • 3)清理解封裝實例询兴、獲取視頻信息實例,release起趾。

更具體細(xì)節(jié)見上述代碼及其注釋

2诗舰、解封裝 MP4 文件中的音頻部分存儲為 AAC 文件

我們還是在一個 MainActivity 中來實現(xiàn)對一個 MP4 文件解封裝、獲取其中的音頻編碼數(shù)據(jù)并存儲為 AAC 文件训裆。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private KFMP4Demuxer mDemuxer; ///< 解封裝實例
    private KFDemuxerConfig mDemuxerConfig; ///< 解封裝配置
    private FileOutputStream mStream = null;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions((Activity) this,
                    new String[] {Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    1);
        }

        mDemuxerConfig = new KFDemuxerConfig();
        mDemuxerConfig.path = Environment.getExternalStorageDirectory().getPath() + "/2.mp4";
        mDemuxerConfig.demuxerType = KFMediaBase.KFMediaType.KFMediaAudio;
        if (mStream == null) {
            try {
                mStream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath() + "/test.aac");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        FrameLayout.LayoutParams startParams = new FrameLayout.LayoutParams(200, 120);
        startParams.gravity = Gravity.CENTER_HORIZONTAL;
        Button startButton = new Button(this);
        startButton.setTextColor(Color.BLUE);
        startButton.setText("開始");
        startButton.setVisibility(View.VISIBLE);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ///< 創(chuàng)建解封裝實例眶根。
                if (mDemuxer == null) {
                    mDemuxer = new KFMP4Demuxer(mDemuxerConfig,mDemuxerListener);

                    ///< 讀取音頻數(shù)據(jù)。
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    ByteBuffer nextBuffer = mDemuxer.readAudioSampleData(bufferInfo);
                    while (nextBuffer != null) {
                        try {
                            ///< 添加 ADTS边琉。
                            ByteBuffer adtsBuffer = KFAVTools.getADTS(bufferInfo.size,mDemuxer.audioProfile(),mDemuxer.samplerate(),mDemuxer.channel());
                            byte[] adtsBytes = new byte[adtsBuffer.capacity()];
                            adtsBuffer.get(adtsBytes);
                            mStream.write(adtsBytes);

                            byte[] dst = new byte[bufferInfo.size];
                            nextBuffer.get(dst);
                            mStream.write(dst);
                        }  catch (IOException e) {
                            e.printStackTrace();
                        }
                        nextBuffer = mDemuxer.readAudioSampleData(bufferInfo);
                    }
                    Log.i("KFDemuxer","complete");
                }
            }
        });
        addContentView(startButton, startParams);
    }

    private KFDemuxerListener mDemuxerListener = new KFDemuxerListener() {
        ///< 解封裝錯誤回調(diào)属百。
        @Override
        public void demuxerOnError(int error, String errorMsg) {
            Log.i("KFDemuxer","error" + error + "msg" + errorMsg);
        }
    };
}

上面是 MainActivity 的實現(xiàn),其中主要包含這幾個部分:

  • 1)設(shè)置好待解封裝的資源艺骂。
    • mDemuxerConfig 中實現(xiàn)诸老,我們這里是一個 MP4 文件。
  • 2)創(chuàng)建解封裝器钳恕。
    • new KFMP4Demuxer(mDemuxerConfig,mDemuxerListener)别伏。
  • 3)讀取解封裝后的音頻編碼數(shù)據(jù)并存儲為 AAC 文件。
    • 循環(huán)讀取 readAudioSampleData AAC 裸數(shù)據(jù)忧额。
    • 需要注意的是厘肮,我們從解封裝器讀取的音頻 AAC 編碼數(shù)據(jù)在存儲為 AAC 文件時需要添加 ADTS 頭。生成一個 AAC packet 對應(yīng)的 ADTS 頭數(shù)據(jù)在 KFAVTools 類的工具方法 static ByteBuffer getADTS(int size, int profile, int sampleRate, int channel) 中實現(xiàn)睦番。這個在前面的音頻編碼的 Demo 中已經(jīng)介紹過了类茂。

3、用工具播放 AAC 文件

完成音頻采集和編碼后托嚣,可以將 sdcard 文件夾下面的 test.aac 文件拷貝到電腦上巩检,使用 ffplay 播放來驗證一下音頻采集是效果是否符合預(yù)期:

$ ffplay -I test.aac

關(guān)于播放 AAC 文件的工具,可以參考《FFmpeg 工具》第 2 節(jié) ffplay 命令行工具《可視化音視頻分析工具》第 1.1 節(jié) Adobe Audition示启。

- 完 -

推薦閱讀

《Android AVDemo(3):音頻封裝》

《Android AVDemo(2):音頻編碼》

《Android AVDemo(1):音頻采集》

《iOS AVDemo(4):音頻解封裝》

《iOS AVDemo(3):音頻封裝》

《iOS AVDemo(2):音頻編碼》

《iOS AVDemo(1):音頻采集》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兢哭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子夫嗓,更是在濱河造成了極大的恐慌迟螺,老刑警劉巖冲秽,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異矩父,居然都是意外死亡锉桑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門窍株,熙熙樓的掌柜王于貴愁眉苦臉地迎上來民轴,“玉大人,你說我怎么就攤上這事夹姥∩嘉洌” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵辙售,是天一觀的道長轻抱。 經(jīng)常有香客問我,道長旦部,這世上最難降的妖魔是什么祈搜? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮士八,結(jié)果婚禮上容燕,老公的妹妹穿的比我還像新娘。我一直安慰自己婚度,他們只是感情好蘸秘,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝗茁,像睡著了一般醋虏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哮翘,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天颈嚼,我揣著相機與錄音,去河邊找鬼饭寺。 笑死阻课,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艰匙。 我是一名探鬼主播限煞,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼员凝!你這毒婦竟也來了晰骑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硕舆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骤公,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡抚官,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阶捆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凌节。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洒试,靈堂內(nèi)的尸體忽然破棺而出倍奢,到底是詐尸還是另有隱情,我是刑警寧澤垒棋,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布卒煞,位于F島的核電站,受9級特大地震影響叼架,放射性物質(zhì)發(fā)生泄漏畔裕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一乖订、第九天 我趴在偏房一處隱蔽的房頂上張望扮饶。 院中可真熱鬧,春花似錦乍构、人聲如沸甜无。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岂丘。三九已至,卻和暖如春昔善,著一層夾襖步出監(jiān)牢的瞬間元潘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工君仆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翩概,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓返咱,卻偏偏與公主長得像钥庇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咖摹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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