iOS-直播開發(fā)(開發(fā)從底層做起)之音視頻采集

timg.jpeg

由于我們公司不是專門做直播的, 所以研究直播開發(fā)完全處于興趣愛好,可能很多地方用處理的不是很周到, 所以, 希望大家多提提意見, 互相學習一下哈!

這里附上我寫的第一篇直播開發(fā)的文章傳送門
iOS-直播開發(fā)(開發(fā)從底層做起)

好啦, 廢話不多說, 直奔主題! 本篇文章是針對直播開發(fā)中的第一部分, 音視頻采集! 用的是iOS 原生的AVFoundation框架!
Demo傳送門GitHub

實現(xiàn)的效果圖


圖片發(fā)自簡書App

1. 所使用的系統(tǒng)類

AVCaptureSession *session;    // 音視頻管理對象
AVCaptureDevice *videoDevice; // 視頻設(shè)備對象 (用來操作閃光燈, 聚焦, 攝像頭切換等)
AVCaptureDevice *audioDevice; // 音頻設(shè)備對象
AVCaptureDeviceInput *videoInput;         // 視頻輸入對象
AVCaptureDeviceInput *audioInput;         // 音頻輸入對象
AVCaptureVideoDataOutput *videoOutput;    // 視頻輸出對象
AVCaptureAudioDataOutput *audioOutput;    // 音頻輸出對象
AVCaptureVideoPreviewLayer *preViewLayer; // 用來展示視頻的layer對象

2. 封裝音視頻采集類

為了方便后邊的使用, 我們把音視頻采集這個功能單獨封裝成一個類, 這里封裝成 JFCaptureSession


JFCaptureSession.h
typedef NS_ENUM(NSUInteger, JFCaptureSessionPreset){
    /// 低分辨率
    JFCaptureSessionPreset368x640 = 0,
    /// 中分辨率
    JFCaptureSessionPreset540x960 = 1,
    /// 高分辨率
    JFCaptureSessionPreset720x1280 = 2
};

這個枚舉是來初始化JFCaptureSession 該類對象的時候需要傳的一個枚舉值, 來制定視頻采集的分辨率, 有三個枚舉值

JFCaptureSessionPreset368x640  //該枚舉值是分辨率最低的, 基本上所有的機型都支持該分辨率
JFCaptureSessionPreset720x1280 //而這個枚舉值分辨率比較高, 可能有些機型不支持該分辨率, .m中的實現(xiàn)有判斷, 如果不支持該分辨率, 則會降一級

.h中的另一個枚舉 該枚舉用來操控前后攝像頭的

// 攝像頭方向
typedef NS_ENUM(NSInteger, JFCaptureDevicePosition) {
    JFCaptureDevicePositionFront = 0,  // 前置攝像頭
    JFCaptureDevicePositionBack        // 后置攝像頭
};

然后就是JFCaptureSession 的代理 JFCaptureSessionDelegate, 用來回調(diào)采集的音視頻幀數(shù)據(jù) CMSampleBufferRef

/** 視頻取樣數(shù)據(jù)回調(diào) */
- (void)videoCaptureOutputWithSampleBuffer:(CMSampleBufferRef)sampleBuffer;
/** 音頻取樣數(shù)據(jù)回調(diào) */
- (void)audioCaptureOutputWithSampleBuffer:(CMSampleBufferRef)sampleBuffer;

JFCaptureSession 該類的初始化方法, 初始化的時候需要傳一分辨率的枚舉值, 來設(shè)置要采集視頻的分辨率

- (instancetype)defaultJFCaptureSessionWithSessionPreset:(JFCaptureSessionPreset)sessionPreset;
@property (nonatomic, strong) UIView *preView;      // 用來展示視頻圖像
@property (nonatomic, assign) JFCaptureDevicePosition videoDevicePosition;    // 先后攝像頭切換
@property (nonatomic, assign) id <JFCaptureSessionDelegate> delegate;  // 代理

開始采集, 暫停采集

/**
 開始
 */
- (void)startRunning;
/**
 暫停
 */
- (void)stopRunning;

JFCaptureSession.m 集體實現(xiàn)音視頻采集的方法
// 初始化方法
- (instancetype)defaultJFCaptureSessionWithSessionPreset:(JFCaptureSessionPreset)sessionPreset {
    if ([super init]) {
        self.sessionPreset = sessionPreset;
        // 初始化Session
        [self initAVCaptureSession];
    }
    return self;
}
- (void)initAVCaptureSession {
    // 初始化
    self.session = [[AVCaptureSession alloc] init];
    // 設(shè)置錄像的分辨率
    [self.session canSetSessionPreset:[self supportSessionPreset]];
/** 注意: 配置AVCaptureSession 的時候, 必須先開始配置, beginConfiguration, 配置完成, 必須提交配置 commitConfiguration, 否則配置無效  **/
    // 開始配置
    [self.session beginConfiguration];
    // 設(shè)置視頻 I/O 對象 并添加到session
    [self videoInputAndOutput];
    // 設(shè)置音頻 I/O 對象 并添加到session
    [self audioInputAndOutput];
    // 提交配置
    [self.session commitConfiguration];
}
// 設(shè)置視頻 I/O 對象
- (void)videoInputAndOutput {
     NSError *error;
    // 初始化視頻設(shè)備對象
    self.videoDevice = nil;
    // 創(chuàng)建攝像頭類型數(shù)組 (前置, 和后置攝像頭之分)
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    // 便利獲取的所有支持的攝像頭類型
    for (AVCaptureDevice *devcie in devices) {
        // 默然先開啟前置攝像頭
        if (devcie.position == AVCaptureDevicePositionFront) {
            self.videoDevice = devcie;
        }
    }
    // 視頻輸入
    // 根據(jù)視頻設(shè)備來初始化輸入對象
    self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.videoDevice error:&error];
    if (error) {
        NSLog(@"== 攝像頭錯誤 ==");
        return;
    }
    // 將輸入對象添加到管理者 AVCaptureSession 中
    // 需要先判斷是否能夠添加輸入對象
    if ([self.session canAddInput:self.videoInput]) {
        // 可以添加, 才能添加
        [self.session addInput:self.videoInput];
    }
    // 視頻輸出對象
    self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    // 是否允許卡頓時丟幀
    self.videoOutput.alwaysDiscardsLateVideoFrames = NO;
    if ([self supportsFastTextureUpload]) {
        // 是否支持全頻色彩編碼 YUV 一種色彩編碼方式, 即YCbCr, 現(xiàn)在視頻一般采用該顏色空間, 可以分離亮度跟色彩, 在不影響清晰度的情況下來壓縮視頻
        BOOL supportFullYUVRange = NO;
        // 獲取輸出對象所支持的像素格式
        NSArray *supportedPixelFormats = self.videoOutput.availableVideoCVPixelFormatTypes;
        for (NSNumber *currentPixelFormat in supportedPixelFormats) {
            if ([currentPixelFormat integerValue] == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
                supportFullYUVRange = YES;
            }
        }
        // 根據(jù)是否支持全頻色彩編碼 YUV 來設(shè)置輸出對象的視頻像素壓縮格式
        if (supportFullYUVRange) {
            [self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
        } else {
            [self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
        }
    } else {
        [self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
    }
    // 創(chuàng)建設(shè)置代理是所需要的線程隊列 優(yōu)先級設(shè)為高
    dispatch_queue_t videoQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    // 設(shè)置代理
    [self.videoOutput setSampleBufferDelegate:self queue:videoQueue];
    // 判斷session 是否可添加視頻輸出對象
    if ([self.session canAddOutput:self.videoOutput]) {
        [self.session addOutput:self.videoOutput];
        // 鏈接視頻 I/O 對象
        [self connectionVideoInputVideoOutput];
    }
}
// 設(shè)置音頻I/O 對象
- (void)audioInputAndOutput {
    NSError *error;
    // 初始音頻設(shè)備對象
    self.audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    // 音頻輸入對象
    self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.audioDevice error:&error];
    if (error) {
        NSLog(@"== 錄音設(shè)備出錯");
    }
    // 判斷session 是否可以添加 音頻輸入對象
    if ([self.session canAddInput:self.audioInput]) {
        [self.session addInput:self.audioInput];
    }
    // 音頻輸出對象
    self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
    // 判斷是否可以添加音頻輸出對象
    if ([self.session canAddOutput:self.audioOutput]) {
        [self.session addOutput:self.audioOutput];
    }
    // 創(chuàng)建設(shè)置音頻輸出代理所需要的線程隊列
    dispatch_queue_t audioQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    [self.audioOutput setSampleBufferDelegate:self queue:audioQueue];
}
// 鏈接 視頻 I/O 對象
- (void)connectionVideoInputVideoOutput {
    // AVCaptureConnection是一個類阵谚,用來在AVCaptureInput和AVCaptureOutput之間建立連接。AVCaptureSession必須從AVCaptureConnection中獲取實際數(shù)據(jù)。
    AVCaptureConnection *connection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
    // 設(shè)置視頻的方向, 如果不設(shè)置的話, 視頻默認是旋轉(zhuǎn) 90°的
    connection.videoOrientation = AVCaptureVideoOrientationPortrait;
    // 設(shè)置視頻的穩(wěn)定性, 先判斷connection 連接對象是否支持 視頻穩(wěn)定
    if ([connection isVideoStabilizationSupported]) {
        connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    }
    // 縮放裁剪系數(shù), 設(shè)為最大
    connection.videoScaleAndCropFactor = connection.videoMaxScaleAndCropFactor;
}
// 判斷是否支持設(shè)置的分辨率, 如果不支持, 默認降一級, 還不支持, 設(shè)為默認
- (NSString *)supportSessionPreset {
    if (![self.session canSetSessionPreset:self.avPreset]) {
        self.sessionPreset = JFCaptureSessionPreset540x960;
        if (![self.session canSetSessionPreset:self.avPreset]) {
            self.sessionPreset = JFCaptureSessionPreset368x640;
        }
    } else {
        self.sessionPreset = JFCaptureSessionPreset368x640;
    }
    return self.avPreset;
}
#pragma mark - Setter
- (void)setSessionPreset:(JFCaptureSessionPreset)sessionPreset {
    _sessionPreset = sessionPreset;
}
// 根據(jù)視頻分辨率, 設(shè)置具體對應(yīng)的類型
- (NSString *)avPreset {
    switch (self.sessionPreset) {
        case JFCaptureSessionPreset368x640:
            _avPreset = AVCaptureSessionPreset640x480;
            break;
        case JFCaptureSessionPreset540x960:
            _avPreset = AVCaptureSessionPresetiFrame960x540;
            break;
        case JFCaptureSessionPreset720x1280:
            _avPreset = AVCaptureSessionPreset1280x720;
            break;
        default:
            _avPreset = AVCaptureSessionPreset640x480;
            break;
    }
    return _avPreset;
}
// 攝像頭切換
- (void)setVideoDevicePosition:(JFCaptureDevicePosition)videoDevicePosition {
    if (_videoDevicePosition != videoDevicePosition) {
        _videoDevicePosition = videoDevicePosition;
        if (_videoDevicePosition == JFCaptureDevicePositionFront) {
            self.videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionFront];
        } else {
            self.videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
        }
        [self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
            NSError *error;
            AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:&error];
            if (newVideoInput != nil) {
                //必選先 remove 才能詢問 canAdd
                [self.session removeInput:_videoInput];
                if ([self.session canAddInput:newVideoInput]) {
                    [self.session addInput:newVideoInput];
                    _videoInput = newVideoInput;
                }else{
                    [self.session addInput:_videoInput];
                }
            } else if (error) {
                NSLog(@"切換前/后攝像頭失敗, error = %@", error);
            }
        }];   
    }
}
// 獲取需要的設(shè)備對象
- (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position {
    // 獲取所有類型的攝像頭設(shè)備
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
    AVCaptureDevice *captureDevice = devices.firstObject;   // 先初始化一個設(shè)備對象并賦初值
    // 便利獲取需要的設(shè)備
    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
           captureDevice = device;
            break;
        }
    }
    return captureDevice;
}
#pragma mark  更改設(shè)備屬性前一定要鎖上
-(void)changeDevicePropertySafety:(void (^)(AVCaptureDevice *captureDevice))propertyChange{
    //也可以直接用_videoDevice,但是下面這種更好
    AVCaptureDevice *captureDevice= [_videoInput device];
    NSError *error;
    //注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖,意義是---進行修改期間,先鎖定,防止多處同時修改
    BOOL lockAcquired = [captureDevice lockForConfiguration:&error];
    if (!lockAcquired) {
        NSLog(@"鎖定設(shè)備過程error,錯誤信息:%@",error.localizedDescription);
    }else{
        //調(diào)整設(shè)備前后要調(diào)用beginConfiguration/commitConfiguration
        [self.session beginConfiguration];
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
        [self.session commitConfiguration];
    }
}
// 展示視頻的試圖
- (void)setPreView:(UIView *)preView {
    _preView = preView;
    if (_preView && !self.preViewLayer) {
        self.preViewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
        self.preViewLayer.frame = _preView.layer.bounds;
        // 設(shè)置layer展示視頻的方向
        self.preViewLayer.connection.videoOrientation = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;
        self.preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        self.preViewLayer.position = CGPointMake(_preView.frame.size.width * 0.5, _preView.frame.size.height * 0.5);
        CALayer *layer = _preView.layer;
        layer.masksToBounds = YES;
        [layer addSublayer:self.preViewLayer];
    }
}

開始和暫停音視頻數(shù)據(jù)的方法實現(xiàn)

#pragma mark - Method
- (void)startRunning {
    [self.session startRunning];
}
- (void)stopRunning {
    if ([self.session isRunning]) {
        [self.session stopRunning];
    }
}

視頻輸出對象和音頻輸出對象的代理方法是同一個

#pragma mark - AVCaptureVideoDataAndAudioDataOutputSampleBufferDelegate
// 實現(xiàn)視頻輸出對象和音頻輸出對象的代理方法, 在該方法中獲取音視頻采集的數(shù)據(jù), 或者叫做幀數(shù)據(jù)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    // 判斷 captureOutput 多媒體輸出對象的類型
    if (captureOutput == self.audioOutput) {    // 音頻輸出對象
        if (self.delegate && [self.delegate respondsToSelector:@selector(audioCaptureOutputWithSampleBuffer:)]) {
            [self.delegate audioCaptureOutputWithSampleBuffer:sampleBuffer];
        }
    } else {                                    // 視頻輸出對象
        if (self.delegate && [self.delegate respondsToSelector:@selector(videoCaptureOutputWithSampleBuffer:)]) {
            [self.delegate videoCaptureOutputWithSampleBuffer:sampleBuffer];
        }
    }
}
// 是否支持快速紋理更新
- (BOOL)supportsFastTextureUpload;
{
#if TARGET_IPHONE_SIMULATOR
    return NO;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
    return (CVOpenGLESTextureCacheCreate != NULL);
#pragma clang diagnostic pop
#endif
}
- (void)dealloc {
    [self stopRunning];
    // 取消代理, 回到主線程
    [self.videoOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];
    [self.audioOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];
}

到此, 音視頻采集的類已經(jīng)封裝完成!

3.JFCaptureSession的使用

用的時候需要先檢驗設(shè)備是否授權(quán)攝像頭或麥克風的使用權(quán)限!

注意Xcode8.0以后, 使用麥克風, 攝像頭, 相冊等需要在info.plist文件中添加開啟權(quán)限的Key 和 value

key value
Privacy - Camera Usage Description cameraDescription
Privacy - Photo Library Usage Description photoLibraryDescription
Privacy - Microphone Usage Description microphoneDescription
infoFile.jpg

攝像頭和麥克風的權(quán)限檢驗

// 檢查是否授權(quán)攝像頭的使用權(quán)限
- (void)checkVideoDeviceAuth {
    switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
        case AVAuthorizationStatusAuthorized:    // 已授權(quán)
            self.authRemember += 1;
            break;
        case AVAuthorizationStatusNotDetermined: // 未授權(quán), 進行允許和拒絕授權(quán)
        {
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if (granted) {
                    NSLog(@"已開啟攝像頭權(quán)限");
                } else {
                    NSLog(@"拒絕授權(quán)");
                }
            }];
        }
            break;
        default:
            NSLog(@"用戶尚未授權(quán)攝像頭的使用權(quán)");
            break;
    }
}
// 檢查是否授權(quán)麥克風的shiyongquan
- (void)checkAudioDeviceAuth {
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
    switch (status) {
        case AVAuthorizationStatusNotDetermined:{
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
                if (granted) {
                    self.authRemember += 1;
                } else {
                    NSLog(@"拒絕授權(quán)");
                }
            }];
        }
            break;
        case AVAuthorizationStatusAuthorized:
            NSLog(@"已開啟麥克風權(quán)限");
            break;
        case AVAuthorizationStatusDenied:
        case AVAuthorizationStatusRestricted:
            break;
        default:
            break;
    }
}

本文中, 設(shè)置的是只有攝像頭和麥克風同事已授權(quán)的時候才初始化的JFCaptureSession的實例對象

self.session = [[JFCaptureSession alloc] defaultJFCaptureSessionWithSessionPreset:JFCaptureSessionPreset540x960];
        _session.preView = self.view;
        _session.delegate = self;  // 記得實現(xiàn)代理方法, 不然獲取不到采集的數(shù)據(jù)
[self.session startRunning];
/** 在需要暫停的時候 調(diào)用
[self.session stopRunning];
*/ 就可以啦

4.Demo下載地址

Demo傳送門GitHub

5.結(jié)尾

本文是用的AVFoundation 框架實現(xiàn)的音視頻數(shù)據(jù)采集, 系統(tǒng)的原生框架進行視頻采集, 如果進行美顏的話, 工作量和難度會增加很多很多, 不過如果需要進行美顏, 我們可以使用GPUImage 開源框架的美顏相機GPUImageVideoCamera來進行視頻數(shù)據(jù)采集! 后邊有時間我會專門寫篇文章, 來跟大家談?wù)撘幌翯PUImageVideoCamera 的視頻數(shù)據(jù)采集等!
音視頻的數(shù)據(jù)采集, 相對來說不是很難, AVFoundation 中的很多類我們都比較陌生, 很少使用到, 所以很感覺相對難一點! 這篇文章只是分享了一下我個人對AVFoundation框架中部分類的使用和見解,拿出來跟大家分享探討一下, 希望能對大家有所幫助, 有不完善的地方, 希望大家能多提提, 我這邊也學習改正一下!
由于工作比較忙, 可能后邊的技術(shù)文正會更的比較慢, 見諒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匹涮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖宵距,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吨拗,居然都是意外死亡满哪,警方通過查閱死者的電腦和手機婿斥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨鸭,“玉大人民宿,你說我怎么就攤上這事∠窦Γ” “怎么了活鹰?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長只估。 經(jīng)常有香客問我志群,道長,這世上最難降的妖魔是什么仅乓? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任赖舟,我火速辦了婚禮,結(jié)果婚禮上夸楣,老公的妹妹穿的比我還像新娘宾抓。我一直安慰自己,他們只是感情好豫喧,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布石洗。 她就那樣靜靜地躺著,像睡著了一般紧显。 火紅的嫁衣襯著肌膚如雪讲衫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天孵班,我揣著相機與錄音涉兽,去河邊找鬼。 笑死篙程,一個胖子當著我的面吹牛枷畏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虱饿,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拥诡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了氮发?” 一聲冷哼從身側(cè)響起渴肉,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爽冕,沒想到半個月后仇祭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡颈畸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年乌奇,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚣艇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡华弓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出困乒,到底是詐尸還是另有隱情寂屏,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布娜搂,位于F島的核電站迁霎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏百宇。R本人自食惡果不足惜考廉,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望携御。 院中可真熱鬧昌粤,春花似錦、人聲如沸啄刹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誓军。三九已至袱讹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昵时,已是汗流浹背捷雕。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壹甥,地道東北人救巷。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像盹廷,于是被迫代替她去往敵國和親征绸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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