月別アーカイブ: 2010年7月

BigStopWatch v2.4.1 リリース

BigStopWatchのv2.4.1が公開されました。

一つ前のv2.4でRetina対応にしたためiOS4.0以降でしかインストールできないようにしていたのですが、iPhone 2GなどiOS4.0にアップグレードできないデバイスを使っている方から3.1.3にも対応してほしいという要望があったので修正しました。他に変更点はありません。

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を無視する必要がありそうです。

BigStopWatch v2.4公開されました

BigStopWatchのv2.4が公開されました。ダウンロードはこちらから。

主な変更点はRetina対応で、iPhone 4をお持ちの方はより美しく表示されます。今までもアンチエイリアスがかかるようにして文字や線の美しさにはこだわっていたのですが、Retina対応することで一段クオリティの高い美しさになってます。iPhone 4をお持ちの方はぜひアップデートをお願いします。

あとはバッテリーインジケータを標準のものに近い形に変えてたり、タッチしたときのアニメーションを変えたりくらいですね。カウントダウンの時に自分的に意図していない表示になっていたところとかも修正したりしてます。

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になってはじめて情報が取得できるという感じです。それなのに、その初期化終わった通知とかが無さそうなのが、いまいちわからないところです。