버전

menu_open
Wwise SDK 2023.1.2
사운드 엔진 플러그인 만들기

Wwise 사운드 엔진 플러그인을 만들면 작업에 큰 도움이 되지만 다소 까다로울 수 있습니다. 예제 플러그인 section 에 제공된 예제와 함께 다음 섹션을 자세히 읽어보길 강력히 권장합니다. 이 예제들은 직접 만들 플러그인의 기본 토대로 사용할 수 있는 안정적인 형식과 구조를 제공합니다.

사운드 엔진 플러그인 개요

플러그인을 이용하면 사운드 엔진의 전체 신호 처리 체인에 커스텀 DSP 루틴을 집어넣을 수 있습니다. 플러그인 매개 변수는 저작 툴이나 게임 내에서 RTPC를 통해 제어할 수 있습니다 ( Understanding Real-Time Parameter Controls (RTPCs) 참고).

각 플러그인은 두 가지 컴포넌트로 구성돼있습니다.

  • 사운드 엔진에 통합된 런타임 컴포넌트로서, 크로스 플랫폼이나 특정 플랫폼에 최적화하여 개발할 수 있습니다. 게임은 이 플러그인을 등록해야 하며 ( 플러그인 정적(static) 등록 참고), 해당 구현에 연결해야 합니다.
  • SoundBank 생성을 위한 저작 컴포넌트와 저작 툴에 통합되는 사용자 인터페이스 이 컴포넌트는 Wwise에 의해 로드되는 DLL에 있습니다 ( 플러그인 라이브러리 형식 저작하기 참고). UI에서 결정되는 모든 플러그인 매개 변수는 뱅크에 저장되므로 이 컴포넌트는 게임에서 사용되지 않습니다. 매개 변수가 게임에서 RTPC로 제어되도록 할 경우, 이 때 필요한 정보도 뱅크에 저장됩니다.
작은 정보: 런타임 컴포넌트를 위한 공통 정적 라이브러리를 생성하세요. 이렇게 하면 게임과 플러그인 사용자 인터페이스 둘 다 Wwise에 의해 로드되는 DLL로 연결됩니다.

매개 변수 노드 인터페이스는 사운드 엔진의 RTPC 관리자나 Wwise 저작 툴에서 생기는 변경 사항에 반응하고, 실행 중 현재 플러그인 인스턴스 매개 변수를 가져오도록 구현돼야 합니다. ( 매개 변수 노드 인터페이스 구현 참고)

사운드 엔진에 통합될 수 있는 플러그인에는 세 가지 주요 범주가 있습니다.

  • 음원 플러그인(Source plug-in). 물리적 모델링, 변조 합성(modulation synthesis), 샘플링 합성(samplig synthesis) 등, 다양한 합성 방법을 이용해 오디오 컨텐츠를 만듭니다. 더 많은 정보는 Creating Sound Engine Source Plug-ins 을(를) 참고하세요.
  • 효과 플러그인(Effect plug-in). 입력 오디오 데이터인 기존 사운드에 DSP 알고리즘을 적용합니다. 더 많은 정보는 Creating Sound Engine Effect Plug-ins 을(를) 참고하세요.
  • 오디오 장치 플러그인(Audio device 플러그인). 과정의 마지막에서 오디오 데이터를 받아 다른 사운드 시스템으로 전달합니다. 더 많은 정보는 Audio Device (Sink) 플러그인 만들기 을(를) 참고하세요.

플러그인 및 그와 연관된 매개 변수 인터페이스는 플러그인 매커님즘을 통해 사운드 엔진에서 만들어집니다. 이 때, 필요에 따라 매개 변수 노드의 새로운 인스턴스와 새로운 플러그인 인스턴스를 반환하는 정적 생성 함수의 노출이 필요합니다. 다음 코드는 이런 처리가 어떻게 되는지 보여줍니다. 생성 함수는 라이브러리 사용자가 볼 수 있도록 반드시 AK::PluginRegistration 정적 인스턴스에 패키징돼야 합니다. 각 플러그인 클래스/타입마다 하나의 AK::PluginRegistration 클래스가 필요합니다.

// 플러그인 관리자에 등록될 정적 매개 변수 노드 생성 함수 콜백
AK::IAkPluginParam * CreateMyPluginParams( AK::IAkPluginMemAlloc * in_pAllocator )
{
return AK_PLUGIN_NEW( in_pAllocator, CAkMyPluginParams() );
}
// 플러그인 관리자에 등록될 정적 플러그인 생성 함수 콜백
AK::IAkPlugin * CreateMyPlugin( AK::IAkPluginMemAlloc * in_pAllocator )
{
return AK_PLUGIN_NEW( in_pAllocator, CAkMyPlugin() );
}
// 정적 초기화 객체가 자동으로 플러그인을 사운드 엔진에 등록함
AK_IMPLEMENT_PLUGIN_FACTORY(MyPlugin, AkPluginTypeEffect, AKCOMPANYID_MYCOMPANY, EFFECTID_MYPLUGIN);
참고: 만들고자 하는 플러그인의 타입에 따라 AK::PluginRegistration 함수의 AkPluginType 인자를 설정하세요. 예를 들어 음원 플러그인은 AkPluginTypeSource 를, 효과 플러그인은 AkPluginTypeEffect 를 설정합니다.
참고: Registration 객체 이름은 매우 중요합니다. 이 이름은 pluginname 뒤에 "Registration"을 연결시키는 AK_STATIC_LINK_PLUGIN(pluginname) 매크로 와 함께 사용됩니다. 다음 플러그인 정적(static) 등록 을 확인하세요.

만약 플러그인 제공사로서 자신의 플러그인이 다른 플러그인과 ID가 겹치지 않게 하려면 support@audiokinetic.com으로 연락해 이미 사용중인 Company ID를 확인하세요.

플러그인 정적(static) 등록

모든 종류의 오디오 플러그인 인스턴스는 Plug-in Manager에 의해 관리됩니다. Plug-in Manager는 CompanyID와 PluginID를 이용해 다양한 플러그인 클래스를 인식합니다. 플러그인은 게임에 사용하기 전에 반드시 Plug-in Manager에 등록되어야 합니다. 등록 과정은 전달인자로 제공되는 생성 함수 콜백에 PluginID를 바인딩합니다.

아래의 예제 코드는 플러그인을 게임으로 어떻게 등록하는지 보여줍니다. Audiokinetic이 제공하는 플러그인 또한 게임에 사용될 경우 반드시 등록해야 합니다.

사용 편의를 위해 모든 플러그인에는 AK_STATIC_LINK_PLUGIN 매크로만 포함된 Factory 헤더 파일이 있습니다. 편리한 플러그인 관리를 위해 자신의 라이브러리 이름과 동일한 방식으로 "Factory"와 연결하여 Factory 헤더 이름을 지정하세요. 예를 들어 아래 코드는 MyPlugin.lib와 연관된 MyPluginFactory.h의 콘텐츠입니다.

AK_STATIC_LINK_PLUGIN(MyPlugin); //이 매크로가 사운드 엔진에 플러그인을 등록합니다.

MyPlugin을 사용하는 게임은 MyPluginFactory 파일을 포함시키고 MyPlugin.lib와 연결합니다.

#include <AK/Plugin/MyPluginFactory.h> // Factory 헤더는 AK_STATIC_LINK_PLUGIN을 통해 플러그인 코드를 강제로 연결합니다.
참고: _linkonceonly 로 끝나는 다중 정의된 심볼 연결 오류가 뜬다면, 여러개의 CPP 파일에서 Factory 헤더를 포함했다는 뜻입니다. Factory는 연결 단위당 (예: DLL, SO, DYLIB, EXE 파일, 등) 한 번만 포함돼야 합니다.

동적 라이브러리 만들기

게임에서 플러그인을 사용하는 방법에는 두 가지가 있습니다. 하나는 정적 라이브러리를 통해서고, 다른 하나는 동적 라이브러리를 통해서입니다. 정적 라이브러리를 배포하는 것은 의무 사항입니다. 동적 라이브러리 배포는 선택 사항이지만 Unity의 Wwise 통합에서 동적 라이브러리를 사용하기 때문에 배포할 것을 강력히 권장하고 있습니다. 반대로 Wwise Unreal 통합은 정적 라이브러리를 사용해야 합니다.

라이브러리로 동적 라이브러리를 만드는 과정은 어렵지 않습니다. 모든 Effect 플러그인에 대해 Audiokinetic이 만들어놓은 많은 예제가 \SDK\samples\DynamicLibraries에 들어있습니다. 다음은 필수적인 사항입니다.

  • 프로젝트를 빌드하는 정적 라이브러리에 반드시 연결해야 합니다.
  • AK_STATIC_LINK_PLUGIN에 직접적이든 헤더 파일을 통해서든 반드시 하나의 레퍼런스를 포함시켜야 합니다.
  • 자신의 Dynamic 라이브러리가 "g_pAKPluginList" 심볼을 내보내도록 합니다. 대부분의 플랫폼에서 내보내기가 가능하게끔 명시적으로 선언돼있기 때문에 이 심볼을 자동으로 내보내게 돼있습니다. 일부의 경우 명시적 DEF 파일이 필요할 수 있습니다. DEFINE_PLUGIN_REGISTER_HOOK 매크로를 이용해 심볼을 정의하세요.
  • 자신의 동적 라이브러리 이름을 XML 선언에 따라 만듭니다. XML에 EngineDllName 속성을 지정하지 않은 경우, XML과 동일한 이름을 사용하세요. 하나의 DLL에 여러 플러그인을 그룹으로 묶을 수 있습니다.

요약하자면, 자신의 정적 라이브러리를 동적 라이브러리로 변형할 때 필요한 단 하나의 코드는 다음과 같습니다.

자신의 플러그인을 Unity에 배포하려면, 동적 라이브러리의 복사본을 Wwise\Deployment\Plugins\[Platform]\DSP 폴더에 넣으세요. 이 폴더의 모든 DSP 플러그인은 게임 출시에 최적화된 환경 설정으로 돼있어야 합니다.

참고: iOS에서는 빌드 시스템 때문에 동적 라이브러리를 사용할 수 없습니다. 따라서 Unity에서 .a 파일과 해당 Factory.h 파일 두 개를 모두 배포해야 합니다. 다른 플랫폼에서의 동적 라이브러리와 iOS에서의 정적 라이브러리 사용은 이름을 통해 연결됩니다. 반드시 DLL 이름을 lib 이름 또는 부분 문자열과 동일하게 만드세요 ("lib" 접두어 제외). 예시:
  • Windows: MyPlugin.dll
  • iOS: libMyPlugin.a + MyPluginFactory.h
참고: Mac에서는 Wwise가 DYLIB 파일만 로드합니다. 그러나 Unity는 DYLIB를 유효한 확장자로 인식하지 못합니다. 따라서 이 파일들은 게임을 빌드할 때 복사/배포되지 않습니다. 이 문제를 해결하기 위해서는 파일이 실제 BUNDLE이 아니어도 확장자 이름을 BUNDLE로 변경합니다. 예시:
  • Windows: MyPlugin.dll
  • Mac (Unity 외 다른 플랫폼 게임): MyPlugin.dylib
  • Mac (Unity 게임): MyPlugin.bundle
참고: Android에서는 동적 라이브러리에 "lib" 접두어를 붙여야 합니다 (예: libMyPlugin.so).

오디오 플러그인의 메모리 할당 및 할당 해제하기

오디오 플러그인 내의 모든 동적 메모리 할당과 할당 해제는 제공된 메모리 할당자 인터페이스를 통해 처리해야 합니다. 그래야 플러그인이 사용하고 해제하는 모든 메모리를 Memory Manager에서 추적할 수 있으며, 특정 메모리 카테고리를 지정하고 Wwise 프로파일러에 표시할 수 있습니다.

new/delete 연산자를 오버로드하고 제공되는 메모리 할당자로 malloc()/free()를 호출하기 위한 매크로가 제공됩니다. 이 매크로는 IAkPluginMemAlloc.h에 있습니다.

오브젝트를 할당하려면, AK_PLUGIN_NEW() 매크로를 이용해 포인터를 메모리 할당자 인터페이스와 원하는 오브젝트 타입으로 보내세요. 매크로가 새로 할당된 오브젝트에 포인터를 반환합니다. 이에 해당하는 AK_PLUGIN_DELETE() 매크로가 메모리를 해제하는데 사용됩니다.

배열을 할당하기 위해서는 AK_PLUGIN_ALLOC()을 사용합니다. 이는 Memory Manager의 요청 크기(단위: 바이트)를 가져다가 할당된 메모리 주소로 void 포인터를 반환합니다. 해당 AK_PLUGIN_FREE() 매크로를 이용해 할당된 메모리를 해제합니다. 아래 예제 코드는 매크로를 어떻게 이용하는지 보여줍니다.

// 단일 CAkMyObject 할당
CAkMykObject * pSingleObject = AK_PLUGIN_NEW( in_pAllocator, CAkMyObject );
// pSingleObject 해제
AK_PLUGIN_DELETE( in_pAllocator, pSingleObject );
// uNumSamples 오디오 샘플의 배열 할당
AkSampleType * pData = static_cast<AkSampleType *>( AK_PLUGIN_ALLOC( in_pAllocator, sizeof(AkSampleType) * uNumSamples) );
// 오디오 샘플의 배열 비우기
AK_PLUGIN_FREE( in_pAllocator, pData );

매개 변수 노드 인터페이스 구현

매개 변수 노드는 기본적으로 매개 변수에 대한 읽기와 쓰기 접근을 한곳으로 집중시킵니다. 플러그인 매개 변수 인터페이스는 다음 메소드로 구성돼있습니다.

AK::IAkPluginParam::Clone()

이 메소드는 매개 변수 인스턴스의 복사본을 생성하고 필요한 내부 상태 변수를 조정해 새로운 플러그인 인스턴스에 사용될 수 있도록 준비합니다. 이런 상황은 예를 들어 이벤트가 새로운 재생 인스턴스를 생성할 때 발생합니다. 해당 함수는 AK_PLUGIN_NEW() 매크로를 이용해 새로운 매개 변수 노드 인스턴스를 반환해야 합니다. 많은 경우, 복사 생성자 호출 하나면 충분합니다 (아래 코드 예제 참고). 메모리가 매개 변수 노드 내에 할당된 경우에는, 깊은 복사(deep copy)가 구현돼야 합니다.

// 공유 매개 변수의 복제 생성
AK::IAkPluginParam * CAkMyPluginParams::Clone( AK::IAkPluginMemAlloc * in_pAllocator )
{
return AK_PLUGIN_NEW( in_pAllocator, CAkMyPluginParams(*this) );
}

AK::IAkPluginParam::Init()

이 함수는 제공된 매개 변수 블록으로 매개 변수를 초기화합니다. 제공된 매개 변수 블록 크기가 0일 경우 (즉, 플러그인을 저작 툴 안에서 사용), AK::IAkPluginParam::Init() 가 기본 설정값으로 매개 변수 구조체를 초기화해야합니다.

작은 정보: AK::IAkPluginParam::SetParamsBlock() 을 호출해 매개 변수 블록이 유효할 때 초기화하세요.
// 매개 변수 노드 초기화
AKRESULT CAkMyPluginParams::Init( AK::IAkPluginMemAlloc * in_pAllocator, void * in_pParamsBlock, AkUInt32 in_ulBlockSize )
{
if ( in_ulBlockSize == 0)
{
// 유효하지 않은 매개 변수 블록일 경우, 기본값으로 초기화
return AK_Success;
}
return SetParamsBlock( in_pParamsBlock, in_ulBlockSize );
}

AK::IAkPluginParam::SetParamsBlock()

메소드는 AK::Wwise::Plugin::AudioPlugin::GetBankParameters() 를 통해 저장된 매개 변수 블록을 이용하여 Wwise에서 뱅크 생성 도중 모든 플러그인 매개 변수를 한번에 설정합니다. 이 매개 변수는 플러그인의 Wwise 대응물이 bank에 작성한 것과 동일한 형식으로 읽히게 됩니다. 데이터는 패키지에 묶인 형태로, 일부 대상 플랫폼에 대해서는 요청한 데이터 타입과 변수가 일치하지 않을 수 있습니다. 이러한 서로 다른 플랫폼 문제를 피하기 위해 AkBankReadHelpers.h 에 제공된 READBANKDATA 헬퍼 매크로를 이용하세요. 애플리케이션에서 데이터가 올바르게 바이트 교환하기 때문에 플러그인 매개 변수의 엔디안 문제를 걱정할 필요는 없습니다.

// 매개 변수 블록을 읽고 파싱합니다.
AKRESULT CAkMyPluginParams::SetParamsBlock( void * in_pParamsBlock, AkUInt32 in_uBlockSize )
{
// 뱅크에 입력된 순서대로 데이터를 읽습니다.
AKRESULT eResult = AK_Success;
AkUInt8 * pParamsBlock = (AkUInt8 *)in_pParamsBlock;
m_fFloatParam1 = READBANKDATA( AkReal32, pParamsBlock, in_ulBlockSize );
m_bBoolParam2 = READBANKDATA( bool, pParamsBlock, in_ulBlockSize );
CHECKBANKDATASIZE( in_ulBlockSize, eResult );
return eResult;
}

AK::IAkPluginParam::SetParam()

이 메소드는 한 번에 하나의 매개 변수를 업데이트합니다. Plugin UI나 RTPC 등에서 매개 변수 값이 변할 때마다 호출됩니다. 업데이트할 매개 변수는 AkPluginParamID 타입의 전달인자에 의해 지정되며, Wwise XML 플러그인 설명 파일에 정의된 AudioEnginePropertyID에 해당됩니다. (더 많은 정보는 Wwise 플러그인 XML 설명 파일 for more information 을 참고하세요.)

작은 정보: XML 파일에 정의된 각 AudioEngineParameterID를 AkPluginParamsID 타입의 상수 변수에 묶을 것을 권장합니다.
참고: RTPC를 지원하는 매개 변수는 XML 파일에 지정된 속성 타입과 상관 없이 AkReal32가 할당됩니다.
// Wwise 또는 RTPC의 매개 변수 ID.
static const AkPluginParamID AK_MYFLOATPARAM1_ID = 0;
static const AkPluginParamID AK_MYBOOLPARAM2_ID = 1;
AKRESULT CAkMyPluginParams::SetParam( AkPluginParamID in_ParamID, void * in_pValue, AkUInt32 in_uParamSize )
{
// 매개 변수 값을 설정.
switch ( in_ParamID )
{
case AK_MYFLOATPARAM1_ID:
m_fFloatParam1.fParam2 = *(AkReal32*)( in_pValue );
break;
case AK_MYBOOLPARAM2_ID:
m_bBoolParam2 = *(bool*)( in_pValue ); // 매개 변수가 RTPC를 지원하지 않음.
or ...
// XML 플러그인 설명의 속성과 상관 없이 RTPC 매개 변수가 항상 float 타입임.
m_bBoolParam2 = (*(AkReal32*)(in_pValue)) != 0;
break;
default:
}
return AK_Success;
}
참고: complex property를 사용할 경우, AK::IAkPluginParam::SetParamAK::Wwise::Plugin::CustomData::GetPluginData 에 있는 AK::IAkPluginParam::ALL_PLUGIN_DATA_ID 를 처리해야합니다. 플러그인이 맨 처음 재생될 때 최소 한 번은 사용됩니다.

AK::IAkPluginParam::Term()

이 메소드는 매개 변수 노드가 종료됐을 때 사운드 엔진에 의해 호출됩니다. 사용된 모든 메모리 리소스는 해제되어야 하며, 매개 변수 노드 인스턴스가 자체 파기를 책임집니다.

// 공유 매개 변수 종료.
{
AK_PLUGIN_DELETE( in_pAllocator, this );
return AK_Success;
}

매개 변수 노드와 플러그인의 커뮤니케이션

각 플러그인에는 연관된 매개 변수 노드가 있으며, 여기서 매개 변수 값을 가져와 DSP를 업데이트합니다. 이 연관 매개 변수 노드 인터페이스는 플러그인 초기화 때 전달되어 플러그인의 수명 기간 내내 유효한 채로 유지됩니다. 그러면 DSP 처리가 필요할 때마다 플러그인이 매개 변수 노드의 정보를 쿼리합니다. 한 플러그인과 그에 연관된 매개 변수 노드의 관계는 일방향이기 때문에 매개 변수 노드로 매개 변수 값 쿼리에 응답하는 것은 구현하는 사용자가 신중히 결정해야 할 일입니다 (예: 접근자 이용).

오디오 플러그인 인터페이스 구현

오디오 플러그인을 개발하기 위해서는 특정 기능을 구현해야 엔진의 오디오 데이터 처리 과정에서 플러그인이 제대로 작동할 수 있습니다. 음원 플러그인의 경우 AK::IAkSourcePlugin 인터페이스로부터 상속받아야 하며, 입력 버퍼를 출력으로 대체할 수 있는 효과의 경우(예: 순서 없는 불규칙 접근 또는 데이터 속도 변경이 필요 없음) AK::IAkInPlaceEffectPlugin 으로부터 상속받아야 합니다. 제자리에 있지 않은 구현이 필요한 다른 효과의 경우에는 AK::IAkOutOfPlaceEffectPlugin 인터페이스로부터 상속받아야 합니다.

A plug-in life cycle always begins with a call to one of the AK::IAkAudioDeviceEffectPlugin::Init(), AK::IAkEffectPlugin::Init(), AK::IAkSinkPluginBase::Init() or AK::IAkSourcePlugin::Init() functions, immediately followed by a call to AK::IAkPlugin::Reset(). 플러그인이 더 많은 데이터를 출력해야 하는 한, AK::IAkPlugin::Execute() 가 새로운 버퍼로 호출됩니다. 플러그인이 더 이상 필요하지 않을 때는 AK::IAkPlugin::Term() 이 호출됩니다.

AK::IAkPlugin::Term()

이 메소드는 플러그인이 종료될 때 호출됩니다. AK::IAkPlugin::Term() 은 플러그인이 사용한 모든 메모리 리소스를 해제해야 하며 해당 플러그인 인스턴스를 자체 파기해야 합니다.

{
if ( m_pMyDelayLine != NULL )
{
AK_PLUGIN_FREE( in_pAllocator, m_pMyDelayLine );
m_pMyDelayLine = NULL;
}
AK_PLUGIN_DELETE( in_pAllocator, this );
return AK_Success;
}

AK::IAkPlugin::Reset()

재설정(reset) 메소드는 플러그인의 상태를 재초기화해 완전히 새로운 오디오 콘텐츠를 수용할 수 있도록 준비해야 합니다. 사운드 엔진 파이프라인은 초기화 직후 및 객체의 상태를 재설정해야 할 때마다 AK::IAkPlugin::Reset() 을 호출합니다. 일반적으로 모든 메모리 할당은 초기화 때 실행돼야 하지만, 딜레이 라인의 상태나 샘플 카운트같은 것들은 AK::IAkPlugin::Reset() 으로 삭제돼야 합니다.

// 플러그인의 내부 상태 재설정
{
// 딜레이 라인 재설정
if ( m_pMyDelayLine != NULL )
memset( m_pMyDelayLine, 0, m_uNumDelaySamples * sizeof(AkSampleType) );
return AK_Success;
}

AK::IAkPlugin::GetPluginInfo()

이 플러그인 정보 쿼리 방식은 사운드 엔진이 플러그인 정보가 필요할 때 사용됩니다. AkPluginInfo 구조체에 구현된 플러그인의 타입(예: 음원, 효과)과 버퍼 사용 방식(예: in place), 처리 모드(예: 동기식)를 설명하는 올바른 정보를 입력하세요.

참고: 효과 플러그인은 모든 플랫폼에서 동기식(synchronous)이어야 합니다.

// 사운드 엔진으로부터 효과 정보 쿼리.
AKRESULT CAkMyPlugin::GetPluginInfo( AkPluginInfo & out_rPluginInfo )
{
out_rPluginInfo.eType = AkPluginTypeSource; // 음원 플러그인.
out_rPluginInfo.bIsInPlace = true; // In-place 플러그인.
out_rPluginInfo.bIsAsynchronous = false; // 동기식 플러그인.
return AK_Success;
}

AkAudioBuffer 구조체를 이용해 데이터에 액세스하기

오디오 데이터 버퍼는 실행시 AkAudioBuffer 구조체를 가리키는 포인터를 통해 플러그인으로 전달됩니다. 모든 오디오 버퍼는 고정 형식을 사용하는 플러그인으로 전달됩니다. 소프트웨어적으로 효과를 지원하는 플랫폼의 경우, 오디오 버퍼의 채널이 인터리브되지 않습니다. 그리고 범위(-1.f,1.f) 내의 모든 샘플은 48 kHz 샘플 레이트에서 실행되는 정규화된 32비트 부동 소수점입니다.

AkAudioBuffer 구조체는 인터리브 데이터와 비인터리브 데이터 모두에 접근할 수 있는 수단을 제공합니다. 여기에는 각 채널 버퍼에서 유효한 샘플 프레임의 개수(AkAudioBuffer::uValidFrames)와 이 버퍼들에 담을 있는 최대 샘플 프레임 개수를 지정하는 입력란이 있습니다 (AkAudioBuffer::MaxFrames()로 반환).

AkAudioBuffer 구조체에는 또한 버퍼의 채널 마스크가 들어있으며, 이를 통해 데이터 안에 존재하는 채널을 정의합니다. 단순히 채널 개수만 필요한 경우 AkAudioBuffer::NumChannels() 를 사용합니다.

인터리브 데이터 구하기

플러그인은 AkAudioBuffer::GetInterleavedData() 를 통해 인터리브 데이터의 버퍼에 접근할 수 있습니다. 오직 음원 플러그인만 인터리브 데이터에 접근하고 이를 출력해야 합니다. 이를 위해서는 초기화할 때 사운드 엔진을 올바르게 준비시켜야 합니다 ( AK::IAkSourcePlugin::Init() 참고). 사운드 엔진은 데이터를 원시 파이프라인 형식으로 올바르게 변환하기 위해 처리된 DSP를 인스턴스화 합니다.

작은 정보: 음원 플러그인이 이미 사운드 엔진의 네이티브 형식에 있는 데이터를 출력하면 더 나은 결과를 얻을 수 있습니다.

비인터리브(deinterleaved) 데이터 구하기

플러그인은 AkAudioBuffer::GetChannel() 를 통해 각각의 비인터리브 채널에 개별적으로 접근합니다. 데이터 타입은 항상 사운드 엔진의 네이티브 형식(AkSampleType)을 따릅니다. 아래의 예제 코드는 처리에 사용되는 모든 비인터리브 채널을 어떻게 구하는지 보여줍니다.

// 모든 채널을 개별적으로 처리.
void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer ) // 입력/출력 버퍼 (여기서 처리가 완료됨).
{
AkUInt32 uNumChannels = io_pBuffer->NumChannels();
for ( AkUInt32 uChan=0; uChan<uNumChannels; uChan++ )
{
AkSampleType * pChannel = io_pBuffer->GetChannel( uChan );
// pChannel의 데이터 처리...
}
}
경고: 플러그인은 각 채널의 버퍼가 항상 메모리에서 연속적이라고 가정해서는 안됩니다.

채널 순서 배치

오디오를 처리하는 채널은 Front Left, Front Right, Center, Rear Left, Rear Right, LFE 순으로 정렬됩니다. Low-Frequency 채널(LFE)은 항상 마지막에 배치됩니다 (음원 플러그인 제외). LFE는 많은 DSP 처리에 사용되기 때문에 별개로 다룰 수 있습니다. 플러그인은 AkAudioBuffer::HasLFE() 로 오디오 버퍼에 LFE 채널이 존재할 경우 쿼리를 할 수 있습니다. 플러그인은 AkAudioBuffer::GetLFE() 를 호출해 LFE 채널에 직접 접근할 수 있습니다. 다음 코드는 LFE 채널을 따로따로 다루는 두 가지 방법을 보여줍니다.

void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer ) // 입력/출력 버퍼 (여기서 처리가 완료됨).
{
// GetLFE()로 LFE 채널을 구함.
AkSampleType * pLFE = io_pBuffer->GetLFE();
if ( pLFE )
{
// pLFE 데이터 처리...
}
또는...
// GetChannel()로 LFE 채널 구하기.
if ( io_pBuffer->HasLFE() )
{
AkSampleType * pLFE = io_pBuffer->GetChannel( io_pBuffer->NumChannels() - 1 );
// pLFE 데이터 처리...
}
}

LFE가 아닌 채널에 특정 처리를 적용하려면 AkCommonDefs.h 의 채널 인덱스 정의를 사용해야 합니다. 예를 들어 5.x 중앙 채널만 처리하고자 할 경우 다음을 실행합니다.

// 5.x 중앙 채널만 처리.
void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer ) // 입력/출력 버퍼 (여기서 처리가 완료됨).
{
// 특정 채널 구성에 대해 쿼리.
if ( io_pBuffer->GetChannelMask() == AK_SPEAKER_SETUP_5 || io_pBuffer->GetChannelMask() == AK_SPEAKER_SETUP_5POINT1 )
{
// 올바른 채널 구성에 대해 정의된 인덱스를 이용해 채널에 접근.
AkSampleType * pCenter = io_pBuffer->GetChannel( AK_IDX_SETUP_5_CENTER );
// pCenter 데이터 처리...
}
}
작은 정보: 채널 인덱스 정의는 구성이 N.0인지 N.1인지에 관계 없이 독립적입니다. 음원 플러그인을 제외한 모든 LFE 채널은 항상 마지막에 오기 때문입니다 (채널 구성에 LFE가 없는 경우에는 AK_IDX_SETUP_N_LFE를 사용해서는 안됩니다).
참고: 7.1 구성의 경우, 음원 플러그인의 인터리브 데이터 채널 순서는 L-R-C-LFE-BL-BR-SL-SR이 됩니다.

플러그인에서 전역적 사운드 엔진 콜백 사용하기

플러그인은 AK::IAkGlobalPluginContext::RegisterGlobalCallback() 을 사용해 다양한 전역적 사운드 엔진 콜백에 등록할 수 있습니다. 예를 들어 플러그인 클래스는 플러그인 인스턴스가 인지하고 있는 싱글톤을 통해 오디오 프레임당 한 번 호출해야 할 수 있습니다. 또한 전역 후크를 이용해 사운드 엔진의 전체 실행 시간동안 지속되는 데이터 구조체를 초기화할 수도 있습니다.

이를 위해서는 기본 AK_IMPLEMENT_PLUGIN_FACTORY 매크로를 자신의 구현으로 대체하여 AK::PluginRegistration의 "RegisterCallback"을 활용합니다. 아래 코드에 보면, 이러한 용도로 사용되는 MyRegisterCallback이라는 정적 콜백이 정의돼있으며 AK::PluginRegistration 객체 MyPluginRegistration으로 전달됩니다.

참고: AK_IMPLEMENT_PLUGIN_FACTORY 매크로는, 인자로서 전달된 플러그인 이름에 문자열을 붙여 팩토리 함수와 AK::PluginRegistration 객체를 선언합니다. 아래 예제를 보면 AK_IMPLEMENT_PLUGIN_FACTORY 가 같은 명명 규칙을 따라 재구현된 것을 알 수 있습니다. 자신의 플러그인에서는 "MyPlugin"에 실제 플러그인 이름을 넣어야 합니다. 그뿐만 아니라 AK_IMPLEMENT_PLUGIN_FACTORY 와 긴밀한 매크로인 AK_STATIC_LINK_PLUGIN 도 동일한 명명 규칙을 따릅니다. AK_IMPLEMENT_PLUGIN_FACTORY 의 명명 규칙에서 벗어나는 경우 AK_STATIC_LINK_PLUGIN 도 함께 재구현해야 합니다.
경고: AK::IAkGlobalPluginContext::RegisterGlobalCallback() 은 오직 AkGlobalCallbackLocation::AkGlobalCallbackLocation_Register 를 받을 때에만 플러그인 등록 콜백 내에서 호출해야 하며, AK::IAkGlobalPluginContext::UnregisterGlobalCallback() AkGlobalCallbackLocation::AkGlobalCallbackLocation_Term 을 받을 때만 허용됩니다. 아래 예제를 참고하세요. 더 구체적으로 말하면, 플러그인의 병렬 처리시 교착 상태(deadlock)를 방지하기 위해 이 함수를 플러그인 인스턴스 내(예: Init, Execute, 등)에서 호출해서는 안 됩니다.
// MyPluginManager의 초기화 및 종료를 위한 전역 콜백 (AkGlobalCallbackFunc).
static void MyRegisterCallback(
AK::IAkGlobalPluginContext * in_pContext, ///< 엔진 콘텍스트.
AkGlobalCallbackLocation in_eLocation, ///< 이 콜백이 호출되는 위치.
void * in_pCookie ///< AK::SoundEngine::RegisterGlobalCallback() 로 전달된 사용자 쿠키.
)
{
if (in_eLocation == AkGlobalCallbackLocation_Register)
{
// 등록 시간: 사운드 엔진이 초기화될 때, 또는 다이내믹 플러그인 lib가 로드될 때 호출됨.
// 싱글톤 생성. 사운드 엔진의 전체 실행 시간을 아울러 할당하기 위해 전역 콘텍스트가 제공하는 할당자를 이용.
MyPluginManager * pMyPluginManager = AK_PLUGIN_NEW(in_pContext->GetAllocator(), MyPluginManager);
if (pMyPluginManager)
{
// 성공. 관리자를 종료하기 위해 "Term" 콜백에 등록.
AKRESULT eResult = in_pContext->RegisterGlobalCallback(
MY_COMPANY_ID,
MY_PLUGIN_ID,
MyRegisterCallback,
pMyPluginManager
);
// 등록 실패 처리.
if (eResult != AK_Success)
{
// ...
}
}
}
else if (in_eLocation == AkGlobalCallbackLocation_Term)
{
// 종료 시간: 사운드 엔진이 종료될 때 호출됨.
// 쿠키는 위에서 등록한 MyPluginManager의 인스턴스임.
AKASSERT(in_pCookie);
// 파기.
AK_PLUGIN_DELETE(in_pContext->GetAllocator(), (MyPluginManager*)in_pCookie);
}
}
// AK_IMPLEMENT_PLUGIN_FACTORY(MyPlugin, AkPluginTypeEffect, MY_COMPANY_ID, MY_PLUGIN_ID) 대체.
// 여기서 "MyPlugin"을 자신의 플러그인 이름으로 대체.
AK::IAkPlugin* CreateMyPlugin(AK::IAkPluginMemAlloc * in_pAllocator);
AK::IAkPluginParam * CreateMyPluginParams(AK::IAkPluginMemAlloc * in_pAllocator);
AK::PluginRegistration MyPluginRegistration(AkPluginTypeEffect, MY_COMPANY_ID, MY_PLUGIN_ID, CreateMyPlugin, CreateMyPluginParams, MyRegisterCallback, NULL);

더 자세한 정보는 다음 섹션을 참조하세요.

AkForceInline bool HasLFE() const
Returns true if there is an LFE channel present.
Definition: AkCommonDefs.h:497
AkForceInline void AK_PLUGIN_DELETE(AK::IAkPluginMemAlloc *in_pAllocator, T *in_pObject)
AkSampleType * GetChannel(AkUInt32 in_uIndex)
Definition: AkCommonDefs.h:575
#define AK_STATIC_LINK_PLUGIN(_pluginName_)
Definition: IAkPlugin.h:1886
@ AkGlobalCallbackLocation_Register
Right after successful registration of callback/plugin. Typically used by plugins along with AkGlobal...
Definition: AkCallback.h:336
@ AkGlobalCallbackLocation_Term
Sound engine termination.
Definition: AkCallback.h:347
AkForceInline AkUInt32 NumChannels() const
Get the number of channels.
Definition: AkCommonDefs.h:491
#define CHECKBANKDATASIZE(_DATASIZE_, _ERESULT_)
Helper macro to determine whether the full content of a block of memory was properly parsed
AKRESULT
Standard function call result.
Definition: AkTypes.h:213
bool bIsInPlace
Buffer usage (in-place or not). If true, and the plug-in is an insert effect, it should implement IAk...
Definition: IAkPlugin.h:77
@ AkPluginTypeEffect
Effect plug-in: applies processing to audio data.
Definition: AkTypes.h:1253
uint8_t AkUInt8
Unsigned 8-bit integer
#define AK_SPEAKER_SETUP_5
5.0 setup channel mask
AKSOUNDENGINE_API AKRESULT Reset()
AKSOUNDENGINE_API AKRESULT Init(const AkCommSettings &in_settings)
#define AK_PLUGIN_NEW(_allocator, _what)
#define NULL
Definition: AkTypes.h:46
#define AK_IMPLEMENT_PLUGIN_FACTORY(_pluginName_, _plugintype_, _companyid_, _pluginid_)
Definition: IAkPlugin.h:1881
float AkReal32
32-bit floating point
@ AK_Success
The operation was successful.
Definition: AkTypes.h:215
#define AK_IDX_SETUP_5_CENTER
Index of center channel in 5.x setups (use with AkAudioBuffer::GetChannel())
AKSOUNDENGINE_API void Term()
@ AK_InvalidParameter
Something is not within bounds, check the documentation of the function returning this code.
Definition: AkTypes.h:231
#define DEFINE_PLUGIN_REGISTER_HOOK
Definition: IAkPlugin.h:1890
#define AK_PLUGIN_FREE(_allocator, _pvmem)
#define AKASSERT(Condition)
Definition: AkAssert.h:67
AkPluginType eType
Plug-in type
Definition: IAkPlugin.h:75
AkReal32 AkSampleType
Audio sample data type (32 bit floating point)
Definition: AkCommonDefs.h:457
AkGlobalCallbackLocation
Bit field of various locations in the audio processing loop where the game can be called back.
Definition: AkCallback.h:335
#define AK_PLUGIN_ALLOC(_allocator, _size)
#define AK_SPEAKER_SETUP_5POINT1
5.1 setup channel mask
#define READBANKDATA(_Type, _Ptr, _Size)
Read and return bank data of a given type, incrementing running pointer and decrementing block size f...
@ AkPluginTypeSource
Source plug-in: creates sound by synthesis method (no input, just output).
Definition: AkTypes.h:1252
uint32_t AkUInt32
Unsigned 32-bit integer
AkInt16 AkPluginParamID
Source or effect plug-in parameter ID
Definition: AkTypes.h:148
virtual AK::IAkPluginMemAlloc * GetAllocator()=0
Get the default allocator for plugins. This is useful for performing global initialization tasks shar...
AkSampleType * GetLFE()
Definition: AkCommonDefs.h:587
virtual AKRESULT RegisterGlobalCallback(AkPluginType in_eType, AkUInt32 in_ulCompanyID, AkUInt32 in_ulPluginID, AkGlobalCallbackFunc in_pCallback, AkUInt32 in_eLocation=AkGlobalCallbackLocation_BeginRender, void *in_pCookie=NULL)=0

이 페이지가 도움이 되었나요?

지원이 필요하신가요?

질문이 있으신가요? 문제를 겪고 계신가요? 더 많은 정보가 필요하신가요? 저희에게 문의해주시면 도와드리겠습니다!

지원 페이지를 방문해 주세요

작업하는 프로젝트에 대해 알려주세요. 언제든지 도와드릴 준비가 되어 있습니다.

프로젝트를 등록하세요. 아무런 조건이나 의무 사항 없이 빠른 시작을 도와드리겠습니다.

Wwise를 시작해 보세요