koogawa blog

iOS、Android、foursquareに関する話題

【Tips】iOSでマイクの音を検知する(Swift編)

数年前に書いた下記記事が古くなってきたので、Swift編 として書き直しました。

f:id:koogawa:20160417130548p:plain


Core Audioを使用して、マイクから音を検知する方法をメモしておきます。

実行環境

  • Xcode 7.3
  • Swift 2.2
  • Deployment Target iOS 8

実装方法

ヘッダをインポートします。

import AudioToolbox

音声入力用のキューと監視タイマーを準備しておきます。

var queue: AudioQueueRef!
var timer: NSTimer!

記録するデータフォーマットを決めます。

var dataFormat = AudioStreamBasicDescription(
    mSampleRate: 44100.0,
    mFormatID: kAudioFormatLinearPCM,
    mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsBigEndian |
        kLinearPCMFormatFlagIsSignedInteger |
        kLinearPCMFormatFlagIsPacked),
    mBytesPerPacket: 2,
    mFramesPerPacket: 1,
    mBytesPerFrame: 2,
    mChannelsPerFrame: 1,
    mBitsPerChannel: 16,
    mReserved: 0)

▼各項目の意味

データ型 メンバ名 意味
Float64 mSampleRate サンプリング周波数(1秒間のフレーム数)
UInt32 mFormatID フォーマットID(リニアPCM、MP3、AACなど)
UInt32 mFormatFlags フォーマットフラグ(エンディアン、整数or浮動小数点数
UInt32 mBytesPerPacket 1パケット(データを読み書きする単位)のバイト数
UInt32 mFramesPerPacket 1パケットのフレーム数
UInt32 mBytesPerFrame 1フレームのバイト数
UInt32 mChannelsPerFrame 1フレームのチャンネル数
UInt32 mBitsPerChannel 1チャンネルのビット数
UInt32 mReserved

音の監視を開始します。

var audioQueue: AudioQueueRef = nil
var error = noErr
error = AudioQueueNewInput(
    &dataFormat,
    AudioQueueInputCallback,
    UnsafeMutablePointer(unsafeAddressOf(self)),
    .None,
    .None,
    0,
    &audioQueue)
if error == noErr {
    self.queue = audioQueue
}
AudioQueueStart(self.queue, nil)

今回は音を検知するだけで、録音の必要はないので AudioInputCallback は空にしておきます。

private func AudioQueueInputCallback(
    inUserData: UnsafeMutablePointer<Void>,
    inAQ: AudioQueueRef,
    inBuffer: AudioQueueBufferRef,
    inStartTime: UnsafePointer<AudioTimeStamp>,
    inNumberPacketDescriptions: UInt32,
    inPacketDescs: UnsafePointer<AudioStreamPacketDescription>)
{
    // Do nothing, because not recoding.
}

マイクのレベルを取得できるように、レベルメータを有効化しておきます。

var enabledLevelMeter: UInt32 = 1
AudioQueueSetProperty(self.queue,
                      kAudioQueueProperty_EnableLevelMetering,
                      &enabledLevelMeter,
                      UInt32(sizeof(UInt32)))

一定時間ごとにマイクレベルを監視します。ここでは NSTimer を使用しました。

self.timer =
    NSTimer.scheduledTimerWithTimeInterval(0.5,
                                           target: self,
                                           selector: #selector(AudioViewController.detectVolume(_:)),
                                           userInfo: nil,
                                           repeats: true)
self.timer.fire()

一定時間ごとにレベルを取得して、画面に表示します。

func detectVolume(timer: NSTimer)
{
    // Get level
    var levelMeter = AudioQueueLevelMeterState()
    var propertySize = UInt32(sizeof(AudioQueueLevelMeterState))

    AudioQueueGetProperty(
        self.queue,
        kAudioQueueProperty_CurrentLevelMeterDB,
        &levelMeter,
        &propertySize)

    // Show the audio channel's peak and average RMS power.
    self.peakTextField.text =
        "".stringByAppendingFormat("%.2f", levelMeter.mPeakPower)
    self.averageTextField.text =
        "".stringByAppendingFormat("%.2f", levelMeter.mAveragePower)

    // Show "LOUD!!" if mPeakPower is larger than -1.0
    self.loudLabel.hidden = (levelMeter.mPeakPower >= -1.0) ? false : true
}

通常、mPeakPowermAveragePower には負の値がセットされており、音量に比例して値も大きくなっていきます。最大値は 0 になります。

最後に忘れずにキューを空にして停止します。

AudioQueueFlush(self.queue)
AudioQueueStop(self.queue, false)
AudioQueueDispose(self.queue, true)

サンプルコード

注意点

  • ユーザがマイクの使用を許可していない場合、音の検知はできません

応用できそうなアプリ

  • 声や拍手などに反応するゲームアプリなど
  • iPhoneに息を吹きかけると何かがめくれるアプリ(実際にありましたねw)

参考にさせて頂いたリンク

LINE BOT API で bot を作ったときにハマったことメモ

LINEBOT APIが公開されたので、簡単な検索botを作ってみました。 botの作り方についてはすでにたくさんの良記事が公開されているので、ここでは主にハマりやすいポイントについてメモしておきます。

開発環境

アカウント登録

下記サイトからBOT API Trial Accountを登録します。

https://business.line.me/services/products/4/introduction

アカウントは1人あたり1つまで申し込めます。先着10,000名様まで利用可能だそうです。(4/9 17:00時点ではまだ申し込めるようです)

注意点

  • 友だち数上限は50人までです
  • 料金は無料です

実装方法

素晴らしい解説記事がすでに公開されているので、ここではリンクを貼るだけにしておきます。先人たちに感謝🙇🏻

ハマったポイント

Callback URLはHTTPS必須

公式ドキュメント

The callback URL must use HTTPS.

とあるように、Callback URLは HTTPS を利用する必要があります。HTTPで登録しようとするとエラーになります。

ポート番号を443にする必要がある

公式ドキュメントではあまり強調されていないのですが、Callback URLにはポート番号を指定する必要があります。

例:https://example.com:443/callback

Callback先をホワイトリストに登録しておく必要がある

Callback先のサーバーを予め「Server IP Whitelist」に登録しておかないと、サーバーからBOT APIをたたくことができません(StatusCode:427が返ってくる)。ホワイトリストはLINE developersの画面から登録することができます。

f:id:koogawa:20160409172820p:plain

Callback URLにリクエストがこない問題

サーバ側で使用しているSSL証明書によってはCallback URLにリクエストが通らない例があるようです。Twitterを見ると Let's Encrypt の証明書と相性が悪いようです。

成果物

キーワードをつぶやくと、それに関するノウハウ記事を教えてくれるbotを作りました。

f:id:koogawa:20160409173934p:plain

こちらから友達登録できます。(予告なく停止する可能性があります)

f:id:koogawa:20160409174015p:plain

リンク

【Tips】iOSで電子コンパスを使う(Swift編)

数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


f:id:koogawa:20160402095838p:plain

磁力センサーを利用し、iPhoneの向きを計測する方法を解説します。

「磁北」と「真北」について

単純に「北」と言っても、「磁北(じほく)」と「真北(しんぽく)」の二種類が存在します(2つの違いについては「真北とは? | 用語集とGISの使い方 | 株式会社パスコ」で丁寧に解説されています)。

ここでは磁北を取得する方法について解説します。

実行環境

実装方法

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

CoreLocation をインポートしましょう。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
    switch status {
    case .NotDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .Restricted, .Denied:
        break
    case .Authorized, .AuthorizedWhenInUse:
        break
    }
}

これで準備が整いました。さっそく、ヘディングイベントの取得を開始してみましょう。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    
    // 何度動いたら更新するか(デフォルトは1度)
    locationManager.headingFilter = kCLHeadingFilterNone
    
    // デバイスのどの向きを北とするか(デフォルトは画面上部)
    locationManager.headingOrientation = .Portrait

    locationManager.startUpdatingHeading()
}

この状態でiPhoneの向きが変わると、次のメソッドが呼ばれます。

func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
    self.textField.text = "".stringByAppendingFormat("%.2f", newHeading.magneticHeading)
}

magneticHeading には 0.0 から 359.9 の値が入ります。北の方向がちょうど 0.0 になり、それから東、南、西の順に値が増えていき、北に戻ると再び 0.0 に戻ります。

方角
0.0
90.0
180.0
西 270.0

ヘディングイベントの取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingHeading()
}

イベントの取得が不要になったら、忘れずに停止しておきましょう。

サンプルコード

注意点

応用できそうなアプリ

参考リンク

なぜ技術ブログを書くのか

昨日、こちらの記事がバズっていました。

www.2dgod.com

ブコメ欄を見ると厳しい意見が並んでいますが、個人的には自分が考えていることや悩みをアウトプットするのは大事だなぁ、と思ったのでした。

自分も普段からブログやQiitaにいろんなことをアウトプットしているので、この機会に なぜブログを書くのか? 自分の考えを整理してみたいと思います。

恩返しをしたかった

プログラミングを始めた頃、先人たちが残してくれた技術記事にいつもお世話になっていました。そして、いつしか自分も「読む側」から「書く側」に回りたいと思うようになりました。

最初は自分の記事なんか人の役に立つのか?そもそも読んでもらえるのか?などと不安でしたが、書いていくうちに「あなたの記事が参考になった」など嬉しい反応を頂けるようになりました。

なんとなく書いたTipsが意外にも多くの反応を頂けたりすることもあるので(困っていることはみんな一緒だったりする)、どんな些細な事でもアウトプットしていくことが大事なんだなぁ、と思いました。

文章力向上のため

自分は元々作文がとても苦手でした(今も得意ではないですが)。ブログを書き始めた頃は、頭の中で考えていることをなかなか文章化できず苦しんだのを覚えています。

しかし慣れというのは不思議なもので、書いているうちに便利な言い回しや、文と文をつなげるテクニック的なものが自然と身に付いてきます。今も決して上手な文章を書けるとは思っていませんが、昔よりは読める文章になってきたと思います。

知識を整理するため

勉強会で発表するときも同じ事が言えますが、いい加減なことをアウトプットすることはできないので、準備に多くの時間を割くことになります。この過程が知識を整理するためのとても良い時間になると思っています。

万が一、内容に間違いがあった場合は、読者の方からご指摘を頂けるのも嬉しいことです*1。こういった助け合いの文化は良いものですね。

自分の存在を知ってもらうため

こちらの記事にとても良いことが書いてあります。

同じようなキャリアを歩んでいる先輩が、他にいなかった 【Kaizen Platform 伊藤直也さん】|転職エージェントのパソナキャリア

いい商品を作っても、マーケティングや営業をしないと売れないケースって多々ありますよね。それと同じで、どんなに高いスキルを持っていてもそれをアピールしなければ、その自分の技術力に気づいてもらう機会はそうそうない。技術系のイベントに登壇したり、ブログで情報発信をしたり、ソースコードを公開してみたりといった活動はキャリア形成において有利に働きます。もちろん内容が伴っていれば、ですよ。僕は自分のブログがたくさん読まれるようになって、それが転職のきっかけの一つになったようなところがありましたから。

私自身、転職する際に自分のブログがとても重要なキーとなったので、この内容にはとても共感できました。自分の名前が外でも通用する名前になるためにも、どんどん情報発信はしていったほうが良いと思います。

はてブ数、ストック数を稼ぐため?

もちろん自分の書いた記事がたくさんブックマークされたりすると嬉しいものです。ただ、それが目的になってしまうのは違うと思っています。

例えば「絶対使うべきライブラリ集」とか「初心者が今すぐ〇〇を使うべき10の理由」といった引きの強いタイトルを付ければ、記事がバズる確率も上がることでしょう。しかし、そういった記事ばかりを書くようにはなりたくないものです。

今後も 瞬間的にバズる記事より、必要なときに参照してもらえる記事 を書いていきたいと思っています。

まとめ

今回書いたこの記事自身も「みんなどういう理由でブログを書いているんだろう?」と疑問に思った方がググった結果として辿り着いてもらえたら嬉しいです。

*1:文句を言うだけで、指摘までしてくれない方もいますが…

【Tips】iOSで標高を取得する方法(Swift 3.0対応)

数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


CoreLocationを使って、位置情報から標高を取得する方法をメモしておきます。

実行環境

実装方法

準備

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

実装

CoreLocation をインポートします。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .restricted, .denied:
        break
    case .authorizedAlways, .authorizedWhenInUse:
        break
    }
}

現在地の取得を開始します。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.startUpdatingLocation()
}

この状態で iPhone の位置情報が更新されると、次のデリゲートが呼ばれます。

// 位置情報が更新されるたびに呼ばれる
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let newLocation = locations.last else {
        return
    }
    self.textField.text = "".appendingFormat("%.2f m", newLocation.altitude)
}

newLocationaltitude プロパティで標高が取得できます。単位は メートル です。

現在地情報の取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingLocation()
}

サンプルコード

注意点

  • 標高は、現在の高度からではなく地図データから測定されます
  • つまり、東京タワーの展望台にいても地上にいても、取得できる標高は一緒になります

応用できそうなアプリ

  • 現在地の標高測定アプリ(すでにいろんなアプリがリリースされている)
  • 標高を競い合ったり、高さによって景品が出るゲーム
  • 気圧計と組み合わせて正確な高度を計測するアプリ

【Tips】iOSで位置情報を取得する方法(Swift 3.0対応)

※数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


CoreLocationを使って、緯度・経度を取得する方法をメモしておきます。

実行環境

実装方法

準備

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

実装

CoreLocation をインポートします。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .restricted, .denied:
        break
    case .authorizedAlways, .authorizedWhenInUse:
        break
    }
}

現在地の取得を開始します。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.startUpdatingLocation()
}

この状態で iPhone の位置情報が更新されると、次のデリゲートが呼ばれます。

// 位置情報が更新されるたびに呼ばれる
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let newLocation = locations.last else {
        return
    }

    self.latTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.latitude)
    self.lngTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.longitude)
}

現在地情報の取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingLocation()
}

サンプルコード

注意点

  • シミュレータでテストする場合は、メニュー > Debug > Location から位置情報をカスタマイズできます
  • 位置情報のアクセス権限について詳しく知りたい場合は下記の記事を参照してください

blog.koogawa.com

参考リンク

2016/3/16 #potatotips #27 (iOS/Android開発Tips共有会) に参加してきたよ

昨日は六本木のグリーさんで開催された potatotips #27 (iOS/Android開発Tips共有会) に参加してきました。

connpass.com

非常に広い会場でした。

f:id:koogawa:20160317002907p:plain

お水も頂きました!ごちそうさまでした😄

f:id:koogawa:20160317002918p:plain

ツイートもまとめておきました。

以下は私の方で気になった発表のメモになります。間違いなどあれば教えて下さい(遅刻してしまったため、最初の方はメモが取れておりません🙇)


目次:

Focus Engineに願いを

GoroOtsubo さんによる発表。tvOSのお話です。

iPhoneiPadとは違い、Apple TVでは画面のボタンを直接タップすることができません。そこで、リモコンでスワイプしてボタンにフォーカスをあわせる必要があるのですが、このフォーカスが思い通りに動かず半日ぐらい悩まれたそうです。フォーカスがどこに移るかはFocus Engine神のみぞ知るとのこと😇

▼GoroOtsubo さんの書籍

iBeacon Tips

@SatoHikaruDev さんによる発表。

iOS 7から搭載された iBeacon ですが、未だあまり浸透してない感じがすると sato さんは語ります。よって、今がチャンスなのだそうです!

Beacon機器としてはestimoteなどがあり、ひとつ300〜3000円くらいで購入可能。乾電池2本で1年以上持つんだとか。

実装上の注意としては、iBeaconの機能を使うためには位置情報を「常に許可」しておかないといけないそうです。また、電源入れた直後は1〜5分iBeaconの機能が使えないとのこと。

実装自体はとても簡単で、ユーザが領域から出たり入ったりしたタイミングでデリゲートを呼ぶようにできます。

tuple tips

成田さんによる発表です。

タプルとは複数個のデータをまとめたもの。一時的に使用する関連する値のグループをまとめるのに使えます。

タプルの要素にはインデックスでアクセスできますが、スコープが長くなった場合に可読性が下がってしまいます。そこで、タプルの要素にキーワードを付けることで可読性を上げるというTipsが!

タプルの比較。タプルの要素が6までのときに比較ができます(6個である理由はスライドを参照)。比較できるのはあくまでタプルの要素がEquatable、Comparableに準拠した型の場合であり、タプル自体はこれらのプロトコルに準拠していないので比較はできないので注意が必要です。タプルの中にタプルを入れると比較ができなくなります。

SwiftでDependency Injection

@yoichitgy さんによる発表。

github.com

WebとiOSの連携方法は何がベストか? 2016年版

@yoneapp さんによる発表。Webからアプリを開く方法のお話です。

標準で SmartAppBanner という仕組みがありますが、これが動くのはSafariのみ。WebViewには対応していません。そこで、各社のアプリはどうハックしているのか、という紹介が興味深かったです。

App Submission Feedback

@tomzoh さんによる発表。

アップルが公開しているリジェクト事例集 Common App Rejectionsを元に、最近多いリジェクト事例の紹介です。

なんと今日は72枚のスライドを準備されたそうですw 個人的にはぜんぶ聞きたかったのですが、時間の関係もあり、いくつかピックアップして発表されていました。

▼こちらはtomzohさん主催のイベント!8月に開催されます iosdc.jp