IOS使用AudioToolbox實(shí)現(xiàn)音頻編解碼

音頻信息是如何捕捉的呢距误?主要通過(guò)圖一的過(guò)程:


圖一

自然界中的聲音非常復(fù)雜簸搞,波形極其復(fù)雜,通常我們采用的是脈沖代碼調(diào)制編碼准潭,即PCM編碼趁俊。PCM通過(guò)抽樣、量化刑然、編碼三個(gè)步驟將連續(xù)變化的模擬信號(hào)轉(zhuǎn)換為數(shù)字編碼寺擂。

  • 抽樣:對(duì)模擬信號(hào)進(jìn)行周期性掃描,把時(shí)間上連續(xù)的信號(hào)變成時(shí)間上離散的信號(hào)泼掠;
  • 量化:用一組規(guī)定的電平怔软,把瞬時(shí)抽樣值用最接近的電平值來(lái)表示,通常是用二進(jìn)制表示;
  • 編碼:用一組二進(jìn)制碼組來(lái)表示每一個(gè)有固定電平的量化值武鲁;

采樣后的數(shù)據(jù)大小 = 采樣率值×采樣大小值×聲道數(shù) bps爽雄。一個(gè)采樣率為44.1KHz蝠检,采樣大小為16bit沐鼠,雙聲道的PCM編碼的WAV文件,它的數(shù)據(jù)速率=44.1K×16×2 bps=1411.2 Kbps= 176.4 KB/s叹谁。

AAC高級(jí)音頻編碼

AAC(Advanced Audio Coding)饲梭,中文名:高級(jí)音頻編碼,出現(xiàn)于1997年焰檩,基于MPEG-2的音頻編碼技術(shù)憔涉。由Fraunhofer IIS、杜比實(shí)驗(yàn)室析苫、AT&T兜叨、Sony等公司共同開(kāi)發(fā)穿扳,目的是取代MP3格式。

AAC音頻格式

AAC音頻格式有ADIF和ADTS:

  • ADIF:Audio Data Interchange Format 音頻數(shù)據(jù)交換格式国旷。這種格式的特征是可以確定的找到這個(gè)音頻數(shù)據(jù)的開(kāi)始矛物,不需進(jìn)行在音頻數(shù)據(jù)流中間開(kāi)始的解碼,即它的解碼必須在明確定義的開(kāi)始處進(jìn)行跪但。故這種格式常用在磁盤(pán)文件中履羞。
  • ADTS:Audio Data Transport Stream 音頻數(shù)據(jù)傳輸流。這種格式的特征是它是一個(gè)有同步字的比特流屡久,解碼可以在這個(gè)流中任何位置開(kāi)始忆首。它的特征類(lèi)似于mp3數(shù)據(jù)流格式。


    image.png

iOS上把PCM音頻編碼成AAC音頻流

  1. 設(shè)置編碼器(codec)被环,并開(kāi)始錄制糙及;
  2. 收集到PCM數(shù)據(jù),傳給編碼器蛤售;
  3. 編碼完成回調(diào)callback丁鹉,寫(xiě)入文件。


    image.png

音頻壓縮原理

  • 時(shí)域冗余
  • 頻域冗余
  • 聽(tīng)覺(jué)冗余
    這部分過(guò)濾原理可以百度看下悴能,簡(jiǎn)單點(diǎn)說(shuō)就是在時(shí)間空間維度上過(guò)濾掉一部分人耳聽(tīng)不到的部分頻率揣钦。

AudioToolbox 的具體代碼實(shí)現(xiàn)

首先定義一個(gè)類(lèi)來(lái)做配置,我們要了解下幾個(gè)概念
采樣頻率(sampleRate):也稱為采樣速度或者采樣率漠酿,定義了每秒從連續(xù)信號(hào)中提取并組成離散信號(hào)的采樣個(gè)數(shù)冯凹,它用赫茲(Hz)來(lái)表示。采樣頻率的倒數(shù)是采樣周期炒嘲,它是采樣之間的時(shí)間間隔宇姚。通俗的講采樣頻率是指計(jì)算機(jī)每秒鐘采集多少個(gè)信號(hào)樣本。采樣頻率越高聲音的還原就越真實(shí)越自然夫凸。

8,000 Hz是電話所用采樣率, 對(duì)于人的說(shuō)話已經(jīng)足夠
11,025 Hz是AM調(diào)幅廣播所用采樣率
22,050 Hz和24,000 Hz- FM是調(diào)頻廣播所用采樣率
32,000 Hz是miniDV 數(shù)碼視頻 camcorder浑劳、DAT (LP mode)所用采樣率
44,100 Hz是音頻 CD, 也常用于 MPEG-1 音頻(VCD, SVCD, MP3)所用采樣率 (超過(guò)該采樣率,人耳很難分辨)
47,250 Hz是商用 PCM 錄音機(jī)所用采樣率
48,000 Hz是miniDV夭拌、數(shù)字電視魔熏、DVD、DAT鸽扁、電影和專(zhuān)業(yè)音頻所用的數(shù)字聲音所用采樣率
50,000 Hz是商用數(shù)字錄音機(jī)所用采樣率
96,000 或者 192,000 Hz - DVD-Audio蒜绽、一些 LPCM DVD 音軌、BD-ROM(藍(lán)光盤(pán))音軌桶现、和 HD-DVD (高清晰度 DVD)音軌所用所用采樣率
2.8224 MHz是Direct Stream Digital 的 1 位 sigma-delta modulation 過(guò)程所用采樣率躲雅。

音頻比特率

音頻的比特率公式: 比特率=采樣率 * 單個(gè)的周期音頻數(shù)據(jù)長(zhǎng)度 (sampleSize)。
如16bit 單聲道(channelCount) 48KHz音頻的比特率:
48KHz * (16 * 1) = 1536kbps = 192 kBps


@interface SQAudioConfig  : NSObject
@property(nonatomic,assign)NSInteger bitrate;
@property(nonatomic,assign)NSInteger channelCount;
/**采樣率*/
@property (nonatomic, assign) NSInteger sampleRate;//(默認(rèn)44100)
/**采樣點(diǎn)量化*/
@property (nonatomic, assign) NSInteger sampleSize;//(16)

@implementation SQAudioConfig
+ (instancetype)defaultConifg {
    return  [[SQAudioConfig alloc] init];
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.bitrate = 96000;
        self.channelCount = 1;
        self.sampleSize = 16;
        self.sampleRate = 44100;
//比特率 = 44100*1*16;
    }
    return self;
}
@end

接下來(lái)我們要配置我們的編碼器骡和,通過(guò)AudioConverterNewSpecific來(lái)創(chuàng)建一個(gè)新專(zhuān)用編碼器相赁,根據(jù)輸入音頻數(shù)據(jù)描述參數(shù)和輸出音頻數(shù)據(jù)描述相寇,用AudioStreamBasicDescription封裝描述信息。

//配置音頻編碼參數(shù)
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
//通過(guò)輸入的sampleBuffer來(lái)獲取輸入描述信息
    AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    //設(shè)置輸出參數(shù)
    AudioStreamBasicDescription outputAudioDes ={0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;   //采樣率
    outputAudioDes.mFormatID = kAudioFormatMPEG4AAC;                //輸出格式
    outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC;              // 如果設(shè)為0 代表無(wú)損編碼
    outputAudioDes.mBytesPerPacket = 0;                             //自己確定每個(gè)packet 大小
    outputAudioDes.mFramesPerPacket = 1024;                         //每一個(gè)packet幀數(shù) AAC-1024钮科;
    outputAudioDes.mBytesPerFrame = 0;                              //每一幀大小
    outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; //輸出聲道數(shù)
    outputAudioDes.mBitsPerChannel = 0;                             //數(shù)據(jù)幀中每個(gè)通道的采樣位數(shù)裆赵。
    outputAudioDes.mReserved =  0;                                  //對(duì)其方式 0(8字節(jié)對(duì)齊)
    //填充輸出相關(guān)信息
    UInt32 outDesSize = sizeof(outputAudioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);
    //獲取編碼器的描述信息(只能傳入software)
    AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    /** 創(chuàng)建converter
        參數(shù)1:輸入音頻格式描述
        參數(shù)2:輸出音頻格式描述
        參數(shù)3:class desc的數(shù)量
        參數(shù)4:class desc
        參數(shù)5:創(chuàng)建的解碼器
    */
    OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"Error!:硬編碼AAC創(chuàng)建失敗, status= %d", (int)status);
        return;
    }
    // 設(shè)置編解碼質(zhì)量
    /*
     kAudioConverterQuality_Max                              = 0x7F,
     kAudioConverterQuality_High                             = 0x60,
     kAudioConverterQuality_Medium                           = 0x40,
     kAudioConverterQuality_Low                              = 0x20,
     kAudioConverterQuality_Min                              = 0
     */
     UInt32 temp = kAudioConverterQuality_High;
     //編解碼器的呈現(xiàn)質(zhì)量
    AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
    //設(shè)置比特率
    uint32_t audioBitrate = (uint32_t)self.config.bitrate;
    uint32_t audioBitrateSize = sizeof(audioBitrate);
    status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, audioBitrateSize, &audioBitrate);
    if (status != noErr) {
        NSLog(@"Error6逅浴:硬編碼AAC 設(shè)置比特率失敗");
    }
}
/**
 獲取編碼器類(lèi)型描述
 參數(shù)1:類(lèi)型
 */
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
    static AudioClassDescription desc;
      UInt32 encoderSpecific = type;
      
      //獲取滿足AAC編碼器的總大小
      UInt32 size;
      /**
       參數(shù)1:編碼器類(lèi)型
       參數(shù)2:類(lèi)型描述大小
       參數(shù)3:類(lèi)型描述
       參數(shù)4:大小
       */
      OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
    if(status != noErr){
        NSLog(@"ErrorU绞凇:硬編碼AAC get info 失敗, status= %d", (int)status);
        return nil;
    }
    //計(jì)算aac編碼器的個(gè)數(shù)
    unsigned int count = size / sizeof(AudioClassDescription);
    //創(chuàng)建一個(gè)包含count個(gè)編碼器的數(shù)組
    AudioClassDescription description[count];
    //將滿足aac編碼的編碼器的信息寫(xiě)入數(shù)組
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
   for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}

對(duì)編碼器做好配置后,我們就可以對(duì)捕獲到的PCM數(shù)據(jù)進(jìn)行處理,AVFoudation捕捉到的數(shù)據(jù)是封裝在CMSampleBufferRef這個(gè)結(jié)構(gòu)體里桨嫁。通過(guò)CMSampleBufferRef可以獲取數(shù)據(jù)所在結(jié)構(gòu)體CMBlockBufferRef植兰,然后調(diào)用CMBlockBufferGetDataPointer來(lái)獲取數(shù)據(jù)內(nèi)存地址。然后通過(guò)AudioConverterFillComplexBuffer來(lái)實(shí)現(xiàn)編碼璃吧,需要注意的是這個(gè)函數(shù)需要將pcm數(shù)據(jù)再進(jìn)行一次封裝(有點(diǎn)繞)楣导,封裝成AudioBufferList.然后編寫(xiě)我們的回調(diào)函數(shù)AudioConverterFillComplexBuffer,這個(gè)函數(shù)主要用于向提供輸入數(shù)據(jù),也就是你要轉(zhuǎn)換的數(shù)據(jù)畜挨,在這里封裝成AudioBufferList筒繁。誤區(qū):
這外面定義的outAudioBufferList和里面的參數(shù)ioData是不同的AudioBufferList一個(gè)是輸出一個(gè)是輸入。

/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
    CFRetain(sampleBuffer);
    if(!_audioConverter){
         [self setupEncoderWithSampleBuffer:sampleBuffer];
    }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_encoderQueue, ^{
        //3.獲取CMBlockBuffer, 這里面保存了PCM數(shù)據(jù)
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
         OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        //5.判斷status狀態(tài)
        NSError *error = nil;
        if (status != kCMBlockBufferNoErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"Error: ACC encode get data point error: %@",error);
            return;
        }
        uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
        memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
        //3.輸出buffer
        /*
         typedef struct AudioBufferList {
         UInt32 mNumberBuffers;
         AudioBuffer mBuffers[1];
         } AudioBufferList;
         
         struct AudioBuffer
         {
         UInt32              mNumberChannels;
         UInt32              mDataByteSize;
         void* __nullable    mData;
         };
         typedef struct AudioBuffer  AudioBuffer;
         */
        //將pcmBuffer數(shù)據(jù)填充到outAudioBufferList 對(duì)象中
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
        outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)_pcmBufferSize;
        outAudioBufferList.mBuffers[0].mData = pcmBuffer;
        //輸出包大小為1
        UInt32 outputDataPacketSize = 1;
        //配置填充函數(shù)巴元,獲取輸出數(shù)據(jù)
        //轉(zhuǎn)換由輸入回調(diào)函數(shù)提供的數(shù)據(jù)
        /*
         參數(shù)1: inAudioConverter 音頻轉(zhuǎn)換器
         參數(shù)2: inInputDataProc 回調(diào)函數(shù).提供要轉(zhuǎn)換的音頻數(shù)據(jù)的回調(diào)函數(shù)毡咏。當(dāng)轉(zhuǎn)換器準(zhǔn)備好接受新的輸入數(shù)據(jù)時(shí),會(huì)重復(fù)調(diào)用此回調(diào).
         參數(shù)3: inInputDataProcUserData
         參數(shù)4: inInputDataProcUserData,self
         參數(shù)5: ioOutputDataPacketSize,輸出緩沖區(qū)的大小
         參數(shù)6: outOutputData,需要轉(zhuǎn)換的音頻數(shù)據(jù)
         參數(shù)7: outPacketDescription,輸出包信息
         */
        status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);
        
        if (status == noErr) {
            //獲取數(shù)據(jù)
            NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            //釋放pcmBuffer
            free(pcmBuffer);
            //添加ADTS頭逮刨,想要獲取裸流時(shí)呕缭,請(qǐng)忽略添加ADTS頭,寫(xiě)入文件時(shí)修己,必須添加
            //            NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
            //            NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
            //            [fullData appendData:adtsHeader];
            //            [fullData appendData:rawAAC];
            //將數(shù)據(jù)傳遞到回調(diào)隊(duì)列中
            dispatch_async(weakSelf.callbackQueue, ^{
                [_delegate audioEncodeCallBack:rawAAC];
            });
        } else {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        }
        
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        if (error) {
            NSLog(@"error: AAC編碼失敗 %@",error);
        }
    });
}
static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    //獲取self
    SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(inUserData);
    //判斷pcmBuffsize大小
    if (!aacEncoder.pcmBufferSize) {
        *ioNumberDataPackets = 0;
        return  - 1;
    }
    //填充
    ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
    ioData->mBuffers[0].mDataByteSize = (uint32_t)aacEncoder.pcmBufferSize;
    ioData->mBuffers[0].mNumberChannels = (uint32_t)aacEncoder.config.channelCount;
    
    //填充完畢,則清空數(shù)據(jù)
    aacEncoder.pcmBufferSize = 0;
    *ioNumberDataPackets = 1;
    return noErr;
}

完整源碼

//
//  SQAudioEncoder.h
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SQAVConfig.h"
NS_ASSUME_NONNULL_BEGIN

@protocol SQAudioEncoderDelegate<NSObject>
-(void)audioEncodeCallBack:(NSData *)aacData;
@end
@interface SQAudioEncoder : NSObject
/**編碼器配置*/
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioEncoderDelegate> delegate;

/**初始化傳入編碼器配置*/
- (instancetype)initWithConfig:(SQAudioConfig*)config;

/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer;
@end

NS_ASSUME_NONNULL_END

//
//  SQAudioEncoder.m
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import "SQAudioEncoder.h"
#import "SQAVConfig.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface SQAudioEncoder()

@property (nonatomic, strong) dispatch_queue_t encoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

//對(duì)音頻轉(zhuǎn)換器對(duì)象
@property (nonatomic, unsafe_unretained) AudioConverterRef audioConverter;
//PCM緩存區(qū)
@property (nonatomic) char *pcmBuffer;
//PCM緩存區(qū)大小
@property (nonatomic) size_t pcmBufferSize;

@end
@implementation SQAudioEncoder
- (instancetype)initWithConfig:(SQAudioConfig*)config{
    self = [super init];
    if(self){
        //音頻編碼隊(duì)列
        _encoderQueue = dispatch_queue_create("aac hard encoder queue", DISPATCH_QUEUE_SERIAL);
        //音頻回調(diào)隊(duì)列
        _callbackQueue = dispatch_queue_create("aac hard encoder callback queue", DISPATCH_QUEUE_SERIAL);
        //音頻轉(zhuǎn)換器
        _audioConverter = NULL;
        _pcmBufferSize = 0;
        _pcmBuffer = NULL;
        _config = config;
        if (config == nil) {
            _config = [[SQAudioConfig alloc] init];
        }
    }
    return self;
}
//配置音頻編碼參數(shù)
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
    AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    //設(shè)置輸出參數(shù)
    AudioStreamBasicDescription outputAudioDes ={0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;   //采樣率
    outputAudioDes.mFormatID = kAudioFormatMPEG4AAC;                //輸出格式
    outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC;              // 如果設(shè)為0 代表無(wú)損編碼
    outputAudioDes.mBytesPerPacket = 0;                             //壓縮的時(shí)候設(shè)置0
    outputAudioDes.mFramesPerPacket = 1024;                         //每一個(gè)packet幀數(shù) AAC-1024恢总;
    outputAudioDes.mBytesPerFrame = 0;                              //壓縮的時(shí)候設(shè)置0
    outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; //輸出聲道數(shù)
    outputAudioDes.mBitsPerChannel = 0;                             //數(shù)據(jù)幀中每個(gè)通道的采樣位數(shù)。壓縮的時(shí)候設(shè)置0
    outputAudioDes.mReserved =  0;                                  //對(duì)其方式 0(8字節(jié)對(duì)齊)
    //填充輸出相關(guān)信息
    UInt32 outDesSize = sizeof(outputAudioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);
    //獲取編碼器的描述信息(只能傳入software)
    AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    /** 創(chuàng)建converter
        參數(shù)1:輸入音頻格式描述
        參數(shù)2:輸出音頻格式描述
        參數(shù)3:class desc的數(shù)量
        參數(shù)4:class desc
        參數(shù)5:創(chuàng)建的解碼器
    */
    OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"Error2欠摺:硬編碼AAC創(chuàng)建失敗, status= %d", (int)status);
        return;
    }
    // 設(shè)置編解碼質(zhì)量
    /*
     kAudioConverterQuality_Max                              = 0x7F,
     kAudioConverterQuality_High                             = 0x60,
     kAudioConverterQuality_Medium                           = 0x40,
     kAudioConverterQuality_Low                              = 0x20,
     kAudioConverterQuality_Min                              = 0
     */
     UInt32 temp = kAudioConverterQuality_High;
     //編解碼器的呈現(xiàn)質(zhì)量
    AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
    //設(shè)置比特率
    uint32_t audioBitrate = (uint32_t)self.config.bitrate;
    uint32_t audioBitrateSize = sizeof(audioBitrate);
    status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, audioBitrateSize, &audioBitrate);
    if (status != noErr) {
        NSLog(@"ErrorF隆:硬編碼AAC 設(shè)置比特率失敗");
    }
}
/**
 獲取編碼器類(lèi)型描述
 參數(shù)1:類(lèi)型
 */
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
    static AudioClassDescription desc;
      UInt32 encoderSpecific = type;
      
      //獲取滿足AAC編碼器的總大小
      UInt32 size;
      /**
       參數(shù)1:編碼器類(lèi)型
       參數(shù)2:類(lèi)型描述大小
       參數(shù)3:類(lèi)型描述
       參數(shù)4:大小
       */
      OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
    if(status != noErr){
        NSLog(@"Error!:硬編碼AAC get info 失敗, status= %d", (int)status);
        return nil;
    }
    //計(jì)算aac編碼器的個(gè)數(shù)
    unsigned int count = size / sizeof(AudioClassDescription);
    //創(chuàng)建一個(gè)包含count個(gè)編碼器的數(shù)組
    AudioClassDescription description[count];
    //將滿足aac編碼的編碼器的信息寫(xiě)入數(shù)組
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
   for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}
/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
    CFRetain(sampleBuffer);
    if(!_audioConverter){
         [self setupEncoderWithSampleBuffer:sampleBuffer];
    }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_encoderQueue, ^{
        //3.獲取CMBlockBuffer, 這里面保存了PCM數(shù)據(jù)
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
         OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        //5.判斷status狀態(tài)
        NSError *error = nil;
        if (status != kCMBlockBufferNoErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"Error: ACC encode get data point error: %@",error);
            return;
        }
        uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
        memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
        //3.輸出buffer
        /*
         typedef struct AudioBufferList {
         UInt32 mNumberBuffers;
         AudioBuffer mBuffers[1];
         } AudioBufferList;
         
         struct AudioBuffer
         {
         UInt32              mNumberChannels;
         UInt32              mDataByteSize;
         void* __nullable    mData;
         };
         typedef struct AudioBuffer  AudioBuffer;
         */
        //將pcmBuffer數(shù)據(jù)填充到outAudioBufferList 對(duì)象中
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
        outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)_pcmBufferSize;
        outAudioBufferList.mBuffers[0].mData = pcmBuffer;
        //輸出包大小為1
        UInt32 outputDataPacketSize = 1;
        //配置填充函數(shù)尤辱,獲取輸出數(shù)據(jù)
        //轉(zhuǎn)換由輸入回調(diào)函數(shù)提供的數(shù)據(jù)
        /*
         參數(shù)1: inAudioConverter 音頻轉(zhuǎn)換器
         參數(shù)2: inInputDataProc 回調(diào)函數(shù).提供要轉(zhuǎn)換的音頻數(shù)據(jù)的回調(diào)函數(shù)砂豌。當(dāng)轉(zhuǎn)換器準(zhǔn)備好接受新的輸入數(shù)據(jù)時(shí),會(huì)重復(fù)調(diào)用此回調(diào).
         參數(shù)3: inInputDataProcUserData
         參數(shù)4: inInputDataProcUserData,self
         參數(shù)5: ioOutputDataPacketSize,輸出緩沖區(qū)的大小
         參數(shù)6: outOutputData,需要轉(zhuǎn)換的音頻數(shù)據(jù)
         參數(shù)7: outPacketDescription,輸出包信息
         */
        status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);
        
        if (status == noErr) {
            //獲取數(shù)據(jù)
            NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            //釋放pcmBuffer
            free(pcmBuffer);
            //添加ADTS頭啥刻,想要獲取裸流時(shí)奸鸯,請(qǐng)忽略添加ADTS頭咪笑,寫(xiě)入文件時(shí)可帽,必須添加
            //            NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
            //            NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
            //            [fullData appendData:adtsHeader];
            //            [fullData appendData:rawAAC];
            //將數(shù)據(jù)傳遞到回調(diào)隊(duì)列中
            dispatch_async(weakSelf.callbackQueue, ^{
                [_delegate audioEncodeCallBack:rawAAC];
            });
        } else {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        }
        
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        if (error) {
            NSLog(@"error: AAC編碼失敗 %@",error);
        }
    });
}

static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    //獲取self
    SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(inUserData);
    //判斷pcmBuffsize大小
    if (!aacEncoder.pcmBufferSize) {
        *ioNumberDataPackets = 0;
        return  - 1;
    }
    //填充
    ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
    ioData->mBuffers[0].mDataByteSize = (uint32_t)aacEncoder.pcmBufferSize;
    ioData->mBuffers[0].mNumberChannels = (uint32_t)aacEncoder.config.channelCount;
    
    //填充完畢,則清空數(shù)據(jù)
    aacEncoder.pcmBufferSize = 0;
    *ioNumberDataPackets = 1;
    return noErr;
}

- (void)dealloc {
    if (_audioConverter) {
        AudioConverterDispose(_audioConverter);
        _audioConverter = NULL;
    }
    
}
@end

解碼AAC

解碼是相反的,我們從AAC輸入 窗怒,然后輸出PCM映跟,其他和編碼差不多蓄拣,還是通過(guò)AudioConverterNewSpecific函數(shù)創(chuàng)建音頻轉(zhuǎn)換器

 AudioStreamBasicDescription outputAudioDes = {0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采樣率
    outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數(shù)
    outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //輸出格式
    outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
    outputAudioDes.mFramesPerPacket = 1;                            //每一個(gè)packet幀數(shù) ;
    outputAudioDes.mBitsPerChannel = 16;                             //數(shù)據(jù)幀中每個(gè)通道的采樣位數(shù)努隙。
    outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一幀大星蛐簟(采樣位數(shù) / 8 *聲道數(shù))
    outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每個(gè)packet大小(幀大小 * 幀數(shù))
    outputAudioDes.mReserved =  0;                                  //對(duì)其方式 0(8字節(jié)對(duì)齊)

    //輸入?yún)?shù)
    AudioStreamBasicDescription inputAduioDes = {0};
    inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
    inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
    inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
    inputAduioDes.mFramesPerPacket = 1024;
    inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;

解碼數(shù)據(jù)的時(shí)候我們自己定義一個(gè)結(jié)構(gòu)體來(lái)封裝ACC數(shù)據(jù)

typedef struct {
    char * data;
    UInt32 size;
    UInt32 channelCount;
    AudioStreamPacketDescription packetDesc;
} SQAudioUserData;

接下來(lái)基本和編碼一樣荸镊,解碼源碼

//
//  SQAudioDecode.h
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@class SQAudioConfig;

NS_ASSUME_NONNULL_BEGIN
/**AAC解碼回調(diào)代理*/
@protocol SQAudioDecoderDelegate <NSObject>
- (void)audioDecodeCallback:(NSData *)pcmData;
@end
@interface SQAudioDecode : NSObject
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioDecoderDelegate> delegate;

//初始化 傳入解碼配置
- (instancetype)initWithConfig:(SQAudioConfig *)config;

/**解碼aac*/
- (void)decodeAudioAACData: (NSData *)aacData;
@end

NS_ASSUME_NONNULL_END

//
//  SQAudioDecode.m
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright ? 2020 SEM. All rights reserved.
//

#import "SQAudioDecode.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "SQAVConfig.h"
typedef struct {
    char * data;
    UInt32 size;
    UInt32 channelCount;
    AudioStreamPacketDescription packetDesc;
} SQAudioUserData;
@interface SQAudioDecode()
@property (strong, nonatomic) NSCondition *converterCond;
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

@property (nonatomic) AudioConverterRef audioConverter;
@property (nonatomic) char *aacBuffer;
@property (nonatomic) UInt32 aacBufferSize;
@property (nonatomic) AudioStreamPacketDescription *packetDesc;

@end
@implementation SQAudioDecode
//初始化 傳入解碼配置
- (instancetype)initWithConfig:(SQAudioConfig *)config{
    self =[super init];
    if(self){
        _decoderQueue = dispatch_queue_create("aac hard decoder queue", DISPATCH_QUEUE_SERIAL);
        _callbackQueue = dispatch_queue_create("aac hard decoder callback queue", DISPATCH_QUEUE_SERIAL);
        _audioConverter = NULL;
        _aacBufferSize = 0;
        _aacBuffer = NULL;
        _config = config;
        if (_config == nil) {
            _config = [[SQAudioConfig alloc] init];
        }
        AudioStreamPacketDescription desc = {0};
        _packetDesc = &desc;
        [self setupEncoder];
    }
    return self;
}

- (void)setupEncoder {
    AudioStreamBasicDescription outputAudioDes = {0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采樣率
    outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數(shù)
    outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //輸出格式
    outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
    outputAudioDes.mFramesPerPacket = 1;                            //每一個(gè)packet幀數(shù) 咽斧;
    outputAudioDes.mBitsPerChannel = 16;                             //數(shù)據(jù)幀中每個(gè)通道的采樣位數(shù)。
    outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一幀大泄妗(采樣位數(shù) / 8 *聲道數(shù))一般是1或者2
    outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每個(gè)packet大姓湃恰(幀大小 * 幀數(shù))
    outputAudioDes.mReserved =  0;                                  //對(duì)其方式 0(8字節(jié)對(duì)齊)

    //輸入?yún)?shù)
    AudioStreamBasicDescription inputAduioDes = {0};
    inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
    inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
    inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
    inputAduioDes.mFramesPerPacket = 1024;
    inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
    //填充輸出相關(guān)信息
    UInt32 inDesSize = sizeof(inputAduioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
    
    //獲取解碼器的描述信息(只能傳入software)
    AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    /** 創(chuàng)建converter
     參數(shù)1:輸入音頻格式描述
     參數(shù)2:輸出音頻格式描述
     參數(shù)3:class desc的數(shù)量
     參數(shù)4:class desc
     參數(shù)5:創(chuàng)建的解碼器
     */
    OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"Error!:硬解碼AAC創(chuàng)建失敗, status= %d", (int)status);
        return;
    }
}

/**
 獲取解碼器類(lèi)型描述
 參數(shù)1:類(lèi)型
 */
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
    
    static AudioClassDescription desc;
    UInt32 decoderSpecific = type;
    //獲取滿足AAC解碼器的總大小
    UInt32 size;
    /**
     參數(shù)1:編碼器類(lèi)型(解碼)
     參數(shù)2:類(lèi)型描述大小
     參數(shù)3:類(lèi)型描述
     參數(shù)4:大小
     */
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
    if (status != noErr) {
        NSLog(@"ErrorA胫蕖:硬解碼AAC get info 失敗, status= %d", (int)status);
        return nil;
    }
    //計(jì)算aac解碼器的個(gè)數(shù)
    unsigned int count = size / sizeof(AudioClassDescription);
    //創(chuàng)建一個(gè)包含count個(gè)解碼器的數(shù)組
    AudioClassDescription description[count];
    //將滿足aac解碼的解碼器的信息寫(xiě)入數(shù)組
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(decoderSpecific), &decoderSpecific, &size, &description);
    if (status != noErr) {
        NSLog(@"ErrorM鸲骸:硬解碼AAC get propery 失敗, status= %d", (int)status);
        return nil;
    }
    for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}

- (void)dealloc {
    if (_audioConverter) {
        AudioConverterDispose(_audioConverter);
        _audioConverter = NULL;
    }
    
}
/**解碼aac*/
- (void)decodeAudioAACData: (NSData *)aacData{
    if (!_audioConverter) { return; }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_decoderQueue, ^{
        //記錄aac 作為參數(shù)參入解碼回調(diào)函數(shù)
        SQAudioUserData userData = {0};
        userData.channelCount = (UInt32)weakSelf.config.channelCount;
        userData.data = (char *)[aacData bytes];
        userData.size = (UInt32)aacData.length;
        userData.packetDesc.mDataByteSize = (UInt32)aacData.length;
        userData.packetDesc.mStartOffset = 0;
        userData.packetDesc.mVariableFramesInPacket = 0;
        
        //輸出大小和packet個(gè)數(shù)
        UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
        UInt32 pcmDataPacketSize = 1024;
        //創(chuàng)建臨時(shí)容器pcm
        uint8_t *pcmBuffer = malloc(pcmBufferSize);
        memset(pcmBuffer, 0, pcmBufferSize);
        
        //輸出buffer
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
        outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
        outAudioBufferList.mBuffers[0].mData = pcmBuffer;
        
        //輸出描述
        AudioStreamPacketDescription outputPacketDesc = {0};
        
        //配置填充函數(shù),獲取輸出數(shù)據(jù)
        OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
        if (status != noErr) {
            NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
            return;
        }
        //如果獲取到數(shù)據(jù)
        if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
            NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            dispatch_async(weakSelf.callbackQueue, ^{
                [weakSelf.delegate audioDecodeCallback:rawData];
            });
        }
        free(pcmBuffer);
        
    });
}
//解碼器回調(diào)函數(shù)
static OSStatus AudioDecoderConverterComplexInputDataProc(  AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,  AudioStreamPacketDescription **outDataPacketDescription,  void *inUserData) {
    
    
    SQAudioUserData *audioDecoder = (SQAudioUserData *)(inUserData);
    if (audioDecoder->size <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
   
    //填充數(shù)據(jù)
    *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;
}
@end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盾剩,一起剝皮案震驚了整個(gè)濱河市雷激,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌告私,老刑警劉巖屎暇,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異驻粟,居然都是意外死亡恭垦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)格嗅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)番挺,“玉大人,你說(shuō)我怎么就攤上這事屯掖⌒兀” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵贴铜,是天一觀的道長(zhǎng)粪摘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)绍坝,這世上最難降的妖魔是什么徘意? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮轩褐,結(jié)果婚禮上椎咧,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好勤讽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蟋座。 她就那樣靜靜地躺著,像睡著了一般脚牍。 火紅的嫁衣襯著肌膚如雪向臀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天诸狭,我揣著相機(jī)與錄音券膀,去河邊找鬼。 笑死驯遇,一個(gè)胖子當(dāng)著我的面吹牛三娩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妹懒,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雀监,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眨唬?” 一聲冷哼從身側(cè)響起会前,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匾竿,沒(méi)想到半個(gè)月后瓦宜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岭妖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年临庇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昵慌。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假夺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斋攀,到底是詐尸還是另有隱情已卷,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布淳蔼,位于F島的核電站侧蘸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹉梨。R本人自食惡果不足惜讳癌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望存皂。 院中可真熱鬧晌坤,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猜憎。三九已至娩怎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胰柑,已是汗流浹背截亦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柬讨,地道東北人崩瓤。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像踩官,于是被迫代替她去往敵國(guó)和親却桶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355