iOS 通过 Audio Unit 播放音频数据
关于AudioUnit
Audio Unit 是iOS系统音频架构的最底层了,这一层架构是最接近硬件层的,也是开发者目前能操作最的层的API架构了。
Audio Unit
这里先解释一下DSP(digital signal processing)数字信号处理,音频信号是需要通过设备采样之后变成的数字信号,以方便数据的传输和记录。目前最常用的是PCM格式的音频数据信号,因为这种高保真的信号方便后续的处理,还有就是它保留了数据的完整性。
关于音频流参数
1、采样率
每秒钟采得声音样本的次数,声音是一种能量波,有振幅和频率,人的耳朵可以听到的频率在20-Hz~20kHz之间的声波,所以采样率越高,获取的到的频率信息就更为丰富,由于人耳的分辨率很有限,太高的频率并不能分辨出来。22050 的采样频率是常用的,44100已是CD音质,超过48000或96000的采样对人耳已经没有意义。
常用的采样率:
8000 Hz - 电话所用采样率
22050 Hz - 无线电广播所用采样率
32000 Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
44100 Hz - 音频 CD, 也常用于 MPEG-1 音频(VCD,SVCD,MP3)所用采样率
47250 Hz - 商用 PCM 录音机所用采样率
48000 Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
50000 Hz - 商用数字录音机所用采样率
96000 Hz或者 192000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率
2、采样位数
采样位数,他是衡量声音播到变化的一个参数,它的数值越大,分辨率就越高,录制和回放的声音就越接近真实。常见的声卡主要有8位和16位两种,如今市面上所有的主流产品都是16位及以上的声卡。
每个采样数据记录的是振幅, 采样精度取决于采样位数的大小:
1 字节(也就是8bit) 只能记录 256 个数, 也就是只能将振幅划分成 256 个等级;
2 字节(也就是16bit) 可以细到 65536 个数, 这已是 CD 标准了;
4 字节(也就是32bit) 能把振幅细分到 4294967296 个等级, 实在是没必要了.
3、通道数
即声音的通道的数目,目前使用较多的是单声道和立体声,相当于从多位置采集声音。
4、比特率
每秒的传输速率(位速, 也叫比特率)。如705.6kbps 或 705600bps, 其中的 b 是 bit, ps 是每秒的意思,表示每秒705600bit的容量。不同的音频格式编码,对PCM都有一个压缩比,所以比特率一般等于原始比特率/音频压缩比。
5、帧长
帧长记录了一个声音单元字节为单位,其长度为:样本长度 * 通道数 = 帧长
6、帧数
每秒数据分为都少帧: 帧长 * 帧数 * 8 = 比特率
Audio Unit 工作时的脑图和流程图
AudioUnit 涉及到的知识图.png整体的一个流程图如下:
Audio Unit Flow.png
使用流程
1、iOS 涉及音频使用和会话都需要使用到 AudioSessionInitialize去初始化音频会话对象。
// set audio session
NSError *error = nil;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
[audioSession setActive:YES error:&error];
2、配置音频组件Audio Unit 并描述输出的单元
//set audio component information
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
3、查找、创建对应的音频输出单元组件
AudioUnit audioUnit;
//set audio component information
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
//Finds the next component that matches a specified AudioComponentDescription structure after a specified audio component.
AudioComponent inputComponent = AudioComponentFindNext(NULL, &audioDesc);
//create a new instance of an audio component
AudioComponentInstanceNew(inputComponent, &audioUnit);
//audio property
UInt32 flag = 1;
if (flag) {
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
OUTPUT_BUS,
&flag,
sizeof(flag));
}
4、配置对应需要播放的音频数据格式内容
// format
AudioStreamBasicDescription outputFormat;
memset(&outputFormat, 0, sizeof(outputFormat));
outputFormat.mSampleRate = 44100; // 采样率
outputFormat.mFormatID = kAudioFormatLinearPCM; // PCM格式
outputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; // 整形
outputFormat.mFramesPerPacket = 1; // 每帧只有1个packet
outputFormat.mChannelsPerFrame = 1; // 声道数
outputFormat.mBytesPerFrame = 2; // 每帧只有2个byte 声道*位深*Packet数
outputFormat.mBytesPerPacket = 2; // 每个Packet只有2个byte
outputFormat.mBitsPerChannel = 16; // 位深
[self printAudioStreamBasicDescription:outputFormat];
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_BUS,
&outputFormat,
sizeof(outputFormat));
if (status) {
NSLog(@"AudioUnitSetProperty eror with status:%d", status);
}
5、指定播放源的相关信息
// callback
AURenderCallbackStruct playCallback;
playCallback.inputProc = PlayCallback;
playCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
OUTPUT_BUS,
&playCallback,
sizeof(playCallback));
OSStatus result = AudioUnitInitialize(audioUnit);
6、给目标播放组件输入播放内容
static OSStatus PlayCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
yourPlayerClass *player = (__bridge yourPlayerClass *)inRefCon;//这里获取之前初始化时配置给播放组件的类对象
// 这里是静音数据,需要播放更对内容可以往ioData->mBuffers输入数据
for (int iBuffer = 0; iBuffer < ioData->mNumberBuffers; ++iBuffer) {
memset(ioData->mBuffers[iBuffer].mData, 0, ioData->mBuffers[iBuffer].mDataByteSize);
}
return noErr;
}
}
7、这里想要补充说明一下
上面的配置和初始中,多次用到了AudioUnitSetProperty,这是一个设置音频单元属性的函数,它的几个参数如下:
参数补充.png
上面用到了两个宏定义 作用于设置AudioUnit的I/O口设置
#define INPUT_BUS 1
#define OUTPUT_BUS 0