버전

menu_open
Wwise SDK 2024.1.0
상위 레벨 Stream Manager API 설명

소개

Stream Manager는 Wwise 사운드 엔진에서 사운드뱅크를 로드하고 스트리밍된 오디오 파일을 읽을 때 사용합니다. 뿐만 아니라 I/O 관리자가 따로 없는 경우, 모든 게임 I/O에 대해 사용해도 됩니다. Stream Manager를 클라이언트로서 직접 사용하지 않을 경우 이번 장 전체를 건너뛰고 단순히 하위 레벨 I/O 연결을 구현해 Wwise I/O를 자신의 게임에 통합하기만 하면 됩니다.

Stream Manager 주요 인터페이스

Stream Manager의 주요 인터페이스는 AK::IAkStreamMgr 에 의해 정의되며, 이는 간단히 말해 스트리밍 오브젝트의 팩토리입니다.

Stream Manager 인스턴스화

Stream Manager를 사용하기 전에 먼저 인스턴스화해줘야 합니다.

Default Streaming Manager Information

Stream Manager 인스턴스화는 구현에 따라 달라집니다. 따라서 Stream Manager의 Audiokinetic 기본 구현 팩토리 함수는 AkStreamMgrModule.h: AK::StreamMgr::Create() 에서 정의됩니다. 이를 구현별 설정으로 전달해야 합니다 (초기화 설정에 대한 설명은 Audiokinetic Stream Manager 초기화 설정 츷 참고하세요).

상위 레벨 스트리밍 장치

Default Streaming Manager Information

스트림 오브젝트를 생성하고 사용하기 전에 상위 레벨 스트리밍 장치를 생성해야 합니다. 이 방식은 Adiokinetic의 Stream Maanger 기본 구현에 특화돼있습니다. 상위 레벨 스트리밍 장치는 근본적으로 I/O 스케줄러를 구현함으로서, 자신의 스레드에서 작동하여 연관된 스트림 오브젝트를 집중화해 데이터 전송 요청을 Low-Level I/O로 전달합니다. 상위 레벨 장치는 일반적으로 그냥 '장치'로 통칭됩니다. 하나 또는 여러 개의 장치가 게임에 의해 생성되며, 대부분 초기화 때에 특정 플래그와 설정으로 생성됩니다. 초기화 설정과 관련한 더 자세한 정보는 Audiokinetic Stream Manager 초기화 설정 를 참고하세요. AK::StreamMgr::CreateDevice() 가 장치 ID를 반환하며, Low-Level I/O가 파일 위치 및 장치 할당을 위해 이를 보관합니다. (더 많은 정보는 파일 위치 결정 섹션을 참고하세요. ) 이 장치 ID는 올바른 종료 처리를 위해 AK::StreamMgr::DestroyDevice() 로 전달돼야 합니다. 모든 구현 함수에 대해, AK::StreamMgr::CreateDevice()AK::StreamMgr::DestroyDevice()AkStreamMgrModule.h 헤더에 정의됩니다.

스트림 생성

Stream Manager 주요 API의 상당수는 스트림 팩토리 메소드 세트입니다. 파일 식별자와 설정을 통해 스트림 오브젝트를 생성하고 해당 스트림으로 인터페이스를 반환합니다. 모든 스트림 작업은 이 인터페이스를 통해 실행됩니다. In this API, it is possible to identify a stream file either by file name (string) or by file ID (integer). This choice is wrapped into the structure AkFileOpenData. Only one method should be used to designate a file. If both are used, the string method will be preferred.

참고: Low-Level I/O 인터페이스의 Open() 메소드는 파일 식별자로부터 파일 설명자를 생성합니다.

파일 식별자와 함께, 스트림 생성 메소드는 파일 위치 플래그가 들어있는 AkFileSystemFlags 구조체를 가리키는 포인터를 받습니다.

Default Streaming Manager Information

이 포인터는 있는 그대로 Low-Level I/O로 전달됩니다. AkFileSystemFlags 와 사운드 엔진이 이를 어떻게 사용하는 지에 대한 더 자세한 정보는 SoundBank스트리밍된 오디오 파일 , 기본 파일 위치 섹션을 참고하세요.

스트림 생성 메소드에는 in_bSyncOpen 라는 또 추가 인자가 있습니다. 호출되는 동안 스트림 오브젝트로 감싼 파일 핸들을 열어야 할 때 사용자(예: 사운드 엔진)가 true를 전달합니다. 만약 사용자가 false를 전달하면, Stream Manager가 파일 열기를 지연키는 것을 허용한다는 뜻입니다. 이는 지연을 할 수도 있고 하지 않을 수도 있습니다. 만약 지연시킨 후 파일 열기가 실패할 경우, 다음 호출인 AK::IAkStdStream::Read()AK::IAkAutoStream::GetBuffer() 가 AK_Fail을 반환합니다.

참고: 이는 치명적인 오류에 해당됩니다. 사운드 엔진이 스트리밍된 오디오 파일에 대해 false를 전달합니다. GetBuffer() 가 false를 반환할 경우, 사운드 엔진이 이를 I/O 오류로 받아들여 스트림을 삭제합니다.
Default Streaming Manager Information

Argument in_bSyncOpen 은 Low-Level I/O로 직접 전달됩니다. Low-Level I/O에서 이 플래그의 역할에 대한 자세한 내용은 지연 열기 을 참고하세요.

AK::IAkStreamMgr::CreateStd() 는 표준 스트림을 생성하고, AK::IAkStreamMgr::CreateAuto() 는 자동 스트림을 생성합니다. 다음 섹션은 이 두 스트림 타입에 대한 설명입니다.

스트림 파기 (Stream Destruction)

두 유형의 스트림 오브젝트 모두 Destroy() 메소드를 정의합니다. 이 스트림이 사용하는 리소스를 제거하기 위해서는 메소드를 호출해야 합니다. 그런 다음 해당 오브젝트를 다시 사용하면 안 됩니다.

Default Streaming Manager Information

스트림 관리자의 기본 구현에서 AK::IAkStdStream::Destroy()AK::IAkAutoStream::Destroy() 는 단순히 스트림 오브젝트에 대해 플래그를 설정하는 역할만 합니다. 실제 파기는 나중에 I/O 스레드에 의해 실행됩니다. I/O 스레드가 활성화될 때마다 모든 열려있는 스트림을 검사하고 I/O 요청이 Low-Level I/O로 발생하는 다음 스트림을 찾습니다. 그리고 모든 쓸모 없는 스트림을 제거합니다. 이 시점에서는 메모리만 지우고, 파일 핸들이 해제된 곳에서 AK::StreamMgr::IAkLowLevelIOHook::Close() 가 호출됩니다.

소멸이 예정된 스트림 오브젝트는 I/O 스레드에 신호를 보내 Low-Level I/O에서 지연되고 있는 I/O 전송이 더 이상 없으면 즉시 제거될 수 있도록 합니다.

스트림 프로파일링이 발생하면, I/O 스레드는 스트림 오브젝트를 배치하기 전에 모니터링 스레드가 승인을 내리기를 기다립니다. 모니터링 스레드는 매 200 ms마다 프로파일링 전달을 실행합니다.

표준 스트림

AK::IAkStreamMgr::CreateStd() 를 호출하면 반환된 AK::IAkStdStream 인터페이스를 통해 표준 스트림 오브젝트를 생성합니다.

정의

표준 스트림(standard stream)은 I/O 작업을 통제할 때 기본 읽기/쓰기 체계를 사용합니다. AK::IAkStdStream::Read()AK::IAkStdStream::Write() 가 호출되면, I/O 요청이 스케줄러의 큐에 담기고 스트림의 상태를 AK_StmStatusPending 으로 설정합니다. 사용자는 요청된 전송 크기와 버퍼의 주소를 전달합니다. 작업이 완료되면, 스트림 상태를 AK_StmStatusCompleted 나 AK_StmStatusError 중 하나로 설정합니다. 해당 위치는 전송된 실제 크기에 따라 증가되어, 다음 작업이 새로운 위치에서 발생하게 됩니다. 다른 위치를 강제로 지정하려면 사용자는 새로운 전송을 시작하기 전에 AK::IAkStdStream::SetPosition() 메소드를 사용해야 합니다.

I/O 작업은 한 번에 하나만 발생할 수 있습니다. 그 다음 작업은 AK::IAkStdStream::Read()AK::IAkStdStream::Write() 를 호출하여 명시적으로 시작됩니다. 사용자가 스트림을 이용해 작업을 끝냈을 경우, AK::IAkStdStream::Destroy() 가 호출돼야 합니다. 그러면 해당 인터페이스가 유효하지 않게 됩니다.

Default Streaming Manager Information

읽기나 쓰기 호출은 Low-Level I/O의 I/O 전송으로 처리됩니다. 클라이언트 단에서 요청된 크기가 스트리밍 장치의 단위(AkDeviceSettings::uGranularity)보다 클 경우, 해당 전송은 작게 쪼개집니다. 스트림은 전체 전송이 완료되거나 파일 끝 상태가 발생할 때까지 AK_StmStatusPending 이나 AK_StmStatusIdle 상태로 남아있습니다.

인터페이스는 설정 쿼리, 현재 상태나 위치에 접근, 이전 작업에 제공된 버퍼에 접근, 등과 같은 다른 메소드를 보여줍니다.

표준 스트림 데이터 접근 체계

표준 스트림 작업을 실행하는 방법은 두 가지입니다.

생성 설정

표준 스트림 생성할 때에는 열기 모드 (AkOpenMode)를 지정해줘야 합니다. 열기 모드는, 스트림을 읽기에 사용할 수 있는지, 쓰기에 사용할 수 있는지, 또는 둘 다에 사용할 수 있는지를 지정합니다. 물론 읽기 전용으로만 열려있는 스트림에 대해 AK::IAkStdStream::Write() 를 호출하면 실패합니다.

또한 AK::IAkStdStream::SetStreamName() 을 이용해 스트림 이름을 지정할 수도 있습니다. 해당 문자열은 스트림 오브젝트에 복사되며, AK::IAkStdStream::GetInfo() 로 쿼리될 수 있습니다.

Default Streaming Manager Information

스트림 이름은 Wwise Advanced Profiler의 streaming 탭에 나타납니다.

휴리스틱

표준 스트림의 휴리스틱은 개별 작업별로 지정됩니다.

AK::IAkStdStream::Read()AK::IAkStdStream::Write() 는 작업의 우선순위와 작업 시간(밀리세컨드 단위)을 필요로 합니다. 일반적으로 Stream Manager는 작업 시간이 짧은 작업부터 처리합니다. 애플리케이션에 저장 장치보다 더 큰 I/O 대역폭이 필요할 때, Stream Manager는 높은 우선순위의 스트림을 먼저 처리합니다. 작업 시간을 0으로 지정하면 데이터가 지금 바로 필요하다는 뜻이므로 I/O는 이미 늦은 상황이 됩니다. 이럴 경우, 보다 낮은 우선 순위의 다른 스트림보다 먼저 처리됩니다.

참고: 사운드 엔진의 Bank Manager는 표준 스트림의 사용자입니다. Bank Manager는 해당 버퍼에서 SoundBank 데이터를 읽고 파싱합니다. 메소드는 사운드 엔진 API에 나타나 평균 처리량과 뱅크 로딩 우선 순위를 지정합니다 ( AK::SoundEngine::SetBankLoadIOSettings() ). ( Wwise 사운드 엔진의 Banks를 참고하세요. ) Bank Manager는 AK::IAkStdStream::Read() 를 호출하고, 완료되면 데이터를 파싱하고 읽습니다. 이 때 해당 메소드의 인자로서 제공된 우선 순위를 사용합니다. Bank Manager는 사용자가 지정한 처리량으로부터 작업 시간을 계산합니다.

fDeadline = uBufferSize / fUserSpecifiedThroughput;

표준 스트림의 작업 시간과 자동 스트림의 평균 처리량은 Stream Manager의 I/O 스케줄러와 동일한 역할을 합니다. 따라서 사운드 엔진 사용자는 오디오 스트림과 게임에서 다른 스트림으로 I/O의 뱅크 로딩 부담을 조절할 수 있습니다.

일반적인 위험과 다른 고려 사항

블록 크기

읽기와 찾기 단위의 하위 레벨 제약과 버퍼 정렬을 Stream Manager 레벨에서 '블록 크기 (block size)'라고 부릅니다. 스트림 인터페이스의 AK::IAkStdStream::GetBlockSize() 메소드는 Low-Level I/O를 쿼리하고 이를 스트림과 연관된 파일 설명자로 전달해 호출자에게 그 값을 반환합니다. 파일 설명자와 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 메소드에 대한 자세한 설명은 Low-Level I/O 섹션을 참고하세요. 일부 저장 장치는 데이터 전송 크기에 제약이 있습니다. 예를 들어, Win32 플랫폼에서 FILE_FLAG_UNBUFFERED 플래그로 연 파일은 물리적 장치의 부분 크기의 배수로만 전송 크기를 허용합니다. 블록 크기의 배수가 아닌 전송 크기를 요청한 경우 읽기나 쓰기 작업이 실패합니다. 상위 레벨 인터페이스 사용자는 AK::IAkStdStream::GetBlockSize()로 반환된 값의 배수로 된 읽기 크기를 요청해야 합니다. AK::IAkStdStream::SetPosition() 또한 동일한 제약이 적용됩니다. 그러나 AK::IAkStdStream::SetPosition() 는 낮은 블록 한도로 자동 스냅한 후 실제 파일 위치 오프셋을 반환합니다.

일반적으로 게임 타이틀이 Stream Manager의 사용자입니다. 게임 타이틀 또한 Low-Level I/O 하위 모듈을 구현하므로 사용할 수 있는 전송 크기를 이미 인지하고 있습니다. 사운드 엔진은 저장 장치 제약에 대해 인지하지 못하고 있으므로 언제나 자체 표준 스트림 읽기 크기를 블록 크기 한도로 스냅합니다.

스트림 상태가 지연 중일 때 호출되는 메소드

스트림이 AK_StmStatusPending 상태에 있을 때 AK::IAkStdStream::Read()AK::IAkStdStream::Write() 를 호출하면 실패합니다. AK::IAkStdStream::GetPosition()AK::IAkStdStream::SetPosition() 은 I/O 전송이 완료되기 전이나 후에 발생할 수 있으므로 분명하지 않은 결과를 산출합니다. 그러나 I/O에서 차단되지는 않습니다.

전송이 지연되는 동안 AK::IAkStdStream::Cancel() 를 사용할 수 있지만, 성능이 안 좋아질 수 있기 때문에 추천하지 않습니다. 이 메소드는 지연 중인 I/O가 없다는 사실을 호출에게 확인시켜 줍니다. Low-Level I/O로 요청이 전송되기 전에 호출이 실행되면 해당 과제는 큐에서 제거됩니다. 그러나 요청이 이미 Low-Level I/O로 발송됐다면 I/O가 완료될 때까지 호출자가 차단됩니다.

작은 정보: 사용자는 스트림을 파기하기 전에 (AK::IAkStdStream::Destroy()) AK::IAkStdStream::Cancel() 를 명시적으로 호출하지 않아도 됩니다. 그러나 가장 좋은 성능을 내려면 사용자가 스트림이 AK_StmStatusPending 상태에 있지 않을 때에만 Destroy()를 호출하면 됩니다.

자동 스트림

AK::IAkStreamMgr::CreateAuto() 를 호출하면 반환된 AK::IAkAutoStream 인터페이스를 통해 자동 스트림 오브젝트를 생성합니다.

정의

자동 스트림은 입력에만 사용되는 스트림입니다. 자동 스트림이라 불리는 이유는, 사용자의 명시적 함수 호출 없이 내부 처리 과정을 통해 I/O 요청이 Low-Level I/O로 전송되기 때문입니다. 스트리밍 메모리는 Stream Manager가 소유하며, 스트리밍 메모리 주소를 요청해 스트리밍된 데이터에 접근할 수 있습니다. 스트리밍 메모리 영역이 사용자에게 부여되면 명시적으로 해제될 때까지 잠긴 상태를 유지합니다. 한편 내부 스케줄러는 잠기지 않은 메모리에서 데이터 전송을 실행합니다. 스트림은 생성될 때 idle 상태입니다. I/O 요청의 자동 스케줄링은 사용자가 AK::IAkAutoStream::Start() 를 호출할 때 시작됩니다. AK::IAkAutoStream::Stop() 을 호출하면 멈추거나 일시 정지하고, AK::IAkAutoStream::Start() 를 호출하면 재개됩니다.

경고: 스트림이 멈춰있는 동안 GetBuffer() 를 호출하지 마세요. 스트림으로부터 버퍼를 구하려고 하기 전에 항상 Start() 를 호출해야 합니다.

데이터는 AK::IAkAutoStream::GetBuffer() 를 호출해 접근할 수 있습니다. 데이터가 Low-Level I/O로부터 이미 읽힌 경우, 메소드는 AK_DataReady, 해당 데이터가 포함된 버퍼의 주소, 그리고 그 크기를 반환합니다. 버퍼가 더 이상 필요하지 않게 되면, AK::IAkAutoStream::ReleaseBuffer() 를 호출하여 해제합니다. 그러면 해제된 버퍼의 크기만큼 사용자에게 보이는 스트림 위치가 증가됩니다. 사용자는 AK::IAkAutoStream::SetPosition() 을 호출해 새로운 위치를 강제로 지정할 수 있습니다. 다음 AK::IAkAutoStream::GetBuffer() 호출은 이 위치와 일치하게 됩니다.

스트림 위치를 변경하면 일부 데이터가 지워질 수 있습니다. 자동 스트림은 순차적인 접근에 최적화돼있습니다. 여기에는 반복 재생을 지정하는 휴리스틱이 있습니다. 이 휴리스틱은 Stream Manager가 스트리밍 메모리를 효율적으로 관리할 수 있도록 돕습니다. 더 자세한 정보는 일반적인 위험과 다른 고려 사항 섹션을 참고하세요.

경고: Low-Level I/O가 오류를 보고하면 해당 스트림이 자체적으로 오류 모드로 들어갑니다. 자동 스트림은 오류 상태에서 복구할 수는 없으며, AK::IAkAutoStream::GetBuffer() 가 항상 AK_Fail을 반환하게 됩니다.

자동 스트림 데이터 접근 체계

자동 스트림의 데이터에 접근하는 방법은 두 가지입니다.

AK::IAkAutoStream::GetBuffer() 에 사용할 수 있는 반환 코드는 다음 네 가지입니다.

  • AK_DataReady
  • AK_NoMoreData
  • AK_NoDataReady
  • AK_Fail

사용자에게 데이터로 채워진 버퍼가 부여되면, 메소드는 AK_DataReady 나 AK_NoMoreData 중 하나를 반환합니다. 만약 스트림 버퍼가 비어있을 경우, AK::IAkAutoStream::GetBuffer() 가 AK_NoDataReady 를 크기 0과 버퍼 주소 null로 반환합니다. Low-Level I/O가 오류를 보고하거나 메소드가 유효하지 않은 매개 변수로 호출되면 AK_Fail을 반환합니다.

파일의 마지막 버퍼가 부여될 때는 AK_DataReady 대신 AK_NoMoreData 가 반환됩니다. Stream Manager가 파일 설명자 구조체 멤버로서 Low-Level I/O가 제공한 파일 크기와 현재 위치(사용자에게 보이는)를 비교해 마지막 버퍼임을 평가합니다. 그 다음 호출인 AK::IAkAutoStream::GetBuffer() 는 AK_NoMoreData 를 크기 0으로 반환합니다.

AK::IAkAutoStream::GetBuffer() 호출은 새로운 버퍼를 제공합니다. 버퍼는 AK::IAkAutoStream::ReleaseBuffer() 를 호출해 명시적으로 해제해야 합니다. 이 버퍼들은 GetBuffer()에 의해 부여된 순서대로 해제됩니다. 버퍼가 해제되면 해당 주소는 유효하지 않게 됩니다. 클라이언트가 아무 버퍼도 갖고있지 않을 경우 ReleaseBuffer() 는 AK_Fail 을 반환합니다. 그러나 이를 치명적인 오류로 간주하지는 않습니다.

작은 정보: 가능하다면 한 번에 버퍼 하나씩만 사용하세요. 이렇게 하면 Stream Manager가 I/O를 실행할 공간이 더 생깁니다. 한 번에 버퍼를 둘 이상 사용하는 경우는 주로 링 버퍼나 이중 버퍼에 접근해야 하는 하드웨어나 다른 인터페이스에만 해당됩니다. 한 스트림에 대해 한 번에 둘 이상의 버퍼를 사용할 경우, 적합한 휴리스틱을 전달해 알려줘야 합니다 (AkAutoStmHeuristics::uMinNumBuffers).

경고: 자동 스트림을 사용할 때 가장 흔히 하는 실수는 ReleaseBuffer() 를 호출을 잊어버리는 것입니다.

성공적인 GetBuffer() 호출에 의해 부여된 버퍼 크기는 Stream Manager에 의해 결정됩니다. 그래도 근본 파일의 끝에 도달하지 않는 한 버퍼 크기는 블록 크기의 배수여야 합니다. 버퍼링 제약을 이용하는 (AkAutoStmBufSettings 참고) GetBuffer() 에 의해 반환된 크기를 강제로 지정해 생성 시간에 전달할 수도 있습니다.

경고: 버퍼 크기를 강제로 지정하면 스트리밍 메모리나 대역폭이 낭비될 수 있기 때문에 최적의 성능을 내지 못할 수 있습니다. 또한, 사용자가 지정한 제약은 지원하지 않습니다. IAkStreamMgr::CreateAuto() 는 AK_Fail 을 반환하게 됩니다.
Default Streaming Manager Information

이 크기는 스트림이 끝에 도달한 경우를 제외하고는 주로 상위 레벨 장치 생성 설정에 지정된 단위와 일치합니다.

차단 (blocking)

True로 설정된 in_bWait 플래그로 AK::IAkAutoStream::GetBuffer() 를 호출하면 데이터가 준비될 때까지 사용자를 차단합니다. 따라서 메소드가 AK_NoDataReady 를 반환할 수 없습니다. 마지막 버퍼가 이미 부여됐거나 해제된 경우, 메소드는 바로 AK_NoMoreData를 크기 0으로 반환합니다.

폴링 (polling)

사용자는, in_bWait 플래그를 False로 설정하여 호출된 AK::IAkAutoStream::GetBuffer() 가 반환한 코드를 평가해 데이터를 구할 수 있습니다. 만약 AK_NoDataReady 인 경우, 사용자는 다른 과제를 실행하고 나중에 다시 시도해야 합니다.

생성 설정

자동 스트림은 표준 스트림보다 더 다양한 설정으로 인스턴스화됩니다. 그중에는 휴리스틱과 버퍼 크기 제약이 있습니다. 이 설정들은 Stream Manager가 최적의 메모리 양을 할당하고 데이터 전송 요청의 우선 순위를 정하는 것을 돕습니다.

버퍼 제약

일부 설정은 사용자가 지정한 메모리 연관 제약들입니다. 이 때 버퍼 크기는 직접 지정할 수 있습니다. Stream Manager가 일정한 한도 내에서 버퍼 크기를 선택하게 하고 싶다면 최소 버퍼 크기나 블록 크기를 지정하면 됩니다. 버퍼 크기는 블록 크기의 배수가 됩니다.

작은 정보: 버퍼 제약은 선택 사항이며, Stream Manager가 스트리밍 메모리를 선택적으로 관리할 수 있도록 일반적으로 사용해서는 안 됩니다. 사운드 엔진은 일부 플랫폼에서 디코딩 파드웨어를 사용할 때 버퍼 제약을 사용합니다. 이 디코더는 주로, 특정한 바이트 정렬(블록 크기)과 최소 버퍼 크기로 한 번에 두 버퍼로 접근해야 합니다.

Default Streaming Manager Information

현재 구현은 커스텀 자동 스트림 버퍼 크기가 장치 단위를 초과할 수 없게 돼있습니다. (세분성에 대한 더 자세한 내용은 Audiokinetic Stream Manager 초기화 설정 을 참고하세요. )

휴리스틱

휴리스틱은 Stream Manager의 스케줄링과 메모리 할당 작업을 돕습니다. 자동 스트림 휴리스틱은 생성 시간에 스트림별로 지정되며 최적의 동작을 내기 위해 매우 중요합니다. 자동 스트림 휴리스틱은 AK::IAkAutoStream::GetHeuristics()AK::IAkAutoStream::SetHeuristics() 를 통해 아무 때나 쿼리되거나 변경될 수 있습니다.

  • 처리량: 평균 처리량은 I/O 스케줄러의 작업 시간이 됩니다. 특정 스트림에 대해 어느 정도의 데이터를 갖고 있는지 알고 있기 때문입니다. 이는 표준 스트림의 작업별 작업 시간 휴리스틱에 해당됩니다.
  • 우선순위: 애플리케이션이 저장 장치 사양보다 더 넓은 I/O 대역폭을 필요로 할 때 보편적으로 이용됩니다. 이 경우 Stream Manager는 더 높은 우선순위의 스트림 요청을 수행하려 합니다. 이는 표준 스트림의 작업별 우선순위 휴리스틱에 해당됩니다.
  • 반복 재생: 반복적으로 재생하는 스트림에서 반복 재생 위치를 지정할 수 있습니다. Stream Manager가 해당 메모릴르 관리하는 데 도움을 줍니다. 반복 재생이 해제되면, 휴리스틱을 다시 'non-looping (반복 재생 아님)'으로 설정하는 것이 좋습니다 (uLoopEnd를 0으로 설정). 이는 단지 휴리스틱이 해당된다는 점을 기억하세요. 클라이언트가 SetPosition() 을 호출하지 않는 한 스트림 위치가 변경돼서는 안 됩니다. 그러나 반복 재생 휴리스틱의 예상치 못한 위치 변경이 발생하면 대부분의 경우 데이터가 지워질 수 있습니다.
  • uMinClientBuffers: 클라이언트가 한 번에 몇 개의 버퍼를 갖게 될 지를 나타냅니다. 일반적으로 한 번에 하나의 버퍼만 있어야 합니다. 가끔 한 번에 두 개의 버퍼가 필요한 경우가 있을 수 있습니다. 이런 경우 uMinClientBuffers를 2로 설정하세요. Stream Manager는 최소 (uMinClientBuffers + 1) 개의 버퍼로 스트림을 사용해야합니다. uMinClientBuffers를 0으로 지정하면 (기본 설정) 1이 됩니다.

일반적인 위험과 다른 고려 사항

블록 크기

Stream Manager는 항상 버퍼 크기를 결정하기 전에 파일의 블록 크기를 Low-Level I/O로 쿼리해 버퍼 크기가 블록 크기의 배수가 되도록 합니다. 따라서 자동 스트림 사용자는, 스트림을 다른 위치로 강제 지정하지 않는 한 Low-Level 블록 크기에 대해 신경 쓰지 않아도 됩니다. 이 경우, 블록 크기를 AK::IAkAutoStream::GetBlockSize() 에서 가져온 것으로 감안하거나, AK::IAkAutoStream::SetPosition() 에서 반환된 실제 절대 상쇄값으로 간주합니다.

스트림 위치

Stream 위치는 사용자 시점에서 평가되며, Get/SetPosition() 메소드로 쿼리되고 설정됩니다. Low-Level I/O로부터 Stream Manager의 버퍼로 전송 받은 데이터는 위치를 계산할 때 포함하지 않습니다. Stream 위치는 사용자에 의해 버퍼가 해제될 때 업데이트됩니다. AK::IAkAutoStream::SetPosition() 가 호출됐을 때 일부 데이터가 이미 Low-Level I/O로부터 전송됐을 경우, 해당 데이터는 일반적으로 지워집니다.

참고: 사용자는 가능하면 항상 AK::IAkAutoStream::SetPosition() 호출을 피해야 합니다. 반드시 사용해야 할 때에는, 최대한 일찍 호출해야 합니다. 예를 들어, 사운드 엔진에서 반복 재생 음원이 Stream Manager로부터 버퍼를 가져올 때 반복 재생의 끝이 그 버퍼에 들어있는지를 확인합니다. 만약 들어있다면, 곧바로 AK::IAkAutoStream::SetPosition() 가 호출되고 (즉, ReleaseBuffer() 한참 전에 호출됨), 그에 따라 필요 없는 데이터의 스트리밍이 발생할 위험을 최소화합니다. 또한 반복 재생 휴리스틱을 지정하면 내부 스케줄러가 Low-Level I/O에 보낼 요청과 관련해 더 나은 결정을 내릴 수 있도록 돕습니다.

작은 정보: 버퍼를 차단하든 차단하지 않든 AK::IAkAutoStream::SetPosition() 를 호출할 수 있지만, 불필요한 스트리밍 데이터가 발생할 위험을 최소화하려면 최대한 일찍 위치를 변경하는 것이 좋습니다. 스트리밍을 적절하게 중지하는 것은 대역폭 낭비를 최소화하는 데에도 도움이 됩니다.

AK::IAkAutoStream::SetPosition() 는 클라이언트가 갖고있는 버퍼를 해제하지 않습니다. 클라이언트는 ReleaseBuffer() 를 명시적으로 호출해 버퍼가 언제 해제될지를 결정합니다. AK::IAkAutoStream::SetPosition() 는 사실상 클라이언트가 GetBuffer() 의 호출 다음으로 기대하는 위치를 나타냅니다.

AK::IAkAutoStream::GetPosition() 는 현재 클라이언트가 갖고있는 첫 번째 버퍼의 위치를 반환합니다. 만약 클라이언트가 아무 버퍼도 갖고있지 않다면 GetBuffer() 의 다음 호출에 부여될 버퍼의 위치를 반환합니다.

AK::IAkAutoStream::GetBuffer() 의 AK_NoMoreData 반환 코드는 스트림 위치로부터 결정됩니다. AK::IAkAutoStream::GetPosition() 에 의해 반환되는 out_bEndOfStream 선택적 플래그도 스트림 위치에 따라 달라집니다. 이는 다음 경우에 한해 True를 반환합니다.

  • uLoopEnd 휴리스틱이 0으로 설정된 경우 (반복 재생 아님),
  • Low-Level I/O로부터 전송되는 데이터가 더 이상 없을 경우,
  • 사용자가 모든 버퍼를 해제한 경우.

지연되는 Low-Level 작업에 대한 차단 방법

자동 스트림 API는 AK::IAkAutoStream::GetBuffer() 호출 차단을 제외하고는 지연되는 Low-Level I/O 처리를 사용자가 절대 차단하지 않도록 설계되었습니다. AK::IAkAutoStream::Destroy() 도 차단해서는 안 됩니다. 일반적으로 한 스트림이 파기될 때 아직 Low-Level I/O와 상호 작용중이라면 이 스트림은 Low-Level I/O과의 현재 전송이 완료될 때까지 내부적으로 활성화돼있습니다.

Stream Manager 오버라이드

전체 Stream Manager를 오버라이드하기 위해서는 게임 타이틀이 IAkStreamMgr.h 에 정의돼있는 모든 인터페이스를 구현해야 합니다. 이 구현은 이 섹션의 다른 곳에서 설명한 규칙을 따라야합니다.

정적 라이브러리 AkStreamMgr.lib를 게임 자체의 정적 라이브러리로 교체해도 됩니다. Low-Level I/O API와 같이 다른 구현 전용 설정과 정의 및 생성 함수는 AkStreamMgrModule.h 에 위치하고 있으며 Stream Manager에 속해있지 않습니다.

사운드 엔진과 연결하기

사운드 엔진은 인라인 정적 AK::IAkStreamMgr::Get() 메소드를 호출해 Stream Manager에 접근하며, 이는 AK::IAkStreamMgr 의 protected 멤버로 정의된 AK::IAkStreamMgr 인터페이스를 가리키는 포인터를 반환합니다. 커스텀 구현은 반드시 하나의 변수(AK::IAkStreamMgr::m_pStreamMgr)만 선언해야 하며, 연결은 자동으로 됩니다. Stream Manager와 사운드 엔진이 같은 실행 파일이나 DLL에서 연결돼있지 않은 경우 AK::IAkStreamMgr::m_pStreamMgr 에 __dllexport 속성이 있어야 합니다. 커스텀 Stream Manager 라이브러리의 컴파일러 설정에서 정의된 AKSTREAMMGR_EXPORTS로 AKSTREAMMGR_API 매크로를 사용할 수 있습니다.

프로파일링

사운드 엔진의 non-AK_OPTIMIZED 버전과 연결하기 위해 프로파일링 인터페이스도 구현돼야 합니다. 인터페이스는 따로 설명이 필요 없을 정도로 명백합니다. 이를 구현하면 Wwise 내 프로파일링을 활성화시킵니다. 프로파일링이 필요 없을 경우, AK::IAkStreamMgr::GetStreamMgrProfile() 이 NULL을 반환하고, 해당 인터페이스는 텅 빈 코드로 구현될 수 있습니다. 이 경우, Wwise에서 아무런 프로파일링 정보도 나타나지 않습니다.

예제 코드

다음 몇 가지 고려해야 할 사항들은 글로 된 설명보다는 아래 나온 코드를 보면 이해가 더 쉽습니다. Stream Manager의 구현을 오버라이드하거나 변경할 때 특히 유용합니다. 이 코드는 특별한 주의가 필요한 내용을 담고 있기 때문에 의도적으로 코드 양이 많습니다.

// 참고: 간소화를 위해 편의상 테스트 파일이 4Gb를 넘지 않는다고 가정합니다 (해당 위치는 32비트에 적합).
// 표준 스트림 사용 및 고려 사항
// 테스트가 성공적이면 (Stream Manager가 올바르게 동작하면) 이 함수가 true를 반환합니다.
bool BasicStdStreamTest(
const AkOSChar * in_pszFilename
)
{
// 기본 표준 스트림 테스트.
bool bSuccess = true;
AkUInt64 iCurPosition;
const AkInt64 POSITION_BEGIN = 0;
// 읽기용 스트림 오브젝트 생성.
AK::IAkStdStream * pStream;
in_pszFilename, // 애플리케이션 스트림 식별자 (파일 이름).
NULL, // 파일 시스템 플래그 없음: 전체 경로가 제공되며 파일 위치 결정 로직이 필요하지 않음.
AK_OpenModeRead, // 생성 설정: Open Mode.
pStream, // 반환된 스트림 인터페이스.
true ); // 동기화 파일 열기 필요.
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Failed creating stream.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 메모리 제공
const int BUFFER_SIZE = 8192;
unsigned char pBuffer[BUFFER_SIZE];
// 읽기를 시도하기 전에 Low-Level I/O 모듈의 구현에 대해 잘 모르는 사용자는
// 사용자의 읽기 크기가 블록 크기 제약과 일치하도록 합니다. 이 테스트는
// BUFFER_SIZE가 블록 크기의 배수라고 추정합니다. 그렇지 않을 경우 더 이상 진행할 수 없습니다.
if ( BUFFER_SIZE % pStream->GetBlockSize() != 0 )
{
printf( "Low-Level storage device requirements are not compatible with this test.\n");
bSuccess = false;
goto stdstmbasictest_cleanup;
}
//
// 버퍼 내에서 BUFFER_SIZE 바이트 읽기. 차단 (blocking) 읽기.
//
AkUInt32 uSizeTransferred;
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 우선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 테스트: 파일 끝에 도달하지 않았다면 읽기 크기는 요청 크기와 동일해야 함. 검토.
bool bReachedEOF;
pStream->GetPosition( &bReachedEOF );
if ( !bReachedEOF &&
uSizeTransferred != BUFFER_SIZE )
{
// 발생할 수 없음.
printf( "Inconsistent transfer size.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 참고: 현재 스트림 위치는 BUFFER_SIZE가 되어야 합니다. 이 테스트가 제대로
// 데이터를 비교하게 하려면, 입력 파일이 충분히 커야 합니다. 이미 파일 끝에 도달한 경우, 여기서 중단합니다.
if ( bReachedEOF )
{
printf( "Input file not big enough. Could not complete test.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
//
// 버퍼 내에서 BUFFER_SIZE 바이트 읽기. 비차단 (non-blocking) 읽기
//
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 비차단 (non-blocking)
AK_DEFAULT_PRIORITY, // 기본 우선순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 데이터가 준비될 때까지 폴링.
AkStmStatus eStatus = pStream->GetStatus();
while ( eStatus != AK_StmStatusCompleted && eStatus != AK_StmStatusError )
{
// 다른 작업을 수행...
}
// 상태 확인.
if ( pStream->GetStatus() != AK_StmStatusCompleted )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 데이터 비교가 실패할 것이기 때문에 파일 끝(EOF)에 도달하지 않았는지 다시 한 번 확인.
pStream->GetPosition( &bReachedEOF );
if ( bReachedEOF )
{
printf( "Input file not big enough. Could not complete test.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
//
// SetPosition을 ABS_POSITION (파일 시작에서부터 절대값)으로 설정하고, 버퍼 내에서 BUFFER_SIZE 바이트를 읽음.
//
AkInt64 ABS_POSITION = 12000;
AkInt64 iRealOffset;
eResult = pStream->SetPosition(
ABS_POSITION,
&iRealOffset );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "SetPosition failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 참고: 반환된 Real Offset을 사용하세요. Low-Level I/O가 블록 크기를 1보다 크게 지정할 경우,
// Stream Manager는 가장 낮은 한계 탐색을 스냅합니다 (예: 탐색이 9001이고 블록
// 크기가 2000이었으면,위치는 8000으로 설정되며, iRealOffset이 됨).
printf( "Set position to %u, low-level block size is %u, actual position set to %u\n",
ABS_POSITION,
pStream->GetBlockSize(),
iRealOffset );
// 읽기.
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 우선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 데이터 비교가 실패할 것이기 때문에 파일 끝(EOF)에 도달하지 않았는지 다시 한 번 확인.
pStream->GetPosition( &bReachedEOF );
if ( bReachedEOF )
{
printf( "Input file not big enough. Could not complete test.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 현재의 절대 위치를 유지
iCurPosition = pStream->GetPosition( NULL );
//
// SetPosition 을 반대 방향으로 설정, 현재 위치에 대해 -REL_POSITION
//
AkInt64 REL_POSITION = -8000;
eResult = pStream->SetPosition(
REL_POSITION,
&iRealOffset );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "SetPosition failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 참고: 블록 크기가 1보다 큰 경우, 실제 절대 이동 상쇄값은 REL_POSITION보다 클 수 있습니다.
// (iRealOffset <= REL_POSITION).
// 참고: 새로운 위치는 파일 시작 위치에 대해 대략 ABS_POSITION+BUFFER_SIZE+REL_POSITION 바이트가 되어야 합니다.
// iCurPosition은 "실제" ABS_POSITION을 포함하고 있으며, iRealOffset은 이제 "실제" REL_POSITION,
// 즉 실제 이동 상쇄값을 포함하고 있습니다. 따라서 항상 절대 위치를 반환하는 GetPosition()은,
// iCurPosition+iRealOffset과 동일해야 합니다.
if ( pStream->GetPosition( NULL ) != iCurPosition+iRealOffset )
{
printf( "Wrong stream position." );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
iCurPosition = pStream->GetPosition( NULL );
printf( "Reading from offset %u.\n", iCurPosition );
// 여기서부터 읽기.
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 우선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
//
// 작업 취소
//
// 읽기.
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 우선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 취소.
pStream->Cancel();
// 여기서 Stream Manager가 하위 레벨 요청을 전송하고 완료할 시간이 있었거나,
// (AK_StmStatusCompleted 상태가 됨), 지연 작업 큐에서 삭제됨
// (AK_StmStatusCancelled 상태가 됨).
// 어느 경우든, 스트림에 대해 지연 중인 작업이 없음을 확인.
if ( pStream->GetStatus() != AK_StmStatusCompleted &&
{
printf( "Inconsistent status after operation cancellation.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
//
// 쓰기.
//
// 읽기.
eResult = pStream->Read(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 우선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 다른 파일로 다시 쓰기 (차단).
// 스트림 생성.
AK::IAkStdStream * pWrStream;
"out.dat",
NULL, // 이 인자를 이용해 Low-Level I/O에게 이 파일이
// 읽기를 지원하는 다른 저장 장치에서 생성될 것을 알려줄 수 있습니다.
pWrStream,
true ); // 동기화 파일 열기 필요.
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Failed creating stream for writing.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
eResult = pWrStream->Write(
pBuffer,
BUFFER_SIZE,
true, // 차단
AK_DEFAULT_PRIORITY, // 기본 운선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success ||
uSizeTransferred != BUFFER_SIZE )
{
printf( "Write failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 위치를 시작점으로 설정하고, 데이터를 비교하기 위해 다른 버퍼에서 파일 다시 읽기.
eResult = pWrStream->SetPosition(
POSITION_BEGIN,
NULL );
if ( eResult != AK_Success )
{
printf( "SetPosition failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
unsigned char pBufferCheck[BUFFER_SIZE];
eResult = pWrStream->Read(
pBufferCheck,
BUFFER_SIZE,
true, // 차단.
AK_DEFAULT_PRIORITY, // 기본 운선 순위.
0, // 작업 시간 0: 데이터가 '지금 바로' 필요함 (물론 이미 늦은 것이 됨).
uSizeTransferred );
// 반환 코드 확인.
if ( eResult != AK_Success ||
uSizeTransferred != BUFFER_SIZE )
{
printf( "Read failed.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
// 데이터 비교.
if ( memcmp( pBuffer, pBufferCheck, uSizeTransferred ) != 0 )
{
printf( "Data read and written do not match.\n" );
bSuccess = false;
goto stdstmbasictest_cleanup;
}
stdstmbasictest_cleanup:
// 스트림 닫기.
if ( pStream )
pStream->Destroy();
if ( pWrStream )
pWrStream->Destroy();
return bSuccess;
}
// 자동 스트림 사용 및 고려 사항.
// 테스트가 성공적이면 (Stream Manager가 올바르게 동작하면) 이 함수가 true를 반환합니다.
bool BasicAutoStreamTest(
const AkOSChar * in_pszFilename
)
{
// 자동 스트림 생성.
bool bSuccess = true;
AK::IAkAutoStream * pStream;
// 휴리스틱 설정.
AkAutoStmHeuristics heuristics;
heuristics.fThroughput = 1048576; // 1 Mb/s
// 참고: 이 스트림이 Stream Manager내에서 생성된 유일한 스트림이 될 것이므로, 처리량은 아무런 영향이 없습니다.
// 스케줄러는 I/O에 대해 항상 이 스트림을 선택합니다.
heuristics.uLoopStart = 0;
heuristics.uLoopEnd = 0; // 반복 재생 아님.
// 참고: 반복 재생 휴리스틱은 전체 애플리케이션에서 스트림이 하나뿐인 경우라도 매우 중요합니다.
// 스트리밍 메모리를 관리하는 방식이 바뀔 수 있기 때문입니다.
// 참고: 우선 순위 또한 무관함. 그러나 AK_MIN_PRIORITY와
// AK_MAX_PRIORITY (포괄적) 사이 값을 지정해줘야 합니다. 그렇지 않으면 Stream Manager가 해당 생성 설정을 유효하지 않은 것으로 판단합니다.
// 스트림 생성.
in_pszFilename, // 파일 이름.
NULL, // 파일 시스템 플래그 없음: 전체 경로가 제공되며 파일 위치 결정 논리가 필요하지 않음.
heuristics, // 휴리스틱.
NULL, // 버퍼 제약: 없음. 사용자는 필요하지 않은 경우 사용자 제약을 지정해서는 안 됩니다.
pStream, // 반환된 스트림 인터페이스.
true ); // 동기화 파일 열기 필요.
// 반환 코드 확인.
if ( eResult != AK_Success )
{
printf( "Failed creating stream.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 스트림 시작. I/O 스케줄러가 해당 스트림을 고려하기 시작.
pStream->Start();
// 스트림 크기 가져오기 (GetInfo 이용).
AkStreamInfo streamInfo;
pStream->GetInfo( streamInfo );
// 첫 번째 버퍼 가져오기. 이 접근은 차단됩니다.
AkUInt32 uBufferSize;
void * pBuffer;
eResult = pStream->GetBuffer(
pBuffer, // 부여된 데이터 공간의 주소.
uBufferSize, // 부여된 데이터 공간의 크기.
true // 데이터가 준비될 때까지 차단.
);
// 반환 코드 확인.
if ( eResult != AK_DataReady &&
eResult != AK_NoMoreData )
{
printf( "GetBuffer failed.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 버퍼에 들어있는 데이터를 파싱했다고 가정해봅시다. 그러면 파일에 대한 정보를 얻을 수 있습니다.
// 예를 들어 파일이 10000과 150000 (바이트)의 위치 사이에서 반복 재생해야 한다는 걸 알려줍니다.
AkUInt32 uLoopStart = 10000;
AkUInt32 uLoopEnd = 150000;
// 참고: 이 테스트가 제대로 데이터를 비교하게 하려면, 입력 파일이 uLoopEnd보다 커야 합니다.
if ( (AkUInt32)streamInfo.uSize < uLoopEnd )
{
printf( "Input file not big enough. Could not complete test.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 최적의 Stream Manager 동작을 내기 위해서는, 휴리스틱을 알맞게 설정해줘야 합니다.
pStream->GetHeuristics( heuristics ); // 참고: 여기서는 휴리스틱을 먼저 구할 필요가 없습니다.
heuristics.uLoopStart = uLoopStart;
heuristics.uLoopEnd = uLoopEnd;
pStream->SetHeuristics( heuristics );
// 참고: 현재 스트림 위치는 클라이언트에게 보여지는 위치입니다. 따라서 0이 됩니다. 왜냐하면
// 버퍼를 아직 해제하지 않았기 때문입니다.
if ( pStream->GetPosition( NULL ) != 0 )
{
printf( "Invalid stream position.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 버퍼를 해제.
eResult = pStream->ReleaseBuffer();
if ( eResult != AK_Success )
{
printf( "ReleaseBuffer failed.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 참고: 이제 버퍼를 해제했으니 현재 스트림 위치는 uBufferSize가 되어야 합니다.
if ( pStream->GetPosition( NULL ) != uBufferSize )
{
printf( "Invalid stream position.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
//
// 다음에서 스트림 관리자 사용자는 'loop end (반복 재생 끝)' 위치가 교차될 때까지 버퍼를 구하고 해제하게 될 것입니다.
// 위치는 GetBuffer() 다음에 쿼리되어, 버퍼의 시작을 나타냅니다.
// 사용자는 스트림 위치와 갖고 있는 버퍼 크기를 추가함으로써 반복 재생 지점이 언제 교차될 것인지를 '예측'할 수 있습니다. 여기서 위치는 반복 재생의 시작으로 되돌아가고,
// 휴리스틱을 변경하며, 파일을 끝까지 읽습니다 (GetBuffer()가 AK_NoMoreData를 반환할 때까지).
// 버퍼를 해제한 후 위치를 가져오는 것이 더 쉽지만,
// 위치를 가능한 일찍 변경하는 것이 좋습니다. Stream Manager에 반복 재생 휴리스틱이 있지만
// 구현에 따라 사용 방식이 달라집니다.
//
bool bEOF = false;
bool bDoLoop = true;
while ( !bEOF )
{
// uLoopEnd 위치를 교차시킬 때까지 폴링 읽기.
eResult = pStream->GetBuffer(
pBuffer,
uBufferSize,
false );
// 반환 코드 확인.
if ( eResult == AK_Fail )
{
assert( !"I/O error" );
return false;
}
else if ( eResult == AK_NoDataReady )
{
printf( "Starving...\n" );
}
else
{
// 파일 끝 상태.
bEOF = ( eResult == AK_NoMoreData ) && !bDoLoop;
// 반복 재생을 처리합니다. SetPosition의 첫 호출 후 bDoLoop를 false로 설정해
// 반복 재생을 중지합니다.
if ( bDoLoop )
{
// 현재 위치를 구해 한계점을 넘었는지 확인합니다.
// 스트림 관리자 사용자가 버퍼를 소유하고 있으므로 이를 재호출하고, GetPosition() 이
// 버퍼 시작의 위치를 반환합니다. 버퍼 크기를
// 그 값에 추가해 해당 버퍼의 끝 위치를 구합니다.
// 참고: GetPosition() 을 호출하지 않고 위치 정보를 계속 확인하는 것이
// 더 효율적입니다.
AkUInt32 uCurPosition = pStream->GetPosition( NULL );
if ( ( uCurPosition + uBufferSize ) > uLoopEnd )
{
// 반복 재생의 끝을 교차시킴.
// 반복 재생 시작에 위치를 설정합니다.
AkInt64 iLoopStart;
iLoopStart.HighPart = 0;
iLoopStart.LowPart = uLoopStart;
AkInt64 iRealOffset;
eResult = pStream->SetPosition(
iLoopStart,
&iRealOffset );
if ( eResult != AK_Success )
{
printf( "SetPosition failed.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 참고: 버퍼를 아직 해제하지 않았기 때문에, GetPosition() 은 아직
// 버퍼의 시작 위치를 반환해야 합니다.
// 검토.
if ( pStream->GetPosition( NULL ) != uCurPosition )
{
printf( "Invalid position returned.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
// 참고: 여기서 uLoopStart 위치는 이 스트림의 Low-Level I/O 블록 크기에
// 해당되지 않을 수 있습니다. Stream Manager 사용자는 이 제약을 관리할 책임이 있습니다.
// 여기서 iRealOffset이 0이 아닐 경우, 요청한 위치보다 작게 됩니다.
// 이 스트림 관리자의 사용자는 이 수정된 값을 로컬 변수에 저장할 수 있습니다.
// 이제 위치가 지정되었으므로, 다음 버퍼 접근은 반복 재생의 시작 지점이 됩니다.
// (수정 값 빼기).
// 최고 성능을 내기 위해, 반복 재생 휴리스틱 값을 곧바로 변경해도 됩니다.
// 더 이상 반복 재생이 없기 때문입니다.
heuristics.uLoopStart = 0;
heuristics.uLoopEnd = 0; // 0: 반복 재생하지 않음.
pStream->SetHeuristics( heuristics );
// 반복 재생 정지.
bDoLoop = false;
}
}
eResult = pStream->ReleaseBuffer();
if ( eResult != AK_Success )
{
printf( "ReleaseBuffer failed.\n" );
bSuccess = false;
goto autostmbasictest_cleanup;
}
}
// 사용자 처리량을 시뮬레이션. 대기 시간은 처리량으로 나눈 버퍼 크기입니다.
//AKPLATFORM::AkSleep( DWORD( 1000*uBufferSize / heuristics.fThroughput ) );
}
autostmbasictest_cleanup:
if ( pStream )
pStream->Destroy();
return bSuccess;
}
static IAkStreamMgr * Get()
Definition: IAkStreamMgr.h:698
AkUInt64 uLoopEnd
Set to the end of loop (byte offset from the beginning of the stream) for streams that loop,...
Definition: IAkStreamMgr.h:98
@ AK_Fail
The operation failed.
Definition: AkTypes.h:137
virtual AkUInt64 GetPosition(bool *out_pbEndOfStream)=0
virtual AKRESULT CreateStd(const AkFileOpenData &in_FileOpen, IAkStdStream *&out_pStream, bool in_bSyncOpen)=0
@ AK_DataReady
The provider has available data.
Definition: AkTypes.h:162
virtual AKRESULT ReleaseBuffer()=0
virtual void GetInfo(AkStreamInfo &out_info)=0
virtual AkUInt32 GetBlockSize()=0
AKRESULT
Standard function call result.
Definition: AkTypes.h:134
@ AK_NoDataReady
The provider does not have available data.
Definition: AkTypes.h:163
@ AK_OpenModeReadWrite
Read and write access
Definition: IAkStreamMgr.h:77
@ AK_NoMoreData
No more data is available from the source.
Definition: AkTypes.h:147
@ AK_OpenModeRead
Read-only access
Definition: IAkStreamMgr.h:74
char AkOSChar
Generic character string
Definition: AkTypes.h:60
virtual AkStmStatus GetStatus()=0
virtual AKRESULT Read(void *in_pBuffer, AkUInt32 in_uReqSize, bool in_bWait, AkPriority in_priority, AkReal32 in_fDeadline, AkUInt32 &out_uSize)=0
@ AK_MoveBegin
Move offset from the start of the stream
Definition: IAkStreamMgr.h:66
#define NULL
Definition: AkTypes.h:46
@ AK_Success
The operation was successful.
Definition: AkTypes.h:136
static const AkPriority AK_DEFAULT_PRIORITY
Default sound / I/O priority
Definition: AkTypes.h:115
virtual AKRESULT SetHeuristics(const AkAutoStmHeuristics &in_heuristics)=0
AkUInt64 uLoopStart
Set to the start of loop (byte offset from the beginning of the stream) for streams that loop,...
Definition: IAkStreamMgr.h:97
AkReal32 fThroughput
Average throughput in bytes/ms
Definition: IAkStreamMgr.h:96
@ AK_MoveCurrent
Move offset from the current stream position
Definition: IAkStreamMgr.h:67
AkForceInline void AkSleep(AkUInt32 in_ulMilliseconds)
Platform Independent Helper
virtual void Destroy()=0
virtual AKRESULT GetBuffer(void *&out_pBuffer, AkUInt32 &out_uSize, bool in_bWait)=0
virtual void GetHeuristics(AkAutoStmHeuristics &out_heuristics)=0
int64_t AkInt64
Signed 64-bit integer
uint64_t AkUInt64
Unsigned 64-bit integer
@ AK_StmStatusCompleted
Operation completed / Automatic stream reached end
Definition: IAkStreamMgr.h:54
AkStmStatus
Stream status.
Definition: IAkStreamMgr.h:52
AkUInt64 uSize
Total stream/file size in bytes
Definition: IAkStreamMgr.h:88
virtual AKRESULT Start()=0
virtual AKRESULT SetPosition(AkInt64 in_iMoveOffset, AkMoveMethod in_eMoveMethod)=0
virtual AKRESULT SetPosition(AkInt64 in_iMoveOffset, AkMoveMethod in_eMoveMethod)=0
uint32_t AkUInt32
Unsigned 32-bit integer
@ AK_StmStatusError
The low-level I/O reported an error
Definition: IAkStreamMgr.h:57
virtual AKRESULT CreateAuto(const AkFileOpenData &in_FileOpen, const AkAutoStmHeuristics &in_heuristics, AkAutoStmBufSettings *in_pBufferSettings, IAkAutoStream *&out_pStream, bool in_bSyncOpen, bool in_bCaching=false)=0
virtual void Destroy()=0
AkPriority priority
The stream priority. it should be between AK_MIN_PRIORITY and AK_MAX_PRIORITY (included).
Definition: IAkStreamMgr.h:103
virtual void Cancel()=0
virtual AKRESULT Write(void *in_pBuffer, AkUInt32 in_uReqSize, bool in_bWait, AkPriority in_priority, AkReal32 in_fDeadline, AkUInt32 &out_uSize)=0
Automatic streams heuristics.
Definition: IAkStreamMgr.h:95
virtual AkUInt64 GetPosition(bool *out_pbEndOfStream)=0
@ AK_StmStatusCancelled
Operation cancelled
Definition: IAkStreamMgr.h:56

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

지원이 필요하신가요?

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

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

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

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

Wwise를 시작해 보세요