Skip to content

Instantly share code, notes, and snippets.

@43x2
Created May 13, 2012 18:00
Show Gist options
  • Save 43x2/2689517 to your computer and use it in GitHub Desktop.
Save 43x2/2689517 to your computer and use it in GitHub Desktop.
XAudio2 でストリーミング再生
#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 )
// 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