iOS 实时音频采集与播放

1、前言

在iOS中有很多方法可以进行音视频采集。如 AVCaptureDevice, AudioQueue以及Audio Unit。其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; 而缺点是学习成本高,难度大。对于一般的iOS应用程序,AVCaptureDevice和AudioQueue完全够用了。但对于音视频直播,最好还是使用 Audio Unit 进行处理,这样可以达到最佳的效果,著名的 WebRTC 就使用的 Audio Unit 做的音频采集与播放。今天我们就重点介绍一下Audio Unit的基本知识和使用。

下图是 Audio Unit在 iOS架构中所处的位置:

2、基本概念

在介绍 Audio Unit 如何使用之前,先要介绍一下Audio Unit的基本概念,这样更有利于我们理解对它的使用。

3、使用流程概要

  1. 描述音频元件
    1. kAudioUnitType_Output
    2. kAudioUnitSubType_RemoteIO
    3. kAudioUnitManufacturerApple
  2. 使用 AudioComponentFindNext(NULL,
  3. &descriptionOfAudioComponent) 获得 AudioComponent。 AudioComponent有点像生产 Audio Unit 的工厂。
  4. 使用 AudioComponentInstanceNew(ourComponent,
    1. &audioUnit) 获得 Audio Unit 实例。
  5. 使用 AudioUnitSetProperty函数为录制和回放开启IO。
  6. 使用 AudioStreamBasicDescription 结构体描述音频格式,并使用AudioUnitSetProperty进行设置。
  7. 使用 AudioUnitSetProperty 设置音频录制与放播的回调函数。
  8. 分配缓冲区。
  9. 初始化 Audio Unit。
  10. 启动 Audio Unit。

C++音视频学习资料免费获取方法:关注音视频开发T哥,点击「链接」即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!

4、初始化

初始化看起来像下面这样。我们有一个 AudioComponentInstance 类型的成员变量,它用于存储 Audio Unit。

下面的音频格式用16位表式一个采样。

#define kOutputBus 0#define kInputBus 1// ...OSStatus status;
AudioComponentInstance audioUnit;// 描述音频元件AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;// 获得一个元件AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);// 获得 Audio Unitstatus = AudioComponentInstanceNew(inputComponent, &audioUnit);
checkStatus(status);// 为录制打开 IOUInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit, 
                              kAudioOutputUnitProperty_EnableIO, 
                              kAudioUnitScope_Input, 
                              kInputBus,
                              &flag, 
                              sizeof(flag));
checkStatus(status);// 为播放打开 IOstatus = AudioUnitSetProperty(audioUnit, 
                              kAudioOutputUnitProperty_EnableIO, 
                              kAudioUnitScope_Output, 
                              kOutputBus,
                              &flag, 
                              sizeof(flag));
checkStatus(status);// 描述格式audioFormat.mSampleRate         = 44100.00;
audioFormat.mFormatID           = kAudioFormatLinearPCM;
audioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger |                                   kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket    = 1;
audioFormat.mChannelsPerFrame   = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame      = 2;// 设置格式status = AudioUnitSetProperty(audioUnit, 
                              kAudioUnitProperty_StreamFormat, 
                              kAudioUnitScope_Output, 
                              kInputBus, 
                              &audioFormat, 
                              sizeof(audioFormat));
checkStatus(status);
status = AudioUnitSetProperty(audioUnit, 
                              kAudioUnitProperty_StreamFormat, 
                              kAudioUnitScope_Input, 
                              kOutputBus, 
                              &audioFormat, 
                              sizeof(audioFormat));
checkStatus(status);// 设置数据采集回调函数AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = recordingCallback;
callbackStruct.inputProcRefCon = self;
status = AudioUnitSetProperty(audioUnit, 
                              kAudioOutputUnitProperty_SetInputCallback, 
                              kAudioUnitScope_Global, 
                              kInputBus, 
                              &callbackStruct, 
                              sizeof(callbackStruct));
checkStatus(status);// 设置声音输出回调函数。当speaker需要数据时就会调用回调函数去获取数据。// 它是 "拉" 数据的概念。callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = self;
status = AudioUnitSetProperty(audioUnit, 
                              kAudioUnitProperty_SetRenderCallback, 
                              kAudioUnitScope_Global, 
                              kOutputBus,
                              &callbackStruct, 
                              sizeof(callbackStruct));
checkStatus(status);// 关闭为录制分配的缓冲区(我们想使用我们自己分配的)flag = 0;
status = AudioUnitSetProperty(audioUnit, 
                            kAudioUnitProperty_ShouldAllocateBuffer,
                            kAudioUnitScope_Output, 
                            kInputBus,
                            &flag, 
                            sizeof(flag));// 初始化status = AudioUnitInitialize(audioUnit);
checkStatus(status);

开启 Audio Unit

OSStatus status = AudioOutputUnitStart(audioUnit);
checkStatus(status);

关闭 Audio Unit

OSStatus status = AudioOutputUnitStop(audioUnit);
checkStatus(status);

结束 Audio Unit

AudioComponentInstanceDispose(audioUnit);

5、录制回调

static OSStatus recordingCallback(void *inRefCon, 
                                  AudioUnitRenderActionFlags *ioActionFlags, 
                                  const AudioTimeStamp *inTimeStamp, 
                                  UInt32 inBusNumber, 
                                  UInt32 inNumberFrames, 
                                  AudioBufferList *ioData) {    // TODO:
    // 使用 inNumberFrames 计算有多少数据是有效的
    // 在 AudioBufferList 里存放着更多的有效空间

    AudioBufferList *bufferList; //bufferList里存放着一堆 buffers,                                  //buffers的长度是动态的。  

    // 获得录制的采样数据

    OSStatus status;

    status = AudioUnitRender([audioInterface audioUnit], 
                             ioActionFlags, 
                             inTimeStamp, 
                             inBusNumber, 
                             inNumberFrames, 
                             bufferList);
    checkStatus(status);    // 现在,我们想要的采样数据已经在                            // bufferList中的buffers中了。
    DoStuffWithTheRecordedAudio(bufferList);    return noErr;
}

6、播放回调

static OSStatus playbackCallback(void *inRefCon, 
                            AudioUnitRenderActionFlags *ioActionFlags, 
                            const AudioTimeStamp *inTimeStamp, 
                            UInt32 inBusNumber, 
                            UInt32 inNumberFrames, 
                            AudioBufferList *ioData) {    
    // Notes: ioData 包括了一堆 buffers 
    // 尽可能多的向ioData中填充数据,记得设置每个buffer的大小要与buffer匹配好。
    return noErr;
}

7、结束

Audio Unit可以做很多非常棒的的工作。如混音,音频特效,录制等等。它处于 iOS 开发架构的底层,特别合适于音视频直播这种场景中使用。

我们今天介绍的只是 Audio Unit众多功能中的一小点知识,但这一点点知识对于我来说已经够用了。对于那些想了解更多Audio Unit的人,只好自行去google了。

“知识无穷尽,只取我所需”。这就是我的思想,哈!


原文链接:iOS 实时音频采集与播放

展开阅读全文

页面更新:2024-03-05

标签:音频   缓冲区   下图   初始化   架构   函数   元件   实时   分配   知识   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top