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は画面タッチしたときに再生します。
プロジェクト一式は、こちらから。
最近のコメント