Android Studio + cmake 編譯 ffmpeg 命令工具

在前一篇文章windows環(huán)境下編譯ffmpeg打包成單個so并使用Cmake集成到Android工程中 我們說到了將ffmpeg 編譯打包成單個so说贝,并使用cmake 集成到android工程中蜗元,現(xiàn)在我們來說說奋单,如何使用cmake 生成能夠使用jni 調用 ffmpeg命令工具。首先链瓦,你得按照前一篇文章所說的生成了libffmpeg.so包租冠,然后再執(zhí)行接下來的步驟搓谆。

1、將ffmpeg的頭文件復制到src/main/cpp/ffmpeg目錄绰寞,將ffmpeg中的cmdutils.c到逊、cmdutils.h、config.h滤钱、ffmpeg.h觉壶、ffmpeg.c、ffmpeg_filter.c件缸、ffmpeg_opt.c復制到src/main/cpp/ffmpeg目錄铜靶。

2、修改 ffmpeg.c 文件他炊,將

int main(int argc, char **argv)

修改為:

int run(int argc, char **argv)

為了能夠重復使用命令争剿,我們需要修改ffmpeg的清理方法ffmpeg_cleanup,在方法的末尾將一些參數(shù)重置痊末,如下所示:

    av_freep(&vstats_filename);
    vstats_filename = NULL;

    av_freep(&input_streams);
    input_streams = NULL;
    nb_input_streams = 0;

    av_freep(&input_files);
    input_files = NULL;
    nb_input_files = 0;

    av_freep(&output_streams);
    output_streams = NULL;
    nb_output_streams = 0;

    av_freep(&output_files);
    output_files = NULL;
    nb_input_files = 0;

將 sigterm_handler 方法中的

exit(123)

修改為

exit_program(123);

在ffmpeg.h 文件的末尾蚕苇,添加聲明

int run(int argc, char **argv);

另外在頭文件開頭聲明Android的Log方法:

#include <android/log.h>

#ifndef LOG_TAG
#define LOG_TAG "FFMPEG"
#endif

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG ,  LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO  ,  LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN  ,  LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

至此凿叠,ffmpeg.c/ffmpeg.h 主文件已經修改完成

3捆蜀、修改 cmdutils.c 文件,添加頭文件和聲明:

#include <setjmp.h>
extern jmp_buf jmp_exit;

修改 exit_program() 方法幔嫂,如下所示:

void exit_program(int ret)
{
    if (program_exit)
    {
        av_log(NULL, AV_LOG_INFO, "run program_exit.\n");
        program_exit(ret);
    }
    else
    {
        av_log(NULL, AV_LOG_INFO, "program_exit is null\n");
    }

    // 轉換錯誤碼11辆它,因為ffmpeg命令執(zhí)行成功返回是1,命令參數(shù)錯誤返回的錯誤碼也是1
    if (ret == 1)
    {
        ret = 11;
    }

    av_log(NULL, AV_LOG_INFO, "exit_program code: %d\n", ret);
    longjmp(jmp_exit, ret);
    // exit(ret);
}

在cmdutils.h文件中添加以下聲明:

#ifdef FFMPEG_RUN_LIB

#ifdef ANDROID

#include <android/log.h>
#ifndef LOG_TAG
#define LOG_TAG "FFMPEG"
#endif
#define XLOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define XLOGD(...) __android_log_print(ANDROID_LOG_DEBUG ,  LOG_TAG, __VA_ARGS__)
#define XLOGI(...) __android_log_print(ANDROID_LOG_INFO  ,  LOG_TAG, __VA_ARGS__)
#define XLOGW(...) __android_log_print(ANDROID_LOG_WARN  ,  LOG_TAG, __VA_ARGS__)
#define XLOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

#else
#include <stdio.h>
#define XLOGV(format, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ": " format "\n", ##__VA_ARGS__)
#define XLOGD(format, ...) __android_log_print(ANDROID_LOG_DEBUG ,  LOG_TAG ": " format "\n", ##__VA_ARGS__)
#define XLOGI(format, ...) __android_log_print(ANDROID_LOG_INFO  ,  LOG_TAG ": " format "\n", ##__VA_ARGS__)
#define XLOGW(format, ...) __android_log_print(ANDROID_LOG_WARN  ,  LOG_TAG ": " format "\n", ##__VA_ARGS__)
#define XLOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG ": " format "\n", ##__VA_ARGS__)

#endif // ANDROID
#endif // FFMPEG_RUN_LIB

到這里我們就已經把ffmpeg命令工具移植到了項目中履恩,但是我們還不能使用锰茉。接下來,我們需要添加自己的jni調用方法切心。

4飒筑、在src/main/cpp目錄下新建一個的ffmpeg_cmd的文件夾片吊,新建ffmpeg_cmd.c/ffmpeg_cmd.h 、ffmpeg_cmd_wrapper.c/ffmpeg_cmd_wrapper.h文件协屡,用來寫我們的jni控制調用ffmpeg命令工具俏脊。
ffmpeg_cmd.h內容如下:

#ifndef CAINCAMERA_FFMPEG_CMD_H
#define CAINCAMERA_FFMPEG_CMD_H

int run_cmd(int argc, char** argv);

#endif //CAINCAMERA_FFMPEG_CMD_H

ffmpeg_cmd.c內容如下:

#include <setjmp.h>
#include <android/log.h>

#include "ffmpeg_cmd.h"
#include "ffmpeg.h"
#ifdef __cplusplus
extern "C" {
#endif

#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, "FFMPEG", __VA_ARGS__)

jmp_buf jmp_exit;

int run_cmd(int argc, char** argv)
{
    int res = 0;
    if(res = setjmp(jmp_exit))
    {
        LOGD("res=%d", res);
        return res;
    }

    res = run(argc, argv);
    LOGD("res_run=%d", res);
    return res;
}

#ifdef __cplusplus
}
#endif

ffmpeg_cmd_wrapper.h內容如下:

#ifndef CAINCAMERA_FFMPEG_CMD_WRAPPER_H
#define CAINCAMERA_FFMPEG_CMD_WRAPPER_H

#include"jni.h"

#ifdef __cplusplus
extern "C" {
#endif


JNIEXPORT jint
JNICALL Java_com_cgfay_caincamera_jni_FFmpegCmd_run
        (JNIEnv *env, jclass obj, jobjectArray commands);

#ifdef __cplusplus
}
#endif


#endif //CAINCAMERA_FFMPEG_CMD_WRAPPER_H

ffmpeg_cmd_wrapper.c內容如下:


#include "ffmpeg_cmd.h"
#include "ffmpeg_cmd_wrapper.h"
#include "jni.h"

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint
JNICALL Java_com_cgfay_caincamera_jni_FFmpegCmd_run
        (JNIEnv *env, jclass obj, jobjectArray commands)
{
    int argc = (*env)->GetArrayLength(env, commands);
    char *argv[argc];
    jstring jstr[argc];

    int i = 0;;
    for (i = 0; i < argc; i++)
    {
        jstr[i] = (jstring)(*env)->GetObjectArrayElement(env, commands, i);
        argv[i] = (char *) (*env)->GetStringUTFChars(env, jstr[i], 0);
    }

    int status = run_cmd(argc, argv);

    for (i = 0; i < argc; ++i)
    {
        (*env)->ReleaseStringUTFChars(env, jstr[i], argv[i]);
    }

    return status;
}
#ifdef __cplusplus
}
#endif

其中,Java_com_cgfay_caincamera_jni_FFmpegCmd_run 跟java中的jni調用方法要對上肤晓,這里可以換成你自己的包路徑和方法名爷贫。接下來我們java層新建一個FFmpegCmd.java文件。文件內容如下:

package com.cgfay.caincamera.jni;

import android.util.Log;

import com.cgfay.caincamera.utils.FileUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * 用于管理FFmpeg命令工具
 * Created by cain.huang on 2017/12/12.
 */

public class FFmpegCmd {
    private static final String TAG = "FFmpegCmd";
    private static boolean VERBOSE = false;

    private static final int RUN_SUCCESS = 0;
    private static final int RUN_FAILED = 1;

    // 是否正在運行命令
    private static boolean mIsRunning = false;

    private static final String STR_DEBUG_PARAM = "-d";

    static {
        System.loadLibrary("ffmpeg");
        System.loadLibrary("ffmpeg_cmd");
    }


    private native static int run(String[] cmd);

    public interface OnCompletionListener {
        void onCompletion(boolean result);
    }

    private static int runSafely(String[] cmd) {
        int result = -1;

        long time = System.currentTimeMillis();
        try {
            result = run(cmd);
            if (VERBOSE) {
                Log.d(TAG, "time = " + (System.currentTimeMillis() - time));
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

    private static void runSync(ArrayList<String> cmds, final OnCompletionListener listener) {
        if (VERBOSE) {
            cmds.add(STR_DEBUG_PARAM);
        }

        final String[] commands = cmds.toArray(new String[cmds.size()]);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int result = runSafely(commands);
                callbackResult(result, listener);
            }
        };
        mIsRunning = true;
        new Thread(runnable).start();
    }

    private static void callbackResult(int result, OnCompletionListener listener) {
        if (VERBOSE) {
            Log.d(TAG, "result = " + result);
        }

        if (listener != null) {
            listener.onCompletion(result == 1);
        }

        mIsRunning = false;
    }

    /**
     * 音視頻混合
     * @param srcVideo      視頻路徑
     * @param videoVolume   視頻聲音
     * @param srcAudio      音頻路徑
     * @param audioVolume   音頻聲音
     * @param desVideo      目標視頻
     * @param callback      狀態(tài)回調
     * @return
     */
    public static boolean AVMuxer(String srcVideo, float videoVolume,
                                  String srcAudio, float audioVolume, String desVideo,
                                  OnCompletionListener callback) {
        if (srcAudio == null || srcAudio.length() <= 0
                || desVideo == null || desVideo.length() <= 0) {
            return false;
        }

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(srcVideo);
        cmds.add("-i");
        cmds.add(srcAudio);

        cmds.add("-c:v");
        cmds.add("copy");
        cmds.add("-map");
        cmds.add("0:v:0");

        cmds.add("-strict");
        cmds.add("-2");

        if (videoVolume <= 0.001f) { // 使用audio聲音
            cmds.add("-c:a");
            cmds.add("aac");

            cmds.add("-map");
            cmds.add("1:a:0");

            cmds.add("-shortest");

            if (audioVolume < 0.99 || audioVolume > 1.01) {
                cmds.add("-vol");
                cmds.add(String.valueOf((int)(audioVolume * 100)));
            }

        } else if (videoVolume > 0.001f && audioVolume > 0.001f) { // 混合音視頻聲音

            cmds.add("-filter_complex");
            cmds.add(String.format(
                    "[0:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,volume=%f[a0]; " +
                    "[1:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,volume=%f[a1];" +
                    "[a0][a1]amix=inputs=2:duration=first[aout]", videoVolume, audioVolume));

            cmds.add("-map");
            cmds.add("[aout]");

        } else {
            Log.w(TAG, String.format(Locale.getDefault(),
                    "Illigal volume : SrcVideo = %.2f, SrcAudio = %.2f",
                    videoVolume, audioVolume));
            if (callback != null) {
                callback.onCompletion(RUN_FAILED == 1);
            }
        }

        cmds.add("-f");
        cmds.add("mp4");
        cmds.add("-y");
        cmds.add("-movflags");
        cmds.add("faststart");
        cmds.add(desVideo);

        runSync(cmds, callback);

        return true;
    }

    /**
     * 設置播放速度
     * @param srcVideo
     * @param speed
     * @param desVideo
     * @param callback
     */
    public static void setPlaybackSpeed(String srcVideo, float speed,
                                        String desVideo, OnCompletionListener callback) {
        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(srcVideo);

        cmds.add("-y");
        cmds.add("-filter_complex");
        cmds.add("[0:v]setpts=" + speed + "*PTS[v];[0:a]atempo=" + 1 / speed + "[a]");
        cmds.add("-map");
        cmds.add("[v]");
        cmds.add("-map");
        cmds.add("[a]");
        cmds.add(desVideo);

        runSync(cmds, callback);
    }


    /**
     * 剪切視頻
     * @param srcVideo
     * @param desVideo
     * @param startTime
     * @param endTime
     * @return
     */
    public static boolean cutVideo(String srcVideo, String desVideo,
                                   float startTime, float endTime) {

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(srcVideo);

        cmds.add("-y");
        cmds.add("-ss");
        cmds.add("" + startTime);
        cmds.add("-t");
        cmds.add("" + endTime);
        cmds.add("-c");
        cmds.add("copy");
        cmds.add(desVideo);

        String[] commands = cmds.toArray(new String[cmds.size()]);

        int result = runSafely(commands);

        return (result == 1);
    }

    /**
     * 將圖片轉成視頻
     * @param picPath   圖片路徑
     * @param duration  時間
     * @param desVideo  輸出視頻路徑
     * @param callback  回調
     */
    public static void convertPictureToVideo(String picPath, float duration,
                                             String desVideo, OnCompletionListener callback) {

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-y");
        cmds.add("-loop");
        cmds.add("1");
        cmds.add("-f");
        cmds.add("image2");
        cmds.add("-i");
        cmds.add(picPath);

        cmds.add("-t");
        cmds.add(""+duration);
        cmds.add("-r");
        cmds.add("15");

        cmds.add(desVideo);

        runSync(cmds, callback);
    }

    /**
     * 添加Gif到視頻
     * @param videoPath
     * @param gifPath
     * @param x
     * @param y
     * @param startTime
     * @param desVideo
     * @param callback
     */
    public static void addGifToVideo(String videoPath, String gifPath,
                                     float x, float y,
                                     float startTime, String desVideo,
                                     OnCompletionListener callback) {
        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-y");
        cmds.add("-i");
        cmds.add(videoPath);
//        cmds.add("-ignore_loop");
//        cmds.add("0");
        cmds.add("-i");
        cmds.add(gifPath);

        cmds.add("-ss");
        cmds.add("" + startTime);

        cmds.add("-filter_complex");
        cmds.add("overlay=" + x + ":" + y);

        cmds.add(desVideo);

        runSync(cmds, callback);
    }

    /**
     * 旋轉視頻
     * @param srcVideo
     * @param desVideo
     * @param callback
     */
    public static void rotateVideo(String srcVideo, String desVideo,
                                   OnCompletionListener callback) {
        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(srcVideo);

        cmds.add("-vf");
//        cmds.add("transpose=1:portrait");
        cmds.add("rotate=PI/2");
        cmds.add(desVideo);

        runSync(cmds, callback);
    }

    /**
     * 添加水印
     * @param srcVideo
     * @param waterMark
     * @param desVideo
     * @param callback
     */
    public static void addWaterMark(String srcVideo, String waterMark,
                                    String desVideo, OnCompletionListener callback) {

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(srcVideo);
        cmds.add("-i");
        cmds.add(waterMark);

        cmds.add("-y");
        cmds.add("-filter_complex");
        cmds.add("[0:v][1:v]overlay=main_w-overlay_w-10:main_h-overlay_h-10[out]"); // 位置
        cmds.add("-map");
        cmds.add("[out]");
        cmds.add("-map");
        cmds.add("0:a");
        cmds.add("-codec:a"); // keep audio
        cmds.add("copy");
        cmds.add(desVideo);

        runSync(cmds, callback);
    }

    /**
     * 將視頻轉成Gif
     * @param videoPath 視頻路徑
     * @param gifPath   gif路徑
     * @param callback  回調
     */
    public static void convertVideoToGif(String videoPath, String gifPath,
                                         OnCompletionListener callback) {
        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-i");
        cmds.add(videoPath);

        cmds.add("-f");
        cmds.add("gif");
        cmds.add(gifPath);

        runSync(cmds, callback);
    }

    /**
     * 合并多個視頻
     * @param videoPathList 視頻列表
     * @param desVideo      輸出視頻
     * @return
     */
    public static boolean combineVideo(List<String> videoPathList, String desVideo) {
        String tmpFile = "/sdcard/videolist.txt";
        String content = "ffconcat version 1.0\n";

        for (String path : videoPathList) {
            content += "\nfile " + path;
        }

        FileUtils.writeFile(tmpFile, content, false);

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-y");

        cmds.add("-safe");
        cmds.add("0");

        cmds.add("-f");
        cmds.add("concat");

        cmds.add("-i");
        cmds.add(tmpFile);


        cmds.add("-c");
        cmds.add("copy");

        cmds.add(desVideo);

        if (VERBOSE) {
            cmds.add(STR_DEBUG_PARAM);
        }

        String[] commands = cmds.toArray(new String[cmds.size()]);
        int result = runSafely(commands);
        FileUtils.deleteFile(tmpFile);

        return result == 1;
    }

    /**
     * 檢測視頻文件是否正確
     * @param videoPath 視頻路徑
     * @param time      時間
     * @param picPath
     * @param callback
     */
    public static void getVideoShoot(String videoPath, float time, String picPath,
                                     OnCompletionListener callback) {

        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-y");
        cmds.add("-ss");
        cmds.add("" + time);
        cmds.add("-i");
        cmds.add(videoPath);
        cmds.add("-r");
        cmds.add("1");
        cmds.add("-vframes");
        cmds.add("1");
//        cmds.add("-vf");
//        cmds.add("select=eq(pict_type\\,I)");
        cmds.add("-an");
        cmds.add("-f");
        cmds.add("mjpeg");
        cmds.add(picPath);

        runSync(cmds, callback);
    }

    /**
     * 裁剪視頻
     * @param srcPath   視頻路徑
     * @param x         x起始坐標
     * @param y         y起始坐標
     * @param width     寬度
     * @param height    高度
     * @param destPath  目標路徑
     * @param callback  回調
     */
    public static void cropVideo(String srcPath, int x, int y, int width, int height,
                                 String destPath, OnCompletionListener callback) {
        ArrayList<String> cmds = new ArrayList<>();
        cmds.add("ffmpeg");
        cmds.add("-y");
        cmds.add("-i");
        cmds.add(srcPath);
        cmds.add("-filter:v");
        cmds.add("crop=" + width + ":" + height + ":" + x + ":" + y);
        cmds.add(destPath);

        runSync(cmds, callback);
    }

}

至此补憾,我們就已經編寫好我們的ffmpeg命令工具了漫萄。但到了這里,還不能執(zhí)行盈匾。因為我們還沒有在CmakeLists.txt中添加對應的文件腾务,如下:

# 設置cmake最低版本
cmake_minimum_required(VERSION 3.4.1)

# 設置路徑
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../libs)

# 加載ffmpeg庫
add_library( ffmpeg
             SHARED
             IMPORTED )
set_target_properties( ffmpeg
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../libs/armeabi-v7a/libffmpeg.so )

# 加載頭文件
# 這里需要添加ffmpeg庫的路徑,用來編譯生成ffmpeg_cmd.so
# 這里如果不引入頭文件削饵,會出現(xiàn)libavresample/avresample.h等頭文件找不到的情況
include_directories(D:/FFmpeg/ffmpeg-3.3.3
                    src/main/cpp/ffmpeg
                    src/main/cpp/ffmpeg/include )

# 添加自身的jni庫
add_library( ffmpeg_cmd

             SHARED

             src/main/cpp/ffmpeg/cmdutils.c
             src/main/cpp/ffmpeg/ffmpeg.c
             src/main/cpp/ffmpeg/ffmpeg_opt.c
             src/main/cpp/ffmpeg/ffmpeg_filter.c
             src/main/cpp/ffmpeg_cmd/ffmpeg_cmd.c
             src/main/cpp/ffmpeg_cmd/ffmpeg_cmd_wrapper.c )

# 查找Android存在的庫
find_library( log-lib

              log )

set(CMAKE_EXE_LINKER_FLAGS "-lz -ldl")

# 鏈接庫文件
target_link_libraries(
                       ffmpeg_cmd

                       # ffmpeg庫
                       ffmpeg

                       ${log-lib} )

sync之后岩瘦,如果在include_directories 中沒有包含ffmpeg庫的目錄,會提示出錯窿撬,找不到路徑:


找不到路徑

原因是我們沒有引入頭文件启昧,前面復制到cpp目錄下的頭文件不全陨囊,我們并沒有用到相應的庫思犁,因此打包出來的結果是沒有相應的頭文件的匹层,比如libavresample喧枷,我們用了libswresample段多。因此這里需要引入ffmpeg庫的頭文件路徑進行編譯轮傍。
我們編譯一個apk包氓栈,解壓之后彬犯,可以看到 libffmpeg_cmd.so已經生成赡模。經過裁剪后的 libffmpeg.so 和 libffmpeg_cmd.so 包體積分別之后3.5MB 和 200K田炭,release 包的體積更小。打包得到libffmpeg_cmd.so之后漓柑,我們就可以從CmakeList.txt中注釋掉編譯libffmpeg_cmd.so庫的代碼教硫。然后將libffmpeg_cmd.so復制到libs/armeabi-v7a目錄。

還有什么不明白的辆布,你可以看我的相機項目 CainCamera 是怎么處理的瞬矩。在錄制多段視頻完成后,在預覽頁面點擊保存锋玲,可以看到DCIM目錄下生成了一個合并后的視頻景用,這個就是用了ffmpeg命令執(zhí)行得到。截止本篇文章發(fā)布前惭蹂,相機項目的多段視頻合成功能基本完成伞插,后續(xù)會添加合成進度提示等細節(jié)割粮,歡迎下載體驗和Stars。(12月22日媚污,由于本人發(fā)現(xiàn)FFmpeg命令行在多段視頻合成時舀瓢,如果存在切換濾鏡,會出現(xiàn)合成成功耗美,但播放不了的情況京髓,目前多段視頻合成bu)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市幽歼,隨后出現(xiàn)的幾起案子朵锣,更是在濱河造成了極大的恐慌谬盐,老刑警劉巖甸私,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異飞傀,居然都是意外死亡皇型,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門砸烦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弃鸦,“玉大人,你說我怎么就攤上這事幢痘』8瘢” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵颜说,是天一觀的道長购岗。 經常有香客問我,道長门粪,這世上最難降的妖魔是什么喊积? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮玄妈,結果婚禮上乾吻,老公的妹妹穿的比我還像新娘。我一直安慰自己拟蜻,他們只是感情好绎签,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酝锅,像睡著了一般诡必。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屈张,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天擒权,我揣著相機與錄音袱巨,去河邊找鬼。 笑死碳抄,一個胖子當著我的面吹牛愉老,可吹牛的內容都是我干的。 我是一名探鬼主播剖效,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嫉入,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了璧尸?” 一聲冷哼從身側響起咒林,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爷光,沒想到半個月后垫竞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蛀序,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年欢瞪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徐裸。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡遣鼓,死狀恐怖,靈堂內的尸體忽然破棺而出重贺,到底是詐尸還是另有隱情骑祟,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布气笙,位于F島的核電站次企,受9級特大地震影響,放射性物質發(fā)生泄漏健民。R本人自食惡果不足惜抒巢,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秉犹。 院中可真熱鬧蛉谜,春花似錦、人聲如沸崇堵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸳劳。三九已至狰贯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涵紊。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工傍妒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摸柄。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓颤练,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驱负。 傳聞我的和親對象是個殘疾皇子嗦玖,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容