12

iOS ハードウェア アクセラレーションによる h.264 デコーディング API を使用して、ローカルの (ストリーミングされていない) ビデオ ファイルをデコードし、その上に他のオブジェクトを作成することは可能であり、サポートされていますか?

ビデオの前にグラフィック オブジェクトを描画するアプリケーションを作成し、再生タイマーを使用して、上に描画しているものとビデオで再生されているものを同期させたいと考えています。次に、ユーザーのアクションに基づいて、上に描画しているものを変更します (ただし、ビデオは変更しません)。

Android 用の DirectX、OpenGL、および OpenGL ES から来て、ビデオをテクスチャにレンダリングし、そのテクスチャを使用してフルスクリーンのクワッドを描画し、他のスプライトを使用して残りのオブジェクトを描画するようなものを描いています。または、レンダラーの直前に中間フィルターを作成して、個々の出力フレームを操作して自分のものを描画できるようにすることもできます。または、ビデオの上にある 2D レイヤーに描画することもできます。

AV Foundation や Core Mediaが私がやっていることを手助けしてくれるようですが、詳細を掘り下げる前に、私がやりたいことを行うことが可能かどうか、そして私の主なことは何かを知りたいと思います。問題にアプローチするルート。

「これはあなたには高度すぎます。最初に Hello World を試してください」という回答はご遠慮ください。私は自分のことを知っていて、自分で詳細を調べる前に、やりたいことが可能かどうかを知りたいだけです (そして、最も重要なのは、サポートされているため、アプリが最終的に拒否されることはありません)。

編集:

私は iOS 開発の知識はありませんが、Android 向けの DirectX、OpenGL、および OpenGL ES を専門的に扱っています。現在持っている Android アプリケーションの iOS バージョンを作成することを検討していますが、これが可能かどうかを知りたいだけです。もしそうなら、iOS 開発をゼロから始めて、自分がやりたいことを実行するのに十分な時間があります。それが不可能な場合は、現時点でプラットフォーム全体の調査に時間を割くつもりはありません。

したがって、これは技術的な実現可能性の問題です。私はコードを要求していません。「はい、できます。A と B を使用し、C を使用して D にレンダリングし、E で描画します」または「いいえ、できません。ハードウェア アクセラレーションによるデコードはサードパーティのアプリケーションでは利用できません」(これは友人が私に言ったことです)。これだけで、私は行くつもりです。

iOS テクノロジーの概要の 32 ページにあるビデオ テクノロジーの概要を読みました。Media Player は最も単純な再生機能 (私が探しているものではありません) に使用でき、UIKit はビデオの埋め込みに使用でき、埋め込みをもう少し制御できますが、実際の再生には使用できません (私が求めているものではありません)。私が探している)、再生をより詳細に制御するための AVFoundation(おそらく必要なものですが、オンラインで見つけたリソースのほとんどはカメラの使用方法について説明しています)、またはビデオを完全に低レベルで制御するための Core Media(おそらく私が必要ですが、文書化が非常に不十分であり、AVFoundation よりも再生に関するリソースがさらに不足しています)。

iOS プログラミングをフルタイムで学習するために次の 6 か月を費やしても、最終的にサード パーティの開発者が関連する API を利用できず、私がやりたいことは iTunes ストアでの展開には受け入れられないことが判明するのではないかと心配しています。これは私の友人が私に言ったことですが、アプリ開発ガイドラインに関連するものを見つけることができないようです. そのため、この分野での経験が豊富な人に、私がやりたいことが可能かどうかを尋ねるためにここに来ました。もういや。

これは有効な高レベルの質問だと思いますが、I-didn't-do-my-homework-plz-give-me-teh-codez の質問と誤解される可能性があります。ここでの私の判断が間違っていた場合は、遠慮なく削除するか、この質問を心から軽蔑してください。

4

1 に答える 1

25

はい、これを行うことができます。あなたの質問は、ここに属するのに十分具体的だったと思います。これをやりたいと思っているのはあなただけではありません。できることとできないことを理解するには、少し掘り下げる必要があります。

AV Foundation を使用すると、AVAssetReader を使用して H.264 ビデオのハードウェア アクセラレーションによるデコードを実行できます。この時点で、デコードされた生のビデオ フレームが BGRA 形式で渡されます。glTexImage2D()これらは、iOS 5.0 のより効率的なテクスチャ キャッシュを使用してテクスチャにアップロードできます。そこから、OpenGL ES からフレームを表示または取得するために処理し、AVAssetWriter を使用して結果のハードウェア アクセラレーションによる H.264 エンコーディングを実行できます。これらはすべてパブリック API を使用しているため、App Store からの拒否につながるようなものに近づくことは決してありません。

ただし、これを独自に実装する必要はありません。私の BSD ライセンスのオープン ソース フレームワークGPUImageは、これらの操作をカプセル化し、これらすべてを処理します。入力 H.264 ムービーの GPUImageMovie インスタンスを作成し、それにフィルターをアタッチ (オーバーレイ ブレンドやクロマ キー操作など) してから、これらのフィルターを表示用の GPUImageView および/または GPUImageMovieWriter にアタッチして、H.264 ムービーを再エンコードします。加工した動画から264動画。

私が現在抱えている問題の 1 つは、再生時にビデオのタイムスタンプに従わないことです。そのため、フレームは映画からデコードされるのと同じくらい速く処理されます。ビデオのフィルタリングと再エンコードの場合、タイムスタンプがレコーダーに渡されるため、これは問題ではありませんが、画面に直接表示する場合、これはビデオを 2 ~ 4 倍高速化できることを意味します。 . 再生レートを実際のビデオのタイムスタンプに同期できるような貢献を歓迎します。

現在、iPhone 4 では 30 FPS をはるかに超える速度で 640x480 ビデオを再生、フィルタリング、および再エンコードできます。また、iPhone 4S では 1080p のフィルタリングと 30 FPS を大幅に超えるエンコードが可能で、720p ビデオは ~20 ~ 25 FPS で再生できます。 . より高価なフィルターの中には、GPU に負担をかけ、これを少し遅くするものもありますが、ほとんどのフィルターはこれらのフレームレート範囲で動作します。

必要に応じて、GPUImageMovie クラスを調べて、この OpenGL ES へのアップロードがどのように行われるかを確認できますが、関連するコードは次のとおりです。

- (void)startProcessing;
{
    NSDictionary *inputOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
    AVURLAsset *inputAsset = [[AVURLAsset alloc] initWithURL:self.url options:inputOptions];

    [inputAsset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"tracks"] completionHandler: ^{
        NSError *error = nil;
        AVKeyValueStatus tracksStatus = [inputAsset statusOfValueForKey:@"tracks" error:&error];
        if (!tracksStatus == AVKeyValueStatusLoaded) 
        {
            return;
        }
        reader = [AVAssetReader assetReaderWithAsset:inputAsset error:&error];

        NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary];
        [outputSettings setObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]  forKey: (NSString*)kCVPixelBufferPixelFormatTypeKey];
        // Maybe set alwaysCopiesSampleData to NO on iOS 5.0 for faster video decoding
        AVAssetReaderTrackOutput *readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[inputAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings];
        [reader addOutput:readerVideoTrackOutput];

        NSArray *audioTracks = [inputAsset tracksWithMediaType:AVMediaTypeAudio];
        BOOL shouldRecordAudioTrack = (([audioTracks count] > 0) && (self.audioEncodingTarget != nil) );
        AVAssetReaderTrackOutput *readerAudioTrackOutput = nil;

        if (shouldRecordAudioTrack)
        {            
            audioEncodingIsFinished = NO;

            // This might need to be extended to handle movies with more than one audio track
            AVAssetTrack* audioTrack = [audioTracks objectAtIndex:0];
            readerAudioTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:nil];
            [reader addOutput:readerAudioTrackOutput];
        }

        if ([reader startReading] == NO) 
        {
            NSLog(@"Error reading from file at URL: %@", self.url);
            return;
        }

        if (synchronizedMovieWriter != nil)
        {
            __unsafe_unretained GPUImageMovie *weakSelf = self;

            [synchronizedMovieWriter setVideoInputReadyCallback:^{
                [weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput];
            }];

            [synchronizedMovieWriter setAudioInputReadyCallback:^{
                [weakSelf readNextAudioSampleFromOutput:readerAudioTrackOutput];
            }];

            [synchronizedMovieWriter enableSynchronizationCallbacks];
        }
        else
        {
            while (reader.status == AVAssetReaderStatusReading) 
            {
                [self readNextVideoFrameFromOutput:readerVideoTrackOutput];

                if ( (shouldRecordAudioTrack) && (!audioEncodingIsFinished) )
                {
                    [self readNextAudioSampleFromOutput:readerAudioTrackOutput];
                }

            }            

            if (reader.status == AVAssetWriterStatusCompleted) {
                [self endProcessing];
            }
        }
    }];
}

- (void)readNextVideoFrameFromOutput:(AVAssetReaderTrackOutput *)readerVideoTrackOutput;
{
    if (reader.status == AVAssetReaderStatusReading)
    {
        CMSampleBufferRef sampleBufferRef = [readerVideoTrackOutput copyNextSampleBuffer];
        if (sampleBufferRef) 
        {
            runOnMainQueueWithoutDeadlocking(^{
                [self processMovieFrame:sampleBufferRef]; 
            });

            CMSampleBufferInvalidate(sampleBufferRef);
            CFRelease(sampleBufferRef);
        }
        else
        {
            videoEncodingIsFinished = YES;
            [self endProcessing];
        }
    }
    else if (synchronizedMovieWriter != nil)
    {
        if (reader.status == AVAssetWriterStatusCompleted) 
        {
            [self endProcessing];
        }
    }
}

- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer; 
{
    CMTime currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(movieSampleBuffer);
    CVImageBufferRef movieFrame = CMSampleBufferGetImageBuffer(movieSampleBuffer);

    int bufferHeight = CVPixelBufferGetHeight(movieFrame);
    int bufferWidth = CVPixelBufferGetWidth(movieFrame);

    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

    if ([GPUImageOpenGLESContext supportsFastTextureUpload])
    {
        CVPixelBufferLockBaseAddress(movieFrame, 0);

        [GPUImageOpenGLESContext useImageProcessingContext];
        CVOpenGLESTextureRef texture = NULL;
        CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, coreVideoTextureCache, movieFrame, NULL, GL_TEXTURE_2D, GL_RGBA, bufferWidth, bufferHeight, GL_BGRA, GL_UNSIGNED_BYTE, 0, &texture);

        if (!texture || err) {
            NSLog(@"Movie CVOpenGLESTextureCacheCreateTextureFromImage failed (error: %d)", err);  
            return;
        }

        outputTexture = CVOpenGLESTextureGetName(texture);
        //        glBindTexture(CVOpenGLESTextureGetTarget(texture), outputTexture);
        glBindTexture(GL_TEXTURE_2D, outputTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        for (id<GPUImageInput> currentTarget in targets)
        {            
            NSInteger indexOfObject = [targets indexOfObject:currentTarget];
            NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];

            [currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex];
            [currentTarget setInputTexture:outputTexture atIndex:targetTextureIndex];

            [currentTarget newFrameReadyAtTime:currentSampleTime];
        }

        CVPixelBufferUnlockBaseAddress(movieFrame, 0);

        // Flush the CVOpenGLESTexture cache and release the texture
        CVOpenGLESTextureCacheFlush(coreVideoTextureCache, 0);
        CFRelease(texture);
        outputTexture = 0;        
    }
    else
    {
        // Upload to texture
        CVPixelBufferLockBaseAddress(movieFrame, 0);

        glBindTexture(GL_TEXTURE_2D, outputTexture);
        // Using BGRA extension to pull in video frame data directly
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(movieFrame));

        CGSize currentSize = CGSizeMake(bufferWidth, bufferHeight);
        for (id<GPUImageInput> currentTarget in targets)
        {
            NSInteger indexOfObject = [targets indexOfObject:currentTarget];
            NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];

            [currentTarget setInputSize:currentSize atIndex:targetTextureIndex];
            [currentTarget newFrameReadyAtTime:currentSampleTime];
        }
        CVPixelBufferUnlockBaseAddress(movieFrame, 0);
    }

    if (_runBenchmark)
    {
        CFAbsoluteTime currentFrameTime = (CFAbsoluteTimeGetCurrent() - startTime);
        NSLog(@"Current frame time : %f ms", 1000.0 * currentFrameTime);
    }
}
于 2012-05-18T16:30:48.877 に答える