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