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ファイルに差し替えているだけなので、説明は省略します。

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