LoginSignup
11

More than 1 year has passed since last update.

iOSでサイン波を鳴らす (Swift4 × AudioUnit)

Last updated at Posted at 2018-12-17

はじめに

この記事は 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での記述例を紹介してみました。
参考になれば幸いです。

また、ご指摘があればコメント頂けるとありがたいです。

参考文献・サイト

  1. Windows にも、Core Audioと呼ばれるものがあります https://docs.microsoft.com/en-us/windows/desktop/coreaudio/about-the-windows-core-audio-apis

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
11