サウンド処理は非同期で その12013/11/28

例えばサウンドの再生、OGGファイルを読み込んで再生するような場合、
・ファイルの読み込み
・オブジェクトの初期化などOpenSLで必要な各種設定
・出だしのデータのデコード
・再生
と、結構多くの処理が必要です。
その処理の間メインを止めてしまっているのは何とも忍びないので、サウンドは別のスレッドで非同期に処理するようにしてみます。

具体的には、
・再生などの命令が来たら、その命令とパラメータをキューに貯める
・別のスレッドで非同期にキューを監視して、命令を処理する
という流れになります。

キューを管理するスレッドの処理は javaで行います。

SoundManager.java
package sys;

import java.util.concurrent.ArrayBlockingQueue;
import android.os.AsyncTask;
import android.util.Log;


/******************
    サウンド管理
 ******************/
public class SoundManager extends AsyncTask<Void, Void, Void>
{
	static private SoundManager		manager;							// サウンド管理タスク
	static private ArrayBlockingQueue<SoundCommand>		queue;			// コマンドキュー


	/************
	    初期化
	 ************/
	static
	public void		init()
	{
		queue	= new ArrayBlockingQueue<SoundCommand>(32, true);		// キュー作成
		manager	= new SoundManager();
		manager.execute();
	}

	/**********
	    終了
	 **********/
	static
	public void		quit()
	{
		queue	= null;
		manager	= null;
	}

	/******************
	    コマンド予約
	 ******************/
	static
	public void		set_command(short _channel, short _command, int _data, int _size, short _loop, float _volume)
	{
		try {
			queue.put(new SoundCommand(_channel, _command, _data, _size, _loop, _volume));
		}
		catch (InterruptedException e) {}
	}

	/******************
		コマンド停止
	 ******************/
	static
	public void		stop_command()
	{
		manager.cancel(true);
		while ( queue != null ) {
			try {
				Thread.sleep(1);
			}
			catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	@Override
	protected void	onPreExecute() {}

	public native void	sendSoundCommand(short _channel, short _command, int _data, int _size, short _loop, float _volume);

	/**********
	    稼働
	 **********/
	@Override
	protected Void	doInBackground(Void... params)
	{
		SoundCommand	_command;

		while ( !isCancelled() ) {
			try {
				_command = queue.take();					// コマンド取得
				if ( _command != null ) {					// コマンド実行
					sendSoundCommand(_command.channel, _command.command, _command.data, _command.size, _command.loop, _command.volume);
					_command = null;
				}
			}
			catch (InterruptedException e) {
				queue.clear();
			}
		}
		queue.clear();
		queue = null;
		return	null;
	}

	protected void	onPostExecute(Void result) {}
}


/**********
    命令
 **********/
class SoundCommand
{
	public short	channel;		// チャンネル
	public short	command;		// コマンド
	public int		data;			// データ
	public int		size;			// サイズ
	public short	loop;			// ループ回数
	public float	volume;			// 音量

	/********************
	    コンストラクタ
	 ********************/
	public SoundCommand(short _channel, short _command, int _data, int _size, short _loop, float _volume)
	{
		channel	= _channel;
		command	= _command;
		data	= _data;
		size	= _size;
		loop	= _loop;
		volume	= _volume;
	}
}

/******************** End of File *******************************************************/

重要なのは2つ、
・AsyncTaskで非同期処理
・ArrayBlockingQueueでキューの管理
ということくらいです。
サウンド管理と名前はついていますが、サウンドの処理自体は native側で行っていますので、ここでは"サウンド用のデータ"を管理しているだけです。

native側からは set_commandコマンドが呼ばれます。
ここでキューに貯められた命令を doInBackgroundの中で取り出して、nativeの関数 sendSoundCommandを呼び出して実際のサウンド処理を行うようになっています。


native側の処理は、また次回以降に。

サウンド - フェードアウト2013/11/26

前回のサウンド管理クラスで、サウンドプレイヤーをシステムで管理できるようになりました。
今回はプレイヤーに毎フレーム実行する処理を加え、サウンドのフェードアウトを実装してみます。


Sound.h
	float			volume;						// 音量
	float			fade_volume;				// フェードアウト音量

クラス sys::SoundPlayerのメンバ変数です。
フェードアウト時に使用する fade_volumeが追加されています。


Sound.cpp
/*************************************
    停止
		引数	_cnt = フェード時間
 *************************************/
void	SoundPlayer::stop(int _cnt)
{
	if ( bqPlayerObject ) {					// 再生中
		if ( (_cnt == 0) || (volume == 0.0f) ) {
			(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);			// 停止状態
			(*bqPlayerObject)->Destroy(bqPlayerObject);
			bqPlayerObject = NULL;
			if ( format == FORMAT_OGG ) {
				::ov_clear(&ov_file);
				format = -1;
			}
			if ( sound_data ) {
				delete	sound_data;
				sound_data = NULL;
			}
		}
		else {
			fade_volume = volume/_cnt;
			return;
		}
	}
	state = STOPPED;
}

サウンド停止関数の引数にフェードアウトする時間(フレーム単位)を追加しました。現在の音量と時間から1フレームの減衰量を fade_volumeに設定します。
時間が0、または音量がすでに0になっている場合は今までと同様に停止処理を行います。

/**********
    稼働
 **********/
void	SoundPlayer::update(void)
{
	switch ( state ) {
	  case STOPPED :						// 停止中
		if ( bqPlayerObject ) {	
			(*bqPlayerObject)->Destroy(bqPlayerObject);
			bqPlayerObject = NULL;
		}
		break;

	  case PLAYING :						// 再生中
		if ( fade_volume > 0.0f ) {
			volume -= fade_volume;
			if ( volume <= 0.0f ) {
				stop();
			}
			else {
				set_volume();
			}
		}
		break;
	}
}

フレーム毎に呼び出される処理です。
再生時、fade_volumeが0以上であればフェードアウト処理(音量を減衰)を行います。

ついでに、停止時プレイヤーオブジェクトが残っている場合に削除しています。終端まで再生して自然に停止したときオブジェクトは残ってしまいますが、使っていないオブジェクトは削除しておいた方が良さそうなので。

/**********
    稼働
 **********/
void	SoundManager::update(void)
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {
		player[i].update();
	}
}

サウンド管理クラスから、各プレイヤーの稼働処理を呼んでいます。
この関数はシステムのメインループから呼ばれています。


サンプルでは、画面をタッチする度にBGMのフェードアウトと再生を交互に繰り返します。
停止関数 stopにフェード時間を指定しているだけなので、説明は省略します。

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

サウンドをチャンネルで管理2013/11/25

サウンドプレイヤーは最低限、同時再生の数だけ必要ですが、通常そんなものはたかがしれています。
システム側でプレイヤーをチャンネルとして管理、アプリ側はチャンネル番号を指定して再生などのサウンド処理を行うようにしてみます。


Sound.h
/******************
    サウンド管理
 ******************/
class SoundManager
{
	static SoundPlayer*		player;				// プレイヤー

public :

	static void		init(void);					// 初期化
	static void		quit(void);					// 終了

	static void		play(int, const void*, u32, int _loop = 1, float _vol = 1.0f);			// 再生
	static void		play(int);
	static void		prepare(int, const void*, u32, int _loop = 1, float _vol = 1.0f);		// 再生準備
	static void		play(void);																// 全て再生
	static void		stop(int);					// 停止
	static void		stop(void);					// 全て停止
	static void		set_volume(int, float);		// 音量設定
	static void		set_master_volume(float);	// マスター音量設定
	static void		set_next(int, const void*, u32, int _loop = 1);							// 連続再生設定
	static void		pause(int);					// 一時停止
	static void		pause(void);				// 全て一時停止
	static void		pause_system(void);			// システムによる一時停止
	static void		resume(int);				// 再開
	static void		resume(void);				// 全て再開
	static void		resume_system(void);		// システムによる再開
};

サウンド管理クラス sys::SoundManagerです。
メンバ関数は基本的に sys::SoundPlayerの関数の先頭の引数にチャンネル番号をくっつけたものとなっています。


Sound.cpp
SoundPlayer*	SoundManager::player = NULL;			// プレイヤー

/************************
    サウンド管理初期化
 ************************/
void	SoundManager::init(void)
{
	SoundPlayer::init_engine();							// サウンドエンジン初期化
	player = new SoundPlayer[SOUND_CHANNEL_MAX];		// プレイヤー
}

/**********
    終了
 **********/
void	SoundManager::quit(void)
{
	if ( player ) {
		delete[]	player;
		player = NULL;
	}
	SoundPlayer::quit_engine();							// サウンドエンジン終了
}

初期化・終了処理です。
プレイヤーのインスタンスの確保・解放を行います。また、sys::SoundPlayerの初期化・終了処理もここで呼んでしまいます。
チャンネル数 SOUND_CHANNEL_MAXは、def.hで定義しています。

/********************************************************
    再生
		引数	_channel = チャンネル番号
				_data    = サウンドデータ
				_size    = サウンドデータサイズ
				_loop    = ループ回数(0:無限ループ)
				_vol     = 音量
 *******************************************************/
void	SoundManager::prepare(int _channel, const void* _data, u32 _size, int _loop, float _vol)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].prepare(_data, _size, _loop, _vol);
}

void	SoundManager::play(int _channel)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].play();
}

void	SoundManager::play(int _channel, const void* _data, u32 _size, int _loop, float _vol)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].prepare(_data, _size, _loop, _vol);
	player[_channel].play();
}

void	SoundManager::play(void)
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {		// 全て再生開始
		player[i].play();
	}
}

/*******************************************
    停止
		引数	_channel = チャンネル番号
 *******************************************/
void	SoundManager::stop(int _channel)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].stop();
}

void	SoundManager::stop(void)
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {		// 全て停止
		player[i].stop();
	}
}

/*********************************************************
    音量設定
		引数	_channel = チャンネル番号
				_vol     = 音量(0.0:最小 ~ 1.0:最大)
 *********************************************************/
void	SoundManager::set_volume(int _channel, float _vol)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].set_volume(_vol);
}

/*****************************************************
    マスター音量設定
		引数	_vol = 音量(0.0:最小 ~ 1.0:最大)
 *****************************************************/
void	SoundManager::set_master_volume(float _vol)
{
	if ( SoundPlayer::set_master_volume(_vol) ) {
		for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {
			player[i].set_volume();						// 音量再設定
		}
	}
}

/*******************************************************
    連続再生設定
		引数	_channel = チャンネル番号
				_data    = サウンドデータ
				_size    = サウンドデータサイズ
				_loop    = ループ回数(0:無限ループ)
 *******************************************************/
void	SoundManager::set_next(int _channel, const void* _data, u32 _size, int _loop)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].set_next(_data, _size, _loop);
}

再生等の処理です。
指定されたチャンネルのプレイヤーの処理を呼んでます。
停止等の処理でチャンネルを指定しない場合は、すべてのチャンネルのプレイヤーで処理を行うようになっています。

マスター音量設定時の各プレイヤーの音量の再設定はここで行いますので、アプリ側で行う必要はなくなりました。

/*********************************************
    一時停止
		引数	_f = 状態フラグを変更するか
 *********************************************/
void	SoundPlayer::pause(Bool _f)
{
	if ( state == PLAYING ) {				// 再生中
		if ( bqPlayerObject ) {
			(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);		// 一時停止状態
		}
		if ( _f ) {
			state = PAUSED;
		}
	}
}

/*********************************************
    一時停止
		引数	_channel = チャンネル番号
 *********************************************/
void	SoundManager::pause(int _channel)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].pause(TRUE);
}

void	SoundManager::pause(void)
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {		// 全て一時停止
		player[i].pause(TRUE);
	}
}

void	SoundManager::pause_system(void)		// システムによる一時停止
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {		// 全て一時停止
		player[i].pause(FALSE);
	}
}

/*********************************************
    再開
		引数	_channel = チャンネル番号
 *********************************************/
void	SoundManager::resume(int _channel)
{
	assert((0 <= _channel) && (_channel < SOUND_CHANNEL_MAX));

	player[_channel].resume();
}

void	SoundManager::resume(void)
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {		// 全て再開
		player[i].resume();
	}
}

void	SoundManager::resume_system(void)		// システムによる再開
{
	for (int i = 0; i < SOUND_CHANNEL_MAX; i++) {
		if ( player[i].get_state() == SoundPlayer::PLAYING ) {
			player[i].play();
		}
	}
}

一時停止・再開の処理も基本的には同じですが、サスペンド・レジュームによる処理をシステム側で行うことにします。
アプリ側で行う一時停止もシステム側で行う一時停止も OpenSLのプレイヤーの処理としては変わりませんが、サウンド管理ではこの二つは分ける必要があります。

例えば、
・メニューを開く、等でBGMを一時停止(アプリ側)
・サスペンドでサウンドを一時停止(システム側)
・レジュームでサウンド再開(システム側)
のときに、アプリ側で一時停止されたBGMを再開させてはいけません。

ここではサスペンドによる一時停止のときはプレイヤーの状態変数を PLAYINGのまま変えず、レジューム時に状態が PLAYINGのプレイヤーだけ再開するようにしています。アプリ側で一時停止されたプレイヤーの状態は PAUSEDになっていますので、このときには再開されません。


サンプル
AppMain.cpp
	sys::SoundManager::play(4, "bgm.ogg", sys::SoundPlayer::FILE_ASSET, 0);					// BGM再生開始

	if ( sys::TouchPanel[0].flag & sys::TouchManager::TRIGGER ) {
		sys::SoundManager::play(se_track, sound_data[SOUND_SE], sound_size[SOUND_SE]);		// SE再生
		se_track = ++se_track % 4;
	}

チャンネル指定でサウンドの再生を行っています。
また、
・プレイヤーのインスタンス確保、解放
・サスペンド、レジューム時のサウンド処理
が削除されています。


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

Ogg Vorbis再生2013/11/23

サウンドの圧縮フォーマット Ogg Vorbisの再生を行います。
といっても、OpenSLでPCMデータを再生する処理はできていますので、VorbisデータをPCMデータにデコードする処理が主となります。

Vorbisのデコードには、xiph.orgのTremorを使用させていただきました。
※TremorはBSDライセンスです。詳しくはプロジェクト内 jni\sys\Tremor\COPYING.txtを参照してください。

Sound.h
	OggVorbis_File	ov_file;					// OggVorbisファイル情報
	long			ov_pos;						// 現在位置
	char			pcm_buffer[2][0x400];		// PCMデータ展開バッファ
	SLuint32		pcm_size;					// PCMデータサイズ
	int				buf_cnt;					// 使用バッファカウンタ

追加されたメンバ変数です。
ov_fileでデコードの管理、pcm_bufferにPCMデータをデコードします。

Sound.cpp
	  case 0x5367674f :			// OGG
		{
			format = FORMAT_OGG;

			sound_data = new SoundData((char*)_data, _size, _loop, _file_data);
			ov_pos = 0;								// 現在位置
			if ( ::ov_open_callbacks(this, &ov_file, NULL, 0, callbacks) ) {
				LOGE("ov_open_callbacks ERROR!");
				::ov_clear(&ov_file);
				delete	sound_data;
				return;
			}

			vorbis_info*	_info = ::ov_info(&ov_file, -1);

			format_pcm.formatType		= SL_DATAFORMAT_PCM;
			format_pcm.numChannels		= (SLuint32)_info->channels;
			format_pcm.samplesPerSec	= (SLuint32)_info->rate*1000;
			format_pcm.bitsPerSample	= (SLuint32)SL_PCMSAMPLEFORMAT_FIXED_16;
			format_pcm.containerSize	= (SLuint32)SL_PCMSAMPLEFORMAT_FIXED_16;
			format_pcm.channelMask		= (_info->channels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
			format_pcm.endianness		= SL_BYTEORDER_LITTLEENDIAN;
		}
		break;

再生準備関数 prepare内です。
sound_dataを設定した後、ov_open_callbacksで ov_fileを初期化します。このとき sounda_dataからデータを取り出すコールバック関数(後述)を指定します。
また、ov_infoでOGGデータのフォーマットを取得してプレイヤーの初期化変数に設定します。

	  case FORMAT_OGG :			// OGG
		{
			result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallbackOGG, this);
			assert(SL_RESULT_SUCCESS == result);										// 再生コールバック設定

			pcm_size	= get_pcm_data(&pcm_buffer[0][0]);								// データ読み込み
			(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, &pcm_buffer[0][0], pcm_size);
			buf_cnt		= 1;
			pcm_size	= get_pcm_data(&pcm_buffer[1][0]);
		}
		break;

prepare内のデータ設定部分です。
まずは Ogg Vorbis用のコールバック関数を設定します。
次に get_pcm_dataで最初のデータをバッファにデコードして Enqueueでキューに送ります。そして、次のデータをデコードしておきます。

/*************************************
    OGG展開PCMデータ取得
			引数	_buf = バッファ
			戻り値	データサイズ
 *************************************/
SLuint32	SoundPlayer::get_pcm_data(char* _buf)
{
	long		read_size;
	SLuint32	total = 0;

	while ( total < 0x400 ) {
		read_size = ::ov_read(&ov_file, _buf + total, 0x400 - total, NULL);
		if ( bqPlayerObject == NULL ) {
			return	0;
		}
		else if ( read_size > 0 ) {
			total += read_size;
		}
		else if ( sound_data->loop != 1 ) {
			::ov_pcm_seek(&ov_file, 0);				// ループ
			if ( sound_data->loop > 1 ) {
				sound_data->loop--;
			}
		}
		else {
			if ( sound_data->next == NULL ) {		// 終了
				break;
			}

			SoundData*	_next = sound_data->next;	// 連続再生

			sound_data->next = NULL;
			delete	sound_data;
			sound_data = _next;

			::ov_clear(&ov_file);
			ov_pos = 0;
			if ( ::ov_open_callbacks(this, &ov_file, NULL, 0, callbacks) ) {
				LOGE("ov_open_callbacks ERROR!");
				::ov_clear(&ov_file);
				delete	sound_data;
				return	0;
			}
		}
	}
	return	total;
}

デコードデータ取得関数です。
基本的には ov_readを呼んでバッファにデータをデコードしているだけです。
戻り値が0の場合は終端までデコードしてしまったということなので、ループのため先頭に戻す・連続再生のため次のデータを設定するといった処理を行っています。

void	SoundPlayer::callback_ogg(void)
{
	if ( pcm_size > 0 ) {
		(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, &pcm_buffer[buf_cnt][0], pcm_size);
		buf_cnt = ++buf_cnt % 2;
		pcm_size = get_pcm_data(&pcm_buffer[buf_cnt][0]);
	}
	else {												// 終了
		(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
		::ov_clear(&ov_file);
		if ( sound_data ) {
			delete	sound_data;
			sound_data = NULL;
		}
		state = STOPPED;
	}
}

再生コールバック関数です。
データが残っている場合は、再生準備の時と同様にデータをキューに送り次のデータをデコードしています。
ループや連続再生の処理は get_pcm_dataの方で行っているので、WAVのコールバックより簡単になっています。

/********************
    メモリ読み込み
 ********************/
static
size_t	callback_ov_read(void* ptr, size_t size, size_t nmemb, void* datasource)
{
	return	((SoundPlayer*)datasource)->ov_read(ptr, size, nmemb);
}

size_t	SoundPlayer::ov_read(void* ptr, size_t size, size_t nmemb)
{
	if ( ptr == NULL ) {
		return	0;
	}

	size_t	_count = (size_t)((sound_data->size - ov_pos)/size);

	if ( _count >= nmemb ) {
		_count = nmemb;
	}
	else if ( _count <= 0 ) {
		return	0;
	}
	size *= _count;
	if ( size <= sound_data->size - ov_pos ) {
		memcpy(ptr, sound_data->data + ov_pos, size);
	}
	else {
		memcpy(ptr, sound_data->data + ov_pos, sound_data->size - ov_pos);
		memset((u8*)ptr + sound_data->size - ov_pos, 0x00, size - (sound_data->size - ov_pos));
	}
	ov_pos += size;

	return	_count;
}

/******************
    メモリシーク
 ******************/
static
int		callback_ov_seek(void* datasource, ogg_int64_t offset, int whence)
{
	return	((SoundPlayer*)datasource)->ov_seek(offset, whence);
}

int		SoundPlayer::ov_seek(ogg_int64_t offset, int whence)
{
	switch( whence ) {
	  case SEEK_CUR :
		ov_pos += offset;
		break;

	  case SEEK_END :
		ov_pos = sound_data->size + offset;
		break;

	  case SEEK_SET :
		ov_pos = offset;
		break;

	  default :
		return	-1;
	}

	if ( ov_pos > sound_data->size ) {
		ov_pos = sound_data->size;
		return	-1;
	}
	else if ( ov_pos < 0 ) {
		ov_pos = 0;
		return	-1;
	}
	return	0;
}

/********************
    メモリクローズ
 ********************/
static
int		callback_ov_close(void* datasource)
{
	return	((SoundPlayer*)datasource)->ov_close();
}

int		SoundPlayer::ov_close(void)
{
	return	0;
}

/********************
    メモリ位置通達
 ********************/
static
long	callback_ov_tell(void* datasource)
{
	return	((SoundPlayer*)datasource)->ov_tell();
}

long	SoundPlayer::ov_tell(void)
{
	return	ov_pos;
}

/***************************
    OggVorbisコールバック
 ***************************/
static
ov_callbacks	callbacks =
				{
					callback_ov_read,
					callback_ov_seek,
					callback_ov_close,
					callback_ov_tell,
				};

デコードライブラリから呼ばれるコールバック関数です。
元はファイルから読み込むことを想定しているようですが、メモリから読み込む処理を行っています。


サンプルはWAVファイルををOGGファイルに差し替えているだけなので、説明は省略します。

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

サウンド - 連続再生など2013/11/22

サウンドプレイヤーにに以下の機能を追加してみます。
・連続再生
・assetsファイル指定再生

連続再生
1つのサウンドデータを鳴らし終わった後に、続けて別のサウンドデータを鳴らす機能です。アプリ側で再生終了を監視して次のデータを再生するという手もありますが、ラグがあったり面倒だったりしますのでサウンドプレイヤーで処理するようにします。
BGMをイントロ部分と、メインのループ部分に分けて鳴らすようなときに使います。

ファイル指定再生
メモリに常駐するまでもない一時的なサウンドを、ファイルから読み込んで再生します。
ファイル上のサウンドデータを鳴らすだけならば、アプリ側でメモリに読み込んで再生すれば良いのですが、再生終了時にメモリを解放する必要があります。アプリ側で終了を監視するのはこれまた面倒なので、プレイヤーで処理します。

Sound.h
/********************
    サウンドデータ
 ********************/
struct SoundData
{
	char*		data;			// データ
	u32			size;			// データサイズ
	int			loop;			// ループカウンタ
	void*		file_data;		// 終了時に解放するデータ
	SoundData*	next;			// 次のデータ

		SoundData(char* _data, u32 _size, int _loop, void* _file = NULL)		// コンストラクタ
		{
			data = _data;
			size = _size;
			loop = _loop;
			file_data = _file;
			next = NULL;
		}
		~SoundData()							// デストラクタ
		{
			if ( file_data ) {
				free(file_data);
			}
			if ( next ) {
				delete	next;
			}
		}
	void	set_next(SoundData* _next)			// 次のデータを設定
			{
				if ( next ) {
					next->set_next(_next);
				}
				else {
					next = _next;
				}
			}
};

連続再生の予約で複数のサウンドデータを持つ必要があるため、サウンドデータを構造体 SoundDataで管理します。
再生終了時に解放するデータは、再生用データとは別に指定します。

Sound.cpp
	void*	_file_data;

	switch ( _size ) {
	  case FILE_ASSET :				// assetファイル
		_file_data = load_asset((char*)_data, &_size);
		_data = _file_data;
		break;

	  default :						// メモリ
		_file_data = NULL;
		break;
	}
			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 = new SoundData((char*)((u32)_data + sizeof(WaveFormat)), _info->data_size, _loop, _file_data);

再生準備関数 prepare内です。
assetsファイルを再生する場合は、_dataにファイル名文字列、_sizeに定数 sys::SoundPlayer::FILE_ASSETを指定します。
データ・サイズ等を管理する SoundDataを作成して sound_dataに保存します。

/****************************************************
    連続再生設定
		引数	_data = サウンドデータ
				_size = サウンドデータサイズ
				_loop = ループ回数(0:無限ループ)
 ****************************************************/
void	SoundPlayer::set_next(const void* _data, u32 _size, int _loop)
{
	if ( sound_data == NULL ) {				// すでに鳴り終わっている可能性
		play(_data, _size, _loop);
		return;
	}

	void*	_file_data;

	switch ( _size ) {
	  case FILE_ASSET :				// assetファイル
		_file_data = load_asset((char*)_data, &_size);
		_data = _file_data;
		break;

	  default :						// メモリ
		_file_data = NULL;
		break;
	}


	WaveFormat*		_info = (WaveFormat*)_data;

	sound_data->set_next(new SoundData((char*)((u32)_data + sizeof(WaveFormat)), _info->data_size, _loop, _file_data));
}

連続再生設定関数です。
再生準備と同様に SoundDataを作成して sound_dataの後ろにくっつけておきます。

void	SoundPlayer::callback_wav(void)
{
	if ( sound_data->loop == 1 ) {
		if ( sound_data ) {
			if ( sound_data->next == NULL ) {			// 終了
				delete	sound_data;
				sound_data = NULL;
			}
			else {										// 連続再生
				SoundData*	_next = sound_data->next;

				sound_data->next = NULL;
				delete	sound_data;
				sound_data = _next;
				(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data->data, (SLuint32)sound_data->size);
				return;
			}
		}
		(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);									// 停止
		state = STOPPED;
		return;
	}
	if ( sound_data->loop > 1 ) {
		sound_data->loop--;
	}
	(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data->data, (SLuint32)sound_data->size);		// 再生開始
}

再生コールバックです。
再生終了時に次のデータがあった場合は sound_dataを入れ替えて、再びキューにデータを送って再生を続けます。
終了したデータは deleteされ、このときファイルから読み込んだデータは SoundDataのデストラクタで解放されます。

この他、停止関数やデストラクタでもsound_dataの解放を行います。


サンプルのBGM再生部分です。
AppMain.cpp
	sound_player[4].play("bgm.wav", sys::SoundPlayer::FILE_ASSET, 3);						// BGM再生開始
	sound_player[4].set_next("bgm2.wav", sys::SoundPlayer::FILE_ASSET, 1);					// 連続再生設定

"bgm.wav"を3回鳴らした後に、"bgm2.wav"を1回鳴らしています。


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