Posted at

Swiftでシステムサウンドを再生する

More than 3 years have passed since last update.

ここでは、AudioServicesPlaySystemSoundを利用してシステムサウンド(効果音)を再生しています。


事前確認

AudioServicesPlaySystemSound関数を使用するには以下のような制限があるので事前に確認しておきます。


You can use System Sound Services to play short (30 seconds or shorter) sounds. The interface does not provide level, positioning, looping, or timing control, and does not support simultaneous playback: You can play only one sound at a time. You can use System Sound Services to provide audible alerts. On some iOS devices, alerts can include vibration.

出典:System Sound Services Reference


・長さが30秒以下

・リニアPCMまたはIMA4(IMA/ADPCM)フォーマット

・.caf、.aif、.wavファイルのパッケージ

・音声は現在のシステムオーディオボリュームで再生され、プログラムでのボリューム制御はできません

・音声はすぐに再生されます

・ループやステレオポジショニングは利用できません

・同時再生は利用できず、一度に一つの音声しか再生できません

出典:System Sound Services


システムサウンドを再生する

let soundIdRing:SystemSoundID = 1000  // new-mail.caf

AudioServicesPlaySystemSound(soundIdRing)

SystemSoundIDを指定して、AudioServicesPlaySystemSoundを呼び出すことでシステムサウンド(バイブレーション有り)を再生すことができます。

iOSのシステムサウンド一覧がここに記載されています。

システムサウンドは再生せずに、バイブレーションだけを利用する場合はsoundIdRingを下記のように定義することで実現できます。

let soundIdRing:SystemSoundID = SystemSoundID(kSystemSoundID_Vibrate)


バイブレーションの無いシステムサウンドを再生する

システムに含まれるサウンドファイルを直接指定することで、バイブレーションがないシステムサウンドを再生することができます。

var soundIdRing:SystemSoundID = 0

let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)
AudioServicesPlaySystemSound(soundIdRing)

ここで設定されるSystemSoundIDを確認してみると4097になっており、直接指定したSystemSoundIDとは違う値になっていることが確認できます。


Bundleしたサウンドを再生する

システムサウンドではなく、Bundleするサウンドを再生する場合は下記のようになります

var soundIdRing:SystemSoundID = 0

let soundUrl = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("new-mail", ofType:"caf")!)
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)
AudioServicesPlaySystemSound(soundIdRing)
//AudioServicesDisposeSystemSoundID(soundIdRing)


繰り返しサウンドを再生する

AudioServicesAddSystemSoundCompletionを利用すると、繰り返しサウンドを再生できるようなのなので試してみます。

まずは下記のコードを試してみます。

override func viewDidLoad() {

super.viewDidLoad()

var soundIdRing:SystemSoundID = 0
let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)

AudioServicesAddSystemSoundCompletion(soundIdRing, nil, nil, completionCallback, nil)
}

func completionCallback(ssID:SystemSoundID,clientData:UnsafeMutablePointer<Void>) -> Void {

}

このコードは以下のようなErrorが出て実行できません。


'(SystemSoundID, clientData: UnsafeMutablePointer) -> Void' is not convertible to 'AudioServicesSystemSoundCompletionProc'


AudioServicesAddSystemSoundCompletionのメソッドの定義を確認すると、下記のようになっています。

func AudioServicesAddSystemSoundCompletion(_ inSystemSoundID: SystemSoundID,

_ inRunLoop: CFRunLoop!,
_ inRunLoopMode: CFString!,
_ inCompletionRoutine: AudioServicesSystemSoundCompletionProc,
_ inClientData: UnsafeMutablePointer<Void>) -> OSStatus

ちなみにobjcでの定義は、下記のようになっています。

 OSStatus AudioServicesAddSystemSoundCompletion ( SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void *inClientData ); 

AudioServicesSystemSoundCompletionProcの定義は下記のようになっています。

/*!

@typedef AudioServicesSystemSoundCompletionProc
@abstract A function to be executed when a SystemSoundID finishes playing.
@discussion AudioServicesSystemSoundCompletionProc may be provided by client application to be
called when a SystemSoundID has completed playback.
@param ssID
The SystemSoundID that completed playback
@param userData
Client application user data
*/

typealias AudioServicesSystemSoundCompletionProc = CFunctionPointer<((SystemSoundID, UnsafeMutablePointer<Void>) -> Void)>

このままでは実行できないので、 https://gist.github.com/jparishy/7b76edf8d0fcca1d63b0 を参考に実装してみます。

ここでは上記のgistでにある、FunctionPointer.h と FunctionPointer.m はそのまま利用します。

Bridging-Headerを追加してFunctionPointer.hはをimportする必要があります。

下記はSwift側で3回サウンドを再生する部分です。


import UIKit
import AudioToolbox

class ViewController: UIViewController {

var functionPointer: AudioServicesCompletionFunctionPointer?
var soundCounter = 3

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

var soundIdRing:SystemSoundID = 0
let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)

let block = { [unowned self] (systemSoundID: SystemSoundID, userData: UnsafeMutablePointer<Void>) -> () in
(self.soundCounter)--
if self.soundCounter > 0 {
NSThread.sleepForTimeInterval(0.5)
AudioServicesPlaySystemSound(soundIdRing)
} else {
AudioServicesRemoveSystemSoundCompletion(soundIdRing)
}
}

var vself = self
let userData = withUnsafePointer(&vself, {
(ptr: UnsafePointer<ViewController>) -> UnsafeMutablePointer<Void> in
return unsafeBitCast(ptr, UnsafeMutablePointer<Void>.self)
})

self.functionPointer = AudioServicesCompletionFunctionPointer(systemSoundID: soundIdRing, block: block, userData: userData)
AudioServicesAddSystemSoundCompletion(soundIdRing, CFRunLoopGetMain(), kCFRunLoopCommonModes, AudioServicesCompletionFunctionPointer.completionHandler(), userData)

AudioServicesPlaySystemSound(soundIdRing)
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

}

ここで作成したサンプルプロジェクトをこちらに置いてあります。


感想

AudioServicesAddSystemSoundCompletionを利用するのが大変でした。この書き方ならObjective-Cで書いた方が簡単に書けると思います。

もう少し簡単に書ける方法が無いかは引き続き調査していきたいところです。


参考URL