SSブログ

QTKit Captureを使ってみる - その10 [プログラミング - QTKit Capture]

QTKit Captureとか言いながら表示品質の問題にばかり注目して、QTKitCaptureの使い方を忘れてしまいそう。今回はCore Videoを使ってみる。

何回か前にCore Videoってなんじゃいな、というのをちょっとだけ見た。ディスプレイのリフレッシュに同期した表示ができるフレームワークらしい。

ちょっと試しに使うことにして、ガイドにあるコードの例をほとんどそのまま引き写してやってみよう。ガイドではQuickTimeムービーを読み込んで表示している。ここではその代わりにCore Imageでやったのと同じようにsetVideoBuffer:メソッドがQTCaptureDecompressedVideoOutputから呼ばれてバッファの中身がセットされるようにしよう。 まずインターフェイスのインスタンス変数。

@interface MyVideoView : NSOpenGLView
{
    NSRecursiveLock         *lock;
    CVDisplayLinkRef        displayLink;
    CVImageBufferRef        currentFrame;
    CIContext               *ciContext;
    
    BOOL                    needsReshape;
}
…
@end
それからインプリメンテーション。ちょっと長いけど。

CVReturn MyDisplayLinkCallback (
                                CVDisplayLinkRef displayLink,
                                const CVTimeStamp *inNow,
                                const CVTimeStamp *inOutputTime,
                                CVOptionFlags flagsIn,
                                CVOptionFlags *flagsOut,
                                void *displayLinkContext)
{
    CVReturn error =
    [(MyVideoView*) displayLinkContext displayFrame:inOutputTime];
    return error;
}

@implementation MyVideoView

- (void)awakeFromNib
{
    CVReturn            error = kCVReturnSuccess;
    CGDirectDisplayID   displayID = CGMainDisplayID();// 1
    
    error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLink);// 2
    if(error) {
        NSLog(@"DisplayLink created with error:¥%d", error);
        displayLink = NULL;
        return;
    }
    error = CVDisplayLinkSetOutputCallback(displayLink,// 3
                                           MyDisplayLinkCallback, self);
    CVDisplayLinkStart(displayLink);
    needsReshape = YES;
    currentFrame = NULL;
    ciContext = nil;
}

- (void)setVideoBuffer:(CVImageBufferRef)buffer
{
    [lock lock];
    currentFrame = buffer;
    CVBufferRetain(currentFrame);
    [lock unlock];
}

- (void)setNeedsReshape:(BOOL)yn
{
    needsReshape = yn;
}

- (CVReturn)displayFrame:(const CVTimeStamp *)timeStamp
{
    CVReturn rv = kCVReturnError;
    NSAutoreleasePool *pool;
    
    pool = [[NSAutoreleasePool alloc] init];
    if(currentFrame) {
        [self drawRect:NSZeroRect];
        rv = kCVReturnSuccess;
    }
    else {
        rv = kCVReturnError;
    }
    [pool release];
    return rv;
}

- (void)setFrameSize:(NSSize)newSize
{
    needsReshape = YES;
    [super setFrameSize:newSize];
}

- (BOOL)getFrameForTime:(const CVTimeStamp*)syncTimeStamp
{
    return (currentFrame != nil);
}

MyDisplayLinkCallback()と言う関数がいわゆるコールバック関数で、Cocoaのデリゲートメソッドのように処理が必要になったとき(この場合、ディスプレイの描画サイクルごとに)呼び出される。これは昔のSequenceGrabberでも同じことをやった。引数の最後のvoidポインタはコールバックを登録するCVDisplayLinkSetOutputCallback()の最後の引数の値が渡される。この場合selfを登録しているのでコールバック関数の中で自分の型であるMyVideoViewにキャストして使っている。このへんのやり方は古いMacでよくあった。

コールバック関数の中身は単に自分のメソッドdisplayFrame:を呼んでいるだけ。コールバックは普通のCの関数なので、こうしないと自分のインスタンス変数にアクセスできない。

displayFrame:はgetFrameForTime:という自分のメソッドで描画フレームがあるかどうかを尋ねて、あればdrawRect:を呼んでいる。普通drawRect:は直接呼ばずにsetNeedsDisplay:をセットするだけにしろ、とreferenceにも書いてあるが、ここでは直接呼んでいる。すぐ描画するということか。

getFrameForTime:メソッドは、ガイドの例ではQuickTimeムービーを表示しているのでムービーのタイムスタンプを読んで描画が必要かどうか判断している。ここでは単にフレームバッファの中身があるかどうかだけで判断している。QTCaptureDecompressedVideoOutputでもらってくるデータは垂れ流し(デリゲートで受ける)なのでこっちのコールバックのタイミングとは同期しない。QTCaptureで入力画像を作る側と、Core Video側でそれを表示する側でフレームバッファへのポインタの値から

ポインタの値画像入力側での解釈表示側での解釈
NULL 表示が終わった画像データがまだない
NULL以外 表示ができていない画像データがそろった
のように解釈する。つまり、入力側はポインタの値がNULLになっているのを確認して、新しいバッファのデータを作ってポインタにセットする。表示側はポインタの値がNULLでなければそのデータを表示してポインタの値をNULLにする。ちゃんと@synchronizedなんかで排他制御しておかないと見落としやすいが難しいバグになる。ここではガイドがNSLockを使っていたのでそのままにしている。

drawRect:は

- (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);
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSDictionary    *colorDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                      (id)colorSpace, kCIContextOutputColorSpace,
                                      (id)colorSpace, kCIContextWorkingColorSpace,nil];
        if (ciContext)
            [ciContext release];
        ciContext = [[CIContext contextWithCGLContext:
                      (CGLContextObj)[[self openGLContext] CGLContextObj]
                                          pixelFormat:(CGLPixelFormatObj)
                      [[self pixelFormat] CGLPixelFormatObj]
                                              options:colorDict] retain];
        CGColorSpaceRelease(colorSpace);
        needsReshape = NO;
    }
    
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    [self renderCurrentFrame];      // 6
    glFlush();// 7
    [lock unlock];// 8
}

- (void)renderCurrentFrame
{
    CGRect      frame = NSRectToCGRect([self frame]);
    CGRect      imageRect;
    CIImage     *inputImage;

    if (currentFrame != NULL) {
        inputImage = [CIImage imageWithCVImageBuffer:currentFrame];// 1
        imageRect = [inputImage extent];// 2
        [ciContext drawImage:inputImage // 3
                      inRect:frame
                    fromRect:imageRect];
        CVBufferRelease(currentFrame);
        currentFrame = NULL;
    }
}
@end
OpenGLの関数が並ぶ。うーん、実はぜんぜんよくわかっていない。射影変換のマトリクスを作って表示領域いっぱいになるようにする。その後画像データを描いてフラッシュする、ということをやっているのだろう。

これでとりあえず表示はできた。スクリーンキャプチャの証拠写真。
0906capture.jpg
640×480で表示してアクティビティモニタのCPUの値は25〜30%ぐらい。QTCaptureViewにせまる軽さだった。

ガイドのもとのコードではQuickTimeムービーを設定するところでOpenGLのテクスチャをコンテクストにセットしている。でもそれがなくても動く。よくわからん。

ウィンドウがリサイズされるとsetFrameSize:が呼ばれるけど、このままではクラッシュした。しかもOSを巻き添えにして。なんでなのかわからないし、ディスプレイがディムして「リセットボタンを押してください云々」を見るのは精神衛生上よろしくないので、たびたび試してみるわけにもいかない。OpenGL(と、Mac OSX独自実装のところも含めて)をまじめに勉強してからにしよう。このままではダメね。


nice!(0)  コメント(9)  トラックバック(0) 

nice! 0

コメント 9

Takashi Kosaka

Macの利用者の方でしたらご存じないとは思いますが、KeyHoleTVと
いうアプリケーションの作者です。Mac利用者からKeyHoleTVの移植の要請がたくさんあって、移植を試みている最中です。Linux版のKeyHoleTV(GTK利用)をMacに移植したら、映像を出す処理が異常に遅いことがわかりました。(GTKは,Macのネイティブを使っているフレームワークです。)表示形だけ、SDLというライブラリを使うと、動画はなんとか見れることができましたが、今度は、イベントがとれない問題にあたりました。ビデオ表示の方法を模索中でしたので、大変参考になりました。ありがとうございます。
by Takashi Kosaka (2008-10-10 10:06) 

decafish

コメントありがとうございます。あのKeyHoleTVの作者のかたからコメントいただけるのは光栄です。私のような素人のお勉強が、専門家の方の参考になるとはうれしいです。よろしくお願いします。
by decafish (2008-10-11 13:53) 

maru

Cocoaプログラム初心者です。

一連のQTKit Captureを使ってみる - の記事を拝見して参考にしたいと思うのですが語られてない部分を補完して試すだけの力がまだないので、できればXcodeプロジェクト全体を公開していただけませんでしょうか。
コードを動かしながら追って行きたいです。

一方的なお願いで恐縮ですが、ご検討いただけますと幸いです。
by maru (2010-03-07 14:49) 

decafish

了解しました。実はもうすでに中身をすっかり忘れてしまっているので、とりあえず一番簡単なQTCaptureViewを使っているものを
http://www011.upp.so-net.ne.jp/decafish/CocoaCodes/DVStreamViewer.zip
に置きました。
コメントもないし、エラー処理もいいかげんなのでお役に立てればよいのですが...
問題ありましたら、またコメントにあげていただければと思います。
よろしくお願いします。
by decafish (2010-03-07 17:09) 

maru

早速の対応ありがとうございます。
ダウンロードさせていただきビルド・実行してみました。データサイズ・表示サイズを切り替える機能などは自分で実装したことがないので参考になりそうです。

一番知りたい(わからなくて悩んでいる)のはこの記事(その10)で扱われているCore Videoを使ってOpenGL経由でレンダリングする方法についてなのですが、このエントリのコードを拝見することはできませんでしょうか。


現在複数カメラからの画像を取得して処理を施して表示する、ということを実現したいと考えておりまして、描画速度とできることの自由度を考えると(アップル社提供のドキュメントを眺めて見ての判断ですが)CoreVideoを使うことが選択肢になるのではないかと思い調査しているところです。Core Video Programming GuideではQTMovieからの入力を扱う前提で記事が書かれていて、さらに動く状態のコードが提供されていないので難儀しているところにこちらのブログを見付けました。

> setVideoBuffer:メソッドがQTCaptureDecompressedVideoOutputから呼ばれてバッファの中身がセットされるようにしよう。

このようにさらっと説明されているところから習熟した方には自明なものなんだろうと思いますので恐縮なのですが、こちらのエントリで紹介されているプログラムのXcodeプロジェクトを公開いただけますと助かります。よろしくお願いします。

# ウェブで検索してもMacOSX固有の開発関連はあまりに情報が少ないので驚いているところです。
by maru (2010-03-07 18:26) 

decafish

それは失礼しました。ただ、OpenGLは僕もよく理解してなくてなぜか表示はするのですが正しいやり方かどうかわかりません。このコードを
http://www011.upp.so-net.ne.jp/decafish/CocoaCodes/DVStreamViewer5.zip
に置きます。どこまでご参考になるかわかりませんが。
QTKit Captureではメインスレッドで入力デバイスごとに複数のストリームを受けられるようになっている(よけいなスレッドは勝手にできますが)らしいのですが、例えば3Dにしようとしたらフレームの同期を取るための機能(タイムスタンプ比較とか)などは見当たりません。
なにか面白いことがわかったらまたお教えください。
よろしくお願いします。
by decafish (2010-03-07 20:32) 

maru

たびたびありがとうございます。

ビルド・動作確認するところまでできました。このコードを精査して調査を進めていきたいと思います。取っ掛かりがなくて困っていたところなのでとてもありがたいです。また何かありましたらよろしくお願いします。

by maru (2010-03-07 22:44) 

イグナチオ

ARの研究をしているイグナチオです。GLUTからMacのネイティブのプログラムを書こうとしていて CoreVideoの大変勉強になりました。私はOpenGLを使って様々なオブジェクトを表示したいと考えているので、CoreView+NSOpenGLViewの組み合わせの見本となるプログラムを探していました。
大変感謝しています。ありがとうございました。
私も無事にビルドし実行することができました。
(私の場合QTCaptureDeviceInputのcaptureAudioDeviceInputは不要で コメントにしました。そしてQTMediaTypeMuxedをQTMediaTypeVideoに変更することで実行できました。)

by イグナチオ (2010-04-09 17:10) 

decafish

コメントありがとうございます。
お役に立ててうれしいです。
ただ、僕はOpenGLのほうをちゃんとわかってなくて、コードはとりあえず動きますが何かするとすぐクラッシュしてしまいますし....
お恥ずかしい限りです。
by decafish (2010-04-10 05:54) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立0905献立090/06 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。