はじめに
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から持ってきた。
#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 -*-
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 "*~"
)
# -*- 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
使用するサウンドファイルはソースに定義されている引数で指定したい場合は各自、ソースを修正すること。