Core Audio音频基础概述

10,884 阅读17分钟

Core Audio

Core Audio提供了数字音频服务为iOS与OS X, 它提供了一系列框架去处理音频.

1.Core Audio

Core Audio中包含我们最常用的Audio Toolbox与Audio Unit框架.

  • 使用Audio Queue做录制,播放,暂停,循环与同步音频
  • 使用Audio File, Converter, Codec Services去从磁盘读取与写入以及执行音频转换等功能.
  • 使用Audio Unit与Audio Processing Graph在应用程序中管理音频单元.在OS X中可以自定义audio units.
  • 使用Music Sequencing Services播放基于MIDI控制的音频数据
  • 使用Core Audio Clock Services用于音频和MIDI同步以及时间格式管理
  • 使用System Sound Services播放系统声音与界面的音效.

Core Audio在iOS中针对移动平台的计算资源作出了优化,同时,音频服务必须严格由系统进行管理,特别是HAL与I/O Kit,然而Apple也提供了只在iOS平台中才有的服务,如Audio Session Service将帮助我们管理音频上下文.

2.iOS_Core Audio

3.CoreAudioLevel

1. Digital Audio与Linear PCM

PCM是最常用的无损压缩数字音频格式数据,根据采样率以规则间隔测量模拟(真实世界)数字音频信号并将每个采集到的样本转换为数值来创建PCM数据.如标准光盘(CD)音频使用44.1 kHz的采样率,16位整数描述每个样本 - 构成分辨率或位深度。

  • sample:一个采样点是对单声道采集到声音的数值
  • frame:一帧数据是一组时间一致的samples,如双声道声音文件中一帧有两个samples,一个左声道,一个右声道.
  • packet:一个或多个连续帧的集合.在线性PCM中,一个packet总是单帧.在其他压缩格式中,一个packet定义给定音频数据格式的最小有意义的帧组。

iOS中使用integer与fixed-point音频数据,目的是在处理音频数据时增加计算速度,减小电池能耗.iOS也提供了来自Audio Converter Services的Converter audio unit服务.

iOS与OS X中,Core Audio提供了最常用的文件格式用于存储域播放音频数据.

2.Audio Unit

Apple针对移动平台对iOS的Audio Unit作出了效率与性能优化,在开发中我们必须将audio unit静态编译进APP,所以无法使用别的APP中的Audio Unit.

3.HAL(Hardware Abstraction Layer)

大多情况下,我们无法直接与HAL进行交互,Apple提供了一个特别的audio unit,即OS X中的AUHAL, iOS中的AURemoteIO, 我们可以通过它们让音频与硬件交互.

4.Properties, Scopes, and Elements

Core Audio接口中使用property管理对象的行为与状态.

  • 属性通常用易记忆的关键字格式,如kAudioFilePropertyFileFormat or kAudioQueueDeviceProperty_NumberChannels.
  • 属性值适用于特定的数据类型,如void*, Float64, AudioChannelLayout...
  • Core Audio对象有一个内部结构,其中每一部分都有属于自己的属性,如一个audio unit对象都有一个input scope, output scope, global scope. 每个scope由一个或多个elements(类似于音频总线)组成.

5.回调函数

Core Audio中常用回调函数以实现音频数据通信,回调函数常有一下功能

  • 提供给应用程序音频数据(如:用麦克风进行录制,将麦克风采集的数据通过回调函数传给使用者)
  • 从应用程序中请求音频数据(如:播放回调)
  • 监听某个对象状态的变化

为了去使用回调函数,我们需要做以下两件事情

  • 注册回调函数(如实现录制,播放回调,需要我们在初始化时提供一个函数)
  • 实现回调函数的功能.(实现初始化时提供的函数)

Note: 在OC中,回调函数是一个C语言形式的函数,我们回调OC本类对象作为对象传入其中, 所以回调函数中不能直接引用self.xxx,需要借助传入的OC对象去实现本类的功能.

6. 音频数据格式

Core Audio封装了音频数据格式,我们只需要对给定结构体赋正确的参数即可。

struct AudioStreamBasicDescription {
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

struct  AudioStreamPacketDescription {
    SInt64  mStartOffset;
    UInt32  mVariableFramesInPacket;
    UInt32  mDataByteSize;
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;

注意,上面结构体中mReserved是Apple的保留参数,必须为0. 其他一些参数在特定情况下也需为0,如:压缩音频格式每个sample使用不同数量的bits。对于这些格式,mBitsPerChannel成员的值为0。

  • 为AudioStreamBasicDescription赋值

你可以手动为ASBD的成员赋值,如果有些值是你不知道的,可以赋0,Core Audio将自动选择适当的值。

  • 标准的音频数据格式

iOS: 线性PCM 16bit integer, Noninterleaved linear PCM 8.24bit 定点samples

struct AudioStreamBasicDescription {
    mSampleRate       = 44100.0;
    mFormatID         = kAudioFormatLinearPCM;
    mFormatFlags      = kAudioFormatFlagsAudioUnitCanonical;
    mBitsPerChannel   = 8 * sizeof (AudioUnitSampleType);                    // 32 bits
    mChannelsPerFrame = 2;
    mBytesPerFrame    = mChannelsPerFrame * sizeof (AudioUnitSampleType);    // 8 bytes
    mFramesPerPacket  = 1;
    mBytesPerPacket   = mFramesPerPacket * mBytesPerFrame;     // 8 bytes
    mReserved         = 0;
};

7. Magic Cookie

在Core Audio中,magic cookie表示被附加到压缩音频数据(文件或流)中的元数据(metadata)。元数据为解码器提供了正确解码文件或流所需要的详细信息。Core Audio可以复制,读取,使用元数据包含的信息。

下面的例子展示了如何将一个文件中magic cookie拷贝提供给audio queue.

- (void) copyMagicCookieToQueue: (AudioQueueRef) queue fromFile: (AudioFileID) file {
 
    UInt32 propertySize = sizeof (UInt32);
 
    OSStatus result = AudioFileGetPropertyInfo (
                            file,
                            kAudioFilePropertyMagicCookieData,
                            &propertySize,
                            NULL
                        );
 
    if (!result && propertySize) {
 
        char *cookie = (char *) malloc (propertySize);
 
        AudioFileGetProperty (
            file,
            kAudioFilePropertyMagicCookieData,
            &propertySize,
            cookie
        );
 
        AudioQueueSetProperty (
            queue,
            kAudioQueueProperty_MagicCookie,
            cookie,
            propertySize
        );
 
        free (cookie);
    }
}

8.Audio Data Packets

音频数据包(packet)是一个或多个帧的集合,对于特定音频格式,它是有意义的最小帧集合,因此它是最佳表示一段时间音频数据的单位。

  • CBR(固定的比特率):PCM,IMA,ADPCM,所有packet具有相同size.
  • VBR(可变的比特率):AAC,MP3,Apple Lossless,所有packet都具有相同的帧数,但是每一帧中的位数不同。
  • VFR(可变的帧率): 每个包中具有不同的帧数,没有这种类型常用的格式。

在CBR,VBR的格式中,对于给定的音频文件或流,每秒钟的包数是固定的,

9.数据格式转换

使用audio converter可以改变音频采样率,交错或不交错,以及压缩与未压缩数据格式相互转换。

  • 将压缩数据格式(如AAC)转成线性PCM格式
  • 将线性PCM格式转成其他格式
  • 在16位signed integer线性PCM与8.24定点PCM间相互转换。

10.音频文件

Core Audio中使用Audio File Service为创建与访问音频文件及包含在其中元数据提供了一个强大的抽象。我们不仅可以使用文件的ID,type,数据格式,还可以添加标记,循环,回放等等功能。

  • 创建一个音频文件
    • 确定文件路径(CFURL/NSURL)
    • 确定文件标识符(ex CAF:kAudioFileCAFType)
    • 放在文件中的ABSD。
AudioFileCreateWithURL (
    audioFileURL,
    kAudioFileCAFType,
    &audioFormat,
    kAudioFileFlags_EraseFile,
    &audioFileID   // the function provides the new file object here
);

  • 打开一个音频文件 使用AudioFileOpenURL函数打开一个文件,提供URL,文件类型,访问权限成功后返回一个文件ID,使用这个ID以及常用函数可以检索我们需要的文件信息。下面列举了一些常用函数
kAudioFilePropertyFileFormat
kAudioFilePropertyDataFormat
kAudioFilePropertyMagicCookieData
kAudioFilePropertyChannelLayout

当一个VBR文件过大时,检索信息速度会较慢,可以使用kAudioFilePropertyPacketSizeUpperBound and kAudioFilePropertyEstimatedDuration.这两个函数快速获取近似值。

  • 读写文件

    • 读写包仅仅针对VBR数据
    • 使用基于包的操作更容易计算时间
  • 扩展 Core Audio提供了一个的API,称为扩展音频文件服务。该接口包含音频文件服务和音频转换器服务中的基本功能,提供与线性PCM之间的自动数据转换

  • iPhone 支持的Audio file格式

Format name Format filename extensions
AIFF .aif,.aiff
CAF .caf
MPEG-1,layer 3 .mp3
MPEG-2 or MPEG-4 ADTS .aac
MPEG-4 .m4a, .mp4
WAV .wav
AC-3 (Dolby Digital) .ac3
Enhanced AC-3 (Dolby Digital Plus) .ec3

iOS与OS X中原生音频文件格式为CAF(Core Audio Format),它可以支持平台中任意音频数据格式。它没有大小限制,可以支持多种元数据,如声道信息,文本注释等

11.音频流

与音频文件不同,我们无法确定一个audio file stream(音频流)的开始与结束点.因为我们往往是通过网络接受音频流数据,开始与结束的时机取决于用户的交互,并且,音频流数据也无法保证一定可以获取,因为网络传输中可能会存储在丢帧,暂停等等情况.

Audio File Service可以通过解析(parse)让我们使用音频流.通过回调函数获取parse到的一系列音频数据.

12.Audio Sessions: 配合Core Audio工作

在iOS中,有时我们需要处理高优先级任务,如接电话,如果当前APP正在播放视频,我们必须做出符合用户期望的事情以协调APP与系统电话.Audio Session对象充当了两者之间的一个中介.每个iPhone应用程序只有一个audio session,通过配置其属性以使用.

开始之前,我们要明确下面几个问题

  • 如何让应用程序响应一些意外中断,如接电话
  • 你打算让你的音频与其他应用程序中的音频混合起来,还是打算对它们做静音操作
  • 应用程序如何响应音频线路改变,如用户插拔耳机

为了解决上面的问题,我们需要配置audio session使用如下特性

Audio Session feature Description
Categories 一个category标识着一组音频行为的键,通过设置分类,可以表明音频的行为,如锁屏时是否应该继续播放音频.
Interruptions and route changes 当音频被中断或音频线路发生改变时,audio session将发送一个通知,通过接收通知以作出相应响应.
Hardware characteristics 通过audio session可以查询当前设备的一些硬件信息,如采样率,声道数,输入源设备等
  • Audio Session默认行为
    • 当用户将手机按键中的静音拨动时,音频将会被静音
    • 当用户按下锁屏键时,音频会被静音
    • 当你的音频启用时,当前正在播放的其他应用程序的音频会被静音

以上行为是audio session默认分类(kAudioSessionCategory_SoloAmbientSound)的行为

  • 中断:停用与激活(Deactivation and Activation) 默认的audio session中缺少一个重要功能就是在中断后无法恢复.audio session有两个重要状态:激活,停用.音频仅能够在激活状态下使用.

启动时,默认的audio session是激活状态,然而,如果有电话打进来(interruption),audio session马上处于停用状态且应用程序音频停止.如果用户选择忽略当前电话,你的应用程序继续运行,但是audio session仍是未激活状态,音频无法继续工作.

如果应用程序中使用OpenAL, I/O unit, Audio Queue Services,我们必须写一个监听中断的回调函数,在中断结束后重新激活audio session.

  • 决定输入源是否可用

使用录制功能的APP是否能录制取决于当前选择的硬件音频输入端,使用kAudioSessionProperty_AudioInputAvailable可以测试当前输入端是否可用

UInt32 audioInputIsAvailable;
UInt32 propertySize = sizeof (audioInputIsAvailable);
 
AudioSessionGetProperty (
    kAudioSessionProperty_AudioInputAvailable,
    &propertySize,
    &audioInputIsAvailable // A nonzero value on output means that
                           // audio input is available
);

  • 使用Audio Session

应用程序仅有一个audio session分类在同一时间(此规则的一个例外是使用System Sound Services播放的音频 - 用于警报和用户界面声音效果的API。此类音频始终使用最低优先级的音频会话类别),

13.使用AVAudioPlayer播放

如果你的应用程序不需要双声道,精确同步以及播放网络流音频,可以使用AVAudioPlayer类实现简单的音频播放.

以下使用范围

  • 播放任意时间段音频
  • 从文件或内存中播放音频
  • 循环音频
  • 同时播放多个音频
  • 控制正在播放每个声音相对播放水平
  • 寻找音频文件中特定点,支持快进快退
  • 获取音量
NSString *soundFilePath =
                [[NSBundle mainBundle] pathForResource: @"sound"
                                                ofType: @"wav"];
 
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
 
AVAudioPlayer *newPlayer =
                [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
                                                       error: nil];
[fileURL release];
 
self.player = newPlayer;
[newPlayer release];
 
[self.player prepareToPlay];
[self.player setDelegate: self];

- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
                        successfully: (BOOL) flag {
    if (flag == YES) {
        [self.button setTitle: @"Play" forState: UIControlStateNormal];
    }
}

- (IBAction) playOrPause: (id) sender {
 
    // if already playing, then pause
    if (self.player.playing) {
        [self.button setTitle: @"Play" forState: UIControlStateHighlighted];
        [self.button setTitle: @"Play" forState: UIControlStateNormal];
        [self.player pause];
 
    // if stopped or paused, start playing
    } else {
        [self.button setTitle: @"Pause" forState: UIControlStateHighlighted];
        [self.button setTitle: @"Pause" forState: UIControlStateNormal];
        [self.player play];
    }
    
    [self.player setVolume: 1.0];    // available range is 0.0 through 1.0

14.录制与播放 Audio Queue Services

Audio Queue Services提供了一种低开销,直接的方式去录制和播放音频,它使你的应用程序使用硬件(麦克风与扬声器)录制与播放并且无需了解硬件接口.它也让我们使用复杂的编解码器而无需了解编解码器的工作原理.

Audio Queue提供了更精确的定时播放以支持预定播放与同步,你可以使用它去同步多个音频播放队列,同时播放声音,独立控制每个队里的音量以及循环播放.

Audio Queue与AVAudioPlayer两者是在iPhone上播放音频的唯一方式

  • 录制与播放的回调函数

通过属性与回调函数让我们与audio queue对象间交互.对于录制,我们通过回调函数接收音频数据.

5.record_audioqueue

对于播放回调,当你的音频播放队列需要播放一个音频数据时它将被调用.你的回调函数将从磁盘读取指定数量的音频数据包然后将它们封装在audio queue对象的buffer中.audio queue将按顺序播放这些buffer.

5.play_audioqueue

  • 创建Audio Queue
    • AudioQueueNewInput:创建录制audio queue对象
    • AudioQueueNewOutput: 创建播放audio queue对象

实现一个播放队列

a. 创建一个结构体管理audio queue需要的信息,如音频格式,采样率等等

b. 定义一个回调函数管理audio queue buffers,这个回调函数使用Audio File Services去读取你想要播放的文件.

c. 初始化audio queue并且使用AudioQueueNewOutput创建对象.

static const int kNumberBuffers = 3;
// Create a data structure to manage information needed by the audio queue
struct myAQStruct {
    AudioFileID                     mAudioFile;
    CAStreamBasicDescription        mDataFormat;
    AudioQueueRef                   mQueue;
    AudioQueueBufferRef             mBuffers[kNumberBuffers];
    SInt64                          mCurrentPacket;
    UInt32                          mNumPacketsToRead;
    AudioStreamPacketDescription    *mPacketDescs;
    bool                            mDone;
};
// Define a playback audio queue callback function
static void AQTestBufferCallback(
    void                   *inUserData,
    AudioQueueRef          inAQ,
    AudioQueueBufferRef    inCompleteAQBuffer
) {
    myAQStruct *myInfo = (myAQStruct *)inUserData;
    if (myInfo->mDone) return;
    UInt32 numBytes;
    UInt32 nPackets = myInfo->mNumPacketsToRead;
 
    AudioFileReadPackets (
        myInfo->mAudioFile,
        false,
        &numBytes,
        myInfo->mPacketDescs,
        myInfo->mCurrentPacket,
        &nPackets,
        inCompleteAQBuffer->mAudioData
    );
    if (nPackets > 0) {
        inCompleteAQBuffer->mAudioDataByteSize = numBytes;
        AudioQueueEnqueueBuffer (
            inAQ,
            inCompleteAQBuffer,
            (myInfo->mPacketDescs ? nPackets : 0),
            myInfo->mPacketDescs
        );
        myInfo->mCurrentPacket += nPackets;
    } else {
        AudioQueueStop (
            myInfo->mQueue,
            false
        );
        myInfo->mDone = true;
    }
}
// Instantiate an audio queue object
AudioQueueNewOutput (
    &myInfo.mDataFormat,
    AQTestBufferCallback,
    &myInfo,
    CFRunLoopGetCurrent(),
    kCFRunLoopCommonModes,
    0,
    &myInfo.mQueue
);

  • 控制Audio Queue播放的音量

Audio queue对象提供了两种方式控制播放音量,一种是直接设置,如下,设置后可以立即生效.

Float32 volume = 1;
AudioQueueSetParameter (
    myAQstruct.audioQueueObject,
    kAudioQueueParam_Volume,
    volume
);

另一种是使用AudioQueueEnqueueBufferWithParameters,设置后在audio queue buffer开始播放时生效.

  • Indicating Audio Queue Playback Level

通过查询audio queue对象的kAudioQueueProperty_CurrentLevelMeterDB属性可以获取当前播放的级别.

typedef struct AudioQueueLevelMeterState {
    Float32     mAveragePower;
    Float32     mPeakPower;
};  AudioQueueLevelMeterState;

  • 同时播放多个声音

为了同时播放多个音频,需要为每个音频创建一个播放audio queue对象.对于每个audio queue,使用AudioQueueEnqueueBufferWithParameters函数安排第一个音频buffer同时启动。

同时播放多个音频,音频格式显得至关重要,因为iOS中某些音频格式使用了高效的硬件编解码器,只能在设备上播放以下格式之一的单个实例.

a. AAC

b. ALAC

c. MP3

如果要播放高质量同步的音频,需要使用线性PCM或IMA4格式.

a. 线性PCM和IMA / ADPCM(IMA4)音频您可以在iOS中同时播放多个线性PCM或IMA4格式声音,而不会产生CPU资源问题。

b. AAC,MP3和Apple Lossless(ALAC)一次只能播放一首此类声音

15.使用OpenAL定位播放

开源的OpenAL音频API(可在OpenAL框架中使用,构建于Core Audio之上)针对播放期间的声音定位进行了优化。使用OpenGL建模的界面,OpenAL可以轻松播放,定位,混合和移动声音,OpenAL和OpenGL共享一个通用坐标系统,使您可以同步音频和视频。OpenAL直接使用Core Audio的I / O audio unit),从而实现最低延迟播放。OpenAL是在iPhone和iPod touch上播放游戏应用中的声音效果的最佳选择。

16.系统声音

Audio Toolbox中的AudioServices.h提供了系统的声音服务,当你仅仅想播放一个系统的短音频时,它将是最好的选择,iOS中播放系统声音最不不能超过30秒.

在iOS中,调用AudioServicesPlaySystemSound可以立即播放,你也可以调用AudioServicesPlayAlertSound提示用户是否播放.

调用AudioServicesPlaySystemSound时使用kSystemSoundID_Vibrate常量可以显式设置振动效果.

#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
 
// Define a callback to be called when the sound is finished
// playing. Useful when you need to free memory after playing.
static void MyCompletionCallback (
    SystemSoundID  mySSID,
    void * myURLRef
) {
        AudioServicesDisposeSystemSoundID (mySSID);
        CFRelease (myURLRef);
        CFRunLoopStop (CFRunLoopGetCurrent());
}
 
int main (int argc, const char * argv[]) {
    // Set up the pieces needed to play a sound.
    SystemSoundID    mySSID;
    CFURLRef        myURLRef;
    myURLRef = CFURLCreateWithFileSystemPath (
        kCFAllocatorDefault,
        CFSTR ("../../ComedyHorns.aif"),
        kCFURLPOSIXPathStyle,
        FALSE
    );
 
    // create a system sound ID to represent the sound file
    OSStatus error = AudioServicesCreateSystemSoundID (myURLRef, &mySSID);
 
    // Register the sound completion callback.
    // Again, useful when you need to free memory after playing.
    AudioServicesAddSystemSoundCompletion (
        mySSID,
        NULL,
        NULL,
        MyCompletionCallback,
        (void *) myURLRef
    );
 
    // Play the sound file.
    AudioServicesPlaySystemSound (mySSID);
 
    // Invoke a run loop on the current thread to keep the application
    // running long enough for the sound to play; the sound completion
    // callback later stops this run loop.
    CFRunLoopRun ();
    return 0;
}

17.Audio Unit

在iOS中,Audio Unit为应用程序提供了实现低延迟输入和输出的机制。它们还提供某些DSP功能.

iOS中Audio Unit输入输出使用8.24位定点线性PCM音频数据.唯一例外的是以下情况.

  • 3D mix unit: 允许任意数量的单声道输入,每个输入可以是8位或16位线性PCM.在8.24位定点PCM中提供一个立体声输出,3D混音器单元对其输入执行采样率转换,并对每个输入通道提供大量控制。此控件包括这些更改的音量,静音,平移,距离衰减和速率控制。以编程方式,这是kAudioUnitSubType_AU3DMixerEmbedded单元。
  • Multichannel mixer unit: 允许任意数量的单声道或立体声输入,每个输入可以是16位线性或8.24位定点PCM。在8.24位定点PCM中提供一个立体声输出。您的应用程序可以静音和取消静音每个输入通道以及控制其音量。以编程方式,这是kAudioUnitSubType_MultiChannelMixer单元。
  • Converter unit: 提供采样率,位深度和位格式(线性到定点)转换。 iPhone converter unit’s的规范数据格式是8.24位定点PCM。它转换为此格式或从此格式转换。以编程方式,这是kAudioUnitSubType_AUConverter单元。
  • I/O unit:提供实时音频输入和输出,并根据需要执行采样率转换。以编程方式,这是kAudioUnitSubType_RemoteIO单元。
  • iPod EQ unit:提供可在应用程序中使用的简单均衡器,并在内置iPhone iPod应用程序中提供相同的预设。 iPod EQ单元使用8.24位定点PCM输入和输出。以编程方式,这是kAudioUnitSubType_AUiPodEQ单元。

每个Audio Unit的唯一标识符由类型,子类型,制造商代码(type, subtype, and manufacturer code)确定.每种子类型更加精确的描述了audio unit的用途.Audio Unit使用属性配置音频信息,如 Properties, Scopes, and Elements.每种audio unit需要一些指定属性,

18.编解码器

iOS中可以用的录制和播放编解码器来平衡音频质量,应用程序开发的灵活性,硬件功能和电池寿命。

19.Audio Processing Graphs

AUGraph:定义了一组复杂的音频执行任务.