SDL2でサウンドエフェクトと音楽の再生

SDL2でサウンドエフェクトと音楽を再生します。別途SDL_mixerが必要です。

 

1 SDL_mixer

SDL2単体でも音を出すことは可能なのですが、APIに則ったサウンドプログラミングを実装する必要があります。SDL_mixerはサウンドプログラミング部分を実装したライブラリで、簡単なコードで音を出すことができるようになります。


2 SDL_mixerの初期化・終了処理


2.1 Mix_Init

Mix_Initで対応フォーマットで使用するライブラリをロードします。

int Mix_Init(int flags)

mp3の場合はflagsにMIX_INIT_MP3を指定します。他のフォーマットも同時にサポートする場合は|で繋げます。Mix_Initを呼ばなくても動くようなのですが、一応実行します。


2.2 Mix_Quit

Mix_Quitでロードしたライブラリを解放します。

void Mix_Quit()

2.3 Mix_OpenAudio

Mix_OpenAudioでSDL_mixerのAPIを初期化します。

int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)

frequencyは音源の周波数設定です。音源ファイルの周波数は統一する必要があります。formatはSDL_AudioFormatで使用する値のようです。MIX_DEFAULT_FORMATで良いと思います。formatは1でモノラル、2でステレオになります。2で固定でしょう。chunksizeは内部で持つバッファのサイズです。4096とかで良いと思います。


2.4 Mix_CloseAudio

Mix_CloseAudioでSDL_mixerのAPIを終了します。

void Mix_CloseAudio()

3 サウンドエフェクトの再生

WAVを再生します。


3.1 Mix_Chunk構造体

音源ファイルを扱う構造体です。メンバ変数が外部から公開されていますが、基本的に触ることはないでしょう。

/* The internal format for an audio chunk */
typedef struct Mix_Chunk {
    int allocated;
    Uint8 *abuf;
    Uint32 alen;
    Uint8 volume;       /* Per-sample volume, 0-128 */
} Mix_Chunk;

3.2 Mix_LoadWAV

Mix_LoadWAVでファイル名からMix_Chunk構造体を生成します。

Mix_Chunk *Mix_LoadWAV(char *file)

Mix_LoadWAV_RWMix_QuickLoad_WAVもあり、メモリ上に展開された音源からもMix_Chunk構造体を生成できます。


3.3 Mix_FreeChunk

Mix_FreeChunkでMix_Chunk構造体を解放します。

void Mix_FreeChunk(Mix_Chunk *chunk)

3.4 Mix_AllocateChannels

使用するチャネルの総数を設定します。-1を指定した場合は現在の使用できるチャネルの総数を取得します。

int Mix_AllocateChannels(int numchans)

本APIを呼ばない場合、使用できるチャネルの総数は8になるようです。


3.5 Mix_PlayChannel

Mix_PlayChannelでMix_Chunk構造体をチャネルに割り当てて再生します。

int Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops)

channelに-1を指定した場合は、停止しているチャネルを割り当て、戻り値として返します。特定のチャネルを特定の音源に割り当てたい場合(例えば、雨の音をループ再生したい場合)、Mix_ReserveChannelsでチャネル番号を予約しておき、Mix_PlayChannelで予約したチャネル番号を指定すれば良いでしょう。


loopsに-1を設定した場合は無限に音源がループします。クリック音等のサウンドエフェクトは1を、雨などの流れ続ける音は-1を設定すれば良いでしょう。


3.6 Mix_HaltChannel

Mix_HaltChannelで再生されているチャネルを停止します。-1を指定した場合はすべてのチャネルを停止します。

int Mix_HaltChannel(int channel)

3.7 Mix_Playing

Mix_Playingでチャネルが再生中かを確認します。-1を指定した場合は再生中のチャネルの総数を取得します。

int Mix_Playing(int channel)

3.8 Mix_Volume

Mix_Volumeでチャネルの音量を設定します。channelに-1を指定した場合は全チャネルの音量を設定します。

int Mix_Volume(int channel, int volume)

Mix_Chunk構造体単位で音量を設定するMix_VolumeChunkもあります。


3.9 Mix_ChannelFinished

Mix_ChannelFinishedで音源の再生が停止した際のコールバック関数を登録します。

void Mix_ChannelFinished(void (*channel_finished)(int channel))

本APIを使用すれば、音源を鳴らす箇所は再生だけをケアすればよくなります。例えば、クリック時のハンドラにて音源を慣らしておき、コールバック関数でMix_Chunk構造体を解放しておくとコードが簡単になります。


4 音楽の再生

mp3を再生します。


4.1 Mix_Music構造体

音楽を扱う構造体です。外部からは隠蔽されています。

/* The internal format for a music chunk interpreted via mikmod */
typedef struct _Mix_Music Mix_Music;

Mix_Chunk構造体とは異なり、同時に再生できる音楽はひとつのみです。


4.2 Mix_LoadMUS

Mix_LoadMUSでMix_Music構造体を生成します。

Mix_Music *Mix_LoadMUS(const char *file)

Mix_Chunk構造体とは異なり、Mix_Musicはファイルストリーム経由でしか生成できません。複数ファイルをまとめたリソースファイルから音楽を再生したい場合はSDL2のコードを改造する必要があります(各プラットフォーム向けのコードを含めるので手間がかかる)。

リソースファイルを用いて、外部から音源をできる限り隠したい場合は、WAV形式にしてMix_Chunk構造体を使うべきです。


4.3 Mix_FreeMusic

Mix_FreeMusicでMix_Music構造体を破棄します。

void Mix_FreeMusic(Mix_Music *music)

4.4 Mix_PlayMusic

Mix_PlayMusicで音楽を再生します。

int Mix_PlayMusic(Mix_Music *music, int loops)

loopsでループ回数を設定します。-1の場合は無限ループします。


4.5 Mix_HaltMusic

Mix_HaltMusicで音楽を停止します。

int Mix_HaltMusic()

4.6 Mix_PlayingMusic

Mix_PlayingMusicで音楽が再生中かどうかを確認します。

int Mix_PlayingMusic()

4.7 Mix_VolumeMusic

Mix_VolumeMusicで音楽のボリュームを設定します。

int Mix_VolumeMusic(int volume)

-1を指定した場合は現在のボリュームを取得します。


5 サンプルプログラム

サウンドエフェクトと音楽を鳴らします。別途、周波数が22050のWAVファイルa.wavとMP3ファイルa.mp3を用意してください。


5.1 Soundクラス

サウンドエフェクトを扱うクラスです。チャネルとクラスへのポインタをマップとして保持しておき、クラスの解放は音源が停止した際にchannelFinish関数で実行します。

#ifndef __MYSDL_SOUND_H
#define __MYSDL_SOUND_H

#include <Mutex.h>
#include <SDL_mixer.h>
#include <string>
#include <map>

namespace mysdl {

extern void channelFinished(int);

class Sound {
 private:
  static std::map<int, Sound *> gMap;
  static Mutex gMutex;
  Mix_Chunk *mChunk;
  int mChannel;

 public:
  static int play(const std::string &fileName)
  {
    Sound *sound = new Sound(fileName);
    int channel;
    gMutex.lock();
    channel = sound->__play();
    if (channel >= 0)
      gMap[channel] = sound;
    gMutex.unlock();
    return channel;
  }

  static void stop()
  {
    gMutex.lock();
    for (std::map<int, Sound *>::iterator i = gMap.begin(),
           e = gMap.end(); i != e; ++i)
      if (i->second != nullptr)
        i->second->__stop();
    gMutex.unlock();
  }

  static void stop(int channel)
  {
    Sound *sound;
    gMutex.lock();
    sound = gMap[channel];
    gMap[channel] = nullptr;
    gMutex.unlock();
    delete sound;
  }

  Sound(const std::string &fileName)
  {
    mChunk = Mix_LoadWAV(fileName.c_str());
  }

  ~Sound()
  {
    Mix_FreeChunk(mChunk);
  }

  int __play()
  {
    return Mix_PlayChannel(-1, mChunk, 0);
  }

  void __stop()
  {
    if (mChannel >= 0) {
      Mix_HaltChannel(mChannel);
      mChannel = -1;
    }
  }

  bool __isPlaying()
  {
    return mChannel >= 0 && Mix_Playing(mChannel);
  }
};

};

#endif /** __MYSDL_SOUND_H */

5.2 Musicクラス

音楽を扱うクラスです。音楽は同時にひとつしか流せない為、グローバルなメソッドで再生と停止します。

#ifndef __MYSDL_MUSIC_H
#define __MYSDL_MUSIC_H

#include <Mutex.h>
#include <SDL_mixer.h>
#include <string>

namespace mysdl {

class Music {
 private:
  static Music *gMusic;
  static Mutex gMutex;
  Mix_Music *mMusic;

 public:
  static bool play(const std::string &fileName)
  {
    bool ret;
    gMutex.lock();
    if (gMusic != nullptr) {
      if (gMusic->__isPlaying())
        gMusic->__stop();
      delete gMusic;
    }
    gMusic = new Music(fileName);
    ret = gMusic->__play();
    gMutex.unlock();
    return ret;
  }

  static void stop()
  {
    gMutex.lock();
    if (gMusic != nullptr) {
      if (gMusic->__isPlaying())
        gMusic->__stop();
      delete gMusic;
    }
    gMusic = nullptr;
    gMutex.unlock();
  }

  static bool isPlaying()
  {
    bool ret;
    gMutex.lock();
    ret = gMusic != nullptr ? gMusic->__isPlaying() : false;
    gMutex.unlock();
    return ret;
  }

  Music(const std::string &fileName)
  {
    mMusic = Mix_LoadMUS(fileName.c_str());
  }

  ~Music()
  {
    Mix_FreeMusic(mMusic);
  }

  bool __play()
  {
    return Mix_PlayMusic(mMusic, -1) == 0;
  }

  void __stop()
  {
    if (isPlaying())
      Mix_HaltMusic();
  }

  bool __isPlaying()
  {
    return Mix_PlayingMusic();
  }
};

};

#endif /** __MYSDL_MUSIC_H */

5.3 main.cc

--- main.cc.org 2015-05-19 07:19:53.000000000 +0900
+++ main.cc 2015-05-19 07:18:03.000000000 +0900
@@ -5,6 +5,8 @@
 #include <StringObject.h>
 #include <PixelObject.h>
 #include <Font.h>
+#include <Sound.h>
+#include <Music.h>
 #include <SDL.h>

 using namespace mysdl;
@@ -118,6 +120,27 @@ static bool input()
   return false;
 }

+static bool initializeAudio()
+{
+  if (Mix_Init(MIX_INIT_MP3) < 0)
+    return false;
+  if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096) < 0)
+    goto err;
+  Mix_ChannelFinished(channelFinished);
+  return true;
+ err:
+  Mix_Quit();
+  return false;
+}
+
+static void finalizeAudio()
+{
+  Sound::stop();
+  Music::stop();
+  Mix_CloseAudio();
+  Mix_Quit();
+}
+
 int main(int argc, char *argv[])
 {
   Window window(SDL_WINDOW_TITLE, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT);
@@ -126,15 +149,22 @@ int main(int argc, char *argv[])

   Font::initialize();

+  if (!initializeAudio())
+    return 1;
+
   gSprite = new SpriteObject("a.png", 10, 10, 100, 100);
   gRenderer->send(gSprite);

+  Sound::play("a.wav");
+  Music::play("a.mp3");
+
   while (1)
     if (input())
       break;

   Font::finalize();

+  finalizeAudio();
   //thread.stop();
   return 0;
 }
ダウンロード
SDL2でサウンドエフェクトと音楽を再生するサンプルプログラム
src.tgz.tar.gz
GNU tar 8.2 KB