Marmalade SDKСтатьи

s3eSoundManager проигрывание wav и ogg файлов

Автор:

s3eSoundManager проигрывание wav и ogg файлов


Android_Icon_170x170_0 | s3eSoundManager проигрывание wav и ogg файлов

за основу был взят замечательный порт libvorbis под Marmalade SDK https://github.com/arf-it/marmalade-libvorbis
однако имеющийся там тестовый прект был заточен исключительно под проигрывание одного единственного семпла.
при попытке масштабировать столкнулся с следующими проблемами:
- зависание из-за дедлоков
- отказ проигрывать семпл после стопа
- базовый класс COggVorbisFileHelper не умеет проигрывать loop звуки.

очевидно что класс COggVorbisFileHelper нуждается если не в переписывании то в фиксе узких мест, мешающих маштабированию.

так же много интересного подчерпнул из проекта DeMoney https://github.com/juliamakogon/marmContrib
с удивлением узнал что звуки можно имксовать все в одном канале и вообще обращяться с ними как со спрайтами :-)
век живи - век учись, а всеравно дураком помрешь :(

прошу прощения что не предоставляю полностью рабочий проект - все наработки я сразу делаю под свой движок и в итоге моя реализация звукового менеджера тянет за собой 100500 говнокода.

начнем с описания манагера звуков и его работы
создаем канал и настраиваем его

bool SoundManager::createOggMixer( bool stereoOutput)
{
  if(stereoOutput)
  {
    if(s3eSoundGetInt(S3E_SOUND_STEREO_ENABLED))
      deviceIsStereo = true;
  }

  int nSoundChannel = s3eSoundGetFreeChannel();
  
  if(nSoundChannel == -1)
    return false;

  if(deviceIsStereo)
  {
    s3eSoundChannelRegister(nSoundChannel, S3E_CHANNEL_GEN_AUDIO, generateAudioCallback, this );
    s3eSoundChannelRegister(nSoundChannel, S3E_CHANNEL_GEN_AUDIO_STEREO, generateAudioCallback, this );
  }
  else
    s3eSoundChannelRegister(nSoundChannel, S3E_CHANNEL_GEN_AUDIO, generateAudioCallback, this );

  int16 dummydata[16];
  s3eSoundChannelPlay(nSoundChannel, dummydata, 8, 0, 0);
  return true;
}

статическая функция создания звукового семпла выглядит следующим образом:

resources::RSound* SoundManager::Sample_Create(char const* sample, char const* pakfilename /*NULL*/)
{
  resources::RSound* mNewSample = _S(SoundManager)->getSound( sample );

  if( mNewSample )
  {
    mNewSample->inc();
    return mNewSample;
  }

  if( strstr( sample, ".ogg" ) || strstr( sample, ".OGG" ) )
    mNewSample = COggSound::OggSample_Create( sample, pakfilename );

  if( strstr( sample, ".wav" ) || strstr( sample, ".WAV" ) )
    mNewSample = CWavSound::WavSample_Create( sample, pakfilename );

  if( !mNewSample )
  {
    return NULL;
  }

  _S( SoundManager )->addSound( mNewSample );

  return mNewSample;
}

тут у меня определяется по расширению что мы закачиваем и проигрываем - wav или ogg и соотвественно две реализации звуковых классов
которые унаследованы от одного базового класса RSound

загрузка и проигрыванеи звука внутри приложения выглядит примерно так

m_sample    = framework::SoundManager::Sample_Create( "0091670256.wav" );
m_sample->play( false );

или если нам надо звук проигрывать бесконечно то так:

m_sample    = framework::SoundManager::Sample_Create( "0091670256.wav" );
m_sample->play( true );


список семплов который проигрывается сейчас это просто массив + переменная в который храним сколько семплов в масиве в данный момент
MAX_SOUNDQUEUE я установил в 64.

int m_PlayingSamplesListLength;
resources::RSound* m_PlayingSamplesList[ MAX_SOUNDQUEUE ];

функция play содержит в себе крит секцию и добавление очередного семпла в список она вызывается изнутри звукового класса и сам объект передает в нее указатель на себя любимого.

bool SoundManager::play( resources::RSound* sample )
{

  s3eThreadLockAcquire( m_Section );

  if( m_PlayingSamplesListLength + 1 >= MAX_SOUNDQUEUE )
    return false;

  m_PlayingSamplesList[ m_PlayingSamplesListLength ] = sample;
  m_PlayingSamplesListLength++;
  
  s3eThreadLockRelease( m_Section  ); 
  
  return true;
}

главная каллбэк функция у меня выглядит так:

int32 SoundManager::generateAudioCallback(void* sys, void* user)
{
  return ((SoundManager*)user)->generateAudioCallback( sys );
}

int32 SoundManager::generateAudioCallback( void* sys )
{
  s3eThreadLockAcquire( m_Section );

  int samplesAvailable = 0;

  s3eSoundGenAudioInfo* info = (s3eSoundGenAudioInfo*)sys;
  int16* target = (int16*)info->m_Target;

  int inputSampleSize = true == deviceIsStereo ? 2 : 1;

  memset(info->m_Target, 0, info->m_NumSamples * inputSampleSize * sizeof(int16));

  formActiveChannelsAndDecode( samplesAvailable );

  if( 0 == samplesAvailable )
  {
    s3eThreadLockRelease( m_Section  ); 
    
    removeDoneChannels();

    return info->m_NumSamples;
  }

  int32 samplesMixed = mixSounds(info->m_NumSamples, samplesAvailable, target);

  removeDoneChannels();

  s3eThreadLockRelease( m_Section  ); 

  return samplesMixed;
}

сначала мы подсчитываем сколько всего семплов у нас готовы к проигрыванию в данный момент времени

void SoundManager::formActiveChannelsAndDecode( int& samplesAvailable )
{
  samplesAvailable = 0x7FFFFFF;  //MAX_INT

  if( 0 == m_PlayingSamplesListLength )
  {
    samplesAvailable = 0;
    return;
  }

  for( int i = 0; i < m_PlayingSamplesListLength; i++ )
  {
    resources::RSound *channel = m_PlayingSamplesList[ i ];

    if(!channel->isPaused() && !channel->isDone())//if active
    {      
      channel->decode();//decode

      int samplesReady = channel->get_decbufspace();
      
      if(samplesAvailable > samplesReady)
        samplesAvailable = samplesReady;
    }
  }

  if( 0x7FFFFFF == samplesAvailable )
    samplesAvailable = 0;
}


в этой функции происходит обработка звуков со всех проигрываемых семплов и заполнение данными канала.

int32 SoundManager::mixSounds(int numSamples, int hasSamples, int16* target)
{
  numSamples = numSamples * 2 > hasSamples ? hasSamples / 2 : numSamples;

  for(int i = 0;  i < numSamples; ++i)
  {
    int16 yLeft = 0;
    int16 yRight = 0;

    for( int i = 0; i < m_PlayingSamplesListLength; i++ )
    {
      resources::RSound *channel = m_PlayingSamplesList[ i ];

      yLeft  = clipToInt16( yLeft + ((channel->getSampleL() * channel->volume) >> 8) );
      
      //мы обязаны прочитать семпл - вдруг там стерео запись
      yRight  = clipToInt16( yRight + ((channel->getSampleR() * channel->volume) >> 8) );

      channel->incSampleCounter();
    }

    *target++ = yLeft;
    
    //но мы не обязаны миксовать если устройство не стерео
    if(deviceIsStereo)
      *target++ = yRight;
  }

  return numSamples;
}

и удаление из списка семплов которые отыглали свое или их застопили.

void SoundManager::removeDoneChannels()
{
  for( int i = 0; i < m_PlayingSamplesListLength; i++ )
  {
    resources::RSound *channel = m_PlayingSamplesList[ i ];

    if( channel->isDone() )
    {
      if( m_PlayingSamplesListLength > 1 )
        m_PlayingSamplesList[ i ] = m_PlayingSamplesList[ m_PlayingSamplesListLength - 1 ];

      m_PlayingSamplesListLength--;

      channel->onEnd();
    }
  }
}

исходники проекта в двух архивах
часть 1 s3eSoundManager
часть 2 s3eSoundManager

20 июня 2012 (Обновление: 1 июля 2012)

Комментарии [13]