본문 바로가기

Software/iOS & Objective-C

Audio Unit Hosting Fundamentals

Audio Unit Hosting Fundamentals

iOS의 모든 Audio 기술은 그림 1-1과 같이 Audio Unit위에 구축되어 있다. Media Player, AV Foundation, Open AL 및 Audio Toolbox와 같은 상위 Level의 기술은 Audio unit을 랩핑하여 특정 작업에 간소화된 API를 제공한다.



고차원 적인 Audio 컨트롤이 아니라면 애플에서는 Audio Unit을 직접적으로 사용하는 것을 권장하지 않으며,  위에서 언급된 랩핑된 Class들을 통해 접근하는 것을 권장한다.


Audio Units in iOS

iOS는 목적에 따른 7개의 Audio  Unit을 제공한다.


Effect Unit

iOS4 부터 내장 iPod Application에서 사용되는 것과 동일한 이퀄라이저인 iPod Equalizer를 하나의 Effect Unit으로 써 제공한다. 이 Audio unit에 대한 iPod Application 사용자 인터페이스 확인을 위해선 설정>음악>EQ를 통해 확인 할 수 있다. Audio unit을 사용할 때는 사용자 고유 UI를 제공해야 하며, Effect Unit은 Bass Booster, Pop 및 Spoken Word와 같은 preset Equalization curves 세트를 제공한다.

Mixer Units

iOS는 2개의 Mixer Unit을 제공한다. 3D Mixer Unit은 OpenAL Based되어 있는 형태이다. 대부분의 경우 3D Mixer Unit의 기능이 필요한 경우 Game App에 적합한 고급 API를 제공하는 OpenAL을 사용하는 것을 권장한다.

Multichannel Mixer Unit은 Stereo출력과 모든 수의 Mono 또는 Stereo Stream에 Mixing을 제공한다. 각 입력을 켜거나 끌 수 있으며 Input gain을 설정하고, Stereo panning position을 설정할 수 있다. 

I/O Units

iOS는 3개의 I/O Unit을 제공한다. 원격 I/O 장치(Remote I/O)가 가장 보편적으로 사용된다. 입/출력 Audio Hardware에 연결하여 개별 수신 및 발신 Audio Sample 값에 대한 대기 시간이 적은 접근(low-latency access)을 제공한다. Format Converter Unit을 통해 Hardware Audio Format과 Application Audio Format간의 Format 변환을 제공한다.

음성처리 I/O(Voice-Processing I/O)는 VoIP 또는 음성 채팅 Application에서 사용할 음향 반향 제거 기능을 추가하여 원격 I/O Unit을 확장한다. 또한 자동 게인 보정, 음성 처리 품질 조정 및 음소거 기능또한 제공한다.

일반 출력 장치(General Output)는 Audio Hardware에 연결하지 않고 Processing chain의 출력을 응용 프로그램에 보내는 메커니즘을 제공한다. 일반적으로 오프라인 오디오 처리에는 Generic Output 장치를 사용한다.


Format Converter Unit
iOS4 부터 일반적으로 I/O Unit을 통해 간접적으로 사용되는 하나의 Format 변환기 유닛을 제공한다.

Use Identifiers to Specify and Obtain Audio Units

Runtime시 Audio Unit을 찾기 위해, 먼저 Audio Component Description Data Structure에서 type(유형), subtype(하위 유형), manufacturer key(제조업체 키)를 지정하여 시작해야 한다.

Audio unit 또는 Audio processing graph API 사용 여부에 관계 없이 해당작업을 수행해야 한다. 해당작업은
Listing 1-1에 명시되어 있다.


Listing 1-1  Creating an audio component description to identify an audio unit

Description은 정확히 하나의 Audio Unit, 즉 Listing 1-1에서는 원격 I/O(Remote I/O) Unit을 지정한다. 다른 iOS Audio Unit의 Keys는 Audio Unit identifier Key 항목에 list up  되어 있다. 모든 iOS Audio Unit은 componentManufacturer 필드에 kAudioUnitManufacturer_Apple 키를 사용한다.

와일드카드(특정한 문자를 임의의 문자열 대신에 표시하는 것) description을 작성하려면, type/subtype field 중 하나 이상을 0으로 설정해야 한다. 모든 I/O 단위를 일치 시키려면 Listing 1-1을 변경하여 componentSubType field에 0 값을 설정해야 한다.

Description이 끝나면 두 개의 API 중 하나를 사용하여 지정된 Audio Unit(또는 Audio Unit Set)에 대한 Library에 대한 reference을 참조한다. Audio Unit API는 Listing 1-2에 명시되어 있다.


Listing 1-2  Obtaining an audio unit instance using the audio unit API


AudioComponentFindNext의 첫 번째 parameter에 NULL을 전달하면 System-defined ordering을 사용하여 description과 일치하는 첫 번째 System audio unit을 찾도록 function을 통해 명령을 전달한다.

해당 parameter에서 이전에 찾은 Audio Unit reference을 전달하는 경우 function은 description과 일치하는 다음 Audio Unit을 찾는다. 이 방법을 사용하면 AudioComponentFindNext를 반복적으로 호출하여 모든 I/O Unit에 대한 reference를 얻을 수 있다.

AudioComponentFindNext function의 두 번째 매개변수는 Listing 1-1에 정의된 Audio Unit description을 참조한다.

AudioComponentFindNext function의 결과는 Audio Unit을 정의하는 동적으로 링크 가능한 라이브러리에(Dynamically-linkable library) 대한 reference 이다. Listing 1-2와 같이 AudioComponentInstanceNew에 대한 reference를 전달하여 Audio Unit을 인스턴스화 한다.

Audio processing graph API를 사용하여 Audio Unit를 대신 인스턴스화 할수 있다. Listing 1-3은 그 방법을 나태내고 있다.


Listing 1-3  Obtaining an audio unit instance using the audio processing graph API

해당 Sample Code는 Audio processing graph의 컨텍스트에서 Audio Unit을 타나내는 불투명한 형식의 AUNode 대해서 소개하고 있다. AUGraphNodeInfo function Call의 Output으로 ioUnit을 매개 변수로 새로운 Audio Unit 인스턴스에 대하여 참조를 확인하고 있다.

AUGraphAddNoe 호출의 두 번째 매개 변수는 Listing 1-1에 정의된 Audio Unit description에 대하여 참조한다.

AUNode를 구성(설정)하기 위해선 Audio Unit Instance를 얻을 후에 가능하다. 이를 위해서는 Audio Unit characteristics(특성) 과 elements의 scopes(범위) 두 가지에 대하여 학습해야 한다.


Use Scopes and Elements to Specify Parts of Audio Units

Audio Unit 부분은 Figure 1-2에서 보는것과 같이 Scope와 Element로 구성된다. Audio Unit을 구성하거나 제어하는 함수를 호출 할 때 특정 대상을 식별하는 Scope와 Element을 지정한다.


Figure 1-2  Audio unit scopes and elements


Scope는 Audio Unit 내의 Programming 방식의 컨텍스트이다. Global Scope라는 이름이 다른 점을 제시할지라도 컨텍스트는 절대 충첩되지 않는다. Audio Unit Scopes enumeration 에서 상수를 사용하여 대상 Scope를 지정한다.

Element는 Audio Unit Scope 내에 중첩 된 Programming 방식 컨텍스트이다. Element가 input 또는 Output Scope의 일부인 경우 물리적 Audio Unit의 신호 버스(Signal Bus)와 유사하므로 버스라고도 불린다. Element와 Bus라는 두 용어는 Audio Unit 프로그래밍에서 같은것을 의미한다. Apple 문서에서는 신호 흐름을 강조하는 경우 "버스(Bus)"라는 표현을 사용하고, I/O Unit의 Input 또는 Output Element와 같은 Audio Unit의 특정 기능적 면을 강조할 때는 "요소(Element)"라는 표현을 사용한다.(참고 . Essential Characteristics of I/O Units)

Element(or Bus)를 0으로 인덱싱 된 정수 값으로 지정한다. Scope 전체에 적용되는 속성이나 매개변수를 설정하는 경우 Element 값 0를 지정한다.

Figure 1-2는 Audio Unit에 대한 하나의 공통 아키텍처를 보여 주며 Input 및 Output의 Element 수가 동일하다.
그러나 Audio Unit들은 다양한 아키텍처를 사용한다. 예를들어 Mixer Unit에는 여러 Input Element가 있지만 출력 Element는 단일로서 존재 한다. 하지만 
아키텍처의 이러한 변화에도 불구하고 Scope와 Element에 대해 여기서 학습한 것을 바탕으로 Audio Unit로 확장하여 설명 가능하다.

Figure 1-2의 하단에 표기된 Global scope는 Audio Unit 전체에 적용되며, 특정 Audio Stream과 연관되지 않는다. 정확히 하나의 Element, 즉 Element 0를 가지고 있다. Slice당 최대 프레임(kAudioUnitProperty_MaximumFramesPerSlice)과 같은 일부 속성은 Global Scope에만 적용된다.

Input 및 Output의 Scope는 Audio Unit을 통해 하나 이상의 Audio Stream을 이동하는 데 직접 관여한다. 예상했다 싶이 Audio는 Input Scope에 들어오게 되며, Output Scope를 통해 빠져나간다.

Property(속성) 또는 매개 변수(Parameter)는 
Element count property(kAudioUnitProperty_ElementCount)의 경우 처럼 Input 또는 Output Scope의 전체에 적용될 수 있다. I/O Enable property(kAudioOutputUnitProperty_EnableIO) 또는
Volume Parameter
(kMultiChannelMixerParam_Volume)와 같은 기타 Property 및 Parameter scope 내의 특정 Element에 따라 적용된다.


Use Properties to Configure Audio Units

Audio Unit property는 Audio unit을 구성하는데 사용할 수 있는 Key - Value 쌍이다. Property의 Key는kAudioUnitProperty_MaximumFramesPerSlice = 14.와 같은 관련 mnemonic(니모닉) 실별자가 있는 고유한 정수이다. Apple은 0부터 63999까지 Property Key를 점유(예약)하여 관리 한다. Mac OS X에서 타사 Audio Unit의 경우 해당 범위를 초과하는 Key범위를 사용한다.

각 Property의 값은 지정된 데이터 유형이며 지정된  Audio Unit Properties Reference.에서 설명된대로 Read/Write Access 여부가 지정된다. Audio Unit에 대한 Property을 설정하려면 flexible한 function인 
AudioUnitSetProperty. 을 사용한다. Listing 1-4에서는 이 함수의 일반적인 사용법을 제시하며, 주석의 Scope와 Element를 지정하는 방법과 Property의 Key & Value를 표기하는 방법을 특별히 다룬다.

Listing 1-4  Using scope and element when setting a property

다음은 Audio Unit 개발에 자주 사용할 몇가지 Properties이다. Reference 문서를 읽고  Audio Mixer(MixerHost)와 같은 Apple의 Audio Unit Sample Code를 살펴봐야 하며 해당 정보들 각각에 대해 확실히 인지해야만 한다.

  • kAudioOutputUnitProperty_EnableIOI/O Unit에서 Input 또는 Output 활성화 또는 비활성화 한다. 기본적으로 Output 사용 가능하게 되어 있으며, Input 사용 불가능하게 정의되어 있다.

  • kAudioUnitProperty_MaximumFramesPerSlice, Audio Unit 렌더링 Call 응답하여 생성 준비가 되어야하는 Audio Data 최대 Frame 수를 지정하기 위한 용도이다. 대부분의 Audio Unit 경우 대부분의 시나리오에서 Reference 문서에 설명된 대로 해당 property 설정해야 한다. 그렇게 하지 않으면 화면이 LockScreen 모드로 진입할 경우 Audio 멈추게 된다.



대부분의 Property value Audio Unit 초기화되지 않은 경우에만 설정할 있다. 이러한 Property 사용자가 변경할 없다. iPod EQ 장치의 kAudioUnitProperty_PresentPreset property Voice-Processing I/O Unit 
kAUVoiceIOProperty_MuteOutput property Audio 재생 중에 변경 있다.


Property 가용성을 찾고 value 접근하고 value 변경을 모니터링 하려면 다음 함수를 사용하면 가능하다.


  • AudioUnitGetPropertyInfo—property 이용 가능한 여부를 판단하기 위한 목적으로 사용. value 데이터 크기와 value 변경할 있는지 확인할 있다.


Use Parameters and UIKit to Give Users Control


Audio Unit parameter는 Audio Unit이 Audio를 생성하는 동안 변경가능하며, 사용자가 조정가능한 설정이다. 실제로 대부분의 parameter(volume 또는 stereo panning position(?))는 Audio Unit이 수행하는 처리의 실시간 조정을 목적으로 한다.
Audio Unit property와 마찬가지로 Audio Unit의 parameter는 Key-Value 쌍으로 이루어져있다. Key는 적용되는 Audio Unit으로 정의된다. Audio Unit은 unique하지만 전역적으로 unique하지 않는 enumeration 상수는 항상
kMultiChannelMixerParam_Pan = 2,  와 같다.

Property 값과 달리 모든 parameter 변수 값은 32비트 부동 소수점 type과 같다. 값의 허용 범위와 parameter가 나타내는 측정 단위는 Audio Unit의 parameter의 구현에 따라 결정된다. iOS Audio Unit의 parameter에 대한 기타 사항은 Audio Unit Parameters Reference 에 설명되어 있다.

Parameter의 값을 가져오거나 설정하려면 다음 함수를 사용하면 가능하다. 자세한 내용은
Audio Unit Component Services Reference: 을 참고하기 바란다.

사용자가 Audio Unit을 제어 할 수 있게하려면, 사용자 인터페이스를 통해 parameter에 대한 access 권한을 사용자에게 부여해야 한다. 먼저 UIKit Framework에서 적절한 클래스를 선택하여 parameter를 표현한다. 예를들면 
Multichannel Mixer Unit의 kMultiChannelMixerParam_Enable parameter와 같은 On/Off 기능의 경우 UISwitch 객체를 사용할 수 있다. kMultiChannelMixerParam_Pan parameter에서 제공하는 Stereo panning position과 같이 지속적으로 변하는 기능의 경우 UISlider 객체를 통해 사용할 수 있다.

UIKit 객체의 현재 구성 값(ex:UISlider의 슬라이더 썸 위치 같은...)을 Audio Unit에 전달한다. IBAction 메소드에서
AudioUnitSetParameter function을 래핑(wrapping)하고 Interface Builder에서 필요한 연결을 설정하면 된다.


Essential Characteristics of I/O Units

I/O Unit은 모든 Audio Unit App에 사용되는 Audio Unit의 type중의 하나 이며, 여러가지면에서 독특한 특징을 가지고 있다. 이러한 이유 때문에 Audio Unit Programming을 잘 다루기 위해서는 I/O Unit에 대한 본질적인 특성(Characteristics)을 숙지하고 있어야 한다.

I/O Unit은 Figure 1-3에서 볼 수 있듯이 정확히 두 개의 Element가 존재한다.

Figure 1-3  The architecture of an I/O unit


두 개의 element는 하나의 Audio Unit의 일부이지만,  App에서는 주로 독립 entities로 취급한다. 예를들면 Enable I/O property (kAudioOutputUnitProperty_EnableIO)을 사용하여 App의 필요에 다라 각 element를 독립적으로 활성화 또는 비활성화 할 수 있다.

I/O Unit의 Element 1은 그림에서 마이크로 표시된 Audio 입력 하드웨어에 직접 연결되어있다. 이 하드웨어 연결은 Element 1의 입력 범위에서 사용자에게 불투명(?)하다. Input 하드웨어에서 입력하는 Audio Data에 대한 첫번째 처리부분은 Element 1의 Output scope에서 이루어진다.

마찬가지로 I/O Unit의 Element 0는 Figure 1-3에 표시된 Unit의 Audio Output 하드웨어를 라우드 스피커와 직접 연결한다.  Element 0의 Input scope에 audio를 전달할 수 있지만 Output scope는 불투명(?)하다.

Audio Unit으로 작업할 때, 번호가 아닌 이름으로 설명되는 I/O Unit의 두 Element를 자주 듣게 된다.

  • Input Element는 Element 1이다.(니모닉 Device : Input이라는 단어의 I가 숫자 1과 비슷한 모양을 가짐.)

  • Output Element는 Element 0이다.(니모닉 Device : Output이라는 단어의 O가 숫자 0과 비슷한 모양을 가짐.)

Figure 1-3에서 볼 수 있듯이 각 Element 자체적으로 Input scope와 Output scope를 가지고 있다. 이러한 이유 때문에 I/O Unit의 이런 부분들에 대하여 설명하는 것이 다소 혼란스러울 수 있다. 예를들어 I/O App에서 Input element의 output scope에서 audio를 수신하고 Output element의 input scope로 보낸다.

결론은 Audio processing graph에서 Audio 흐름을 시작하고 중지 할 수 있는 유일한 Audio Unit은 I/O Unit이다. 이러한 방식으로  I/O Unit은 Audio Unit을 사용하는 App의 Audio 흐름을 담당한다.


Audio Processing Graphs Manage Audio Units

Audio processing graph는 AUGraph라는 Core Foundation style의 불투명한 type으로, Audio Unit의 processing chain을 구성하고 관리하는데 사용된다. Graph는 다수의 Audio Unit의 및 다수의 렌더링 Callback function을 할용할 수있으므로 상상할 수 있는 거의 모든 Audio processing solution을 만들 수 있다.

AUGraph Type은 Audio Unit Story에 thread 안정성을 가지고 있다. 즉시 processing chain을 구성할 수 있으며, 예를들어 Audio가 재생되는 동안 Mixer input에 대한 다른 렌더 Callback 함수로 안전하게 이퀄라이저를 삽입하거나 스왑 할 수 있다. 실제로 AUGraph Type은 Audio App에서 이러한 종류의 동적 재구성을 수행하는 iOS의 유일한 API를 제공한다.

Audio processing graph API는 또 다른 불명한 Type인 AUNode를 사용하여 Graph 컨텍스트내에서 개별 Audio Unit을 나타낸다. Graph를 사용할 때는 일반적으로 Audio Unit과 직접 상호 작용하지 않고 포함된 Audio Unit의 proxies로 Node와 상호 작용한다.

Graph를 결합 할 때 각 Audio Unit을 구성해야하며 이를 위해서는 Audio Unit API를 통해 Audio Unit과 직접 상호 작용해야 한다. Audio Unit Node 자체는 구성 할 수 없다. 이러한 방식으로 Graph를 생성하려면 Use the Two Audio Unit APIs in Concert. 를 통해 확인하기 바란다.

완전한 Audio processing subgraph를 나타내기 위해 Node를 정의하여, AUNode 인스턴스를 복잡한 Graph 요소로 사용할 수도 있다. 이 경우 하위 Graph 끝에있는 I/O Unit은 Unit Hardware에 연결되지 않은 I/O Unit의 한 Type인 일반 Output Unit이어야 가능하다.

다양한 strokes에서 Audio processing graph를 작성하는 방법에는 세가지 작업이 필요하다.

  1. Graph에 Node를 추가하기

  2. Node가 나타내는 Audio Unit를 직접 구성하기

  3. Node간 상호 연결하기

이러한 작업 및 다른 Audio processing graph lifecycle에 대한 자세한 설명은 Constructing Audio Unit Apps을 참조하기 바라며, 다른 추가적인 API에 대한 설명은 Audio Unit Processing Graph Services Reference. 를 참고 하기 바란다.

An Audio Processing Graph Has Exactly One I/O Unit

모든 Audio processing graph API는 녹음, 재생 또는 동시 I/O 중 어떤 동작을 수행하던 하나의 I/O Unit을 가진다. I/O Unit은 App의 요구에 따라 iOS에서 사용할 수 있는 Unit 중 하나 일 수 있다. I/O Unit이 다양한 사용 시나리오에서 Audio processing graph의 아키텍처에 맞는 방법에 대한 자세한 내용은 Start by Choosing a Design Pattern.를 통해 학인하기 바란다.

Graph를 사용하면 AUGraphStart 및 AUGraphStop 기능을 통해 Audio 흐름을 시작하고 중지할 수 있다. 이 함수는  AudioOutputUnitStart 또는 AudioOutputUnitStop function을 호출하여 I/O Unit에 Start 또는 Stop 메세지를 전달한다. 이런 방식으로 Graph의 I/O Unit이 Graph의 Audio 흐름을 담당한다.


Audio Processing Graphs Provide Thread Safety

Audio processing graph API는 thread에 대한 safety를 제공하기 위해 "to-do-list" metaphor를 채용하고 있다. 이 API의 특정 Function은 나중에 실행될 변경 사항 목록에 작업 Unit를 추가한다. 전체 변경 사항 SET을 지정한 후에 Graph를 구현하도록 해야한다.

다음은 Audio processing graph API에서 지원하는 일반적인 재구성과 관련된 function 이다 :

실행중인 Audio processing graph를 재구성하는 예제를 살펴보자. 예를들어 Multichannel Mixer Unit과 Remote I/O Unit이 포함된 Graph를 작성하여 두 개의 혼합된 sound를 재생할 수 있다. Sound를 Mixer의 두 개의 input bus에 공급한다. Mixer의 Output은 I/O 장치의 Output element로 가고 Output audio hardware로 간다. Figure 1-4는 해당 flow에 대한 아키텍처를 설명한다.


Figure 1-4  A simple audio processing graph for playback


이제 사용자가 두 개의 Audio Stream 중 하나에 이퀄라이저를 삽입하려고 한다고 가정해보자. 해당 기능을 구현하기 위해선 Figure 1-5와 같이 Sound 중 하나의 Feed와 이동하는 Mixer 사이에 iPod EQ Unit을 추가해야 한다.


Figure 1-5  The same graph after inserting an equalizer

Equalizer를 재구성하는 흐름은 다음과 같다.

  1. AUGraphDisconnectNodeInput를 호출하여 Mixer Unit의 Input 1로 부터의 "Beat sound" Callback을 disconnect 시킨다.

  2. iPod EQ Unit이 포함된 Audio Unit Node를 Graph에 추가한다. AudioComponentDescription 구조로 iPod EQ Unit을 지정한 다음 AUGraphAddNode.를 호출하여 추가한다. 이 시점에서 iPod EQ Unit은 객체는 생성되었으나 초기화되지 않았다. 실제 Graph에 추가되어 소유되어 있지만 실질적으로 아직 Audio flow에 참여하지는 않는다.

  3. iPod EQ Unit구성하고 초기화 한다. 이 Step에서는 몇가지 작업을 해야 한다.

  • AudioUnitGetProperty function을 Call 하여 Mixer input으로 부터 stream format(kAudioUnitProperty_StreamFormat)을 검색한다.

  • AudioUnitSetProperty function을 두번 Call 한다. 첫번째는 iPod EQ Unit의 input에 stream formt을 설정하고 두번째는 Output에 Output format을 지정한다.(iPod EQ Unit을 구성하는 방법에 대한 전체 설명은 Using Effect Units.를 통해 확인하면 되겠다.)

  • AudioUnitInitialize function을 Call하여 iPod EQ Unit에 대한 리소스를 할당(메모리 할당-초기화)하고 Audio process를 준비한다. 이 함수 Call은 thread에 안전하지는 않지만 AUGraphUpdate function을 아직 Call하지 않았기 때문에 iPod EQ Unit이 Audio processing graph에 활성화되어 audio flow에 관여하지 않으면 sequence(분기 or step:단계)의 이 시점에 이를 수행할 수 있다.

4. AUGraphSetNodeInputCallback을 호출하여 "Beats sound" Callback 함수를 iPod EQ의 input에 연결한다.


앞의 전 처리 과정에서 AUGraph의 함수 호출 과정인 1, 2, 및 4단계가 "to-do-list" 목록에 추가 되었다. AUGraphUpdate function이 성공적으로 반환되면 Graph가 동적으로 재구성되고 iPod EQ가 자신의 위치에서 audio를 처리한다.


Audio Flows Through a Graph Using “Pull”


Audio processing graph에서 Consumer는 더 많은 Audio Data가 필요할 때 provider를 call한다. Audio Data에 대한 request flow가 있다면, flow는 audio flow의 반대방향으로 진행된다. Figure 1-6은 해당 메커니즘에 대한 설명을 설명한다.

Figure 1-6  The pull mechanism of audio data flow


Data set에 대한 각각의 request는 렌더링 Call 또는 비공식적으로 pull(informally, as a pull) 한다고 표현한다. Figure 1-6은 렌더링 Call을 회색 "control flow" 화살표로 보여주고 있다. 렌더 call에 의해 요청된 데이터는 Audio sample frame set으로 더 잘 알려져 있다.(frame 참고)

렌더링 Call에 응답하여 제공된 Audio Sample frame Set을 Slice라고 한다.(slice 참고) Slice를 제공하는 코드는 렌더링 Callback function으로 에서 관리되며, 이에 대한 정보는 Render Callback Functions Feed Audio to Audio Units 에 설명되어 있다.

Figure 1-6에서 Pull이 진행되는 과정은 다음과 같다 :

AUGraphStart function을 call하면 virtual output device에서는 Remote I/O Unit의 Output element의 렌더링 callback을 call한다. 이 call은 처리된 audio data frame의 slice에 대하여 요청한다.

Remote I/O Unit의 렌더링 callback function은 렌더링 buffer를 처리하기 위해 input buffer에서 audio data를 찾는다. 처리 대기중인 데이터가 있는 경우, Remote I/O Unit은 해당 data를 사용한다. 대기중인 data가 없는 경우는 Figure 1-6에서 설명하는 것과 같이 연결된 모든 항목의 렌더링 callback을 call한다. 해당 예제에서는 Remote I/O Unit의 input이 Effect Unit의 output에 연결된다. 따라서 I/O Unit은 Effect Unit을 pull하여 audio frame slice를 요청한다.

Effect Unit은 Remote I/O Unit 처럼 동작한다. Audio data가 필요하면 input connection을 통해 가져온다. 해당 예제에서는 effect Unit이 App의 렌더링 callback function을 사용한다.

App의 렌더링 callback function이 pull의 최종 수신자이다. 요청된 frame을 effect unit에 공급한다.

Effect Unit은 App의 렌더링  callback에 의해 제공된 slice를 처리한다.  그런 후 effect Unit은 이전에 요청된 data(2 단계에서)를 Remote I/O Unit에 공급한다.

Remote I/O Unit은 Effect Unit에 의해 제공된는 slice를 처리한다. 그런 후 Remote I/O Unit은 원래 요청 처리된 slice(1단계 에서)를 virtual output device에 제공한다. 이것을 마지막으로 한번의 pull 작업을 완료한다.


Render Callback Functions Feed Audio to Audio Units

Disk 또는 Memory의 Audio를 Audio Unit input bus에 제공하려면,  AURenderCallback 프로토 타입을 준수하는 렌더링 callback function을 사용하여 전달한다. Audio Unit input은 sample frame의 다른 slice가 필요할 때 callback을 호출한다.(참고 : Audio Flows Through a Graph Using Pull)

렌더링 Callback function을 작성하는 과정은 Audio Unit Application을 설계하고 제작하는데 있어서 가장 창의적인 부분일 것이다. 이 부분에서 상상할 수 있는 방식으로 사운드를 생성하거나 변경할 수 있는 기회이다.

동시에 렌더링 Callback은 엄격한 성능 요구 사항을 준수해야한다. 렌더링 Callback은 Real-time priority thread(실시간 우선 순위 스레드)에 있으며, 이 후 렌더링 호출은 비동기 적으로 호출된다. 렌더링 callback 본문에서 수행하는 작업은 제한된 환경에서 발생한다. 다음 렌더링 호출이 발생할때 Callback은 이전 렌더링 호출에 대한 응답으로 Sample frame을 생성하는 경우 사운드에 갭이 생긴다. 이러한 이유 때문에 lock을 걸거나,  메모리를 할당하거나, 파일 시스템이나 네트워크에 연결을 하거나, 렌더링 Callback function 본문에서 시간 소모적인 작업을 수행해서는 안된다.


Understanding the Audio Unit Render Callback Function


Listing 1-5는 AURenderCallback 프로토타입을 준수하는 렌더링 Callback function의 헤더를 보여준다. 여기에서는 차례대로 각 parameter의 목적을 설명하고 각 parameter를 사용하는 방법을 설명한다.


Listing 1-5  A render callback function header


inRefCon parameter는 callback을 Audio Unit input에 연결할 때 지정하는 프로그래밍 방식 컨텍스트를 사리킨다(참고: Write and Attach Render Callback Functions). 이 컨텍스트의 목적은 callback function에 audio input data 또는 주어진 렌더링 호출에 대한 output audio를 계산하는 데 필요한 상태 정보를 제공하는 것이다.

ioActionFlags parameter를 사용하면 audio unit에 처리 할 audio가 없다는 힌트를 callback에서 제공할 수 있다. 예를들어, App이 합성 기타이고 사용자가 현재 메모를 재생하지 않는 경우 수행한다. 무음을 출력하려는 callback 호출 중에 callback 본문에서 다음과 같은 문을 사용한다.

무음을 생성 하려면 ioData parameter가 가리키는 버퍼를 명시적으로 0을 설정해야한다.

inTimeStamp parameter는 callback이 호출된 시간을 나타낸다. 이 시간은 mSampleTime filed가 sample frame counter인 AudioTimeStamp struct가 포함되어 있다. Callback을 호출 할 때마다 mSampleTime의 field 갑은 inNumberFrames parameter의 숫자만큼 증가한다. 예를들어 App이 Sequencer 또는 Drum machine인 경우, mSampleTime 값을 사용하여 사운드를 예약할 수 있다.

inBusNumber parameter는 callback을 호출 한 audio unit bus를 나타내며, 이 값에 따라 callback 내에서 분기를 처리 할 수 있다. 또한 callback을 audio unit에 연결하는 경우 각 bus에 대해 서로 다른 컨텍스트(inRefCon)를 지정할 수 있다.

inNumberFrames parameter는, 현재의 호출로 callback이 요구하고 있는 audio sample frame의 수를 나타낸다. 이러한 frame을 iodate parameter의 변수의 buffer에 제공한다.

ioData parameter는 callback이 호출될 때 채워야하는 audio data buffer를 가리킨다. 이 buffer에 넣는 audio는 callback을 호출한 bus의 audio stream format을 따라야한다.

Callback의 특정 호출에 대해 무음을 재생하는 경우 memset function을 사용하여 명시 적으로 이러한 버퍼를 0으로 설정해야 한다. 

Figure 1-7은 ioData parameter의 한 쌍의 non interleaved stereo buffer를 설명한다. Figure의 요소들을 살펴봄으로 서 callback이 채워야 하는 iodata buffer의 세부 사항을 시각화하여 이해해보자.


Figure 1-7  The ioData buffers for a stereo render callback function



Audio Stream Formats Enable Data Flow

Audio units을 사용할 때 처럼 개별 sample level 수준에서 audio data로 작업 할 때 audio를 타나내기 위해 올바른 data format을 지정하는 것만으로는 충분하지 않다. Single audio sample 값의 bit layout은 의미가 있으므로 Float32 또는 UInt16과 같은 data type은 충분히 표현할 수 없다. 이번 장에서는 해당 문제에 대한 Core Audio의 솔루션에 대해 알아보자.


Working with the AudioStreamBasicDescription structure

App에서, 그리고 App과 Audio hardware 사이에서 audio 값을 이동시키기위한 방법은 Listing 1-6에서 설명 하였으며,  Core Audio Data Types Reference 에 자세히 설명된 AudioStreamBasicDescription구조 이다.


Listing 1-6  The AudioStreamBasicDescription structure

AudioStreamBasicDescription 이라는 이름이 길기 때문에 흔히 문서나 대화를 통해 말하는 경우 ASBD로 축약되는 경우가 많다. ASBD의 field 값을 정의하려면 Listing 1-7과 비슷한 코드를 작성해야 한다.


Listing 1-7  Defining an ASBD for a stereo stream

시작하려면 하나의 audio sample 값을 나타내는 data type을 결정해야 한다. 

이 예제애서는 AudioUnitSampleType 으로 type을 사용한다. 이 Type은 대부분의 audio unit에 권장되는 data type이다. iOS에서 AudioUnitSampleType은 8.24 고정 소수점 정수로 정의된다. Listing 1-7dㅢ 첫 번째 줄은 유형의 바이트 수를 계산한다. 이 번호는 목록에서 볼 수 있듯이 ASBD의 일부 field 값을 정의 할 때 필요하다.

다음은 여전히 Listing 1-7을 참고하여 AudioStreamBasicDescription 유형의 변수를 선언하고 해당 field를 0으로 초기화하여 garbage data가 없는 field가 없는지 확인한다. 이 Zeroing step을 건너 뛰어서는 안된다. 해당 초기화 과정을 무시할 경우, 나중에 문제가 발생할 수 있다.

이제 ASBD의 field 값을 정의한다.  mFormatID field에 대하여 kAudioFormatLinearPCM 을 지정한다. Audio unit은 압축되지 않은 audio data를 사용하므로 audio unit으로 작업할 때마다 사용할 올바른 format 식별자이다.

그 후, 대부분의 audio unit에 대해 mFormatFlags field에 대해 kAudioFormatFlagsAudioUnitCanonical metaflag를 지정해야 한다. 이 flag는  CoreAudio.framework/CoreAudioTypes.h에 정의되어 있다.


Understanding Where and How to Set Stream Formats

Audio processing graph의 중요한 지점에서 audio data stream format을 설정해야 한다. 다른 지점에서 system은 format을 설정한다. 또 다른 포인트에서, audio unit connections는 하나의 audio unit에서 다른 audio unit으로 stream format을 전달한다.

iOS device의 audio input 및 output hardware는 시스템에 다라 결정되는 audio stream format이 있다. 이러한 형식은 항상 Linear PCM format으로 압축되지 않고 interleaved(주어진 전파 채널에서 주 채널 또는 각각의 주파수 채널 및 인접 채널 사이에 추가 채널을 끼워 넣는 것)된다. System은 Figure 1-8과 같이 Audio processing graph에서 I/O Unit의 바깥 측면에 이러한 format을 적용한다.


Figure 1-8  Where to set audio data stream formats

Figure 1-8에서 마이크는 Input Audio hardware를 나타낸다. System은 Input hardware의 Audio stream format을 결정하고 이를 Remote I/O Unit의 input element의 input scope에 보내기 시작한다.

마찬가지로 라우드 스피커는 Output  audio hardware를 나타낸다. System은 Output hardware의 stream format을 결정하고 이를 Remote I/O Unit의 output element의 output scope에 보내기 시작한다.

Application은 I/O Unit element의 내부에서 audio stream format을 설정한다. I/O Unit은 application format과 hardware format간에 필요한 변환을 수행한다. App은 Graph에 필요한 모든 stream format을 설정해야 하는 의무를 가진다. Figure 1-8의 Multichannel Mixer Unit와 같이 일부 경우에는 format의 일부, 즉 sample 속도만을 설정해야 한다. Start by Choosing a Design Pattern는 다양한 type의 audio unit app에 대한 stream format을 설정하는 방법을 설명한다. Using Specific Audio Units은 각 iOS audio unit에 대한 stream format 요구 사항을 설명한다.

Audio unit connection의 핵심 기능은 Figure 1-8에서 볼 수 있듯이, Connection이 원본 audio unit의 output에서 대상 audio unit의 input으로 audio data stream format을 전달하는 것이다. Stream format 전달은 audio unit 연결을 통해 한 방향으로만 source audio unit의 output에서 대상 audio unit의 input에 이르기까지 발생한다.

Format 전달을 이점을 활용해야 한다. 이는 작성해야할 코드의 양을 상당히 줄일 수 있다. 예를들어 Multichannel Mixer Unit의 Output 재생을 위해 Remote I/O Unit에 연결하는 경우 I/O Unit의 stream format을 설정할 필요가 없다. 이 값은 Mixer의 output stream format에 따라 audio unit간의 연결에 의해 적절하게 설정된다.(Figure 1-8 참고)

Stream format 전달은 Audio processing graph의 life cycle 즉, 초기화 시에 특정 시점에 발생한다.(참고 :Initialize and Start the Audio Processing Graph)

우리는 Application의 audio stream format을 유연하게 정의 할 수 있다. 그러나 가능한 경우 Hardware가 사용하는 sample rate를 사용해햐 한다. 그렇게 하면 I/O Unit이 Sample rate 변환을 수행 할 필요가 없다. 이를 통해 모바일 device에서 중요한 고려 사항인 베터리 사용을 최소화 하고 audio 품질을 극대화 할 수 있다. Hardware sample rate 작업에 대한 자세한 내용은 Configure Your Audio Session 을 통해 확인할 수 있다.

반응형

'Software > iOS & Objective-C' 카테고리의 다른 글

Audio Unit Hosting에 대하여...  (0) 2016.12.05
iOS Audio Play  (0) 2016.11.23
Concurrency Programming Guide 요약  (0) 2016.10.10
[iOS] App 내에서 언어 변경법  (0) 2016.03.02
클래스 기본 문법  (0) 2015.11.09