Hello, World! あーみーです.
この記事はLife is Tech! Tokai Mentors Advent Calendar 2018 23日目の記事です.
忙しくてずっと下書きで眠っていたので,アドベントカレンダーの力を借りて記事を完成させました.
今回は僕が作ったアプリで恐縮ですが.このアプリの音声の入出力の部分を作ります.
はじめに
僕は合唱をやっていたので,音楽に関係するアプリを作りたいなと思い,iPhoneがマイクになるアプリを作ろうと思いました.
iPhoneでの音声入力は調べたら結構実装できる程度には記事が見つかるんですが,マイクを作りたいので,入力だけではいけません.その入力した音声を同時にスピーカーから出力しないとマイクとは言えませんから.
音声を入力と同時に出力する方法は調べてもなかなか記事が見つからなかったため,記事にしました.
環境
僕の今回のPC環境を以下に示します.
- High Sierra 10.13
- XCode 10.1
- Swift 4
- iPhoneX(実機でのテストのため)
マイク実装
#1. とりあえず部品を配置する
音声入力と音声出力に必要なUI部品は特に何もないですが,スピーカーに繋げて出力した時にハウリングしたり,音が大きいとビックリするので,それを制御するために以下の部品を配置します.
- マイクON/OFF用
UISwitch
- マイクボリューム用
UISlider
#2. 音声入出力
音声の入出力自体はとても簡単でViewController
を以下のようにするだけで,音は出るようになります.
import UIKit
import AVFoundation
class ViewController: UIViewController {
private var engine = AVAudioEngine()
override func viewDidLoad() {
super.viewDidLoad()
let input = engine.inputNode
let output = engine.mainMixerNode
let format = engine.inputNode.inputFormat(forBus: 0)
engine.connect(input, to: output, format: format)
try! engine.start()
}
}
簡単ですね!
ただ,このままではマイクに無許可でアクセスしようとして起動と同時にアプリが落ちてしまうので,Info.plist
に以下を追加します.
Key | Value |
---|---|
Privacy - Microphone Usage Description | [マイクを使用する理由] |
また,#1の時に配置したUISwitch
とUISlider
でマイクを制御するため,ViewController
に以下も追記します.
// マイクON/OFF用UISwitch
@IBAction func changeMicState(sw: UISwitch){
if sw.isOn {
try! engine.start()
}else{
engine.stop()
}
}
// マイクボリューム用UISlider
@IBAction func micSlider(slider: UISlider){
engine.inputNode.volume = slider.value
}
どちらも値が変わった時に変更を加えたいので,Value Changed
でアタッチします.
#3. Bluetoothスピーカーから出力
マイクのように声を増幅させたいので,スピーカーから出力することが目的となります.
今でも,iPhoneのイヤホンジャックとスピーカーをミニプラグで繋げれば可能ですが,最近はイヤホンジャックが廃止されていますし,有線だと,スマートじゃありません.
なので,Bluetoothでスピーカーに接続していきます.viewDidLoad
内に以下を追記すればBluetoothスピーカーにも対応できます.
override func viewDidLoad() {
super.viewDidLoad()
// Bluetooth接続を許可
try! AVAudioSession.sharedInstance()
.setCategory(.playAndRecord,
mode: .voiceChat,
options: .allowBluetoothA2DP)
}
ただ,Bluetoothでスピーカーに繋ぐと音声が入力されてから出力されるまでにタイムラグがあります.
自分の声が遅れて聞こえてくるので喋りづらいかもしれません.しょうがない気もしますが.
タイムラグをなくすことができるかどうかはわかりません.もし,方法知ってるよって方がいればコメント等お願いします.
これでマイクアプリは完成です!
エフェクト実装
せっかくのマイクアプリなので,多少のマイクパフォーマンスができるように変更していきます.
今回は以下の2つの機能をつけます.
- リバーブ
- ディレイ
#1. とりあえず部品を配置する
マイク用の部品に加えてリバーブ用,ディレイ用にもUISlider
を追加します.
画像の上から順に
- マイクON/OFF用
UISwitch
- マイクボリューム用
UISlider
- リバーブレベル用
UISlider
- ディレイタイム用
UISlider
となります,
#2. AVAudioEngineに繋げる
先程のViewController
を以下のように変更します.
import UIKit
import AVFoundation
class ViewController: UIViewController {
private var engine = AVAudioEngine()
private var reverb = AVAudioUnitReverb()
private var delay = AVAudioUnitDelay()
override func viewDidLoad() {
super.viewDidLoad()
// Bluetooth接続を許可
try! AVAudioSession.sharedInstance()
.setCategory(.playAndRecord,
mode: .voiceChat,
options: .allowBluetoothA2DP)
let input = engine.inputNode
let output = engine.mainMixerNode
let format = engine.inputNode.inputFormat(forBus: 0)
engine.attach(reverb)
engine.attach(delay)
reverb.wetDryMix = 0
delay.delayTime = 0
engine.connect(input, to: reverb, format: format)
engine.connect(reverb, to: delay, format: format)
engine.connect(delay, to: output, format: format)
try! engine.start()
}
}
音声にエフェクトをつけたい時は,ギターのエフェクターを繋げるみたいに,それぞれのエフェクトをinput
からoutput
までの間に入れてあげればできます.
図だとこんな感じですね.
そして,エフェクターのつまみを回すように,エフェクトレベルを変更する関数を用意します.
// リバーブレベル用UISlider
@IBAction func reverbSlider(slider: UISlider){
reverb.wetDryMix = slider.value
}
// ディレイタイム用UISlider
@IBAction func delaySlider(slider: UISlider){
delay.delayTime = TimeInterval(slider.value)
}
どちらも値が変わった時に変更を加えたいので,Value Changed
でアタッチします.
リポジトリ
サンプルのプロジェクトがあるリポジトリはこちら
参考にしたサイト
さいごに
今回はiPhoneをマイクにするため,入力した音声をリアルタイムで出力するアプリを作成しました.また,マイクパフォーマンスができるようにエフェクトも追加しました.
忘年会,新年会の季節ですが,人数が多いと声が後ろまで届きませんよね.
是非,幹事の人は使ってみてはいかがでしょうか.
それでは,Happy Hacking!