OpenSLでサウンド再生 ― 2013/11/19
OpenSLによるサウンドの再生です。
今回は最低限の処理として、PCMデータの再生を行います。
NDKにあるサンプル native-audioを参考にしていますが、データはWAVファイルから読み込むようにしています。
サウンドプレイヤークラス sys::SoundPlayer
Sound.h
静的メンバが、サウンドシステム全体として必要な共通部分です。
native-audioを見ると outputMixオブジェクトは Engineオブジェクトを作成するとき一緒に作成していますが、プレイヤー毎に作成しなければいけないようです。
Sound.cpp
サウンドシステム全体で必要なエンジン(Engine)オブジェクトとインタフェースを作成・初期化しています。
プレイヤーのコンストラクタ(及び、デストラクタ)です。
エンジンインタフェースから、出力(outputMix)オブジェクトを作成しています。
肝心のプレイヤーオブジェクトはPCMデータのフォーマットが必要なので、再生時に作成します。
サウンドの再生部分です。
再生されるまでそこそこ処理があるので、準備(prepare)と再生(play)の関数を分けています。
まず124~146行で WAVデータのヘッダから PCMのフォーマットを設定します。
そして sound_data・sound_sizeに PCMデータ本体のアドレスとサイズを格納しておきます。
次に156行でプレイヤーオブジェクトを作成して、175行までいろいろ OpenSLとして必要な設定を行います(今回音量の変更は行っていませんが、とりあえずインタフェースの取得だけ行っています)。
177行の Enqueueでサウンドデータをキューに送ります。
その後、183行で状態を SL_PLAYSTATE_PLAYINGに設定することによって再生が開始されます。
再生コールバックです。
ループ回数が残っていれば、再びデータをキューに送ります。
無ければ SL_PLAYSTATE_STOPPEDで停止状態にします。
停止処理です。
停止状態にしたうえで、プレイヤーオブジェクトを削除しておきます。
※再生コールバックの停止でも削除を試してみましたが、その後の処理が行われませんでした。Destroyした時点で、コールバック処理自体が切られてまうようです。
最後にサンプルプログラムです。
AppMain.cpp
サウンドプレイヤー:sound_player
4:BGM用
0~3:SE用
BGMは初期化時、SEは画面タッチしたときに再生します。
プロジェクト一式は、こちらから。
今回は最低限の処理として、PCMデータの再生を行います。
NDKにあるサンプル native-audioを参考にしていますが、データはWAVファイルから読み込むようにしています。
サウンドプレイヤークラス sys::SoundPlayer
Sound.h
#ifndef ___SOUND_H___ #define ___SOUND_H___ #include "common.h" #include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> namespace sys { /************************ サウンドプレイヤー ************************/ class SoundPlayer { public : static SLObjectItf engineObject; // エンジンオブジェクト static SLEngineItf engineEngine; // インタフェース static void init_engine(void); // 初期化 static void quit_engine(void); // 終了 private : SLObjectItf outputMixObject; // 出力オブジェクト SLObjectItf bqPlayerObject; // プレイヤーオブジェクト SLPlayItf bqPlayerPlay; // インタフェース SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; // バッファキューインタフェース SLVolumeItf bqPlayerVolume; // 音量インタフェース char* sound_data; // サウンドデータ u32 sound_size; // サウンドデータサイズ int sound_loop; // ループカウンタ public : SoundPlayer(void); // コンストラクタ ~SoundPlayer(); // デストラクタ void play(const void*, u32, int _loop = 1); // 再生 void play(void); void prepare(const void*, u32, int _loop = 1); // 再生準備 void stop(void); // 停止 void callback_wav(void); // 再生コールバック }; } #endif /********************* End of File ********************************/
静的メンバが、サウンドシステム全体として必要な共通部分です。
native-audioを見ると outputMixオブジェクトは Engineオブジェクトを作成するとき一緒に作成していますが、プレイヤー毎に作成しなければいけないようです。
Sound.cpp
/******************** エンジン初期化 ********************/ void SoundPlayer::init_engine(void) { SLresult result; result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); // エンジンオブジェクト作成 assert(SL_RESULT_SUCCESS == result); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // リアライズ assert(SL_RESULT_SUCCESS == result); result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); assert(SL_RESULT_SUCCESS == result); // インタフェース取得 } /****************** エンジン終了 ******************/ void SoundPlayer::quit_engine(void) { if ( engineObject ) { (*engineObject)->Destroy(engineObject); engineObject = NULL; } }
サウンドシステム全体で必要なエンジン(Engine)オブジェクトとインタフェースを作成・初期化しています。
/******************** コンストラクタ ********************/ SoundPlayer::SoundPlayer(void) { SLresult result; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); assert(SL_RESULT_SUCCESS == result); // 出力オブジェクト作成 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); // リアライズ bqPlayerObject = NULL; } /****************** デストラクタ ******************/ SoundPlayer::~SoundPlayer() { stop(); (*outputMixObject)->Destroy(outputMixObject); }
プレイヤーのコンストラクタ(及び、デストラクタ)です。
エンジンインタフェースから、出力(outputMix)オブジェクトを作成しています。
肝心のプレイヤーオブジェクトはPCMデータのフォーマットが必要なので、再生時に作成します。
struct WaveFormat { s8 riff[4]; u32 total_size; s8 fmt[8]; u32 fmt_size; u16 format; u16 channel; u32 rate; u32 avgbyte; u16 block; u16 bit; s8 data[4]; u32 data_size; }; /**************************************************** 再生 引数 _data = サウンドデータ _size = サウンドデータサイズ _loop = ループ回数(0:無限ループ) ****************************************************/ void SoundPlayer::prepare(const void* _data, u32 _size, int _loop) { stop(); SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; SLDataFormat_PCM format_pcm; SLDataSource audioSrc = {&loc_bufq, &format_pcm}; switch ( *((u32*)_data) ) { // データフォーマット case 0x46464952 : // WAV { WaveFormat* _info = (WaveFormat*)_data; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = (SLuint32)_info->channel; format_pcm.samplesPerSec = (SLuint32)_info->rate*1000; format_pcm.bitsPerSample = (SLuint32)_info->bit; format_pcm.containerSize = (SLuint32)_info->bit; format_pcm.channelMask = (_info->channel == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; sound_data = (char*)((u32)_data + sizeof(WaveFormat)); sound_size = _info->data_size; sound_loop = _loop; } break; default : assert(FALSE); break; } SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; SLDataSink audioSnk = {&loc_outmix, NULL}; const SLInterfaceID ids[3] = {SL_IID_PLAY, SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; SLresult result; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req); if ( SL_RESULT_SUCCESS != result ) { // プレイヤーオブジェクト作成 bqPlayerObject = NULL; return; } result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); // リアライズ assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); assert(SL_RESULT_SUCCESS == result); // インタフェース取得 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); assert(SL_RESULT_SUCCESS == result); // バッファキューインタフェース result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); assert(SL_RESULT_SUCCESS == result); // 音量インタフェース result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallbackWAV, this); assert(SL_RESULT_SUCCESS == result); // 再生コールバック設定 (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data, (SLuint32)sound_size); } void SoundPlayer::play(void) { if ( bqPlayerObject ) { (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); // 再生開始 } } void SoundPlayer::play(const void* _data, u32 _size, int _loop) { prepare(_data, _size, _loop); play(); }
サウンドの再生部分です。
再生されるまでそこそこ処理があるので、準備(prepare)と再生(play)の関数を分けています。
まず124~146行で WAVデータのヘッダから PCMのフォーマットを設定します。
そして sound_data・sound_sizeに PCMデータ本体のアドレスとサイズを格納しておきます。
次に156行でプレイヤーオブジェクトを作成して、175行までいろいろ OpenSLとして必要な設定を行います(今回音量の変更は行っていませんが、とりあえずインタフェースの取得だけ行っています)。
177行の Enqueueでサウンドデータをキューに送ります。
その後、183行で状態を SL_PLAYSTATE_PLAYINGに設定することによって再生が開始されます。
/********************** 再生コールバック **********************/ static void bqPlayerCallbackWAV(SLAndroidSimpleBufferQueueItf, void* context) { ((SoundPlayer*)context)->callback_wav(); } void SoundPlayer::callback_wav(void) { if ( sound_loop == 1 ) { (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); // 停止 return; } if ( sound_loop > 1 ) { sound_loop--; } (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data, (SLuint32)sound_size); // 再生開始 }
再生コールバックです。
ループ回数が残っていれば、再びデータをキューに送ります。
無ければ SL_PLAYSTATE_STOPPEDで停止状態にします。
/********** 停止 **********/ void SoundPlayer::stop(void) { if ( bqPlayerObject ) { // 再生中 (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); // 停止状態 (*bqPlayerObject)->Destroy(bqPlayerObject); bqPlayerObject = NULL; } }
停止処理です。
停止状態にしたうえで、プレイヤーオブジェクトを削除しておきます。
※再生コールバックの停止でも削除を試してみましたが、その後の処理が行われませんでした。Destroyした時点で、コールバック処理自体が切られてまうようです。
最後にサンプルプログラムです。
AppMain.cpp
#include "common.h" #include "Sprite.h" #include "TouchPanel.h" #include "Sound.h" // スプライト enum { SPR_PHOTO = 0, // 背景 SPR_MAX, }; static sys::Sprite* sprite; // スプライト // サウンドデータ enum { SOUND_BGM = 0, // BGM SOUND_SE, // SE SOUND_MAX, }; static sys::SoundPlayer* sound_player; // サウンドプレイヤー static char* sound_data[SOUND_MAX]; // サウンドデータ static u32 sound_size[SOUND_MAX]; // サウンドデータサイズ static int se_track; // SEトラック番号 /************ 初期化 ************/ void init_app(void) { struct SprDef { const char* tex_name; // テクスチャファイル名 SRect coord; // UV座標 }; static const SprDef spr_def[SPR_MAX] = { {"photo.pkm", { 0, 0, 640, 960}}, // 背景 }; sprite = new sys::Sprite[SPR_MAX]; // スプライト初期化 for (int i = 0; i < SPR_MAX; i++) { sprite[i].set(sys::TexCache::RES_ASSET, spr_def[i].tex_name, &spr_def[i].coord); } sound_player = new sys::SoundPlayer[5]; // サウンドプレイヤー sound_data[SOUND_BGM] = (char*)sys::load_asset("bgm.wav", &sound_size[SOUND_BGM]); // サウンドデータ読み込み sound_data[SOUND_SE] = (char*)sys::load_asset("se.wav", &sound_size[SOUND_SE]); se_track = 0; sound_player[4].play(sound_data[SOUND_BGM], sound_size[SOUND_BGM], 0); // BGM再生開始 } /********** 終了 **********/ void quit_app(void) { delete[] sound_player; for (int i = 0; i < SOUND_MAX; i++) { free(sound_data[i]); } delete[] sprite; } /****************************** 稼働 戻り値 アプリ続行か ******************************/ Bool update_app(void) { sprite[SPR_PHOTO].draw(0.0f, 0.0f); // 背景 if ( sys::TouchPanel[0].flag & sys::TouchManager::TRIGGER ) { sound_player[se_track].play(sound_data[SOUND_SE], sound_size[SOUND_SE]); // SE再生 se_track = ++se_track % 4; } return TRUE; } /************** 一時停止 **************/ void pause_app(void) { for (int i = 0; i < 5; i++) { sound_player[i].stop(); } } /********** 再開 **********/ void resume_app(void) { } /**************** End of File *************************************************/
サウンドプレイヤー:sound_player
4:BGM用
0~3:SE用
BGMは初期化時、SEは画面タッチしたときに再生します。
プロジェクト一式は、こちらから。
最近のコメント