はじめに
この記事は DMM.com Advent Calendar 17日目の記事です。
iOSアプリ内で音を鳴らすには様々な方法があります。
今回は、Audio Unitフレームワークを用いた音の再生について紹介します。
使用環境
- Swift 4.1
- XCode 9.2
- iOS 11
Audio Unitとは
Audio Unitとは、オーディオ処理を行うためのソフトウェアプラグイン規格を指します。
iOSおよびMacOS Xでは、オーディオを処理するためのライブラリやフレームワークの総称をCore Audio 1 と呼んでおり、Audio UnitはCore Audio内で定義されているものの一つになります。
今回は、Audio Unitの中でも、オーディオの入出力を担当しているRemote IO Unitを使用して、音を再生します。
音を鳴らしてみる
クラス作成
音を鳴らすための適当なクラスを作ります。
今回はAudio Generator
という名前にしています。
sampleRate
は、1秒間のフレーム数 (サイン波を計算し、値を書き込む回数) を表します。
import Foundation
import AVFoundation
/// Audio Unitを使って、音を再生するクラス
class AudioGenerator {
var audioUnit: AudioUnit?
static let sampleRate: Float = 44100.0 // サンプリングレート
static let toneA: Float = 440.0 // 440Hz = ラの音
static var frame: Float = 0 // フレーム数
/// 初期化
init () {
prepareAudioUnit()
}
}
初期化
音を再生するための初期化を行います。
初期化では、主に以下の3つが必要になります。
- Remote IO Unitのインスタンス化
- コールバック関数の設定
- Remote IO Unitのオーディオデータフォーマットの設定
/// Audio Unitを使用する準備をする
func prepareAudioUnit() {
// この中に処理を書く
}
Remote IO Unitのインスタンス化
Audio Unitでは、Audio Componentというプラグインの形式をとっています。
AudioComponentDescription
で、どのようなAudio Componentを使用するかを定義し、インスタンス化した後、初期化を行います。
// RemoteIO AudioUnitのAudioComponentDescriptionを作成
var acd = AudioComponentDescription()
acd.componentType = kAudioUnitType_Output // カテゴリの指定
acd.componentSubType = kAudioUnitSubType_RemoteIO // 名前の指定
acd.componentManufacturer = kAudioUnitManufacturer_Apple // ベンダー名
acd.componentFlags = 0 // 使用しない
acd.componentFlagsMask = 0 // 使用しない
// Audio Componentの定義を取得
let audioComponent: AudioComponent = AudioComponentFindNext(nil, &acd)!
// インスタンス化
AudioComponentInstanceNew(audioComponent, &audioUnit)
// 初期化
AudioUnitInitialize(audioUnit!);
コールバック関数の設定
コールバック関数は、Remote IO Unitの処理が開始された時、システムにより自動的に繰り返し呼ばれる関数です。
AURenderCallbackStruct構造体を作成し、AudioUnitSetProperty()
で、コールバック関数の登録を行います。
// AURenderCallbackStruct構造体の作成
var callbackStruct: AURenderCallbackStruct = AURenderCallbackStruct(
inputProc: renderCallback, // コールバック関数の名前
inputProcRefCon: &audioUnit // コールバック関数内で参照するデータ
)
// コールバック関数の設定
AudioUnitSetProperty(
audioUnit!, // 対象のAudio Unit
kAudioUnitProperty_SetRenderCallback, // 設定するプロパティ
kAudioUnitScope_Input, // 入力スコープ
0, // バスの値(出力なので0)
&callbackStruct, // プロパティに設定する値
UInt32(MemoryLayout.size(ofValue: callbackStruct)) // 値のデータサイズ
)
Remote IO Unitのオーディオデータフォーマットの設定
AudioStreamBasicDescription構造体を設定し、Audio Unitに設定します。
// ASBDの作成
var asbd = AudioStreamBasicDescription()
asbd.mSampleRate = Float64(AudioGenerator.sampleRate) // サンプリングレートの指定
asbd.mFormatID = kAudioFormatLinearPCM // フォーマットID (リニアPCMを指定)
asbd.mFormatFlags = kAudioFormatFlagIsFloat // フォーマットフラグの指定 (Float32形式)
asbd.mChannelsPerFrame = 1 // チャンネル指定 (モノラル)
asbd.mBytesPerPacket = UInt32(MemoryLayout<Float32>.size) // 1パケットのバイト数
asbd.mBytesPerFrame = UInt32(MemoryLayout<Float32>.size) // 1フレームのバイト数
asbd.mFramesPerPacket = 1 // 1パケットのフレーム数
asbd.mBitsPerChannel = UInt32(8 * MemoryLayout<UInt32>.size) // 1チャンネルのビット数
asbd.mReserved = 0 // 使用しない
// AudioUnitにASBDを設定
AudioUnitSetProperty(
audioUnit!, // 対象のAudio Unit
kAudioUnitProperty_StreamFormat, // 設定するプロパティ
kAudioUnitScope_Input, // 入力スコープ
0, // 出力バス
&asbd, // プロパティに設定する値
UInt32(MemoryLayout.size(ofValue: asbd)) // 値のデータサイズ
)
以上で初期化処理は完了です。
コールバック関数の作成
コールバック関数の中身をクラス内に定義します。
コールバック関数には、AURenderCallback型が適用されます。
サイン波を鳴らす設定は、この関数内で行います。
ここでは、sin()
でサイン波の計算をし、その結果をabl
に書き込んでいます。
// コールバック関数
let renderCallback: AURenderCallback = {(
inRefCon: UnsafeMutableRawPointer,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>?
) -> OSStatus in
// チャンネルの数分のAudioBuffer参照の取り出し
let abl = UnsafeMutableAudioBufferListPointer(ioData)
// フレーム数分のメモリキャパシティ
let capacity = Int(abl![0].mDataByteSize) / MemoryLayout<Float>.size
// バッファに値を書き込む
if let buffer: UnsafeMutablePointer<Float> = abl![0].mData?.bindMemory(to: Float.self, capacity: capacity) {
for i: Int in 0 ..< Int(inNumberFrames) {
// サイン波を生成
buffer[i] = sin(frame * toneA * 2.0 * Float(Double.pi) / sampleRate)
frame += 1
}
}
return noErr
}
1回のrenderCallback
内で計算されるinNumberFrames
の値が512であるとすると、このコールバック関数は、1秒間に 44100 / 512 = 約86回 呼ばれている計算になります。
スタート・ストップ関数の設定
外から呼ぶ用の音声スタート・ストップ用関数をクラス内に作成します。
AudioOutputUnitStart
を呼ぶと、コールバック関数renderCallback
が繰り返し呼ばれるようになります。
/// 再生スタート
func start() {
AudioGenerator.frame = 0
AudioOutputUnitStart(audioUnit!) // Remote IO Unit 出力開始
print("start")
}
/// 再生ストップ
func stop() {
AudioOutputUnitStop(audioUnit!) // Remote IO Unit 出力停止
print("stop")
}
あとは、start()
とstop()
を適宜外から呼んであげると、ラの音が鳴ったり止んだりするようになります。
Audio Unitの破棄
音を鳴らす必要がなくなった際は、以下のような記述でAudio Unitの破棄を行います。
/// Audio Unitの破棄
func dispose() {
AudioUnitUninitialize(audioUnit!)
AudioComponentInstanceDispose(audioUnit!)
}
おわり
iOS上で音の出力をする際の一例と、Swift4での記述例を紹介してみました。
参考になれば幸いです。
また、ご指摘があればコメント頂けるとありがたいです。
参考文献・サイト
- Apple Inc. "Core Audio | Apple Developer Documentation". Apple Developer. https://developer.apple.com/documentation/coreaudio, (参照: 2018-12-15).
- 永野 哲久. iPhone Core Audio プログラミング. 初版, ソフトバンククリエイティブ, 2009, p545.
- ペブ. "swift3でCoreAudioを使う 再生編". Pebble Coding. 2015-12-05. https://www.pebblewind.com/entry/2015/12/05/192914, (参照: 2018-12-15).
-
Windows にも、Core Audioと呼ばれるものがあります https://docs.microsoft.com/en-us/windows/desktop/coreaudio/about-the-windows-core-audio-apis ↩