IAudioLinkFactory 정의 및 등록 부분을 갖춘 코드 몇 줄만으로도 기본 오디오 링크 구현을 생성할 수 있습니다. 하지만 다른 API 타입을 사용하면 구현을 빌드하는 데 도움을 받을 수 있습니다.
아래에는 각 핵심 오디오 링크 C++ 타입의 요약과 코드 예시가 정리되어 있습니다.
오디오 링크 소스 코드는 Engine\Source\Runtime\AudioLink\ 에서 찾을 수 있습니다.
IAudioLinkFactory
모든 오디오 링크 구현에는 IAudioLinkFactory 에서 파생된 팩토리 클래스가 필요합니다. 이 클래스에는 구현의 엔트리 포인트를 정의하는 몇 가지 순수 가상 함수가 있습니다.
null이 반환되면 어서트가 발생하므로 각 함수를 구현해야 합니다. 언리얼 엔진은 효율적인 반환을 위해 디스패치된 모든 함수가 스레드 세이프일 것으로 예상하며, 이상적으로는 잠금이 없어야 합니다.
다수의 개별 파라미터 대신 파라미터 구조체를 사용하면 이러한 함수 호출의 익스텐션을 간소화할 수 있습니다.
팩토리 오브젝트는 하나만 등록할 수 있습니다. 둘 이상의 팩토리가 등록된 경우 스타트업 시 치명적인 오류가 어서트됩니다.
플러그인 구현
오디오 링크는 IModularFeature 인터페이스를 사용하여 언리얼 엔진을 확장합니다. IAudioLinkFactory 생성자가 등록을 처리하므로 팩토리의 인스턴스를 생성하는 것으로 이를 인식시키는 데 충분합니다. 이것은 구현의 StartupModule() 부분에서 처리하면 됩니다.
언리얼 엔진에서 플러그인을 구현하는 방법에는 여러 가지가 있지만, IModularFeature 인터페이스가 오디오 익스텐션에서는 가장 일반적입니다.
예시: 서브믹스 오디오 링크 구현
// 서브믹스 오디오 링크 생성 시 파라미터에서 사용합니다.
struct FAudioLinkSubmixCreateArgs
{
TWeakObjectPtr<const USoundSubmix> Submix;
FAudioDevice* Device = nullptr;
TWeakObjectPtr<const UAudioLinkSettingsAbstract> Settings;
};
// 서브믹스 오디오 링크를 생성합니다.
// @param InCreateArgs 오디오 링크 인스턴스를 생성하는 데 사용되는 실행인자입니다.
// @return 성공한 경우, 새로 생성된 링크 인스턴스를 반환합니다.
virtual TUniquePtr<IAudioLink> CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs& InCreateArgs) = 0;
/**
* 서브믹스 오디오 링크 생성 시 파라미터에서 사용합니다.
*/
struct FAudioLinkSubmixCreateArgs
{
TWeakObjectPtr<const USoundSubmix> Submix;
FAudioDevice* Device = nullptr;
TWeakObjectPtr<const UAudioLinkSettingsAbstract> Settings;
};
/**
* 서브믹스 오디오 링크를 생성합니다.
* @param InCreateArgs 오디오 링크 인스턴스를 생성하는 데 사용되는 실행인자입니다.
* @return 성공한 경우, 새로 생성된 링크 인스턴스를 반환합니다.
*/
virtual TUniquePtr<IAudioLink> CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs& InCreateArgs) = 0;
예시: 팩토리 구현
class FAudioLinkExampleFactory : public IAudioLinkFactory
{
public:
FAudioLinkExampleFactory() = default;
virtual ~FAudioLinkExampleFactory() = default;
static FName GetFactoryNameStatic();
protected:
/** IAudioLinkFactory 시작 */
FName GetFactoryName() const override;
TSubclassOf<UAudioLinkSettingsAbstract> GetSettingsClass() const override;
TUniquePtr<IAudioLink> CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs&) override;
TUniquePtr<IAudioLink> CreateSourceAudioLink(const FAudioLinkSourceCreateArgs&) override;
FAudioLinkSourcePushedSharedPtr CreateSourcePushedAudioLink(const FAudioLinkSourcePushedCreateArgs&) override;
FAudioLinkSynchronizerSharedPtr CreateSynchronizerAudioLink() override;
/** IAudioLinkFactory 끝 */
};
UAudioLinkSettingsAbstract
연관된 데이터를 사용하여 링크를 생성하려면 모든 오디오 링크 구현에 UAudioLinkSettingsAbstract 에서 파생된 세팅 클래스가 필요합니다. 예를 들어 버퍼 크기, 핸드셰이크, UAsset 레퍼런스 등입니다.
예시: 세팅 구현
UCLASS(config = Engine, defaultconfig)
class AUDIOLINKEXAMPLERUNTIME_API UAudioLinkSettingsExample : public UAudioLinkSettingsAbstract
{
GENERATED_BODY()
UPROPERTY(Config, EditAnywhere, Category = "Example|AudioLink")
float MyBufferSize = 1.0f;
};
class FAudioLinkSettingsProxyExample : public IAudioLinkSettingsProxy
{
public:
FAudioLinkSettingsProxyExample(const UAudioLinkSettingsExample&);
virtual ~FAudioLinkSettingsProxyExample() = default;
float GetMyBufferSize() const { return MyBufferSize; }
private:
#if WITH_EDITOR
void RefreshFromSettings(UAudioLinkSettingsAbstract* InSettings, FPropertyChangedEvent& InPropertyChangedEvent) override
{
MyBufferSize = CastChecked<UAudioLinkSettingsExample>(InSettings)->MyBufferSize;
}
#endif //WITH_EDITOR
float MyBufferSize = 0;
};
IAudioLinkSettingsProxy
오디오 링크 세팅은 프록시 디자인 패턴을 따르므로 오디오가 일반적으로 실행되는 게임 스레드 밖에서도 안전하게 존재할 수 있습니다. 세팅의 스레드 세이프 사본이 생성되고 세팅의 UObject 에 공유 포인터로 어태치됩니다.
이 접근법에는 두 가지 주요 장점이 있습니다.
- 아직 공유 포인터가 있는 스레드는 계속해서 안전하게 작업할 수 있으므로 가비지 컬렉션 보호를 제공합니다.
PostEditChangedProperty는 에디터에서 변경사항이 발생하면 자동으로 데이터를 프록시로 전송하여 세팅이 표준 에셋처럼 행동할 수 있게 합니다.
모든 오디오 링크 세팅 에셋은 해당 프록시를 구현하고 소유 에셋이 RefreshFromSettings 함수를 통해 변경될 때 새로고침을 처리해야 합니다.
또한 DefaultEngine.ini 에서 시리얼라이즈되는 프로퍼티의 디폴트 세팅을 구현해야 합니다. 세팅 구현 예시에서 이는 세팅 오브젝트의 defaultconfig 마크업을 통해 아카이브 보관됩니다.
세팅 디폴트는 프로퍼티가 설정되지 않은 경우에 사용되므로 오디오 링크를 생성할 때마다 전달할 필요가 없습니다.
UFactory 구현
언리얼 엔진은 오디오 링크 세팅을 에셋으로 시리얼라이즈합니다. 그 결과, 사용자가 세팅 클래스를 위한 에셋 팩토리 구현을 담당하게 되므로 에셋을 생성할 수 있습니다.
예시: 표준 세팅 팩토리 구현
class FAssetTypeActions_AudioLinkExampleSettings : public FAssetTypeActions_Base
{
public:
virtual FText GetName() const override;
virtual FColor GetTypeColor() const override;
virtual const TArray<FText>& GetSubMenus() const override;
virtual UClass* GetSupportedClass() const override;
virtual uint32 GetCategories() override;
};
UCLASS(hidecategories = Object, MinimalAPI)
class UAudioLinkExampleSettingsFactory : public UFactory
{
GENERATED_UCLASS_BODY()
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual uint32 GetMenuCategories() const override;
};
IAudioLink
IAudioLink 는 IAudioLinkFactory 에서 사용되는 프라이머리 추상화이며, 일반적으로 TUniquePtr 에 의해 반환됩니다. 플러그인별 구현의 숨겨진 디테일을 포함하도록 디자인된 불투명 타입이며, 컨슈머 및 프로듀서 양쪽 모두에서 스레드 세이프 공유 포인터에 있습니다. 컨슈머 오브젝트는 프로듀서에 대해 약한 레퍼런스를 유지하며 프로듀서가 삭제될 때 안전하게 연결을 종료합니다. 이는 보통 수명 만료로 링크가 끝날 때 발생합니다.
예시: 인스턴스 구현
//* 오디오 링크 인스턴스로, 수명 관리를 위한 공유 포인터를 갖는 컨테이너입니다. */
struct FExampleLink : IAudioLink
{
// 새 버퍼를 수신하는 원형 버퍼(서브믹스/소스)입니다.
FSharedBufferedOutputPtr ProducerSP;
// 예시 클라이언트입니다.
FSharedExampleAudioInputClientPtr ConsumerSP;
// ...
};
IAudioMixerPlatformInterface
오디오 링크 팩토리를 언리얼 엔진에 등록할 때 오디오 링크 플랫폼 믹서가 인스턴스화되어 플랫폼의 하드웨어 대신 오디오 링크에 후킹됩니다. 이전까지는 이를 방지하기 위해 서드 파티 라이브러리에서 .ini 파일을 수정해야 했지만 오디오 링크는 이 과정을 간소화합니다.
IBufferedAudioOutput
대부분의 오디오 링크 인스턴스는 IBufferedAudioOutput 에서 파생된 클래스를 사용하여 프로듀서 오브젝트를 생성합니다. 내부적으로 이는 컨슈머 오브젝트에 의해 소모되기를 기다리는 펄스 코드 변조(Pulse-Code Modulation, PCM) 데이터의 원형 버퍼로 기능합니다.
원형 버퍼는 언리얼 엔진 바운더리에서 PCM 데이터를 버퍼링하는 선입선출(First in, First out, FIFO) 데이터 구조입니다. 오디오 링크의 경우 아토믹이지만 잠금 없음이 아닙니다.
이 인터페이스의 구현은 이미 소스 코드(FBufferedSourceListener 및 FBufferedSubmixListener)에서 정의되어 있으므로 이러한 타입의 자체 파생 버전을 빌드할 필요는 없는 경우가 많습니다.
종종 수신 오디오의 포맷이 인식되기 전에 링크가 생성됩니다. 그 결과, 포맷이 알려진 뒤에 OnFormatKnown 델리게이트가 실행됩니다. 이 경우, 델리게이트가 실행될 때 델리게이트를 설정하고 재생을 시작해야 합니다.
이 오브젝트에 얼마나 많은 공간을 할당할지 주의 깊게 고려해야 합니다. 크기가 너무 크면 지연시간이 발생할 수 있고, 너무 작으면 버퍼 언더런으로 이어질 수 있습니다. 일반적으로 컨슈머 비트레이트 비율이 2:1 이상인 크기를 사용해야 합니다.
링크 인스턴스의 수명
링크 인스턴스의 수명은 타입에 따라 다릅니다.
- 소스 링크는 일반적으로 소스 재생의 수명 동안 유지됩니다. 소스 재생의 수명이 끝나면 해당 소스가 삭제되고 링크는 끊어집니다. 이렇게 끊어지는 주된 이유는 소스 포맷이 모든 새 소스에서 달라질 수 있기 때문입니다.
- 서브믹스 링크는 서브믹스가 실행되는 동안 열려 있으며, 이는 보통 애플리케이션의 런타임입니다. 이는 특히 에디터에서 인스턴스 수명에 의해 발생하는 문제로 이어질 수 있습니다. 자세한 내용은 문제 해결을 참고하세요.
IAudioLinkSynchronizer
모든 오디오 링크 구현은 IAudioLinkSynchronizer 에서 파생된 싱크로나이저 클래스가 필요합니다. 이는 다양한 콜백을 설정하기 위한 호출 등록 및 제거를 포함합니다. 이 클래스는 다른 클럭 소스에 동기화되므로 언리얼 엔진과 외부 애플리케이션이 동기화 상태를 유지할 수 있게 해줍니다.
각 콜백에 대해 스레드 세이프 델리게이트를 제공해야 합니다.
SuspendResumeOpenStreamCloseStreamBeginRenderEndRender
OpenStream 을 사용하여 언리얼 엔진에 외부 오디오 링크 팩토리의 포맷을 알립니다. 이 알림은 블록/샘플 레이트와 채널 수를 일치시키는 데 가장 유용합니다.
BeginRender 및 EndRender 를 사용하여 언리얼 엔진을 외부 렌더러와 동기화합니다.
예시: 싱크로나이저 구현
struct FExampleSynchronizerAudioLink : IAudioLinkSynchronizer
{
IAudioLinkSynchronizer::FOnSuspend OnSuspend;
IAudioLinkSynchronizer::FOnResume OnResume;
IAudioLinkSynchronizer::FOnOpenStream OnOpenStream;
IAudioLinkSynchronizer::FOnCloseStream OnCloseStream;
IAudioLinkSynchronizer::FOnBeginRender OnBeginRender;
IAudioLinkSynchronizer::FOnEndRender OnEndRender;
FRWLock RwLock;
// ...
#define MAKE_DELEGATE_FUNC(X)\
FDelegateHandle Register##X##Delegate(const FOn##X::FDelegate& InDelegate) override\
{\
FWriteScopeLock WriteLock(RwLock);\
return On##X.Add(InDelegate);\
}\
bool Remove##X##Delegate(const FDelegateHandle& InHandle) override\
{\
FWriteScopeLock WriteLock(RwLock);\
return On##X.Remove(InHandle);\
}
MAKE_DELEGATE_FUNC(Suspend)
MAKE_DELEGATE_FUNC(Resume)
MAKE_DELEGATE_FUNC(OpenStream)
MAKE_DELEGATE_FUNC(CloseStream)
MAKE_DELEGATE_FUNC(BeginRender)
MAKE_DELEGATE_FUNC(EndRender)
#undef MAKE_DELEGATE_FUNC
};