OpenSLでサウンド再生2013/11/19

OpenSLによるサウンドの再生です。
今回は最低限の処理として、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は画面タッチしたときに再生します。


プロジェクト一式は、こちらから。