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を実装します。


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

1 件のコメント:

  1. 終端にノイズが走っていたのはPCMデータの取得のし方が悪かっただけらしい。
    BufferQueueCallbackのループ時にPlayだとちょっとおかしいかも。
    this_player->m_enqueue_data = this_player->m_enqueue_data_base;
    this_player->Enqueue();
    の方が自然。

    返信削除