Wwise SDK 2023.1.9
|
Default Streaming Manager Information Low-Level I/O는 상위 레벨 Stream Manager API 기본 구현의 하위 모듈로서, 상위 레벨 Stream Manager API보다 훨씬 더 간단히 구현되는 I/O 전송 인터페이스를 제공합니다. 따라서 Low-Level I/O는 오직 Stream Manager의 기본 구현만 관련이 있습니다. |
Low-Level I/O 시스템은 Audiokinetic의 Stream Manager 구현에 특화돼있습니다. 해당 인터페이스는 <Wwise Installation>/SDK/include/AK/SoundEngine/AkStreamMgrModule.h에 정의돼있습니다.
Low-Level I/O 시스템에는 두 가지 용도가 있습니다.
AK::StreamMgr::IAkFileLocationResolver 를 구현하는 유일한 오브젝트는 File Location Resolver라고 불리며, Stream Manager에 등록돼야 합니다 (AK::StreamMgr::SetFileLocationResolver()
사용). 표준 또는 자동 스트림을 열기 위해서는 Stream Manager가 AK::StreamMgr::IAkFileLocationResolver::GetNextPreferredDevice()
를 호출해 어느 스트리밍 장치에 파일이 있는지를 찾습니다. 스트리밍 장치는 AK::StreamMgr::CreateDevice()
를 이용해 Stream Manager에 생성되고 등록됩니다.
각 스트리밍 장치에 대해 하위 레벨 I/O 연결이 제공돼야 합니다. 이 I/O 후크는 AK::StreamMgr::IAkLowLevelIOHook
를 구현하고 플랫폼의 I/O API와의 통신을 담당하며 물리적 장치의 고유한 속성과 동작을 처리합니다. 스트리밍 장치가 I/O 전송을 수행해야할 때마다 하위-레벨 I/O 후크의 AK::StreamMgr::IAkLowLevelIOHook::BatchRead()
또는 AK::StreamMgr::IAkLowLevelIOHook::BatchWrite()
메소드를 호출합니다. 플랫폼의 I/O API에 대한 직접 호출은 High-Level Stream Manager에서 실행되지 않습니다. File Location Resolver와 I/O 후크는 함께 Low-Level I/O 시스템을 구성합니다.
다음 표는 Low-Level I/O 시스템 인터페이스와 이들이 Stream Manager에 어떻게 보이는지를 나타냅니다.
게임 타이틀은 파일 위치를 결정하고 실제 I/O 전송을 수행하기 위해 Low-Level I/O 인터페이스를 구현해야 합니다. Wwise 사운드 엔진의 I/O 관리를 자신의 게임에 통합하는 가장 쉽고 빠른 방법은, Stream Manager의 기본 설정 구현을 이용하고 Low-Level I/O 시스템을 구현하는 것입니다. 여기서부터 시작해 네이티브 파일 읽기를 수행하거나, I/O 요청을 자신의 I/O 관리자로 라우팅하면 됩니다.
Wwise SDK에는 Low-Level I/O의 기본 구현이 포함돼있습니다. 제공된느 그대로 사용하거나, 자신의 구현을 시작하는 밑그림으로 사용해도 됩니다. Low-Level I/O 예제와 관련해 더 자세한 개념 설명은
샘플 기본 구현 설명 섹션을 참고하세요.
File Location Resolver는 AK::StreamMgr::SetFileLocationResolver()
를 이용해 Stream Manager에 등록돼야 합니다. Stream Manager는 스트림 오브젝트를 생성할 때마다 해당 리졸버(resolver)의 GetNextPreferredDevice() 메소드를 호출하고 파일을 여는 데 사용할 I/O 장치를 식별하는 AkDeviceID 를 반환합니다. 이 함수의 목적은 디스크 조작이나 기타 물리적인 확인을 수행하지 않고 파일 이름, 플래그, 언어 등만으로 파일 위치를 찾는 것입니다. 실제 Open 호출은 디스크 작업을 수행하기 위해 지정된 장치로 전달됩니다. 따라서 이 함수는 빠르게 반환됩니다. Device가 파일을 찾거나 열지 못하면 GetNextPreferredDevice가 다시 호출됩니다. 다른 장치가 없거나 파일을 다른 위치에서 찾을 가능성이 없는 경우 AK_FileNotFound를 반환하여 검색 종료를 알려야 합니다.
같은 개수의 파일이 Stream Manager 에서 스트림 오브젝트로 Low-Level I/O에 동시에 존재하고 있습니다.
게임은 AK::StreamMgr::CreateDevice()
를 이용해 Stream Manager에 최소 하나의 스트리밍 장치를 생성해야 합니다. 반환된 AkDeviceID 는 호출자가 보관해야 하며 위에서 설명한 대로 AK::StreamMgr::GetNextPreferredDevice
에 반환되어야 합니다. 그러나 필요한 만큼 여러 개의 장치를 생성할 수 있습니다. 각 스트리밍 장치는 각자의 스레드에서 실행되며, I/O 요청을 각자의 I/O 연결로 전송합니다. 일반적으로 한 물리적 장치당 하나의 스트리밍 장치를 생성해야 합니다. AkFileDesc 구조체에 deviceID라는 영역이 있습니다. File Location Resolver는 생성된 스트리밍 장치 중 하나의 deviceID로 설정해야 합니다. 이는 파일 처리를 적절한 장치로 어떻게 전달하는지 나타냅니다. 또한, 파일의 크기와 오프셋(uSector
)이 반환돼야 하며, 시스템 파일 핸들이 생성돼야 합니다 (꼭 필요하지 않은 경우에도 해당됨).
Stream Manager의 클라이언트는 문자열(const AkOSChar *)이나 ID (AkFileID, 정수), 또는 파일 설명자(AkFileDesc)를 이용해 파일을 식별합니다. 그렇기 때문에 Stream Manager의 스트림 생성 메소드(AK::IAkStreamMgr::CreateStd()
, AK::IAkStreamMgr::CreateAuto()
)는 가능한 파일 식별 메소드 둘을 그룹화하는 구조체 호출 AkFileOpenData 를 사용합니다. 파일이 열리면 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
메소드는 유효한 파일 설명자를 생성해야 합니다.
이 파일 설명자는 하위-레벨 I/O 후크의 메소드를 호출할 때마다 다시 전달됩니다. 해당 구조체의 세 가지 멤버는 High-Level Stream Manager가 사용합니다.
AK::CreateDevice()
호출로 얻어진 유효한 장치 ID여야 합니다. 올바른 상위 레벨 장치에 파일을 연결하기 위해 Stream Manager가 사용합니다. 연결이 되면, 장치를 생성할 때 전달된 I/O 연결을 통해 I/O 전송이 실행됩니다.AkFileDesc::hFile
로 표시되는 파일의 시작과 관련돼있습니다. 블록 (부분)으로 나타내며, 이 파일 설명자에 대해 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize()
가 반환하는 값에 해당하는 크기를 말합니다.스트리밍 장치가 I/O 전송 메소드를 호출할 때, AkTransferInfo 구조체의 일부로서 파일 시작에서 하위 레벨 I/O 연결로 오프셋(단위: 바이트)을 전송합니다. 오프셋은 다음과 같이 계산합니다. Current_Position + ( AkFileDesc::uSector * Block_Size) 블록 크기 또한 이 연결을 통해 파일 설명자당 한 번씩 쿼리된다는 사실에 유의하세요.
AK::IAkStdStream::GetPosition()
과 AK::IAkAutoStream::GetPosition()
을 통해 이를 사용자에게 알리고 자동 스트림의 I/O 전송을 멈춥니다.나머지 멤버는 Low-Level I/O 시스템이 독점으로 소유합니다. 예를 들어, AkFileHandle
의 경우, Win32에서 HANDLE로 타입이 정의돼있어, Win32의 ReadFile()
로 전송되는 실제 유효한 파일 핸들을 가질 수 있습니다. 그러나 AkFileHandle
를 ID나 포인터로 사용할 수도 있습니다. 상위 레벨 장치는 파일 설명자 영역을 변경하거나 읽지 않습니다. 따라서 게임 디스크에 파일들이 과다하게 열려있을 경우 Low-Level I/O는 파일 핸들을 자유롭게 닫거나 다시 열 수 있습니다.
AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
에서 반환되는 파일 설명자 구조는 AK::StreamMgr::IAkLowLevelIOHook::Close()
가 호출될 때까지 연결된 스트림 오브젝트의 수명 동안 동일하게 유지돼야 합니다.
AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
이 수행하는 모든 파일 열기 작업은 동기식이거나 지연될 수 있습니다. 장치의 네이티브 API가 지연된 열기를 지원하는 경우 성능상의 이유로 항상 이를 사용하는 것이 좋습니다. 이렇게 하면 특정 시스템에서 파일 열기 및 읽기/쓰기 작업이 동시에 수행되거나 우선 순위가 다르게 지정될 수 있습니다.
Open()에 대한 매개변수는 AK::IAkStreamMgr::CreateStd()
또는 AK::IAkStreamMgr::CreateAuto()
함수에서 전달된 모든 정보가 들어있는 AkFileOpenData 에서 파생되는 AkAsyncFileOpenData 구조체입니다. 여기에는 또한 Open 작업 완료를 상위-레벨 Streaming Manager에 알리는 데 필요한 콜백 함수와 작성할 반환 AkFileDesc
구조체가 포함돼 있습니다.
All calls to AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
are considered asynchronous, from the Stream Manager perspective. Once the open operation is resolved, signal the result of the operation through the notification callback (io_pOpenData->pCallback
). It is imperative to notify of the result once it is known, otherwise the stream object waiting for this file will stay alive indefinitely and cause a memory leak. If the result reported by io_pOpenData->pCallback
is AK_Success
, it is expected that io_pOpenData->pFileDesc
is filled properly and usable. Note that it is supported to call io_pOpenData->pCallback
right away (synchronously) if the result of the operation is known at the moment of the BatchOpen() call.
작은 정보: The AkAsyncFileOpenData (io_pOpenData ) parameter that is passed to AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() will stay valid until the notification io_pOpenData->pCallback is received. |
If the file is opened successfully, set in_eResult
to AK_Success
and create the file descriptor structure (io_pOpenData->pFileDesc
) properly. The pFileDesc
member must point to memory that will be valid for the lifetime of the stream object, until Close() is called. Use AK_FileNotFound
if the file was not found and set io_pOpenData->pFileDesc
to null. Other error codes are possible, such as AK_FilePermissionError
, AK_FilePathTooLong
or AK_UnknownFileError
.
Stream Manager의 AK::IAkStreamMgr::CreateStd()
와 AK::IAkStreamMgr::CreateAuto()
메소드는 AkFileSystemFlags 구조체를 가리키는 포인터를 받으며, 이는 AK::StreamMgr::IAkFileLocationResolver::GetNextPreferredDevice()
와 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
로 전달됩니다. 이 구조체는 사용자에서 Low-Level I/O로 직접 정보를 전달하는 방법입니다. 이 정보는 파일 위치 논리같은 것을 완료하는 데 사용됩니다. Stream Manager의 일반 사용자는 NULL을 전달할 수 있지만, 사운드 엔진은 항상 관련 정보로 채워진 구조체를 전달해 File Location Resolver에게 사운드 엔진에서 온 요청임을 알립니다.
파일 시스템 플래그 구조체에는 다음과 같은 입력란이 있습니다.
사운드 엔진은 SoundBank 파일과 스트림된 오디오 파일을 읽습니다. 이번 하위 섹션에서는 Low-Level I/O가 해당 식별자를 실제 파일로 어떻게 해결하는지 설명합니다.
AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 에서 받은 ID를 Low-Level I/O의 유효한 파일 설명자로 매핑하는 데에는 다양한 방법이 있습니다.
Low-Level I/O의 SDK 예제는 서로 다른 두 개의 방식을 사용해 파일 위치를 결정합니다. 하나는 CAkFileLocationBase로서, 전역적으로 된 경로를 연결시켜, 플랫폼 fopen()
메소드로 사용할 수 있는 전체 경로 문자열을 생성하는 방법입니다. 또 다른 하나는 CAkFilePackageLowLevelIO
로, File Packager 유틸리티로 생성된 파일 패키지를 관리합니다. 파일 패키지는 단순히 많은 파일들이 연결된 하나의 큰 파일로서, 각 원본 파일의 관련 오프셋을 표시하는 헤더가 있습니다.
File Location Resolver의 구현은 SDK 예제에 나와있습니다. 이 예제에서는 다양한 방식을 이용해 파일 위치를 관리합니다. 이 방식들은 이 섹션의 마지막 예제 코드 설명에 나와있습니다.
Low-Level I/O의 기본 구현에 사용하는 방식에 대한 자세한 설명은 기본 파일 위치 를 참고하세요.
File Packages(CAkFilePackageLowLevelIO)를 사용하는 Low-Level I/O 구현에 사용하는 방식에 대한 자세한 설명은 파일 위치 을 참고하세요.
주요 API( AK::SoundEngine::LoadBank()
와 AK::SoundEngine::PrepareEvent()
)로부터 뱅크를 로드하는 명시적 요청이나 암묵적 요청에 따라, 사운드 엔진의 Bank Manager가 ANSI 문자열이나 AK::IAkStreamMgr::CreateStd() 의 ID 오버로드를 호출할 수 있습니다. 어떤 오버로드가 선택되는지 결정하는 각기 다른 환경에 대해서는 파일 시스템에서 뱅크의 로드 를 참고하세요.
두 경우 모두 파일 시스템 플래그에서 AKCOMPANYID_AUDIOKINETIC 을 Company ID로 사용하며, AKCODECID_BANK 를 코덱 ID로 사용합니다.
사운드 엔진 API의 LoadBank()
메소드는 해당 뱅크가 특정 언어에 한정돼있는지를 명시하는 플래그를 표시하지 않습니다. 이는 Low-Level I/O 구현이 결정할 일입니다. 사운드 엔진은 SoundBank가 특정 언어에 한정돼있는지 알지 못하므로 bIsLanguageSpecific 플래그를 True로 하여 Stream Manager를 호출합니다. Stream Manager(Low-Level I/O)가 뱅크를 여는 데 실패하면 사운드 엔진이 이번에는 bIsLanguageSpecific
플래그를 False
로 설정해 다시 시도합니다.
Wwise SDK에서 특정 언어에 한정된 뱅크를 다루는 방법에 대한 자세한 정보는 언어-전용 ("Voice"와 "Mixed") SoundBank 를 참고하세요.
작은 정보: Bank Manager가 현지화되지 않은 뱅크에 대해 Stream Manager를 두 번 호출하는 것을 방지하고 언어에 상관 없이 파일 구성 체계를 이용해 이를 찾을 수 있도록 하려면 bIsLanguageSpecific 플래그를 무시하고 올바른 위치에서 바로 사운드뱅크를 열면 됩니다. 작업하는 사람만이 어느 사운드뱅크가 어디 있는지 알고 있기 때문입니다. 또한, AK::SoundEngine::LoadBank() 의 비동기 버전으로 전달하는 쿠키는 in_pFlags->pCustomData의 값으로 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 에 전달됩니다. 해당 뱅크를 특정 언어에 특화된 디렉터리로부터 열어야할 지 결정하는 데 이 쿠키를 사용할 수 있습니다. |
스트리밍된 파일 레퍼런스는 정수 ID로 뱅크에 저장됩니다. 스트리밍될 변환된 오디오 파일의 실제 파일 경로는 SoundBanksInfo.xml 파일에 들어있으며, 해당 뱅크와 함께 생성됩니다 (보다 자세한 정보는 SoundBanksInfo.xml 참고).
작은 정보: Wwise SoundBank 설정에서 'Copy streamed files'를 선택하면 [ID].[ext] 식으로 이름을 변경해 특정 플랫폼의 Generated SoundBanks 폴더에 자동으로 복사합니다. 더 많은 정보는 스트림된 오디오 파일 를 참고하세요. 기본 파일 위치 구현(CAkFileLocationBase)은 'Copy Loose/Streamed Media' 옵션에 사용하도록 고안되었습니다. |
사운드 엔진은 스트리밍된 오디오 파일을 재생하고자 할 때 AK::IAkStreamMgr::CreateAuto() 의 ID 오버로드를 호출합니다. 그러면 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 의 ID 오버로드로 전달됩니다. ID와 함께 AkFileSystemFlags 구조체를 다음 정보를 이용해 전달합니다.
Once file location has been resolved through AK::IAkFileLocationResolver::GetNextPreferredDevice()
, the Stream Manager then calls AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
on the selected streaming device, which interacts with the Low-Level I/O system through its own I/O hook. 그러면 가장 먼저 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 를 호출해 하위 레벨 블록 크기 제약을 쿼리합니다. Then, every input data transfer is executed through the hook's BatchRead() method, and output through the hook's BatchWrite() method. When the stream is destroyed, AK::StreamMgr::IAkLowLevelIOHook::Close()
is called.
각각의 메소드는 File Location Resolver에 의해 채워진 동일한 파일 설명자를 전달합니다.
Stream Manager의 현재 구현은 단일 유형의 I/O 후크를 정의하며, Low-Level I/O와 비동기 핸드셰이킹을 사용합니다.
AK::StreamMgr::CreateDevice()
는 File Location Resolver에 의해 파일 설명자 구조체에 설정된 장치 ID를 반환합니다.
다음 섹션은 지연된 I/O 후크에 대한 설명입니다.
Stream Manager는 AK::StreamMgr::IAkLowLevelIOHook
인터페이스를 통해 Low-Level I/O 시스템과 상호작용하는 스트리밍 장치를 생성합니다.
Low-Level I/O implementations must handle multiple transfer requests at the same time. The interface defines a few important methods, AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
,AK::StreamMgr::IAkLowLevelIOHook::BatchRead()
, AK::StreamMgr::IAkLowLevelIOHook::BatchWrite()
and AK::StreamMgr::IAkLowLevelIOHook::BatchCancel()
. BatchRead() 와 BatchWrite() 는 즉시 반환해야 하며, 스트리밍 장치에 제공된 콜백 함수를 통해 하나 또는 여러 개의 전송이 언제 완료될 지를 알려줘야 합니다. See details about AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()
in the section 지연 열기.
사용자는 스트리밍 장치가 Low-Level I/O로 보내게 될 동시 I/O 전송의 최대 개수를 초기화 설정에서 지정하게 됩니다 (AkDeviceSettings::uMaxConcurrentIO).
For each transfer request sent to AK::StreamMgr::IAkLowLevelIOHook::BatchRead()
or AK::StreamMgr::IAkLowLevelIOHook::BatchWrite()
, the provided callback function in AkAsyncIOTransferInfo
must be called once, with the result of the transfer. It must be called even in failure cases, otherwise the engine will wait for this transfer forever. The callback can be deferred to later, as it is common for asynchronous I/O subsystems to execute the transfer in the background.
The in_eResult parameter of the callback should be AK_Success if the transfer succeeds or any other error code if it fails. If any transfer is marked as AK_Fail, the corresponding stream will be destroyed, and an "I/O error" notification will appear in the transfer log.
참고: AkDeviceSettings::uMaxConcurrentIO 는, 해당 장치가 Low-Level I/O에 발송할 수 있는 전송 요청의 최대 개수를 나타냅니다. 장치의 스케줄러는 Stream Manager의 클라이언트가 AK::IAkStdStream::Read()/Write() 를 호출할 때, 또는 실행 중인 자동 스트림의 버퍼링이 버퍼링 목표(AkDeviceSettings::fTargetAutoStmBufferLength)보다 낮을 경우에만 전송 요청을 게시하기로 결정합니다 (목표 버퍼링 길이에 대한 자세한 내용은 Audiokinetic Stream Manager 초기화 설정 참고). |
스트리밍 장치는 BatchIoTransferItem의 배열을 각 AK::StreamMgr::IAkLowLevelIOHook
의 함수로 전달합니다. 이 구조체에는 AkFileDesc와 AkIoHeuristics, AkAsyncIOTransferInfo 를 비롯한 각 전송에 대한 정보가 담겨있습니다. AkAsyncIOTransferInfo 구조체는 앞서 언급한 AkIOTransferInfo 구조체를 확장합니다. AkAsyncIOTransferInfo 에는 읽기나 쓰기를 하는 버퍼 주소가 포함돼있고, pUserData 입력란이 있어 구현하는 사용자가 지연 전송에 메타데이터를 연결할 수 있습니다. AkAsyncIOTransferInfo 구조체는 콜백이 호출될 때까지 존재하게 됩니다. 콜백을 호출한 후에는 이 구조체를 참조해서는 안 됩니다.
읽기나 쓰기를 자신의 I/O 스트리밍 기술로 라우팅할 경우, I/O 요청 우선 순위를 재설정할 때 AkIoHeuristics 에 들어있는 정보가 매우 유용할 수 있습니다.
작은 정보: 기본 Stream Manager의 스케줄러의 구현은 '디스크 대역폭 휴리스틱'이 아니라 '클라이언트 휴리스틱'에 기반하고 있습니다. Stream Manager는 디스크상에 있는 파일들의 레이아웃을 인지하지 못합니다. 자신의 스트리밍 기술이 허용할 경우, 이 지식을 이용해 I/O 요청을 재정렬해 디스크 검색을 최소화합니다. |
스트리밍 장치는 가끔 데이터를 삭제해줘야 합니다. 데이터 삭제는 Stream Manager의 클라이언트가 AK::IAkAutoStream::SetPosition()
을 호출하거나반복 재생 휴리스틱을 변경할 때 발생합니다. 때에 따라서는 데이터가 해당 전송이 완료되기도 전에 삭제돼야 할 때도 있습니다. 이러한 삭제는 AkDeviceSettings::uMaxConcurrentIO 와 AkDeviceSettings::fTargetAutoStmBufferLength 가 큰 경우에 발생할 가능성이 높습니다. 지연된 I/O 연결 API에는 도입 지점이 있어 BatchCancel() 이 발생했을 때 알림을 받습니다. 스트리밍 장치가 Low-Level I/O에서 아직 지연 중인 하나 이상의 I/O 전송과 연관된 데이터를 삭제해야할 때, 내부적으로 이 전송들에 'cancelled' 태그를 달고, AK::StreamMgr::IAkLowLevelIOHook::BatchCancel()
을 호출한 다음, 콜백이 호출되기를 기다립니다. BatchCancel() 은 Low-Level I/O에 알림을 보내는 데에만 사용되며, 아무 작업을 안 할 수도 있습니다. 스트리밍 장치는 어느 전송이 취소돼야 하는지 알고 있으며, 이를 취소하는 대신 정상적으로 완료하게 하려면 완료와 동시에 삭제하면 됩니다. 어떤 경우라도 콜백 함수는 I/O 전송 정보와 버퍼를 자유롭게 처리할 수 있다는 것을 스트리밍 장치에 알리기 위해 호출돼야 합니다.
경고: 한 전송에 대해 콜백을 두 번 호출하지 않도록 주의하세요. |
작은 정보:
|
경고: 취소된 전송의 콜백 함수를 호출할 때 반드시 AK_Success 를 전달해야 합니다. 그 이외의 다른 것들은 I/O 오류로 간주되어 연관 스트림이 종료됩니다. |
경고: AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 은 어떤 스레드에서든 호출할 수 있습니다. 따라서 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 을 구현할 경우 Low-Level I/O 잠금에 대해 특별히 주의해야 합니다. 특히, BatchCancel() 로부터의 pCallback 콜백과 일반 I/O 완료 코드 경로로부터의 콜백 간에 레이스 컨디션이 발생하지 않도록 주의해야 합니다. 더 자세한 내용은 함수의 설명에 나와있습니다. |
작은 정보: 반드시 의무적으로 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 을 구현해야 할 필요는 없습니다. 잠금 문제가 있기 때문에 요청을 정상적으로 완료하는 것보다 취소하는 것이 더 큰 비용을 발생시킬 수 있습니다. |
앞서 언급한 것처럼 Stream Manager 사용자는 허용된 전송 크기와 관련해 하위 레벨 I/O 제약을 고려해야 합니다. 가장 일반적인 제약은 일정 값의 배수로 크기가 정해진다는 것입니다. 이 값은 파일 설명자에 대해 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 로 반환됩니다. 예를 들어, Windows®에서 FILE_FLAG_NO_BUFFERING 플래그로 연 파일은 해당 섹터 크기의 배수 크기로 읽어야 합니다. AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 메소드는 섹터 크기를 반환합니다. 그러나 만약 반대로 Win32 파일을 이 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 플래그로 열지 않는 경우에는 1을 반환하여 Stream Manager 클라이언트를 제한하지 않도록 합니다.
경고: AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 는 절대로 0을 반환해서는 안 됩니다. |
작은 정보: 하위 레벨 블록 크기의 제약을 다루는 책임은 Stream Manager의 클라이언트에게로 전달됩니다. 블록 크기가 클 수록 사운드 엔진이 스트리밍 데이터를 더 낭비하게 됩니다. 플랫폼의 I/O 시스템에 특정 정렬 제약이 있거나 I/O 대역폭 성능을 크게 높일 수 있는 경우를 제외하고는 하위 레벨 블록 크기를 1로 사용해야 합니다. |
AK::StreamMgr::IAkLowLevelIOHook::GetDeviceDesc() 는 Wwise에서 프로파일링에 사용됩니다. Low-Level I/O의 기본 구현의 정보는 Wwise에서 프로파일링할 때 나타나는 정보입니다.
AK::StreamMgr::IAkLowLevelIOHook::GetDeviceData() 는 비슷하지만 모든 프로파일링 프레임에 호출된다는 점이 다릅니다. 반환되는 값은 Streaming Device 탭의 Custom Parameter 열에 나타납니다.
Low-Level I/O의 기본 구현은 Wwise SDK에 제공되고 있습니다. samples/SoundEngine/ 디렉터리에서 찾아볼 수 있습니다.
아래 그림은 Low-Level I/O 예제와 Low-Level I/O API와의 관계를 나타낸 클래스 다이어그램입니다.
CAkDefaultIOHookDeferred implements the File Location Resolver API (AK::StreamMgr::IAkFileLocationResolver
) and the deferred I/O hook (AK::StreamMgr::IAkLowLevelIOHook
). This implementation can be used in a single-device I/O system. CAkDefaultIOHookDeferred::Init()는 Stream Manager에 스트리밍 장치를 생성하고, 이를 장치 설정으로 전달해 반환된 장치 ID를 저장합니다.
또한 이 장치는 Stream Manager에 스스로를 유일한 File Location Resolver로 등록합니다. 그러나 이는 Stream Manager 에 등록돼있는 File Location Resolver가 없는 경우에 한합니다.
다음 그림은 단일 장치 I/O 시스템을 나타낸 블록 다이어그램입니다. 'Low-Level I/O'는 File Location Resolver API뿐만 아니라 I/O 연결 API 중 하나를 구현하는 클래스입니다. 다음 예제 클래스 중 어느 것이든 될 수 있습니다.
다음은 CAkDefaultIOHookDeferred만을 사용해 (오류 처리 없이) I/O 시스템을 초기화하는 방법입니다.
장치 내 File Location Resolver 구현은 상속받은 CAkFileLocationBase의 서비스를 사용합니다. CAkFileLocationBase에 구현된 파일 위치 방식에 대한 더 자세한 정보는 아래의 기본 파일 위치 을 참고하세요.
기본 지연 I/O 후크의 구현에 대한 자세한 정보는 아래의 지연 I/O 연결 설명 섹션을 참고하세요.
AK::StreamMgr::IAkFileLocationResolver 및 AK::StreamMgr::IAkLowLevelIOHook 를 모두 구현하는 클래스에 파일 패키지를 관리하는 기능을 추가하는 템플릿으로 작성된 또 다른 클래스가 있습니다 (예: CAkDefaultIOHookDeferred). 바로 CAkFilePackageLowLevelIO<>입니다. 파일 패키지는 AK File Packager 유틸리티를 이용해 생성된 파일들입니다. Low-Level I/O의 파일 패키지 관리에 대한 더 자세한 정보는 아래의 예제 파일 패키지 Low-Level I/O 구현 설명 섹션을 참고하세요. CAkFilePackageIOHookDeferred 클래스는 파일 패키지 관리 기능이 강화된 CAkDefaultIOHookDeferred의 구체적인 정의입니다.
둘 이상의 장치로 I/O 시스템을 구현하고자 할 경우 Stream Manager에 각각 별도의 File Location Resolver를 등록해야 하며, 적합한 장치로 파일 관리를 발송하는 역할을 합니다. SDK는 이 기능을 구현할 때 사용할 밑그림으로 CAkDefaultLowLevelIODispatcher를 제공합니다. 다중 장치 I/O 시스템에 대한 더 자세한 정보는 다중 장치 I/O 시스템 를 참고하세요.
사운드 엔진이 사용하는 파일은 ID(스트리밍된 오디오 파일과 SoundBank에 사용)나 문자열(일반적으로 SoundBank를 위해 예약됨)로 열게 됩니다. CAkDefaultIOHookDeferred 는 CAkFileLocationBase로부터 상속되며, 전역적 경로를 설정하도록 메소드를 나타냅니다 (SetBasePath(), AddBasePath(), SetBankPath(), SetAudioSrcPath()). Both overloads of CAkDefaultIOHookDeferred::BatchOpen() call CAkFileLocationBase::GetFullFilePath(), to create a full file name that can be used with native file open functions. 먼저 기본 경로가 맨 앞에 추가됩니다. 그런 다음, 파일이 SoundBank일 경우 해당 SoundBank 경로가 추가됩니다. 만약 스트리밍된 오디오 파일일 경우, 오디오 음원 경로가 추가됩니다. 어느 경우든, 현재 언어에 의존적인 위치의 파일일 경우에는 해당 언어 디렉터리 이름이 추가됩니다.
문자열 오버로드를 사용할 경우, 파일 이름 문자열이 이 경로 끝에 추가됩니다.
ID 오버로드에서 Audiokinetic의 파일 ID만 결정됩니다. ID 오버로드를 사용하는 게임은 해당 ID 매핑 체계에 맞게 구현을 변경해줘야 합니다. CAkFileLocationBase의 매핑 체계는, 파일 ID를 기반으로 문자열을 생성하고, 파일 타입에 따른 확장자를 끝에 추가하는 방식입니다 (Codec ID로 지정됨). 이는 'Copy Streamed Files' 생성 직후 단계 (post-generation step) 사운드뱅크 설정이 사용하는 스트리밍 파일 명명 규칙과 호환됩니다. 사운드뱅크 설정에 대한 더 자세한 정보는 Wwise 도움말을 참고하세요.
참고: SoundBank 설정의 'Use SoundBank names' 옵션이 선택돼있지 않은 경우, Wwise는 [ID].bnk 포맷에 있는 이름으로 뱅크 파일을 생성합니다. 따라서 ID에 의한 명시적 뱅크 로드(AK::SoundEngine::LoadBank()의 ID 오버로드이용)와 AK::SoundEngine::PrepareEvent()로부터 트리거되는 암묵적 뱅크 로드는 기본 Low-Level I/O에 올바르게 매핑됩니다. 'Use SoundBank names' 옵션이 선택된 경우, Wwise는 원본 이름으로 뱅크 파일을 생성합니다 (bank_name.bnk). 암묵적 뱅크 로딩과 문자열에 의한 명시적 뱅크 로딩이 올바르게 매핑됩니다. 그러나 ID에 의한 명시적 뱅크 로딩은 제대로 작동하지 않습니다. 기본 Low-Level I/O가 존재하지 않는 [ID].bnk 이름으로 파일을 열려고 하기 때문입니다. SDK 관점에서의 'Use SoundBank names' 옵션에 대한 정보는 SoundBank 이름 사용하기 를 참고하세요. 일반적인 SoundBank 설정에 대한 정보는 Wwise 도움말을 참고하시면 됩니다. 같은 방식으로, 기본 Low-Level I/O는 [ID].[ext] 포맷의 이름으로 스트리밍 오디오 파일을 엽니다. [ext]는 오디오 형식에 따른 확장자입니다. Wwise가 SoundBank 생성 끝에 Generated SoundBank 경로로 [ID].[ext] 파일 이름 형식을 이용해 모든 스트리밍 오디오 파일을 자동으로 복사하게 할 수 있습니다 ( 스트림된 오디오 파일 과 Wwise 도움말 참고). |
전체 파일 경로가 생긴 다음, CAkDefaultIOHookDeferred::Open()이 시스템 API(특정 플랫폼 전용 예제 파일 AkFileHelpers.h에 구현된 헬퍼로 감싸져있음)를 이용해 직접 파일을 엽니다.
위에서 언급한 CAkFileLocationBase 메소드를 이용해 게임 코드에서 기본 사항 및 SoundBank, 오디오 음원, 특정 언어 경로를 설정할 수 있습니다. 더 자세한 정보는 기본 Low-Level I/O 구현 의 예제 코드를 참고하세요.
작은 정보: 사운드 엔진은 특정 언어 전용 디렉터리에서 뱅크를 언제 로드해야하는지 알지 못합니다. 따라서 true로 설정된 AkFileSystemFlags 구조체의 bIsLanguageSpecific 플래그로 항상 AK::IAkStreamMgr::CreateStd() 를 호출하고, 만약 처음 호출이 실패하면 false를 설정합니다. Low-Level I/O의 기본 예제 구현은 현재 특정 언어 전용 디렉터리로부터 무작정 파일을 열려고 합니다. 이는 실패한 fopen() 호출때문에 매우 비효율적인 처리 방식이 되기 때문에 피해야 합니다. 항상 필요에 맞게 Low-Level I/O를 재구현해야합니다. 특정 언어 전용 SoundBank의 이름을 알 경우나, 이들을 식별하기 위해 명명하는 방식을 정의한 경우, 처리 과정의 앞단계에서 올바른 폴더로부터 이를 로드해야 합니다. |
일반적으로 플랫폼의 비동기 파일 읽기 API는 특정 플랫폼 전용 구조체를 fread()로 전달하고 (Windows에서는 OVERLAPPED), I/O가 완료됐다고 알리는 콜백 함수가 호출될 때까지 전체 I/O 작업 시간 동안 이를 유지합니다.
구현은 모든 플랫폼이 비슷합니다. CAkDefaultIOHookDeferred는 이 특정 플랫폼 전용 구조체의 배열을 해당 메모리 풀에 할당합니다. CAkDefaultIOHookDeferred::Read()가 호출되면, 비어있는 첫 번째 구조체를 찾아 'used'라고 표시하고, AkAsyncIOTransferInfo에 있는 정보로 채워넣은 다음, fread()로 전달합니다. 또한 서명이 플랫폼의 비동기 fread() 함수와 호환되는 로컬 정적 콜백 함수를 전달합니다. 이 함수에서 작업이 성공적이었는지를 판단하며, 특정 플랫폼 전용 I/O 구조체를 배포하고 스트리밍 장치를 호출합니다.
Read()/Write()와 시스템의 콜백 간 경쟁 상태를 피해야 하기 때문에 배열에서 특정 플랫폼 전용 I/O 구조체를 구하고 배포하는 일은 극미합니다.
일부 플랫폼에는 이미 커널에 전송된 I/O 요청을 취소하는 기능이 있습니다. 이 경우, CAkDefaultIOHookDeferred::Cancel()에서 호출됩니다. Windows에서는 CancelIO()가 파일 핸들에 대해 모든 요청을 취소합니다. 따라서, 스트리밍 장치가 이 함수를 호출하기 전에 io_bCancelAllTransfersForThisFile 인자를 확인해 해당 파일의 모든 요청을 취소하도록 해야 합니다. 그렇지 않을 경우, Cancel()이 아무 작업도 하지 않으므로 요청이 완료될 때까지 기다립니다. 스트리밍 장치는 어느 것을 제거해야 하는지 알고 있습니다.
경고: 스트리밍 장치에 의해 명시적으로 취소되지 않은 요청을 취소하지 마세요. 만약 이러한 요청을 취소할 경우, 유효하지 않거나 손상된 데이터가 전달되고 사운드 엔진에 크래시를 발생시킬 수 있습니다. |
다음은 다중 장치 I/O 시스템을 나타낸 그림입니다.
여러 개의 스트리밍 장치로 작업할 때는, 장치의 하위 레벨 I/O 연결과는 전혀 별개로 File Location Resolver를 인스턴스화하고 등록해야 합니다. 그 목적은, 적절한 장치로 파일을 발송하기 위해서입니다. 어느 장치가 어느 파일을 다룰 것인지를 결정하는 방식은 직접 정의하기 나름입니다. 밑그림으로 CAkDefaultLowLevelIODispatcher를 사용할 수도 있습니다. 기본 구현에서는 브루트 포스(brute force) 기법을 사용합니다. 즉, 성공할 때까지 파일을 열도록 각 장치에 요청합니다. 따라서 이 장치들도 AK::StreamMgr::IAkFileLocationResolver 인터페이스를 구현해야 합니다. 이 SDK에 제공된 어떤 예제를 사용하든 상관 없습니다.
다음은 지연 장치와 파일 패키지 장치를 다중 장치 시스템에서 인스턴스화하는 방법입니다 (그러나 실제 작업에서는 이 예제가 큰 도움이 되지는 않습니다).
작은 정보: 다중 장치는 반드시 다중 물리적 장치와 함께 사용해야 합니다. |
CAkFilePackageLowLevelIO<> 클래스는 기본 하위 레벨 I/O 연결 위의 레이어입니다. 이 클래스는 예제 File Packager로 생성된 파일을 로드할 수 있게 함으로써 기본 하위 레벨 I/O 연결을 확장합니다 ( File Packager 유틸리티 참고). 이는 ID를 파일 설명자로 처리하는 더 고급 방식을 사용합니다. 파일 패키지는 연결된 파일들 (스트리밍 오디오 파일 및 뱅크 파일)로 구성돼있으며, 헤더에는 이 파일들의 정보가 들어있습니다.
예제 코드는 파일 패키지 Low-Level I/O 구현 을 참고하세요. File Package Low-Level I/O를 있는 그대로 (CAkFilePackageLowLevelIODeferred) 사용할 수 있으며, 이에 상응하는 File Packager 유틸리티도 사용할 수 있습니다. 또는, 이를 단순히 고급 파일 위치 결정 메소드 구현의 개념 증명(proof of concept) 용도로만 생각해도 됩니다.
File Package Low-Level I/O는 CAkFilePackageLowLevelIO::LoadFilePackage()를 표시하며, 해당 인자는 예제 File Packager로 생성된 패키지의 파일 이름입니다. 기본 구현 서비스를 이용하여 연 다음, 헤더를 파싱하고 조회 테이블을 생성합니다. 파일 패키지의 개수는 원하는 만큼 로드할 수 있습니다. LoadFilePackage()는 ID를 반환하며, UnloadFilePackage()를 사용해 이를 언로드할 수 있습니다.
CAkFilePackage 클래스는 로드된 파일 패키지를 나타내며, 파일 조회를 다루는 모든 데이터 구조체와 코드는 CAkFilePackageLUT 클래스에 정의돼있습니다. CAkFilePackageLowLevelIO<> 클래스는 기본 I/O 연결 메소드 중 일부를 오버라이드하여, CAkFilePackageLUT 조회 기능을 작동시킵니다. 파일 설명자를 찾지 못할 경우나, 요청이 파일 패키지에 속한 파일 설명자를 고려하지 않을 경우, 기본 구현이 호출됩니다.
패키지 내 파일 조회의 목적은, 플랫폼의 파일 열기 함수에서 파일 핸들을 한 번만 구한 후, AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 이 호출될 때마다, 파일 패키지 내 원본 파일의 오프셋에 해당하는 값으로 이 파일 핸들을 이용하는 파일 설명자를 반환하기만 하면 되게끔 하는 것입니다 (AkFileDesc 파일 설명자의 uSector 영역 사용). 이렇게 하면 디스크 상의 파일 배치를 좀 더 쉽게 통제할 수 있습니다.
File Packager 유틸리티가 면밀하게 헤더를 준비하면 Low-Level I/O는 여기 들어있는 조회 테이블을 구하기 위해 몇몇 포인터를 캐스팅하기만 하면 됩니다. 이 조회 테이블 중 하나는 스트리밍 오디오 파일용이고 다른 하나는 뱅크 파일용입니다. 조회 테이블은 다음 구조체들의 배열입니다.
테이블 키는 파일의 fileID입니다. 서로 다른 언어의 해당 파일들은 fileID는 같지만, uLanguageID는 다릅니다. File Packager는 항상 파일 항목을 fileID로 먼저 정렬하고 그 다음에 uLanguageID로 정렬합니다. CAkFilePackageLowLevelIO::Open()에서, ID는 CAkFilePackageLUT::LookupFile()로 전달됩니다 (Open()의 문자열 버전에서는, 사운드 엔진 API의 AK::SoundEngine::GetIDFromString() 기능을 이용해 먼저 문자열이 해시됩니다). CAkFilePackageLUT::LookupFile()은 플래그의 uCodecID를 기반으로 검색에 적합한 테이블을 선택해 fileID와 uLanguageID 키로 바이너리 검색을 수행합니다. 일치하는 파일을 찾으면, 해당 파일 항목의 주소가 CAkFilePackageLowLevelIO::Open()으로 반환되고 필요한 정보를 모아 파일 설명자에 입력합니다 (AkFileDesc).
작은 정보: 일치하는 파일이 나올 때까지 각 파일 패키지을 검색합니다. 한 파일 패키지를 오직 사운드뱅크와 사용하고, 다른 파일 패키지는 스트리밍 파일하고만 사용할 경우, AkFileSystemFlags 를 사용하는 구현을 변경해 올바른 파일 패키지에서만 파일을 검색하도록 해야 합니다. |
파일 설명자의 핸들인 hFile은 파일 패키지의 핸들입니다. 파일 크기인 iFileSize와 시작 블록인 uSector는 파일 항목에 직접 저장되어있습니다.
참고: Stream Manager는, hFile 핸들로 표현된 파일의 시작으로 부터 바이트 오프셋이 아닌 블록(sector) 오프셋을 요구한다는 것을 기억하세요. 블록 크기는 파일 위치의 단위를 나타냅니다. File Packager 현재 버전은 모든 파일에 대해 동일한 블록을 사용하며, 이는 생성 시점에 지정됩니다 (-blocksize 스위치 이용. File Packager의 명령줄 인자에 대한 더 자세한 내용은 Wwise 도움말 참고). 0으로 채우기(zero-padding)를 수행해, 연결된 파일들이 항상 블록 경계에서 시작하게 합니다. |
File Package 하위 레벨 I/O는 파일 설명자의 uCustomParamSize 영역을 이용해 블록 크기를 저장합니다. 여기에는 두 가지 목적이 있습니다.
Wwise 버전 2011.2부터, AK::StreamMgr::SetCurrentLanguage()로 기본 Stream Manager 모듈에 현재 언어가 설정돼있고 이는 AkStreamMgrModule.h에 정의돼있습니다. 맨 끝에 붙는 슬래시나 역슬래시 없이 언어 이름을 전달합니다.
CAkFileLocationBase로부터 상속된 기본 하위 레벨 I/O 구현은 언어 이름을 Stream Manager로부터 가져와 기본 경로 뒤에 붙입니다. 따라서 언어 이름은, 이 특정 언어에 대해 현지화된 에셋이 저장돼 있는 디렉터리 이름과 같아야 합니다.
File Packager 유틸리티가 생성한 파일 패키지에는 다양한 언어로 돼있는 동일한 에셋의 하나 또는 여러 버전이 있을 수 있습니다. 이들 헤더에는 언어 이름의 문자열 맵이 있습니다. File Package Low-Level I/O는 Stream Manager에서 언어가 바뀌는 것을 듣고, 현재 언어 이름을 사용해 패키징된 현지화 에셋의 올바른 현지화 버전을 검색합니다.
프로젝트를 등록하세요. 아무런 조건이나 의무 사항 없이 빠른 시작을 도와드리겠습니다.
Wwise를 시작해 보세요