iOS AudioUnit笔记
  Yj0NQuFjxzMg 2023年11月02日 58 0

创建 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


公众号:风海铜锣

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  gBkHYLY8jvYd   2023年12月09日   29   0   0 cii++数据
Yj0NQuFjxzMg