※こっちの記事のほうが新しいです
はじめに
iPhoneで録音した音声ファイルをまったく同時に再生したいと思ったのですが、AVAudioPlayerではうまくいきませんでした。
どうすればいいか調べたのですが、どうやらAudioUnitを使わないと完全に同時に再生することはできないようです。
なので、まずはAudioUnitを使って音を鳴らすところからはじめることにします。
Swiftや音声処理については全くの素人なので、間違っているところやわからなかったところが多々あります。ご存知の方にツッコミを入れていただきたく思い、公開させていただきました。
よろしくお願いします。
音を出す
Swift初心者なもので、どうやっていいか、さっぱりわかりませんでしたが、とりあえず動くものができました。
とりあえず動くもの
下記コードをどっかのファイルにコピーして、SoundPlayer#Play()
とやればラの音(440Hz)の音が出るはずです。
iOSシミュレータではノイズが乗りまくりました。
実機ではうまくいくんですが・・・。
func RenderCallback (
inRefCon: UnsafeMutablePointer<Void>,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>) -> (OSStatus)
{
let tmp: UnsafeMutablePointer<SoundPlayerData> = UnsafeMutablePointer<SoundPlayerData>(inRefCon);
let data: SoundPlayerData = tmp.memory;
let buf: AudioBufferList = ioData.memory;
var datas: UnsafeMutablePointer<Float> = UnsafeMutablePointer<Float>(buf.mBuffers.mData);
let sineWaveFreq: Float = 440.0;
let samplingRate: Float = 44100.0;
let freq: Float = sineWaveFreq * 2.0 * Float(M_PI) / samplingRate;
for(var i: UInt32 = 0; i < inNumberFrames; i++)
{
var tmpVal: Float = sin(data.time);
memcpy(datas, &tmpVal, sizeof(Float));
datas++;
data.time += freq;
}
return noErr;
}
class SoundPlayerData
{
var time: Float = 0.0;
}
class SoundPlayer
{
var audioUnit: AudioUnit?;
var acd: AudioComponentDescription?;
var ac: AudioComponent?;
var data: SoundPlayerData = SoundPlayerData();
var isPlaying: Bool = false;
func play()
{
// AudioUnitの準備
audioUnit = AudioUnit();
// AudioComponentDescriptionの準備
acd = AudioComponentDescription();
acd?.componentType = kAudioUnitType_Output;
acd?.componentSubType = kAudioUnitSubType_RemoteIO;
acd?.componentManufacturer = kAudioUnitManufacturer_Apple;
acd?.componentFlags = 0;
acd?.componentFlagsMask = 0;
// AudioComponentの生成
ac = AudioComponentFindNext(nil, &acd!);
// AudioUnitとAudioComponentとの関連づけ
AudioComponentInstanceNew(ac!, &audioUnit!);
// 音声処理のコールバックメソッドを準備
// メソッドとメソッドに渡すデータを用意しておきます。
var input = AURenderCallbackStruct(inputProc: RenderCallback, inputProcRefCon: &data);
// AudioUnitとコールバックメソッドの関連づけ
AudioUnitSetProperty(audioUnit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, UInt32(sizeofValue(input)));
// AudioUnitの初期化
AudioUnitInitialize(audioUnit!);
// 音声再生実行開始
AudioOutputUnitStart(audioUnit!);
isPlaying = true;
}
func stop()
{
if(audioUnit == nil || isPlaying == false)
{
return;
}
AudioOutputUnitStop(audioUnit!);
audioUnit = nil;
}
}
ソースコードの解説
音がどうやって出ているかについては参考の第三回あたりを読むと良いでしょう。
音は空気の振動であり、44.1kHzでサンプリングされた音は、1秒間に44100回の振動データを保存しているということになります。
逆に再生する時には44100回、スピーカーに波の信号を送っているということになります。
play()
メソッド
play()
メソッドが音を鳴らすための準備と再生開始のメソッドとなっています。
流れとしては、次のようになります。
- AudioUnitを生成
- AudioComponentDescriptionを生成
- AudioComponentDescriptionからAudioComponentを生成
- AudioComponentとAudioUnitを関連づける
- 音を鳴らすための計算を行うコールバック関数を設定する
- コールバック関数が呼ばれた時に渡されるデータに
self
をセットすることはできませんでした。Mutableなポインタにできないとか言われた気がしますが、なんででしょうか。
- コールバック関数が呼ばれた時に渡されるデータに
- AudioUnitの初期化
- 再生開始(コールバックメソッドが呼び出されるようになります。)
RenderCallback()
関数
play()
を実行すると、RenderCallback()
メソッドが1秒間に86回(だったかな)呼び出されるようになります。
第一引数のinRefCon
はplay()
メソッド内で設定したコールバック関数に渡されるデータのポインタが入っています。
第五引数のinNumberFrames
は第六引数であるバッファの箱の数が入っています。バッファは,1/86秒分の音声を保持することができます。
第六引数のioData
は音声出力用バッファです。ここに-1.0 ~ 1.0までのデータを連続で入力してあげることで、1/86秒分の音声を出力することができます。
関数の先頭で、引数に渡されたUnsafeMutablePointer
の値をSwiftで扱えるようにキャストしたりしています。やり方がさっぱりわからなかったため、強引にやっています。正しいやり方を教えてください・・・。
次に、440Hzの音声を出力するために必要な定数を計算しています。ここでいちいち計算する必要はないので、無駄な処理ですね。
最後にループ内で、inNumberFrames
個分のSin波を計算してバッファに詰めています。
がんばったところ
- ポインタのキャスト
- Swiftで扱えるような形にポインタをキャストするのに苦労しました。正しいやり方とは思えませんが・・・。
- 出力用のバッファに値を書き込むところ
- バッファに値がどうしてもうまく書き込めなかったので、memcpyを使っています。たぶん、もっとスマートな方法があると思います。教えてください。
わからないところ
本来であれば、AudioBufferList
は次のような構造体だそうです。
struct AudioBufferList
{
UInt32 mNumberBuffers;
AudioBuffer mBuffers[kVariableLengthArray];
};
typedef struct AudioBufferList AudioBufferList;
struct AudioBuffer
{
UInt32 mNumberChannels;
UInt32 mDataByteSize;
void* mData;
};
typedef struct AudioBuffer AudioBuffer;
しかし、Swiftでは次のように定義されていました。
public struct AudioBufferList {
public var mNumberBuffers: UInt32
public var mBuffers: (AudioBuffer) // this is a variable length array of mNumberBuffers elements
public init()
public init(mNumberBuffers: UInt32, mBuffers: (AudioBuffer))
}
public struct AudioBuffer {
public var mNumberChannels: UInt32
public var mDataByteSize: UInt32
public var mData: UnsafeMutablePointer<Void>
public init()
public init(mNumberChannels: UInt32, mDataByteSize: UInt32, mData: UnsafeMutablePointer<Void>)
}
AudioBufferList#mBuffers
は配列であり、ステレオ出力の場合にはmNumberBuffers
が2となります。Obj-Cのサンプルでは
AudioBufferList#mBuffers[0]
AudioBufferList#mBuffers[1]
というふうにアクセスして左右の音声を出力していたのですが、Swiftは配列ではないように見えます。
public var mBuffers: (AudioBuffer) // []で括ってないから配列ではないのでは?
コメントには、
this is a variable length array of mNumberBuffers elements
これはmNumberBuffers
個の可変長の変数です。
とあるので、配列なんだと思うのですが、アクセス方法がわかりませんでした。
どなたか、詳しい方、教えてください。