2012年9月28日金曜日

Cocoaアプリケーション間の通信について

OS X のコアはBSDです.なので,Cocoaアプリケーション間の通信にも,ちょっと反則だけれども,いかにもUNIXな方法が使えます.

Cocoaアプリケーション間の通信は,Finder関係なら NSWorkspace を,一般的なイベント送信なら AppleScript のメカニズムを,本格的なオブジェクト間通信なら Objective-C のオブジェクト間メッセージング機能を使うことが望ましいとされています.というより,Objective-C はオブジェクト間のメッセージングを初めから想定して設計されています.

とはいえ、面倒臭い時もありますよね.

そんなときはパイプを使いましょう.まずホームディレクトリにでもコマンドラインから mkfifo datapipe としてパイプを作って下さい.

あとは,メッセージを受け取りたいアプリケーションから,ノンブロッキングモードでdatapipeをopenします.ついで,ランループの何処かでreadが1以上を返す限りdatapipeからデータを読み出します.

メッセージを送りたいアプリケーションは,datapipeをノンブロッキングモードでopenし,適当にwriteします.先にデータ受け取り側が起動していないと,writeは異常終了しますぞ.

2012年9月20日木曜日

2012年8月15日水曜日

OS X v10.8 からのメモリ管理

 Mac向けの開発言語として OS X v10.5 (Leopard) から Objective-C に代わって Objective-C 2.0 が導入されました.Objective-C 2.0 は Objective-C を大幅に拡張したものですが,その中の最大のものはガベージコレクタの導入でしょう.もはや retain/release は考えなくてよくなったのです.

その後登場した iOS (当時は iPhone OS)では Objective-C 2.0 が採用されましたがガベージコレクタは引き継がれませんでした.プログラマは手動で retain/release しなければならないのです.(リファレンスカウンタが使えるため C++ の new/delete よりははるかに安全ですが.)

OS X v10.8 (Mountain Lion) になり,この Objective-C 2.0 ガベージコレクタ機能は非推奨になりました.iOSとの整合性を考えると,iOSにガベージコレクタを搭載するか,OS X から再びガベージコレクタを取り除くかですが,アップルは後者を選択しました.(プログラマから見るとやや驚きですよね.)

そのかわり,Xcode 4.2 からコンパイル時に retain/release を自動的に挿入する機能が付きました.アップルはこの技術を Automatic Reference Count (ARC) と呼んでいます.

ガベージコレクタを前提にしたコードは変更の必要は(原則として)ありません.一方,Objective-C 1.0 時代のコードはおそらく NSAutoreleasePool を多用していると思いますが,この NSAutoreleasePool はサポートされなくなったため,書き直しが必要です.(おそらくいちばん簡単なコード移行方法は,retain/release を削除してしまうことです.)

2012年8月5日日曜日

OS X v10.8 からの新フレームワーク

Apple の資料によれば,Mountain Lion から八つのフレームワークが新たに加わったようです.iOS 5 には搭載されていたフレームワークがやっと OS X にもやってきました.


Accounts (Accounts.framework). シングルサインオン(single sign on)機能を提供します.シングルサインオンを使うことで,アプリケーション毎にログイン情報を表示する必要がなくなり,ユーザエクスペリエンスが向上します.アカウントの認証処理を管理することでアプリの開発が容易になります.

Audio Video Bridging (AudioVideoBridging.framework). このフレームワークはオーディオ・ビデオ・ブリッジ(AVB)をサポートします.AVB ネットワーク上のメディアストリームのサービスの質を高め,遅延や通信帯域の保証をします.

Event Kit (EventKit.framework). ユーザのカレンダイベントやリマインダ項目にアクセスするインタフェースを提供します.このフレームワークの API を使って,既存のイベントを取得したり,新たにユーザカレンダにイベントを加えられます.Event Kit API で作成されたイベントは他のデバイスの CalDAV や Exchange calendars に伝達されます.カレンダには配信のタイミングルールを設定できるアラームを含めることができます.
また,Event Kit API を使うことで,リマインダリストにアクセスしたり,新規リマインダを作成したり,アラームを加えたり,期間や開始日を指定したり,リマインダに完了マークを付けることができます.
Note. Calendar Store framework (CalendarStore.framework) は OS X v10.8 では非推奨になっていますので,Event Kit framework を使いましょう.

Game Kit (GameKit.framework). これは開発アプリを Game Center に参加させるための API を提供しています.API を使い,ゲームアプリ内でリーダボードを表示したり,ユーザにスコアの共有やマルチプレイヤのゲームを行う機会を与えます.

GL Kit (GLKit.framework). このフレームワークは,OpenGL アプリの開発を容易にするために,一般的な関数やクラスのライブラリを提供します.さらに,最適数値計算,テクスチャ読み込みの簡易化,シェーダ効果の実装を行う API を含んでいます.

Scene  Kit (SceneKit.framework). これは 3D シーンの読み込み,操作,描画を効果的に行うために使うことができる高度な Objective-C の API を提供します.Scene Kit を使って Digital Asset Exchange files (.dae files) を書き出したり,3D シーンで定義されたオブジェクトや光,カメラ,幾何的データにアクセスできます.

Social (Social.framework). Socail framework はユーザに代わって操作を実行するサポートされたソーシャルネットワーキングサービスにリクエストを送信する API を提供します.また,開発アプリ内で,ソーシャルネットワーキングアカウントを統合するための情報検索に利用できます.

Video Toolbox (VideoToolbox.framework). これは QuickTime Image Compression Manager を 64 bit で置き換えたものを含んでいます.Video Toolbox API はビデオの圧縮・非圧縮やCore Video ピクセルバッファにあるラスタイメージフォーマット間の変換のサービスを提供します.


各 framework には Programming Guide があり,詳細を知りたい場合はそちらを参考にしてください.

2012年7月11日水曜日

AV Foundation 4

AV Foundation の解説文書(日本語)がADCに置かれています.iOS向けですが,ほぼ OS X でも通用する内容です.ただし5ページの図の UI Kit, Media Player は OS X にはありません.OS X では UI Kit の代わりに App Kit を使います.また OS X 10.7 には Media Player に相当するものはありません.(同じ目的の QT Kit は下位レイヤーとして AV Foundation を使わないため,今後 Media Player が OS X に移植されることが期待されます.)

なお,AV Foundation を使うためには,Cocoa フレームワークの約束事,key-value コーディング,ブロック構文,Core Animation の知識が必要になります.今後必要になり次第説明をします.

2012年5月17日木曜日

AV Foundation 3

AV Foundation はこれまでの QuickTime や QuickTime Kit と違い,レンダリング先に Core Animation のレイヤーと呼ばれるフレームバッファを想定しています.そこで,Core Animation について勉強しましょう.

Core Animation は Mac OS X Leopard から導入された,主に2Dアニメーション用の高レベルグラフィックAPIです.雰囲気としては,Core Graphics と OpenGL の間に入って,複数の Core Graphics ドローイングをキャンバス上で様々に重ねられるAPIといったところです.Quartz Compositor が複数ウィンドウの合成を担当しているのに対し,Core Animation はひとつのウィンドウのなかの様々なドローイング部品を合成するのに用いられます.

Core Animation で基本となる概念は「レイヤー」です.ひとつのグラフィックは,複数の,位置や透明度が様々に異なるレイヤーの重ねあわせで表現されます.そして,各レイヤーの位置,透明度,ズーム,などなどのプロパティ(これは Objective-C プロパティで表されます)を変更するだけで,自動的にレイヤーがアニメーションします.

OS X, iOS 開発者の間で大変有名な木下誠さんが詳しい解説を書いて下さっています.是非お読み下さい.

2012年5月13日日曜日

Core Video Task (6)

Core Video の役割は次の三つ.
1) Obtaing Frames Using the Display Link
2) Manipulationg Frames
3) Using Core Image Filtering With Core Video 

今回は 3) Using Core Image Filtering With Core Video です.

あるビデオにフィルタリング効果を適用したい場合,おおよそ Core Image filter を適用する方が,自分でコーディングするよりシンプルなことが多いらしいです.そのようにするには,対象となるフレームを Core Image 画像として扱う必要があります.

Core Image CIFilter のメソッド filterWithName: を使って Core Image filter を以下のように読み込みます.
effectFilter = [[CIFilter filterWithName:@"CILineScreen"] retain];
[effectFilter setDefaults];

フィルタを読み込んだら,そのフィルタを使って対象の画像を処理します. Listing 2-9 にどのように適用するかを示してあります. これは, Core Image context に描く前に入力画像をフィルタリングしている以外は Listing 2-8 と同じものです.

Listing 2-9  Applying a Core Image filter to an image
- (void)renderCurrentFrameWithFilter
{
    NSRect      frame = [self frame];
 
    if(currentFrame)
    {
        CGRect      imageRect;
        CIImage     *inputImage, *outputImage;
 
        inputImage = [CIImage imageWithCVImageBuffer:currentFrame];
 
        imageRect = [inputImage extent];
        [effectFilter setValue:inputImage forKey:@"inputImage"];// 1
        [[[NSGraphicsContext currentContext] CIContext]// 2
            drawImage:[effectFilter valueForKey:@"outputImage"]
            atPoint:CGPointMake((int)
                ((frame.size.width - imageRect.size.width) * 0.5),
                (int)((frame.size.height - imageRect.size.height) * 0.5))
            fromRect:imageRect];
 
    }
    QTVisualContextTask(qtVisualContext);
}
コードの説明です.

  1. 対象フレームに CIImage フィルタをセットします
  2. その指定されたフィルタで描画をします


以上です.

2012年5月9日水曜日

Core Video Task (5)

Core Video の役割は次の三つ.
1) Obtaing Frames Using the Display Link
2) Manipulationg Frames
3) Using Core Image Filtering With Core Video

今回は 2) Manipulationg Frames です.

これまで Display link を使ってフレームを得るまでの流れを見てきました.この後どのように料理するかは皆様方それぞれの一存で決まります.OpenGL texture として取得すれば,OpenGL の呼び出し(コール)を使って操作できます. Listing 2-7 では NSView の drawRect メソッドをオーバーライドしてそのビューに OpenGL を描画する方法を示しています.

Listing 2-7  Displaying OpenGL in a rectangle
- (void)drawRect:(NSRect)theRect
{
    [lock lock];    // 1
    NSRect      frame = [self frame];
    NSRect      bounds = [self bounds];
 
    [[self openGLContext] makeCurrentContext];// 2
    if(needsReshape)// 3
    {
        GLfloat     minX, minY, maxX, maxY;
 
        minX = NSMinX(bounds);
        minY = NSMinY(bounds);
        maxX = NSMaxX(bounds);
        maxY = NSMaxY(bounds);
 
        [self update];
 
        if(NSIsEmptyRect([self visibleRect])) // 4
        {
            glViewport(0, 0, 1, 1);
        } else {
            glViewport(0, 0,  frame.size.width ,frame.size.height);
        }
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(minX, maxX, minY, maxY, -1.0, 1.0);
 
        needsReshape = NO;
    }
 
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
 
    if(!currentFrame)// 5
        [self updateCurrentFrame];
    [self renderCurrentFrame];      // 6
    glFlush();// 7
    [lock unlock];// 8
}
では,説明をしていきます.

  1. スレッドをロックします. OpenGL はスレッドセーフではないため1スレッドです.
  2. レンダリングのために OpenGL をコンテキストにセットする.
  3. 描画領域のリサイズ時に, OpenGL コンテキストの更新を行う.
  4. ビューがビジブルなら, OpenGL コンテキストを新たなビュー領域に表す.インビジブルなら表さない.
  5. 現在のフレームが無ければ,再度フレームを取得する.これはリサイズ時に呼び出されることになる.
  6. 現在のフレームを OpenGL コンテキストに描画する. renderCurrentFrame メソッドはカスタムフレームコード(次参照)を保持します.
  7. Flush します.これでフレームは適切なタイミングでスクリーンに表示されます.
  8. スレッドをアンロックします.
renderCurrentFrame は皆様がフレームに対して行いたい処理を含められます. Listing 2-8 は簡単なサンプルで,どのように実装したらよいかを示しています.この例では, Core Image を使って OpenGL のコンテキストに描いています.
Listing 2-8  Drawing a frame
- (void)renderCurrentFrame
{
    NSRect      frame = [self frame];
 
    if(currentFrame)
    {
        CGRect      imageRect;
        CIImage     *inputImage;
 
        inputImage = [CIImage imageWithCVImageBuffer:currentFrame];// 1
 
        imageRect = [inputImage extent];// 2
        [ciContext drawImage:inputImage // 3
                atPoint:CGPointMake(
                (int)((frame.size.width - imageRect.size.width) * 0.5),
                (int)((frame.size.height - imageRect.size.height) * 0.5))
                fromRect:imageRect];
 
    }
    QTVisualContextTask(qtVisualContext);// 4
}
このコードは,以下のような手順です.

  1. 現在のフレームから Core Image 画像を作成します. Core Image のメソッド ImageWithCVImageBuffer はどんなイメージバッファタイプ(ピクセルバッファ, OpenGL バッファ, OpenGL コンテキスト)からも Core Image 画像を作り出します.
  2. その画像の境界矩形を取得します.
  3. Core Image コンテキストにその画像を描画します.drawImage:atPoint:fromRect メソッドは特定の場所の特定の visual コンテキストにそのフレームを描きます.
    描く前に, OpenGL コンテキストと同じ領域を参照する Core Image コンテキストを作成する必要があります.そうすることで Core Image API でその領域に描け,そして OpenGL を使ってそれを表示できます.例えば, Listing 2-3 の OpenGL コンテキスト作成後に次のコードを追加します.
    /* Create CGColorSpaceRef */
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
     
    /* Create CIContext */
    ciContext = [[CIContext contextWithCGLContext:
                    (CGLContextObj)[[self openGLContext] CGLContextObj]
                    pixelFormat:(CGLPixelFormatObj)
                    [[self pixelFormat] CGLPixelFormatObj]
                    options:[NSDictionary dictionaryWithObjectsAndKeys:
                    (id)colorSpace,kCIContextOutputColorSpace,
                    (id)colorSpace,kCIContextWorkingColorSpace,nil]] retain];
    CGColorSpaceRelease(colorSpace);
    Core Image に関しては, Core Image Programming Guide をご覧下さい.
  4. うまく行くように時間を与えます.描画メソッドを通すたびに, QTVisualContextTask を呼ばなければならないようです.

少々長かったですが, Core Video で取得した画像に対して, Core Image を効果的に用いられる術が分かりました.次回は, Core Image のフィルタを使った表現になります.



2012年5月2日水曜日

Core Video Task (4)

Core Video の役割は次の三つ.
1) Obtaing Frames Using the Display Link
2) Manipulationg Frames
3) Using Core Image Filtering With Core Video

今回は 1) - 3 Implementing the Display Link Output Callback Function です.

Implementing the Display Link Output Callback Function
display link が動作している間,フレームを準備する必要があるたびにアプリケーションに定期的にコールバックします.コールバック関数は OpenGL のテクスチャとして指定されたビデオソースからフレームを取得し,それをスクリーンへと表示する必要があります.

もしオブジェクト指向であれば,以下の Listing 2 - 4, Listing 2 - 5 のようなメソッドを呼び出すことになります.

Listing 2-4  Invoking a method from your callback
CVReturn MyDisplayLinkCallback (
    CVDisplayLinkRef displayLink,
    const CVTimeStamp *inNow,
    const CVTimeStamp *inOutputTime,
    CVOptionFlags flagsIn,
    CVOptionFlags *flagsOut,
    void *displayLinkContext)
{
 CVReturn error =
        [(MyVideoView*) displayLinkContext displayFrame:inOutputTime];
 return error;
}
この例では,コールバック関数は MyVideoView class に実装された displayFrame メソッドを単に呼び出します.このクラスのインスタンスは displayLinkContext parameter にあるコールバックに渡されます.ちなみに, MyVideoView class は NSOpenGLView class のサブクラスである必要があります.
Listing 2-5  Implementing the displayFrame method
- (CVReturn)displayFrame:(const CVTimeStamp *)timeStamp
{
    CVReturn rv = kCVReturnError;
    NSAutoreleasePool *pool;
 
    pool = [[NSAutoreleasePool alloc] init];
    if([self getFrameForTime:timeStamp])
    {
        [self drawRect:NSZeroRect];
        rv = kCVReturnSuccess;
    }
    else
    {
       rv = kCVReturnError;
    }
    [pool release];
    return rv;
}
 

Listing 2 - 6 では, QuickTime からビデオフレームを取得するときに,どのように getFrameForTime メソッドを実装するかを示しています.

Listing 2-6  Obtaining frames from QuickTime
- (BOOL)getFrameForTime:(const CVTimeStamp*)syncTimeStamp
{
    CVOpenGLTextureRef      newTextureRef = NULL;
 
    QTVisualContextTask(qtVisualContext);// 1
    if(QTVisualContextIsNewImageAvailable(qtVisualContext, syncTimeStamp))// 2
    {
        QTVisualContextCopyImageForTime(qtVisualContext, NULL, syncTimeStamp,// 3
                 &newTextureRef);
 
        CVOpenGLTextureRelease(currentFrame);// 4
        currentFrame = newTextureRef;
 
        CVOpenGLTextureGetCleanTexCoords (// 5
                    currentFrame, lowerLeft, lowerRight, upperRight, upperLeft);
        return YES; // we got a frame from QT
    }
    else
    {
        //NSLog(@"No Frame ready");
    }
    return NO;  // no frame available
}
以下のような処理になっています.

  1. 要求される管理維持が実行できるように,コンテキストに時間を与えます.各フレームを取得する前にこの関数を呼ばなければなりません.
  2. 与えられた時間に,新しいフレームが利用可能かどうかを確認します.要求された時間は現在の時間ではなく,フレームが表示されるであろう未来の時間を表します.
  3. もしフレームが利用可能であれば,それを OpenGL のテクスチャとして取得します.QuickTime 関数の QTVisualContextCopyImageForTime は QuickTime から任意の Core Video イメージバッファタイプとしてフレームを取得します.
  4. 現在のフレームを解放し,新しく取得したテクスチャをセットします.解放しなければメモリリーが起きてしまうようです.
  5. テクスチャの表示部の座標設定を行う.主に,テクスチャの境界 bounds を設定する.