视频开发程序猿阵线联盟-汇总各类技术干货

Core Audio音频基础概述

2019-05-02  本文已影响4人  小东邪啊

Core Audio

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

1.Core Audio

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

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位整数描述每个样本 - 构成分辨率或位深度。

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管理对象的行为与状态.

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。

你可以手动为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,VBR的格式中,对于给定的音频文件或流,每秒钟的包数是固定的,

9.数据格式转换

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

10.音频文件

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

AudioFileCreateWithURL (
    audioFileURL,
    kAudioFileCAFType,
    &audioFormat,
    kAudioFileFlags_EraseFile,
    &audioFileID   // the function provides the new file object here
);

kAudioFilePropertyFileFormat
kAudioFilePropertyDataFormat
kAudioFilePropertyMagicCookieData
kAudioFilePropertyChannelLayout

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

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默认分类(kAudioSessionCategory_SoloAmbientSound)的行为

启动时,默认的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分类在同一时间(此规则的一个例外是使用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

实现一个播放队列

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对象提供了两种方式控制播放音量,一种是直接设置,如下,设置后可以立即生效.

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

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

通过查询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音频数据.唯一例外的是以下情况.

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

18.编解码器

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

19.Audio Processing Graphs

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

上一篇 下一篇

猜你喜欢

热点阅读