iOS开发-AudioUnit实时录音(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 {
  // 优先16000,如果设备不支持使用其它采样率
    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];
  // 处理数据
    [self processSampleData:data];
};

2.双声道录音

说明:
公司新业务要接入蓝牙耳机,支持蓝牙耳机,左右耳机分别进行语音识别等功能。该业务牵扯到实时双通道录音,分别提取左右buffer,类似的业务需求市场上也是有的,比如AirPods,百度的一款蓝牙耳机(小度APP“流浪地球模式”,具体可以买一个个试用下)。
废话不多说了,直接看代码就行。

在上述基础上做修改

// 1.设置声道数量
asbd.mChannelsPerFrame = 2;//每帧的声道数量
// 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

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

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

推荐阅读更多精彩内容