11.01.2012

GoogleTestをiOSで行うときのまとめ

Google C++ Testing FrameworkをiOSで行うときに行ったことのまとめ
  1. GoogleTestのXcode用のプロジェクトのうちgtest-staticとgtest_main-static以外は必要ないのでスキーム及びターゲットは消してもかまわない。
  2. BaseSDKをiOSSDKのものに変更する。
  3. Compiler for C/C++/Ovjective-CをDefault Compilerに変更する。 
  4.  ビルド時にcrt_extern.hがないといわれることがあるので、crt_externs.hを追加する。
  5. あとはOSXで行うのと同様に行える。

8.19.2012

OpenSL ESでサスペンドしたい

OpenSL ESでOpenALで言うところの、
ALCdevice *device;
ALCcontext *alContext;
alcOpenDevice(NULL);
alContext = alcCreateContext(device, NULL);
alcMakeContextCurrent(alContext);


alcMakeContextCurrent(NULL);
alcSuspendContext(alContext);
alcSuspendContextに相当するものが欲しいので探してみましたが見つからないようなので、サスペンドする時状態を記録してから一度全SLObjectの破棄とEngineを破棄するようにしました。(しないとサスペンド状態の時に別のアプリケーションを立ち上げてそれでもSLを使用していた時に落ちてしまったので。)
復帰時には再生成したのち、前回の状態を見て再生したりということをしてみました。
もう少しスマートにできればいいのですけれども。

7.10.2012

ボリュームボタンでメディアの音量を調節できるようにする

OpenSL ESでサウンドを再生していて、音を再生しているときにしか音量がボリュームボタンで変更できなかったので調べた。

メディアの音量変更を有効にするには、ActivityのsetVolumeControlStreamAudioManager.STREAM_MUSICを渡すことで設定できるようになる。
他にも STREAM_ALARM, STREAM_DTMF, STREAM_NOTIFICATION, STREAM_RING, STREAM_SYSTEM, STREAM_VOICE_CALLがある。via(AndroidDevelopers.AudioManager)
あとはJNIを使用してJava側に作ったメソッドを呼び出せばよい。

const char* methodName = /*Java側に作成したメソッド名*/;
struct android_app* androidApp = /*android_mainの引数をあらかじめ保存しておく*/;

JavaVM* vm = androidApp->activity->vm;
jobject clazzObj = androidApp->activity->clazz;
JNIEnv* env;
vm->AttachCurrentThread( &env, NULL );
jclass clazz = env->GetObjectClass( clazzObj );
jmethodID methodID = env->GetMethodID( clazz, methodName, "()V" );
env->CallVoidMethod( clazzObj, methodID );
vm->DetachCurrentThread();

初期化のタイミングなどにコールすればいいだろう。

6.22.2012

ADBで端末のGPUの判別を行う

adbから端末のGPUの判別を行い各GPUに対応した圧縮テクスチャを転送などの固有処理を行うのに使用する。

adb shell cat /system/lib/egl/egl.cfg

これだけで値を取得することが出来る。ちなみに私の端末の場合は、

0 0 android
0 1 adreno200

と返ってくる。

6.18.2012

OpenAL その1 alSourceUnqueueBuffersが失敗する

OpenALにて、ストリーミング再生をするときに、アンキューを行うが、特定の条件の時に失敗する。
bufferネームとsourceネームをそれぞれ[buffer]と[source]としたとき

{
 ALint processed_buffer_num[1];
 alGetSourcei(source, AL_BUFFERS_PROCESSED, processed_buffer_num);
 if (processed_buffer_num[0] > 0)
 {
  ALenum type[1];
 alGetSourcei(source, AL_SOURCE_TYPE, type);
  alGetError();
  alSourceUnqueueBuffers(source, 1, buffer);
  ALenum error = alGetError();
 }
}
この状態でerrorが受け取った値がAL_INVALID_OPERATIONだった。つまりコンテキストがおかしいということらしい。
もちろんtype[0]の値は、AL_STREAMINGになるように設定している。
調べてみるとAL_LOOPINGをTRUEにしているとこの症状になるようだということが分かった。
なので、
{
 ALint processed_buffer_num[1];
 alGetSourcei(source, AL_BUFFERS_PROCESSED, processed_buffer_num);
 if (processed_buffer_num[0] > 0)
 {
  ALint loop = AL_FALSE;
  alGetSourcei(source, AL_LOOPING, &loop);
  alSourcei(source, AL_LOOPING, FALSE);
  alSourceUnqueueBuffers(source, 1, buffer);
  alSourcei(m_sources[0], AL_LOOPING, loop);
 }
}
とすることでアンキューできるようになった。しかしあまりスマートな感じがしない・・・

5.26.2012

OpenSL ES その4SLAndroidSimpleBufferQueueItfとSLBufferQueueItfの違い

SLAndroidSimpleBufferQueueItfとSLBufferQueueItfの違いがいまいちわからなかったので調べた。
 OpenSL ES for Androidを読んだ限りだとほとんど変わらない模様。ただSLBufferQueueItfは今後インターフェースが変更されたりするかもしれないからSLAndroidSimpleBufferQueueItfを作った模様。SLAndroidSimpleBufferQueueItfは今後変更することはないとのこと。単純な録音や再生だけならこっちを使ったほうがいいとか。
インターフェイスが同じなので
SL_IID_BUFFERQUEUE を SL_IID_ANDROIDSIMPLEBUFFERQUEUE
に、
SL_DATALOCATOR_BUFFERQUEUE を SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
に、
SLDataLocator_BufferQueue を SLDataLocator_AndroidSimpleBufferQueue
に、
SLBufferQueueItf を SLAndroidSimpleBufferQueueItfに置換するだけで変更することができる。

5.16.2012

OpenSL ES その3 BufferQueueを使用する

前回のPlayerですと予めプレイヤーを音声ファイル分作成しないといけないので鳴らせる数に限りがあるため(私の環境で試した場合は31個までできた。)PCMの再生に切り替えます。

enum SoundLoadType

class File;
class String;

{
 kSoundLoadOnetime,//先に全て読み込む
 kSoundLoadStream //ストリームで必要な時に読み込む
};

enum tDevice
{
 kDeviceAsset,//アセットから読み込むか
 kDeviceSDCard//SDカードから読み込むか
};

/**
 * @class
 * @brief サウンドデータ
*/
class SoundData
{
public:
 SoundData();
 ~SoundData();
 void Init(const cstr* file_name, tDevice device, SoundLoadType load_type);
 void Terminate();
 SLint16* PcmData() const {return m_pcm_data;}
 size_t PcmDataSize() const {return m_pcm_data_size;}
 void LoadProgress();
 void Use();
 void Unuse();
 SoundLoadType LoadType() const {return m_sound_load_type;}
private:
 SLint16* m_pcm_data;
 size_t m_pcm_data_size;
 size_t m_pcm_data_loaded_size;
 SoundLoadType m_sound_load_type;
 File m_file;
 int m_used_ref_count;
 String m_file_name;
 tDevice m_file_device;
public:
 const SoundDataInfo* Info() const {return &m_sound_data_info;}
private:
 SoundDataInfo m_sound_data_info;
};

/**
 * @class
 * @brief サウンド再生
*/
class SoundEngine::SoundPlayer
{
public:
 SoundPlayer();
 void Init(int channel);
 void Terminate();
 void Progress();
 void Play();
 void Pause();
 void Resume();
 void Stop();
 void SeekHead();
 void Loop(bool loop);
 float Volume()const;
 void Volume(float volume);
 void Mute(bool mute);
 bool IsPlaying() const;
 bool IsStopped() const;
 bool IsPaused() const;
 bool IsPlayend() const;
 void Playend(bool playend);
 void UseSoundData(SoundData* sound_data);
 void UnuseSoundData();
 bool Idle() const;
 bool Enqueue();
 void QueueClear();
private:
 static void BufferQueueCallback(SLBufferQueueItf caller, void *pContext);
 SLmillisecond Position()const;

 SLint16* m_enqueue_data_base;
 SLint16* m_enqueue_data;
 SLuint32 m_enqueue_data_size;
 SLPlayItf m_player_play;
 SLObjectItf m_player_object;
 SLVolumeItf m_player_volume;
 SLBufferQueueItf m_player_queue;
 SoundData* m_sound_data;
 bool m_playend;
 bool m_loop;
 float m_volume;
};

データとプレイヤーを分けます。使用方法としては最初に初期時にチャンネル数を渡し(モノラル(1)かステレオ(2))、Idleで使用可能状態か問い合わせ、Idle状態であればUseSoundDataで予め作成しておいたSoundDataを渡し、その後Playなどで再生できるようにする。尚、Progressは毎フレーム呼ぶ必要がある。

前回と被るので要所だけまとめると

/**
 * @brief  チャンネルの初期化
 * @param  チャンネル数
*/
void SoundPlayer::Init(int channel)
 gAssert(m_player_object == NULL, "player already initialized.");
 {
  m_sound_data = NULL;
 }

 SLresult result;
 {
  // configure audio source
  SLDataFormat_PCM pcm_format;
  pcm_format.formatType = SL_DATAFORMAT_PCM;
  pcm_format.samplesPerSec = SL_SAMPLINGRATE_22_05;
  pcm_format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
  pcm_format.containerSize = 16;
  pcm_format.endianness = SL_BYTEORDER_LITTLEENDIAN;
  switch (channel)
  {
  case 2:
   {
    //steleo
    pcm_format.numChannels = 2;
    pcm_format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    break;
   }
  case 1:
   {
    //mono
    pcm_format.numChannels = 1;
    pcm_format.channelMask = SL_SPEAKER_FRONT_CENTER;
    break;
   }
  default:
   gAssert(0, "t channnel isn't implemen.");
  }
  SLDataLocator_BufferQueue data_locator_buffer_queue = {SL_DATALOCATOR_BUFFERQUEUE, 4};

  SLDataSource audio_src = {&data_locator_buffer_queue, &pcm_format};

  // configure audio sink
  SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,  Instance().m_output_mix_object};
  SLDataSink audio_sink = {&loc_outmix, NULL};
  // create audio player
  const SLInterfaceID iids[] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
  const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
  SLEngineItf engine =  Instance().m_engine;
  result = (*engine)->CreateAudioPlayer(engine, &m_player_object, &audio_src, &audio_sink, ARRAY_SIZE(iids), iids, req);
  CheckError(result);
 }
 // realize the player
 result = (*m_player_object)->Realize(m_player_object, SL_BOOLEAN_FALSE);
 CheckError(result);
 // get the play interface
 result = (*m_player_object)->GetInterface(m_player_object, SL_IID_PLAY, &m_player_play);
 CheckError(result);
 // get the volume interface
 result = (*m_player_object)->GetInterface(m_player_object, SL_IID_VOLUME, &m_player_volume);
 CheckError(result);
 // get the queue interface
 result = (*m_player_object)->GetInterface(m_player_object, SL_IID_BUFFERQUEUE, &m_player_queue);
 CheckError(result);
 // setup to receive buffer queue event callbacks
 result = (*m_player_queue)->RegisterCallback(m_player_queue, BufferQueueCallback, this);
 CheckError(result);
}

/**
 * @brief ストリーミング読み込み
*/
void SoundPlayer::Progress()
{
 if (m_sound_data != NULL)
 {
  m_sound_data->LoadProgress();
 }
}

/**
 * @brief  頭出しシーク
*/
void SoundPlayer::SeekHead()
{
 Playend(false);
 Stop();
 QueueClear();
 Enqueue();
}

/**
 * @brief  サウンドデータをプレイヤーに結びつける
*/
void SoundPlayer::UseSoundData(SoundData* sound_data)
{
 gAssert(m_sound_data == NULL, "sound data already used.");
 sound_data->Use();
 m_enqueue_data_base = sound_data->PcmData();
 m_enqueue_data = m_enqueue_data_base ;
 m_enqueue_data_size = sound_data->PcmDataSize();
 m_sound_data = sound_data;
}

/**
 * @brief  サウンドデータをプレイヤーから解除する
*/
void SoundPlayer::UnuseSoundData()
{
 if (m_sound_data != NULL)
 {
  Stop();
  m_enqueue_data_base = NULL;
  m_enqueue_data = NULL;
  m_enqueue_data_size = 0;
  m_sound_data->Unuse();
  m_sound_data = NULL;
 }
}

/**
 * @brief  バッファクリア
*/
void  SoundPlayer::QueueClear()
{
 const SLresult result = (*m_player_queue)->Clear(m_player_queue);
 CheckError(result);
 m_enqueue_data = m_enqueue_data_base;
}

/**
 * @brief  キューのコールバック
 * @param
*/
void SoundPlayer::BufferQueueCallback(SLBufferQueueItf caller, void *context)
{
 SoundPlayer* this_player = reinterpret_cast(context);
 if (!this_player->Enqueue())
 {
  if (this_player->m_loop)
  {
   this_player->Play();
  }
  else
  {
   this_player->Stop();
   this_player->Playend(true);
  }
 }
}


データはwavの22.05Sample/secを想定しています。後はデータのパラメータkSoundLoadOnetimeかkSoundLoadStreamで初期化時にまとめて読むかProgressで必要な分ずつバッファリングしていくかの違いがあるだけでそれ以外の挙動は同じになるようにDataを実装します。


これを実装した場合ノイズが終端に走ることがあると思います。それの対処は次回ということで

5.06.2012

OpenSL ESその2

前回はエンジンとそこから出力を作成したので今回はそれらを利用してオーディオファイルの再生を行います。
まずオーディオを再生するためのプレイヤークラスを作成します。
class SoundPlayer
{
public:
  enum tDevice
  {
    kDeviceAsset,
    kDeviceSDCard:
  };
  SoundPlayer();
  ~SoundPlayer();
  void Init(const char* file_name, tDevice device, SLEngineItf engine, SLObjectItf output_mix_object);
  void Terminate();

  void Play();
  void Pause();
  void Stop();
  void Seek(float seek_second);
  void Loop(bool loop);
  float Volume()const;
  void Volume(float volume);
  void Mute(bool mute);
  bool IsPlaying() const;
  bool IsStopped() const;
  bool IsPaused() const;
  bool IsPlayend() const;

  void Playend(bool playend);
private:
  void CreatePlayerFromAsset(const cstr* file_name, SLEngineItf engine, SLObjectItf output_mix_object);
  void CreatePlayerFromFile(const cstr* file_name, SLEngineItf engine, SLObjectItf output_mix_object);

  static void PlayCallback(SLPlayItf caller, void *pContext, SLuint32 event);

  SLObjectItf m_player_object;
  SLPlayItf m_player_play;
  SLSeekItf m_player_seek;
  SLVolumeItf m_player_volume;
  bool m_playend;
  float m_volume;
};

/**
 * @class SoundPlayer
 * @brief オーディオ再生
*/
SoundPlayer::SoundPlayer()
: m_player_object(NULL)
, m_player_play(NULL)
, m_player_seek(NULL)
, m_player_volume(NULL)
, m_playend(false)
, m_volume(1.f)
{
}

SoundPlayer::SoundPlayer()
{
  Terminate();
}

/**
 * @brief  チャンネルの初期化
 * @param  読み込むファイル名
 * @param  読み込み元デバイスの指定
 * @param  プレイヤーを生成するためにエンジンを渡す
 * @param  出力先の指定
*/
void SoundPlayer::Init(const char* file_name, tDevice device, SLEngineItf engine, SLObjectItf output_mix_object)
{
  SLresult result;
  
  switch(device)
  {
  case kDeviceAsset:
    CreatePlayerFromAsset(file_path, engine, output_mix_object);
    break;
  case kDeviceSDCard:
    CreatePlayerFromFile(file_path, engine, output_mix_object);
  break;
  default:
    break;
  }
  result = (*m_player_object)->Realize(m_player_object, SL_BOOLEAN_FALSE);
  CheckError(result);
  result = (*m_player_object)->GetInterface(m_player_object, SL_IID_PLAY, &m_player_play);
  CheckError(result);
  result = (*m_player_object)->GetInterface(m_player_object, SL_IID_SEEK, &m_player_seek);
  CheckError(result);
  result = (*m_player_object)->GetInterface(m_player_object, SL_IID_VOLUME, &m_player_volume);
  CheckError(result);
  result = (*m_player_play)->RegisterCallback(m_player_play, PlayCallback, this);
  CheckError(result);
  result = (*m_player_play)->SetCallbackEventsMask(m_player_play, SL_PLAYEVENT_HEADATEND);
  CheckError(result);
}

void SoundPlayer::Terminate()
{
  if (m_player_object != NULL)
   {
    (*m_player_object)->Destroy(m_player_object);
    m_player_object = NULL;
    m_player_play = NULL;
    m_player_seek = NULL;
    m_player_volume = NULL;
  }
}

/**
 * @brief  シーク
 * @param シーク位置(ミリ秒)
*/
void SoundPlayer::Seek(float seek_milli_second)
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    Playend(false);
    result = (*m_player_seek)->SetPosition(m_player_seek, seek_milli_second, SL_SEEKMODE_FAST);
    CheckError(result);
  }
}

/**
 * @brief  ループの設定
 * @param
 * @return
*/
void SoundPlayer::Loop(bool loop)
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    result = (*m_player_seek)->SetLoop(m_player_seek, loop ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE, 0, SL_TIME_UNKNOWN);
    CheckError(result);
  }
}

float SoundPlayer::Volume(void) const
{
  return m_volume;
}

void SoundPlayer::Volume(float volume)
{
  gPrintfC("vol=%f\n", volume);
  m_volume = 1.f;
  m_volume = volume;
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    result = (*m_player_volume)->SetVolumeLevel(m_player_volume, Volume2Milibell(volume));
    CheckError(result);
  }
}

/**
 * @brief  再生
*/
void SoundPlayer::Play()
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    Playend(false);
    result = (*m_player_play)->SetPlayState(m_player_play, SL_PLAYSTATE_PLAYING);
    CheckError(result);
  }
}

/**
 * @brief  再生の一時停止
*/
void SoundPlayer::Pause()
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    result = (*m_player_play)->SetPlayState(m_player_play, SL_PLAYSTATE_PAUSED);
    CheckError(result);
  }
}

/**
 * @brief  再生の停止
*/
void SoundPlayer::Stop()
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    result = (*m_player_play)->SetPlayState(m_player_play, SL_PLAYSTATE_STOPPED);
    CheckError(result);
    Seek(0.f);
  }
}

/**
 * @brief  ミュートの設定
 * @param  ミュートにするか
*/
void SoundPlayer::Mute(bool mute)
{
  SLresult result;
  ASSERT(NULL != m_player_play, "player is NULL.");
  {
    result = (*m_player_volume)->SetMute(m_player_volume, mute ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE);
    CheckError(result);
  }
}

/**
 * @brief  再生中か
 * @return
*/
bool SoundPlayer::IsPlaying()const
{
  SLresult result;
  SLuint32 state;
  result = (*m_player_play)->GetPlayState(m_player_play, &state);
  CheckError(result);
  return SL_PLAYSTATE_PLAYING == state;
}

/**
 * @brief  停止したか
 * @return
*/
bool SoundPlayer::IsStopped()const
{
  SLresult result;
  SLuint32 state;
  result = (*m_player_play)->GetPlayState(m_player_play, &state);
  CheckError(result);
  return SL_PLAYSTATE_STOPPED == state;
}

/**
 * @brief  一時停止中か
 * @return
*/
bool SoundPlayer::IsPaused()const
{
  SLresult result;
  SLuint32 state;
  result = (*m_player_play)->GetPlayState(m_player_play, &state);
  CheckError(result);
  return SL_PLAYSTATE_PAUSED == state;
}

/**
 * @brief  再生終端か
 * @return
*/
bool SoundPlayer::IsPlayend()const
{
  return m_playend;
}

/**
 * @brief  再生終了を設定する
 * @param
 * @return
*/
void SoundPlayer::Playend(bool playend)
{
  m_playend = playend;
}

/**
 * @brief  アセットからプレイヤーを生成
 * @param  ファイルパス
*/
void SoundPlayer::CreatePlayerFromAsset(const cstr* file_name, SLEngineItf engine, SLObjectItf output_mix_object)
{
  SLresult result;
  struct android_app* pAndroidApp = nsPFW::IApplication::gArguments().mAndroid.mpAndroidApp;
  AAssetManager* asset_manager = pAndroidApp->activity->assetManager;
  AAsset* asset = AAssetManager_open(asset_manager, file_name, AASSET_MODE_UNKNOWN);
  ASSERT(asset != NULL, "asset is not found.");
  off_t start, length;
  const int fd = AAsset_openFileDescriptor(asset, &start, &length);
  ASSERT(0 <= fd, "asset not found.");
  AAsset_close(asset);

  SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
  SLDataFormat_MIME format = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_AAC};
  SLDataSource audio_src = {&loc_fd, &format};
  SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, output_mix_object};
  SLDataSink audio_sink = {&loc_outmix, NULL};
  const SLInterfaceID iids[] = {SL_IID_PLAY, SL_IID_SEEK, SL_IID_VOLUME};
  const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
  result = (*engine)->CreateAudioPlayer(engine, &m_player_object, &audio_src, &audio_sink, SPL_ARRAY_SIZE(iids), iids, req);
  CheckError(result);
}

/**
 * @brief  ファイルからプレイヤーを生成
 * @param  ファイルパス
 * @return
*/
void SoundPlayer::CreatePlayerFromFile(const cstr* file_name, SLEngineItf engine, SLObjectItf output_mix_object)
{
  SLresult result;
  {
    SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, reinterpret_cast(file_name)};
    SLDataFormat_MIME format = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_AAC};
    SLDataSource audio_src = {&loc_uri, &format};

    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, output_mix_object};
    SLDataSink audio_sink = {&loc_outmix, NULL};

    const SLInterfaceID iids[] = {SL_IID_PLAY, SL_IID_SEEK, SL_IID_VOLUME};
    const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engine)->CreateAudioPlayer(engine, &m_player_object, &audio_src, &audio_sink, SPL_ARRAY_SIZE(iids), iids, req);
    CheckError(result);
  }
}

/**
 * @brief  再生用のコールバック関数
 * @param
*/
void SoundPlayer::PlayCallback(SLPlayItf caller, void *pContext, SLuint32 event)
{
  SoundPlayer* player = reinterpret_cast(pContext);
  switch(event)
  {
  case SL_PLAYEVENT_HEADATEND:
    player->Playend(true);
    gPassed;
    break;
  }
}

大まかな流れとしてはあらかじめ生成したエンジンとアウトプットミックスを渡して初期化し、その後各インターフェイスを取り出す。取り出したものに対して操作を行っていきます。
SLPlayItfは再生・停止・一時停止などの操作に使用します。
SLSeekItfは再生位置の変更やループの設定などが行えます。
SLVolumeItfは音量の設定やミュートなどに使用します。
尚音量はミリベルで指定するので

#include
/**
 * @brief 音量(0-1)をミリベルへ変換する
 */
SLint16 Volume2Milibell(float volume)
{
  const float min_limit = 0.1f;
  const float vol = gClamp(1.f - volume, 0.f, 1.f);
  SLint16 volume_level = SL_MILLIBEL_MIN;
  if (volume > min_limit)
  {
    const static float base2 = log(2);
    const float antilogarithm = log(1.f / vol);
    const float bell = base2 / antilogarithm;
    volume_level = static_cast(bell * -1000.f);
  }
  return volume_level;
}

のようなものを用意しておくとよいと思います。

4.24.2012

OpenSL ES事始

OpenSL ESを触っていたので備忘録的にまとめたいと思います。
最初なのでエンジンの生成、破棄の流れをざらっと書いてみます。

#define PRINT(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ERROR_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

#define CheckError(result) ErrorAnalyze(result, __LINE__)
#define LOG_RESULT(type) case type:\
                         ERROR_LOG("Error="#type"(%d)", line);\
                         break;
#define ASSERT(exp, ...) if (!(exp)){ERROR_LOG(__VA_ARGS__); for(;;){}}
//エラー内容の出力
inline void ErrorAnalyze(SLresult result, int line)
{
  if (SL_RESULT_SUCCESS != result)
  {
    switch(result)
    {
      LOG_RESULT(SL_RESULT_PRECONDITIONS_VIOLATED)
      LOG_RESULT(SL_RESULT_PARAMETER_INVALID)
      LOG_RESULT(SL_RESULT_MEMORY_FAILURE)
      LOG_RESULT(SL_RESULT_RESOURCE_ERROR)
      LOG_RESULT(SL_RESULT_RESOURCE_LOST)
      LOG_RESULT(SL_RESULT_IO_ERROR)
      LOG_RESULT(SL_RESULT_BUFFER_INSUFFICIENT)
      LOG_RESULT(SL_RESULT_CONTENT_CORRUPTED)
      LOG_RESULT(SL_RESULT_CONTENT_UNSUPPORTED)
      LOG_RESULT(SL_RESULT_CONTENT_NOT_FOUND)
      LOG_RESULT(SL_RESULT_PERMISSION_DENIED)
      LOG_RESULT(SL_RESULT_FEATURE_UNSUPPORTED)
      LOG_RESULT(SL_RESULT_INTERNAL_ERROR)
      LOG_RESULT(SL_RESULT_UNKNOWN_ERROR)
      LOG_RESULT(SL_RESULT_OPERATION_ABORTED)
      LOG_RESULT(SL_RESULT_CONTROL_LOST)
      default:
        return;
    }
  }
  ERROR_LOG("error=%u", result);
}

SLEngineItf engine;
SLObjectItf engine_object;
SLObjectItf output_mix_object;

//生成
{
  SLresult result;
  {
    //エンジンの生成
    const SLInterfaceID iids[] = {SL_IID_ENGINE};
    const SLboolean reqs[] = {SL_BOOLEAN_TRUE};
    result = slCreateEngine(&engine_object, 0, NULL, ARRAY_SIZE(iids), iids, reqs);
    CheckError(result);
  }
  {
    result = (*engine_object)->Realize(engine_object, SL_BOOLEAN_FALSE);
    CheckError(result);
    result = (*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine);
    CheckError(result);
  }
  {
    //出力先の生成
    const SLInterfaceID iids[] = {};
    const SLboolean reqs[] = {};
    (*engine)->CreateOutputMix(engine, &output_mix_object, ARRAY_SIZE(iids), iids, reqs);
    CheckError(result);
  }
  {
    result = (*output_mix_object)->Realize(output_mix_object, SL_BOOLEAN_FALSE);
    CheckError(result);
  }
}

//破棄
{
  //出力先の破棄
  if (output_mix_object != NULL)
  {
    (*output_mix_object)->Destroy(output_mix_object);
    output_mix_object = NULL;
  }
  //エンジンの破棄
  if (engine_object != NULL)
  {
    (*engine_object)->Destroy(engine_object);
    engine_object = NULL;
    engine = NULL;
  }
}

こんな感じでエンジンを先ず作成し、そこから必要なオブジェクトを作成していきます。
作成するときに必要なインターフェイスを渡してあげます。

2.27.2012

Googleドキュメントのテスト

GoogleドキュメントのテストとしてOpenSL ESを簡単に触れたプレゼンテーションを公開してみる。

2.04.2012

vs-androidでコンパイルできない

vs-andoridを導入したときにコンパイルできなかったので解決に至るまでのメモ。

Android/Usage of vs-android を参考にしてインストールしましたが、ビルドしたときにAndroidSDKの\tools\ant\buid.xmlで途中でメモリが足りなくなりコンパイルタスク以降が実行できないということがありました。見ていくとどうやらJVMが悪いというよりはvs-android側が悪いようです。
公式のissueのIssue 15: -dex: could not create the Java virtual machineを参考にやったところ、regeditで HKEY_CURRENT_USER\Environmentに新規に「_JAVA_OPTIONS」を追加し、「-Xms256m -Xmx512m」と値を追加してリブートしたところ無事ビルドできるようになりました。

1.30.2012

AndroidNDK環境構築

AndroidNDKを使う機会があったのメモ程度に手順をまとめたいと思います。
今回WinGDB  for Mobile Systems Developmentというのを使用しました。これはVisualStudio上でビルドを行いデバッグが一応できます。
この前までβ版だったのですが正式版が出ました。 1ライセンス$89なので個人で利用するにしても手が出しやすいのではないかなと思います。ただ、まだバグがちらほらあります。

  • AndroidSDKのインストール
まずこれがないと始まりません。

  Android DeveropmentからSDKをダウンロードし、インストールします。
インストールしたらSDK Managerを起動し、必要になるバージョンを一通りインストールします。
※ API 9などのあまり使われていないバージョンのAPIがほしいときは下のほうのObsoleteにチェックを入れます。
※私が行ったときはAndroid SDK ToolsのRev.16をインストールするときに使用中になり上書きできない状態になっていたので一度SDK Managerを終了させてtoolsディレクトリを別名でその場にコピーしてコピーしたディレクトリの中のandroid.batを起動して事なきを得ました。もう少しうまいことしてほしいです。
  •  AndroidNDKのインストールを行います。
  • Antのインストールを行います。バージョン1.8以降のものが必要です。
  • JDKのインストールを行います。JDK5以降のものが必要です。
  • Cygwinのインストールを行います。 バージョン1.7以降のものが必要です。GNU Make 3.81以降が入っていなければ別途インストールするなりアップデートするなりしてください。
  • WinGDB  for Mobile Systems Developmentのインストールを行います。
  • VisualStudioを起動しWinGDBのPreferencesを開き、今までインストールしたもののパスを設定します。
  • 環境によってはビルド時にawkうんぬんといわれるかもしれません。 そのときは
    cygwin\bin\gawk.exeをandroid-ndk\prebuilt\windows\bin\awk.exeにコピーしてリネームするなりシンボリックリンク作るなりiniファイルを書き換えてください。