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;
}

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