using System;
namespace FDK.メディア.サウンド.WASAPI排他
{
public unsafe class Sound : IDisposable
{
public enum E再生状態
{
停止中, // 初期状態
再生中,
一時停止中,
再生終了,
};
public E再生状態 再生状態 = E再生状態.停止中;
///
/// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。
///
public float 音量
{
set
{
float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f ); // 0.0未満は0.0へ、1.0超は1.0へ。
lock( this.排他利用 )
{
this.bs_音量 = 設定値;
}
}
get
{
lock( this.排他利用 )
{
return this.bs_音量;
}
}
}
public double 長さsec
{
get
{
lock( this.排他利用 )
{
return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate );
}
}
}
public Sound()
{
}
public Sound( string サウンドファイルuri ) : this()
{
this.ファイルから作成する( サウンドファイルuri );
}
public void ファイルから作成する( string サウンドファイルuri )
{
lock( this.排他利用 )
{
#region " 作成済みなら先にDisposeする。"
//-----------------
if( this.作成済み )
this.Dispose();
this.作成済み = false;
//-----------------
#endregion
byte[] encodedPcm = null;
using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) )
using( var pcmStream = new System.IO.MemoryStream() )
{
#region " サウンドファイル名から SourceReader を作成する。"
//-----------------
// 先述の using で作成済み。
// 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
// メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。)
using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
{
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Pcm );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WAVEフォーマット.Channels );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WAVEフォーマット.SampleRate );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WAVEフォーマット.BlockAlign );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WAVEフォーマット.AverageBytesPerSecond );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WAVEフォーマット.BitsPerSample );
mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE
// 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。
sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType );
}
// 最初のオーディオストリームが選択されていることを保証する。
sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
//-----------------
#endregion
#region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。"
//-----------------
using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) )
{
while( true )
{
// 次のサンプルを読み込む。
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 やエラーも含まれる。
// サンプルをロックし、オーディオデータへのポインタを取得する。
int cbMaxLengthRef = 0;
int cbCurrentLengthRef = 0;
using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
{
// オーディオデータをメモリストリームに書き込む。
var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
byte[] dstData = new byte[ cbCurrentLengthRef ];
byte* psrcData = (byte*) audioData.ToPointer(); // fixed
fixed ( byte* pdstData = dstData )
{
CopyMemory( pdstData, psrcData, cbCurrentLengthRef );
}
pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
// サンプルのロックを解除する。
mediaBuffer.Unlock();
}
}
}
// ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。)
encodedPcm = pcmStream.ToArray();
}
//-----------------
#endregion
}
#region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。"
//-----------------
using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) )
{
// PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。)
this.サウンドデータサイズbyte = encodedPcm.Length * 2; // 32bit は 16bit の2倍。
this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8; // 1sample = 32bit×2h = 64bit = 8bytes
// オーバーサンプリングサウンドデータ用メモリを確保する。
this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte );
// ストリームからオーバーサンプリングサウンドデータへ転送する。
var p = (Int32*) this.サウンドデータ;
for( int i = 0; i < this.サウンドデータサイズsample; i++ )
{
// 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。
*p++ = (Int32) pcmReader.ReadInt16(); // 左ch
*p++ = (Int32) pcmReader.ReadInt16(); // 右ch
}
}
//-----------------
#endregion
this.再生位置sample = 0;
this.作成済み = true;
}
}
public void 再生を開始する( double 再生開始位置sec = 0.0 )
{
lock( this.排他利用 )
{
if( false == this.作成済み )
return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate );
if( 開始位置sample < this.サウンドデータサイズsample )
{
this.再生状態 = E再生状態.再生中;
this.再生位置sample = 開始位置sample;
}
}
}
public void 再生を一時停止する()
{
lock( this.排他利用 )
{
if( false == this.作成済み )
return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
this.再生状態 = E再生状態.一時停止中;
}
}
public void 再生を再開する()
{
lock( this.排他利用 )
{
if( false == this.作成済み )
return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
if( E再生状態.一時停止中 != this.再生状態 )
this.再生位置sample = 0;
this.再生状態 = E再生状態.再生中;
}
}
public void 再生を停止する()
{
lock( this.排他利用 )
{
if( false == this.作成済み )
return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
this.再生状態 = E再生状態.停止中;
this.再生位置sample = 0;
}
}
public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である )
{
lock( this.排他利用 )
{
#region " 未作成、または再生中でないなら無音フラグをもって帰還。"
//-----------------
if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) )
return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
//-----------------
#endregion
int オーバーサンプルサイズbyte = 4 * 2; // 32bit×2ch
Int32* 出力元 = (Int32*) ( this.サウンドデータ + ( this.再生位置sample * オーバーサンプルサイズbyte ) );
Int32* _出力先 = (Int32*) 出力先; // この実装ではサンプルは Int32 単位
int 出力できるサンプル数 = System.Math.Min( 出力サンプル数, ( this.サウンドデータサイズsample - this.再生位置sample ) );
int 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数;
if( 出力できるサンプル数 <= 0 )
this.再生状態 = E再生状態.再生終了; // 念のため
if( 最初の出力である )
{
#region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。"
//-----------------
if( 1.0f == this.bs_音量 )
{
// 原音(最大音量)。
CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) );
}
else
{
// 音量を反映。
for( int i = 0; i < 出力できるサンプル数; i++ )
{
// 1サンプル = 2ch×INT32
*_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
*_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
}
}
if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した
{
// 残りの部分は、とりあえず今は無音。(ループ再生未対応)
ZeroMemory(
(void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ),
出力できないサンプル数 * オーバーサンプルサイズbyte );
}
//-----------------
#endregion
}
else
{
#region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。"
//-----------------
for( int i = 0; i < 出力できるサンプル数; i++ )
{
// 1サンプル = 2ch×INT32
*_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
*_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
}
if( 0 < 出力できないサンプル数 )
{
// 残りの部分は、今回の実装では無視。(ループ再生未対応。)
}
//-----------------
#endregion
}
#region " 再生位置を移動。"
//---------------------------------------------------
this.再生位置sample += 出力できるサンプル数;
if( this.サウンドデータサイズsample <= this.再生位置sample ) // サウンドデータの末尾に達した
{
this.再生位置sample = this.サウンドデータサイズsample;
this.再生状態 = E再生状態.再生終了; // 再生終了に伴う自動終了なので、"停止中" ではない。
}
//---------------------------------------------------
#endregion
}
return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
}
#region " Dispose-Finalizeパターン "
//----------------
~Sound()
{
this.Dispose( false );
}
public void Dispose()
{
this.Dispose( true );
GC.SuppressFinalize( this );
}
protected void Dispose( bool Managedも解放する )
{
Action サウンドデータを解放する = () => {
if( null != this.サウンドデータ )
{
FDK.Memory.Free( (void*) this.サウンドデータ );
this.サウンドデータ = null;
}
};
if( Managedも解放する )
{
// C#オブジェクトの解放があればここで。
// this.排他利用を使った Unmanaged の解放。
lock( this.排他利用 )
{
サウンドデータを解放する();
}
}
else
{
// (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。
サウンドデータを解放する();
}
}
//----------------
#endregion
private bool 作成済み = false;
private byte* サウンドデータ = null;
private int サウンドデータサイズbyte = 0;
private int サウンドデータサイズsample = 0;
private int 再生位置sample = 0;
private readonly SharpDX.Multimedia.WaveFormat WAVEフォーマット = new SharpDX.Multimedia.WaveFormat( 44100, 16, 2 ); // 固定
private readonly object 排他利用 = new object();
#region " バックストア "
//----------------
private float bs_音量 = 1.0f;
//----------------
#endregion
#region " Win32 API "
//-----------------
[System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
private static extern unsafe void CopyMemory( void* dst, void* src, int size );
[System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
private static extern unsafe void ZeroMemory( void* dst, int length );
//-----------------
#endregion
}
}