メインメニューを開く

Xamarin.Mac/動画から静止画を抽出する

< Xamarin.Mac
2017年6月13日 (火) 01:42時点における103.22.200.204 (トーク)による版
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

Xamarin.Mac上で動画から静止画を抽出したいこともあると思う。 動画のサムネイルを作ったりするのに使える。 動画編集ソフトを実現するのにも使える。

本当は動画からアニメーションGIFを作りたかったが、まだ上手くいっていない。 これを見た人は大至急アニメGIFの作り方を書いてください。

需要を考えるとサムネイルの生成が圧倒的多数を占めると思うので、CocoaのAPIに依存せず、PCL100%なピュアマネージドコードで実装してASP.NETで使えた方が遥かに嬉しいかもしれない。この手の実装は汎用的なはずなのに.NET FrameworkMonoの両方で動くポータブルな実装って少ないよね。

目次

実装1:AVAssetImageGenerator.CopyCGImageAtTime編集

実装の基本形となる方法だと思う。 AVAssetImageGeneratorというイカすクラスがあるようだ。

ただしCopyCGImageAtTimeメソッドはアホみたいに遅い。 変換頻度が高い場合は後述するGenerateCGImagesAsynchronouslyメソッドを使った方がよい。

            // 動画を読み込む
            var asset = new AVUrlAsset(videoFilePath, new AVUrlAssetOptions{} );

            // 増分
            var increment = asset.Duration.TimeScale / framerate;
            // 終端
            var framemax = asset.Duration.Value / increment;

            // イメージジェネレーター
            var generator = new AVAssetImageGenerator(asset);
            generator.RequestedTimeToleranceBefore = CMTime.Zero;
            generator.RequestedTimeToleranceAfter  = CMTime.Zero;

            // 動画のど真ん中の画像(CGImage)を取得する。
            CMTime requestedTime = new CMTime(asset.Duration.Value / 2, asset.Duration.TimeScale);
            CMTime actualTime;
            NSError error;
            var cgImage = generator.CopyCGImageAtTime(requestedTime, out actualTime, out error);

なお、上記のCGImageの保存はC#の拡張メソッドで独自に実装しているものであり、標準では存在しない。 このCGImageをファイルに保存する拡張メソッドの詳細は下記を参照。

実装2: AVAssetImageGenerator.GenerateCGImagesAsynchronously編集

GenerateCGImagesAsynchronouslyはかなり速い。 名称からもわかるように非同期で実行される。

今回はUIを持たないコマンドラインで実験したので非同期実行の終了を待たないと一瞬でアプリが終わってしまう。そのためC#では定番のManualResetEventを使っている。UIを持つアプリであれば処理を一時停止させる必要などないと思われ、インジケーター表示関連の処理に差し替えるなどすると捗ると思われる。

            var framerate = 5f;
                
            // 動画を読み込む
            var asset = new AVUrlAsset(videoFilePath, new AVUrlAssetOptions{} );

            // 増分
            var increment = asset.Duration.TimeScale / framerate;
            // 終端
            var framemax = asset.Duration.Value / increment;

            // イメージジェネレーター
            var generator = new AVAssetImageGenerator(asset);
            generator.RequestedTimeToleranceBefore = CMTime.Zero;
            generator.RequestedTimeToleranceAfter  = CMTime.Zero;

            // 画像を取得する間隔
            var requests = new List<NSValue>();
            for (var count = 0f; count < asset.Duration.Value; count += increment)
            {
                var time = new CMTime((long)count, asset.Duration.TimeScale);
                var val = NSValue.FromCMTime(time);
                requests.Add(val);
            }

            // 待機用
            var wait = new ManualResetEvent(false);
            wait.Reset();

            long framecur = 0;

            // イメージを取得して保存
            generator.GenerateCGImagesAsynchronously(
                requests.ToArray(), 
                new AVAssetImageGeneratorCompletionHandler((requestedTime, imageRef, actualTime, result, error) => {
                    var cgImage = new CGImage(imageRef);

                    var savePath = string.Format("/tmp/img_{0:D8}.png", requestedTime.Value);
                    cgImage.Save(savePath, MobileCoreServices.UTType.PNG);

                    Console.WriteLine(requestedTime + "/" + actualTime);

                    framecur++;

                    if (framemax <= framecur)
                    {
                        // おわり
                        wait.Set();
                    }
                })
            );

            // 終わるのを待つ
            wait.WaitOne();

関連項目編集

参考文献編集