From 71f1af2ce852fcfbb02d2907402c498058b18d14 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E3=81=8F=E3=81=BE=E3=81=8B=E3=81=BF=E5=B7=A5=E6=88=BF?= Date: Thu, 17 Nov 2016 21:15:00 +0900 Subject: [PATCH] =?utf8?q?=E6=8E=92=E4=BB=96=E3=83=A2=E3=83=BC=E3=83=89?= =?utf8?q?=E3=81=A8=E5=85=B1=E6=9C=89=E3=83=A2=E3=83=BC=E3=83=89=E3=81=AE?= =?utf8?q?=E4=B8=A1=E6=96=B9=E3=81=AE=E5=AE=9F=E7=9B=B8=E3=82=92=E5=AE=8C?= =?utf8?q?=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- FDK24/メディア/サウンド/WASAPI/Decoder.cs | 117 +++++----- FDK24/メディア/サウンド/WASAPI/Device.cs | 242 ++++++++++++++++----- FDK24/メディア/サウンド/WASAPI/Mixer.cs | 67 ++---- FDK24/メディア/サウンド/WASAPI/Sound.cs | 21 +- .../ステージ/演奏/演奏ステージ.cs | 4 +- 5 files changed, 263 insertions(+), 188 deletions(-) diff --git a/FDK24/メディア/サウンド/WASAPI/Decoder.cs b/FDK24/メディア/サウンド/WASAPI/Decoder.cs index 2041d06..19d8343 100644 --- a/FDK24/メディア/サウンド/WASAPI/Decoder.cs +++ b/FDK24/メディア/サウンド/WASAPI/Decoder.cs @@ -52,16 +52,15 @@ namespace FDK.メディア.サウンド.WASAPI /// /// メディアファイル(動画、音楽)をデコードする。 /// - /// メディアファイル(MediaFoundation でデコードできるもの) - /// デコード先のフォーマット。 - public Decoder( string path, CSCore.WaveFormat waveFormat ) + public Decoder( string path, CSCore.WaveFormat targetFormat ) { - //if( ( waveFormat.WaveFormatTag != AudioEncoding.IeeeFloat ) && - // ( !CSCore.WaveFormatExtensible.SubTypeFromWaveFormat( waveFormat ).Equals( SharpDX.MediaFoundation.AudioFormatGuids.Float ) ) ) - //{ - // throw new NotSupportedException( "IEEE Float 以外の形式のフォーマットはサポートしません。" ); - //} - this.WaveFormat = waveFormat; + // ISampleSource は IWaveSource を 32bit-float に変換して出力する仕様なので、 + // 最初からその形式でデコードして ISampleSource.Read() の変換負荷を下げる。 + this.WaveFormat = new CSCore.WaveFormat( + targetFormat.SampleRate, // サンプルレートと + 32, + targetFormat.Channels, // チャンネルは、指定されたものを使う。 + AudioEncoding.IeeeFloat ); this._初期化する( path ); } @@ -115,7 +114,7 @@ namespace FDK.メディア.サウンド.WASAPI return count; } - private SharpDX.MediaFoundation.MediaType _MediaType = null; + private CSCore.MediaFoundation.MFMediaType _MediaType = null; private byte[] _EncodedWaveData = null; private long _Position = 0; @@ -123,88 +122,76 @@ namespace FDK.メディア.サウンド.WASAPI { try { - using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( path ) ) + // SharpDX ではなく CSCore を使う。(MediaType から WaveFormat に一発で変換できるので。) + using( var sourceReader = new CSCore.MediaFoundation.MFSourceReader( path ) ) using( var waveStream = new System.IO.MemoryStream() ) { #region " 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。" //---------------- - sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false ); - sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); + sourceReader.SetStreamSelection( (int) SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false ); + sourceReader.SetStreamSelection( (int) SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); //---------------- #endregion - #region " デコードフォーマットを持つ MediaType を作成し、SourceReader に登録する。" + #region " デコード後フォーマットを持つメディアタイプを作成し、SourceReader に登録する。" //---------------- - using( var partialMediaType = new SharpDX.MediaFoundation.MediaType() ) + using( var partialMediaType = CSCore.MediaFoundation.MFMediaType.FromWaveFormat( this.WaveFormat ) ) // WaveFormatEx にも対応。 { - // 部分メディアタイプを作成し、オーディオフォーマットを設定する。 - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio ); - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Float ); - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WaveFormat.Channels ); - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WaveFormat.SampleRate ); - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, 32 ); - //partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WaveFormat.BytesPerSecond ); - //partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WaveFormat.BlockAlign ); - partialMediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE - // 作成したメディアタイプを sourceReader にセットする。必要なデコーダが見つからなかったら、ここで例外が発生する。 - sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, partialMediaType ); + sourceReader.SetCurrentMediaType( (int) SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, partialMediaType ); // 完成されたメディアタイプを取得する。 - this._MediaType = sourceReader.GetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream ); + this._MediaType = sourceReader.GetCurrentMediaType( (int) SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream ); - int wfsize; - var wfx = this._MediaType.ExtracttWaveFormat( out wfsize ); - this.WaveFormat = new CSCore.WaveFormat( wfx.SampleRate, wfx.BitsPerSample, wfx.Channels, AudioEncoding.IeeeFloat ); + // メディアタイプからフォーマットを取得する。(同じのはずだが念のため) + this.WaveFormat = this._MediaType.ToWaveFormat( CSCore.MediaFoundation.MFWaveFormatExConvertFlags.Normal ); // 最初のオーディオストリームが選択されていることを保証する。 - sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); + sourceReader.SetStreamSelection( (int) SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); } //---------------- #endregion #region " sourceReader からサンプルを取得してデコードし、waveStream へ書き込んだのち、byte[] _EncodedWaveData へ出力する。" //----------------- - using( var pcmWriter = new System.IO.BinaryWriter( waveStream ) ) + while( true ) { - while( true ) + // 次のサンプルを読み込む。 + int actualStreamIndexRef = 0; + var dwStreamFlagsRef = CSCore.MediaFoundation.MFSourceReaderFlags.None; + Int64 llTimestampRef = 0; + + using( var sample = sourceReader.ReadSample( + (int) SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, + (int) CSCore.MediaFoundation.SourceReaderControlFlags.None, + out actualStreamIndexRef, + out dwStreamFlagsRef, + out llTimestampRef ) ) { - // 次のサンプルを読み込む。 - int dwActualStreamIndexRef = 0; - var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None; - Int64 llTimestampRef = 0; - using( var sample = sourceReader.ReadSample( - SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, - SharpDX.MediaFoundation.SourceReaderControlFlags.None, - out dwActualStreamIndexRef, - out dwStreamFlagsRef, - out llTimestampRef ) ) - { - if( null == sample ) - break; // EndOfStream やエラーのときも null になる。 + if( null == sample ) + break; // EndOfStream やエラーのときも null になる。 - // sample をロックし、オーディオデータへのポインタを取得する。 - int cbMaxLengthRef = 0; - int cbCurrentLengthRef = 0; - using( var mediaBuffer = sample.ConvertToContiguousBuffer() ) + // sample をロックし、オーディオデータへのポインタを取得する。 + int cbMaxLengthRef = 0; + int cbCurrentLengthRef = 0; + using( var mediaBuffer = sample.ConvertToContiguousBuffer() ) + { + // オーディオデータをメモリストリームに書き込む。 + var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef ); + try { - // オーディオデータをメモリストリームに書き込む。 - var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef ); - try - { - byte[] dstData = new byte[ cbCurrentLengthRef ]; - System.Runtime.InteropServices.Marshal.Copy( audioData, dstData, 0, cbCurrentLengthRef ); - pcmWriter.Write( dstData, 0, cbCurrentLengthRef ); - } - finally - { - mediaBuffer.Unlock(); - } + byte[] dstData = new byte[ cbCurrentLengthRef ]; + System.Runtime.InteropServices.Marshal.Copy( audioData, dstData, 0, cbCurrentLengthRef ); + waveStream.Write( dstData, 0, cbCurrentLengthRef ); + } + finally + { + mediaBuffer.Unlock(); } } } - - // ストリームの内容を byte 配列に出力。 - this._EncodedWaveData = waveStream.ToArray(); } + + // ストリームの内容を byte 配列に出力。 + this._EncodedWaveData = waveStream.ToArray(); //----------------- #endregion } diff --git a/FDK24/メディア/サウンド/WASAPI/Device.cs b/FDK24/メディア/サウンド/WASAPI/Device.cs index 2801c46..b101f4b 100644 --- a/FDK24/メディア/サウンド/WASAPI/Device.cs +++ b/FDK24/メディア/サウンド/WASAPI/Device.cs @@ -26,7 +26,7 @@ namespace FDK.メディア.サウンド.WASAPI protected set { this._遅延100ns = value; } } - public CSCore.WaveFormat フォーマット + public CSCore.WaveFormat WaveFormat { get { return this._WaveFormat; } } @@ -39,26 +39,35 @@ namespace FDK.メディア.サウンド.WASAPI { get { - return ( null != this._レンダリング先 ) ? this._レンダリング先.Volume : 1.0f; + return ( null != this._Mixer ) ? this._Mixer.Volume : 1.0f; } set { if( ( 0.0f > value ) || ( 1.0f < value ) ) throw new ArgumentOutOfRangeException(); - this._レンダリング先.Volume = value; + this._Mixer.Volume = value; } } - public Device( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double 遅延sec = 0.010, CSCore.WaveFormat 希望フォーマット = null ) + public Device( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double バッファサイズsec = 0.010, CSCore.WaveFormat 希望フォーマット = null ) { this._共有モード = 共有モード; - this.遅延sec = 遅延sec; + this.遅延sec = バッファサイズsec; this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped; this._初期化する( 希望フォーマット ); this.PlayRendering(); + + FDK.Log.Info( $"WASAPIデバイスを初期化しました。" ); + FDK.Log.Info( $" Mode: {this._共有モード}" ); + FDK.Log.Info( $" Laytency: {this.遅延sec * 1000.0} ms" ); + var wfx = this.WaveFormat as CSCore.WaveFormatExtensible; + if( null == wfx ) + FDK.Log.Info( $" Format: {this.WaveFormat.WaveFormatTag}, {this.WaveFormat.SampleRate}Hz, {this.WaveFormat.Channels}ch, {this.WaveFormat.BitsPerSample}bits" ); + else + FDK.Log.Info( $" Format: {wfx.WaveFormatTag}[{CSCore.AudioSubTypes.EncodingFromSubType( wfx.SubFormat )}], {wfx.SampleRate}Hz, {wfx.Channels}ch, {wfx.BitsPerSample}bits" ); } /// @@ -265,10 +274,6 @@ namespace FDK.メディア.サウンド.WASAPI private System.Threading.EventWaitHandle _レンダリングイベント = null; - private CSCore.Streams.VolumeSource _レンダリング先 = null; - - private CSCore.IWaveSource _生レンダリング先 = null; - private Mixer _Mixer = null; private readonly object _スレッド間同期 = new object(); @@ -276,7 +281,7 @@ namespace FDK.メディア.サウンド.WASAPI private bool _dispose済み = false; - private void _初期化する( CSCore.WaveFormat 希望フォーマット = null ) + private void _初期化する( CSCore.WaveFormat 希望フォーマット ) { lock( this._スレッド間同期 ) { @@ -296,10 +301,8 @@ namespace FDK.メディア.サウンド.WASAPI this._AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( this._MMDevice ); // フォーマットを決定する。 - if( null == ( this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ?? this._AudioClient.GetMixFormat() ) ) ) - { + if( null == ( this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ) ) ) throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" ); - } // 遅延を既定値にする(共有モードの場合のみ)。 if( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared ) @@ -311,7 +314,7 @@ namespace FDK.メディア.サウンド.WASAPI this._共有モード, CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback, // イベント駆動で固定。 this._遅延100ns, - this._遅延100ns, + this._遅延100ns, // イベント駆動の場合、Periodicity は BufferDuration と同じ値でなければならない。 this._WaveFormat, Guid.Empty ); }; @@ -325,11 +328,15 @@ namespace FDK.メディア.サウンド.WASAPI // この場合、バッファサイズを調整して再度初期化する。 if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED ) { - int サイズframe = this._AudioClient.GetBufferSize(); + int サイズframe = this._AudioClient.GetBufferSize(); // アライメント済みサイズが取得できる。 this._遅延100ns = (long) ( 10.0 * 1000.0 * 1000.0 * サイズframe / this._WaveFormat.SampleRate + 0.5 ); // +0.5 は四捨五入 AudioClientを初期化する(); // それでも例外なら知らん。 } + else + { + throw; + } } // イベント駆動用に使うイベントを生成し、AudioClient へ登録する。 @@ -340,11 +347,8 @@ namespace FDK.メディア.サウンド.WASAPI this._AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this._AudioClient ); this._AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this._AudioClient ); - // ミキサーを生成し、デバイスのソース(DirectSound でいうところのプライマリバッファ)として登録する。 - this._Mixer = new Mixer( this._WaveFormat ) { - DivideResult = false, // BGMのような長い音にドラムのような短い音が入るとBGMの音量がコロコロ変わってしまうので、このオプションは false で固定。 - }; - this._SetSource( this._Mixer ); + // ミキサーを生成する。 + this._Mixer = new Mixer( this._WaveFormat ); } } @@ -373,15 +377,6 @@ namespace FDK.メディア.サウンド.WASAPI FDK.Utilities.解放する( ref this._MMDevice ); } - private void _SetSource( CSCore.ISampleSource targetSource ) - { - if( null != this._レンダリング先 ) - throw new InvalidOperationException( "レンダリングターゲットはすでに設定済みです。" ); - - this._レンダリング先 = new CSCore.Streams.VolumeSource( targetSource ); // サンプル単位のレンダリング先と、 - this._生レンダリング先 = targetSource.ToWaveSource( targetSource.WaveFormat.BitsPerSample ); // データ(byte)単位のレンダリング先とを持っておく。 - } - /// /// 希望したフォーマットをもとに、適切なフォーマットを調べて返す。 /// @@ -395,7 +390,7 @@ namespace FDK.メディア.サウンド.WASAPI var 最も近いフォーマット = (CSCore.WaveFormat) null; var 最終的に決定されたフォーマット = (CSCore.WaveFormat) null; - if( this._AudioClient.IsFormatSupported( this._共有モード, waveFormat, out 最も近いフォーマット ) ) + if( ( null != waveFormat ) && this._AudioClient.IsFormatSupported( this._共有モード, waveFormat, out 最も近いフォーマット ) ) { // (A) そのまま使える。 最終的に決定されたフォーマット = waveFormat; @@ -417,13 +412,44 @@ namespace FDK.メディア.サウンド.WASAPI } else { - // (D) AudioClient が共有モードのフォーマットすらNGと言ってきた場合は、以下から探す。 + // (D) AudioClient が共有モードのフォーマットもNGである場合は、以下から探す。 + + CSCore.WaveFormat closest = null; + + bool found = this._AudioClient.IsFormatSupported( CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive, + new WaveFormat( 48000, 24, 2, AudioEncoding.Pcm ) { + + }, + out closest ); 最終的に決定されたフォーマット = new[] { - new CSCore.WaveFormat( waveFormat.SampleRate, 16, waveFormat.Channels, AudioEncoding.Pcm ), - new CSCore.WaveFormat( waveFormat.SampleRate, 24, waveFormat.Channels, AudioEncoding.Pcm ), - new CSCore.WaveFormat( waveFormat.SampleRate, 8, waveFormat.Channels, AudioEncoding.Pcm ), - new CSCore.WaveFormat( waveFormat.SampleRate, 32, waveFormat.Channels, AudioEncoding.IeeeFloat ), + new CSCore.WaveFormat( 48000, 32, 2, AudioEncoding.IeeeFloat ), + new CSCore.WaveFormat( 44100, 32, 2, AudioEncoding.IeeeFloat ), + /* + * 24bit PCM には対応しない。 + * + * > wFormatTag が WAVE_FORMAT_PCM の場合、wBitsPerSample は 8 または 16 でなければならない。 + * > wFormatTag が WAVE_FORMAT_EXTENSIBLE の場合、この値は、任意の 8 の倍数を指定できる。 + * https://msdn.microsoft.com/ja-jp/library/cc371566.aspx + * + * また、Realtek HD Audio の場合、IAudioClient.IsSupportedFormat() は 24bit PCM でも true を返してくるが、 + * 単純に 1sample = 3byte で書き込んでも正常に再生できない。 + * おそらく 32bit で包む必要があると思われるが、その方法は不明。 + */ + //new CSCore.WaveFormat( 48000, 24, 2, AudioEncoding.Pcm ), + //new CSCore.WaveFormat( 44100, 24, 2, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 48000, 16, 2, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 44100, 16, 2, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 48000, 8, 2, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 44100, 8, 2, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 48000, 32, 1, AudioEncoding.IeeeFloat ), + new CSCore.WaveFormat( 44100, 32, 1, AudioEncoding.IeeeFloat ), + //new CSCore.WaveFormat( 48000, 24, 1, AudioEncoding.Pcm ), + //new CSCore.WaveFormat( 44100, 24, 1, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 48000, 16, 1, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 44100, 16, 1, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 48000, 8, 1, AudioEncoding.Pcm ), + new CSCore.WaveFormat( 44100, 8, 1, AudioEncoding.Pcm ), } .FirstOrDefault( ( format ) => ( this._AudioClient.IsFormatSupported( this._共有モード, format ) ) ); @@ -445,9 +471,10 @@ namespace FDK.メディア.サウンド.WASAPI try { + #region " 初期化。" + //---------------- int バッファサイズframe = this._AudioClient.BufferSize; - int フレームサイズbyte = this._WaveFormat.Channels * this._WaveFormat.BytesPerSample; - var バッファ = new byte[ バッファサイズframe * フレームサイズbyte ]; + var バッファ = new float[ バッファサイズframe * this.WaveFormat.Channels ]; // 前提1・this._レンダリング先(ミキサー)の出力は 32bit-float で固定。 // このスレッドの MMCSS 型を登録する。 int taskIndex; @@ -465,9 +492,11 @@ namespace FDK.メディア.サウンド.WASAPI // 起動完了を通知する。 ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set(); 起動完了通知 = null; + //---------------- + #endregion - // 以下、メインループ。 - + #region " メインループ。" + //---------------- var イベントs = new System.Threading.WaitHandle[] { this._レンダリングイベント }; while( this.レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped ) { @@ -486,14 +515,122 @@ namespace FDK.メディア.サウンド.WASAPI if( 空きframe > 5 ) // あまりに空きが小さいならスキップする。 { - if( !this._バッファを埋める( バッファ, 空きframe, フレームサイズbyte ) ) - this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped; + // レンダリング先からデータを取得して AudioRenderClient へ出力する。 + + int 読み込むサイズsample = 空きframe * this.WaveFormat.Channels; // 前提2・レンダリング先.WaveFormat と this.WaveFormat は同一。 + 読み込むサイズsample -= ( 読み込むサイズsample % ( this.WaveFormat.BlockAlign / this.WaveFormat.BytesPerSample ) ); // BlockAlign 境界にそろえる。 + + if( 0 < 読み込むサイズsample ) + { + // ミキサーからの出力をバッファに取得する。 + int 読み込んだサイズsample = this._Mixer.Read( バッファ, 0, 読み込むサイズsample ); + + // バッファのデータを変換しつつ、AudioRenderClient へ出力する。 + IntPtr bufferPtr = this._AudioRenderClient.GetBuffer( 空きframe ); + try + { + var encoding = CSCore.AudioSubTypes.EncodingFromSubType( CSCore.WaveFormatExtensible.SubTypeFromWaveFormat( this.WaveFormat ) ); + + if( encoding == AudioEncoding.Pcm ) + { + if( 24 == this.WaveFormat.BitsPerSample ) + { + #region " (A) Mixer:32bit-float → AudioRenderClient:24bit-PCM の場合 " + //---------------- + unsafe + { + byte* ptr = (byte*) bufferPtr.ToPointer(); // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。 + + for( int i = 0; i < 読み込んだサイズsample; i++ ) + { + float data = バッファ[ i ]; + if( -1.0f > data ) data = -1.0f; + if( +1.0f < data ) data = +1.0f; + + uint sample32 = (uint) ( data * 8388608f - 1f ); // 24bit PCM の値域は -8388608~+8388607 + byte* psample32 = (byte*) &sample32; + *ptr++ = *psample32++; + *ptr++ = *psample32++; + *ptr++ = *psample32++; + } + } + //---------------- + #endregion + } + else if( 16 == this.WaveFormat.BitsPerSample ) + { + #region " (B) Mixer:32bit-float → AudioRenderClient:16bit-PCM の場合 " + //---------------- + unsafe + { + byte* ptr = (byte*) bufferPtr.ToPointer(); // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。 + + for( int i = 0; i < 読み込んだサイズsample; i++ ) + { + float data = バッファ[ i ]; + if( -1.0f > data ) data = -1.0f; + if( +1.0f < data ) data = +1.0f; + + short sample16 = (short) ( data * short.MaxValue ); + byte* psample16 = (byte*) &sample16; + *ptr++ = *psample16++; + *ptr++ = *psample16++; + } + } + //---------------- + #endregion + } + else if( 8 == this.WaveFormat.BitsPerSample ) + { + #region " (C) Mixer:32bit-float → AudioRenderClient:8bit-PCM の場合 " + //---------------- + unsafe + { + byte* ptr = (byte*) bufferPtr.ToPointer(); // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。 + + for( int i = 0; i < 読み込んだサイズsample; i++ ) + { + float data = バッファ[ i ]; + if( -1.0f > data ) data = -1.0f; + if( +1.0f < data ) data = +1.0f; + + byte value = (byte) ( ( data + 1 ) * 128f ); + *ptr++ = unchecked(value); + } + } + //---------------- + #endregion + } + } + else if( encoding == AudioEncoding.IeeeFloat ) + { + #region " (D) Mixer:32bit-float → AudioRenderClient:32bit-float の場合 " + //---------------- + Marshal.Copy( バッファ, 0, bufferPtr, 読み込んだサイズsample ); + //---------------- + #endregion + } + } + finally + { + int 出力したフレーム数 = 読み込んだサイズsample / this.WaveFormat.Channels; + this._AudioRenderClient.ReleaseBuffer( + 出力したフレーム数, + ( 0 < 出力したフレーム数 ) ? CSCore.CoreAudioAPI.AudioClientBufferFlags.None : CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent ); + } + + // レンダリング先からの出力がなくなったらおしまい。 + if( 0 == 読み込んだサイズsample ) + this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped; + } } } } + //---------------- + #endregion - // 以下、終了処理。 - + #region " 終了。" + //---------------- // このスレッドの MMCSS 特性を元に戻す。 Device.AvRevertMmThreadCharacteristics( avrtHandle ); avrtHandle = IntPtr.Zero; @@ -504,6 +641,8 @@ namespace FDK.メディア.サウンド.WASAPI // AudioClient を停止する。 this._AudioClient.Stop(); this._AudioClient.Reset(); + //---------------- + #endregion } catch( Exception e ) { @@ -519,23 +658,6 @@ namespace FDK.メディア.サウンド.WASAPI } } - private bool _バッファを埋める( byte[] バッファ, int フレーム数, int フレームサイズbyte ) - { - int 読み込むサイズbyte = フレーム数 * フレームサイズbyte; - 読み込むサイズbyte -= ( 読み込むサイズbyte % this._生レンダリング先.WaveFormat.BlockAlign ); // BlockAlign の倍数にする。 - - if( 読み込むサイズbyte <= 0 ) - return true; - - int 読み込んだサイズbyte = this._生レンダリング先.Read( バッファ, 0, 読み込むサイズbyte ); - - IntPtr ptr = this._AudioRenderClient.GetBuffer( フレーム数 ); - Marshal.Copy( バッファ, 0, ptr, 読み込んだサイズbyte ); - this._AudioRenderClient.ReleaseBuffer( 読み込んだサイズbyte / フレームサイズbyte, CSCore.CoreAudioAPI.AudioClientBufferFlags.None ); - - return ( 0 < 読み込んだサイズbyte ); - } - #region " Win32 " //---------------- private const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019); diff --git a/FDK24/メディア/サウンド/WASAPI/Mixer.cs b/FDK24/メディア/サウンド/WASAPI/Mixer.cs index d79ec38..4c29f26 100644 --- a/FDK24/メディア/サウンド/WASAPI/Mixer.cs +++ b/FDK24/メディア/サウンド/WASAPI/Mixer.cs @@ -13,16 +13,6 @@ namespace FDK.メディア.サウンド.WASAPI internal class Mixer : CSCore.ISampleSource { /// - /// true なら、サンプルの値を、合成したソースの数で割る。 - /// (例えば、ソースを3つ合成した場合、合成した結果のサンプル値を3で割る。各サウンドはそれぞれ小さく聞こえる。) - /// - public bool DivideResult - { - get; - set; - } = false; - - /// /// 音量。0.0(無音)~1.0(原音)。 /// public float Volume @@ -67,9 +57,9 @@ namespace FDK.メディア.サウンド.WASAPI /// /// 指定したフォーマットを持つミキサーを生成する。 /// - public Mixer( CSCore.WaveFormat waveFormat ) + public Mixer( CSCore.WaveFormat deviceWaveFormat ) { - this._WaveFormat = waveFormat; + this._WaveFormat = deviceWaveFormat; } public void Dispose() @@ -94,10 +84,12 @@ namespace FDK.メディア.サウンド.WASAPI if( null == sound ) throw new ArgumentNullException(); - if( ( sound.WaveFormat.Channels != this._WaveFormat.Channels ) || - ( sound.WaveFormat.SampleRate != this._WaveFormat.SampleRate ) ) + if( ( sound.SampleSource.WaveFormat.Channels != this._WaveFormat.Channels ) || + ( sound.SampleSource.WaveFormat.SampleRate != this._WaveFormat.SampleRate ) || + ( sound.SampleSource.WaveFormat.WaveFormatTag != AudioEncoding.IeeeFloat ) ) // IWaveSource.ToSampleSource() で作成した ISampleSource ならすべて 32bit-float であるはず。 { - throw new ArgumentException(); + // これらの変換は面倒なのでサポートしない。 + throw new ArgumentException( "ミキサーと同じチャンネル数、サンプルレート、かつ 32bit float 型である必要があります。" ); } lock( this._スレッド間同期 ) @@ -135,27 +127,26 @@ namespace FDK.メディア.サウンド.WASAPI /// /// バッファにサウンドデータを出力する。 /// - /// 出力したサンプル数。 + /// 実際に出力したサンプル数。 public int Read( float[] バッファ, int バッファの出力開始位置, int 出力サンプル数 ) { + // ミキサに登録されている Sound の入力とこのメソッドが出力するデータはいずれも常に 32bit-float であり、 + // これは this.WaveFormat.WaveFormatTag とは無関係なので注意。(this.WaveFormat は、チャンネル数とサンプルレートしか見てない。) + if( 0 < 出力サンプル数 ) { lock( this._スレッド間同期 ) { // 中間バッファが十分あることを確認する。足りなければ新しく確保して戻ってくる。 - this._中間バッファ = this._中間バッファ.CheckBuffer( 出力サンプル数 ); + this._中間バッファ = this._中間バッファ.CheckBuffer( 出力サンプル数 ); // サンプル数であって、フレーム数(サンプル数×チャンネル数)ではない。 // 無音を出力する。 Array.Clear( バッファ, 0, 出力サンプル数 ); - // 登録されたサンプルソースを出力する。 + // ミキサに登録されているすべての Sound を出力する。 if( 0 < this._Sounds.Count ) { - // DiviveResult 用。 - var サウンド別読み出し数 = new List( this._Sounds.Count ); - - // ミキサに登録されているすべての Sound について……(リストから Remove する場合があるので、逆順に。) - for( int m = this._Sounds.Count - 1; m >= 0; m-- ) + for( int m = this._Sounds.Count - 1; m >= 0; m-- ) // リストから Remove する場合があるので、リストの後ろから進める。 { var sound = this._Sounds[ m ]; @@ -173,40 +164,12 @@ namespace FDK.メディア.サウンド.WASAPI バッファ[ i ] += data; } - if( 0 < 受け取ったサンプル数 ) - { - // DiviveResult 用。 - サウンド別読み出し数.Add( 受け取ったサンプル数 ); - } - else + if( 0 == 受け取ったサンプル数 ) { // 再生終了。リストから削除。 this.RemoveSound( sound ); } } - - if( DivideResult ) - { - サウンド別読み出し数.Sort(); - - int 出力位置 = バッファの出力開始位置; - int 残りのSound数 = サウンド別読み出し数.Count; - - foreach( var 読み出し数 in サウンド別読み出し数 ) - { - if( 0 == 残りのSound数 ) - break; - - while( 出力位置 < ( バッファの出力開始位置 + 読み出し数 ) ) - { - バッファ[ 出力位置 ] /= 残りのSound数; - バッファ[ 出力位置 ] = Math.Max( -1, Math.Min( 1, バッファ[ 出力位置 ] ) ); - 出力位置++; - } - - 残りのSound数--; - } - } } } } diff --git a/FDK24/メディア/サウンド/WASAPI/Sound.cs b/FDK24/メディア/サウンド/WASAPI/Sound.cs index e4464bd..817bc68 100644 --- a/FDK24/メディア/サウンド/WASAPI/Sound.cs +++ b/FDK24/メディア/サウンド/WASAPI/Sound.cs @@ -12,7 +12,6 @@ namespace FDK.メディア.サウンド.WASAPI { get { return this._SampleSource.Length; } } - public double 長さsec { get { return this._SampleToSec( this.長さsample ); } @@ -23,7 +22,6 @@ namespace FDK.メディア.サウンド.WASAPI get { return this._SampleSource.Position; } set { this._SampleSource.Position = value; } } - public double 位置sec { get @@ -41,15 +39,14 @@ namespace FDK.メディア.サウンド.WASAPI } } - public WaveFormat WaveFormat - { - get { return this._SampleSource.WaveFormat; } - } - public CSCore.ISampleSource SampleSource { get { return this._SampleSource; } } + public CSCore.IWaveSource WaveSource + { + get { return this._WaveSource; } + } /// /// 音量。0.0(無音)~1.0(原音)。 @@ -116,15 +113,19 @@ namespace FDK.メディア.サウンド.WASAPI private long _SecToSample( double 時間sec ) { - long 時間sample = (long) ( 時間sec * this.WaveFormat.SampleRate * this.WaveFormat.Channels + 0.5 ); // +0.5 は四捨五入 - 時間sample -= ( 時間sample % this.WaveFormat.Channels ); // チャンネル数の倍数にする。 + var wf = this.SampleSource.WaveFormat; + + long 時間sample = (long) ( 時間sec * wf.SampleRate * wf.Channels + 0.5 ); // +0.5 は四捨五入 + 時間sample -= ( 時間sample % wf.Channels ); // チャンネル数の倍数にする。 return 時間sample; } private double _SampleToSec( long 時間sample ) { - return 時間sample / ( this.WaveFormat.Channels * this.WaveFormat.SampleRate ); + var wf = this.SampleSource.WaveFormat; + + return 時間sample / ( wf.Channels * wf.SampleRate ); } } } diff --git a/StrokeStyleT/ステージ/演奏/演奏ステージ.cs b/StrokeStyleT/ステージ/演奏/演奏ステージ.cs index adc8d74..ca4871a 100644 --- a/StrokeStyleT/ステージ/演奏/演奏ステージ.cs +++ b/StrokeStyleT/ステージ/演奏/演奏ステージ.cs @@ -162,12 +162,14 @@ namespace SST.ステージ.演奏 this.FPS = new FDK.カウンタ.FPS(); double 演奏開始位置の先頭からの時間sec = 0.0; + int 演奏開始小節番号 = 0; var msg = StrokeStyleT.最後に取得したビュアーメッセージ; if( null != msg ) { - 演奏開始位置の先頭からの時間sec = this.スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( msg.演奏開始小節番号 ); + 演奏開始小節番号 = msg.演奏開始小節番号; this.Autoチップのドラム音を再生する = msg.ドラムチップ発声; } + 演奏開始位置の先頭からの時間sec = this.スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( 演奏開始小節番号 ); long position, qpcPosition, frequency; StrokeStyleT.サウンドデバイス.GetClock( out position, out qpcPosition, out frequency ); -- 2.11.0