バージョン

menu_open

高レベルストリームマネージャAPI概要

イントロダクション

Wwise サウンドエンジンは、ストリームマネージャを使用してサウンドバンクをロードし、ストリーミングされたオーディオファイルを読み込みます。I/Oマネージャをお持ちでない場合は、これをあらゆるゲーム I/O にご使用いただくことが可能です。高レベルストリームマネージャをクライアントとして直接使用せず、低レベル I/O フックの実装により Wwise I/O をゲームに統合する場合には、この章を読み飛ばしていただいても結構です。

ストリームマネージャ メインインターフェイス

ストリームマネージャのメインインターフェイスは、AKIAkStreamMgr により定義されますが、これは基本的にストリーミングオブジェクトのファクトリです。

ストリームマネージャのインスタンス化

ストリームマネージャを使用する前に、これをインスタンス化する必要があります。

Default Streaming Manager Information

ストリームマネージャのインスタンス化は、実装固有のものです。従って、ストリームマネージャの Audiokinetic デフォルト実装ファクトリ関数は、AkStreamMgrModule.h: AK::StreamMgr::Create()で定義されています。これに実装固有の設定を渡す必要があります(初期設定に関する詳細は、Audiokinetic ストリームマネージャ初期化設定 を参照してください)。

高レベルストリーミングデバイス

Default Streaming Manager Information

ストリームオブジェクト作成と使用の前に、高レベルストリーミングデバイスを作る必要があります。この概念はAudiokineticストリームマネージャのデフォルト実装に固有のものです。高レベルストリーミングデバイスは、基本的に独自のスレッドで実行されるI/Oスケジューラ実装で、これに関連付けられているストリームオブジェクトを一元化し、データ転送要求を低レベル I/Oにポストします。一般に高レベルデバイスは、単にデバイスと呼ばれます。特定にフラグおよび設定を持つ1つ以上のデバイスが、好ましくは初期化時にゲームによって作成されます。初期設定に関する詳細は、Audiokinetic ストリームマネージャ初期化設定 のセクションをご覧ください。AKStreamMgr::CreateDevice() は、デバイスIDを返しますが、これはファイルロケーションおよびデバイス割り当てのために低レベルI/Oによって保持される必要があります。(更なる詳細は、ファイルロケーションの解決 のセクションをご覧ください。)正しい終了のためには、このデバイスIDが AK::StreamMgr::DestroyDevice() に渡されなければなりません。実装固有の全ての関数については、AKStreamMgr::CreateDevice()AK::StreamMgr::DestroyDevice() は、ヘッダ AkStreamMgrModule.h で定義されています。

ストリームの作成

ストリームマネージャ メインAPIの多くはストリームファクトリメソッドのセットです。ファイル識別子と設定を与えられ、これらはストリームオブジェクトを作成し、そのストリームにインターフェイスを戻します。すべてのストリーム操作は、このインターフェイスを介して実行されます。 ストリーム作成メソッドの両セットはオーバーロードを2つ持ちますが、これらは使用するファイル識別方式に応じて異なります。1つ目のオーバーロードは、ファイルの識別に文字列を使用しますが、2つ目のオーバーロードは、整数IDを使用します。

Default Streaming Manager Information

AK::IAkStreamMgr::CreateStd() または AK::IAkStreamMgr::CreateAuto() 文字列ファイルオーバーロードを使用する場合、ストリームマネージャは、この文字列を AK::StreamMgr::IAkFileLocationResolver::Open() の文字列オーバーロードに転送します。同様に、IDファイル識別子のオーバーロードを呼び出すと、AKStreamMgr::IAkFileLocationResolver::Open() のIDオーバーロードへの呼び出しがトリガされます。詳細は、 ファイルロケーションの解決 セクションをご覧ください。

Note.gif
Note: 低レベルI/OインターフェースのOpen() メソッドは、ファイル識別子からファイル記述子を作成します。

ストリーム作成メソッドは、ファイル識別子に加えて、ファイルロケーション用フラグを含む AkFileSystemFlags 構造体へのポインタを受け付けます。

Default Streaming Manager Information

このポインタはそのまま低レベルI/Oへ渡されます。AkFileSystemFlags に関する詳細およびサウンドエンジンがどのようにこれらを使用するかについては、サウンドバンクストリーミングオーディオファイル基本的なファイルロケーション のセクションをご覧ください。

ストリーム作成メソッドには、追加引数があります:in_bSyncOpen。ユーザーは(サウンドエンジンと同じように)、ストリームオブジェクトでラップされたファイルハンドルがこのコール中にオープンされる必要がある場合には、trueを渡します。falseを渡すと、Stream Managerによるファイルオープン延期を許可することになります。延期は発生する可能性も発生しない可能性もあります。延期が発生して、ファイルオープンに失敗した場合、AKIAkStdStream::Read() または AK::IAkAutoStream::GetBuffer() への次のコールはAK_Failを返します。

Note.gif
Note: これは、致命的なエラーとみなされるべきではありません。サウンドエンジンはストリームされたオーディオファイルに対して false を渡します。GetBuffer() が false を返すと、これは I/O エラーとみなされ、ストリームが正常に破棄されます。
Default Streaming Manager Information

引数 in_bSyncOpenは、低レベルI/Oに直接渡されます。低レベルI/Oのこのフラグの影響についての詳細は 遅延オープン を参照してください。

AK::IAkStreamMgr::CreateStd() は、標準ストリームを作成し、AKIAkStreamMgr::CreateAuto() は自動ストリームを作成します。これらの2タイプのストリームについて、以下のセクションで解説しています。

ストリームの破棄

どちらのタイプのストリームオブジェクトも Destroy() メソッドを定義します。ストリームにより使用されているリソースを解放するためには、このメソッドを呼び出す必要があります。これを実行した後には、オブジェクトを再び使用することはできません。

Default Streaming Manager Information

ストリームマネージャのデフォルト実装では、AKIAkStdStream::Destroy() とAKIAkAutoStream::Destroy() は単にストリームオブジェクトにフラグを設定するために使用されます。実際の破棄は、I/Oスレッドによって後から実行されます。これは、ウェイクアップのたびに、すべてのオープンストリームを点検し、I/O要求が低レベルI/Oへ発行されるための次のストリームを見つけ、すべての終了したストリームをクリーンアップします。メモリが解放されるのは、この時点のみで、(ファイルハンドルを解放する)AKStreamMgr::IAkLowLevelIOHook::Close() が呼び出されます。

破棄が予定されているストリームオブジェクトは、低レベルI/O で保留されている所有I/O転送がなくなったらすぐに解放されるよう、I/Oスレッドに信号を送ります。

ストリームプロファイリングが発生している場合、I/Oスレッドは、監視スレッドによる承認を待ってからストリームオブジェクトを破棄します。監視スレッドは、200ミリ秒ごとにプロファイリングパスを実行します。

標準ストリーム

AK::IAkStreamMgr::CreateStd() への呼び出しにより、返される AK::IAkStdStream インターフェースを介して制御可能である標準ストリームオブジェクトが作成されます。

定義

標準ストリームは、基本的なリード/ライト方式を使用してI/O動作を制御します。AKIAkStdStream::Read() または AK::IAkStdStream::Write() が呼び出されると、I/O要求がスケジューラにキューイングされ、ストリームはそのステータスを AK_StmStatusPending に設定します。ユーザーは、要求転送サイズとバッファのアドレスを渡します。完了すると、ストリームはそのステータスをAK_StmStatusCompleted または  AK_StmStatusError に設定します。その位置は、実際に転送されるサイズによってインクリメントされるので、次の操作は新しい位置で発生します。別の位置を強制するには、新しい転送を開始する前に、ユーザーが AK::IAkStdStream::SetPosition() メソッドを使用する必要があります。

同時に発生可能なI/O操作は1つのみです。後続の操作は、AKIAkStdStream::Read() または AK::IAkStdStream::Write()を呼び出すことによって、明示的に開始される必要があります。ユーザーがストリームの使用を終えた時は、AKIAkStdStream::Destroy() を呼び出す必要があります。これにより、インターフェースが無効になります。

Default Streaming Manager Information

リードまたはライトのコールは、低レベルI/OのI/O転送につながります。クライアントレベルで要求されるサイズがストリーミングデバイスの粒度(AkDeviceSettingsuGranularity)より大きい場合、転送が分割されます。ストリームは、全転送が完了するまで、または、エラーまたはエンドオブファイル状況が発生するまで、AK_StmStatusPending または AK_StmStatusIdle 状態のままになります。

インターフェースは、クエリ設定、現在のステータスまたは位置へのアクセス、および以前の操作に供給されるバッファへのアクセスなど、他のメソッドを公開します。

標準ストリームデータアクセス方式

標準ストリーム操作を起動する方法は2つあります:

  • ブロッキング:AKIAkStdStream::Read() と AK::IAkStdStream::Write() の in_bWait パラメータが true の場合、I/Oが完了した後でのみメソッドが戻ります。
  • ポーリング:ユーザーは、AKIAkStdStream::Read() とAKIAkStdStream::Write() を(in_bWait Falseで)非同期に呼び出し、これがAK_StmStatusCompletedを返すまで、AKIAkStdStream::GetStatus() を使用してストリームのステータスをポーリングすることができます。この後データは AK::IAkStdStream::GetData() を介してアクセス可能です。

作成設定

標準ストリームを作成する時には、オープンモード(AkOpenMode)を指定する必要があります。オープンモードは、ストリームがリードまたはライトのどちらに使用されるのか、または、リードとライトの両方に使用されるのかを指定します。リードのみのためにオープンされたストリームへの AK::IAkStdStream::Write() の呼び出しは、明らかに失敗してしまいます。

ストリームの名前を AK::IAkStdStream::SetStreamName() を使用して指定することができます。文字列がストリームオブジェクトにコピーされ、 AK::IAkStdStream::GetInfo() でクエリすることが可能です。

Default Streaming Manager Information

ストリーム名は、Wwise の Advanced Profiler ストリーミングタブに表示されるものです。

ヒューリスティック

標準ストリームのヒューリスティックは、操作ごとに指定されています。

AK::IAkStdStream::Read()AK::IAkStdStream::Write() には、操作の優先度と(ミリ秒単位での)期限が必要です。一般的には、Stream Manager は、期限の短い操作を好みます。ストレージデバイスが提供できるより大きなI/O帯域幅をアプリケーションが必要とする場合、優先度の高いストリームが好まれます。期限をゼロに指定すると、データが今必要とされていることを意味し、I/O は既に遅れています。このような場合には、優先度の低い他のストリームより前に処理されます。

Note.gif

Note: サウンドエンジンのBank Manager(バンクマネージャ)は、標準ストリームを使用します。SoundBank データを独自のバッファに読み込んで解析します。バンクローディングの平均スループットと優先度を指定するためのメソッドが1つサウンドエンジンAPIで公開されています:AKSoundEngine::SetBankLoadIOSettings()。(詳細は、Wwise Sound Engine の Banks を参照してください。)バンクマネージャは、AKIAkStdStream::Read() を呼び出し、これが完了するとデータを解析して読み取ります。これには、このメソッドの引数として与えられた優先度が使用されます。操作の期限が、ユーザーにより指定されたスループットから計算されます:

fDeadline = uBufferSize / fUserSpecifiedThroughput;

標準ストリーム操作の期限と自動ストリームの平均スループットのデッドラインは、Stream Manager のI/Oスケジューラと等価です。サウンドエンジンの使用により、I/O上でのバンクロードの負荷を、オーディオストリームやゲーム内の他のストリームの負荷と調整することができます。

一般的な注意点とその他の考慮事項

ブロックサイズ

リードおよびシークの粒度やバッファアラインメントへの低レベルでの制約は、Stream Manager レベルでは“ブロックサイズ”と呼ばれます。ストリームのインターフェース上のAKIAkStdStream::GetBlockSize() メソッドは、低レベルI/Oをクエリし、これにストリームに関連付けられたファイル記述子を渡して、呼び出し元にその値を返します。ファイル記述子に関する詳細は、低レベルI/OのセクションとAKStreamMgr::IAkLowLevelIOHook::GetBlockSize() メソッドを参照してください。ストレージデバイスの中には、データ転送サイズへの制約があるものもあります。例えば、Win32プラットフォームでは、FILE_FLAG_UNBUFFERED フラグで開かれるファイルは、物理デバイスのセクタサイズのみの倍数の転送サイズを可能にします。リードまたはライトの操作がブロックサイズの倍数ではない転送サイズを要求した場合には、これらの操作は失敗します。高レベルインターフェースのユーザーは、AKIAkStdStream::GetBlockSize() に返される値の倍数であるリードサイズを要求しなければなりません。AKIAkStdStream::SetPosition() は、同様の制約を受けます。しかしながら、これは自動的に下位のブロック境界にスナップし、実際のファイル位置オフセットを返します。

一般的に、ゲームタイトルでは、Stream Manager が使用されます。ゲームタイトルは、低レベルI/Oサブモジュールも実装するので、使用可能な転送サイズを認識しています。サウンドエンジンは、ストレージデバイスの制約を認識しないので、常に標準ストリームのリードサイズをブロックサイズの境界までスナップします。

ストリームステータス保留中に呼び出されるメソッド

AK::IAkStdStream::Read()AK::IAkStdStream::Write() は、ストリームが AK_StmStatusPending ステートにある間に呼び出された場合には失敗します。AKIAkStdStream::GetPosition()AK::IAkStdStream::SetPosition() は、I/O転送が完了する前または後で発生する可能性があるので、不定な結果をもたらします。しかし、これらがI/Oでブロックすることはありません。

AK::IAkStdStream::Cancel() は、転送の保留中に使用することが可能ですが、パフォーマンスが低下するので、これの使用はお薦めできません。このメソッドは、保留されているI/Oがないことを呼び出し元に確認します。低レベルI/Oに要求が送信される前に呼び出しが実行されると、タスクがキューから除去されます。しかしながら、既に低レベルI/Oへ要求が送信されている場合には、I/Oが完了するまで呼び出し元がブロックされます。

Tip.gif
Tip: ユーザーは、ストリームを破棄(AKIAkStdStream::Destroy())する前に AK::IAkStdStream::Cancel() を明示的に呼び出す必要はありません。しかし、最良のパフォーマンスを得るためにも、ストリームが AK_StmStatusPending ステートにない場合にのみ、ユーザーは Destroy() を呼び出すべきです。

自動ストリーム

AK::IAkStreamMgr::CreateAuto() を呼び出すと、返される AK::IAkAutoStream インターフェースを介して制御可能である自動ストリームオブジェクトが作成されます。

定義

自動ストリームは、入力のみに使用されるストリームです。I/O要求が、ユーザーによる明示的な関数呼び出しなしで、低レベルI/Oへ “フードの下で” 送信されるため自動ストリームと呼ばれています。ストリーミングメモリは Stream Manager に所有されており、ストリーミングされたデータには、ストリーミングメモリアドレスを求めることによりアクセス可能です。ストリーミングメモリの一領域がユーザーに許可されると、これが明示的に解放されるまでロックされたままになります。一方、内部スケジューラは、ロックされていないメモリ内でデータ転送を実行します。ストリームは、作成時にはアイドル状態です。I/O要求の自動スケジューリングは、ユーザーが AK::IAkAutoStream::Start() を呼び出すと開始します。これは、AKIAkAutoStream::Stop() の呼び出しにより停止または一時停止、AKIAkAutoStream::Start() の再度呼び出しにより再開することが可能です。

Caution.gif
Caution: ストリームが停止している間は、GetBuffer() を呼び出さないでください。ストリームからバッファを取得する前に必ず Start() を呼び出す必要があります。

データは、AKIAkAutoStream::GetBuffer() への呼び出しによりアクセス可能です。低レベルI/Oから既にデータが読み出されている場合、このメソッドは AK_DataReady、このデータを含むバッファのアドレス、およびそのサイズを返します。バッファがもはや必要とされない場合は、これをAKIAkAutoStream::ReleaseBuffer() への呼び出しにより解放することができます。続いて、ストリームの位置は、解放されたバッファのサイズだけインクリメントされます。ユーザーは、AKIAkAutoStream::SetPosition() への呼び出しにより新しい位置を強制することができます。AKIAkAutoStream::GetBuffer() への次の呼び出しは、この位置と一致します。

Caution.gif
Caution: ストリームの位置を変更すると、一部データのフラッシュが発生する場合があります。自動ストリームはシーケンシャルアクセス用に最適化されています。ループを指定するヒューリスティックがあり、これにより Stream Manager は、ストリーミングメモリを効率的に管理することができます。詳細は、一般的な注意点とその他の考慮事項 section セクションをご覧ください。
Caution.gif
Caution: 低レベル I/O がエラーを報告すると、ストリームがエラーモードになります。自動ストリームはエラー状態から回復することができないため、AKIAkAutoStream::GetBuffer() は常に AK_Fail を返します。

自動ストリームデータへのアクセス法

自動ストリーム内のデータへは次の2つの方法でアクセス可能です:

AK::IAkAutoStream::GetBuffer() には、4つのリターンコードがあります:

  • AK_DataReady
  • AK_NoMoreData
  • AK_NoDataReady
  • AK_Fail

ユーザーが、データで満たされたバッファを付与されている場合、メソッドは AK_DataReady または AK_NoMoreData を返します。ストリームバッファが空の場合、AKIAkAutoStream::GetBuffer() は、サイズがゼロおよびバッファアドレスが null で AK_NoDataReady を返します。低レベルI/Oがエラーを報告した場合、または、無効なパラメータでメソッドが呼び出された場合には、AK_Fail が返されます。

ファイルの最後のバッファが付与されると AK_DataReady の代わりに AK_NoMoreData が返されます。Stream Manager は、(ユーザーから見た)現在の位置を、ファイル記述子構造体のメンバとして低レベルI/Oにより提供されるファイルサイズと比較することにより、これが最後のバッファであると判断します。AKIAkAutoStream::GetBuffer() への後続の呼び出しは、サイズゼロで AK_NoMoreData を返します。

AK::IAkAutoStream::GetBuffer() を呼び出すたびに、新しいバッファが提供されます。バッファは、AKIAkAutoStream::ReleaseBuffer() への呼び出しにより明示的に解放される必要があります。これらのバッファは、GetBuffer() によって付与された順番に解放されます。バッファが解放されると、そのアドレスは無効になります。ReleaseBuffer() は、クライアントがバッファを保持していない場合には、AK_Fail を返します。しかし、これは致命的エラーとみなされるべきではありません。

Tip.gif

Tip: 可能であれば、一度に一つのバッファのみを取得するようにしてください。これにより、Stream Manager はI/Oを実行するためのスペースをより多く得ることができます。複数のバッファの同時使用は、主にリング/ダブルバッファへのアクセスを必要とするハードウェアまたはその他のインターフェース向けです。特定のストリームに対して複数のバッファを使用する場合は、適切なヒューリスティック(AkAutoStmHeuristicsuMinNumBuffers)を渡すことにより通知する必要があります。

Caution.gif
Caution: 自動ストリーム使用時によくあるミスは、ReleaseBuffer() の呼び出しを忘れることです。

GetBuffer() への呼び出しに成功し付与されたバッファサイズは、Stream Manager により決定されます。下層ファイルの末尾に到達した場合以外は、ブロックサイズの倍数でなければなりません。また、作成時に渡されたバッファ制約(AkAutoStmBufSettings 参照)を使用して、GetBuffer() により返されるサイズを強制することも可能です。

Caution.gif
Caution: バッファサイズを強制すると、ストリーミングメモリと/または帯域幅が浪費される可能性があるので、パフォーマンスが最適以下になる場合があります。また、ユーザー指定の制約はサポートされない場合があります。IAkStreamMgrCreateAuto() は、AK_Fail を返します。
Default Streaming Manager Information

通常、ストリームが終わりに達した場合を除き、このサイズは高レベルデバイスの作成設定で指定された粒度に対応します。

ブロッキング

in_bWait フラグを True に設定した状態での AK::IAkAutoStream::GetBuffer() への呼び出しは、データの準備ができるまでユーザーをブロックします。従って、メソッドは AK_NoDataReady を返すことができません。最終バッファが既に付与・解放されている場合には、メソッドはサイズゼロで AK_NoMoreData を直ちに返します。

ポーリング

ユーザーは、 in_bWait フラグを False に設定して呼び出された AK::IAkAutoStream::GetBuffer() によって返されるコードを評価することで、データを取得することができます。AK_NoDataReady が返された場合、他のタスクを行い、しばらくしてから再度試してみてください。

作成設定

自動ストリームは、標準ストリームより多くの設定でインスタンス化されますが、特にバッファサイズ制約とヒューリスティックが重要です。これらの設定により、Stream Manager は最適メモリ量を割り当て、データ転送要求に優先順位をつけることができます。

バッファ制約

一部の設定は、ユーザーにより指定されるメモリ関連の制約です。バッファサイズは直接指定することが可能です。Stream Manager にバッファサイズを選択させたいけれども制限内にとどめたい場合は、最小限のバッファサイズまたはブロックサイズ、あるいは両方を指定することができます。バッファサイズはブロックサイズの倍数になります。

Tip.gif
Tip: バッファ制約はオプションであり、通常 Stream Manager がストリーミングメモリを最適に管理できるようにすることを目的で使用するべきではありません。サウンドエンジンは、一部プラットフォーム上でのデコードハードウェアを使用する時に、バッファ制約を使用します。これらのデコーダは、特定のバイトアラインメント(ブロックサイズ)と最小バッファサイズで、一度に2つのバッファにアクセスする必要があります。

Default Streaming Manager Information

現在の実装では、カスタム自動ストリームバッファサイズがデバイス粒度を超えることはできません。(粒度に関する詳細は Audiokinetic ストリームマネージャ初期化設定 を参照。)

ヒューリスティック

Stream Manager によるスケジューリングとメモリ割り当て実行を支援するためにヒューリスティックが提供される必要があります。自動ストリームヒューリスティックは、作成時にストリームごとに指定されますが、最適な動作のために非常に重要です。AKIAkAutoStream::GetHeuristics() や AK::IAkAutoStream::SetHeuristics() を介して、いつでもクエリや変更が可能です。

  • スループット:平均スループットは、特定のストリームに対して既に利用可能なデータをどれだけ持っているか認識しているため、I/Oスケジューラのデッドラインとして変換します。これは、標準ストリームの操作ごとのデッドラインヒューリスティックに相当します。
  • プライオリティ(優先度):これは通常、アプリケーションがストレージデバイスが提供できるより大きい帯域幅を必要とする場合に使用されます。この場合、Stream Manager は優先度の高いストリームの要求を履行します。標準ストリームの操作ごとの優先ヒューリスティックに相当します。
  • ループ:ループするストリーム内でループ位置を指定することができます。これにより、Stream Manager のメモリ管理を支援することができます。ループが解放された場合、ヒューリスティックを “非ループ” に設定(uLoopEnd を 0 に設定)することをお勧めします。これは単なるヒューリスティックであることに注意してください:ストリーム位置は、クライアントが SetPosition() を呼ばない限り変更されません。しかし、ループヒューリスティックに関する予期しない位置の変更は、通常データのフラッシュにつながります。
  • uMinClientBuffers: これは、クライアントが同時にどれぐらいのバッファを保持しようとしているかを示します。通常、バッファは同時に1つだけ保持されるべきです。時として、同時に2つのバッファを保持するよう要求される場合があります。この場合には、uMinClientBuffers を2に設定してください。Stream Manager は少なくとも( uMinClientBuffers + 1)のバッファを使用してストリームをバッファリングする必要があります。uMinClientBuffers(デフォルト値)に 0 を指定するのは 1 と同等であることに注意してください。

一般的な注意点とその他の考慮事項

ブロックサイズ

Stream Manager は、バッファサイズがブロックサイズの倍数であることを確認するために、バッファサイズの選択に先立って特定ファイルのブロックサイズを低レベル I/O にクエリします。従って、自動ストリームのユーザーは、ストリームを別の位置へ強制しない限り、低レベルのブロックサイズを意識する必要はありません。この場合、AKIAkAutoStream::GetBlockSize() から取得したブロックサイズ、または、 AK::IAkAutoStream::SetPosition() が返す実際の絶対オフセットを考慮する必要があります。

ストリーム位置

ストリーム位置は、ユーザーの観点から評価され、Get/SetPosition() メソッドによりクエリおよび設定されます。Stream Manager のバッファへ低レベル I/O から転送されるデータは、位置の計算では考慮されません。このデータは、バッファがユーザーにより解放される時に更新されます。AKIAkAutoStream::SetPosition() が呼び出された時に、低レベル I/O から既に転送されてしまったデータは、通常フラッシュされます。

Note.gif

Note: ユーザーは、可能な限り AK::IAkAutoStream::SetPosition() の呼び出しを避けてください。これを使用する必要がある場合は、できるだけ早期に呼び出す必要があります。例えば、サウンドエンジンで、ループ中のサウンドソース(音源)は、Stream Manager からバッファを取得する時に、ループエンドがこのバッファに含まれているかどうかを確認します。そうであれば、AKIAkAutoStream::SetPosition() が直ちに(つまり、ReleaseBuffer() のかなり前に)呼び出され、無用なデータをストリーミングするリスクを最小限に抑えようとします。また、ループヒューリスティックを指定すると、内部スケジューラが低レベル I/O への要求に関してより優れた意思決定をできるよう支援することができます。

Tip.gif

Tip: AK::IAkAutoStream::SetPosition() は、バッファがロックされた状態でもロックされていない状態でも呼び出し可能ですが、無用にデータをストリーミングするリスクを最小限に抑えるため、できるだけ早期の位置変更をお勧めします。ストリームを適切に停止することで、帯域幅の浪費を最小限に抑えることができます。

AK::IAkAutoStream::SetPosition() は、クライアントに保持されているバッファを解放しません。クライアントは、ReleaseBuffer() を明示的に呼び出すことにより、バッファがいつ解放されるかを決定します。AKIAkAutoStream::SetPosition() は、実際に GetBuffer() への次の呼び出しに対してクライアントが期待する位置を示します。

AK::IAkAutoStream::GetPosition() は、現在クライアントが保持している最初のバッファの位置を返します。クライアントがバッファを保持していない場合、GetBuffer() への次の呼び出しで付与されるバッファの位置を返します。

AK::IAkAutoStream::GetBuffer() のAK_NoMoreData リターンコードは、ストリームの位置から決定されます。AKIAkAutoStream::GetPosition() に返される out_bEndOfStream オプションフラグも、ストリーム位置にバインドされています。これは、以下のような場合のみ True を返します:

  • uLoopEnd ヒューリスティックが0(ループしない)に設定されている。
  • 低レベルI/Oから転送されるデータがない。
  • ユーザーがすべてのバッファを解放した。

保留中の低レベル操作でブロックするメソッド

自動ストリームAPIは、AKIAkAutoStream::GetBuffer() 呼び出しをブロックするのを除き、ユーザーが低レベル I/Oとの保留中のトランザクションを決してブロックしないようにデザインされています。AKIAkAutoStream::Destroy() さえもブロックされません。通常、ストリームが破棄されたにも関わらず、低レベルI/Oと相互作用をしている場合、低レベルI/Oとの現在の転送が終了するまでこのストリームは内部で行き続けます。

Manager のオーバーライド

Stream Manager 全体をオーバーライドするには、ゲームタイトルは IAkStreamMgr.h で定義されているすべてのインターフェースを実装している必要があります。実装は、このセクションの他の部分で説明されている各規則に従わなければなりません。

スタティックライブラリ AkStreamMgr.lib をゲーム独自のスタティックライブラリと置き換えることができます。作成関数やその他の実装に固有な設定や定義、例えば低レベルI/O APIなどは、AkStreamMgrModule.h にあるので、Stream Manager の一部ではありません。

サウンドエンジンとのリンケージ

サウンドエンジンは、インラインスタティック AK::IAkStreamMgr::Get() メソッドを呼び出すことで Stream Manager にアクセスします。これは、AKIAkStreamMgr のプロテクトメンバとして定義されている、AKIAkStreamMgr インターフェースへのポインタを返します。カスタム実装が宣言する必要があるのは変数1つのみで(AKIAkStreamMgr::m_pStreamMgr)、リンケージは自動的に行われます。Stream Manager とサウンドエンジンが、同一の実行可能ファイルまたはDLL内でリンクされていない場合は、AKIAkStreamMgr::m_pStreamMgr が __dllexport 属性を持っている必要があります。AKSTREAMMGR_API マクロは、カスタム Stream Manager ライブラリのコンパイラ設定で定義されている AKSTREAMMGR_EXPORTS と一緒に使用可能です。

プロファイリング

プロファイリングインターフェースも、サウンドエンジンの非 AK_OPTIMIZED バージョンとのリンクを可能にするために実装される必要があります。このインターフェースはかなり自己説明的です。これを実装することで、Wwiseでのプロファイリングが可能になります。プロファイリングが必要でない場合は、AKIAkStreamMgr::GetStreamMgrProfile() がNULLを返し、インターフェースは空のコードで実装されます。この場合、Wwise では、プロファイリング情報は Wwise に表示されません。

サンプルコード

以下のコードは、文章で説明するよりもコードでの方がより良く説明可能ないくつかの考慮事項を示すためのもので。Stream Manager の実装をオーバーライドまたは変更する場合に特に便利です。このコードは、特に注意する必要のある事項を指摘できるよう、故意にボリュームのあるものになっています。

// Note: For the sake of simplicity, we assume that the test file is smaller than 4Gb (its
position fits on 32 bits).

// Standard stream usage and considerations.
// This function returns true if the test is successful (if the Stream Manager behaves
correctly).
bool BasicStdStreamTest(
    const AkOSChar * in_pszFilename
    )
{
    // Basic standard stream test.
    bool bSuccess = true;
    AkUInt64 iCurPosition;
    const AkInt64 POSITION_BEGIN = 0;

    // Create a stream object for reading.
    AK::IAkStdStream * pStream;
    AKRESULT eResult = AK::IAkStreamMgr::Get()->CreateStd(
        in_pszFilename,     // Application stream identifier (file name).
        NULL,               // No file system flags: we provide the full path and do not need any file location resolving logic.
        AK_OpenModeRead,    // Creation setting: Open Mode.
        pStream,            // Returned stream interface.
        true );             // Require synchronous file open.
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Failed creating stream.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Provide memory.
    const int BUFFER_SIZE = 8192;
    unsigned char pBuffer[BUFFER_SIZE];

    // Before attempting any read, users who are unaware of the implementation of the Low-Level I/O module
    // should ensure their read sizes are consistent with the block size constraint. This test assumes
    // that BUFFER_SIZE is a multiple of the block size. If it is not, we do not continue.
    if ( BUFFER_SIZE % pStream->GetBlockSize() != 0 )
    {
        printf( "Low-Level storage device requirements are not compatible with this test.\n");
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    //
    // Read BUFFER_SIZE bytes in buffer. Blocking read.
    //
    AkUInt32 uSizeTransferred;
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Test: If we did not reach the end of file, size read should equal size requested. Verify.
    bool bReachedEOF;
    pStream->GetPosition( &bReachedEOF );
    if ( !bReachedEOF &&
         uSizeTransferred != BUFFER_SIZE )
    {
        // This cannot happen.
        printf( "Inconsistent transfer size.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Note: the current stream position should be BUFFER_SIZE. In order for this test to properly
    // compare data, the input file should be big enough. If we already reached the end of file, leave now.
    if ( bReachedEOF )
    {
        printf( "Input file not big enough. Could not complete test.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    //
    // Read BUFFER_SIZE bytes in buffer. Non-blocking read.
    //
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Non-blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of
        course).
        uSizeTransferred );
    // Check code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Poll until data is ready.
    AkStmStatus eStatus = pStream->GetStatus();
    while ( eStatus != AK_StmStatusCompleted && eStatus != AK_StmStatusError )
    {
        // Do something else...
        AKPLATFORM::AkSleep(0);
    }

    // Check status.
    if ( pStream->GetStatus() != AK_StmStatusCompleted )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Check again that we did not reach EOF, because data comparison would fail.
    pStream->GetPosition( &bReachedEOF );
    if ( bReachedEOF )
    {
        printf( "Input file not big enough. Could not complete test.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }


    //
    // SetPosition to ABS_POSITION (absolute from the beginning of the file), and read BUFFER_SIZE bytes in buffer.
    //
    AkInt64 ABS_POSITION = 12000;
    AkInt64 iRealOffset;
    eResult = pStream->SetPosition(
        ABS_POSITION,
        AK_MoveBegin,
        &iRealOffset );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "SetPosition failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // NOTE: Use the returned Real Offset. If the Low-Level I/O specifies a block size greater than 1,
    // the Stream Manager will snap the seek to the lowest boundary (for example, if seek was 9001 and block
    // size was 2000, the position would be set to 8000, and so would be iRealOffset).
    printf( "Set position to %u, low-level block size is %u, actual position set to %u\n",
        ABS_POSITION,
        pStream->GetBlockSize(),
        iRealOffset );

    // Read.
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }
    // Check again that we did not reached EOF, because data comparison would fail.
    pStream->GetPosition( &bReachedEOF );
    if ( bReachedEOF )
    {
        printf( "Input file not big enough. Could not complete test.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Keep the current absolute position.
    iCurPosition = pStream->GetPosition( NULL );

    //
    // SetPosition backwards, -REL_POSITION relative to current position
    //
    AkInt64 REL_POSITION = -8000;
    eResult = pStream->SetPosition(
        REL_POSITION,
        AK_MoveCurrent,
        &iRealOffset );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "SetPosition failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // NOTE: If the block size is greater than 1, the real absolute move offset could be greater than REL_POSITION
    // (iRealOffset <= REL_POSITION).

    // NOTE: The new position should be about ABS_POSITION+BUFFER_SIZE+REL_POSITION bytes relative to the beginning
    // of the file.
    // iCurPosition contains the "real" ABS_POSITION, and iRealOffset now contains the "real" REL_POSITION,
    // that is, the real move offset. Therefore, GetPosition(), which always returns the absolute position,
    // should equal 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 );

    // Read from there.
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of
        course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }


    //
    // Cancelling an operation
    //
    // Read.
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of
        course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Cancel.
    pStream->Cancel();

    // Here, either the Stream Manager had time to send and complete the request to low-level
    // (status would be AK_StmStatusCompleted), or it removed it from the queue of pending operations
    // (status would be AK_StmStatusCancelled).
    // In either case, we can be sure that no operation is pending for our stream.
    if ( pStream->GetStatus() != AK_StmStatusCompleted &&
         pStream->GetStatus() != AK_StmStatusCancelled )
    {
        printf( "Inconsistent status after operation cancellation.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }


    //
    // Writing.
    //
    // Read.
    eResult = pStream->Read(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Write back to another file (blocking).
    // Create stream.
    AK::IAkStdStream * pWrStream;
    eResult = AK::IAkStreamMgr::Get()->CreateStd(
        "out.dat",
        NULL,                   // We could use this argument to tell the Low-Level I/O that this file needs to
                                // be created in another storage device (one that supports writing).
        AK_OpenModeReadWrite,
        pWrStream,
        true );                 // Require synchronous file open.
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Failed creating stream for writing.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }
    eResult = pWrStream->Write(
        pBuffer,
        BUFFER_SIZE,
        true,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success ||
         uSizeTransferred != BUFFER_SIZE )
    {
        printf( "Write failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }
    // Set position to beginning, and read back file in another buffer to compare data.
    eResult = pWrStream->SetPosition(
        POSITION_BEGIN,
        AK_MoveBegin,
        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,                   // Blocking
        AK_DEFAULT_PRIORITY,    // Default priority.
        0,                      // Deadline is 0: data is requested NOW (will be late, of course).
        uSizeTransferred );
    // Check return code.
    if ( eResult != AK_Success ||
         uSizeTransferred != BUFFER_SIZE )
    {
        printf( "Read failed.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

    // Compare data.
    if ( memcmp( pBuffer, pBufferCheck, uSizeTransferred ) != 0 )
    {
        printf( "Data read and written do not match.\n" );
        bSuccess = false;
        goto stdstmbasictest_cleanup;
    }

stdstmbasictest_cleanup:

    // Close streams.
    if ( pStream )
        pStream->Destroy();
    if ( pWrStream )
        pWrStream->Destroy();

    return bSuccess;
}


// Automatic streams usage and considerations.
// This function returns true if the test is successful (if the Stream Manager behaves correctly).
bool BasicAutoStreamTest(
    const AkOSChar * in_pszFilename
    )
{
    // Create an automatic stream.
    bool bSuccess = true;
    AK::IAkAutoStream * pStream;

    // Set heuristics.
    AkAutoStmHeuristics heuristics;
    heuristics.fThroughput  = 1048576;  // 1 Mb/s
    // Note: Since this will be the only stream created in the Stream Manager, throughput has no effect.
    // The scheduler will always choose this stream for I/O.
    heuristics.uLoopStart   = 0;
    heuristics.uLoopEnd     = 0;    // Not looping.
    // Note: Looping heuristics are important even if there is only one stream in the whole application,
    // because it might change the way streaming memory is managed.
    heuristics.priority     = AK_DEFAULT_PRIORITY;
    // Note: Priority is also irrelevant. However, you must specify a value between AK_MIN_PRIORITY
    // and AK_MAX_PRIORITY (inclusive), otherwise the Stream Manager will consider its creation settings to be invalid.

    // Create stream.
    AKRESULT eResult = AK::IAkStreamMgr::Get()->CreateAuto(
        in_pszFilename, // File name.
        NULL,           // No file system flags: we provide the full path and do not need any file location resolving logic.
        heuristics,     // Our heuristics.
        NULL,           // Buffer constraints: none. Users should not specify user constraints if they don't need to.
        pStream,        // Returned stream interface.
        true );         // Require synchronous file open.
    // Check return code.
    if ( eResult != AK_Success )
    {
        printf( "Failed creating stream.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    // Start stream. I/O scheduler starts considering this stream.
    pStream->Start();

    // Get stream size (through GetInfo).
    AkStreamInfo streamInfo;
    pStream->GetInfo( streamInfo );

    // Get the first buffer. This access will be blocking.
    AkUInt32 uBufferSize;
    void * pBuffer;
    eResult = pStream->GetBuffer(
        pBuffer,        // Address of granted data space.
        uBufferSize,    // Size of granted data space.
        true            // Block until data is ready.
        );
    // Check return code.
    if ( eResult != AK_DataReady &&
         eResult != AK_NoMoreData )
    {
        printf( "GetBuffer failed.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    // Let's suppose we parsed data contained in the buffer, which gave us information about the file.
    // For example, it says that the file should be looping between positions 10000 and 150000 (bytes).
    AkUInt32 uLoopStart = 10000;
    AkUInt32 uLoopEnd = 150000;

    // Note: In order for this test to properly compare data, the input file should be bigger than uLoopEnd.
    if ( (AkUInt32)streamInfo.uSize < uLoopEnd )
    {
        printf( "Input file not big enough. Could not complete test.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    // For optimal Stream Manager behavior, we should set the heuristics accordingly.
    pStream->GetHeuristics( heuristics );   // Note: In the present case, it is not necessary to get the heuristics first.
    heuristics.uLoopStart = uLoopStart;
    heuristics.uLoopEnd = uLoopEnd;
    pStream->SetHeuristics( heuristics );

    // Note: The current stream position is the position seen by the client. It is therefore 0, because
    // we did not release the buffer yet.
    if ( pStream->GetPosition( NULL ) != 0 )
    {
        printf( "Invalid stream position.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    // Release the buffer.
    eResult = pStream->ReleaseBuffer();
    if ( eResult != AK_Success )
    {
        printf( "ReleaseBuffer failed.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    // Note: Now that we have released the buffer, the current stream position should be uBufferSize.
    if ( pStream->GetPosition( NULL ) != uBufferSize )
    {
        printf( "Invalid stream position.\n" );
        bSuccess = false;
        goto autostmbasictest_cleanup;
    }

    //
    // In the following sequence, the stream manager user will get and release buffers until the "loop end" position is crossed.
    // Positions are queried after GetBuffer(), so they refer to the beginning of the buffer.
    // The user "foresees" when the looping point will be crossed by adding the stream position and the size of the owned
    // buffer. At this point, the position is set back to the beginning of the loop,
    // change the heuristics, and read the file until the end (until GetBuffer() returns AK_NoMoreData).
    // It is easier to get the position after having released the buffer, but we recommend you
    // change the position as early as possible. Stream Manager has looping heuristics but their usage
    // is implementation-specific.
    //
    bool bEOF = false;
    bool bDoLoop = true;
    while ( !bEOF )
    {
        // Polling reads until we cross position uLoopEnd.
        eResult = pStream->GetBuffer(
            pBuffer,
            uBufferSize,
            false );
        // Check return code.
        if ( eResult == AK_Fail )
        {
            assert( !"I/O error" );
            return false;
        }
        else if ( eResult == AK_NoDataReady )
        {
            printf( "Starving...\n" );
        }
        else
        {
            // End of file condition.
            bEOF = ( eResult == AK_NoMoreData ) && !bDoLoop;

            // Handle looping. After first call to SetPosition, set bDoLoop to false
            // so that we stop looping.
            if ( bDoLoop )
            {
                // Get the current position to see if we crossed the boundary.
                // Recall that since the stream manager user owns a buffer, GetPosition() returns the
                // position of the beginning of the buffer. Add the buffer size to
                // that value to get the position of the end of the buffer.
                // Note: Keeping track of the position without calling GetPosition()
                // would be more efficient.
                AkUInt32 uCurPosition = pStream->GetPosition( NULL );
                if ( ( uCurPosition + uBufferSize ) > uLoopEnd )
                {
                    // We crossed the loop end.
                    // Set the position to the loop start.
                    AkInt64 iLoopStart;
                    iLoopStart.HighPart = 0;
                    iLoopStart.LowPart = uLoopStart;
                    AkInt64 iRealOffset;
                    eResult = pStream->SetPosition(
                        iLoopStart,
                        AK_MoveBegin,
                        &iRealOffset );
                    if ( eResult != AK_Success )
                    {
                        printf( "SetPosition failed.\n" );
                        bSuccess = false;
                        goto autostmbasictest_cleanup;
                    }

                    // Note: Since we did not release the buffer yet, GetPosition() should still return the
                    // position of the start of the buffer.
                    // Verify this.
                    if ( pStream->GetPosition( NULL ) != uCurPosition )
                    {
                        printf( "Invalid position returned.\n" );
                        bSuccess = false;
                        goto autostmbasictest_cleanup;
                    }

                    // Note: Our uLoopStart position might not fall directly on the Low-LevelIO block size
                    // for that stream. Users of the Stream Manager are responsible for handling this constraint.
                    // Here, if iRealOffset is not 0, it will be smaller than the requested position.
                    // The stream manager user could store that corrected value in a local variable.

                    // Now that position is set, the next buffer access will be at the beginning of the loop
                    // (minus the correction value).
                    // For best performance, the looping heuristic value can be changed right away because
                    // there is no looping anymore.
                    heuristics.uLoopStart = 0;
                    heuristics.uLoopEnd = 0;    // 0: Not looping.
                    pStream->SetHeuristics( heuristics );

                     // Stop looping.
                    bDoLoop = false;
                }
            }

            eResult = pStream->ReleaseBuffer();
            if ( eResult != AK_Success )
            {
                printf( "ReleaseBuffer failed.\n" );
                bSuccess = false;
                goto autostmbasictest_cleanup;
            }
        }

        // Simulate user throughput. Wait time is the buffer size divided by the throughput.
        //AKPLATFORM::AkSleep( DWORD( 1000*uBufferSize / heuristics.fThroughput ) );
        AKPLATFORM::AkSleep( 0 );
    }

autostmbasictest_cleanup:

    if ( pStream )
        pStream->Destroy();

    return bSuccess;
}

このページはお役に立ちましたか?

サポートは必要ですか?

ご質問や問題、ご不明点はございますか?お気軽にお問い合わせください。

サポートページをご確認ください

あなたのプロジェクトについて教えてください。ご不明な点はありませんか。

プロジェクトを登録していただくことで、ご利用開始のサポートをいたします。

Wwiseからはじめよう