読者です 読者をやめる 読者になる 読者になる

よいちろ日記

忘れないようにメモ。

大手キャリアS社からFreetelに乗り換え。

調べたこと

f:id:yoichiro0903:20161210153025p:plain

携帯電話通信キャリアを、S社からMVNO(Freetel)に乗り換えた。端末は、iphone6(S社)からGalaxy S6(d社)へ。

  • 乗り換えのきっかけ。
  • MVNO拡大の障壁。
  • 端末の選び方。
  • どこのMVNOを選ぶか。 

乗り換えのきっかけ。

S社の更新月が来たのでいいきっかけと思い。月々8,500円の携帯料金が、2,000-3,000円になるのは魅力的。あと、Super Fridayだかなんだかのあのこすい戦略のカモ(資金源)になっていると思うと、なさけなくなってきたので。乗り換えを迷いながらもS社にMNP(番号そのままキャリア乗り換え)のやり方聞こうと思ってサポートセンターに電話したんだけど、ものすごく邪険にされて、MVNOに乗り換えを決意した。

MVNO拡大の障壁。

で、今回乗り換えるってなって割と心理的な障壁があるなと思った。MVNOはまだ市場として拡大フェーズだと思うのだけど、その心理的な障壁を超えられるかがカギな気がする。使う上でのユーザーデメリットは今のところないように思える。

仕組みとかよくわからないから面倒くさいと思われてしまう。

ある程度のリテラシーがないと、そもそも仕組みがわからなくて諦めそうだと思った。学習コストが払えて(ググれて)、かつケータイ料金を自分で払っていて経済的なメリットある人たちにしか検討されなそうなのが課題かなと思った。実際仕組みを理解するまでちょっとかかるけど、思っているほどこんがらがっているものではなかった。なんでもそうだけど知っちゃうとなんのことはなくても、調べる前はなんだかややこしそう。 

「白ロム」「SIM解除」とか怪しそうだし、何か起きて命綱のケータイが使えなくなったら困る。

イメージがひとり歩きしている気がしている。繁華街にある雑居ビルの法的にグレーなケータイ屋のイメージ。白ロムとか犯罪に使われそうだしね。でも実は赤ロム保証とか、MNPとか、MVNOのサポートセンターとか意外に整備されている。実際全然困らなかったし、参考記事や、やってみたブログがたくさんある。あとMVNOの電話サポートは大手キャリア(S社)よりよっぽど親切丁寧。怪しいイメージを払拭するのには、世の中にMVNOが浸透するのを待つほかなくて、大手キャリアとの差額数千円/月を安心料と思って我慢できてしまうレベルの人も多いかと思う。

用語についてはここが詳しい。
「白ロム」「赤ロム保証」

SIMフリー

実店舗がないからいざという時困る。

近くにMVNO乗り換え経験者もいなくて聞けないし、自分と全く同じ状況じゃないと頼っても意味なさそうなのでリアル店舗員がいないというのは、不安になるというのもわかる。いざという時文句言う対象がいるのも大きいのかもしれない。店舗員なら詰め寄ればなんとかしてくれそうだし(かわいそうに)。ただ、実店舗員がやってることを考えればわかるけど、大手キャリアとMVNO業者の安心感や対処方法に差異はない。(結局ググって出て来る情報教えてくれたり、サポセンに端末送ったりするだけ。)

他にも、最新端末を入手しづらい可能性がある、通話料が気になるなどあるけど、瑣末な要因かなと思った。

なので、いかに仕組みに関する学習コストをいかに下げ、怪しさを払拭できるかが普及のカギだと思う。アンバサダー制度導入とか地味に効きそう。また、Freetelのサイトの作りは素晴らしく分かりやすかった。こういう部分もMNOの逆を行っていて好感が持てる。検討していた楽天バイルは登録寸前のところでオプションだの何だのよくわからない画面に遷移して離脱してしまった。大手キャリアに対してMVNOって「安い」「シンプル」「明瞭会計」ってのが売りだと思うのだけど、足並み揃えない業者がいると普及のブロッカーになりそう。業界全体でMNOの逆を行く必要があると思う。会社の社用端末に導入してもらうとかはコストカットという意味で、別口で普及しそうだけど。各社価格帯、料金体系は結構似通っていて差別化は難しい。(まだパイの取り合いでとりあえず宣伝戦略とかの戦いになりそうだけど)差別化で何かやるなら実店舗サポートとかかな。元キャリア端末のMNPSIM解除から請け負えるようになったらもっといいけど、大手キャリアが第三者のSIM解除を絶対解禁しなさそう。その点、d社は全端末SIMフリー対応で真っ向勝負してて偉いと思う。 

端末の選び方。

まず、S社は端末をS社から買った本人以外はSIM解除できない上に、そもそもSIM解除できる端末が限られている。手持ちのiphone6SIM解除対象外だったので、この時点で端末購入は必須に。a社は本人以外でも契約後180日以上経っているものであれば3,000円で店舗でSIM解除できるよう。d社はSIM解除しなくてもMVNOのSIMが使える。(d社もSIM解除するにはd社から端末を買った本人しかできなかったはず。あと契約後180日以上縛りもあったかな。)

またMVNOによって端末キャリアの相性がある。(というより、どこの大手キャリア(MNO)の基地局電波をMVNOが借りているかという話っぽいけど。)例えば、mineo、UQ mobileはa社端末しか動作保証していないなど。その視点で考えると、d社網を使っているMVNOが多いので、プランなど比較するという意味でも、今後MVNO間で乗り換えをするかもしれないという意味でもd社の端末を購入するのが良いと思い、d社のGalaxy S6 32Gを購入した。中古で34,000円くらいだった。美品だったし安い。

どこのMVNOを選ぶか。

まず、通信料。5G(ギガ)通信プランだと軒並み2,000円/月前後でどこでも良い気がしてくる。LINEやポケモンGoの通信量が無料になるとかも決め手として弱い。

次に通話。店の予約とかちょっとした仕事の電話とか、気にせずかけたいので、かけ放題プランがあるところがリード。1回の通話がX分以内なら何度かけても月額XX円みたいなプラン。流石に何分でも何回かけてもXX円ってMNOには劣るけど、5分かけ放題とかなら問題ないかなと。知人との長時間通話はLINE使えばいいし。

この時点でFreetelと楽天バイルの二択。DMMの海外プランってのが直近海外旅行の予定があった自分としては気になったけど、イモトのWiFiでいい。

最終的には、よくわからん関連サービスの宣伝とか挟んでこなくて、ステージ制で料金変動のFreetelに。

乗り換えの手順としては、

  1. d社のGalaxy S6を購入。
  2. S社にMNP予約番号を発行してもらう。(サポセンに電話でOK)
  3. ネットでFreetelに会員登録&SIMカード申し込み。この際にS社から発行してもらったMNP予約番号が必要。
  4. FreetelからSIMカードが自宅に届く。約2日。
  5. SIMカードをGalaxy S6に入れて、APN設定。これは説明書通りにやればOK。
  6. Freetelのサポートセンターに、前の端末(iphone)から乗り換え準備完了の旨を伝える電話をする。
  7. 乗り換え完了。

とまあ、なんの難しさもなかった。

で、MVNOに乗り換えて2週間くらい使ってるけど、大きく困ることはない。メリットデメリットは最後にまとめた。電波も困らないし何より料金が安い。管理画面に料金つぶやくTweetボタンつける自信はすごいけど。 

Freetel増田社長のこのインタビュー見ると、真摯にユーザーと市場に向き合ってる姿勢がひしひしと伝わってくる。応援したい。

MNOの通信キャリアとしての価値はどんどん薄れていく気がする。最終的には基地局のメンテサービスで生きていくしか無いんじゃないかな。最低限の設備投資で、月々の料金割引に積極的に利益還元していくMVNOと、かたやユーザーから搾り取った金をマネタイズできていない他事業に投資し、リアル店舗という手離れの悪い宣伝費を持ち続け、他者協業などのしがらみで余分な機能やキャンペーンでお茶を濁すようにユーザー還元するMNO。通信キャリアとしての勝敗は明らかだと思った。いい買い物をしたなと思った2016年末でした。

 

追加で、今回iOSAndroidの乗り換えをしてみて、OS間での違いについて思った点もメモ。

Androidのいいところ

  • ウィジェットやホーム画面のカスタマイズが細かくできる。
  • Chromecastで画面ごとキャストできる。
  • 電子マネーバンザイ。
  • バックキーめっちゃ便利。アプリ間のシームレスな移動が心地いい。
  • 通知バーからの着信が地味に良い。着信受けつつLINE返信止めなくてよかったり。
  • GearVRすごい。(Galaxyのみ)


Androidの気になるところ

  • LINE通話しながらブラウザ操作すると通話が不安定になる。(自宅WiFiなので端末起因。Androidってか端末の状態?)
  • おサイフケータイのせいで、クレカをケースに入れてオートチャージとかできなくなった。クレカと分離しておサイフケータイ運用に。
  • GoogleIMEの学習速度が遅い。Appleのタイポ直してくれる日本語入力の便利さが懐かしい。
  • 電子マネーの履歴、アプリの使い勝手が楽天Edy以外クソ。モバイルSuicaWAONガラケーのアプリそのままで化石感やばい。
  • 電池の持ちが若干不安。1日充電せずにフル使用すると、15時くらいに瀕死に。
  • MicrosoftPixがない。無音カメラださい。
  • ファイル操作ができる一方で、画像がギャラリーに入ってくれなかったり若干手間。
  • Twitter公式アプリの上部メニューが出たり消えたりうざい。(これはGalaxyのタッチ感度の問題っぽい)
  • Macで着電できなくなった。
  • アプリの完成度がまちまち。iOSでは優秀なものでも、Androidでは立ち上がらないことすらある。

Appleは偉い。Androidアプリって審査ないからソフト体験の良い悪いが激しい。UIしょぼいの多い。あと、一度開いただけで勝手にローカルプッシュしてくるアプリが多い。オプトアウトでプッシュ消せるからまあいいけど。ロック画面とか乗っ取らないでほしい。ES Smart Lockとか。AVGとか。

でもまあ全体的に気に入っている。

 

自転車のライト盗まれて悲しかったから盗難防止対策ライト調べた。

調べたこと

今日、駐輪場で自転車のライト盗まれた。悲しい。悲しいついでに盗難防止対策してる自転車LEDライト調べた。色々あるけど、品切れだったり、高かったりで、結局は結束バンドと激安ライト運用にした。盗まれても1個500円ならそこまで痛くない。(悲しいけど。)

 

特殊工具じゃないと外せない系

盗難防止ノウハウは買ってからのお楽しみで、買ってまで仕組み調べるドロボーいないから抑止になってるのかな。それが真なら殆どの泥棒はいたずらで盗んでるな。

 

サドルに取り付け+特殊工具系か。これは盗みにくそう。

 

結束バンド系

結束バンドは安くて良い。ハサミで一発だけど、結束バンド一体型なら盗んだ人は使えないからいいのか。


結束バンドでぐるぐる巻きにするという案も。ハサミ持ち歩いてないと外せないからCATEYE盗られた自分としては良いかも。


動かすと音鳴る系

なんかソリューションとして微妙だよなあ。これはかっこいいし高機能だけど、高え。


GPS

これは自転車本体にもつけられそう。だけど、TrackR bravo以外は電池持ちが微妙。TrackR bravoも仕組みに運任せ感があるから悩ましい。


Sherlock GPS trackerは一週間か。あとSIM代がかかる。


TrackerPadも一週間。


 

安くて、盗難防止できて、できれば普通のLEDライトにAddOnできる防犯グッズあったらいいなあ。どんなのがいいかな。ちょっと考えてみよう。

 

鳥の糞のシールは知ってればなんてこと無いので、知ってても盗難防止効果があるやつ。(冬は)手袋とかチェーン(走行時には肩がけするので)にライトつけるという発想もあり。

 

アンガーマネジメントなんていらないと思ってたけど。

あたま整理
自分は自分のことを温厚だと思っていたし、感情をコントロールできると思っていたけど、環境とか自分のバイオリズムとかが色々と重なると、そうも行かないなとわかったので、その時のことを書いておきたい。世の中見渡せば、そんなこと悩むレベルか?とか、仕事舐めてんのか、と自分でもレベルの低さを感じるけど、だからこそ書いておく。レベル低くて器小さくて、ダサい。けど、書く。書くことで整理されて、イライラしたときにどういう風に考え方を変えれば、怒りを健全に発散できるか、を考えるためにも書く。
 
違う文脈で書かれていることだけど、書いておくことで誰かに観られる余地を残しておきたいからってのは、すごくよく分かる。
 で、こういうことを思っていてもわざわざ書くのはカッコ悪いんだよ。分かっててやってるんだよ。そうしないと自分の軸がどこにあるのか、本当は見失いそうになってるんだから。でもカッコ悪くてもやり方がこれしかないんだよ。そんでもって、それをどこか誰かが見えるところに置いておくと、カッコ悪いけどみんなにこんなこと言っちゃったからやらないともっとカッコ悪いしなって思うんだよ。

無駄に意識高かった系学生の末路 - shi3zの長文日記

 

整理すると自分が最近の環境の変化で感じた不快感というか、違和感、怒り?は、
  • 自分ができないことを棚に上げて(できているかのように振る舞い)他人はできて当たり前というような態度をとられること。
  • 自分ができることは他人にも求めてしまうがゆえに「べき」が引き起こすイライラ。こんな単純なことに意外に気付けなくなる。
  • 上司、自分、部下という関係で、親(自分)の幸せは二の次で、孫の未来や成長を優先させたがる祖父祖母。親あっての孫だと思うのだけど。
  • 使い勝手が良い、と思われていることを、頼られていると、脳内変換させようとしている自分。思う壺。
  • クソほど何の役にも立たなそうな、人を向いた仕事は、やらないと咎められる立場にいる自分がさせられるというチキンレースに強制参加。
というところ。
 
まあ、色々と考えたり調べたりして、一個一個解決していくしか無いなと思う。
で、悩んで、まあ、最近良く聞く「アンガーマネジメント」なるものを知ってみようと思って書店に。
「アンガーマネジメント」のすぐ横に心を落ち着かせるという文脈?で「マインドフルネス」コーナーもあって、この辺をななめ読んでみたけどどれもピンとこない。結局自分特有の怒りの原因をしっかり文字で整理し、何が原因で、どう解決できるかを考えることが重要に思えた。
 
サーチ・インサイド・ユアセルフ――仕事と人生を飛躍させるグーグルのマインドフルネス実践法

サーチ・インサイド・ユアセルフ――仕事と人生を飛躍させるグーグルのマインドフルネス実践法

 

あまり怒りで困っていない人向けな気がした。

 

だいじょうぶ 自分でできる怒りの消火法ワークブック (イラスト版 子どもの認知行動療法 2) (イラスト版子どもの認知行動療法)

だいじょうぶ 自分でできる怒りの消火法ワークブック (イラスト版 子どもの認知行動療法 2) (イラスト版子どもの認知行動療法)

 

子供向けなのでたとえが秀逸だった。

 

アンガーマネジメント 11の方法―怒りを上手に解消しよう

アンガーマネジメント 11の方法―怒りを上手に解消しよう

  • 作者: ロナルド T.ポッターエフロン,パトリシア S.ポッターエフロン,藤野京子
  • 出版社/メーカー: 金剛出版
  • 発売日: 2016/09/09
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
 

 教科書的な。自分の怒りパターンがわかるのが面白い。

 

自分ができないことを棚に上げて(できているかのように振る舞い)他人はできて当たり前というような態度をとられること。

一番気になっているのはこれ。よくもまあいけしゃあしゃあとこういうことが言えるなと思うのだけど。もちろん、特別なスキルや専門が決まっていて、自分の専門外のことをお願いするのはわかる。ただ、あくまでもお願いというスタンスは守るべき。できなくて手伝ってもらうのだから。そうではなくて、自分がさぼってできなかったことを、やってこなかったことを、あたかもやってきたかのように振る舞い、それを他人に押し付けるような態度は見ていて惨めになる。本当に能力がなくてできなかったのかもしれないけれど、その努力さえもしてこなかったようにみえる人がそういうことをやるので。こればかりはどうしたら良いかわからないけど、せいぜい、「ああ、俺が同じ立場だったらこいつほどできなかっただろうな」と思わせて、その人の中での認識を改める(塗り替える)ことくらいだろうな。結局相手のメリットになっているところがすごく嫌だけど、怒ることでやることを拒否するよりかは、一度やってみてだめなら、「お前だってできないだろ」「本当にできなかったからそれは采配を変えてくれ」とバトンタッチするのがよろしいかな。まずは目の前のことを実直にやってみる。
 

自分ができることは他人にも求めてしまうがゆえに「べき」が引き起こすイライラ。こんな単純なことに意外に気付けなくなる。

これは相手も思っているはず。そう思うことで割と収めることができる。自分が相手に当然求めたいことが得られなくてイライラするとき、相手も自分に同じく当然求めたいことが得られていない可能性が高い。だから、こちらもあなたの期待に応えられていなくてごめんなさいの気持ちは持ちたい。余裕があるときは。そのレベルが低くて本当にどうしようもなくイライラするときは(「べき」が本当に「べき」なんだけど満たされていないとき)相手への期待値を低くする。相手に期待してしまうことは自分の無能さの裏返しとシビアに受け止める。「そうですよね。あなたにそんなことができるわけないですよね。僕がカバーしますよ。」と。そのカバーリングで相手が幸せになってくれるなら結果自分も幸せだからいい。人の役に立てるじゃないか。身近な人が喜んでくれるのは嬉しいよ。(喜んでくれているというより、「ああ、こいつなんでも言うこと聞いてくれるわ。俺はいつも楽できてラッキー!」くらいに思ってるかもしれないけれど、そういう人は、その後に押し寄せてくる罪悪感に苛まれてくれ。悪人だったらもうしゃあない。)真剣に、同類として仕事をするからこそ生まれる怒りでもあると思う。うまく働けば切磋琢磨できるので、「あなたにはここまで求めたい。」と伝えるのも手。
 

上司、自分、部下という関係で、親(自分)の幸せは二の次で、孫の未来や成長を優先させたがる祖父祖母。親あっての孫だと思うのだけど。

言ってることとやってることが矛盾しているのに気づかないのかなという案件。お前が俺を幸せにしてくれないのに、そう配慮してくれないのに、孫を幸せにできなかったらそれはお前の責任だと。この矛盾にはしびれた。そもそも他人が他人を幸せにできると思っているあたりが気に食わない。ここばかりは上司の目を気にせず、自分の幸せを優先させようと思っている。孫の幸せの援助はできる範囲でする。決して祖父祖母の言いなりにはならない。それで評価が下がるなら、それはそれでいい。あってないから、飛び出すか追い出すとき。自分の幸せが一番。そう考えていいだろ。孫の幸せは孫が見出してくれよ。結局は自分含めて幸せになれないのは、面白い仕事を提示できないリーダーの責任なので、これは自戒の念も込めて、面白い仕事を作り出すということには集中したい。人で幸せを動かそうとする行為は、スケールもしないし、何より自分が疲れる。
 

使い勝手が良い、と思われていることを、頼られていると、脳内変換させようとしている自分。思う壺。

試されている、から期待に答えなければ、とか、色々と仕事くれるのは期待の裏返しとか、ある程度まではワークすると思うのだけど、誰でもできる仕事、自分が今まで何度もやってきた仕事は、うまく処理しないと、いつまでたっても同じステージにいることになってしまう。雑用をやらされているのか、本当に挑戦的な仕事なのかは都度見極めて、「誰がどういう風に得するのか、誰が喜ぶのか。その結果、自分が嬉しいか。」を考えて引き受けないと、機械になってしまう。自分が活きる、頼られる仕事の割合を増やしていくとともに、使われるなかでも光る何かを見せたい。なにが自分にとって得られるものなのかを見極めて、初心を持って取り組む。
 
「頼られる」というのは貴方にしかできないことをお願いされる状態です。
「いいように使われる」はその人や他の人でもできるのに、責任逃れなどの理由で仕事を押し付けられる状態です。

 

Yesと言えるのにあえてNoというには、Yesと言った後にDoneと言わせた経歴が必要なんだよ。まだそれがないのにNoというのは、それはNoではなくてCannotなんだよ。本当にCannotだったら悪いことは言わない。辞めて別の仕事を探せばいい。もっともNoしか言えない人にいい仕事は来ないけど、それは君が選んだ無理。高福祉も高成長も高収入もというのはもっと無理。
〜略〜
私は見栄っ張りなので、NoはYesと言えるときでないと言いたくない。本当に出来ないときにはNoじゃなくて I'm sorry って言う。でも私のところに来る案件は、私が No って言ったら後がないものばかり。25歳の時もそうだったけど今もそう。こういう仕事には No っていいづらい。でも他に行き先があるのが明らかな場合は、遠慮なく No って言っている。厳密には No じゃなくて無理な見積書を出すだけなのだけどね。
 

クソほど何の役にも立たなそうな、人を向いた仕事は、やらないと咎められる立場にいる自分がさせられるというチキンレースに強制参加。

上司には自分の評価を下げるという切り札があるし、部下には自分に責任を負わせることが許される。やらないと損するのは自分、得するのは上司、という状況で誰がやるのやらないの、結局自分がやる、のチキンレースを強いられている。せめて誰がどういう風に喜んで、どう回り回って自分たちに利益があるかくらい知りたい。それが依頼する側の最低限の責任でしょ。だめだよ。ただのわがままでお願いしちゃ。あと、仕事を作り出す、進行するということを任せてくるならば、仕事を断るという判断権限も欲しい。これは自分が上に上がってこういう仕事を抹殺するときのために、どういうたぐいのものが本当のクソ仕事かを覚えておこう。すごくネガティブだけど、いなくなってから困るのは彼らなんだからせいぜい依存させておけばいいという、熟年離婚を企む主婦の気持ちがよーく分かる。
 
 
 
つらつら書いてだいぶ整理されたような気がする。ちょっと俯瞰して見ると、すべては慢性的に人手が足りないことに依っていると思うし、それはどこでもそうだと思うので、少しでも自分が意欲的に働きたくなる環境を作るために、お金を稼いだり、事業の状態を良くすることに腐心しよう。こんなつまらないことは、「回り回って自分の養分になる」と思ってちゃちゃっと処理していこう。ふっと落ち着いて(肚の底の想いとは違っても)ニコニコしてれば、とりあえず問題の本質とは関係なく周りも幸せだ、という結論に至ったが、自分でもおぞましくなる程のサラリーマン精神を発揮してしまって吐きそう。

iOSでBluetoothヘッドセット音声認識アプリを作りかけた。(録音、マイク音量取得 編)

開発メモ

に引き続き。 
 
・その他の機能について。
 ┗ 音声認識開始トリガーのためにマイク音量を取得する。
 ┗ 音声認識対象の音声データの保存する。
この2つについてメモ。
 

音声認識開始トリガーのためにマイク音量を取得する。

マイク音量を取るには、AudioToolboxを使うようだ。 

ここが一番詳しくわかりやすく書かれていた。この通りに実装したら動いたのでとりあえず良し。
 

Core Audio その2 AudioStreamBasicDescription | Objective-Audio
AudioStreamBasicDescription dataFormat;
の中で、kAudioFormatLinearPCMとかフォーマットっぽい定数が出てくるんだけどそれは構造体としてフレームワーク(AudioToolbox?)のなかで定義されているっぽい。詳しくは追いきれていない。

 
1つ問題があって、UIDictationControllerを使って音声認識する際には、マイク入力がそっちに取られてしまう。
UIDictationControllerでの音声認識中のマイク音量は[UIDictationController audioLevel]で取得して、切り替える必要がある。なので、普通に、
BOOL isUIDictContSpeechRecognition = YES;
って感じでフラグ管理する。
 
//UIDictationController マイク音量取得
_audioLevelTimer = [NSTimer
                    scheduledTimerWithTimeInterval:1
                    target:self
                    selector:@selector(audioLevel)
                    userInfo:nil repeats:YES];

//UIDictationControllerで音声認識中はこれでマイク音量が取れる。
//AudioQueueRefへのマイク入力はなくなってしまう。UIDictControllerに乗っ取られる。
- (float)audioLevel {
    float audioLevel;
    audioLevel = [_dictationController audioLevel];
    NSLog(@"audioLevel: %f", audioLevel);
    return audioLevel;
}
UIDictationControllerによる音量取得は取り続けていればOKだが、AudioToolboxによる音量取得はUIDictationControllerにマイクを乗っ取られて戻ってくるときにリセットしないとおかしくなる。
- (void)stopUpdatingVolume {
    AudioQueueFlush(queue);
    AudioQueueStop(queue, NO);
    AudioQueueDispose(queue, YES);
}
でリセットできるので、切替時にマイク音量数値をリセットする。
 

音声認識対象の音声データの保存する。

マイク入力で得た音声データを保存する。

ここのコードとチュートリアルが完璧すぎてもうほかに何も要らない。
 
備忘録に触りがわかるようなコメントをしたコードを記載。 
.hファイル
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface RecordAudio : UIView <AVAudioRecorderDelegate, AVAudioPlayerDelegate, NSURLConnectionDataDelegate>

@property (strong, nonatomic) AVAudioRecorder *recorder; //録音用インスタンス
@property (strong, nonatomic) AVAudioPlayer *player; //再生用インスタンス
@property (strong, nonatomic) AVAudioSession *session; //セッション管理インスタンス

//音声ファイルのパス
@property (nonatomic, strong) NSString *filePath;

- (void)initInstance; //インスタンシエイト。サンプリング周波数など録音設定をする。
- (void)recordStart; //[_recorder record]を実行。
- (void)playAudio; //[_player play]を実行。
- (void)recordStop; //[_recorder stop]を実行。
@end
 
.mファイル
#import "RecordAudio.h"

@implementation RecordAudio

- (void)initInstance {
    //録音先のファイルパスを設定する。
    self.filePath = [self makeFilePath];
    NSURL *outputFileURL = [NSURL URLWithString:self.filePath];
    
    //セッションを張る。
    _session = [AVAudioSession sharedInstance];
    [_session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; //お約束。
    
    //NSMutableDictionary形式で録音設定を定義。
    //kで始まる定数はAudioToolbox(?)で構造体として定義されているっぽい。
    NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
    [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
    [recordSetting setValue:[NSNumber numberWithFloat:16000.0] forKey:AVSampleRateKey];
    [recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
    [recordSetting setValue:[NSNumber numberWithUnsignedInt:16] forKey:AVLinearPCMBitDepthKey];
    
    //録音用インスタンスを容易。
    _recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
    _recorder.delegate = self;
    _recorder.meteringEnabled = YES;
    [_recorder prepareToRecord]; //お約束。
}

- (void)recordStart {
    //念のため。あと2回連続で録音開始しようとしたとき用。
    if (_player.playing) {
        [_player stop];
    }
    if (!_recorder.recording) {
        [_session setActive:YES error:nil];
        //録音開始。
        [_recorder record];
    } else {
        //ポーズもできる。
        [_recorder pause];
    }
}

//============================================================
//録音した音声データを再生。
- (void)playAudio {
    if (!_recorder.recording){
        _player = [[AVAudioPlayer alloc] initWithContentsOfURL:_recorder.url error:nil];
        [_player setDelegate:self];
        [_player play];
        
        NSString *recorderUrl = self.filePath;
        NSData *data = [NSData dataWithContentsOfFile:recorderUrl];
        NSLog(@"@%@", recorderUrl);
    }
}

//録音停止。
- (void)recordStop {
    [_recorder stop];
}

#pragma mark - AVAudioRecorderDelegate
//デリゲートメソッド。ログを出すのみ。
- (void) audioRecorderDidFinishRecording:(AVAudioRecorder *)avrecorder successfully:(BOOL)flag{
    NSLog(@"audioRecorderDidFinishRecording");
}

#pragma mark - AVAudioPlayerDelegate
//デリゲートメソッド。ログを出すのみ。
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"audioPlayerDidFinishPlaying");
}
//============================================================
//音声データを保存するファイルパスを設定するメソッド。
- (NSString *)makeFilePath
{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyyMMdd"];
    NSString *fileName = [NSString stringWithFormat:@"%@.wav", [formatter stringFromDate:[NSDate date]]];
    
    return [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
}

@end

 

その他Tipsを載せておく。

  • ダウンロードしたサンプルコードとかがiphone4サイズとかで、iphone6でビルドすると画面の上下に余白ができることがあったのだけど、それは「General」の「App Icons and Launch Images」を設定しないようにしたらなおった。

  • 今回特に学んだ、NSNotificationCenterについて。http:// http://yatemmma.hatenablog.com/entry/2014/04/12/002806
  • NSDictionaryControllerは非公開クラスとだけあって(?)、普通に[[hoge alloc] init]形式で記述しようとするとエラーになるので、[NSClassFromString(@"UIDictationController") performSelector:@selector(sharedInstance)];という書き方になる。
  • 初出のstatic voidについて。C言語のstatic指定子について
  • - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNilはコードからUIViewControllerを作るメソッド。

iOSでBluetoothヘッドセット音声認識アプリを作りかけた。(Google Speech API 編)

開発メモ

に続いて、Google音声認識APIを利用してみる。

 

Google Speech API(Cloud & Not Cloudの2種類)を利用する。

f:id:yoichiro0903:20161203235246p:plain

これは勘違いしてて混乱したけど、Google Speech APIには2種類ある。
Speech APIGoogle Cloud Speech APIである。
※両方試したが、認識精度は素人目にはほぼ同じで高い認識率だと感じた。

Speech APIは、Chrome-devグループに参加しないとAPI検索にすら引っ掛かってこない。で、無料でつかう場合、1日50回まで(+100秒に1人100回まで)という制限がある。

一方、Google Cloud Speech APIGoogle Cloud Platform上で「API Manager > ライブラリ」で検索かければ出てくる。1日25万リクエストまで。個人利用なら余裕。

f:id:yoichiro0903:20161203235020p:plain
「Speech API Private API」ってのが、グループに入らないと使えないやつ。

 

Speech APIの使い方。

この記事を参考にグループ参加、キーの作成を行う。

上記記事から、API ManagerのUIが変わっているみたいなので、掲載。認証情報を作成から、APIキーを作成。作成後はAPIキー欄のキーがAPIに接続するときに必要になる。

f:id:yoichiro0903:20161203235319p:plain

ダッシュボードから各APIの利用状況などが見れる。

curlで試すとこんな感じ。

// soxインストール
$ brew install sox
// 音声録音
$ rec --encoding signed-integer --bits 16 --channels 1 --rate 16000 <出力ファイル名(test.wav)>
// flac形式に変換(flac形式のファイルが作られる(test.flac))
$ flac -V <変換するファイル(test.wav)>
// google speech API音声認識
$ curl -X POST \
--data-binary @'<ファイル名>' \
--header 'Content-Type: audio/x-flac; rate=16000;' \

f:id:yoichiro0903:20161203235415p:plain

アプリへの組み込みは後述。

Google Cloud Speech APIの使い方。

以下の記事を参考にAPIキーを取得するところまで行う。Speech APIGoogle Cloud Speech APIを混同して、Speech APIの導入記事を読んで、Google Cloud Speech APIの実装をするみたいな、わけわからんことしてた。しかもいろんな記事に「課金設定を有効に」と書かれていたのも「課金なんていらんだろ」てな具合で無視して進んでて、クレカ登録してなかった。これがあとでAPIを利用するために必要になることを知る。

さすがGoogleとだけあって、iOSはじめアプリへの組み込みサンプルコードが充実している。
https://cloud.google.com/speech/docs/samples?hl=ja


動作検証をアプリと同時進行していて、アプリだと戻り値が無くてうんともすんとも言わないから、原因が(アカウントの課金設定だとは)わからずATSが原因かな?とか思って、 

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

あたりをいじっていたけど違った。 原因解明のために、そもそもアプリのコードが問題なのか、APIが使えてないのか切り分けることに。

ここを参考にCloud SDKで動作確認。


gcloudコマンドでservice-account-key-fileを指定してアクセストークンを発行。service-account-key-fileはAPI Managerの「認証情報を作成 > サービスアカウントキー」から、「Compute Engine default service account」を選択するとjsonがダウンロードできるので、そのjsonを指定する。

すると、

[~]$ curl -s -k -H "Content-Type: application/json" -H "Authorization: Bearer <アクセストークン>"  https://speech.googleapis.com/v1beta1/speech:syncrecognize -d @sync-request.json
{
  "error": {
    "code": 403,
    "message": "Project <プロジェクト名>(#000000000) has billing disabled. Please enable it.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Google developer console API key",
            "url": “<Google Cloud Platformの管理画面URLがでてくる>"
          }
        ]
      }
    ]
  }
}

billing disabled!これか!と膝を打った。

ググると、

Cloud Vision APIは、課金を有効にしないと利用できません。APIキーは発行できたので、そのまま使ってみるか、といった具合にリクエストをしても、次のエラーメッセージが含まれたJSONが返ってきます。「課金有効にしろや」というメッセージですね。

すべてを理解した。

で、クレカ登録を済ませると、できるようになったというオチ。(使い方ミスって巨額請求きたりしたら怖えとか思いながら登録したけど今のところ平気。)

 

iOSアプリでの利用方法。

動作確認だけであれば、GoogleのサンプルコードのAPIKeyを入れ替えるだけで動く。
音声認識につかうAPIの切り替えは、簡単に言うと接続先URLを変更するだけ。

 

 

コードの流れとしては、

  •  AVAudioRecorderで音声データを保存。
  • 保存した音声データをNSData形式で取り出し。
  • 音声データをbase64EncodedStringWithOptionsでエンコードして、NSDictionary形式に。
  • 音声認識時に必要な設定に関するデータもNSDictionary形式で定義。
  • 両方をリクエスト用のNSDictionaryにまとめる。
  • NSURLSessionTaskで接続先URLやら設定やら音声データやらをひとまとめに投げて、音声認識開始。
  • Blocks処理内で音声認識結果を受け取る。
- (IBAction) processAudio:(id) sender {
  [self stopAudio:sender];

  NSString *service = @"https:/speech.googleapis.com/v1beta1/speech:syncrecognize";
  service = [service stringByAppendingString:@"?key="];
  service = [service stringByAppendingString:API_KEY];

  NSData *audioData = [NSData dataWithContentsOfFile:[self soundFilePath]];
  NSDictionary *configRequest = @{@"encoding":@"LINEAR16",
                                  @"sampleRate":@(SAMPLE_RATE),
                                  @"languageCode":@"ja-JP",
                                  @"maxAlternatives":@30};
  NSDictionary *audioRequest = @{@"content":[audioData base64EncodedStringWithOptions:0]};
  NSDictionary *requestDictionary = @{@"config":configRequest,
                                      @"audio":audioRequest};
  NSError *error;
  NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestDictionary
                                                        options:0
                                                          error:&error];

  NSString *path = service;
  NSURL *URL = [NSURL URLWithString:path];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  // if your API key has a bundle ID restriction, specify the bundle ID like this:
  [request addValue:[[NSBundle mainBundle] bundleIdentifier] forHTTPHeaderField:@"X-Ios-Bundle-Identifier"];
  NSString *contentType = @"application/json";
  [request addValue:contentType forHTTPHeaderField:@"Content-Type"];
  [request setHTTPBody:requestData];
  [request setHTTPMethod:@"POST"];

  NSURLSessionTask *task =
  [[NSURLSession sharedSession]
   dataTaskWithRequest:request
   completionHandler:
   ^(NSData *data, NSURLResponse *response, NSError *error) {
     dispatch_async(dispatch_get_main_queue(),
                    ^{
                      NSString *stringResult = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                      _textView.text = stringResult;
                      NSLog(@"RESULT: %@", stringResult);
                    });
   }];
  [task resume];
}

 とまあGoogleさまのおかげでとっても簡単に音声認識を組み込んだアプリが作れそう。音声データの保存とマイク音量取得についても書く。

iOSでBluetoothヘッドセット音声認識アプリを作りかけた。(UIDictationController 編)

開発メモ

やりたいこと。

iOS(9.3)で音声認識アプリを作りかけたので備忘録。Bluetoothヘッドセットで発話の音を拾って認識する。Xcodeは8.1を使用。
 
音声認識エンジンをどうするか。
 ┗ UIDictaionControllerを利用する。
 ┗ Google Speech API(Cloud & Not Cloudの2種類)を利用する。
・その他の機能について。
 ┗ 音声認識開始トリガーのためにマイク音量を取得する。
 ┗ 音声認識対象の音声データの保存する。
 ┗ (これは実装できてないけど)保存する音声データをシリアル化して端末に保存。
 

音声認識をどうするか。

この記事を参考に取りうる手段を決定。

お試しかつ初心者なので、有償のものやドキュメントが少なそうなものは脱落。
長時間の音声データを認識させたかったという点でSpeech.framework(端末ごとに認識回数の厳しい制約がある)は脱落。
また、認識エンジン自体をいじくり回すのが面倒だったので、(組み込みに集中したかったので)Juliusは脱落。面白そうなので次回。

その他の機能について。

ヘッドセット(ハンズフリーイヤホン?)でアプリ操作なく音声認識の開始終了を行いたかったため、認識前後、認識中のマイク音量を取得する機能も実装した。認識対象の音声データが保存されていれば、認識エンジンを変えて、より精度のよい結果が得られるため可能であればwaveファイルなどでデータ保存したい。音声データって結構大きくなってしまうかもだから圧縮して、しかも端末に保存できるといいなあというのも。

 

 

今回はUIDictationControllerを使った音声認識についてメモ。

 サンプルコードはこちら。(大半が先人の知恵をお借りしたコピペです。)

UIDictaionControllerを利用する。

図式すると、以下のような仕組み。

f:id:yoichiro0903:20161203153655p:plain

  • 無料。
  • 高い音声認識率。(Appleのキーボードの音声入力と同じ)
  • 端末ごとや時間ごとの音声認識回数制限なし。
  • 音声データ秒数制限は1回の認識につき15秒程度。

 

UIDictationControllerクラスが非公開クラスなので実際に審査とかは通らないらしいけど、遊んでみる分には良い。


【Objective-C】iOSで音声認識サンプル(UIDictationController) | AS blind side

このブログがめちゃんこ参考になりました。ありがとうございます。

 

ここにUIDictationControllerで使用できるメソッドがわかる。

 

コメント見る感じ、このメソッド一覧はRuntimeBrowserで調べたっぽい。


キーボード(UITextInput)に含まれるクラスらしく、デリゲートとしてUITextInputをしていしたUIViewクラス(UIDictContRecognitionと命名)を作る。UITextInputクラス必須のデリゲートメソッドは中身カラで書いておかないとエラーになる。

この辺、いつもテキトーにやってしまっているから改めて書いておいた。

 

 コード部に入る。
+ (id)sharedInstance; //インスタンス- (float)audioLevel; //音声取得中のマイク音量取得
- (void)startDictation; //音声取得開始
- (void)stopDictation; //音声取得終了&音声認識開始
- (void)cancelDictation; //音声取得中止
UIDictationControllerクラスのメソッドは上記を使った。
ViewControllerにて、
_dictationController = [NSClassFromString(@"UIDictationController") performSelector:@selector(sharedInstance)]; 
でUIDictationControllerクラスのインスタンス生成。
 
ボタンなどでViewControllerクラスに実装したstartDictationなどを呼び出して、その中でUIDictationControllerのstartDicataionメソッドを呼び出す。(紛らわしいので違う名前にしたら良かったけども。)
- (void)stopDictation {
    [_dictationController stopDictation];
}
最初は再帰的に見えて、わけがわからなかったけど、
[self startDictaion] = [ViewController startDictation]
ってこと(ViewController.mの話なので)で、
[_dictaitonController startDictaion] = [UIDictationController startDictaion]
ってこと。
 
で、startDictaionで音声取得を開始、stopDictationで音声取得を終了、すると音声データがAppleのサーバーに送られて、音声認識結果が返ってくる。
返ってきた音声認識結果は、UIDictContRecognitionクラスで通知される。
 
UIDictContRecognitionにて、
-(void)insertDictationResult:(NSArray *)dictationResult {
    [
     [NSNotificationCenter defaultCenter]
     postNotificationName:@"DictationRecognitionSucceededNotification"
     object:self
     userInfo:[NSDictionary dictionaryWithObject:dictationResult forKey:@"DictationResultKey"]
     ];
}
insertDictationResultで通知されるので、それをViewControllerクラスに通知。userInfoに音声認識結果がNSDictionary形式でkey=DictationResultKey, value=音声認識結果、という感じで格納されているので渡してあげる。
ViewControllerにて、
[
     [NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(dictationRecognitionSucceeded:)
        name:@"DictationRecognitionSucceededNotification"
        object:nil
];
音声認識結果を受け取って、予め実装しておいたdictationRecognitionSucceededメソッド内でNSLogとかで確認。
- (void)dictationRecognitionSucceeded:(NSNotification *)notification {
    //Output result.
    NSDictionary *userInfo = notification.userInfo;
    NSArray *dictationResult = [userInfo objectForKey:@"DictationResultKey"];
    NSString *resultText = [self wholeTextWithDictationResult:dictationResult]; //音声認識結果を取り出すメソッド
}
音声認識中(音声取得中)のマイク音量は、
_audioLevelTimer = [NSTimer
                     scheduledTimerWithTimeInterval:1
                     target:self
                     selector:@selector(audioLevel)
                     userInfo:nil repeats:YES
                   ];
- (float)audioLevel {
    float audioLevel;
    audioLevel = [_dictationController audioLevel];
    NSLog(@"audioLevel: %f", audioLevel);
    return audioLevel;
}
と取れる。この値が閾値以下になったら音声取得終了&音声認識開始とかするとスマート。(音声取得開始は別途他の方法でトリガー作る必要あるけど。後述。)
 
一旦UIDictationControllerに関してはここまで。

隅っこで苦しんでいる人がいる職場で、良いものが作れるか。

あたま整理
とある事業のバックエンド作業がかなり煩雑化していた。何十万行にも及ぶEXCEL作業、複数あるマスタファイル、属人化する知識。自分はその事業の企画開発側の人間で、いままでお世話になりっぱなしだったが、とうとう見るに耐えなくなり、腰を据えて効率化を手伝わせてもらうことにした。
 
バックエンド側の人は、開発に支障をきたすのでは、こんなこと言ってはわがままなのではと、なかなか声をあげられないし、その実を知るには、かなり奥まで入って一緒にどぶさらいする必要があった。けっこう大変だったし、その間、本業は進まなかったけれど、ようやく解決に向かって踏み出した。
 
こういうところって高時給の人(サービスエンジニア)たちは往々にして誰もやりたがらないけど、実は彼らの能力を活かす余地は大きい。もっと大きな視点で見れば、馬力のあるベンチャーレガシィ産業の隙間を縫って産業構造を変革するに近い。大袈裟すぎか。
 
隅っこの方で、誰に感謝されるでもなく、黙々と、地味でただ辛くて、間違えたときだけ減点方式で目立ってしまう仕事をしている人はたくさんいる。好きでやっている人たちばかりではないだろう。今回、そういう人たちを幸せにすることは職場の雰囲気を隅から隅まで良くするという意味でも、その人達がいきいきとしたことに時間を使えるようになるという意味でも、とても重要なんだと感じた。
 
こういったことが原因でやめてしまう人もいるはず。同じ職場の人間が少しでもネガティブな理由でやめていくような場で、お客さんに対していいものを作ろうなんていうのは、難しいのではないかなとも。
 
かっこいい仕事ばかりやりたいと思ってしまうけれど、その影で泣いている人を救えなければ、総幸福度はマイナスなんじゃないかな。土台は想像以上に大事。