音视频-音频编/解码 实战

音视频-视频编/解码 实战文章中已经知道,我们音视频采集后的数据格式为CMSampleBufferRef类型。如果不需要对音频的编解码操作,可以直接播放该PCM音频数据。

一、直接播放采集到的CMSampleBufferRef音频数据

1.1 创建PCM音频播放器

- (instancetype)initWithConfigure:(CQAudioCoderConfigure *)configure {
    self = [super init];
    if (self) {
        //1、设置AudioStreamBasicDescription
        self.configure = configure;
        AudioStreamBasicDescription audioDescription = {0};
        audioDescription.mSampleRate = (Float64)self.configure.sampleRate;//采样率
        audioDescription.mChannelsPerFrame = (UInt32)self.configure.channelCount;//输出声道
        audioDescription.mFormatID = kAudioFormatLinearPCM;//输出格式
        audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;//编码 12
        audioDescription.mFramesPerPacket = 1;//每个包的帧数
        audioDescription.mBitsPerChannel = 16;//数据帧中每个通道的采样位数
        audioDescription.mBytesPerFrame = audioDescription.mBitsPerChannel / 8 * audioDescription.mChannelsPerFrame;//每一帧的大小(采样位数 / 8 * 声道数)
        audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket;//每个packet的大小(每帧的大小 * 帧数)
        audioDescription.mReserved = 0;//是否倒置
        
        CQAudioPlayerState playerState = {0};
        playerState.audioDescription = audioDescription;
        self.playerState = playerState;
        
        //2、设置session
        NSError *error;
        //注意:该操作为同步操作,会阻塞线程。
        [[AVAudioSession sharedInstance] setActive:YES error:&error];
        if (error) {
            NSLog(@"AVAudioSession setActive error: %@", error);
        }
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
        if (error) {
            NSLog(@"AVAudioSession setCategory error: %@", error);
        }
        
        //3、创建播放队列
        OSStatus status = AudioQueueNewOutput(&_playerState.audioDescription, CQAudioQueueOutputCallback, NULL, NULL, NULL, 0, &_playerState.audioQueue);
        if (status != noErr) {
            error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"AudioQueue create error: %@", error);
            return self;
        }
        //4、设置播放音频队列参数值
        AudioQueueSetParameter(_playerState.audioQueue, kAudioQueueParam_Volume, 1);
        self.isPlaying = false;
    }
    return self;
}

static void CQAudioQueueOutputCallback(void * __nullable inUserData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer) {
    AudioQueueFreeBuffer(audioQueue, audioQueueBuffer);
}
  • 1、设置AudioStreamBasicDescription(音频流的基本信息描述)
  • 2、激活AVAudioSession(同步操作,会阻塞线程),设置会话类别。
  • 3、AudioQueueNewOutput创建音频队列,用于播放音频数据。
    参数1:音频流基本信息描述的结构指针。
    参数2:播放完缓冲区后要调用的回调函数的指针。
    参数3:指向指定要传递给回调函数的数据的值或指针。
    参数4:事件循环,指定NULL,则在音频队列的一个内部线程上调用回调。
    参数5:循环模式。
    参数6:0
    参数7:指向新创建的播放音频队列对象的指针
  • 4、设置播放音频队列参数值
    参数1:要开始的音频队列
    参数2:属性(kAudioQueueParam_Volume音量)
    参数3:对应的属性值

1.2 将CMSampleBufferRef转换为NSData

- (NSData *)convertToPcmData:(CMSampleBufferRef)sampleBuffer {
    size_t pcmSize = CMSampleBufferGetTotalSampleSize(sampleBuffer);
    
    int8_t *audioData = (int8_t *)malloc(pcmSize);
    memset(audioData, 0, pcmSize);
    
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    CMBlockBufferCopyDataBytes(blockBuffer, 0, pcmSize, audioData);
    NSData *data = [NSData dataWithBytes:audioData length:pcmSize];
    free(audioData);
    return data;
}
  • 1、CMSampleBufferGetTotalSampleSize:获取PCM数据大小pcmSize
  • 2、为音频数据开辟内存空间audioData
  • 3、CMSampleBufferGetDataBuffer:获取CMBlockBuffer, 这里面保存了PCM数据。
  • 4、CMBlockBufferCopyDataBytes:将音频数据拷贝到audioData

1.3 播放NSData格式的音频数据

- (void)startPlayPCMData:(NSData *)data {
    AudioQueueBufferRef audioQueueBuffer; 
    AudioQueueAllocateBuffer(_playerState.audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffer);
    
    memcpy(audioQueueBuffer->mAudioData, data.bytes, data.length);
    audioQueueBuffer->mAudioDataByteSize = (UInt32)data.length;
    
    //将缓存区添加到录制或播放音频队列的缓冲区队列
    OSStatus status = AudioQueueEnqueueBuffer(_playerState.audioQueue, audioQueueBuffer, 0, NULL);
    if (status != noErr) {
        NSLog(@"Error: audio queue palyer  enqueue error: %d",(int)status);
    }
    
    QueueStart(_playerState.audioQueue, NULL);
}
  • 1、AudioQueueAllocateBuffer使用 音频队列对象 分配 音频队列缓冲区对象
    参数1:要分配缓冲区的音频队列
    参数2:新缓冲区所需的字节大小(MIN_SIZE_PER_FRAME每一帧使用最小字节大小)
    参数3:指向新分配的 音频队列缓冲区对象
  • 2、memcpy:将data中的数据拷贝到audioQueueBuffer->mAudioData
  • 3、AudioQueueEnqueueBuffer为录音或回放的音频队列分配缓冲区。
  • 4、开始播放或录制音频 。

二、对采集到的CMSampleBufferRef音频数据进行编码

2.1 创建音频转换器

- (void)createAudioConverter:(CMSampleBufferRef)sampleBuffer {
    //1、获取输入AudioStreamBasicDescription
    CMFormatDescriptionRef description = CMSampleBufferGetFormatDescription(sampleBuffer);
    AudioStreamBasicDescription inAudioDescription = *CMAudioFormatDescriptionGetStreamBasicDescription(description);
    
    //2、设置输出AudioStreamBasicDescription
    AudioStreamBasicDescription outAudioDescription = {0};
    outAudioDescription.mSampleRate = (Float64)self.configure.sampleRate;//采样率
    outAudioDescription.mFormatID = kAudioFormatMPEG4AAC;                //输出格式
    outAudioDescription.mFormatFlags = kMPEG4Object_AAC_LC;              //如果设为0 代表无损编码
    outAudioDescription.mBytesPerPacket = 0;                             //自己确定每个packet 大小
    outAudioDescription.mFramesPerPacket = 1024;                         //每一个packet帧数 AAC-1024;
    outAudioDescription.mBytesPerFrame = 0;                              //每一帧大小
    outAudioDescription.mChannelsPerFrame = (uint32_t)self.configure.channelCount;//输出声道数
    outAudioDescription.mBitsPerChannel = 0;                             //数据帧中每个通道的采样位数。
    outAudioDescription.mReserved =  0;                                  //对其方式 0(8字节对齐)
    
    //填充输出相关信息
    UInt32 outSize = sizeof(outAudioDescription);
    OSStatus status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outSize, &outAudioDescription);
    if (status != noErr) {
        NSLog(@"AudioFormatGetProperty status : %d", status);
    }
    
    //3、获取编码器类型描述(只能传入software)
    AudioClassDescription *audioClassDesc = [self getAudioClassDescriptionWithFormatID:outAudioDescription.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    
  // 4、创建音频转换器
    status = AudioConverterNewSpecific(&inAudioDescription, &outAudioDescription, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"音频转换器创建失败 status:%d", (int)status);
        return;
    }
    
    // 5、设置音频转换器的属性
    //编解码质量
    UInt32 temp = kAudioConverterQuality_High;
    AudioConverterSetProperty(self.audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
    //设置比特率
    uint32_t bitrate = (uint32_t)self.configure.bitrate;
    uint32_t bitrateSize = sizeof(bitrate);
    status = AudioConverterSetProperty(self.audioConverter, kAudioConverterCodecQuality, bitrateSize, &bitrate);
    if (status != noErr) {
        NSLog(@"设置比特率失败 status:%d", (int)status);
    }
}
  • 1、获取 音频流数据基本描述 的输入对象。
  • 2、设置 音频流数据基本描述 的输出对象。
  • 3、获取编码器类型描述AudioClassDescription
  • 4、AudioConverterNewSpecific创建音频转换器
    参数1:输入音频流数据基本描述
    参数2:输出音频流数据基本描述
    参数3:编码器类型描述AudioClassDescription的数量
    参数4:编码器类型描述AudioClassDescription
    参数5:创建的音频转换器
  • 5、AudioConverterSetProperty设置音频转换器的属性。

看下第三步:获取编码器类型描述AudioClassDescription:

- (AudioClassDescription *)getAudioClassDescriptionWithFormatID:(AudioFormatID)formatID fromManufacture: (uint32_t)manufacturer {
    //1、
    UInt32 encoderSpecifier = formatID;
    UInt32 size;//获取满足AAC编码器的总大小
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size);
    if (status != noErr) {
        NSLog(@"AudioFormatGetPropertyInfo error status: %d", (int)status);
        return nil;
    }
    unsigned int count = size / sizeof(AudioClassDescription);//aac编码器的个数
    AudioClassDescription description[count];//编码器的数组
    
    //2、将编码器的信息写入数组description
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size, &description);
    if (status != noErr) {
        NSLog(@"AudioFormatGetProperty error status: %d", (int)status);
        return nil;
    }
    //3、
    static AudioClassDescription audioClassDescription;
    for (unsigned int i = 0; i < count; i++) {
        if (formatID == description[i].mSubType && manufacturer == description[i].mManufacturer) {
            audioClassDescription = description[i];
            return &audioClassDescription;
        }
    }
    return nil;
}
  • 1、AudioFormatGetPropertyInfo获取编码器的属性信息。
    参数1:编码器类型
    参数2:类型描述大小
    参数3:类型描述
    参数4:编码器的总大小
  • 2、AudioFormatGetProperty将满足AAC编码的编码器的信息写入数组。
  • 3、循环查询符合类型的编码器类型描述

2.2 编码

- (void)encodeSamepleBuffer:(CMSampleBufferRef)sampleBuffer {
    CFRetain(sampleBuffer);
    //1.创建音频转换器。
    if (!self.audioConverter) {
        [self createAudioConverter:sampleBuffer];
    }
    
    dispatch_async(self.encoderQueue, ^{
        //2.获取blockBuffer中音频数据大小以及音频数据地址
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
        OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &self->_pcmBufferSize, &self->_pcmBuffer);
        if (status != kCMBlockBufferNoErr) {
            NSLog(@"CMBlockBufferGetDataPointer error status: %d", (int)status);
            return;
        }
       
        uint8_t *pcmBuffer = malloc(self->_pcmBufferSize); //为pcmBuffer开辟内存空间
        memset(pcmBuffer, 0, self->_pcmBufferSize);//将pcmBuffer数据set为0

        //3.将pcmBuffer数据填充到outBufferList 对象中
        AudioBufferList outBufferList = {0};
        outBufferList.mNumberBuffers = 1;
        outBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.configure.channelCount;
        outBufferList.mBuffers[0].mDataByteSize = (UInt32)self->_pcmBufferSize;
        outBufferList.mBuffers[0].mData = pcmBuffer;
        
        //4.实时音频压缩
        UInt32 outPacketSize = 1;//输出包大小为1
        //转换由输入回调函数提供的数据
        status = AudioConverterFillComplexBuffer(self.audioConverter, inputEncodeDataProc, (__bridge void * _Nullable)(self), &outPacketSize, &outBufferList, NULL);
        if (status == noErr) {
            //5、获取数据
            NSData *rawAAC = [NSData dataWithBytes: outBufferList.mBuffers[0].mData length:outBufferList.mBuffers[0].mDataByteSize];
            free(pcmBuffer);
            //6、添加`ADTS`头。想要获取裸流时,不用添加`ADTS`头。写入文件时,必须添加。
//            NSData *adtsData = [self setADTSDataForPacketLength:encodedAudioData.length];
//            NSMutableData *entireData = [NSMutableData dataWithCapacity:adtsData.length + encodedAudioData.length];
//            [entireData appendData:adtsData];
//            [entireData appendData:encodedAudioData];
            //7、将数据传递到回调队列中
            dispatch_async(self->_callbackQueue, ^{
                [self->_delegate audioEncodeCallback:rawAAC];
            });
        } else {
            NSLog(@"AudioConverterFillComplexBuffer error status: %d", (int)status);
        }
        //释放
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
    });
}
  • 1、创建音频转换器。
  • 2、获取CMBlockBufferRefCMBlockBufferRef中音频数据大小以及音频数据地址。
  • 3、创建AudioBufferList对象。
  • 4、AudioConverterFillComplexBuffer实时音频压缩,转换由输入回调函数提供的数据:
    参数1: 音频转换器
    参数2: inputEncodeDataProc回调函数,提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调。
    参数3: 回调函数中用到的值,一般情况下将我们自定义的编码器对象传过去,这里也就是self
    参数4: 输出包的大小
    参数5: 存储pcmBuffer数据的AudioBufferList对象地址
    参数6: outPacketDescription输出包信息
  • 5、获取压缩后的音频数据outBufferList.mBuffers[0].mData
  • 6、添加ADTS头。想要获取裸流时,不用添加ADTS头。写入文件时,必须添加。
  • 7、将压缩后的数据传递到回调队列中。
  • 8、手动释放blockBuffersampleBuffer

看下inputEncodeDataProc输入回调函数:

static OSStatus inputEncodeDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    
    CQAudioEncoder *audioEncoder = (__bridge CQAudioEncoder *)(inUserData);
    
    //判断pcmBuffsize大小
    if (!audioEncoder.pcmBufferSize) {
        *ioNumberDataPackets = 0;
        return  -1;
    }
    
    //填充数据
    ioData->mBuffers[0].mData = audioEncoder.pcmBuffer;
    ioData->mBuffers[0].mDataByteSize = (uint32_t)audioEncoder.pcmBufferSize;
    ioData->mBuffers[0].mNumberChannels = (uint32_t)audioEncoder.configure.channelCount;
    
    //清空数据
    audioEncoder.pcmBufferSize = 0;
    *ioNumberDataPackets = 1;
    return noErr;
}
  • 该函数的核心功能就是 提供要转换的音频数据。当转换器准备好接受新的输入数据时,会重复调用此回调。

添加ADTS头:

- (NSData*)setADTSDataForPacketLength:(NSUInteger)packetLength {
    int adtsLength = 7;
    char *packet = malloc(sizeof(char) * adtsLength);
    int profile = 2;
    int freqIdx = 4;
    int chanCfg = 1;
    NSUInteger fullLength = adtsLength + packetLength;
    
    packet[0] = (char)0xFF;
    packet[1] = (char)0xF9;
    packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
    packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
    packet[4] = (char)((fullLength&0x7FF) >> 3);
    packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
    packet[6] = (char)0xFC;
    NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
    return data;
}

三、解码

解码跟编码流程很像,这里就不详细介绍了,看核心代码:

3.1 创建音频转换器

- (void)createAudioConverter {
    //输出参数
    AudioStreamBasicDescription outAudioDes = {0};
    outAudioDes.mSampleRate = (Float64)self.configure.sampleRate;       //采样率
    outAudioDes.mChannelsPerFrame = (UInt32)self.configure.channelCount; //输出声道数
    outAudioDes.mFormatID = kAudioFormatLinearPCM;                //输出格式
    outAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //编码 12
    outAudioDes.mFramesPerPacket = 1;                            //每一个packet帧数 ;
    outAudioDes.mBitsPerChannel = 16;                             //数据帧中每个通道的采样位数。
    outAudioDes.mBytesPerFrame = outAudioDes.mBitsPerChannel / 8 *outAudioDes.mChannelsPerFrame;                              //每一帧大小(采样位数 / 8 *声道数)
    outAudioDes.mBytesPerPacket = outAudioDes.mBytesPerFrame * outAudioDes.mFramesPerPacket;                             //每个packet大小(帧大小 * 帧数)
    outAudioDes.mReserved =  0;                                  //对其方式 0(8字节对齐)
    
    //输入参数
    AudioStreamBasicDescription inAduioDes = {0};
    inAduioDes.mSampleRate = (Float64)self.configure.sampleRate;
    inAduioDes.mFormatID = kAudioFormatMPEG4AAC;
    inAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
    inAduioDes.mFramesPerPacket = 1024;
    inAduioDes.mChannelsPerFrame = (UInt32)self.configure.channelCount;
    
    //填充输出相关信息
    UInt32 inDesSize = sizeof(inAduioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inAduioDes);
    
    //获取编码器类型描述(只能传入software)
    AudioClassDescription *audioClassDesc = [self getAudioClassDescriptionWithFormatID:outAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer ];

    OSStatus status = AudioConverterNewSpecific(&inAduioDes, &outAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"AudioConverterNewSpecific error status: %d", (int)status);
        return;
    }
}

**获取编码器类型描述AudioClassDescription: **

 (AudioClassDescription *)getAudioClassDescriptionWithFormatID:(AudioFormatID)formatID fromManufacture: (uint32_t)manufacturer  {
    static AudioClassDescription desc;
    UInt32 decoderSpecific = formatID;
    
    UInt32 size;//获取满足AAC编码器的总大小
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
    if (status != noErr) {
        NSLog(@"AudioFormatGetPropertyInfo Decoder error status= %d", (int)status);
        return nil;
    }
    unsigned int count = size / sizeof(AudioClassDescription);
    AudioClassDescription description[count];
    //将满足aac解码的解码器的信息写入数组
    status = AudioFormatGetProperty(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size, &description);
    if (status != noErr) {
        NSLog(@"AudioFormatGetProperty Decoder error status= %d", (int)status);
        return nil;
    }
    for (unsigned int i = 0; i < count; i++) {
        if (formatID == description[i].mSubType && manufacturer == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}

3.2解码

- (void)audioDecode:(NSData *)data {
    if (!_audioConverter) { return; }
    dispatch_async(self.decoderQueue, ^{
        //记录需要解码的数据, 作为参数传入解码回调函数
        CQAudioUserData userData = {0};
        userData.channelCount = (UInt32)self.configure.channelCount;
        userData.data = (char *)[data bytes];
        userData.size = (UInt32)data.length;
        userData.packetDesc.mDataByteSize = (UInt32)data.length;
        userData.packetDesc.mStartOffset = 0;
        userData.packetDesc.mVariableFramesInPacket = 0;
        
        //输出大小和packet个数
        UInt32 pcmBufferSize = (UInt32)(2048 * self.configure.channelCount);
        UInt32 pcmDataPacketSize = 1024;
        //创建临时容器pcm
        uint8_t *pcmBuffer = malloc(pcmBufferSize);
        memset(pcmBuffer, 0, pcmBufferSize);
        
        //输出outBufferList
        AudioBufferList outBufferList = {0};
        outBufferList.mNumberBuffers = 1;
        outBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.configure.channelCount;
        outBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
        outBufferList.mBuffers[0].mData = pcmBuffer;
        
        AudioStreamPacketDescription outPacketDescription = {0};
        //配置填充函数,获取输出数据
        OSStatus status = AudioConverterFillComplexBuffer(self->_audioConverter, &outDecodeDataProc, &userData, &pcmDataPacketSize, &outBufferList, &outPacketDescription);
        if (status != noErr) {
            NSLog(@"AAC Decoder error status: %d",(int)status);
            return;
        }
        //如果获取到数据
        if (outBufferList.mBuffers[0].mDataByteSize > 0) {
            NSData *decodedData = [NSData dataWithBytes:outBufferList.mBuffers[0].mData length:outBufferList.mBuffers[0].mDataByteSize];
            dispatch_async(self->_callbackQueue, ^{
                [self->_delegate audioDecodeCallback:decodedData];
            });
        }
        free(pcmBuffer);
    });
}

解码器回调函数

static OSStatus outDecodeDataProc(  AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,  AudioStreamPacketDescription **outDataPacketDescription,  void *inUserData) {
    
    CQAudioUserData *audioDecoder = (CQAudioUserData *)(inUserData);
    if (audioDecoder->size <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
   
    //传入数据
    *outDataPacketDescription = &audioDecoder->packetDesc;
    (*outDataPacketDescription)[0].mStartOffset = 0;
    (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
    (*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
    
    ioData->mBuffers[0].mData = audioDecoder->data;
    ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
    ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
    return noErr;
}

最后,解码后的数据直接使用我们上面创建的PCM播放器去播放即可。

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

推荐阅读更多精彩内容