46

画面の一部でビデオを再生する必要がある iPad 用の iOS アプリを開発しています。コンパイル時に指定されていない順序で次々に再生する必要があるビデオ ファイルがいくつかあります。1 つのビデオが再生されているように見える必要があります。あるビデオから次のビデオに移動するときに、2 つのビデオの最後のフレームまたは最初のフレームが表示されるときに多少の遅延が発生することは問題ありませんが、ちらつきやコンテンツのない白い画面があってはなりません。ビデオには音声が含まれていません。メモリ使用量を考慮することが重要です。ビデオの解像度は非常に高く、複数の異なるビデオ シーケンスを同時に並べて再生できます。

これを得るために、私はこれまでいくつかの解決策を試してきました。以下にそれらを示します。

1.すべてのビデオを含むAVCompositionを備えたAVPlayer

このソリューションでは、隣り合わせに配置されたすべてのビデオを含む AVComposition を使用して作成された AVPlayerItem でのみ使用する AVPlayer があります。特定のビデオに移動するとき、コンポジション内で次のビデオが始まる時間をシークします。このソリューションの問題は、プレーヤーをシークするときに、シークしているフレームの一部がすぐに表示されることです。これは受け入れられません。コンポジションの特定の時間に直接ジャンプする方法はないようです。ちょうど終了したビデオの最後のフレームの画像を作成し、シーク中にAVPLayerの前に表示し、シークが完了した後に最後に削除することで、これを解決しようとしました。AVAssetImageGeneratorを使って画像を作っているのですが、なぜか画質が動画と同じではなく、そのため、ビデオの上に画像を表示したり隠したりすると、顕著な変化があります。もう 1 つの問題は、単一の AVPlayerItem がすべてのビデオを保持するため、AVPlayer が大量のメモリを使用することです。

2. 複数の AVPlayerItem を持つ AVPlayer

このソリューションは、各ビデオに AVPlayerItem を使用し、新しいビデオに切り替えるときに AVPlayer のアイテムを置き換えます。これに伴う問題は、AVPlayer のアイテムを切り替えると、新しいアイテムをロードしている間、短時間白い画面が表示されることです。これを修正するには、ロード中に最後のフレームの前に画像を配置するソリューションを使用できますが、それでも画像とビデオの品質が異なり、注目に値するという問題があります。

3. AVPlayerItem を順番に再生する互いの上にある 2 つの AVPlayer

私が試した次の解決策は、AVPlayerItem を交互に再生する 2 つの AVPlayer を重ね合わせることでした。そのため、プレーヤーの再生が完了すると、ビデオの最後のフレームにとどまります。もう一方の AVPlayer が前面に表示され (項目が nil に設定されているため、透明になります)、次の AVPlayerItem がその AVPlayer に挿入されます。ロードされるとすぐに再生が開始され、2 つのビデオ間のスムーズなトランザクションの錯覚は損なわれません。このソリューションの問題は、メモリ使用量です。場合によっては、画面上で同時に 2 つのビデオを再生する必要があります。その結果、AVPlayerItem がロードされた 4 つの AVPlayer が同時に発生します。ビデオは非常に高解像度になる可能性があるため、これは単純にメモリが多すぎます。


全体的な問題と上記の解決策に関する考え、提案、コメント、または何かがある人はいますか。

4

3 に答える 3

53

このプロジェクトは現在 App Store で公開されており、このスレッドに戻って調査結果を共有し、最終的に何をしたかを明らかにする時が来ました。

うまくいかなかったこと

すべてのビデオを含む大きな AVComposition で使用した最初のオプションは、コンポジション内の特定の時間にジャンプする方法がなく、小さなスクラブ グリッチが発生しなかったため、十分ではありませんでした。さらに、API が一時停止のフレーム保証を提供できなかったため、コンポジション内の 2 つのビデオの間で正確にビデオを一時停止することに問題がありました。

2 つの AVPlayer を持ち、順番に実行できる 3 つ目は、実際にはうまく機能しました。特に iPad 4 や iPhone 5 では。RAM 容量の少ないデバイスでは問題がありました。メモリ内に複数のビデオを同時に保存すると、メモリが大量に消費されるためです。特に、非常に高解像度のビデオを扱う必要があったためです。

私がやったこと

さて、残りはオプション番号 2 でした。必要に応じてビデオの AVPlayerItem を作成し、それを AVPlayer にフィードします。このソリューションの良い点は、メモリ消費量です。AVPlayerItems を怠惰に作成し、不要になった瞬間に破棄することで、メモリ消費を最小限に抑えることができました。これは、RAM が限られている古いデバイスをサポートするために非常に重要でした。このソリューションの問題は、あるビデオから次のビデオに移動するときに、次のビデオがメモリにロードされている間、すぐに空白の画面が表示されることでした。これを修正する私の考えは、プレーヤーがバッファリングしているときに表示される画像を A​​VPlayer の背後に置くことでした。ビデオと完全に一致するピクセル単位の画像が必要であることはわかっていたので、ビデオの最後のフレームと最初のフレームの正確なコピーである画像をキャプチャしました。

このソリューションの問題

ビデオ/画像がネイティブサイズまたはモジュール4のスケーリングでない場合、UIImageView内の画像の位置がAVPlayer内のビデオの位置と同じではないという問題がありました。つまり、UIImageView と AVPlayer で半分のピクセルを処理する方法に問題がありました。同じようには見えませんでした。

どのように修正したか

私のアプリケーションは、さまざまなサイズで表示されるインタラクティブな方法でビデオを使用していたため、多くのことを試しました。AVPlayerLayer と CALayer の倍率フィルタと縮小フィルタを同じアルゴリズムを使用するように変更しようとしましたが、実際には何も変更されませんでした。最終的に、必要なすべてのサイズでビデオのスクリーンショットを自動的に取得し、ビデオが特定のサイズにスケーリングされたときに適切な画像を使用できる iPad アプリを作成することになりました。これにより、特定のビデオを表示していたすべてのサイズでピクセル パーフェクトな画像が得られました。完璧なツールチェーンではありませんが、結果は完璧でした。

最終回想

この位置の問題が私にとって非常に目に見えた (したがって、解決することが非常に重要だった) 主な理由は、私のアプリが再生しているビデオ コンテンツが描画されたアニメーションであり、多くのコンテンツが固定位置にあり、その一部しかないためです。画像が動いています。すべてのコンテンツが 1 ピクセルだけ移動すると、非常に目に見えて醜い不具合が発生します。今年の WWDC で、AVFoundation の専門家である Apple のエンジニアとこの問題について話し合いました。私が彼に問題を紹介したとき、彼の提案は基本的にオプション 3 を使用することでしたが、メモリを消費するためそれは不可能であり、その解決策は既に試していることを説明しました。その観点から、彼は私が適切な解決策を選択したと述べ、ビデオがスケーリングされたときの UIImage/AVPlayer の配置についてバグ レポートを提出するように依頼しました。

于 2013-07-01T19:45:58.990 に答える
18

あなたはすでにこれを見ているかもしれませんが、AVQueuePlayer ドキュメンテーションをチェックアウトしましたか?

キューで再生するように設計されてAVPlayerItemsおり、AVPlayer の直接のサブクラスであるため、同じように使用してください。次のように設定します。

AVPlayerItem *firstItem = [AVPlayerItem playerItemWithURL: firstItemURL];
AVPlayerItem *secondItem = [AVPlayerItem playerItemWithURL: secondItemURL];

AVQueuePlayer *player = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:firstItem, secondItem, nil]];

[player play];

実行時に新しいアイテムをキューに追加したい場合は、次のメソッドを使用してください。

[player insertItem:thirdPlayerItem afterItem:firstPlayerItem];

これがあなたが言及したちらつきの問題を軽減するかどうかをテストしていませんが、これが進むべき道のようです.

于 2013-02-20T14:33:51.790 に答える
1

アップデート — https://youtu.be/7QlaO7WxjGg

例としてコレクション ビューを使用した回答を次に示します。これは一度に 8 を再生します (いかなる種類のメモリ管理も必要ないことに注意してください。ARC を使用することもできます)。

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];

    // Enumerate array of visible cells' indexPaths to find a match
    if ([self.collectionView.indexPathsForVisibleItems
         indexOfObjectPassingTest:^BOOL(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             return (obj.item == indexPath.item);
         }]) dispatch_async(dispatch_get_main_queue(), ^{
             [self drawLayerForPlayerForCell:cell atIndexPath:indexPath];
         });


    return cell;
}

- (void)drawPosterFrameForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    [self.imageManager requestImageForAsset:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item]
                                                          targetSize:AssetGridThumbnailSize
                                                         contentMode:PHImageContentModeAspectFill
                                                             options:nil
                                                       resultHandler:^(UIImage *result, NSDictionary *info) {
                                                           cell.contentView.layer.contents = (__bridge id)result.CGImage;
                                                       }];
}

- (void)drawLayerForPlayerForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.contentView.layer.sublayers = nil;
    [self.imageManager requestPlayerItemForVideo:(PHAsset *)self.assetsFetchResults[indexPath.item] options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            if([[info objectForKey:PHImageResultIsInCloudKey] boolValue]) {
                [self drawPosterFrameForCell:cell atIndexPath:indexPath];
            } else {
                AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:[AVPlayer playerWithPlayerItem:playerItem]];
                [playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
                [playerLayer setBorderColor:[UIColor whiteColor].CGColor];
                [playerLayer setBorderWidth:1.0f];
                [playerLayer setFrame:cell.contentView.layer.bounds];
                [cell.contentView.layer addSublayer:playerLayer];
                [playerLayer.player play];
            }
        });
    }];
}

drawPosterFrameForCell メソッドは、デバイスではなく iCloud に保存されているため、動画を再生できない場所に画像を配置します。

とにかく、これが出発点です。これがどのように機能するかを理解すれば、あなたが説明したメモリに関するグリッチなしで、あなたが望むすべてのことを行うことができます.

于 2016-06-15T10:48:04.457 に答える