Skip to content

Instantly share code, notes, and snippets.

@43x2
Created May 15, 2012 10:02
Show Gist options
  • Save 43x2/2700516 to your computer and use it in GitHub Desktop.
Save 43x2/2700516 to your computer and use it in GitHub Desktop.
XAudio2 で 32bit float ストリーミング再生
#if !defined( WAVE_FILE_READER__H )
#define WAVE_FILE_READER__H
// C/C++ Common
#include <cstdio>
#include <cstring>
// Others
#include <pstdint.h> // Available at http://www.azillionmonkeys.com/qed/pstdint.h
class WaveFileReader
{
public:
// コンストラクタ
WaveFileReader()
: m_pFile( NULL )
, m_hasGotWaveFormat( false )
, m_firstSampleOffset( -1 )
, m_dataChunkSize( 0 )
, m_dataChunkSamples( 0 )
{
}
// デストラクタ
~WaveFileReader()
{
Close();
}
// オープン
bool Open( const TCHAR * filename )
{
if ( m_pFile ) return false;
if ( _tfopen_s( &m_pFile, filename, _T( "rb" ) ) != 0 ) return false;
return true;
}
// フォーマット情報を取得
const WAVEFORMATEX * GetWaveFormat()
{
// オープン済みか
if ( !m_pFile ) return NULL;
if ( !m_hasGotWaveFormat )
{
long offset = 12;
while ( 1 )
{
// チャンク先頭へ移動
if ( fseek( m_pFile, offset, SEEK_SET ) != 0 ) break;
// チャンクシグネチャを読み込み
char chunkSignature[ 4 ] = { 0 };
std::size_t readChars = 0;
while ( readChars < 4 )
{
std::size_t ret = fread( chunkSignature + readChars, sizeof( char ), 4 - readChars, m_pFile );
if ( ret == 0 ) break;
readChars += ret;
}
// チャンクサイズを読み込み
uint32_t chunkSize = 0;
if ( fread( &chunkSize, sizeof( uint32_t ), 1, m_pFile ) == 0 ) break;
// fmt チャンクが見つかったらフォーマット情報を読み込み
if ( strncmp( chunkSignature, "fmt ", 4 ) == 0 )
{
std::size_t readSize = chunkSize < sizeof( WAVEFORMATEX ) ? chunkSize : sizeof( WAVEFORMATEX );
if ( fread( &m_waveFormat, readSize, 1, m_pFile ) == 0 ) break;
// PCM のときは一応 cbSize を 0 にしておく (無視されるらしいけど)
if ( m_waveFormat.wFormatTag == WAVE_FORMAT_PCM ) m_waveFormat.cbSize = 0;
// フォーマット情報取得済み
m_hasGotWaveFormat = true;
}
// data チャンクが見つかったらオフセットとサイズを記憶
if ( strncmp( chunkSignature, "data", 4 ) == 0 )
{
m_firstSampleOffset = offset + 8; // シグネチャ 4bytes + サイズ 4bytes
m_dataChunkSize = chunkSize;
}
// 次のチャンクへ
offset += ( static_cast< long >( chunkSize ) + 8 );
}
if ( !m_hasGotWaveFormat ) return NULL; // どっかでエラーが起きてちゃんと拾えなかった
// フォーマット情報が取得でき次第 data チャンク内のサンプル数を計算
m_dataChunkSamples = m_dataChunkSize / m_waveFormat.nBlockAlign; // 必ず割り切れるはず
}
return &m_waveFormat;
}
// サンプル数を取得
std::size_t GetSamples()
{
// オープン済みか
if ( !m_pFile ) return 0;
// フォーマット情報を取得していなければここで
if ( !m_hasGotWaveFormat ) GetWaveFormat();
return m_dataChunkSamples;
}
// 生データ読み込み
std::size_t ReadRaw( const std::size_t start, const std::size_t samples, void * buffer )
{
// バッファアドレスが不正ではないか
if ( !buffer ) return 0; // 本来なら assert すべき
// オープン済みか
if ( !m_pFile ) return 0;
// フォーマット情報を取得していなければここで
if ( !m_hasGotWaveFormat )
{
if ( !GetWaveFormat() ) return 0;
}
// 開始位置がオーバーしていないか
if ( start >= m_dataChunkSamples ) return 0;
// 実際に読み込むサンプル数を計算
std::size_t actualSamples = start + samples > m_dataChunkSamples ? m_dataChunkSamples - start : samples;
// 読み込み開始位置へ移動
if ( fseek( m_pFile, m_firstSampleOffset + start * m_waveFormat.nBlockAlign, SEEK_SET ) != 0 ) return 0;
// 読み込み
std::size_t readSamples = 0;
while ( readSamples < actualSamples )
{
std::size_t ret = fread( reinterpret_cast< uint8_t * >( buffer ) + readSamples * m_waveFormat.nBlockAlign,
m_waveFormat.nBlockAlign,
actualSamples - readSamples,
m_pFile );
if ( ret == 0 ) break;
readSamples += ret;
}
return readSamples;
}
// 正規化済みデータ読み込み
std::size_t ReadNormalized( const std::size_t start, const std::size_t samples, float * left, float * right )
{
// 少なくとも 1ch ぶんは指定されているか
if ( !left ) return 0; // 本来なら assert すべき
// オープン済みか
if ( !m_pFile ) return 0;
// フォーマット情報を取得していなければここで
if ( !m_hasGotWaveFormat )
{
if ( !GetWaveFormat() ) return 0;
}
// 開始位置がオーバーしていないか
if ( start >= m_dataChunkSamples ) return 0;
// 実際に読み込むサンプル数を計算
std::size_t actualSamples = start + samples > m_dataChunkSamples ? m_dataChunkSamples - start : samples;
// 読み込み開始位置へ移動
if ( fseek( m_pFile, m_firstSampleOffset + start * m_waveFormat.nBlockAlign, SEEK_SET ) != 0 ) return 0;
// 読み込み
std::size_t readSamples = 0;
for ( ; readSamples < actualSamples; ++readSamples )
{
// 1 サンプル読み込み
uint32_t data[ 2 ];
std::size_t ret = fread( data, m_waveFormat.nBlockAlign, 1, m_pFile ); // Warning: 3 チャンネル以上が来るとバッファオーバーラン
if ( ret == 0 ) break;
// 量子化ビット数によって個別に処理
switch ( m_waveFormat.wBitsPerSample )
{
case 8:
// 8bit signed: -128...0...127
{
int8_t * data_s8 = reinterpret_cast< int8_t * >( data );
if ( m_waveFormat.nChannels == 1 )
{
float l = ( data_s8[0] < 0 ) ? static_cast< float >( data_s8[0] ) / 128.0f :
static_cast< float >( data_s8[0] ) / 127.0f ;
left[ readSamples ] = l;
if ( right ) right[ readSamples ] = l;
}
else
{
float l = ( data_s8[0] < 0 ) ? static_cast< float >( data_s8[0] ) / 128.0f :
static_cast< float >( data_s8[0] ) / 127.0f ;
float r = ( data_s8[1] < 0 ) ? static_cast< float >( data_s8[1] ) / 128.0f :
static_cast< float >( data_s8[1] ) / 127.0f ;
if ( right )
{
left[ readSamples ] = l;
right[ readSamples ] = r;
}
else
{
left[ readSamples ] = ( l + r ) * 0.5f;
}
}
}
break;
case 16:
// 16bit signed: -32768...0...32767
{
int16_t * data_s16 = reinterpret_cast< int16_t * >( data );
if ( m_waveFormat.nChannels == 1 )
{
float l = ( data_s16[0] < 0 ) ? static_cast< float >( data_s16[0] ) / 32768.0f :
static_cast< float >( data_s16[0] ) / 32767.0f ;
left[ readSamples ] = l;
if ( right ) right[ readSamples ] = l;
}
else
{
float l = ( data_s16[0] < 0 ) ? static_cast< float >( data_s16[0] ) / 32768.0f :
static_cast< float >( data_s16[0] ) / 32767.0f ;
float r = ( data_s16[1] < 0 ) ? static_cast< float >( data_s16[1] ) / 32768.0f :
static_cast< float >( data_s16[1] ) / 32767.0f ;
if ( right )
{
left[ readSamples ] = l;
right[ readSamples ] = r;
}
else
{
left[ readSamples ] = ( l + r ) * 0.5f;
}
}
}
break;
}
}
return readSamples;
}
// クローズ
void Close()
{
if ( m_pFile )
{
fclose( m_pFile );
m_pFile = NULL;
m_hasGotWaveFormat = false;
m_firstSampleOffset = -1;
m_dataChunkSize = 0;
m_dataChunkSamples = 0;
}
}
private:
// ファイルハンドル
FILE * m_pFile;
// フォーマット情報を取得済みか
bool m_hasGotWaveFormat;
// フォーマット情報
WAVEFORMATEX m_waveFormat;
// data チャンク内先頭サンプルへのオフセット
long m_firstSampleOffset;
// data チャンクサイズ
std::size_t m_dataChunkSize;
// data チャンク内サンプル数
std::size_t m_dataChunkSamples;
};
#endif // !defined( WAVE_FILE_READER__H )
// C/C++ Common
#include <cstdio>
#include <cstring>
#include <clocale>
#include <vector>
// Windows
#include <windows.h>
#include <tchar.h>
// XAudio2
#include <xaudio2.h>
// Others
#include "WaveFileReader.h"
#define COM_SAFE_RELEASE( p ) { if(p) { (p)->Release(); (p) = NULL; } }
int _tmain( int argc, TCHAR * argv[] )
{
_tsetlocale( LC_ALL, _T( "" ) );
HRESULT hr;
//
// WAVE ファイルオープン
WaveFileReader reader;
if ( !argv[1] || !reader.Open( argv[1] ) )
{
_tprintf_s( _T( "Failed: WaveFileReader::Open\n" ) );
//
return 1;
}
//
// XAudio2 初期化
//
if ( FAILED( hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ) ) )
{
_tprintf_s( _T( "Failed: CoInitializeEx (code=0x%X)\n" ), hr );
//
reader.Close();
return 1;
}
IXAudio2 * pXAudio2 = NULL;
UINT32 flags = 0;
#if defined( _DEBUG )
flags |= XAUDIO2_DEBUG_ENGINE;
#endif
if ( FAILED( hr = XAudio2Create( &pXAudio2, flags ) ) )
{
_tprintf_s( _T( "Failed: XAudio2Create (code=0x%X)\n" ), hr );
//
CoUninitialize();
reader.Close();
return 1;
}
_tprintf_s( _T( "pXAudio2 (0x%p)\n" ), pXAudio2 );
//
// マスタリングボイスの生成
//
IXAudio2MasteringVoice * pMasteringVoice = NULL;
if ( FAILED( hr = pXAudio2->CreateMasteringVoice( &pMasteringVoice, 2 ) ) )
{
_tprintf_s( _T( "Failed: IXAudio2::CreateMasteringVoice (code=0x%X)\n" ), hr );
//
COM_SAFE_RELEASE( pXAudio2 );
CoUninitialize();
reader.Close();
return 1;
}
_tprintf_s( _T( "pMasteringVoice (0x%p)\n" ), pMasteringVoice );
{
XAUDIO2_VOICE_DETAILS details;
pMasteringVoice->GetVoiceDetails( &details );
_tprintf_s( _T( " チャンネル数: %u\n" ), details.InputChannels );
_tprintf_s( _T( " サンプリングレート: %uHz\n" ), details.InputSampleRate );
}
//
// ソースボイスの生成 (44.1kHz, 32bit-float, 1ch)
//
WAVEFORMATEX sourceWaveFormat;
sourceWaveFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
sourceWaveFormat.nChannels = 2;
sourceWaveFormat.nSamplesPerSec = 44100;
sourceWaveFormat.wBitsPerSample = 32;
sourceWaveFormat.nBlockAlign = ( sourceWaveFormat.wBitsPerSample / 8 ) * sourceWaveFormat.nChannels;
sourceWaveFormat.nAvgBytesPerSec = sourceWaveFormat.nSamplesPerSec * sourceWaveFormat.nBlockAlign;
sourceWaveFormat.cbSize = 0;
IXAudio2SourceVoice * pSourceVoice = NULL;
if ( FAILED( hr = pXAudio2->CreateSourceVoice( &pSourceVoice, &sourceWaveFormat ) ) )
{
_tprintf_s( _T( "Failed: IXAudio2::CreateSourceVoice (code=0x%X)\n" ), hr );
//
pMasteringVoice->DestroyVoice();
COM_SAFE_RELEASE( pXAudio2 );
CoUninitialize();
reader.Close();
return 1;
}
_tprintf_s( _T( "pSourceVoice (0x%p)\n" ), pSourceVoice );
//
// バッファの準備
//
std::size_t nextFirstSample = 0;
std::size_t submitTimes = 0;
std::size_t bufferSamples = sourceWaveFormat.nSamplesPerSec * 3;
// プライマリバッファ
std::vector< float > primaryLeft( bufferSamples );
std::vector< float > primaryRight( bufferSamples );
std::vector< float > primaryMixed( bufferSamples * 2 );
if ( nextFirstSample < reader.GetSamples() )
{
std::size_t readSamples = reader.ReadNormalized( nextFirstSample, bufferSamples,
&( primaryLeft[0] ), &( primaryRight[0] ) );
if ( readSamples > 0 )
{
primaryMixed.clear();
for ( std::size_t i = 0; i < readSamples; ++i )
{
primaryMixed.push_back( primaryLeft.at( i ) );
primaryMixed.push_back( primaryRight.at( i ) );
}
XAUDIO2_BUFFER bufferDesc = { 0 };
bufferDesc.Flags = nextFirstSample + readSamples >= reader.GetSamples() ? XAUDIO2_END_OF_STREAM : 0;
bufferDesc.AudioBytes = readSamples * sourceWaveFormat.nBlockAlign;
bufferDesc.pAudioData = reinterpret_cast< BYTE * >( &( primaryMixed[0] ) );
pSourceVoice->SubmitSourceBuffer( &bufferDesc );
_tprintf_s( _T( "Read: 0・・・%u-----%u・・・%u\n" ),
nextFirstSample, nextFirstSample + readSamples - 1, reader.GetSamples() - 1 );
nextFirstSample += readSamples;
++submitTimes;
}
}
// セカンダリバッファ
std::vector< float > secondaryLeft( bufferSamples );
std::vector< float > secondaryRight( bufferSamples );
std::vector< float > secondaryMixed( bufferSamples * 2 );
//
// 再生
//
pSourceVoice->Start();
_tprintf_s( _T( "再生\n" ) );
while ( 1 )
{
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState( &state );
if ( state.BuffersQueued < 2 )
{
// キューにバッファを追加
std::vector< float > & bufferLeft = submitTimes & 1 ? secondaryLeft : primaryLeft;
std::vector< float > & bufferRight = submitTimes & 1 ? secondaryRight : primaryRight;
std::vector< float > & bufferMixed = submitTimes & 1 ? secondaryMixed : primaryMixed;
std::size_t readSamples = reader.ReadNormalized( nextFirstSample, bufferSamples,
&( bufferLeft[0] ), &( bufferRight[0] ) );
if ( readSamples > 0 )
{
bufferMixed.clear();
for ( std::size_t i = 0; i < readSamples; ++i )
{
bufferMixed.push_back( bufferLeft.at( i ) );
bufferMixed.push_back( bufferRight.at( i ) );
}
XAUDIO2_BUFFER bufferDesc = { 0 };
bufferDesc.Flags = nextFirstSample + readSamples >= reader.GetSamples() ? XAUDIO2_END_OF_STREAM : 0;
bufferDesc.AudioBytes = readSamples * sourceWaveFormat.nBlockAlign;
bufferDesc.pAudioData = reinterpret_cast< BYTE * >( &( bufferMixed[0] ) );
pSourceVoice->SubmitSourceBuffer( &bufferDesc );
_tprintf_s( _T( "Read: 0・・・%u-----%u・・・%u\n" ),
nextFirstSample, nextFirstSample + readSamples - 1, reader.GetSamples() - 1 );
nextFirstSample += readSamples;
++submitTimes;
}
// 終わりまで読み切ったら頭から読み直し (ループ再生)
if ( nextFirstSample >= reader.GetSamples() ) nextFirstSample = 0;
}
// ESC キーによる再生中断チェック
if ( GetAsyncKeyState( VK_ESCAPE ) & 0x1 ) // コンソールアプリにこういうのは不作法かもだがw
{
if ( GetForegroundWindow() == GetConsoleWindow() )
{
_tprintf_s( _T( "中断リクエスト\n" ) );
break;
}
}
// 過負荷にならないよう調整
Sleep( 1 );
}
pSourceVoice->Stop();
_tprintf_s( _T( "停止\n" ) );
// ちょっとだけ待つ
Sleep( 200 );
//
// 後始末
//
pSourceVoice->DestroyVoice();
pMasteringVoice->DestroyVoice();
COM_SAFE_RELEASE( pXAudio2 );
CoUninitialize();
reader.Close();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment