「Objective-C」カテゴリーアーカイブ

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

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

音声認識機能を調べていて、OpenEarsは日本語には今ひとつなので、非公式クラスらしけど、UIDictationControllerを使ってテストしてみました。

【参考】
iOS 5.1 の音声入力を使ってアプリケーションを操作してみる – 24/7 twenty-four seven
【iOS】【JPlayer】再生/停止の切り替えで音声認識をスタートして自動でストップする – The jonki

何故か1つ目のリンクのサンプルはうまく動かなくて、2つ目のリンクの方は音楽プレーヤーに組み込まれているので、音声認識だけを切り出してサンプルを作ってみました。

参考にしたリンクによれば、音声認識はキーボード経由でのみ行われ・・・

1.キーボードを表示。
2.音声入力ボタンを押す。
3.話す。
4.完了ボタンを押す。

という手順を踏んだ後、録音された音声がサーバ経由で文字列変換されて戻ってくるらしいです。

で、上記2.と4.を自動化するメソッドが、UIDictationControllerという非公開クラスに含まれています。

※キーボードは、UITextInputを紐付けた不可視のUIViewを用意して、becomeFirstResponderで表示する。
※UITextInputを使うので、必要なメソッドが定義されていないと、かなりの数のエラーがでます。警告だけど。

このサンプルでは、開始ボタンを押すと音声認識が開始(startDictation)され、timer(4.75秒)もしくは停止ボタンで音声認識が終了(stopDictation)するようになっています。画面はこんな感じ。

IMG_7121

認識率はさすがにいいです。コレ、早く公開APIにしてくれたらいろいろ使えそうなのになあ。iOS5.1からずっと非公開ってことは何かあるんですかね、問題が。

音声認識を使ったアプリのアイデアはあるんですが、OpenEarsで試してみるか、この方式で作っておいて公開APIになるまで寝かしておくか、悩みどころです。

一応、ソースをGitHubに上げてみました。初めてのGitHub(笑)。ご参考までに。

【Objective-C】ランダムな数値を取得する方法

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

配列からランダムな数を取得するというのは、どんなプログラムでもよくやります。

for (int i=[iconArray count]-1; i>=0; i--) {
	int j = rand() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

一見よさそうだけど、なんとコレ、毎回同じ数値しか返ってきません。このrand()という関数は、毎回初期化しないと同じ値を返すらしい。たとえばこの記事なんかは、初期化をすっ飛ばして書いてあるので、実際に使ってみるとハマるわけです(笑)。

※上の処理だと、初期化がまったく行われていないので、ビルドしたアプリを起動し直しても、ビルドし直しても毎回必ず同じ値になります。

で、初期化を加えたのがコレ。

srand(time(NULL));
for (int i=[iconArray count]-1; i>=0; i--) {
	int j = rand() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

srand()はrand()の初期化用関数で、毎回違う値を返すことで、返す値が変わることになるらしい。

【参考】
指定した範囲内で乱数を発生させる | Objective-Cでのアプリ開発記

で、こっちのarc4random()は初期化込みの関数で、初期化を意識せずに使える。

for (int i=[iconArray count]-1; i>=0; i--) {
	int j = arc4random() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

この方法が、初期化ミスもなく一番いいのではないかという結論になるわけですが。

【参考】
[Xcode]ランダム数値の生成 | C++ | alperithm
iPhone-Labo: ランダムな数値を取得する – rand() arc4random()

ググってみるとだいたい「arc4random()使えば問題ないよね」って書いてあるんだけど、そもそもsrand()とrand()が別々に実装されている理由って何なんですかね?そっちの方がすごく気になる。知ってる人がいたら教えてください。

ちなみにココで書かれている、配列を後ろから参照して入れ替える方法をFisher–Yatesアルゴリズムというらしい。初めて聞きました。

【参考】
Fisher–Yatesアルゴリズムがすごかったです。: PandaNoir
Fisher-Yates Shuffle – Faith and Brave – C++で遊ぼう

【Objective-C】アニメーションの終了イベント

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

とりあえずこんな感じで。delegateを設定して、終了時に呼ばれる関数をsetAnimationDidStopSelector:@selectorで指定する。

//アニメーション:設定
[UIView setAnimationsEnabled:YES];
[UIView beginAnimations:@"Flip" context:nil];
[UIView setAnimationDelegate : self ];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:card cache:YES];
[UIView setAnimationDidStopSelector:@selector(compareCard)];

//アニメーション:開始
[UIView commitAnimations];

※card:アニメーションするUIView、compareCard:アニメーション終了時に呼ばれる関数

【参考】
あじもんすたー日記,iOSアニメーションでのミス

【Objective-C】再生リストからiCloudとDRM付きの曲を外す

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

前回書いたように、AVPlayer(AVQeuePlayer)でiPodライブラリの音楽を再生する場合、DRM付きの曲が再生できない。表示されているのに再生できないのはアプリとしてマズいので、再生リストから外す方法を考えてみました。

#import "SelectViewController.h"

@interface SelectViewController ()

@end

const int CATEGORY_ALBUM = 0;
const int CATEGORY_PLAYLIST = 1;
const int CATEGORY_ARTIST = 2;

@implementation SelectViewController
{
    NSDictionary *dataSource;
    NSMutableArray *key;

    int current_category;
}

//(中略)

-(void)setMusicCategory
{
    MPMediaQuery *query;
    switch (current_category) {
        case CATEGORY_ALBUM:
            query = [MPMediaQuery albumsQuery];
            break;
        case CATEGORY_PLAYLIST:
            query = [MPMediaQuery playlistsQuery];
            break;
        case CATEGORY_ARTIST:
            query = [MPMediaQuery artistsQuery];
            break;

        default:
            break;
    }

    //iCloudデータをフィルタリングで除外
    [query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];

    NSArray *collectionList = query.collections;
    key = [[NSMutableArray alloc] init];
    NSMutableArray *albumList = [[NSMutableArray alloc] init];

    for (MPMediaItemCollection *collection in collectionList) {
        MPMediaItem *representativeItem = [collection representativeItem];
        NSString *albumtTitle = [representativeItem valueForProperty:MPMediaItemPropertyAlbumTitle];

        //DRMチェック(AVPlayerで再生できないため/DRM対象曲のみを弾く)
        int drm_count = 0;

        NSMutableArray *itemList = [[NSMutableArray alloc] init];

        for (MPMediaItem *item in collection.items) {
            NSURL *url_item = [item valueForProperty:MPMediaItemPropertyAssetURL];
            if (url_item == NULL) {
                drm_count++;
            } else {
                [itemList addObject:item];
            }
        }

        if (itemList.count > 0) {
            MPMediaItemCollection *collection_new = [MPMediaItemCollection collectionWithItems:itemList];

            [albumList addObject:collection_new];

            NSString *keyword;
            switch (current_category) {
                case CATEGORY_ALBUM:
                    keyword = albumtTitle;
                    break;
                case CATEGORY_PLAYLIST:
                    keyword = [collection valueForProperty:MPMediaPlaylistPropertyName];
                    break;
                case CATEGORY_ARTIST:
                    keyword = [representativeItem valueForProperty:MPMediaItemPropertyArtist];
                    break;

                default:
                    break;
            }

            [key addObject:[NSString stringWithFormat:@"%@",keyword]];
        }
	}

    dataSource = [NSDictionary dictionaryWithObjects:albumList forKeys:key];
}

※ SelectViewControllerは、UITableViewControllerを継承したカスタムクラスです。

上に書いた関数「setMusicCategory」は、UITableViewに選択させる再生リストを表示するためのdataSourceを作成するための関数で、ボタンなどによって再生リストの種類(プレイリスト/アルバム/アーティスト)を切り換えたときに・・・

current_category = CATEGORY_ALBUM;
[self setMusicCategory];
[self.myTableView reloadData];

※self.myTableViewは、UITableViewControllerに含まれるUITableView。

みたいな感じで使います。

・iCloudのデータを除外する
これはフィルタ使って弾けるので、上のソースのこの部分。

//iCloudデータをフィルタリングで除外
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];

・DRM付きの曲を除外する
1つのアルバムで、DRMつきとDRMなしが混在することはないと思われるけど、プレイリストは任意に作れるので、引っ張ってきたMPMediaItemCollectionから、MPMediaItemのMPMediaItemPropertyAssetURLをひとつずつチェックしていくしかなさそうです。

for (MPMediaItem *item in collection.items) {
    NSURL *url_item = [item valueForProperty:MPMediaItemPropertyAssetURL];
    if (url_item == NULL) {
	drm_count++;
    } else {
	[itemList addObject:item];
    }
}

一応、コレで問題なさそうです。もっと効率的な方法があったら教えてください。

【Objective-C】AVQeuePlayerでMPMediaItemPropertyAssetURLがnullになる

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

AVPlayer(AVQeuePlayer)でiPodライブラリの音楽を再生するは、MPMediaItemからMPMediaItemPropertyAssetURLを取りだして、AVPlayerItemを生成する必要がある。

MPMediaItemPropertyAssetURL
A URL pointing to the media item, from which an AVAsset object (or other URL-based AV Foundation object) can be created, with any options as desired. Value is an NSURL object.

The URL has the custom scheme of ipod-library. For example, a URL might look like this:

ipod-library://item/item.m4a?id=12345
Usage of the URL outside of the AV Foundation framework is not supported.

Available in iOS 4.0 and later.

要はAV Foundation frameworkであるAVPlayer(AVQeuePlayer)で使える形に変換する必要があるってことですね。実際にはこんな感じ。

NSMutableArray *qItems;

for (MPMediaItem *mpItem in collection.items) {
	NSURL *url = [mpItem valueForProperty:MPMediaItemPropertyAssetURL];
	AVPlayerItem *api = [[AVPlayerItem alloc] initWithURL:url];
	[qItems addObject:api];
}

※collectionは、MPMediaItemCollection

ただこれでテストしていたら再生できない曲があって、いろいろ調べてみたら、MPMediaItemPropertyAssetURLがnullだった。

・昔、iTunesで購入したDRM付きの曲。
・iCloudにあって、iPhoneにダウンロードされていない曲。

この2つはURLがNULLになるらしい。

【参考】
blog.polikeiji.net: MPMediaItemPropertyAssetURLがnullのとき
iphone – How to detect if an MPMediaItem represents a DRM-protected audio track on iOS – Stack Overflow
ios4 – MPMediaItemPropertyAssetURL becomes null when Using MPMediaItems to play songs – Stack Overflow

で、こんな記事を見つけたので、再ダウンロードしてみたけど、DRMが外れない曲がある。まあ提供元が対応してなければ、そのままだよね。仮にDRMを外すことができたとしても、その行為をユーザにお願いするのはどうか?という問題もあるし。

iTunes Storeで購入したDRM楽曲は、再ダウンロードするとDRMフリーになる? | iPod love

ということで、この2種類は再生リストから外す方向で処理を考えてみます(続く)。

【Objective-C】AVPlayer関連リンク

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

とりあえずiPodライブラリを再生する音楽プレーヤー的なものを考えていたら、音の再生には3種類あるらしい。

・AVAudioPlayerクラス
・MPMusicPlayerControllerクラス
・AVPlayerクラス

【参考】Slow Days » Blog Archive » Core Audioざっくり覚え書き

今回考えているのは・・・

・バックグラウンド再生が可能(標準のミュージックプレイヤーと同じ)
・iPodライブラリ(自分がiPhoneに入れている音楽)の再生が可能

という前提なので、これだと「AVPlayerクラス」一択になるらしいので、関連リンクをメモしておきます。
(厳密には今回は、AVPlayerのサブクラスであるAVQueuePlayerを使っています)

Background Modes in iOS Tutorial
(これのPlaying Audioの箇所が役に立ちました)

かゆいところに手が届きそうなAVFoundationとMediaPlayerによる音楽再生アプリ作成メモ | Qiita
(ロック画面へアプリから情報表示など、知らなかったことばかり)

ios – Possible to insert item at top of queue using AVQueuePlayer? – Stack Overflow
(AVQueuePlayerで巻き戻しメソッドがないけどどうするの?キュー(Queue)なので、再生終わると配列から削除されます。で、原始的だけど、1つ前のAVPlayerItemを配列に入れて、順序を操作することで対応出来ます。ただ後ろに追加しかできないので、ちょっと面倒。)

AVPlayer Class Reference

AVQueuePlayer Class Reference

AVPlayerItem Class Reference

Objective-CのKVO(キー値監視)の使い方 | blog.nambo.jp

【2014.11.2 追記】
iOS の動画再生を試す | アカベコマイリ
(「AVPlayer の初期化」あたりは音楽でも同じ)

既にいくつか試してるのですが、その辺の記事は追々。

【Xcode】iOS用のOCRライブラリ

あるんですね。有名なのは、この二つらしいです。

tesseract-ocr
nhocr

ググってみると、tesseract-ocrを使ったサンプルが多いようです。

iOSで日本語OCR!サンプルアプリ構築編〜iOS SDK 6.1 + tesseract-ocr 3.02〜 | Developers.IO
tesseract-orc で 自作プログラムに画像の文字を読ませてみた – Kikuchy’s Second Memory

一応、メモとして。

【Objective-C】MPMediaQueryで取得される曲情報にはiCloudも含まれる

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

MPMusicPlayerControllerでミュージックライブラリの再生をしていて、一覧に出てくるのに再生できない曲があって「何だろう?プログラムの問題?」といろいろ調べていたら・・・

MPMediaQueryで取得される曲情報にはiCloudも含まれる

ということらしい。リストにはあるけど、曲データがないので再生できない。というか、[MPMusicPlayerController play] ではエラーにはならずに再生した途端に「MPMusicPlayerControllerPlaybackStateDidChangeNotification」が返ってきて、MPMediaItem == NULL で再生終了になる。

※問題の出たアルバムは1曲しか含まれていなかったので。

具体的には、MPMediaItem に MPMediaItemPropertyIsCloudItem というプロパティがあります。これでiCloudにあるのかどうか判定できます。

・MPMediaItemPropertyIsCloudItem
A Boolean value indicating whether the media item is an iCloud item (YES), or not (NO). A media item is considered an iCloud item if it is available via iTunes Match and is not already stored on the device. Value is an NSNumber object representing a BOOL data type.

Available in iOS 6.0 and later.

・元々のコードはこんな感じ。

MPMediaQuery *query = [MPMediaQuery albumsQuery];

NSArray *albumlists = query.collections;

for (MPMediaItemCollection *albumlist in albumlists) {
MPMediaItem *representativeItem = [albumlist representativeItem];
NSString *albumtTitle = [representativeItem valueForProperty:MPMediaItemPropertyAlbumTitle];
}

・で、調べてみたら、iCloudのデータを除外するには [MPMediaQuery addFilterPredicate:] ですればOK。

MPMediaQuery *query = [MPMediaQuery albumsQuery];

[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];

NSArray *albumlists = query.collections;

for (MPMediaItemCollection *albumlist in albumlists) {
MPMediaItem *representativeItem = [albumlist representativeItem];
NSString *albumtTitle = [representativeItem valueForProperty:MPMediaItemPropertyAlbumTitle];
}

問題の出た曲データが特定できなくて、iOS純正のミュージックプレイヤーと自作アプリの曲アルバム一覧を比較していて、やっと気づきました。

【参考】
MPMediaItem Class Reference | iOS Developer Library
MPMediaQuary albumsQuery結果からiCloudのみにあるものを除く | 響雲
articles of samekard: iPod library access クラウド音源への対応
MPMediaQuery – ignore songs on Cloud (i.e., iTunes Match)

【Objective-C】音楽再生関連のまとめ

諸々調べているのでメモ。

MPMusicPlayerControllerクラス | Second Flush
MPMediaQuery Class Reference | iOS Developer Library — Pre-Release
iOSで、ミュージックライブラリにアクセスして音楽を再生する | nackpan Blog
音楽プレイヤのメソッド(1) | Second Flush
Objective-C – ARC環境下でAVAudioPlayerを使い柔軟かつシンプルにサウンドを再生する方法 – Qiita
プロフェッショナルプログラマー: iOS 音声再生(AVAudioPlayer)
MPMediaEntity Class Reference | iOS Developer Library
iOS – iTunesみたいに再生中の曲のアートワーク(ジャケット写真)に合わせて背景色と文字色を変える – Qiita
Media Player Framework Reference | iOS Developer Library
AddMusic | iOS Developer Library
AVPlayerDemo | iOS Developer Library
[iOS]AVPlayerを使う | nackpan Blog

【Objective-C】UIPageControlのページ切替タイミング

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

前回の投稿絡みで、自前でUIPageControlを追加したときに、ページ数表示更新のタイミングでハマったのでメモ。

要は・・・

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed;
{
  ...
}

このページ切替アニメーションの終了タイミングで更新するのが吉、ということ。

自前のスクリプト載せようと思ったら、要らなくなったんで消した後でした・・・。

【参考】
これからiOS(iPhone)アプリを開発する人々へ: UIPageViewControllerについて