iOS開發(fā)-AudioUnit實(shí)時錄音(OC)

1.單聲道錄音

//  FSUnitRecorder.h

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void (^kAudioUnitRecorderOnputBlock)(AudioBufferList *bufferList);

@interface FSUnitRecorder : NSObject

@property (assign, nonatomic) double sampleRate;
@property (assign, nonatomic, readonly) BOOL isRecording;
@property (copy, nonatomic) kAudioUnitRecorderOnputBlock bufferListBlock;

- (void)start;
- (void)stop;

@end

NS_ASSUME_NONNULL_END
//  FSUnitRecorder.m
#import "FSUnitRecorder.h"

@interface FSUnitRecorder ()
{
    AudioUnit audioUnit;
    BOOL audioComponentInitialized;
}

@property (nonatomic,assign) AudioStreamBasicDescription inputStreamDesc;

@end

@implementation FSUnitRecorder

- (instancetype)init {
    self = [super init];
    if (self) {
        [self defaultSetting];
    }
    return self;
}

- (void)defaultSetting {
  // 優(yōu)先16000摄职,如果設(shè)備不支持使用其它采樣率
    NSArray *sampleRates = @[@16000, @11025, @22050, @44100];
    for (NSNumber *sampleRate in sampleRates) {
        OSStatus status = [self prepareRecord:sampleRate.doubleValue];
        if (status == noErr) {
            self.sampleRate = [sampleRate doubleValue];
            break;
        }
    }
}

- (OSStatus)prepareRecord:(double)sampleRate {
    OSStatus status = noErr;

    NSError *error;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth  error:&error];
    [[AVAudioSession sharedInstance] setActive:YES error:&error];
  // This doesn't seem to really indicate a problem (iPhone 6s Plus)
#ifdef IGNORE
    NSInteger inputChannels = session.inputNumberOfChannels;
    if (!inputChannels) {
        NSLog(@"ERROR: NO AUDIO INPUT DEVICE");
        return -1;
  }
#endif

      if (!audioComponentInitialized) {
        audioComponentInitialized = YES;
        // Describe the RemoteIO unit
        AudioComponentDescription audioComponentDescription;
        audioComponentDescription.componentType = kAudioUnitType_Output;
        audioComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
        audioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
        audioComponentDescription.componentFlags = 0;
        audioComponentDescription.componentFlagsMask = 0;

        // Get the RemoteIO unit
        AudioComponent remoteIOComponent = AudioComponentFindNext(NULL,&audioComponentDescription);
        status = AudioComponentInstanceNew(remoteIOComponent,&(self->audioUnit));
        if (CheckError(status, "Couldn't get RemoteIO unit instance")) {
          return status;
        }
      }

      UInt32 oneFlag = 1;
      AudioUnitElement bus0 = 0;
      AudioUnitElement bus1 = 1;

      if ((NO)) {
        // Configure the RemoteIO unit for playback
        status = AudioUnitSetProperty (self->audioUnit,
                                       kAudioOutputUnitProperty_EnableIO,
                                       kAudioUnitScope_Output,
                                       bus0,
                                       &oneFlag,
                                       sizeof(oneFlag));
        if (CheckError(status, "Couldn't enable RemoteIO output")) {
          return status;
        }
      }

      // Configure the RemoteIO unit for input
      status = AudioUnitSetProperty(self->audioUnit,
                                    kAudioOutputUnitProperty_EnableIO,
                                    kAudioUnitScope_Input,
                                    bus1,
                                    &oneFlag,
                                    sizeof(oneFlag));
      if (CheckError(status, "Couldn't enable RemoteIO input")) {
        return status;
      }

      AudioStreamBasicDescription asbd;
      memset(&asbd, 0, sizeof(asbd));
      asbd.mSampleRate = sampleRate; // 采樣率
      asbd.mFormatID = kAudioFormatLinearPCM;
      asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
      asbd.mBytesPerPacket = 2;
      asbd.mFramesPerPacket = 1;
      asbd.mBytesPerFrame = 2;
      asbd.mChannelsPerFrame = 2;
      asbd.mBitsPerChannel = 16;

      // Set format for output (bus 0) on the RemoteIO's input scope
      status = AudioUnitSetProperty(self->audioUnit,
                                    kAudioUnitProperty_StreamFormat,
                                    kAudioUnitScope_Input,
                                    bus0,
                                    &asbd,
                                    sizeof(asbd));
      if (CheckError(status, "Couldn't set the ASBD for RemoteIO on input scope/bus 0")) {
        return status;
      }

      // Set format for mic input (bus 1) on RemoteIO's output scope
      status = AudioUnitSetProperty(self->audioUnit,
                                    kAudioUnitProperty_StreamFormat,
                                    kAudioUnitScope_Output,
                                    bus1,
                                    &asbd,
                                    sizeof(asbd));
      if (CheckError(status, "Couldn't set the ASBD for RemoteIO on output scope/bus 1")) {
        return status;
      }

      // Set the recording callback
      AURenderCallbackStruct callbackStruct;
      callbackStruct.inputProc = inputCallBackFun;
      callbackStruct.inputProcRefCon = (__bridge void *) self;
      status = AudioUnitSetProperty(self->audioUnit,
                                    kAudioOutputUnitProperty_SetInputCallback,
                                    kAudioUnitScope_Global,
                                    bus1,
                                    &callbackStruct,
                                    sizeof (callbackStruct));
      if (CheckError(status, "Couldn't set RemoteIO's render callback on bus 0")) {
        return status;
      }

      if ((NO)) {
        // Set the playback callback
        AURenderCallbackStruct callbackStruct;
        callbackStruct.inputProc = playbackCallback;
        callbackStruct.inputProcRefCon = (__bridge void *) self;
        status = AudioUnitSetProperty(self->audioUnit,
                                      kAudioUnitProperty_SetRenderCallback,
                                      kAudioUnitScope_Global,
                                      bus0,
                                      &callbackStruct,
                                      sizeof (callbackStruct));
        if (CheckError(status, "Couldn't set RemoteIO's render callback on bus 0")) {
          return status;
        }
      }

      // Initialize the RemoteIO unit
      status = AudioUnitInitialize(self->audioUnit);
      if (CheckError(status, "Couldn't initialize the RemoteIO unit")) {
        return status;
      }

      return status;
}

- (void)start {
    [self deleteAudioFile];
    CheckError(AudioOutputUnitStart(audioUnit), "AudioOutputUnitStop failed");
    _isRecording = YES;
}

- (void)stop {
    CheckError(AudioOutputUnitStop(audioUnit),
    "AudioOutputUnitStop failed");
    _isRecording = NO;
}

- (void)deleteAudioFile {
    NSString *pcmPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"record.mp3"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:pcmPath]) {
        [[NSFileManager defaultManager] removeItemAtPath:pcmPath error:nil];
    }
}

- (void)dealloc {
    CheckError(AudioComponentInstanceDispose(audioUnit),
               "AudioComponentInstanceDispose failed");
    NSLog(@"UnitRecorder銷毀");
}

static OSStatus CheckError(OSStatus error, const char *operation) {
  if (error == noErr) {
    return error;
  }
  char errorString[20] = "";
  // See if it appears to be a 4-char-code
  *(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(error);
  if (isprint(errorString[1]) && isprint(errorString[2]) &&
      isprint(errorString[3]) && isprint(errorString[4])) {
    errorString[0] = errorString[5] = '\'';
    errorString[6] = '\0';
  } else {
    // No, format it as an integer
    sprintf(errorString, "%d", (int)error);
  }
  fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
  return error;
}

static OSStatus playbackCallback(void *inRefCon,
                                 AudioUnitRenderActionFlags *ioActionFlags,
                                 const AudioTimeStamp *inTimeStamp,
                                 UInt32 inBusNumber,
                                 UInt32 inNumberFrames,
                                 AudioBufferList *ioData) {
  OSStatus status = noErr;

  // Notes: ioData contains buffers (may be more than one!)
  // Fill them up as much as you can. Remember to set the size value in each buffer to match how
  // much data is in the buffer.
  FSUnitRecorder *recorder = (__bridge FSUnitRecorder *) inRefCon;

  UInt32 bus1 = 1;
  status = AudioUnitRender(recorder->audioUnit,
                           ioActionFlags,
                           inTimeStamp,
                           bus1,
                           inNumberFrames,
                           ioData);
  CheckError(status, "Couldn't render from RemoteIO unit");
  return status;
}

static OSStatus inputCallBackFun(void *inRefCon,
                    AudioUnitRenderActionFlags *ioActionFlags,
                    const AudioTimeStamp *inTimeStamp,
                    UInt32 inBusNumber,
                    UInt32 inNumberFrames,
                    AudioBufferList * __nullable ioData)
{

    FSUnitRecorder *recorder = (__bridge FSUnitRecorder *)(inRefCon);
    
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = NULL;
    bufferList.mBuffers[0].mDataByteSize = 0;
    
    AudioUnitRender(recorder->audioUnit,
                    ioActionFlags,
                    inTimeStamp,
                    1,
                    inNumberFrames,
                    &bufferList);
    if (recorder.bufferListBlock) {
        recorder.bufferListBlock(&bufferList);
    }
    
    return noErr;
}

@end

使用

- (FSUnitRecorder *)recorder {
    if (!_recorder) {
        _recorder = [[FSUnitRecorder alloc] init];
    }
    return _recorder;
}

@weakify(self);
self.recorder.bufferListBlock = ^(AudioBufferList * _Nonnull bufferList) {
    @strongify(self);
    AudioBuffer buffer = bufferList->mBuffers[0];
    NSData *data = [NSData dataWithBytes:buffer.mData length:buffer.mDataByteSize];
  // 處理數(shù)據(jù)
    [self processSampleData:data];
};

2.雙聲道錄音

說明:
公司新業(yè)務(wù)要接入藍(lán)牙耳機(jī)诬留,支持藍(lán)牙耳機(jī)吃既,左右耳機(jī)分別進(jìn)行語音識別等功能锄开。該業(yè)務(wù)牽扯到實(shí)時雙通道錄音艺玲,分別提取左右buffer姻乓,類似的業(yè)務(wù)需求市場上也是有的想幻,比如AirPods恋谭,百度的一款藍(lán)牙耳機(jī)(小度APP“流浪地球模式”厅瞎,具體可以買一個個試用下)饰潜。
廢話不多說了,直接看代碼就行和簸。

在上述基礎(chǔ)上做修改

// 1.設(shè)置聲道數(shù)量
asbd.mChannelsPerFrame = 2;//每幀的聲道數(shù)量
// 2.分離左右聲道<左左右右左左...>
static OSStatus inputCallBackFun(void *inRefCon,
                    AudioUnitRenderActionFlags *ioActionFlags,
                    const AudioTimeStamp *inTimeStamp,
                    UInt32 inBusNumber,
                    UInt32 inNumberFrames,
                    AudioBufferList * __nullable ioData)
{
    ZDUnitRecorder *recorder = (__bridge ZDUnitRecorder *)(inRefCon);
    
    AudioBuffer buffer;
    buffer.mData = NULL;
    buffer.mDataByteSize = 0;
    buffer.mNumberChannels = 1;
            
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0] = buffer;
    
    AudioUnitRender(recorder->audioUnit,
                    ioActionFlags,
                    inTimeStamp,
                    inBusNumber,
                    inNumberFrames,
                    &bufferList);
    NSData *data = [NSData dataWithBytes:bufferList.mBuffers[0].mData length:bufferList.mBuffers[0].mDataByteSize];
    
    NSMutableData *leftData = [NSMutableData dataWithCapacity:0];
    NSMutableData *rightData = [NSMutableData dataWithCapacity:0];
    // 分離左右聲道
    for (int i = 0; i < data.length; i+=4) {
        [leftData appendData:[data subdataWithRange:NSMakeRange(i, 2)]];
        [rightData appendData:[data subdataWithRange:NSMakeRange(i+2, 2)]];
    }
    if (recorder.bufferListBlock) {
        recorder.bufferListBlock(leftData, rightData);
    }
    
    return noErr;
}

//************************************************************************//
Best Regard!
生命不止彭雾,奮斗不息

個人博客: ?? ForgetSou

//************************************************************************//

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锁保,隨后出現(xiàn)的幾起案子薯酝,更是在濱河造成了極大的恐慌,老刑警劉巖爽柒,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吴菠,死亡現(xiàn)場離奇詭異,居然都是意外死亡浩村,警方通過查閱死者的電腦和手機(jī)做葵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來心墅,“玉大人酿矢,你說我怎么就攤上這事≡踉铮” “怎么了瘫筐?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铐姚。 經(jīng)常有香客問我策肝,道長,這世上最難降的妖魔是什么隐绵? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任之众,我火速辦了婚禮,結(jié)果婚禮上依许,老公的妹妹穿的比我還像新娘棺禾。我一直安慰自己,他們只是感情好悍手,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布帘睦。 她就那樣靜靜地躺著袍患,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竣付。 梳的紋絲不亂的頭發(fā)上诡延,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音古胆,去河邊找鬼肆良。 笑死,一個胖子當(dāng)著我的面吹牛逸绎,可吹牛的內(nèi)容都是我干的惹恃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棺牧,長吁一口氣:“原來是場噩夢啊……” “哼巫糙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颊乘,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤参淹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乏悄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浙值,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年檩小,在試婚紗的時候發(fā)現(xiàn)自己被綠了开呐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡规求,死狀恐怖筐付,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颓哮,我是刑警寧澤家妆,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布鸵荠,位于F島的核電站冕茅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛹找。R本人自食惡果不足惜姨伤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庸疾。 院中可真熱鬧乍楚,春花似錦、人聲如沸届慈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至臊泌,卻和暖如春鲤桥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渠概。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工茶凳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人播揪。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓贮喧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猪狈。 傳聞我的和親對象是個殘疾皇子箱沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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