Created
May 15, 2012 10:02
-
-
Save 43x2/2700516 to your computer and use it in GitHub Desktop.
XAudio2 で 32bit float ストリーミング再生
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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