LoginSignup
14
10

More than 5 years have passed since last update.

MacでPortAudioを使ってWavファイルを再生する

Posted at

はじめに

PortAudioはmac, linux, windowsと共通で使える音操作ライブラリなので、今後、音に関する実装をする場合は、なるべくportaudioを使っていこうと思う。StackOverflowでサンプルソースは載っているけど、前提となるライブラリのインストールに関しては、触れられていないので、macにおけるインストール・ビルド方法を簡単にまとめてみる。なお、パッケージ管理としてはhomebrewを使うことにして、MacPortsは使わない。なお、Linuxもほとんど手順が変わらないので、brewをapt-getに読みかえれば、この手順で進めることができると思う。

インストール/セットアップ

portaudioを使って、wavファイルを再生するためには以下の2つのライブラリをインストールする必要がある。

  • libsndfile
  • portaudio

portaudioは、普通にhomebrewでインストールできるが、sndfileは見つからなかったので、ソースからビルドすることにする。

libsndfileのインストール

以下のサイトから、ソースを持ってきて、ビルド・インストールする。

$ wget http://www.mega-nerd.com/libsndfile/files/libsndfile-1.0.25.tar.gz
$ cd libsndfile-1.0.25
$ ./configure
$ make

makeは最後までいかず、examplesのどれかで失敗する(Carbon.hが見つからないとエラーがでる)けど、ライブラリのビルドは終了しているので、気にせずインストールする。

$ sudo make install

注意:/usr/local/libにインストールされるが、その後、brewのインストール時に警告が出る。出ないようにするには別のprefixを指定してインストールして、パスを指定する必要があるが、ここではやらない。

portaudioのインストール

portaudioはbrewでインストールできるので、さくっとインストールする。

$ brew install --universal portaudio

サンプルコード

あとは自由にやれば良いが、ページ的に収まりが悪いので、サンプルのビルドも書いておく。サンプルは、Stackoverflowから持ってきた。

main.cpp
#include "sndfile.h"
#include "portaudio.h"
#include <stdlib.h>
#include <stdio.h>

/* the filename is defined here because this is just a demo */
#define FILE_NAME "test.wav"


/*
Data structure to pass to callback
includes the sound file, info about the sound file, and a position
cursor (where we are in the sound file)
*/
struct OurData
{
  SNDFILE *sndFile;
  SF_INFO sfInfo;
  int position;
};

/*
Callback function for audio output
*/
int Callback(const void *input,
             void *output,
             unsigned long frameCount,
             const PaStreamCallbackTimeInfo* paTimeInfo,
             PaStreamCallbackFlags statusFlags,
             void *userData)
{
  OurData *data = (OurData *)userData; /* we passed a data structure
into the callback so we have something to work with */
  int *cursor; /* current pointer into the output  */
  int *out = (int *)output;
  int thisSize = frameCount;
  int thisRead;

  cursor = out; /* set the output cursor to the beginning */
  while (thisSize > 0)
  {
    /* seek to our current file position */
    sf_seek(data->sndFile, data->position, SEEK_SET);

    /* are we going to read past the end of the file?*/
    if (thisSize > (data->sfInfo.frames - data->position))
    {
      /*if we are, only read to the end of the file*/
      thisRead = data->sfInfo.frames - data->position;
      /* and then loop to the beginning of the file */
      data->position = 0;
    }
    else
    {
      /* otherwise, we'll just fill up the rest of the output buffer */
      thisRead = thisSize;
      /* and increment the file position */
      data->position += thisRead;
    }

    /* since our output format and channel interleaving is the same as
sf_readf_int's requirements */
    /* we'll just read straight into the output buffer */
    sf_readf_int(data->sndFile, cursor, thisRead);
    /* increment the output cursor*/
    cursor += thisRead;
    /* decrement the number of samples left to process */
    thisSize -= thisRead;
  }

  return paContinue;
}


int main()
{
  OurData *data = (OurData *)malloc(sizeof(OurData));
  PaStream *stream;
  PaError error;
  PaStreamParameters outputParameters;

  /* initialize our data structure */
  data->position = 0;
  data->sfInfo.format = 0;
  /* try to open the file */
  data->sndFile = sf_open(FILE_NAME, SFM_READ, &data->sfInfo);

  if (!data->sndFile)
  {
    printf("error opening file\n");
    return 1;
  }

  /* start portaudio */
  Pa_Initialize();

  /* set the output parameters */
  outputParameters.device = Pa_GetDefaultOutputDevice(); /* use the
default device */
  outputParameters.channelCount = data->sfInfo.channels; /* use the
same number of channels as our sound file */
  outputParameters.sampleFormat = paInt32; /* 32bit int format */
  outputParameters.suggestedLatency = 0.2; /* 200 ms ought to satisfy
even the worst sound card */
  outputParameters.hostApiSpecificStreamInfo = 0; /* no api specific data */

  /* try to open the output */
  error = Pa_OpenStream(&stream,  /* stream is a 'token' that we need
to save for future portaudio calls */
                        0,  /* no input */
                        &outputParameters,
                        data->sfInfo.samplerate,  /* use the same
sample rate as the sound file */
                        paFramesPerBufferUnspecified,  /* let
portaudio choose the buffersize */
                        paNoFlag,  /* no special modes (clip off, dither off) */
                        Callback,  /* callback function defined above */
                        data ); /* pass in our data structure so the
callback knows what's up */

  /* if we can't open it, then bail out */
  if (error)
  {
    printf("error opening output, error code = %i\n", error);
    Pa_Terminate();
    return 1;
  }

  /* when we start the stream, the callback starts getting called */
  Pa_StartStream(stream);
  Pa_Sleep(2000); /* pause for 2 seconds (2000ms) so we can hear a bit
of the output */
  Pa_StopStream(stream); // stop the stream
  Pa_Terminate(); // and shut down portaudio
  return 0;
}

後は、makefileを書くだけ、cmakeでビルドすることにする。サンプルだけにしては仰々しいが以下のファイル構成において、CMakeLists.txtを書いた。

├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp

cmakeがインストールされていない場合は、brewで入れておく

$ brew install cmake

以下、それぞれのCMakeLists.txtの中身。

CMakeLists.txt
# -*- CMakeLists.txt -*-

cmake_minimum_required (VERSION 2.6)

# バージョン番号
set (serial "0.0.1")

# 共有ライブラリのバージョン番号
set (soserial "0")

project (sound)
include_directories ("${PROJECT_SOURCE_DIR}/include")

# サブディレクトリの指定
add_subdirectory(src)

# クリーン対象の追加
set_directory_properties (
  PROPERTIES
  ADDITIONAL_MAKE_CLEAN_FILES "*~"
  )
src/CMakeLists.txt
# -*- CMakeLists.txt -*-

# プログラムの追加
add_executable(myplay main.cpp)

# CFLAGSの追加
set (CMAKE_CXX_FLAGS "-Wall -I${PROJECT_SRC_DIRECTORY}/include")

# LDFLAGSの追加
set (CMAKE_EXE_LINKER_FLAGS ${PLATFORM_LDFLAGS})

# ライブラリの追加
set (LOCAL_LINK_LIBRARIES portaudio sndfile)
link_directories (${PROJECT_SRC_DIRECTORY}/lib)
target_link_libraries (myplay ${LOCAL_LINK_LIBRARIES})

# クリーン対象の追加
set_directory_properties (
  PROPERTIES
  ADDITIONAL_MAKE_CLEAN_FILES
  "*~"
  )

後は、buildディレクトリでも作って、ビルドするだけ

$ mkdir build
$ cmake ..
$ make
$ myplay

使用するサウンドファイルはソースに定義されている引数で指定したい場合は各自、ソースを修正すること。

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10