全般 – iPhone」カテゴリーアーカイブ

OpenGL ESのRetina対応

OpenGL ESのRetina対応の方法をちょっとメモしておきます。OpenGL以外のRetina対応については@k_katsumiさんの記事「アプリケーションを iPhone 4 の Retina Display に対応するための方法いろいろ」がとても参考になると思います。というか、この記事を見てOpenGLについては書いてなかったので触発されて書くことにしました。

Xcodeの「OpenGL ES Application」で作成したプロジェクトの場合で書きますと、EAGLViewのViewのサイズはRetinaディスプレイでも変わらず320×480です。iOS4からUIView(およびEAGLViewなどのサブクラス)にcontentScaleFactorというメソッドが追加され、この値を変更することでUIViewの中の解像度を変更することができるようになりました。もうひとつ、UIViewがもってるCALayerのcontentsScaleでも解像度を変えることができるようです。デフォルトでは1.0になっていますので、Retinaディスプレイの解像度に合わせるには2.0にします。実際には、UIScreenのscaleからディスプレイのサイズの比率が取得できますので、そこからセットします。

ちょっとやってみましょう。AppDelegateのapplication:didFinishLaunchingWithOptions:メソッドに次のコードを書き加えてください。

glView.contentScaleFactor = [UIScreen mainScreen].scale;

基本、これだけでRetinaディスプレイのiPhone4だと倍の解像度になるのですが、「OpenGL ES Application」のデフォルト状態の描画内容だと効果が全然わからないので、さらにコードを変えてみます。

まず、シェーダーとか使ってるとめんどくさいのでES2Rendererを使わないようにします。以下のようにEAGLViewのinitWithCoderの中の一行を書き換えてください。

renderer = [[ES2Renderer alloc] init];
↓
renderer = nil;

次に、ES1Rendererのrenderメソッドの中の縦に動かしている一行を、高解像度の効果がわかりやすいように回転に変更します。

glTranslatef(0.0f, (GLfloat)(sinf(transY)/2.0f), 0.0f);
↓
glRotatef(transY, 0, 0, 1.0);

コードの変更は以上です。一番最初にAppDelegateに追加したglView.contentScaleFactorの一行のありなしでそれぞれiPhone 4で実行してみると四角形のエッジのスムーズさの違いがわかると思います。iPhone 4実機をもっていなくても、iPhone simulatorのデバイスをiPhone 4にすることで確認できます。

OpenGL的には単純に解像度が倍になりますので、ディスプレイのサイズに依存してアプリを作っていたら結構な修正を余儀なくされると思います。場所がずれたりとか、テクスチャも荒く表示されちゃったりとかすると思います。さらにやっかいなのが、UITouchの位置は2倍にならないってことです。scaleに応じて位置を調整する必要があります。

ちなみに、EAGLViewのcontentScaleFactorをセットするだけで解像度が変わっているのではなく、scaleをセットしてからESRendererのresizeFromLayerメソッドの中の[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]が呼ばれて始めてGLの解像度が変わります。もし、glView生成時でなく、後からscaleを変更したいなんていう場合には、contentScaleFactorをセットしたあとにresizeFromLayerを呼んでください。

あと今、iOS4より前のバージョンもサポートするなら、iPadのiPhoneシミュレータで動くことも考えないといけなそうです。iPadでiPhoneアプリを2倍に表示するとUIScreenのscaleが2.0になってしまいますが、UIViewにはcontentScaleFactorがありません。また、CALayerのcontentsScaleはiPadでも受け付けることができますが、GLの解像度が変わることはありません。プロパティの有無やOSのバージョンなどでscaleを無視する必要がありそうです。

AVPlayerでムービーを再生する方法

AVFoundationといえばOS3.xではAVAudioPlayerとAVAudioRecorderくらいだったのですが、iOS4.0から一気にいろんなものが増えてます。僕が以前予想していたように、オーディオだけじゃなくて映像関係も扱えるように進化していますね。なんかいろいろありすぎて、かつ自分がAudioUnitでやりたいことを置き換えるようなものじゃなさそうなので基本的にはスルーしているのですが、一番手軽で役に立ちそうだったけどちょっと使い方にハマったAVPlayerを取り上げたいと思います。

AVAudioPlayerはサウンドファイルを再生するだけでしたが、AVPlayerはサウンドもムービーも関係なくこれひとつで再生できます。サウンドはファイルパス渡してプレイってするだけでなんとなく鳴ってしまうのであえて説明する必要はないと思いますが、ちょっと使い方を知らないと厄介なのがムービーの再生です。

僕がハマってしまったのは、AppleのAVPlayerLayerのリファレンスに嘘が書いてあるところです。いや嘘というよりも、OS4.0のベータが進んでいくうちに使い方が変わってしまっているのにリファレンスがそのまま残っているというところです。

以下が、AVPlayerLayerのリファレンスに書いてあるコードです。

AVPlayer *player = <#A configured AVPlayer object#>;
CALayer *superlayer = <#Get a CALayer#>;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];

どっかのCALayerのサブレイヤーにAVPlayerLayerをのせろと書いてあります。ですが、この通りにやっても正常にムービーは映し出されないようです。一瞬だけ映像が見えることもありますが、音だけが鳴り、映像が再生されることはありません。最初この状態になったときには、きっとGMの頃には再生できるようになるだろうとおもっていたのですが、GMになっても再生できず、じゃあ別のやり方に変わったんだろうと妄想を膨らませて解決しました。

ではどうするかというと、UIViewのサブクラスを作り、そのlayerClassメソッドをオーバーライドして、UIViewのlayerをAVPlayerLayerに差し替えます。OpenGLのEAGLViewでやっているのと同じ手法です。

さっそく、その方法でムービーを再生するサンプルを作ってみたいと思います。まずは、XcodeでWindow-based Applicationを「AVPlayerTest」とかの名前で作成して、AVFoundationをインポートしてください。

次に、UIViewのサブクラスを作成してください。名前は「AVPlayerView」としておきます。コードを以下のような感じで書き換えてください。

//
//   AVPlayerView.h
//
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface AVPlayerView : UIView {
}
@end
//
//   AVPlayerView.m
//
#import "AVPlayerView.h"
@implementation AVPlayerView
+ (Class)layerClass
{
    return [AVPlayerLayer class];
}
@end

AVPlayerViewを書き換えたら、今度はAVPlayerTestAppDelegateのapplication:didFinishLaunchingWithOptions:メソッドにもろもろセットアップする処理を記述します。以下のようなコードをAppDelegateに書き足してください。

//
//   AVPlayerTestAppDelegate.m
//
#import "AVPlayerView.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [UIApplication sharedApplication].statusBarHidden = YES;
    
    CGRect viewFrame = CGRectMake(0, 0, window.frame.size.height, window.frame.size.width);
    AVPlayerView *view = [[AVPlayerView alloc] initWithFrame:viewFrame];
    view.center = CGPointMake(window.frame.size.width / 2.0, window.frame.size.height / 2.0);
    view.transform = CGAffineTransformMakeRotation(M_PI_2);
    [window addSubview:view];
    [view release];
    
    // ↓m4vのところを再生したいムービーの拡張子に変更してください
    NSArray *paths = [[NSBundle mainBundle] pathsForResourcesOfType:@"m4v" inDirectory:nil];
    NSURL *url = [NSURL fileURLWithPath:[paths objectAtIndex:0]];
    AVPlayer *player = [AVPlayer playerWithURL:url];
    
    AVPlayerLayer *playerLayer = (AVPlayerLayer *)view.layer;
    playerLayer.player = player;
    
    [player play];
    [window makeKeyAndVisible];
    
    return YES;
}

コードはこんなところでしょうか。あとはバンドルになにかひとつムービーを入れてアプリを起動させればムービーが再生されると思います。上のコードではムービーの拡張子をm4vとしているので、違う拡張子のムービーであれば変更してください。

あと、AVPlayerを使ってみて気になったのが、ムービーの元のサイズとかの情報が、AVPlayerを生成したときには取得できなさそうだということです。どうもAVPlayerの中での初期化処理がバックグラウンドで行われているような感じで、それが終わってAVPlayerのstatusがReadyToPlayになってはじめて情報が取得できるという感じです。それなのに、その初期化終わった通知とかが無さそうなのが、いまいちわからないところです。

iOS4.0のオーディオ

iOS4.0がリリースされましたので、個人的に気になった変更点などをまとめておきたいと思います。大々的に告知されていたような目玉機能の羅列ではなく、主にオーディオ周りで気になったところです。

バックグラウンドオーディオ

これはiOS4.0のオーディオ系では一番の改良点と思われます。自分のアプリがバックグラウンドに入っても音を流し続けられることができます。対応しているのはたしかiPhone 3GSとiPod touch第3世代とiPhone 4あたりですね。マルチタスク非対応のデバイスでは残念ながら使うことはできません。

とりあえずバックグラウンドで流せればいいのであれば設定は簡単です。info.plistに「Required background modes」という項目を作って、そこで選べるモードの中から「App plays audio」を選択するだけです。これでバックグラウンドに入ってもAudioUnitなどが止まらずに動き続けてくれます。

appplaysaudio.jpg

しかし、自分のアプリが音を流しっぱなしにできるということは、iPodアプリだけでなく、他のいろいろなアプリも同じようにしてくるということです。iOS4デバイスの中でオーディオの主導権を握る仁義なき戦いが繰り広げられることになります。

そこでやっぱり重要なのがカテゴリの設定です。自分のアプリから音を出すときに、他のアプリの音をどうするか。逆に自分のアプリをバックグラウンドで流すときにどうするのか。OS3.xの時と比べて結構シビアになってくると思います。ガッツリ音を鳴らすようなアプリでもないのに他のアプリの音を消してしまったりしたら、ユーザーさんからクレームが来るかもしれません。

あとバッテリーのことを考えると、アプリがバックグラウンドに入るときに継続して音が鳴らないタイミングであれば、AudioUnitを止めておくことも必要と思われます。まぁ、これはバックグラウンドじゃなくてもそうかもしれませんが、再生するときのレスポンスとかもありますし。

また、オーディオに限りませんが、マルチタスキング対応にしていると基本的にずっとアプリが起動しているような状態になっていますので、ユーザデフォルトが設定アプリから変更されたり、File Sharingでドキュメントフォルダの内容が変更されたりということもあります。自分のアプリがアクティブになったらデータを更新するということも忘れないようにしないといけないでしょう。

オーディオカテゴリが厳格になった?

もしかするとiOS4.0からではないのかもしれませんが、オーディオ関連の挙動がオーディオカテゴリの設定にかなり厳格になっているようです。

たしかOS3.0とかの頃に試していたときにはカテゴリはあくまでルーティングが変わるというような影響でしかなかったのですが、iOS4.0を試していたところ、カテゴリをRecordにするとAudioUnitでアウトプットを生成できなかったり、MediaPlaybackにするとインプットを生成できなかったりします。イレギュラーな事にはカテゴリのオーバーライドで対応しろということでしょうかね。

Accelerate Framework

iOSにもいよいよ念願のAccelerate Frameworkが実装されました。

といっても、MacのAccelerate Frameworkそのままではなく、vecLibのみ。その中でも限定されていて、vDSPとcblasとclapackだけが使えるようです。FFTは使えますし、deq22もあるのでEQも楽に作れそうです。でも、vForceあたり(サイン・コサインとか)は全滅なのが、ちょっと残念。

とりあえずこんなところです。まだ全然チェックしてませんが、iPhone Dev CenterのサンプルコードもどっさりiOS4.0に対応しているみたいです。オーディオ以外や、ちゃんとまとめておきたいことでまた何かエントリを書くかもしれません。

※以下追記 2010/8/1

iPodライブラリからのファイル書き出しが可能に!

この記事を書いたときに気づいてなかったのですが、iPodライブラリから生のデータとして扱えるファイルに書き出すことができるようになりました。くわしい方法はこちらのエントリへどうぞ。

日本語ドキュメントに追加

今日アップルの日本語ドキュメント見たらOS3.0の新機能のものが追加されてました。前からあった2.X用のドキュメントも、そのうち3.0用に直される模様。

iPhone Dev Centerの日本語ドキュメント
※もちろん要アカウント

追加になったのは、

・Apple Push Notificationサービス プログラミングガイド
・Store Kit プログラミングガイド
・Game Kit プログラミングガイド

の3つ。

ちょうどGame Kitでやりたい事があって調べようと思っていたからうれしいです。

同じページに日本語訳されているAudio Session プログラミングガイドは、全iPhoneアプリ開発者必読だと思いますので、ついでにぜひ。ちょこっとでも音を鳴らすなら間違いなく知っていた方が良いです。

時差

iTunes Connect上でのリリース日を、アプリのアップデートが公開された日に設定すると、AppStore上のリリース日もアップデートされた日に更新されるというのは開発者のみなさんなら周知の事実だと思います。ですが、今日2月14日にReady for saleが来た!と思って2月14日に設定したら、アメリカの人からUSのストアにアプリが無いよってメールが来ました。

日本やその他いくつかの国を見てもちゃんとあるのに、USに無いのはどうしてだろうと考えてみたら、アメリカはまだ2月13日だったんですね。自分の場合、審査がおわってReady for saleがくるのは日本時間の午前中なので、一つ前の日に設定するように気をつけなくちゃいけないなと思いました。

ところで、エリカさんの「iPhone デベロッパーズ クックブック」が届きました。先行発売されているのを立ち読みしたときから思っていたのですが、オーディオに関してはAudioUnitもOpenALもまったく触れられていないのが残念でした。いや、触れられていないどころか、全く存在しないような書き方です。逆に、Celestialっていう非公式のAPIを説明していたりして、かなりハックよりの内容ですね。

それより、AppleのiPhone用日本語ドキュメントにCore AudioとCore Animationに関する4つが追加されているようです。少なくともオーディオに関してはエリカ本は決して参考にせず、公式ドキュメントを参考にすることをお進めします。

文字列のイメージを作成する

OpenGL ESで文字列を表示しようと思ったのですが、OpenGL自体にそんな機能は無さそうなので、テクスチャに文字列を描画する方法を調べてみました。

テクスチャをCGContextから作って貼付けるところはDev Centerのサンプルソースを参考にしていただくとして、その前段階でCGImageの文字列を作成するコードです。

CGRect imageRect; //文字列用の画像の大きさ
CGContextRef bitmapContext; //文字列用のコンテキスト
Byte *bitmapBuffer; //コンテキストのバッファ

というのが用意されているとして、以下のような感じです。

memset(bitmapBuffer, 0, imageRect.size.width * imageRect.size.height * 4);
    
CGContextSetRGBFillColor (bitmapContext, 0.0, 0.0, 0.0, 1.0);
CGContextFillRect (bitmapContext, imageRect);
    
UIGraphicsPushContext(bitmapContext);
    
UIFont *tFont = [UIFont systemFontOfSize:40];
[[UIColor whiteColor] set];
[@"Test String 1234567890" drawAtPoint:CGPointMake(0, 0) withFont:tFont];
[@"日本語もOK!" drawAtPoint:CGPointMake(0, 44) withFont:tFont];
    
UIGraphicsPopContext();
    
CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
//ここでテクスチャに描画する
CGImageRelease(image);

NSStringの描画メソッドはコンテキストを指定する事が出来ないので、UIGraphicPushContext()でbitmapContextをカレントのコンテキストにしています。実際にテクスチャに貼付けて表示してみるとこんな感じ。

drawtext.jpg

ちなみに、ランドスケープで左上原点にしてみるってこともやっていたので、こんな位置に表示されています。

これでパフォーマンス的にどうなのかは実際にアプリに組み込んでみないとまだ分かりませんけど。