サウンド処理は非同期で その1 ― 2013/11/28
例えばサウンドの再生、OGGファイルを読み込んで再生するような場合、
・ファイルの読み込み
・オブジェクトの初期化などOpenSLで必要な各種設定
・出だしのデータのデコード
・再生
と、結構多くの処理が必要です。
その処理の間メインを止めてしまっているのは何とも忍びないので、サウンドは別のスレッドで非同期に処理するようにしてみます。
具体的には、
・再生などの命令が来たら、その命令とパラメータをキューに貯める
・別のスレッドで非同期にキューを監視して、命令を処理する
という流れになります。
キューを管理するスレッドの処理は javaで行います。
SoundManager.java
重要なのは2つ、
・AsyncTaskで非同期処理
・ArrayBlockingQueueでキューの管理
ということくらいです。
サウンド管理と名前はついていますが、サウンドの処理自体は native側で行っていますので、ここでは"サウンド用のデータ"を管理しているだけです。
native側からは set_commandコマンドが呼ばれます。
ここでキューに貯められた命令を doInBackgroundの中で取り出して、nativeの関数 sendSoundCommandを呼び出して実際のサウンド処理を行うようになっています。
native側の処理は、また次回以降に。
・ファイルの読み込み
・オブジェクトの初期化など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
クラス sys::SoundPlayerのメンバ変数です。
フェードアウト時に使用する fade_volumeが追加されています。
Sound.cpp
サウンド停止関数の引数にフェードアウトする時間(フレーム単位)を追加しました。現在の音量と時間から1フレームの減衰量を fade_volumeに設定します。
時間が0、または音量がすでに0になっている場合は今までと同様に停止処理を行います。
フレーム毎に呼び出される処理です。
再生時、fade_volumeが0以上であればフェードアウト処理(音量を減衰)を行います。
ついでに、停止時プレイヤーオブジェクトが残っている場合に削除しています。終端まで再生して自然に停止したときオブジェクトは残ってしまいますが、使っていないオブジェクトは削除しておいた方が良さそうなので。
サウンド管理クラスから、各プレイヤーの稼働処理を呼んでいます。
この関数はシステムのメインループから呼ばれています。
サンプルでは、画面をタッチする度にBGMのフェードアウトと再生を交互に繰り返します。
停止関数 stopにフェード時間を指定しているだけなので、説明は省略します。
プロジェクト一式は、こちらから。
今回はプレイヤーに毎フレーム実行する処理を加え、サウンドのフェードアウトを実装してみます。
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
サウンド管理クラス sys::SoundManagerです。
メンバ関数は基本的に sys::SoundPlayerの関数の先頭の引数にチャンネル番号をくっつけたものとなっています。
Sound.cpp
初期化・終了処理です。
プレイヤーのインスタンスの確保・解放を行います。また、sys::SoundPlayerの初期化・終了処理もここで呼んでしまいます。
チャンネル数 SOUND_CHANNEL_MAXは、def.hで定義しています。
再生等の処理です。
指定されたチャンネルのプレイヤーの処理を呼んでます。
停止等の処理でチャンネルを指定しない場合は、すべてのチャンネルのプレイヤーで処理を行うようになっています。
マスター音量設定時の各プレイヤーの音量の再設定はここで行いますので、アプリ側で行う必要はなくなりました。
一時停止・再開の処理も基本的には同じですが、サスペンド・レジュームによる処理をシステム側で行うことにします。
アプリ側で行う一時停止もシステム側で行う一時停止も OpenSLのプレイヤーの処理としては変わりませんが、サウンド管理ではこの二つは分ける必要があります。
例えば、
・メニューを開く、等でBGMを一時停止(アプリ側)
・サスペンドでサウンドを一時停止(システム側)
・レジュームでサウンド再開(システム側)
のときに、アプリ側で一時停止されたBGMを再開させてはいけません。
ここではサスペンドによる一時停止のときはプレイヤーの状態変数を PLAYINGのまま変えず、レジューム時に状態が PLAYINGのプレイヤーだけ再開するようにしています。アプリ側で一時停止されたプレイヤーの状態は PAUSEDになっていますので、このときには再開されません。
サンプル
AppMain.cpp
チャンネル指定でサウンドの再生を行っています。
また、
・プレイヤーのインスタンス確保、解放
・サスペンド、レジューム時のサウンド処理
が削除されています。
プロジェクト一式は、こちらから。
システム側でプレイヤーをチャンネルとして管理、アプリ側はチャンネル番号を指定して再生などのサウンド処理を行うようにしてみます。
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
追加されたメンバ変数です。
ov_fileでデコードの管理、pcm_bufferにPCMデータをデコードします。
Sound.cpp
再生準備関数 prepare内です。
sound_dataを設定した後、ov_open_callbacksで ov_fileを初期化します。このとき sounda_dataからデータを取り出すコールバック関数(後述)を指定します。
また、ov_infoでOGGデータのフォーマットを取得してプレイヤーの初期化変数に設定します。
prepare内のデータ設定部分です。
まずは Ogg Vorbis用のコールバック関数を設定します。
次に get_pcm_dataで最初のデータをバッファにデコードして Enqueueでキューに送ります。そして、次のデータをデコードしておきます。
デコードデータ取得関数です。
基本的には ov_readを呼んでバッファにデータをデコードしているだけです。
戻り値が0の場合は終端までデコードしてしまったということなので、ループのため先頭に戻す・連続再生のため次のデータを設定するといった処理を行っています。
再生コールバック関数です。
データが残っている場合は、再生準備の時と同様にデータをキューに送り次のデータをデコードしています。
ループや連続再生の処理は get_pcm_dataの方で行っているので、WAVのコールバックより簡単になっています。
デコードライブラリから呼ばれるコールバック関数です。
元はファイルから読み込むことを想定しているようですが、メモリから読み込む処理を行っています。
サンプルはWAVファイルををOGGファイルに差し替えているだけなので、説明は省略します。
プロジェクト一式は、こちらから。
といっても、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
連続再生の予約で複数のサウンドデータを持つ必要があるため、サウンドデータを構造体 SoundDataで管理します。
再生終了時に解放するデータは、再生用データとは別に指定します。
Sound.cpp
再生準備関数 prepare内です。
assetsファイルを再生する場合は、_dataにファイル名文字列、_sizeに定数 sys::SoundPlayer::FILE_ASSETを指定します。
データ・サイズ等を管理する SoundDataを作成して sound_dataに保存します。
連続再生設定関数です。
再生準備と同様に SoundDataを作成して sound_dataの後ろにくっつけておきます。
再生コールバックです。
再生終了時に次のデータがあった場合は sound_dataを入れ替えて、再びキューにデータを送って再生を続けます。
終了したデータは deleteされ、このときファイルから読み込んだデータは SoundDataのデストラクタで解放されます。
この他、停止関数やデストラクタでもsound_dataの解放を行います。
サンプルのBGM再生部分です。
AppMain.cpp
"bgm.wav"を3回鳴らした後に、"bgm2.wav"を1回鳴らしています。
プロジェクト一式は、こちらから。
・連続再生
・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回鳴らしています。
プロジェクト一式は、こちらから。
最近のコメント