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ファイルに差し替えているだけなので、説明は省略します。
プロジェクト一式は、こちらから。
最近のコメント