一篇文章学会Android Camera2 Api,实现自定义相机入门。

前言

最近在做相机相关的项目,考虑到调用系统相机部分功能不能满足项目需求,于是考虑自定相机这一块,由于之前没有接触过自定义相机这一块,于是查阅了相关资料,结合自身实践,做一个简单的总结。

相关参考资料

感谢下面文章提供的参考

切入正题

1)camera2包架构示意图:

image

2) camera2包中的主要API结构图:

image

3)主要API详解:

  • CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用CameraManagergetCameraCharacteristics(String)方法即可获取指定摄像头的相关特性。
  • CameraCharacteristics:摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头所支持的各种特性。
  • CameraDevice:代表系统摄像头。该类的功能类似于早期的Camera类。
  • CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()
    为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera v2 API为CameraCaptureSession提供了StateCallbackCaptureCallback等内部类。
  • CameraRequestCameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

4)自定义相机拍照大致流程:

image

1.

调用 CameraManageropenCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler) 方法打开指定摄像头。该方法的第一个参数cameraId代表要打开的摄像头ID(摄像头ID(通常0代表后置摄像头,1代表前置摄像头);第二个参数用于监听摄像头的状态;第三个参数代表执行callbackHandler,如果程序希望直接在当前线程中执行callback,则可将handler参数设为null

2. 获取CameraDevice对象

当摄像头被打开之后,程序即可获取CameraDevice—即根据摄像头ID获取了指定摄像头设备,然后调用CameraDevicecreateCaptureSession(List<Surface> outputs, CameraCaptureSession. StateCallback callback,Handler handler)方法来创建CameraCaptureSession。该方法的第一个参数是一个List集合,封装了所有需要从该摄像头获取图片的Surface,第二个参数用于监听CameraCaptureSession的创建过程;第三个参数代表执行callbackHandler,如果程序希望直接在当前线程中执行callback,则可将handler参数设为null

3. 设置设置摄像头模式

不管预览还是拍照,程序都调用CameraDevicecreateCaptureRequest(int templateType)方法创建CaptureRequest.Builder,该方法支持TEMPLATE_PREVIEW(预览)、TEMPLATE_RECORD(拍摄视频)、TEMPLATE_STILL_CAPTURE(拍照)等参数。
通过第3步所调用方法返回的CaptureRequest.Builder设置拍照的各种参数,比如对焦模式、曝光模式等。
调用CaptureRequest.Builderbuild()方法即可得到CaptureRequest对象,接下来程序可通过CameraCaptureSessionsetRepeatingRequest()方法开始预览,或调用capture()方法拍照。

5)案例代码

配置静态权限
6.0之后记得在代码中添加相机和读写存储卡的动态权限

    <!-- 相机 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.autocues"/> 
    <!-- 用于读写存储卡 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

相机界面简单布局
界面很简单,就一个TextureView和一个拍照按钮
activity_camera.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="net.ticp.uwonders.imagerecognize.activity.CameraActivity">

    <TextureView
        android:id="@+id/camera_texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/capture_ib"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="bottom|center"
        android:layout_marginBottom="10dp"
        android:background="@drawable/home_scan" />

</FrameLayout>

CameraActivity
代码实现,项目中使用了 Butterknife 简单注解
该文件注释比较清楚,相信大家能够看懂,就不分方法细讲了

/**
 * @author Marlon
 * @desc Camera2Activity 基于Camera2 API 自定义相机
 * @date 2018/6/13
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Activity extends AppCompatActivity {

    @BindView(R.id.texture)
    AutoFitTextureView texture;
    @BindView(R.id.takephoto)
    ImageView takephoto;
    @BindView(R.id.change)
    Switch change;
    @BindView(R.id.auto)
    RadioButton auto;
    @BindView(R.id.open)
    RadioButton open;
    @BindView(R.id.close)
    RadioButton close;
    @BindView(R.id.flash_rg)
    RadioGroup flashRg;

    /*** 相机管理类*/
    CameraManager mCameraManager;

    /*** 指定摄像头ID对应的Camera实体对象*/
    CameraDevice mCameraDevice;


    /**
     * 预览尺寸
     */
    private Size mPreviewSize;
    private int mSurfaceWidth;
    private int mSurfaceHeight;

    /*** 打开摄像头的ID{@link CameraDevice}.*/
    private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT;

    /*** 处理静态图像捕获的ImageReader。{@link ImageReader}*/
    private ImageReader mImageReader;

    /*** 用于相机预览的{@Link CameraCaptureSession}。*/
    private CameraCaptureSession mCaptureSession;

    /*** {@link CaptureRequest.Builder}用于相机预览请求的构造器*/
    private CaptureRequest.Builder mPreviewRequestBuilder;

    /***预览请求, 由上面的构建器构建出来*/
    private CaptureRequest mPreviewRequest;
    /**
     * 从屏幕旋转图片转换方向。
     */
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    /***判断是否支持闪关灯*/
    private boolean mFlashSupported;

    /*** 用于运行不应阻塞UI的任务的附加线程。*/
    private HandlerThread mBackgroundThread;

    /*** 用于在后台运行任务的{@link Handler}。*/
    private Handler mBackgroundHandler;

    /**
     * 文件存储路径
     */
    private File mFile;
    /**
     * 预览请求构建器, 用来构建"预览请求"(下面定义的)通过pipeline发送到Camera device
     * 这是{@link ImageReader}的回调对象。 当静止图像准备保存时,将会调用“onImageAvailable”。
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

    };

    /*** {@link CameraDevice.StateCallback}打开指定摄像头回调{@link CameraDevice}*/
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            cameraDevice.close();
            cameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            cameraDevice.close();
            cameraDevice = null;
        }

    };

    /**
     * TextureView 生命周期响应
     */
    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override //创建
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            //当TextureView创建完成,打开指定摄像头相机
            openCamera(width, height, mCameraId);
        }

        @Override //尺寸改变
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override //销毁
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override //更新
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {

        }
    };
    private int CONTROL_AE_MODE;


    /**
     * 打开指定摄像头ID的相机
     *
     * @param width
     * @param height
     * @param cameraId
     */
    private void openCamera(int width, int height, int cameraId) {
        if (ActivityCompat.checkSelfPermission(Camera2Activity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        try {
            mSurfaceWidth = width;
            mSurfaceHeight = height;
//            getCameraId(cameraId);
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId + "");
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 获取设备方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int totalRotation = sensorToDeviceRotation(characteristics, rotation);
            boolean swapRotation = totalRotation == 90 || totalRotation == 270;
            int rotatedWidth = mSurfaceWidth;
            int rotatedHeight = mSurfaceHeight;
            if (swapRotation) {
                rotatedWidth = mSurfaceHeight;
                rotatedHeight = mSurfaceWidth;
            }
            // 获取最佳的预览尺寸
            mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
            if (swapRotation) {
                texture.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                texture.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
            }
            if (mImageReader == null) {
                // 创建一个ImageReader对象,用于获取摄像头的图像数据,maxImages是ImageReader一次可以访问的最大图片数量
                mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                        ImageFormat.JPEG, 2);
                mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
            }
            //检查是否支持闪光灯
            Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
            mFlashSupported = available == null ? false : available;
            mCameraManager.openCamera(mCameraId + "", mStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 设置相机闪关灯模式
     *
     * @param AE_MODE 闪关灯的模式
     * @throws CameraAccessException
     */
    private void setFlashMode(int AE_MODE) {
        if (mFlashSupported) {
            this.CONTROL_AE_MODE = AE_MODE;
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE);
            if (AE_MODE == CaptureRequest.CONTROL_AE_MODE_OFF) {
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
            }
        }

        // 构建上述的请求
        mPreviewRequest = mPreviewRequestBuilder.build();
        // 重复进行上面构建的请求, 用于显示预览
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 创建预览对话
     */
    private void createCameraPreview() {
        try {
            // 获取texture实例
            SurfaceTexture surfaceTexture = texture.getSurfaceTexture();
            assert surfaceTexture != null;
            //我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
            surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            // 用来开始预览的输出surface
            Surface surface = new Surface(surfaceTexture);
            //创建预览请求构建器
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //将TextureView的Surface作为相机的预览显示输出
            mPreviewRequestBuilder.addTarget(surface);
            //在这里,我们为相机预览创建一个CameraCaptureSession。
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // 相机关闭时, 直接返回
                            if (null == mCameraDevice) {
                                return;
                            }
                            //会话准备就绪后,我们开始显示预览。
                            // 会话可行时, 将构建的会话赋给field
                            mCaptureSession = cameraCaptureSession;

                            //相机预览应该连续自动对焦。
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            //设置闪关灯模式
                            setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("预览失败了");

                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 拍照时调用方法
     */
    private void captureStillPicture() {
        try {
            if (mCameraDevice == null) {
                return;
            }
            // 创建作为拍照的CaptureRequest.Builder
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 将imageReader的surface作为CaptureRequest.Builder的目标
            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
/*            // 设置自动对焦模式
            mBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 设置自动曝光模式
            mBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);*/
            //设置为自动模式
//            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            setFlashMode(CONTROL_AE_MODE);
            // 停止连续取景
            mCaptureSession.stopRepeating();
            // 捕获静态图像
            mCaptureSession.capture(mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                // 拍照完成时激发该方法
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                    //重新打开预览
                    createCameraPreview();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取设备方向
     *
     * @param characteristics
     * @param deviceOrientation
     * @return
     */
    private static int sensorToDeviceRotation(CameraCharacteristics characteristics, int deviceOrientation) {
        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        deviceOrientation = ORIENTATIONS.get(deviceOrientation);
        return (sensorOrientation + deviceOrientation + 360) % 360;
    }

    /**
     * 获取可用设备可用摄像头列表
     */
    private void getCameraId(int ID) {
        try {
            for (String cameraId : mCameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == ID) {
                    continue;
                }
                mCameraId = Integer.valueOf(cameraId);
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置最佳尺寸
     *
     * @param sizes
     * @param width
     * @param height
     * @return
     */
    private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
        List<Size> collectorSizes = new ArrayList<>();
        for (Size option : sizes) {
            if (width > height) {
                if (option.getWidth() > width && option.getHeight() > height) {
                    collectorSizes.add(option);
                }
            } else {
                if (option.getHeight() > width && option.getWidth() > height) {
                    collectorSizes.add(option);
                }
            }
        }
        if (collectorSizes.size() > 0) {
            return Collections.min(collectorSizes, new Comparator<Size>() {
                @Override
                public int compare(Size s1, Size s2) {
                    return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
                }
            });
        }
        return sizes[0];
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera2);
        ButterKnife.bind(this);
        initView();
    }

    private void initView() {
        change.setOnCheckedChangeListener((buttonView, isChecked) -> {
            closeCamera();
            if (!isChecked) {
                //后置摄像头
                mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            } else {
                //前置摄像头
                mCameraId = CameraCharacteristics.LENS_FACING_BACK;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            }
        });

        flashRg.setOnCheckedChangeListener((group, checkedId) -> {
            switch (checkedId) {
                case R.id.auto:
                    //自动闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    break;
                case R.id.open:
                    //开启闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
                    break;
                case R.id.close:
                    //关闭闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_OFF);

                    break;
                default:
                    break;

            }
        });
        // 获取CameraManager 相机设备管理器
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    }

    /**
     * 初试化拍照线程
     */
    public void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("Camera Background");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    public void stopBackgroundThread() {
        if (mBackgroundThread != null) {
            mBackgroundThread.quitSafely();
            try {
                mBackgroundThread.join();
                mBackgroundThread = null;
                mBackgroundHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Closes the current {@link CameraDevice}.
     * 关闭正在使用的相机
     */
    private void closeCamera() {
        // 关闭捕获会话
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        // 关闭当前相机
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        // 关闭拍照处理器
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (texture.isAvailable()) {
            openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
        } else {
            texture.setSurfaceTextureListener(textureListener);
        }
        startBackgroundThread();
    }

    @Override
    protected void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @OnClick(R.id.takephoto)
    public void onViewClicked() {
        captureStillPicture();
    }

    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上显示Toast的方法
     *
     * @param text The message to show
     */
    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上显示Toast的方法
     *
     * @param text The message to show
     */
    private void showToast(final String text) {
        runOnUiThread(() -> Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show());
    }

    /**
     * Saves a JPEG {@link Image} into the specified {@link File}.
     * 保存图片到自定目录
     * 保存jpeg到指定的文件夹下, 开启子线程执行保存操作
     */
    private static class ImageSaver implements Runnable {

        /**
         * The JPEG image
         * 要保存的图片
         */
        private final Image mImage;
        /**
         * The file we save the image into.
         * 图片存储的路径
         */
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}

具体代码请查看项目demo地址demo

总结

本文主要总结使用Android Camera2 API 自定义相机,及实现闪光灯的打开,关闭,自动,前后摄像头的切换等功能的集成等)。
demo中包含了使用Android Camera1/ Camera2 API 自定义相机及相关功能,同时也包含了使用google框架 cameraView 自定义相机的demo,欢迎大家浏览和Star!
笔者水平有限,如有任何疑问,请大神多多赐教!

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

推荐阅读更多精彩内容