Created
May 13, 2012 18:00
-
-
Save 43x2/2689517 to your computer and use it in GitHub Desktop.
XAudio2 でストリーミング再生
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; | |
... | |
return 0; | |
} | |
// クローズ | |
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 <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; | |
// | |
// XAudio2 初期化 | |
// | |
if ( FAILED( hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ) ) ) | |
{ | |
_tprintf_s( _T( "Failed: CoInitializeEx (code=0x%X)\n" ), hr ); | |
goto FAILED_COINITIALIZEEX; | |
} | |
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 ); | |
goto FAILED_XAUDIO2CREATE; | |
} | |
_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 ); | |
goto FAILED_CREATEMASTERINGVOICE; | |
} | |
_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 ); | |
} | |
{ | |
// | |
// ソースボイスの生成 | |
// | |
WaveFileReader reader; | |
if ( !argv[1] || !reader.Open( argv[1] ) ) | |
{ | |
_tprintf_s( _T( "Failed: WaveFileReader::Open\n" ) ); | |
goto FAILED_WAVEFILEREADER_OPEN; | |
} | |
const WAVEFORMATEX * pWaveFormat = reader.GetWaveFormat(); | |
if ( !pWaveFormat ) | |
{ | |
_tprintf_s( _T( "Failed: WaveFileReader::GetWaveFormat\n" ) ); | |
goto FAILED_WAVEFILEREADER_GETWAVEFORMAT; | |
} | |
IXAudio2SourceVoice * pSourceVoice = NULL; | |
if ( FAILED( hr = pXAudio2->CreateSourceVoice( &pSourceVoice, pWaveFormat ) ) ) | |
{ | |
_tprintf_s( _T( "Failed: IXAudio2::CreateSourceVoice (code=0x%X)\n" ), hr ); | |
goto FAILED_CREATESOURCEVOICE; | |
} | |
_tprintf_s( _T( "pSourceVoice (0x%p)\n" ), pSourceVoice ); | |
_tprintf_s( _T( " チャンネル数: %u\n" ), pWaveFormat->nChannels ); | |
_tprintf_s( _T( " サンプリングレート: %uHz\n" ), pWaveFormat->nSamplesPerSec ); | |
_tprintf_s( _T( " 量子化ビット数: %u\n" ), pWaveFormat->wBitsPerSample ); | |
{ | |
// | |
// バッファの準備 | |
// | |
std::size_t nextFirstSample = 0; | |
std::size_t submitCount = 0; | |
// プライマリバッファ | |
std::vector< BYTE > primary( pWaveFormat->nAvgBytesPerSec * 3 ); | |
if ( nextFirstSample < reader.GetSamples() ) | |
{ | |
std::size_t readSamples = reader.ReadRaw( nextFirstSample, pWaveFormat->nSamplesPerSec * 3, &( primary[0] ) ); | |
if ( readSamples > 0 ) | |
{ | |
XAUDIO2_BUFFER bufferDesc = { 0 }; | |
bufferDesc.Flags = nextFirstSample + readSamples >= reader.GetSamples() ? XAUDIO2_END_OF_STREAM : 0; | |
bufferDesc.AudioBytes = readSamples * pWaveFormat->nBlockAlign; | |
bufferDesc.pAudioData = &( primary[0] ); | |
pSourceVoice->SubmitSourceBuffer( &bufferDesc ); | |
_tprintf_s( _T( "Read: 0・・・%u-----%u・・・%u\n" ), | |
nextFirstSample, nextFirstSample + readSamples - 1, reader.GetSamples() - 1 ); | |
nextFirstSample += readSamples; | |
++submitCount; | |
} | |
} | |
// セカンダリバッファ | |
std::vector< BYTE > secondary( pWaveFormat->nAvgBytesPerSec * 3 ); | |
// | |
// 再生 | |
// | |
pSourceVoice->Start(); | |
_tprintf_s( _T( "再生\n" ) ); | |
while ( 1 ) | |
{ | |
XAUDIO2_VOICE_STATE state; | |
pSourceVoice->GetState( &state ); | |
if ( state.BuffersQueued == 0 && nextFirstSample >= reader.GetSamples() ) | |
{ | |
// すべて再生し終わっている | |
break; | |
} | |
else if ( state.BuffersQueued < 2 && nextFirstSample < reader.GetSamples() ) | |
{ | |
// キューにバッファを追加 | |
std::vector< BYTE > & buffer = submitCount & 1 ? secondary : primary; | |
std::size_t readSamples = reader.ReadRaw( nextFirstSample, pWaveFormat->nSamplesPerSec * 3, &( buffer[0] ) ); | |
if ( readSamples > 0 ) | |
{ | |
XAUDIO2_BUFFER bufferDesc = { 0 }; | |
bufferDesc.Flags = nextFirstSample + readSamples >= reader.GetSamples() ? XAUDIO2_END_OF_STREAM : 0; | |
bufferDesc.AudioBytes = readSamples * pWaveFormat->nBlockAlign; | |
bufferDesc.pAudioData = &( buffer[0] ); | |
pSourceVoice->SubmitSourceBuffer( &bufferDesc ); | |
_tprintf_s( _T( "Read: 0・・・%u-----%u・・・%u\n" ), | |
nextFirstSample, nextFirstSample + readSamples - 1, reader.GetSamples() - 1 ); | |
nextFirstSample += readSamples; | |
++submitCount; | |
} | |
} | |
// ESC キーによる再生中断チェック | |
if ( GetKeyState( VK_ESCAPE ) & 0x8000 ) // コンソールアプリにこういうのは不作法かもだがw | |
{ | |
if ( GetForegroundWindow() == GetConsoleWindow() ) | |
{ | |
_tprintf_s( _T( "中断リクエスト\n" ) ); | |
break; | |
} | |
} | |
// 過負荷にならないよう調整 | |
Sleep( 1 ); | |
} | |
pSourceVoice->Stop(); | |
_tprintf_s( _T( "停止\n" ) ); | |
// | |
// 後始末 | |
// | |
pSourceVoice->DestroyVoice(); | |
} | |
FAILED_CREATESOURCEVOICE: | |
FAILED_WAVEFILEREADER_GETWAVEFORMAT: | |
reader.Close(); | |
FAILED_WAVEFILEREADER_OPEN: | |
; | |
} | |
pMasteringVoice->DestroyVoice(); | |
FAILED_CREATEMASTERINGVOICE: // IXAudio2::CreateMasteringVoice() が失敗 | |
COM_SAFE_RELEASE( pXAudio2 ); | |
FAILED_XAUDIO2CREATE: // XAudio2Create() が失敗 | |
CoUninitialize(); | |
FAILED_COINITIALIZEEX: // CoInitializeEx() が失敗 | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment