Android音视频录制与播放功能简述

安卓平台和声音录制与播放相关的主要是4个类:MediaRecorderMediaPlayerSoundPool,AudioRecordAudioTrack

  1. MediaRecorder可以录制视频和音频到文件
  2. MediaPlayer可以播放视频和音频文件
  3. SoundPool用于播放比较短的音频片段
  4. AudioRecord可以提供接口读取音频流数据(byte数组或者short数组)
  5. AudioTrack提供接口用于播放音频流数据。

其中MediaRecorder和AudioRecord用于声音录制,SoundPool、MediaPlayer和AudioTrack用于声音播放。AudioRecord和AudioTrack用于操作音频流数据,操作对象是byte数组(或者short数组),而MediaRecorder和MediaPlayer提供了经过更高层抽象和封装接口,直接对文件进行操作,而且他俩功能更丰富,同时支持音频和视频。

一、MediaRecorder

Paste_Image.png
MediaRecorder的状态简述:

Initial:初始化。也就是MediaRecorder 刚被创建的时候。在这个时候我们去设置音频或者视频的来源了,可能这个时候就有人问了 音频或者视频的来源是什么意思,举个例子吧,比如当我们在录音的时候,这个声音的来源就可以设置成手机的麦克风。
Initialized:初始化完成。这里我们已经知道音频或者视频的来源了,在这里我们就可以设置一些输出的属性了,比如输出文件的保存格式,编码格式等。
DataSourceConfigured:数据源配置改变。也就是我们改变了一些输出的属性,就会进入到这个状态。
Prepered:处于这个状态就说明了我们的配置已经完成了,现在准备去录制了。
Recordeing:录制中。
Released:资源被释放了。
Error:录制的时候发生了错误。

主要方法:
 setAudioChannels(int numChannels) 设置录制的音频通道数
 setAudioEncoder(int audio_encoder) 设置audio的编码格式
 setAudioEncodingBitRate(int bitRate) 设置录制的音频编码比特率
 setAudioSamplingRate(int samplingRate) 设置录制的音频采样率
 setAudioSource(int audio_source) 设置用于录制的音源
 setAuxiliaryOutputFile(String path) 辅助时间的推移视频文件的路径传递
 setAuxiliaryOutputFile(FileDescriptor fd)在文件描述符传递的辅助时间的推移视频
 setCamera(Camera c) 设置一个recording的摄像头
 setCaptureRate(double fps) 设置视频帧的捕获率
 setMaxDuration(int max_duration_ms) 设置记录会话的最大持续时间(毫秒)
 setMaxFileSize(long max_filesize_bytes) 设置记录会话的最大大小(以字节为单位)
 setOutputFile(FileDescriptor fd) 传递要写入的文件的文件描述符
 setOutputFile(String path) 设置输出文件的路径
 setOutputFormat(int output_format) 设置在录制过程中产生的输出文件的格式
 setPreviewDisplay(Surface sv) 表面设置显示记录媒体(视频)的预览
 setVideoEncoder(int video_encoder) 设置视频编码器,用于录制
 setVideoEncodingBitRate(int bitRate) 设置录制的视频编码比特率
 setVideoFrameRate(int rate) 设置要捕获的视频帧速率
 setVideoSize(int width, int height) 设置要捕获的视频的宽度和高度
 setVideoSource(int video_source) 开始捕捉和编码数据到setOutputFile(指定的文件)
 setLocation(float latitude, float longitude) 设置并存储在输出文件中的地理数据(经度和纬度)
 setProfile(CamcorderProfile profile) 指定CamcorderProfile对象
 setOrientationHint(int degrees)设置输出的视频播放的方向提示
 setOnErrorListener(MediaRecorder.OnErrorListener l)注册一个用于记录录制时出现的错误的监听器
 setOnInfoListener(MediaRecorder.OnInfoListener listener)注册一个用于记录录制时出现的信息事件
 getMaxAmplitude() 获取在前一次调用此方法之后录音中出现的最大振幅
 prepare()准备录制。
 release()释放资源
 reset()将MediaRecorder设为空闲状态
 start()开始录制
 stop()停止录制
MediaRecorder主要配置参数:
1、视频编码格式MediaRecorder.VideoEncoder
    default,H263,H264,MPEG_4_SP,VP8
2、音频编码格式MediaRecorder.AudioEncoder
    default,AAC,HE_AAC,AAC_ELD,AMR_NB,AMR_WB,VORBIS
3、视频资源获取方式MediaRecorder.VideoSource
    default,CAMERA,SURFACE
4、音频资源获取方式MediaRecorder.AudioSource
    defalut,camcorder,mic,voice_call,voice_communication,voice_downlink,voice_recognition, voice_uplink
5、资源输出格式MediaRecorder.OutputFormat
    amr_nb,amr_wb,default,mpeg_4,raw_amr,three_gpp,aac_adif, aac_adts, output_format_rtp_avp, output_format_mpeg2ts ,webm
实现录音的一般步骤:
1、实例化MediaRecorder mr,调用构造方法 
2、初始化mr:mr.setAudioSource(MIC)/setVideoSource(CAMERA) 
3、配置DataSource:设置输出文件格式/路径,编码器等 
4、准备录制:mr.prepare() 
5、开始录制:mr.start() 
6、停止录制:mr.stop() 
7、释放资源:mr.release() 
注:2,3不可调换顺序 
添加权限: 
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
<uses-permission android:name="android.permission.RECORD_AUDIO"> 

二、MediaPlayer

Paste_Image.png
1、当一个MediaPlayer对象被创建或者调用reset()方法之后,它处于空闲状态,调用release()方法后处于结束状态。
  • 一个MediaPlayer对象调用了reset()方法后,再调用其它方法可能会触发OnErrorListener.onError()事件,未调用reset()方法则不会触发。
  • 当Mediaplayer对象不再被使用时,最好调用release()方法对其进行释放,使其处于结束状态,此时它不能被使用
  • Mediaplayer对象被创建时(调用构造方法)处于空闲状态,若使用create()方法创建后则处于准备状态。
2、 一般情况下,一些常用的播放控制操作可能因为音频、视频的格式不被支持或者质量较差以及流超时,也有可能由于开发者的疏忽使得Mediaplayer对象处于无效状态等而导致错误。此时可通过注册setOnErrorListener方法实现监控。如果发生了错误,Mediaplayer对象将处于多雾状态,可以使用reset()方法来回复错误。
3、任何Mediaplayer对象都必须先处于准备状态,然后才开始播放
4、要开始播放Mediaplayer对象都必须成功调用start()方法,可通过isPlaying()方法来检测是否正在播放
5、当Mediaplayer对象在播放时,可以进行暂停和停止操作,pause()方法暂停播放,stop()方法停止播放。处于暂停暂停时可通过start()方法恢复播放,但是处于停止状态时则必须先调用prepare()方法使其处于准备状态,再调用start()方法。

主要方法:

  1. 实例化方式
    使用直接new的方式:
    MediaPlayer mp = new MediaPlayer();
    使用create的方式:
    MediaPlayer mp = MediaPlayer.create(this, R.raw.test);
  2. 设置播放源
setDataSource(String path)//指定装载path路径所代表的文件。
setDataSource(Context context, Uri uri, Map<String, String headers)//指定装载uri所代表的文件。
setDataSource(Context context, Uri uri)//指定装载uri所代表的文件。
setDataSource(FileDescriptor fd, long offset, long length)//指定装载fd所代表的文件中从offset开始长度为length的文件内容。
setDataSource(FileDescriptor fd)//指定装载fd所代表的文件。
  1. 配置播放参数
setAudioStreamType(int streamtype)//设置音频流的类型。
setDisplay(SurfaceHolder sh)//设置显示方式。
setLooping(boolean looping)//设置是否循环播放。
setNextMediaPlayer(MediaPlayer next)//设置当前流媒体播放完毕,下一个播放的MediaPlayer。
setScreenOnWhilePlaying(boolean screenOn)//设置是否使用SurfaceHolder来显示。
setSurface(Surface surface)//设置Surface。
setVideoScalingMode(int mode)//设置视频缩放的模式。
setVolume(float leftVolume, float rightVolume)//设置播放器的音量。
setWakeMode(Context context, int mode)//为MediaPlayer设置低级电源管理行为。
  1. 播放控制函数
start()//开始或恢复播放。
stop()//停止播放。
pause()//暂停播放。
prepare()//准备播放(装载音频),调用此方法会使MediaPlayer进入Prepared状态。
prepareAsync()//准备播放异步音频。
release()//释放媒体资源
reset()//重置MediaPlayer进入未初始化状态。
seekTo(int msec)//指定的时间位置。
  1. 监听事件函数
setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)//注册一个回调函数,在网络视频流缓冲变化时调用。
setOnCompletionListener(MediaPlayer.OnCompletionListener listener)//为Media Player的播放完成事件绑定事件监听器。
setOnErrorListener(MediaPlayer.OnErrorListener listener)//为MediaPlayer的播放错误事件绑定事件监听器。
setOnPreparedListener(MediaPlayer.OnPreparedListener listener)//当MediaPlayer调用prepare()方法时触发该监听器。
setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener)//当MediaPlayer调用seek()方法时触发该监听器。
setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener)//注册一个用于监听视频大小改变的监听器。
  1. 获取参数函数
getCurrentPosition()//获取当前播放的位置。
getDuration()//获取音频的时长。
getVideoHeight()//获取视频的高度。
getVideoWidth()//获取视频的宽度。
isLooping()//判断MediaPlayer是否正在循环播放。
isPlaying()//判断MediaPlayer是否正在播放。

三、AudioRecord

  1. AudioRecord 的工作流程
(1) 配置参数,初始化内部的音频缓冲区
(2) 开始采集
(3) 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。
(4) 停止采集,释放资源
  1. AudioRecord 的参数配置
  • audioSource
    该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。
  • sampleRateInHz
    采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
  • channelConfig
    通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)。
  • audioFormat
    这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。

四、AudioTrack

  1. ** AudioTrack 的工作流程**
(1) 配置参数,初始化内部的音频播放缓冲区
(2) 开始播放
(3) 需要一个线程,不断地向 AudioTrack 的缓冲区“写入”音频数据,注意,这个过程一定要及时,否则就会出现“underrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“送入”音频数据,导致内部的音频播放缓冲区为空。
(4) 停止播放,释放资源
  1. ** AudioTrack 的参数配置**
  • streamType
    这个参数代表着当前应用使用的哪一种音频管理策略,当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果,该参数的可选的值以常量的形式定义在 AudioManager 类中,主要包括:
STREAM_VOCIE_CALL:电话声音
STREAM_SYSTEM:系统声音
STREAM_RING:铃声
STREAM_MUSCI:音乐声
STREAM_ALARM:警告声
STREAM_NOTIFICATION:通知声
  • sampleRateInHz
    采样率,从AudioTrack源码的“audioParamCheck”函数可以看到,这个采样率的取值范围必须在 4000Hz~192000Hz 之间。
  • mode
    AudioTrack 提供了两种播放模式,一种是 static 方式,一种是 streaming 方式,前者需要一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段; 后者则是按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景。
    可选的值以常量的形式定义在 AudioTrack 类中,一个是 MODE_STATIC,另一个是 MODE_STREAM,根据具体的应用传入对应的值即可。

五、SoundPool

soundlPool 用于播放比较短的音频片段,比如游戏声音、按键声、铃声片段等等,它可以同时播放多个音频。

主要方法:

  1. 构造实例
    SoundPool(int maxStreams, int streamType, int srcQuality) 参数依次是:
    ①指定支持多少个声音,SoundPool对象中允许同时存在的最大流的数量,该值太大就会报错AudioFlinger could not create track, status: -12 ,就听不到声音
    ②指定声音类型,流类型可以分为STREAM_VOICE_CALL, STREAM_SYSTEM, STREAM_RING,STREAM_MUSIC 和STREAM_ALARM四种类型。在AudioManager中定义。
    ③指定声音品质(采样率变换质量),一般直接设置为0!
      在低版本中可以用上述构造方法,而API 21(Android 5.0)后这个构造方法就过时了! 而用到一个SoundPool.Builder的东东,我们要实例化SoundPool只需调用:
SoundPool.Builder spb = new SoundPool.Builder();
spb.setMaxStreams(10);
spb.setAudioAttributes(null);    //转换音频格式
SoundPool sp = spb.build();      //创建SoundPool对象
  1. 加载声音资源文件
load(Context context, int resId, int priority) //从APK资源载入
load(String path, int priority)
load(FileDescriptor fd, long offset, long length, int priority)
load(AssetFileDescriptor afd, int priority)

参数介绍

context:上下文
resId:资源id,如从raw文件获取填写R.raw.xxx
priority:没什么用的一个参数,建议设置为1,保持和未来的兼容性
path:文件路径,文件的绝对路线,如存放在sd卡中的音频
FileDescriptor:文件描述符
AssetFileDescriptor:从asset目录读取某个资源文件,context.getAssets().openFd("xxx"),xxx表示文件名

上述方法都会返回一个声音的ID,Integer类型,我们可以通过建立一个Map<Integer,Integer> 来存储和获取声音方法如下,

Map<Integer,Integer> map=new HashMap<Integer, Integer>();
map.put(1,soundPool.load(context.getAssets().openFd("FadeOut.ogg"),1));
  1. 播放音频文件
    play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate),其返回值为一个int类型的数字
    参数依次是:
soundID:Load()返回的声音ID号,以上可以通过map.get(1)获取
leftVolume:左声道音量设置  一般为0-1,默认填1
rightVolume:右声道音量设置 一般为0-1,默认填1
priority:指定播放声音的优先级,数值越高,优先级越大。默认填0
loop:指定是否循环:-1表示无限循环,0表示不循环,其他值表示要重复播放的次数
rate:指定播放速率:1.0的播放率可以使声音按照其原始频率,而2.0的播放速率,可以使声音按照其 原始频率的两倍播放。如果为0.5的播放率,则播放速率是原始频率的一半。播放速率的取值范围是0.5至2.0。

如果SoundPool刚调完加载load函数之后,直接调用SoundPool的play函数可能出现error "sample 1 not READY",所以建议,调用加载资源函数load之后,实现资源加载结束的监听函数,在这个监听到资源加载结束之后,播放音频文件。

soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                soundPool.play(map.get(1),1,1,0,0,1);
            }
        });
  1. 去除音频或者停止播放重置资源
soundPool.pause(int streamID)  暂停指定播放流的音效
streamID:应通过play()返回
soundPool.resume(int streamID)  继续播放指定播放流的音效
streamID:应通过play()返回
soundPool.stop(int streamID) 终止指定播放流的音效
streamID:应通过play()返回
soundPool.unload(int soundID) 卸载一个指定的音频资源.
soundID:Load()返回的声音ID号,以上可以通过map.get(1)获取
 soundPool.release(); 释放SoundPool中的所有音频资源.

注意:

  1. play()函数传递的是一个load()返回的soundID——指向一个被记载的音频资源 ,如果播放成功则返回一个非0的streamID——指向一个成功播放的流 ;同一个soundID 可以通过多次调用play()而获得多个不同的streamID (只要不超出同时播放的最大数量);
  2. pause()、resume()和stop()是针对播放流操作的,传递的是play()返回的streamID ;
  3. play()中的priority参数,只在同时播放的流的数量超过了预先设定的最大数量是起作用,管理器将自动终止优先级低的播放流。如果存在多个同样优先级的流,再进一步根据其创建事件来处理,新创建的流的年龄是最小的,将被终止;
  4. 无论如何,程序退出时,手动终止播放并释放资源是必要的。
  5. 如果你音效多,也不要指望unload方法来清除掉一些音效后再load新的进去,虽然unload后音效卸载了,但是前面分给它在SoundPool里面的Id可没有释放掉,也就是说这个时候你load新的进去只会在后面继续累加,然后累加多了就超过256了,然后就就听不到声音,然后就没有然后了。要想彻底清掉前面的音效请使用release方法,它会连内存中占用的资源一起释放掉。
  6. 其他还有点什么呢,load需要一点点时间,load后不要马上unload,load ---play--unload的做法并不可取,不要load太大的音效,它只会申请1M的内存空间。SoundPool出错后通常会看到return的值是0。

五、MediaRecorder 和 AudioRecord的选择

Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。

如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger 进行交互的。

音频的开发,更广泛地应用不仅仅局限于本地录音,因此,我们需要重点掌握如何利用更加底层的 AudioRecord API 来采集音频数据(注意,使用它采集到的音频数据是原始的PCM格式,想压缩为mp3,aac等格式的话,还需要专门调用编码器进行编码)。

六、MediaPlayer,SoundPool和AudioTrack的选择

MediaPlayer 更加适合在后台长时间播放本地音乐文件或者在线的流式资源;
SoundPool 则适合播放比较短的音频片段,比如游戏声音、按键声、铃声片段等等,它可以同时播放多个音频;
而 AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景。

七、播放视频 VideoView

主要方法:

getBufferPercentage:得到缓冲的百分比 
getCurrentPosition:得到当前播放位置 
getDuration:得到视频文件的时间 
resolveAdjustedSize:调整视频显示大小 
setMediaController:设置播放控制器模式(播放进度条) 
setOnCompletionListener:当视频文件播放完时触发事件 
setVideoPath:设置视频源路径 
setVideoURI:设置视频源地址 

八、相机Camera

Camera回调事件
  1. android.hardware.Camera.AutoFocusCallback
    当自动对焦时候调用,该接口具有一个void onAutoFocus(boolean success,Camera camera)函数;
  2. android.hardware.Camera.ErrorCallback
    摄像头出错的时候调用,这个接口具有一个void onError(int error,Camera camera)函数;前者表示数据类型,取值是Camera类中的常量CAMERA_ERROR_UNKNOWN或者是 CAMERA_ERROR_SERVICE_DIED;
    前者是不明错误,后者是表示服务已经关闭,在这种情况下需要释放当前的Camera对象,然后再初 始化一个。
  3. android.hardware.camera.PreviewCallback
    在图像预览时调用,这个接口有一个void onPreviewFrame(byte[] data,Camera camera);参数data为每帧图像的数据流。我们可以根据实际需要来实现这个接口。
  4. android.hardware.Camera.ShutterCallback
    在图像预览的时候调用,这个接口具有一个void onShutter();
    可以在改函数中通知用户快门已经关闭,例如播放一个声音。
  5. android.hardware.Camera.PictureCallback
    当拍摄相片的时候调用,该接口具有一个void onPictureTaken(byte[] data,Camera camera)函数;参数和预览的一样。在android中主要有三个类实现了这个接口,分别是PostViewPictureCallback、 RawPictureCallback、JepgPictureCallback。我们可以根据需要定义自己需要的类。
  6. 还提供了放大缩小的监听器android.hardware.Camera.OnZoomChangeListener
private final class ZoomListener implements android.hardware.Camera.OnZoomChangeListener {
public void onZoomChange(int value, boolean stopped, android.hardware.Camera camera) ;
Camera.Parameters 相机的属性参数

通过Camera.open(int cameraId)启动相机服务,然后通过Camera对象的getParameters()函数来得到一个android.hardware.Camera.Parameters 对象,Parameters提供了一些接口来设置Camera的属性:

  1. setPictureFormat(int pixel_format)
    设置图片的格式,其取值为ImageFormat.JPEG、ImageFormat.RGB_565或者ImageFormat.FLEX_RGB_888等格式。
  2. setPreviewFormat(int pixel_format)
    设置图片的预览格式,取值如上。
  3. setPictureSize(int width,int height)
    设置图片的高度和宽度,单位为像素。
  4. setPreviewSize(int width,int height)
    设置预览的高度和宽度,取值如上。
  5. setPreviewFrameRate(int fps)
    设置图片预览的帧速。
  6. setFocusMode(String value)
    设置聚焦模式,如Camera.Parameters.FOCUS_MODE_AUTO。
Camera类的方法

在设置好Camera的参数后,通过以下方法可执行相应操作。

  1. camera.startPreview()
    开始预览图像。
  2. camera.autoFocus(AutoFocusCallback cb)
    自动对焦。
  3. camera.takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg)
    执行拍照。
    注:takePicture方法要实现3个回调函数作为它的三个参数:Camera.ShutterCallback(快门回调接口),和两个Camera.Picture.Callback(原生图像数据接口和压缩格式图片数据接口)。
    需要许可
    <uses-permission android:name="android.permission.CAMERA" />
    若要将图片存储至sd卡中,则需要sd卡读写许可
  4. camera.stopPreview()
    停止预览 。
  5. camera.release()
    释放相机服务 。

九、闹钟设置AlarmManager

相关类:AlarmManager,它是专门用来设定在某个指定的时间去完成指定的事件。AlarmManager提供了访问系统警报的服务,只要在程序中设置了警报服务,AlarmManager就会通过onReceive()方法去执行这些事件,就算系统处于待机状态,同样不会影响运行。可通过Context.getSystemService(ALARM_SERVICE)方法来获得该服务。 
 方法说明: 
cancel:    取消AlarmManager服务 
set:    设置AlarmManager服务 
setInexactRepeating:设置不精确周期 
setRepeating:设置精确周期 
setTimeZone:设置时区 
 注:需创建一个BroadcastReceiver的子类,并覆盖onReceive()方法 
 铃声设置 
 系统自带的铃声都放在/system/medio/audio/文件夹中 
 铃音类型: TYPE_RINGTONE(来电铃音),TYPE_ALARM,TYPE_NOTIFICATION 
相关类:RingtoneManager 
方法介绍: 
getActualDefaultRingtoneUri:取得指定类型的铃声 
getCursor:返回所有可用铃声的游标 
getDefaultType:得到指定URI默认的铃声类型 
getRingtone 
 getRingtonePosition:得到铃声位置 
getRingtoneUri 
 getValidRingtoneUri:得到一个可用铃声的URI 
 isDefault:得到指定的Uri是否为默认的铃声 
setActualDefaultRingtoneUri:设置默认的铃声 

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

推荐阅读更多精彩内容