0

ディスクベースの昔ながらの AVI アニメーション (A、B、C...) のシーケンスを連続して実行しようとしています。

確かに、しばらくの間スレッド化作業を行っておらず、NSOperationorで何もしたことがないことは確かですが、少しのガイダンスを探していGCDます。

これらのアニメーションは 30 fps で実行され、通常は 1 分未満の長さでCoreImage、その間に アシスト付きのトランジションがあります。タイミング的にはかなりタイトなので、マルチスレッドが必要です。ディスク アクセスに SSD を使用しているため、読み取り速度は (理論的には) 消費速度の約 2 倍ですが、読み取り後処理と表示前処理のかなりの部分が発生し、プロセス全体に遅れが生じます。シングルスレッド -- 言うまでもなく、それは本当に悪い考えです。

フローは次のとおりです。まず、アニメーション A の開始フレームを読み取ります (おそらくNSOperation、これらの読み取りにはシリアル キューを使用します)。オブジェクトの中に生データがNSMutableArrayあります。次に、読み取られたフレームごとに、配列内のデータをCoreImageフォーマットに変換します (類似しているが別個のシリアル「レンダリング」キューを使用するか、ディスクから読み取られた各フレームの完了ハンドラーとして)。

(しわ: アニメーションが AVI 形式でない場合は、代わりに and を使用AVAssetImageGeneratorして、generateCGImagesAsynchronouslyForTimesレンダリング結果を生成します)。

このプロセスをプロデューサーのようなキューとしてファイル全体で続行し、データが読み込まれて変換されてから 2 ~ 3 秒後にスロットルを戻します。結果として得られるイメージ データの配列を、境界のある循環バッファーとして扱います。

レンダリングされたキューから項目をプルする別のコンシューマー キュー (CVDisplaylink垂直ブランキング呼び出し) があります。これは 60hz のメイン スレッドになります。30fps のスループットを得るために、オフサイクルでレンダリング イメージを描画し、偶数サイクルでスワップします。

アニメーション「A」がスムーズに実行されていることを確認したら (たとえば、5 秒後)、さらに別のシリアル キューをスピンアップして、次のトランジションのフレームのペアリングを開始します...アニメーション「A」に「n」個のフレームがある場合"、次に A のフレーム n-15 (最後から 0.5 秒) を読み取り、それをアニメーション "B" の最初のフレームと一致させ、これらの 2 つのフレームを に送信してCoreImage、 経由で遷移させCIFilterます。フレーム n-14 (A) とフレーム 2 (B) のマッチングを続行します。明らかに、これらのフレーム読み取りごとに、変換と、別のデータ構造への保存が必要になります。これにより、次の 1/2 秒のトランジションが作成されます。

アニメーション トランジションを表示するときが来たら、これらのトランジション フレームにサブして表示し、残りのアニメーション B の表示を続けます... トランジション用にアニメーション C をスピンアップします。

どこから始めるべきかについての指針はありますか?

4

1 に答える 1

1

あなたの状況は、 Apple のドキュメントで概説されている状況よりも少し複雑ですが、意図したパターンを理解するために、それを読むと役に立ちます (そして、それを読んだ後にまだ「え?」と言っている場合は、このSO の回答を読んでください)。 . 要するに、一般的な考え方は、プロデューサーがチェーンを「駆動」し、OS の GCD のフックが、カーネル内のさまざまなものの状態に基づいて、物事が適切にディスパッチされていることを確認するのに役立ちます。

ここでの問題に関するそのアプローチの問題は、消費者が消費可能なリソースの可用性だけではなく、垂直ブランキングコールバックによってリアルタイムで駆動されるため、プロデューサー側にここで物事を駆動させるのは簡単ではないことです. このケースは、ワークフローの本質的にシリアルな性質によってさらに複雑になります。たとえば、フレーム データのデコードを画像に理論的に並列化できたとしても、画像はパイプラインの次のステージにシリアルで配信する必要があります。ストリーミングのケースで GCD API によってうまく処理されないケース (つまり、一度にすべてをメモリに格納できれば簡単に処理dispatch_applyできますが、問題の核心に切り込みます: 準ストリーミングでこれを行う必要があります)環境。)

これをどのように処理するかを考えようとして、ファイルの各行がビデオの「フレーム」であり、2 つを「クロスフェード」するテキスト ファイルを使用して状況をシミュレートしようとする次の例を思いつきました。文字列を追加してクリップします。これの完全に機能する (少なくとも私にとっては) バージョンは、こちらから入手できます。このコードは、GCD プリミティブのみを使用し、(大部分) プロデューサー主導のパターンを使用しながら、CVDisplayLinkベースのコンシューマーとリンクしながら、このような処理パイプラインを設計する方法を示すことを目的としています。

それは防弾ではありません(つまり、オーバーラップするのに必要なフレーム数よりも少ないフレームを持つファイルには寛容ではありません)、リアルタイムまたはメモリ使用の境界要件に完全に対処できない可能性があります(これは私には困難です)私がやりたい以上の作業をせずにレプリケートしてテストします. :) ) また、次の前に再シリアル化する必要があるワークロードを並列化できる可能性があるという、上記の問題に対処しようとしません。パイプラインの段階。(コードも ARC を前提としています。) これらすべての注意事項がありますが、興味深い/関連するアイデアがまだいくつかあることを願っています。コードは次のとおりです。

static void DieOnError(int error);
static NSString* NSStringFromDispatchData(dispatch_data_t data);
static dispatch_data_t FrameDataFromAccumulator(dispatch_data_t* accumulator);
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext);

static const NSUInteger kFramesToOverlap = 15;

@implementation SOAppDelegate
{
    // Display link state
    CVDisplayLinkRef mDisplayLink;

    // State for our file reading process -- protected via mFrameReadQueue
    dispatch_queue_t mFrameReadQueue;
    NSUInteger mFileIndex; // keep track of what file we're reading
    dispatch_io_t mReadingChannel; // channel for reading
    dispatch_data_t mFrameReadAccumulator; // keep track of left-over data across read operations

    // State for processing raw frame data delivered by the read process - protected via mFrameDataProcessingQueue
    dispatch_queue_t mFrameDataProcessingQueue;
    NSMutableArray* mFilesForOverlapping;
    NSMutableArray* mFrameArraysForOverlapping;

    // State for blending frames (or passing them through)
    dispatch_queue_t mFrameBlendingQueue;

    // Delivery state
    dispatch_queue_t mFrameDeliveryQueue; // Is suspended/resumed to deliver one frame at a time
    dispatch_queue_t mFrameDeliveryStateQueue; // Protects access to the iVars
    dispatch_data_t mDeliveredFrame; // Data of the frame that has been delivered, but not yet picked up by the CVDisplayLink
    NSInteger mLastFrameDelivered; // Counter of frames delivered
    NSInteger mLastFrameDisplayed; // Counter of frames displayed
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    mFileIndex = 1;
    mLastFrameDelivered = -1;
    mLastFrameDisplayed = -1;

    mFrameReadQueue = dispatch_queue_create("mFrameReadQueue", DISPATCH_QUEUE_SERIAL);
    mFrameDataProcessingQueue = dispatch_queue_create("mFrameDataProcessingQueue", DISPATCH_QUEUE_SERIAL);
    mFrameBlendingQueue = dispatch_queue_create("mFrameBlendingQueue", DISPATCH_QUEUE_SERIAL);
    mFrameDeliveryQueue = dispatch_queue_create("mFrameDeliveryQueue", DISPATCH_QUEUE_SERIAL);
    mFrameDeliveryStateQueue = dispatch_queue_create("mFrameDeliveryStateQueue", DISPATCH_QUEUE_SERIAL);

    CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink);
    CVDisplayLinkSetOutputCallback(mDisplayLink, &MyDisplayLinkCallback, (__bridge void*)self);

    [self readNextFile];
}

- (void)dealloc
{
    if (mDisplayLink)
    {
        if (CVDisplayLinkIsRunning(mDisplayLink))
        {
            CVDisplayLinkStop(mDisplayLink);
        }
        CVDisplayLinkRelease(mDisplayLink);
    }
}

- (void)readNextFile
{
    dispatch_async (mFrameReadQueue, ^{
        NSURL* url = [[NSBundle mainBundle] URLForResource: [NSString stringWithFormat: @"File%lu", mFileIndex++] withExtension: @"txt"];

        if (!url)
            return;

        if (mReadingChannel)
        {
            dispatch_io_close(mReadingChannel, DISPATCH_IO_STOP);
            mReadingChannel = nil;
        }

        // We don't care what queue the cleanup handler gets called on, because we know there's only ever one file being read at a time
        mReadingChannel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, [[url path] fileSystemRepresentation], O_RDONLY|O_NONBLOCK, 0, mFrameReadQueue, ^(int error) {
            DieOnError(error);

            mReadingChannel = nil;

            // Start the next file
            [self readNextFile];
        });

        // We don't care what queue the read handlers get called on, because we know they're inherently serial
        dispatch_io_read(mReadingChannel, 0, SIZE_MAX, mFrameReadQueue, ^(bool done, dispatch_data_t data, int error) {
            DieOnError(error);

            // Grab frames
            dispatch_data_t localAccumulator = mFrameReadAccumulator ? dispatch_data_create_concat(mFrameReadAccumulator, data) : data;
            dispatch_data_t frameData = nil;
            do
            {
                frameData = FrameDataFromAccumulator(&localAccumulator);
                mFrameReadAccumulator = localAccumulator;
                [self processFrameData: frameData fromFile: url];
            } while (frameData);

            if (done)
            {
                dispatch_io_close(mReadingChannel, DISPATCH_IO_STOP);
            }
        });
    });
}

- (void)processFrameData: (dispatch_data_t)frameData fromFile: (NSURL*)file
{
    if (!frameData || !file)
        return;

    // We want the data blobs constituting each frame to be processed serially
    dispatch_async(mFrameDataProcessingQueue, ^{
        mFilesForOverlapping = mFilesForOverlapping ?: [NSMutableArray array];
        mFrameArraysForOverlapping = mFrameArraysForOverlapping ?: [NSMutableArray array];

        NSMutableArray* arrayToAddTo = nil;
        if ([file isEqual: mFilesForOverlapping.lastObject])
        {
            arrayToAddTo = mFrameArraysForOverlapping.lastObject;
        }
        else
        {
            arrayToAddTo = [NSMutableArray array];
            [mFilesForOverlapping addObject: file];
            [mFrameArraysForOverlapping addObject: arrayToAddTo];
        }

        [arrayToAddTo addObject: frameData];

        // We've gotten to file two, and we have enough frames to process the overlap
        if (mFrameArraysForOverlapping.count == 2 && [mFrameArraysForOverlapping[1] count] >= kFramesToOverlap)
        {
            NSMutableArray* fileOneFrames = mFrameArraysForOverlapping[0];
            NSMutableArray* fileTwoFrames = mFrameArraysForOverlapping[1];

            for (NSUInteger i = 0; i < kFramesToOverlap; ++i)
            {
                [self blendOneFrame:fileOneFrames[0] withOtherFrame: fileTwoFrames[0]];
                [fileOneFrames removeObjectAtIndex:0];
                [fileTwoFrames removeObjectAtIndex:0];
            }

            [mFilesForOverlapping removeObjectAtIndex: 0];
            [mFrameArraysForOverlapping removeObjectAtIndex: 0];
        }

        // We're pulling in frames from file 1, haven't gotten to file 2 yet, have more than enough to overlap
        while (mFrameArraysForOverlapping.count == 1 && [mFrameArraysForOverlapping[0] count] > kFramesToOverlap)
        {
            NSMutableArray* frameArray = mFrameArraysForOverlapping[0];
            dispatch_data_t first = frameArray[0];
            [mFrameArraysForOverlapping[0] removeObjectAtIndex: 0];
            [self blendOneFrame: first withOtherFrame: nil];
        }
    });
}

- (void)blendOneFrame: (dispatch_data_t)frameA withOtherFrame: (dispatch_data_t)frameB
{
    dispatch_async(mFrameBlendingQueue, ^{
        NSString* blendedFrame = [NSString stringWithFormat: @"%@%@", [NSStringFromDispatchData(frameA) stringByReplacingOccurrencesOfString: @"\n" withString:@""], NSStringFromDispatchData(frameB)];
        dispatch_data_t blendedFrameData = dispatch_data_create(blendedFrame.UTF8String, blendedFrame.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
        [self deliverFrameForDisplay: blendedFrameData];
    });
}

- (void)deliverFrameForDisplay: (dispatch_data_t)frame
{
    // By suspending the queue from within the block, and by virtue of this being a serial queue, we guarantee that
    // only one task will get called for each call to dispatch_resume on the queue...

    dispatch_async(mFrameDeliveryQueue, ^{
        dispatch_suspend(mFrameDeliveryQueue);
        dispatch_sync(mFrameDeliveryStateQueue, ^{
            mLastFrameDelivered++;
            mDeliveredFrame = frame;
        });

        if (!CVDisplayLinkIsRunning(mDisplayLink))
        {
            CVDisplayLinkStart(mDisplayLink);
        }
    });
}

- (dispatch_data_t)getFrameForDisplay
{
    __block dispatch_data_t frameData = nil;
    dispatch_sync(mFrameDeliveryStateQueue, ^{
        if (mLastFrameDelivered > mLastFrameDisplayed)
        {
            frameData = mDeliveredFrame;
            mDeliveredFrame = nil;
            mLastFrameDisplayed = mLastFrameDelivered;
        }
    });

    // At this point, I've either got the next frame or I dont...
    // resume the delivery queue so it will deliver the next frame
    if (frameData)
    {
        dispatch_resume(mFrameDeliveryQueue);
    }

    return frameData;
}

@end

static void DieOnError(int error)
{
    if (error)
    {
        NSLog(@"Error in %s: %s", __PRETTY_FUNCTION__, strerror(error));
        exit(error);
    }
}

static NSString* NSStringFromDispatchData(dispatch_data_t data)
{
    if (!data || !dispatch_data_get_size(data))
        return @"";

    const char* buf = NULL;
    size_t size = 0;
    dispatch_data_t notUsed = dispatch_data_create_map(data, (const void**)&buf, &size);
#pragma unused(notUsed)
    NSString* str = [[NSString alloc] initWithBytes: buf length: size encoding: NSUTF8StringEncoding];
    return str;
}

// Peel off a frame if there is one, and put the left-overs back.
static dispatch_data_t FrameDataFromAccumulator(dispatch_data_t* accumulator)
{
    __block dispatch_data_t frameData = dispatch_data_create(NULL, 0, NULL, NULL); // empty
    __block dispatch_data_t leftOver = dispatch_data_create(NULL, 0, NULL, NULL); // empty

    __block BOOL didFindFrame = NO;
    dispatch_data_apply(*accumulator, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
        ssize_t newline = -1;
        for (size_t i = 0; !didFindFrame && i < size; ++i)
        {
            if (((const char *)buffer)[i] == '\n')
            {
                newline = i;
                break;
            }
        }

        if (newline == -1)
        {
            if (!didFindFrame)
            {
                frameData = dispatch_data_create_concat(frameData, region);
            }
            else
            {
                leftOver = dispatch_data_create_concat(leftOver, region);
            }
        }
        else if (newline >= 0)
        {
            didFindFrame = YES;
            frameData = dispatch_data_create_concat(frameData, dispatch_data_create_subrange(region, 0, newline + 1));
            leftOver = dispatch_data_create_concat(leftOver, dispatch_data_create_subrange(region, newline + 1, size - newline - 1));
        }

        return true;
    });

    *accumulator = leftOver;

    return didFindFrame ? frameData : nil;
}

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
    SOAppDelegate* self = (__bridge SOAppDelegate*)displayLinkContext;

    dispatch_data_t frameData = [self getFrameForDisplay];

    NSString* dataAsString = NSStringFromDispatchData(frameData);

    if (dataAsString.length == 0)
    {
        NSLog(@"Dropped frame...");
    }
    else
    {
        NSLog(@"Drawing frame in CVDisplayLink. Contents: %@", dataAsString);
    }

    return kCVReturnSuccess;
}

理論的には、GCD はこれらのキューのバランスを取ることになっています。たとえば、「プロデューサー」キューの処理を許可するとメモリ使用量が増加する場合、GCD は (理論的には) 他のキューを解放し始め、プロデューサー キューを保持します。実際には、このメカニズムは私たちには不透明であるため、実際の状況下で、特にリアルタイムの制限に直面して、それがどれだけうまく機能するかは誰にもわかりません.

ここで不明な点がある場合は、コメントを投稿してください。詳しく説明します。

于 2013-08-21T01:49:41.647 に答える