天天看点

iOS 通过 Audio Unit 播放音频数据

作者:音视频开发T哥

1、关于AudioUnit

Audio Unit 是iOS系统音频架构的最底层了,这一层架构是最接近硬件层的,也是开发者目前能操作最的层的API架构了。

iOS 通过 Audio Unit 播放音频数据

Audio Unit

这里先解释一下DSP(digital signal processing)数字信号处理,音频信号是需要通过设备采样之后变成的数字信号,以方便数据的传输和记录。目前最常用的是PCM格式的音频数据信号,因为这种高保真的信号方便后续的处理,还有就是它保留了数据的完整性。

2、关于音频流参数

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 = 比特率

3、Audio Unit 工作时的脑图和流程图

iOS 通过 Audio Unit 播放音频数据

AudioUnit 涉及到的知识图.png

整体的一个流程图如下:

iOS 通过 Audio Unit 播放音频数据

Audio Unit Flow.png

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

4、使用流程

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,这是一个设置音频单元属性的函数,它的几个参数如下:

iOS 通过 Audio Unit 播放音频数据

参数补充.png

上面用到了两个宏定义 作用于设置AudioUnit的I/O口设置

#define INPUT_BUS 1
#define OUTPUT_BUS 0           

原文链接:iOS 通过 Audio Unit 播放音频数据 - 简书

继续阅读