前回、Ogg Vorbisファイル(ストリームになっていれば、ファイルじゃなくてもいいけれど)を開いて、正しく閉じるところまで実装しました。今回は、デコードの前に、他のインフラを整えます。つまり、フォーマットを取得したり、位置を取得・設定できるようにします。基本的な部分は前回実装してしまったので、今回は楽です。書く方にとっても。
まずはフォーマットを取得しましょう。ここでいうフォーマットというのはストリームのデータがOgg Vorbisかどうか、ということではなくて、ウェーブフォーマットのことです。Managed DirectXでは、Microsoft.DirectX.DirectSound.WaveFormat
構造体に当たります。この構造体はセカンダリバッファを作るときに必要になります。また、データの長さも必要になるでしょう。
using Microsoft.DirectX.DirectSound; public class OggVorbisDecoder : IDisposable { // …… private WaveFormat format; public WaveFormat Format { get { return this.format; } } private long dataLength; public long Length { get { return this.dataLength; } } // …… }
と、外部から見えるようにしておきます。実際にthis.formatやthis.dataLengthの内容を書き換えるのは、OggVorbisDecoder.OpenOggVorbisの中にします。
private void OpenOggVorbis() { // ……Functions.OpenCallbacksでファイルを開く処理(前回説明) IntPtr information; information = Functions.GetInformation( this.oggVorbisFile, -1 ); if ( information == IntPtr.Zero ) throw new InvalidOperationException(); this.format.FormatTag = WaveFormatTag.Pcm; this.format.Channels = (short)Marshal.ReadInt32( information, 4 ); this.format.BitsPerSample = 16; this.format.BlockAlign = (short)( 2 * this.format.Channels ); this.format.SamplesPerSecond = Marshal.ReadInt32( information, 8 ); this.format.AverageBytesPerSecond = this.format.BlockAlign * this.format.SamplesPerSecond; this.dataLength = Functions.PCMTotal( this.oggVorbisFile, -1 ); if ( this.dataLength < 0 ) throw new InvalidOperationException(); return; }
フォーマットはov_infoに相当するFunctions.GetInformation
で取得できます。ここで失敗するとNULL(=0)を返すので一応例外を投げるようにしておきます。
実は、WaveFormat構造体にセットする必要がある値のうち、vorbis_info構造体には2つしか含まれていません。他の値は自分で適宜設定しなければなりません。
- FormatTag
- PCMデータであることを示す
WaveFormatTag.Pcm
を代入します。ov_readもPCMデータを返しますし、DirectSoundではPCM以外扱えません。 - Channels
- チャネル数を代入します。大抵1(モノラル)か2(ステレオ)です。これは、vorbis_info構造体の2番目のメンバ(int)に入っています。これは先頭から4バイトの位置にあるので、
Marshal.ReadInt32( information, 4 )
で値を取得してshortにキャストして代入します。 - BitsPerSample
- サンプリングサイズをビット単位で設定します。Ogg Vorbisの場合、デコード時に決められるので、16にしておきます。
- BlockAlign
- 全てのチャネルを合わせた1サンプル(ブロック)のバイト数を代入します。16ビット(2バイト)にチャネル数をかければいいでしょう。
- SamplesPerSecond
- サンプリング周波数です。これはvorbis_info構造体の3番目のメンバ(int)に入っているので、informationから8バイトの所にある値を代入します。
- AverageBytesPerSecond
- PCMの場合、単に一秒当たりのバイト数なので、BlockAlign * SamplesPerSecondと等価です。
フォーマットの設定が終わったら、データの長さも取得しておきます。Functions.PCMTotal
はサンプル単位でデータ長を返してくれるので、それをそのままthis.dataLengthに入れます。負の値が返されたときは例によって例外を。
フォーマットとデータ長は用意ができたので、最後に位置の取得と設定を行うプロパティを作ります。
public long Position { get { long currentSampleIndex; currentSampleIndex = OggVorbis.PCMTell( this.oggVorbisFile ); if ( currentSampleIndex < 0 ) throw new InvalidOperationException(); return currentSampleIndex; } set { if ( value < 0 ) value = 0; else if ( value > this.dataLength ) value = this.dataLength; if ( OggVorbis.PCMSeek( this.oggVorbisFile, value ) < 0 ) throw new InvalidOperationException(); return; } }
データ長もサンプル単位で行ったので、ここでもPCMデータのサンプル単位で処理を行います。エラーが起きると何れも負の値を返すので、例外を投げましょう。
これで準備は終わりです。次は実際にデコードする関数を定義します。
コメントする