2013年10月21日月曜日

NVIDIA G-SYNC

ディスプレイのリフレッシュは垂直同期 (V-Sync) 信号に合わせるのが普通だ.そのため,フレーム描画も V-Sync 前に終わらせないといけない.そのタイミングを計るのが Core Video のひとつの機能なわけだが,NVIDIAが発表した G-SYNC はリフレッシュのタイミングをGPU側で柔軟に制御しましょうというもの.

ただし,単純な話がGenLockであり,従前の QUADRO SYNC とあまり変わらないのかもしれない.

2013年9月16日月曜日

CGFloatとNSIntegerのサイズ

iOSは当面32-bitと64-bitが混在するでしょう.そこで,よく使うCGFloatとNSIntegerのサイズをメモしておきます.

CGFloatNSInteger
iOS 32-bitfloat (32-bit)long (32-bit)
iOS 64-bitdouble (64-bit)long (64-bit)

2013年7月10日水曜日

Xcode 5 bots

Xcode 5 に搭載される新機能のひとつにボットがあります.バックグラウンドでビルドしたり,テストを走らせたりするボットが導入されるそうなのですが,コードの静的解析を行なって怪しい部分を見つけてくれるボットも登場するそうです.

2013年6月26日水曜日

OS X 10.9 (Mavericks) でついに QuickTime にさようならを

OS X 10.9 (Mavericks) ではついに QuickTime Kit にさようならします.アップルのサイトには次のように書かれています.

Now you can easily play modern media formats within your Mac app with AV Kit. With very little code, playback can be embedded into a standard Cocoa view, along with navigation controls, chapter selection, and support for subtitles. This easy-to-use framework is built on AV Foundation and is an ideal starting point for transitioning your QuickTime-based apps to the latest media technologies.

新しいメディアフォーマットもAVKitを使って簡単にプレイできるようになるよ.僅かなコードだけで,標準的なCocoaビューにナビゲーション,チャプタ選択,字幕機能を持ったプレイバック(ビュー)を貼り付けられるんだ.この扱いやすいフレームワークは AV Foundation 上に構築されていて,古い QuickTime ベースのアプリを移植するにも便利だよ.*

* アップルはこれまで QuickTime ベースのアプリを AV Foundation ベースへと移行するよう推奨していました.

2013年6月7日金曜日

Graphic Tools for Xcode

Quartz Composer を含む Graphic Tools for Xcode の入手方法をこちらに記載しました.今のところ Graphic Tools に含まれているのは

  • CI Filter Browser (Dashboardウィジェット)
  • Icon Composer
  • OpenGL Driver Monitor
  • OpenGL Profiler
  • OpenGL Shader Builder
  • Pixie
  • Quartz Composer
  • Quartz Composer Visualizer
  • Quartz Debug

です.

2013年6月5日水曜日

AVGreenScreenPlayer 5

前回紹介した Objective-C modern syntax にも関連しますが,AVGreenScreenPlayer の GSPlayerView クラスではインスタンス変数を定義するのに,実装ファイル(.mファイル)で GSPlayerView クラスに無名カテゴリを追加してそこでインスタンス変数を定義するというやや回りくどいことをしています.

現在は実装部に直接インスタンス変数を記述できますので,このような回りくどい書き方は必要なくなりました.具体的には,


@interface GSPlayerView ()
{
  // ...
  CVDisplayLinkRef _displayLink;
  // ...
}
@end

@implementation GSPlayerView
//...
@end

としていたところを,

@implementation GSPlayerView
{
  // ...
  CVDisplayLinkRef _displayLink;
  // ...
}
//...
@end


と出来ます.

2013年5月29日水曜日

Objective-C の新文法 (modern syntax) について


Objective-C の仕様は近年急激に変化しています.

  • Objective-C 2.0 (2006, Leopard)
  • Objective-C 2.0 with Blocks (2009, Snow Leopard)
  • Modern Objective-C (2011, Lion)

また,ランタイムのほうも2005年のTigerで一段落したかと思いきや,その後ガーベージコレクタを入れたり非推奨にしたりとめまぐるしく仕様が変わっています.ここらへんは OS X だけでなく iOS とも歩調を合わせるために致し方の無いことかもしれません.

さて,Lionから導入された Objective-C の modern syntax ですが,この新文法を使うことにより非常に簡潔にプログラムが書けるようになりました.

極端な例を挙げますと,

#import <Foundation/Foundation.h>

@interface PointObject: NSObject
@property float x;
@property float y;
@end

@implementation PointObject
@end

これだけで,2次元ベクトルクラスが書けます.次のように使います.

int main() {
  PointObject *p = [[PointObject alloc] init];
  p.x = 100;
  p.y = 200;
  NSLog(@"x: %f, y: %f", p.x, p.y);
  return 0;
}

もちろん Key-Value-Observing に対応します.元の PointObject クラスに一切触れること無く,プロパティの変更を外部に通知させることが出来ます.

#import <Foundation/Foundation.h>

@interface PointObject: NSObject
@property float x;
@property float y;
@end

@implementation PointObject
@end

@interface PointObserver: NSObject
@end

@implementation PointObserver
- (void)observeValueForKeyPath: (NSString *)keyPath
                      ofObject: (id)object
                        change: (NSDictionary *)change
                       context: (void *)context
{
  PointObject *p = (PointObject *)object;
  NSLog(@"x: %f", p.x);
}
@end

int main() {
  PointObject *p = [[PointObject alloc] init];
  PointObserver *o = [[PointObserver alloc] init];
  [p addObserver: o 
      forKeyPath: @"x"
         options: NSKeyValueObservingOptionInitial
         context: nil];
  p.x = 100;
  p.y = 200;
  NSLog(@"x: %f, y: %f", p.x, p.y);
  return 0;
}

実行結果は次のようになります.

2013-05-28 14:29:05.719 test[11088:707] x: 0.000000
2013-05-28 14:29:05.721 test[11088:707] x: 100.000000
2013-05-28 14:29:05.721 test[11088:707] x: 100.000000, y: 200.000000

2013年5月10日金曜日

Core Animation レイヤーに setNeedsDisplay メッセージを送っても drawLayer: inContext: メソッドが起動しない?

脱線続きですが,最近はまったもう一つの例を挙げます.皆さんのコーディング時間を無駄にしないように.

OS X で Core Animation を使ったビューをプログラムしているとします.OS X は iOS とは違いデフォルトではビューの Core Animation がオフになっていますが,xibエディタでビューの Core Animation Layer チェックボックスをオンにするか,ビューに setWantsLayer: メッセージを送ることで Core Animation をオンにできます.

その後,ビュー(NSViewのサブクラス)の更新はビューの drawRect: メソッドではなく,レイヤー(CALayer かそのサブクラス)のデリゲートの drawLayer: inContext: メソッドを使います.また,このメソッドを起動するにはレイヤーに対し setNeedsDisplay メッセージを送ります.(NSView の場合はメッセージにYESというパラメタが必要でしたが,CALayer の場合はパラメタ無しです.)

ただし,レイヤーの大きさがゼロの場合,レイヤーはどれだけ setNeedsDisplay メッセージを受け取っても drawLayer: inContext: メソッドを起動しません.そこで,レイヤーを作成したら必ずframeプロパティを設定しておきましょう.もしビューの中で設定するのであれば,

layer.frame = (CGRect)self.frame;

とすればよいでしょう.CALayer の frame プロパティは CGRect 型,NSView の frame プロパティは NSRect 型ですが,この両者は互換性があるため,そのまま代入できます.

2013年4月29日月曜日

Quartz 2D (App Kit) コードを Core Animation に対応させる


話題から逸れますが,最近筆者がはまったのでメモも兼ねて掲載します.

御存知の通り,OS X の2DグラフィックスAPIはC言語APIである Core Graphics があり,その上に Objective-C API である App Kit があります.Apple はこの両者をもともと Quartz 2D と呼んでいましたが,徐々に Quartz 2D という呼び方はしなくなってきました.また iOS にはそもそも2DグラフィックスAPIとして Core Graphics しか用意されていないこともあり,Quartz 2D の影はどんどん薄くなってきています.

OS X においても,従来の App Kit を使った描画コードにそのまま Core Animation を被せることはできません.しかしながら,従来の App Kit を使った描画コードは Core Animation の1レイヤーとして共存させることは可能です.

そのやり方を解説しておきましょう.

App Kit を使った描画の場合,常套手段として NSView のサブクラスで drawRect: をオーバーライドしますね?このメソッドの中で NSBezierPath クラスなどの stroke メソッドを呼んだりします.例えばこんな感じでしょう.

MyView.h

@interface MyView: NSView {
  …
}

@end

MyView.m

@implementation MyView {
  …
}

- (void)drawRect: (NSRect)rect {
  [[NSColor whiteColor] set];
  NSRectFill(rect);

  NSBezierPath *path = …;
  ...
  [path stroke];
}

イベントハンドラでは NSView クラスの setNeedsDisplay: メソッドを使って drawRect: を間接的に呼び出します.例えばこんな感じです.

- (void)mouseDown: (NSEvent *)event {
  NSPoint locationInView = [self convertPoint: event.locationInWindow fromView: nil];
  ...
  [self setNeedsDisplay: YES];
}

一方 Core Animation を使っている場合 drawRect: は呼んでもらえません.その代わり,レイヤは自身のドローイング時に drawLayer: inContext: を呼び出します.このメソッドはデリゲートがあればデリゲートのものが使われます.

まずは Core Animation を使った場合の基本パタンからおさらいしましょう.

MyView.m

@implementation MyView {
  CALayer *backgroundLayer;
}

- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self) {
    backgroundLayer = [CALayer layer];
    CGColorRef white = CGColorCreateGenericGray(1.0f, 1.0f);
    backgroundLayer.backgroundColor = white;
    CGColorRelease(white);
    [self setLayer: backgroundLayer];
    [self setWantsLayer: YES];
  }
  return self;
}

こんな感じで Core Animation レイヤを初期化します.

まずは MyView クラスに drawLayer: inContext: メソッドを実装しておきましょう.このメソッドは Core Graphics グラフィックスコンテキストを渡してもらえますので,これを使って App Kit のグラフィックスコンテキストとします.

MyView.m

- (void)drawLayer: (CALayer *)layer inContext: (CGContextRef)context {
  NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort: context flipped: NO];
  [NSGraphicsContext saveGraphicsState];
  [NSGraphicsContext setCurrentContext: nsGraphicsContext];

  // ここに App Kit 時代のコードを書く
  NSBezierPath *path = …;
  …
  [path stroke];

  [NSGraphicsContext restoreGraphicsState];
}

次に,このメソッドが backgroundLayer の描画時に呼ばれないといけません.そこで initWithFrame: に以下のようにコードを追加します.

MyView.m

- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self) {
    backgroundLayer = [CALayer layer];
    CGColorRef white = CGColorCreateGenericGray(1.0f, 1.0f);
    backgroundLayer.backgroundColor = white;
    CGColorRelease(white);
    backgroundLayer.delegate = self;
    [self setLayer: backgroundLayer];
    [self setWantsLayer: YES];
  }
  return self;
}

イベントハンドラは次のように書き換えます.

- (void)mouseDown: (NSEvent *)event {
  NSPoint locationInView = [self convertPoint: event.locationInWindow fromView: nil];
  ...
  [backgroundLayer setNeedsDisplay];
}

これで,App Kit 時代のコードが  Core Animation レイヤとして使えるようになりました.

Apple は QuickDraw, QuickTime に続き Quartz 2D も終了させようとしているのかもしれません.でも,Core Animation, Core Image そしてもちろん Core Video は Quartz Core というフレームワークに収められています.しばらく Quartz の名前は残りそうですね.

2013年4月2日火曜日

AVGreenScreenPlayer 4

前回に引き続き,GSPlayerView.m の中身を見て行きましょう.

GSPlayerView クラスにはプライベートなインスタンス変数 _displayLink がありました.これは CVDisplayLinkRef 型で,Core Video が提供する Display Link を表す変数でした.

Display Link は平たく言えばディスプレイのリフレッシュに連動した割り込みでした.

さて,GSPlayerView.m のソースコード上で CVDisplayLinkRef を検索してみましょう.早速

static CVReturn displayLinkCallBack(...);

というプロトタイプ宣言が見つかります.関数定義はソースコードの終わりの方にあります.

displayLinkCallBackの関数定義を見てみると,最後の引数としてvoidポインタdisplayLinkContextを受け取っていますが,これは任意のオブジェクトを渡す効率的な方法がvoidポインタしか無いため仕方なくvoidポインタになっているものです.(そもそもC言語スタイルのコールバックにするためこのような仕様になっているのでしょう.)

関数定義の冒頭で displayLinkContext は GSPlayerView ポインタへキャストされています.つまり,C++で言うところのthisポインタがわたってくるのですね.また,この「thisポインタ」経由で AVPlayerItemVideoOutput ポインタである _playerItemVideoOutput インスタンス変数を取り出しています.

で,関数の中で何をしているのかと見てみると,単に新しい画像があれば適切なタイミングで描画し(キューに入れ),無ければしばらく待つだけです.

つまり,displayLinkCallBack は単にタイミングをとっているだけなんですね.

では実際の描画処理はどこで行われているかというと,displayLinkCallBack 関数から呼び出されている displayPixelBuffer メソッドなのですが,ほとんどの仕事は AVSampleBufferDisplayLayer クラス(の変数 videoLayer)で行われています.

この AVSampleBufferDisplayLayer クラスは OS X 10.8 から追加されたクラスで,Core Animation レイヤーにビデオを流す機能を持っています.かつては QTMovieLayer という QTMovie を再生する Core Animation レイヤーがあったのですが,QuickTime Kit のフェードアウトとともに,こちらも AVFoundation への移行が進んでいます.単にムービーを再生するだけなら AVPlayerLayer を,Core Image フィルタを使うなら AVSampleBufferDisplayLayer を使います.ただし,AVSampleBufferDisplayLayer は新しいクラスのせいか,まだドキュメントが整備されていないようです.

次回はこのあたりのメカニズムを見て行きましょう.

2013年2月25日月曜日

AVGreenScreenPlayer 3

GSPlayerView.m の冒頭で,GSPlayerViewクラスのプライベートなインスタンス変数が定義されています.

特に注目しておきたいのが,

CVDisplayLinkRef _displayLink;

です.CVDisplayLinkRef というのは Core Video Display Link へのリファレンスです.ではこの Core Video Display Link とは何かというと,昔のvsync割り込みに相当するシステムです.その実態は独立したスレッドで,フレームのリフレッシュが必要になるたびに登録されたコールバック関数を呼び出します.

Apple は Core Video をC言語APIとして設計したため,Display Link から呼び出せるのはC言語の関数(つまり普通のコールバック)だけです.Objective-C に慣れてしまうと,継承やデリゲートを使いたくなるところですが,そのようなAPIは用意されていません.

それではあまりにも不便なので,Display Link のコールバック関数はvoidポインタを引数として受け取れます.実際にCocoaアプリから Core Video を使う場合はこのvoidポインタを使ってObjective-Cインスタンスを渡すことになります.

2013年2月13日水曜日

AVGreenScreenPlayer 2

前回の続きです.AVGreenScreenPlayer の中身を見て行きましょう.

画像処理系のサンプルプログラムの読み方の常套手段として,ビューから確認して行きましょう.XIBsフォルダの下にある GSDocument.xib を開いてみましょう.ちなみにXIBは昔のNIBに相当します.つまり,昔のインターフェイスビルダーのファイルですね.このファイルを開くと,ウィジェットの種類と配置を確認することができます.

AVGreenScreenPlayer はドキュメントベースアプリ(複数のウィンドウを開けるアプリ)ですから,XIBファイルは2種類あります.GSDocument.xib がひとつのドキュメント(ひとつのウィンドウ)に対応し,MainMenu.xib がアプリケーション全体に対応します.

さて,GSDocument.xib を開くと,ムービーを再生しているビューが GSPlayerView であることがわかります.そこでViewsフォルダの GSPlayerView.h を開いてみましょう.GSPlayerView.h を見てみると,GSPlayerView は NSView にいくつかのプロパティを追加しただけということが解ります.

追加されたプロパティは

  • AVPlayerItem *playerItem;
  • AVSampleBufferDisplayLayer *videoLayer;
  • NSColor *chromaKeyColor;

です.

すぐに GSPlayerView.m を開くのではなく,まずは追加されたプロパティのクラスについて調べておきましょう.

普通,ムービーを再生するには AVSimplePlayer のように,ムービープレイヤーである AVPlayer とムービーの描画先である AVPlayerLayer をプロパティとして用意しておき,Core Animation のレイヤーとして AVPlayerLayer をビューに挿入するのが王道なのですが,やや様子が異なることを感じられると思います.つまり,GSPlayerView.m ではムービーの再生を AVFoundation に丸投げせず,Core Video と連携して描画しているんだなとあたりをつけられます.

次回は GSPlayerView.m の中身を見て行きましょう.

2013年2月7日木曜日

AVGreenScreenPlayer

少し実践から離れていたので,具体的な画像処理プログラム例としてアップルの AVGreenScreenPlayer を今後しばらく取り上げることにしましょう.AVGreenScreenPlayer は Core Image フィルタを使ってクロマキーイングを行うサンプルコードです.

このサンプルコードは AVPlayer の使い方とともに,Core Image での独自フィルタの実装方法の解説にもなっています.

サンプルコードをダウンロードして,適当なムービーファイルを開いてクロマキーイングをまず試してみてください.

上の例は,青でキーイングした例です.

2013年1月29日火曜日

Mountain Lion (OS X 10.8) から Core Animation が非同期モードをサポート

OS X 10.8 からは Core Animation が非同期モードをサポートしています.NSViewのサブクラスの layerContentsRedrawPolicy プロパティを NSViewLayerContentsRedrawOnSetNeedsDisplay にすることで,ビューのドローイングがより軽量になります.

詳しくは NSView のリファレンスを御覧ください.