创建 AudioUnit 组件实体
AudioUnit 组件实体的创建函数是 AudioComponentInstanceNew
。
OSStatus AudioComponentInstanceNew(AudioComponent inComponent,
AudioComponentInstance* outInstance);
传入 AudioComponent 并输出 AudioComponentInstance,返回错误码。
AudioComponent 需要通过函数 AudioComponentFindNext 来获得。
AudioComponentFindNext (AudioComponent inComponent,
const AudioComponentDescription* inDesc);
AudioComponentDescription 结构体定义如下:
typedef struct AudioComponentDescription {
OSType componentType;
OSType componentSubType;
OSType componentManufacturer;
UInt32 componentFlags;
UInt32 componentFlagsMask;
} AudioComponentDescription;
- componentType: 音频组件类型
- componentSubType: 音频组件子类型
- componentManufacturer: 制造商
- componentFlags: 标志,一般设置为 0
- componentFlagsMask: 标志位掩码,一般设置为 0
综合以上部分的初始化代码如下:
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent component = AudioComponentFindNext(NULL, &desc);
AudioUnit audioUnit;
OSStatus status = AudioComponentInstanceNew(component, &audioUnit);
配置音频参数
配置音频参数需要用到的函数是 AudioUnitSetProperty
。
AudioUnitSetProperty 是一个设置音频单元属性的函数,它属于多用途函数,可以根据不同的属性要求配置不同的数据。
配置音频参数只是它其中一个用途,当需要配置音频参数使,需要给 AudioUnitSetProperty 传入参数 kAudioUnitProperty_StreamFormat 。
OSStatus AudioUnitSetProperty(AudioUnit inUnit,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const void* inData,
UInt32 inDataSize);
- inUnit: 音频单元,由之前的代码创建
- inID: 属性 ID,根据 ID 决定配置的属性
- inScope: 设置范围,例如是设置输入还是设置输出
- inElement: 输入范围的索引,通常为 0
- inData: 传入的参数数据,一般是个结构体指针
- inDataSize: 传入的参数数据大小
配置音频参数所需要传入的 inData 是 AudioStreamBasicDescription。
struct AudioStreamBasicDescription
{
Float64 mSampleRate;
AudioFormatID mFormatID;
AudioFormatFlags mFormatFlags;
UInt32 mBytesPerPacket;
UInt32 mFramesPerPacket;
UInt32 mBytesPerFrame;
UInt32 mChannelsPerFrame;
UInt32 mBitsPerChannel;
UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
- mSampleRate: 即采样率,每秒采集的样本数据单元个数
- mFormatID: 音频数据的格式标识符
- mFormatFlags: 音频格式标识符的附加信息
- mBytesPerPacket: 一个数据包的字节数
- mFramesPerPacket: 一个数据包的帧数
- mBytesPerFrame: 一帧的字节数
- mChannelsPerFrame: 音频声道数
- mBitsPerChannel: 每个频道的比特数
- mReserved: 保留字段
下面这段代码示例处理的是通过 AudioUnitSetProperty 配置音频的参数:
AudioStreamBasicDescription streamFormat = {};
// 采样率,选择常用的 44100
streamFormat.mSampleRate = (double)44100;
// 使用 PCM 作为音源格式
streamFormat.mFormatID = kAudioFormatLinearPCM;
// 设置格式标识符,由以下两个组合:
// 1. 带符号整形标识
// 2. 数据按照连续字节的顺序存储
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
// 每一块数据包含一帧
streamFormat.mFramesPerPacket = 1;
// 每一帧有一个数据频道
streamFormat.mChannelsPerFrame = 1;
// 每个通道的比特数,选择的是16位精度
streamFormat.mBitsPerChannel = sizeof(int16_t) * 8;
// 每一帧的字节数,可以通过每一帧的通道数乘以每个通道的比特数除以 8 算出
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * streamFormat.mBitsPerChannel / 8;
// 数据包的字节数,通过数据包的帧数乘以每一帧的字节数算出
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;
// 设置音频格式
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
sizeof(streamFormat));
设置回调函数
回调函数的设置也是通过 AudioUnitSetProperty 函数。
当需要设置回调函数时,需要给 AudioUnitSetProperty 传入参数 kAudioUnitProperty_SetRenderCallback 。
通过 kAudioUnitProperty_SetRenderCallback 设置回调时,要求传入回调函数。
回调函数的传入需要通过结构体 AURenderCallbackStruct 完成。
struct AURenderCallbackStruct {
AURenderCallback inputProc;
void* inputProcRefCon;
};
其中 inputProc 是一个函数指针类型 AURenderCallback 。
OSStatus
(*AURenderCallback)(void* inRefCon,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList* ioData);
为此,用户需要定义一个符合 AURenderCallback 声明的函数,将它传入结构体 AURenderCallbackStruct 的 inputProc 成员内。
而 inputProcRefCon 则是传入时所附带的用户自定义数据。
完整的回调函数设置调用过程如下:
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = audioCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&callbackStruct,
sizeof(callbackStruct));
这里 inputProcRefCon 携带的数据直接携带了调用方的类对象 self,因为它是一个 Objective-C 类,所以需要做桥接,因此需要用到桥接关键字 __bridge
。
这里注册了一个回调函数 audioCallback,但是还没对它进行定义。所以需要在这之前定义这个函数。
static OSStatus audioCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
}
- inRefCon: 传入的用户自定义参数
- ioActionFlags: 配置音频单元渲染的标志位
- inTimeStamp: 包含了当前回调的时间戳信息
- inBusNumber: 输入总线编号
- inNumberFrames: 帧数
- ioData: 声音缓冲数据
如下示例回调将会产生一个频率为 440 的正弦波:
static OSStatus audioCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData)
{
static double p = 0;
for (int k = 0; k < (int)ioData->mNumberBuffers; ++k) {
AudioBuffer buffer = ioData->mBuffers[k];
int16_t* data = (int16_t*)buffer.mData;
for (int i = 0; i < (int)inNumberFrames; ++i) {
data[i] = INT16_MAX * sin(p);
p += 2 * M_PI * 440 / SAMPLE_RATE;
if (p > 2 * M_PI) {
p -= 2 * M_PI;
}
}
}
return noErr;
}
样例代码地址:
https://github.com/jumpright233/sample-code
公众号:风海铜锣
微信:xq2723866