Android OpenGL ES 十一.MediaCodec录制视频之EglHelper(转载整理)

前言

这篇文章简单介绍一下在Android平台下的EGL环境的相关内容,由于OpenGL ES并不负责窗口管理以及上下文管理,该职责由各个平台自行完成;在Android平台下OpenGL ES的上下文环境是依赖EGL的API进行搭建的。

对于EGL这个框架,谷歌已经提供了GLSurfaceView,是一个已经封装EGL相关处理的工具类,但是不够灵活;对于更加核心的OpengGL ES的用法(例如多线程共享纹理)则需要开发者自行搭建EGL开发环境。


前置知识

Java层实现
在Java层,EGL封装了两套框架,分别是:

  • 位于javax.microedition.khronos.egl包下的EGL10
  • 位于android.opengl包下的EGL14

其主要区别是:

  • EGL14是在Android 4.2(API 17)引入的,换言之API 17以下的版本不支持EGL14
  • EGL10不支持OpenGL ES 2.x,因此在EGL10中某些相关常量参数只能用手写硬编码代替,例如EGL14.EGL_CONTEXT_CLIENT_VERSION以及EGL14.EGL_OPENGL_ES2_BIT等等。

PS:由于主体流程基本一致,所以本篇以EGL10的代码进行示例。

Native层实现
程序在Native层使用EGL环境时。
需要引入EGL的so库:

Android.mk:

LOCAL_LDLIBS += -lEGL

CMake:

find_library( EGL-lib
        EGL )

需要包含头文件:

#include <EGL/egl.h>
#include <EGL/eglext.h>


EGL环境配置整体流程

  1. 获取默认的EGLDisplay
  2. EGLDisplay进行初始化。
  3. 输入预设置的参数获取EGL支持的EGLConfig
  4. 通过EGLDisplayEGLConfig创建一个EGLContext上下文环境。
  5. 创建一个EGLSurface来连接EGL和设备的屏幕。
  6. 在渲染线程绑定EGLSurfaceEGLContext
  7. 【进行OpenGL ES的API渲染步骤】(与EGL无关)
  8. 调用SwapBuffer进行双缓冲切换显示渲染画面。
  9. 释放EGL相关资源EGLSurfaceEGLContextEGLDisplay

获取显示设备

首先,EGL是需要知道绘制内容的目标在哪里,EGLDisplay是一个封装了物理屏幕的数据类型,也可以理解为绘制目标的一个抽象。

通常通过eglGetDisplay()方法返回EGLDisplay来作为OpenGL ES的渲染目标,在该方法中,一般来说都会将常量EGL_DEFAULT_DISPLAY传进方法中,而各个手机厂商则会返回默认的显示设备。

java代码:

mEgl = (EGL10) EGLContext.getEGL();
// 返回默认的显示设备.
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
// 需判断是否成功获取EGLDisplay
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
    throw new RuntimeException("eglGetDisplay failed");
}

c代码:

EGLDisplay egl_display;
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//需判断是否成功获取EGLDisplay
if (egl_display == EGL_NO_DISPLAY)
    return error;

一般来说,我们需要验证eglGetDisplay()的返回值,如果是EGL_NO_DISPLAY的话,那么就是没有获取默认显示设备,需要返回给客户端上层处理异常。

当我们获取到了EGLDisplay,我们需要调用eglInitialize()对其进行初始化,该方法会返回一个bool变量代表执行是否成功。

java代码:

int version[] = new int[2];
if (!EGL10.eglInitialize(eglDisplay, version, 0, version, 1)) {
    throw new RuntimeException("eglInitialize failed");
}

c代码:

EGLint major, minor;
if (!eglInitialize(egl_display, &major, &minor))
    return error;

方法的后面参数代表MajorMinor的版本,比如EGL的版本号是1.0,那么Major将返回1,Minor返回0。
如果不关心版本号,这两个参数可以传入NULL


配置输出格式

当我们获取到EGLDisplay后,其实已经可以将OpenGL ES的输出与设备的屏幕桥接起来了,但是还是需要指定一些配置项,例如色彩格式、像素格式、RGBA的表示以及SurfaceType等,实际上也就是指FrameBuffer的配置参数。

一般来说不同平台的EGL标准是不同的,以下是Android平台一个比较通用的配置参数(Java的就不列举了):

const EGLint config_attribs[] = {
        EGL_BUFFER_SIZE, 32,   //颜色缓冲区中所有组成颜色的位数
        EGL_ALPHA_SIZE, 8,     //颜色缓冲区中透明度位数
        EGL_BLUE_SIZE, 8,      //颜色缓冲区中蓝色位数
        EGL_GREEN_SIZE, 8,     //颜色缓冲区中绿色位数
        EGL_RED_SIZE, 8,       //颜色缓冲区中红色位数
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,  //egl版本  2.0
        EGL_NONE
};

PS:EGL的参数配置一般都以 id,value 依次存放,对于个别的属性可以只有 id 没有 value ,并以EGL_NONE标识结尾信息。
最终可以通过调用eglChooseConfig()方法得到配置选项信息:

java代码:

EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
// eglChooseConfig()方法得到配置选项信息
if(!mEgl.eglChooseConfig(mEglDisplay, attribList, configs, configs.length, numConfigs))
       throw new IllegalArgumentException("eglChooseConfig failed");
   
// 如果没有配置的Config
if (numConfigs[0] < 0) 
       throw new RuntimeException("Unable to find any matching EGL config");

EGLConfig eglConfig = configs[0];
// 对应的Config不存在
if (eglConfig == null) 
       throw new RuntimeException("eglChooseConfig returned null");


c代码:

EGLint num_config;
EGLConfig  egl_config;
//检测返回值是否成功
if (!eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_config))
        return error;
//如果没有配置的Config
if (num_config < 0)
        return error;
//对应的Config不存在
if (_egl_config == NULL)
        return error;

简单看一下函数原型:eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,EGLint *num_config)可以知道:
第2个参数attrib_list指的是配置参数列表,也就是上面的config_attribs[]
第3个参数configs返回输出的EGLConfigs数据,可能有多个;
第4个参数config_size则表示最多需要输出多少个EGLConfig
第5个参数num_config则代表满足配置参数的EGLConfig的个数。

附带一个EGLConfig属性表格:

属性 描述 默认值
EGL_BUFFER_SIZE 颜色缓冲区中所有组成颜色的位数 0
EGL_RED_SIZE 颜色缓冲区中红色位数 0
EGL_GREEN_SIZE 颜色缓冲区中绿色位数 0
EGL_BLUE_SIZE 颜色缓冲区中蓝色位数 0
EGL_LUMINANCE_SIZE 颜色缓冲区中亮度位数 0
EGL_ALPHA_SIZE 颜色缓冲区中透明度位数 0
EGL_ALPHA_MASK_SIZE 遮挡缓冲区透明度掩码位数 0
EGL_BIND_TO_TEXTURE_RGB 绑定到 RGB 贴图使能为真 EGL_DONT_CARE
EGL_BIND_TO_TEXTURE_RGBA 绑定到 RGBA 贴图使能为真 EGL_DONT_CARE
EGL_COLOR_BUFFER_TYPE 颜色缓冲区类型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER EGL_RGB_BUFFER
EGL_CONFIG_CAVEAT 配置有关的警告信息 EGL_DONT_CARE
EGL_CONFIG_ID 唯一的 EGLConfig 标示值 EGL_DONT_CARE
EGL_CONFORMANT 使用EGLConfig 创建的上下文符合要求时为真
EGL_DEPTH_SIZE 深度缓冲区位数 0
EGL_LEVEL 帧缓冲区水平 0
EGL_MAX_PBUFFER_WIDTH 使用EGLConfig 创建的PBuffer的最大宽度
EGL_MAX_PBUFFER_HEIGHT 使用EGLConfig 创建的PBuffer最大高度
EGL_MAX_PBUFFER_PIXELS 使用EGLConfig 创建的PBuffer最大尺寸
EGL_MAX_SWAP_INTERVAL 最大缓冲区交换间隔 EGL_DONT_CARE
EGL_MIN_SWAP_INTERVAL 最小缓冲区交换间隔 EGL_DONT_CARE
EGL_NATIVE_RENDERABLE 如果操作系统渲染库能够使用EGLConfig 创建渲染渲染窗口 EGL_DONT_CARE
EGL_NATIVE_VISUAL_ID 与操作系统通讯的可视ID句柄 EGL_DONT_CARE
EGL_NATIVE_VISUAL_TYPE 与操作系统通讯的可视ID类型 EGL_DONT_CARE
EGL_RENDERABLE_TYPE 渲染窗口支持的布局组成标示符的遮挡位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that EGL_OPENGL_ES_BIT
EGL_SAMPLE_BUFFERS 可用的多重采样缓冲区位数 0
EGL_SAMPLES 每像素多重采样数 0
EGL_S TENCIL_SIZE 模板缓冲区位数 0
EGL_SURFACE_TYPE EGL 窗口支持的类型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT EGL_WINDOW_BIT
EGL_TRANSPARENT_TYPE 支持的透明度类型 EGL_NONE
EGL_TRANSPARENT_RED_VALUE 透明度的红色解释 EGL_DONT_CARE
EGL_TRANSPARENT_GRE EN_VALUE 透明度的绿色解释 EGL_DONT_CARE
EGL_TRANSPARENT_BLUE_VALUE 透明度的兰色解释 EGL_DONT_CARE

创建EGL上下文环境

当拿到EGLDisplayEGLConfig后,就可以开始创建EGL的上下文环境EGLContext了。
EGLContext的存在是因为OpenGL ES所创建的资源对于开发者来说可见的仅仅只是一个 ID 而已,而其实际内容依赖于这个上下文。
一个EGLContext只能在一个线程中使用,如果将EGLContext所持有的OpengGL资源在多线程间共享,那么需要用到共享上下文(share context)。

简单看下EGLContext的创建代码:

java代码:

 // 指定OpenGL ES2版本
 int[] attrib_list = {
       EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
       EGL10.EGL_NONE
 };
// 创建EGLContext上下文
if(mEGLContext != null)
      mEGLContext = mEgl.eglCreateContext(mEglDisplay, configs[0], null,attrib_list);
else
      mEGLContext = mEgl.eglCreateContext(mEglDisplay, configs[0], EGL10.EGL_NO_CONTEXT,attrib_list);
//需要检测Context是否存在
if (mEGLContext == EGL10.EGL_NO_CONTEXT) {
       throw new RuntimeException("Failed to create EGL context");
}

c代码:

EGLContext egl_context;
//指定OpenGL ES2版本
const EGLint context_attribs[] = {
      EGL_CONTEXT_CLIENT_VERSION, 2,
      EGL_NONE
};
//创建EGLContext上下文
egl_context = eglCreateContext(egl_display, egl_config, NULL, context_attribs);
//需要检测Context是否存在
if (egl_context == EGL_NO_CONTEXT)
    return error;

函数eglCreateContext()的第三个参数可以传入一个EGLContext的变量,该变量的意义是指可以与正在创建的上下文环境共享OpenGL ES资源,包括纹理、FrameBuffer以及其他的Buffer等资源。
如果传入NULL代表不需要与其他的OpenGL ES上下文共享任何资源。


连接EGL和设备屏幕

当我们需要将EGL跟设备的屏幕桥接起来时,我们需要用到EGLSurfaceEGL有一个“桥”的功能,从而使得OpenGL ES的输出可以渲染到设备屏幕上;
EGLSurface其实就是一个FrameBuffer,通过EGL库提供的eglCreateWindowSurface()可以创建一个可实际显示的Surface;也可以通过EGL库提供的eglCreatePbufferSurface()方法创建一个OffScreenSurface

java代码:

 // 创建可显示的Surface
// 第三个参数在EGL10中只支持SurfaceHolder和SurfaceTexture
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, configs[0], surface,null);
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
    throw new RuntimeException("createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}

这里需要强调一点的是eglCreateWindowSurface()的第三个入参surface虽然可以传入Object;但是在EGL14中只支持SurfaceSurfaceTexture,在EGL10中只支持SurfaceHolderSurfaceTexture

c代码:

//创建可显示的Surface
EGLSurface egl_surface;
const EGLint attribs[] = { EGL_NONE };
egl_surface = eglCreateWindowSurface(egl_display, egl_config, window, attribs);
if (egl_surface == EGL_NO_SURFACE)
    return error;

在Native层,eglCreateWindowSurface()的第三个入参window是需要传入一个ANativeWindow对象,也就是本地设备屏幕的表示。

我们可以通过Surface(由SurfaceView或者TextureView获得或者构建出来的Surface对象)来构建ANativeWindow
需要引入头文件:

#include <android/native_window.h>
#include <android/native_window_jni.h>

获取ANativeWindow的代码如下:

//surface也就是一个jobject,对应java层的Surface。
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);

如果我们要做离屏渲染的话,就需要用到离屏处理的Surface,也就是创建一个PBufferSurfacePBufferSurface的保存位置是在显存中的帧,具体代码可以参考。

java代码:

//创建离屏Surface
EGLSurface eglSurface;
int[] surfaceAttribs = {
        EGL14.EGL_WIDTH, width,
        EGL14.EGL_HEIGHT, height,
        EGL14.EGL_NONE
};
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0);
if (eglSurface == EGL14.EGL_NO_SURFACE) {
    throw new RuntimeException("Failed to create pixel buffer surface");
}

c代码:

//创建离屏Surface
EGLSurface egl_surface;
const EGLint attribs[] = {
    EGL_WIDTH, width,
    EGL_HEIGHT, height,
    EGL_NONE
};
egl_surface = eglCreatePbufferSurface(egl_display, egl_config, attribs);
if (egl_surface == EGL_NO_SURFACE)
    return error;

另外说一点,EGLSurface还支持参数的查询与设置,例如我们想知道新创建的Surface的宽高,那么可以用到下面的方法。

java代码:

//查询Surface的width
int[] array = new int[1];
EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, array, 0);
//设置Surface的width
if (!EGL14.eglSurfaceAttrib(eglDisplay, eglSurface, EGL14.EGL_WIDTH, 600))
    throw new RuntimeException("eglSurfaceAttrib fail");

c代码:

//查询Surface的width
EGLint value;
eglQuerySurface(_egl_display, _egl_surface, EGL_WIDTH, &value);
//设置Surface的width
if (!eglSurfaceAttrib(_egl_display, _egl_surface, EGL_WIDTH, 600))
    return error;


EGL变量与线程的绑定

一般来说,开发者需要为OpenGL ES开辟一个新的线程,来执行渲染操作,并且需要为该线程绑定显示设备EGLSurface和上下文环境EGLContext

每个线程都需要绑定一个上下文,才可以开始执行OpenGL ES指令,我们可以通过eglMakeCurrent来为该线程绑定SurfaceContext,值得注意一点的是一个EGLContext只能绑定到一个线程上面。

java代码:

if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
    throw new RuntimeException("eglMakeCurrent failed");
}

c代码:

if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context))
    return error;

可以通过返回值来判断eglMakeCurrent()是否成功,这个也是必要的。


OpenGL的渲染

涉及到OpenGl ES的绘图API和纹理相关方面的知识,本篇这里不做介绍,请关注后续相关文章。


双缓冲机制

EGL在初始化的时候默认设置的是双缓冲模式,也就是两份FrameBuffer;
即一份缓冲用于绘制图像,一份缓冲用于显示图像,每次显示时需要交换两份缓冲。
我们需要在OpenGL ES绘制完毕后,调用

eglSwapBuffers(egl_display, egl_surface);

将前台的FrameBuffer和后台FrameBuffer进行交换。


EGL的资源释放

当然,EGL在我们不需要的时候也是需要进行释放的。
我们需要将线程跟EGL环境解除绑定:

eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

然后要销毁EGLSurface:

eglDestroySurface(egl_display, egl_surface);

接着清理掉上下文环境:

eglDestroyContext(egl_display, egl_context);

最终关闭掉显示设备:

eglTerminate(egl_display);

通过上面也就是完成了一个EGL的资源释放工作。


整体java代码如下:


package com.sjy.livepushmine.opengl;

import android.util.Log;
import android.view.Surface;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;

/**
 * An EGL helper class.
 * 可参照:https://www.jianshu.com/p/d5ff1ff4ee2a,比较详细
 */

public class EglHelper {

    private EGL10 mEgl;
    private EGLDisplay mEglDisplay;
    private EGLContext mEglContext;
    private EGLSurface mEglSurface;
    private EGLConfig mEGLConfig;

    //    /**
//     * 用给的mEGLContext和mEGLSurface创建egl
//     */
    public void eglSetup( Surface surface,EGLContext eglContext) {
        try {
//            mEglContext = mEGLContext;
            // 获取egl实例
            mEgl = (EGL10) EGLContext.getEGL();

            // 返回默认的显示设备.
            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            // 需判断是否成功获取EGLDisplay
            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
                throw new RuntimeException("eglGetDisplay failed");
            }

            // mEglDisplay进行初始化
            int[] version = new int[2];
            if(!mEgl.eglInitialize(mEglDisplay, version)) {
                throw new RuntimeException("eglInitialize failed");
            }

            // 已经将OpenGL ES的输出与设备的屏幕桥接起来,需指定一些配置项
            int[] attributes = new int[]{
                    EGL10.EGL_RED_SIZE, 8,//颜色缓冲区红色分量的位数为8
                    EGL10.EGL_GREEN_SIZE, 8,
                    EGL10.EGL_BLUE_SIZE, 8,
                    EGL10.EGL_ALPHA_SIZE, 8,//模板缓冲区
                    EGL10.EGL_RENDERABLE_TYPE, 4,//egl版本  2.0
                    EGL10.EGL_NONE //并以EGL_NONE标识结尾信息
            };
            /*
             *   EGL_RENDERABLE_TYPE注释:
             *   android不支持OpenGL ES 2.x,因此在EGL10中某些相关常量参数只能用手写硬编码代替,
             *   例如EGL14.EGL_CONTEXT_CLIENT_VERSION以及EGL14.EGL_OPENGL_ES2_BIT等等
             *   public static final int EGL14.EGL_OPENGL_ES2_BIT                 = 0x0004;
             */
            int[] num_config = new int[1];
            // eglChooseConfig()方法得到配置选项信息
            if(!mEgl.eglChooseConfig(mEglDisplay, attributes, null, 1, num_config)){
                throw new IllegalArgumentException("eglChooseConfig failed");
            }

            int numConfigs = num_config[0];
            if (numConfigs <= 0) {
                throw new IllegalArgumentException(
                        "No configs match configSpec");
            }

            EGLConfig[] configs = new EGLConfig[numConfigs];
            if (!mEgl.eglChooseConfig(mEglDisplay, attributes, configs, numConfigs, num_config)) {
                throw new IllegalArgumentException("eglChooseConfig#2 failed");
            }

            mEGLConfig = configs[0];

            // 对应的Config不存在
            if (mEGLConfig == null) {
                throw new RuntimeException("eglChooseConfig returned null");
            }

            // 指定OpenGL ES2版本
            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2,EGL10.EGL_NONE};
            // 创建EGLContext上下文
            if(eglContext != null)
                mEglContext = mEgl.eglCreateContext(mEglDisplay, mEGLConfig, eglContext,attrib_list);
            else
                mEglContext = mEgl.eglCreateContext(mEglDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,attrib_list);
            // 创建可显示的Surface
            // 第三个参数在EGL10中只支持SurfaceHolder和SurfaceTexture
            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, configs[0], surface,null);
            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
                throw new RuntimeException("createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
            }

            /*
             每个线程都需要绑定一个上下文,才可以开始执行OpenGL ES指令,
             我们可以通过eglMakeCurrent来为该线程绑定Surface和Context
             */
            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
                Log.e("Egl", "eglSetup: " + Integer.toHexString(mEgl.eglGetError()));
                throw new RuntimeException("egl makeCurrent failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean swapBuffers() {
        if (mEgl != null) {
            return mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
        }
        Log.e("TAG", "egl is null");
        return false;
    }




    /**
     * 销毁mEglDisplay和当前线程绑定
     */
    public void destroySurface() {
        if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
            // 需要将线程跟EGL环境解除绑定
            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_CONTEXT);
            // 要销毁EGLSurface
            mEgl.eglDestroySurface(mEglDisplay,mEglSurface);
            mEglSurface = null;
            // 接着清理掉上下文环境
            mEgl.eglDestroyContext(mEglDisplay,mEglContext);
            mEglContext = null;
            // 最终关闭掉显示设备
            mEgl.eglTerminate(mEglDisplay);
            mEglDisplay = null;
        }
    }

    public EGLConfig getEglConfig() {
        return mEGLConfig;
    }

    public EGLSurface getEglSurface() {
        return mEglSurface;
    }

    public EGLContext getEGLContext() {
        return mEglContext;
    }
}

最后说一些

在Android平台上面,当程序切到后台的时候,需要释放EGL环境,在应用挪回前台时,重新初始化相关环境。
不过谷歌提供了一个已经封装完善的GLSurfaceView提供OpenGL ES的绘制环境,普通需求下采用GLSurfaceView进行绘制也是一个不错的选择。
有兴趣可以读读这篇文章:源码解析:Android源码GLSurfaceView源码解析
最后放一个EGL的官方文档地址


结语

这篇文章简单介绍了一下Android平台下的EGL环境的相关内容,并提供了基于java层EGL10和native层的EGL相关API的使用示例。
后续文章将介绍怎么在EGL环境下进行OpenGL ES相关 API的一些使用。

End!

转自作者:码农叔叔
转自链接:https://www.jianshu.com/p/d5ff1ff4ee2a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容